Compare commits
28 Commits
fix/pylint
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d57f57bd50 | ||
|
|
8d9bb883cf | ||
|
|
233faaa638 | ||
|
|
9c0da926a0 | ||
|
|
b02e93a5bb | ||
|
|
493887f794 | ||
|
|
d93edbaa10 | ||
|
|
2433975916 | ||
|
|
c31a3718d4 | ||
|
|
e3b9a5e660 | ||
|
|
6c32d16acb | ||
|
|
bdcdbddebe | ||
|
|
f7b5667883 | ||
|
|
ee25eb6ed9 | ||
|
|
36b18b2674 | ||
|
|
5d80d415fc | ||
|
|
900dc1fdb7 | ||
|
|
97b2c8068b | ||
|
|
84b25710bb | ||
|
|
6f082c08f7 | ||
|
|
53bb419566 | ||
|
|
f51e0bdadc | ||
|
|
9560962aa1 | ||
|
|
c79b9ba5d7 | ||
|
|
9cf456ddc1 | ||
|
|
e7fdc51e8d | ||
|
|
acbe3c0783 | ||
|
|
3304e6f123 |
38
.github/workflows/django.yml
vendored
Normal file
38
.github/workflows/django.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
|
||||
EMAIL_HOST: smtp.gmail.com
|
||||
EMAIL_PORT: 587
|
||||
EMAIL_USE_TLS: True
|
||||
EMAIL_FROM_ADDRESS: "<email>"
|
||||
EMAIL_HOST_USER: "<email>"
|
||||
EMAIL_HOST_PASSWORD: "<password>"
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.8, 3.9]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
run: |
|
||||
python manage.py test
|
||||
12
README.md
12
README.md
@ -1,8 +1,12 @@
|
||||

|
||||

|
||||
|
||||
### The world’s most high-end designed, lightweight, and feature-rich learning management system.
|
||||
|
||||
# SkyLearn: Open source learning management system
|
||||
|
||||
Feature-rich learning management system using django web framework. You may want to build a learning management system(AKA school management system) for a school organization or just for the sake of learning the tech stack and building your portfolio, either way, this project would be a good kickstart for you.
|
||||
Learning management system using Django web framework. You might want to develop a learning management system (also known as a school/college management system) for a school/college organization, or simply for the purpose of learning the tech stack and enhancing your portfolio. In either case, this project would be a great way to get started. The aim is to create the world's most lightweight yet feature-rich learning management system. However, this is not possible without your support, so please give it a star ⭐️.
|
||||
|
||||
_Documentation is under development_
|
||||
|
||||
Let's enhance the project by contributing! 👩💻👩💻
|
||||
|
||||
@ -37,7 +41,7 @@ Let's enhance the project by contributing! 👩💻👩💻
|
||||
- Pass marks can be set
|
||||
- Multiple choice question type
|
||||
- True/False question type
|
||||
- Essay question type
|
||||
- Essay question type................._Coming soon_
|
||||
- Custom message displayed for those that pass or fail a quiz
|
||||
- Custom permission (view_sittings) added, allowing users with that permission to view quiz results from users
|
||||
- A marking page which lists completed quizzes, can be filtered by quiz or user, and is used to mark essay questions
|
||||
@ -84,7 +88,7 @@ python manage.py runserver
|
||||
|
||||
Last but not least, go to this address http://127.0.0.1:8000
|
||||
|
||||
#### _Check [this page](https://adilmohak.github.io/dj-lms-starter/) for quick up and running._
|
||||
#### _Check [this page](https://adilmohak.github.io/dj-lms-starter/) for more insight and support._
|
||||
|
||||
# References
|
||||
|
||||
|
||||
6
TODO.md
6
TODO.md
@ -1,6 +1,12 @@
|
||||
# TODO
|
||||
|
||||
- **School calendar**
|
||||
- School calendar should be implemented displayed on the home page along with news and events
|
||||
- Managed by admin/superadmin
|
||||
- **News and events**
|
||||
- News and events should be associated with the calendar
|
||||
- **Add and Drop**:
|
||||
- Department head
|
||||
- The add and drop page should only include courses offered by the department head.
|
||||
- Add and drop date should be restricted by the school calendar.
|
||||
- **Payment integration**:
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = "__all__"
|
||||
@ -1,8 +0,0 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "accounts-api"
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.UserListAPIView.as_view(), name="users-api"),
|
||||
]
|
||||
@ -1,23 +0,0 @@
|
||||
from rest_framework import generics
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from .serializers import UserSerializer
|
||||
|
||||
|
||||
class UserListAPIView(generics.ListAPIView):
|
||||
lookup_field = "id"
|
||||
serializer_class = UserSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = get_user_model().objects.all()
|
||||
query = self.request.GET.get("q")
|
||||
if query is not None:
|
||||
queryset = queryset.filter(username__iexact=q)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserDetailView(generics.RetrieveAPIView):
|
||||
User = get_user_model()
|
||||
lookup_field = "id"
|
||||
queryset = User.objects.all()
|
||||
model = User
|
||||
@ -1,41 +0,0 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponse
|
||||
from django.test import TestCase, RequestFactory
|
||||
from accounts.decorators import admin_required
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class AdminRequiredDecoratorTests(TestCase):
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser(
|
||||
username="admin", email="admin@example.com", password="password"
|
||||
)
|
||||
self.user = User.objects.create_user(
|
||||
username="user", email="user@example.com", password="password"
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def admin_view(self, request):
|
||||
return HttpResponse()
|
||||
|
||||
def test_admin_required_decorator(self):
|
||||
# Apply the admin_required decorator to the view function
|
||||
decorated_view = admin_required(self.admin_view)
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.user = self.user
|
||||
response = decorated_view(request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_admin_required_decorator_with_redirect(self):
|
||||
# Apply the admin_required decorator to the view function
|
||||
decorated_view = admin_required(function=self.admin_view, redirect_to="/login/")
|
||||
|
||||
request = self.factory.get("/")
|
||||
request.user = self.user
|
||||
response = decorated_view(request)
|
||||
|
||||
# Assert redirection to login page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/login/")
|
||||
@ -1,2 +1,2 @@
|
||||
# from .test_decorators import AdminRequiredDecoratorTests
|
||||
# __all__ = [""]
|
||||
# accounts/tests/__init__.py
|
||||
# This ensures that tests is treated as a package.
|
||||
|
||||
@ -51,7 +51,6 @@ DJANGO_APPS = [
|
||||
THIRD_PARTY_APPS = [
|
||||
"crispy_forms",
|
||||
"crispy_bootstrap5",
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
]
|
||||
|
||||
@ -208,17 +207,6 @@ CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
LOGOUT_REDIRECT_URL = "/"
|
||||
|
||||
# DRF setup
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_PERMISSION_CLASSES": [
|
||||
"rest_framework.permissions.IsAuthenticated",
|
||||
],
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
"rest_framework.authentication.BasicAuthentication",
|
||||
],
|
||||
}
|
||||
|
||||
# Strip payment config
|
||||
STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY", default="")
|
||||
STRIPE_PUBLISHABLE_KEY = config("STRIPE_PUBLISHABLE_KEY", default="")
|
||||
|
||||
@ -26,7 +26,6 @@ urlpatterns += i18n_patterns(
|
||||
path("search/", include("search.urls")),
|
||||
path("quiz/", include("quiz.urls")),
|
||||
path("payments/", include("payments.urls")),
|
||||
path("accounts/api/", include("accounts.api.urls", namespace="accounts-api")),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
# from rest_framework import serializers
|
||||
# from django.contrib.auth.views import get_user_model
|
||||
#
|
||||
# User = get_user_model()
|
||||
#
|
||||
#
|
||||
# class UserSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = User
|
||||
# fields = '__all__'
|
||||
@ -390,40 +390,80 @@ def handle_video_delete(request, slug, video_slug):
|
||||
@login_required
|
||||
@student_required
|
||||
def course_registration(request):
|
||||
student = get_object_or_404(Student, student__pk=request.user.id)
|
||||
current_semester = Semester.objects.filter(is_current_semester=True).first()
|
||||
if not current_semester:
|
||||
messages.error(request, "No active semester found.")
|
||||
return render(request, "course/course_registration.html")
|
||||
|
||||
if request.method == "POST":
|
||||
course_ids = request.POST.getlist("course_ids")
|
||||
for course_id in course_ids:
|
||||
course = get_object_or_404(Course, pk=course_id)
|
||||
TakenCourse.objects.get_or_create(student=student, course=course)
|
||||
student = Student.objects.get(student__pk=request.user.id)
|
||||
ids = ()
|
||||
data = request.POST.copy()
|
||||
data.pop("csrfmiddlewaretoken", None) # remove csrf_token
|
||||
for key in data.keys():
|
||||
ids = ids + (str(key),)
|
||||
for s in range(0, len(ids)):
|
||||
course = Course.objects.get(pk=ids[s])
|
||||
obj = TakenCourse.objects.create(student=student, course=course)
|
||||
obj.save()
|
||||
messages.success(request, "Courses registered successfully!")
|
||||
return redirect("course_registration")
|
||||
else:
|
||||
current_semester = Semester.objects.filter(is_current_semester=True).first()
|
||||
if not current_semester:
|
||||
messages.error(request, "No active semester found.")
|
||||
return render(request, "course/course_registration.html")
|
||||
|
||||
taken_course_ids = TakenCourse.objects.filter(student=student).values_list(
|
||||
"course__id", flat=True
|
||||
)
|
||||
courses = Course.objects.filter(
|
||||
program=student.program,
|
||||
level=student.level,
|
||||
semester=current_semester.semester,
|
||||
).exclude(id__in=taken_course_ids)
|
||||
# student = Student.objects.get(student__pk=request.user.id)
|
||||
student = get_object_or_404(Student, student__id=request.user.id)
|
||||
taken_courses = TakenCourse.objects.filter(student__student__id=request.user.id)
|
||||
t = ()
|
||||
for i in taken_courses:
|
||||
t += (i.course.pk,)
|
||||
|
||||
registered_courses = Course.objects.filter(id__in=taken_course_ids)
|
||||
all_courses = Course.objects.filter(level=student.level, program=student.program)
|
||||
courses = (
|
||||
Course.objects.filter(
|
||||
program__pk=student.program.id,
|
||||
level=student.level,
|
||||
semester=current_semester,
|
||||
)
|
||||
.exclude(id__in=t)
|
||||
.order_by("year")
|
||||
)
|
||||
all_courses = Course.objects.filter(
|
||||
level=student.level, program__pk=student.program.id
|
||||
)
|
||||
|
||||
context = {
|
||||
"courses": courses,
|
||||
"registered_courses": registered_courses,
|
||||
"student": student,
|
||||
"current_semester": current_semester,
|
||||
"all_courses_registered": all_courses.count() == registered_courses.count(),
|
||||
}
|
||||
return render(request, "course/course_registration.html", context)
|
||||
no_course_is_registered = False # Check if no course is registered
|
||||
all_courses_are_registered = False
|
||||
|
||||
registered_courses = Course.objects.filter(level=student.level).filter(id__in=t)
|
||||
if (
|
||||
registered_courses.count() == 0
|
||||
): # Check if number of registered courses is 0
|
||||
no_course_is_registered = True
|
||||
|
||||
if registered_courses.count() == all_courses.count():
|
||||
all_courses_are_registered = True
|
||||
|
||||
total_first_semester_credit = 0
|
||||
total_sec_semester_credit = 0
|
||||
total_registered_credit = 0
|
||||
for i in courses:
|
||||
if i.semester == "First":
|
||||
total_first_semester_credit += int(i.credit)
|
||||
if i.semester == "Second":
|
||||
total_sec_semester_credit += int(i.credit)
|
||||
for i in registered_courses:
|
||||
total_registered_credit += int(i.credit)
|
||||
context = {
|
||||
"is_calender_on": True,
|
||||
"all_courses_are_registered": all_courses_are_registered,
|
||||
"no_course_is_registered": no_course_is_registered,
|
||||
"current_semester": current_semester,
|
||||
"courses": courses,
|
||||
"total_first_semester_credit": total_first_semester_credit,
|
||||
"total_sec_semester_credit": total_sec_semester_credit,
|
||||
"registered_courses": registered_courses,
|
||||
"total_registered_credit": total_registered_credit,
|
||||
"student": student,
|
||||
}
|
||||
return render(request, "course/course_registration.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -432,6 +472,7 @@ def course_drop(request):
|
||||
if request.method == "POST":
|
||||
student = get_object_or_404(Student, student__pk=request.user.id)
|
||||
course_ids = request.POST.getlist("course_ids")
|
||||
print("course_ids", course_ids)
|
||||
for course_id in course_ids:
|
||||
course = get_object_or_404(Course, pk=course_id)
|
||||
TakenCourse.objects.filter(student=student, course=course).delete()
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
# #! /usr/bin/env python3.6
|
||||
|
||||
# """
|
||||
# server.py
|
||||
# Stripe Sample.
|
||||
# Python 3.6 or newer required.
|
||||
# """
|
||||
|
||||
# import stripe
|
||||
# import json
|
||||
# import os
|
||||
|
||||
# from flask import Flask, render_template, jsonify, request, send_from_directory
|
||||
# from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
# # Set your secret key. Remember to switch to your live secret key in production.
|
||||
# # See your keys here: https://dashboard.stripe.com/account/apikeys
|
||||
# stripe.api_key = "sk_test_51IcEVZHbzY4cUA9T3BZdDayN4gmbJyXuaLCzpLT15HZoOmC17G7CxeEdXeIHSWyhYfxpljsclzzjsFukYNqJTbrW00tv3qIbN2"
|
||||
|
||||
# intent = stripe.PaymentIntent.create(
|
||||
# amount=1099,
|
||||
# currency='usd',
|
||||
# )
|
||||
|
||||
# # Setup Stripe python client library
|
||||
# load_dotenv(find_dotenv())
|
||||
# stripe.api_key = os.getenv('STRIPE_SECRET_KEY')
|
||||
# stripe.api_version = os.getenv('STRIPE_API_VERSION')
|
||||
|
||||
# static_dir = str(os.path.abspath(os.path.join(__file__ , "..", os.getenv("STATIC_DIR"))))
|
||||
# app = Flask(__name__, static_folder=static_dir,
|
||||
# static_url_path="", template_folder=static_dir)
|
||||
|
||||
|
||||
# @app.route('/checkout', methods=['GET'])
|
||||
# def get_checkout_page():
|
||||
# # Display checkout page
|
||||
# return render_template('index.html')
|
||||
|
||||
|
||||
# def calculate_order_amount(items):
|
||||
# # Replace this constant with a calculation of the order's amount
|
||||
# # Calculate the order total on the server to prevent
|
||||
# # people from directly manipulating the amount on the client
|
||||
# return 1400
|
||||
|
||||
|
||||
# @app.route('/create-payment-intent', methods=['POST'])
|
||||
# def create_payment():
|
||||
# data = json.loads(request.data)
|
||||
# # Create a PaymentIntent with the order amount and currency
|
||||
# intent = stripe.PaymentIntent.create(
|
||||
# amount=calculate_order_amount(data['items']),
|
||||
# currency=data['currency']
|
||||
# )
|
||||
|
||||
# try:
|
||||
# # Send publishable key and PaymentIntent details to client
|
||||
# return jsonify({'publishableKey': os.getenv('STRIPE_PUBLISHABLE_KEY'), 'clientSecret': intent.client_secret})
|
||||
# except Exception as e:
|
||||
# return jsonify(error=str(e)), 403
|
||||
|
||||
|
||||
# @app.route('/webhook', methods=['POST'])
|
||||
# def webhook_received():
|
||||
# # You can use webhooks to receive information about asynchronous payment events.
|
||||
# # For more about our webhook events check out https://stripe.com/docs/webhooks.
|
||||
# webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
|
||||
# request_data = json.loads(request.data)
|
||||
|
||||
# if webhook_secret:
|
||||
# # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
|
||||
# signature = request.headers.get('stripe-signature')
|
||||
# try:
|
||||
# event = stripe.Webhook.construct_event(
|
||||
# payload=request.data, sig_header=signature, secret=webhook_secret)
|
||||
# data = event['data']
|
||||
# except Exception as e:
|
||||
# return e
|
||||
# # Get the type of webhook event sent - used to check the status of PaymentIntents.
|
||||
# event_type = event['type']
|
||||
# else:
|
||||
# data = request_data['data']
|
||||
# event_type = request_data['type']
|
||||
# data_object = data['object']
|
||||
|
||||
# if event_type == 'payment_intent.succeeded':
|
||||
# print('💰 Payment received!')
|
||||
# # Fulfill any orders, e-mail receipts, etc
|
||||
# # To cancel the payment you will need to issue a Refund (https://stripe.com/docs/api/refunds)
|
||||
# elif event_type == 'payment_intent.payment_failed':
|
||||
# print('❌ Payment failed.')
|
||||
# return jsonify({'status': 'success'})
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# app.run()
|
||||
@ -21,10 +21,6 @@ class ChoiceInline(admin.TabularInline):
|
||||
|
||||
|
||||
class QuizAdminForm(TranslationModelForm):
|
||||
class Meta:
|
||||
model = Quiz
|
||||
exclude = []
|
||||
|
||||
questions = forms.ModelMultipleChoiceField(
|
||||
queryset=Question.objects.all().select_subclasses(),
|
||||
required=False,
|
||||
@ -32,12 +28,16 @@ class QuizAdminForm(TranslationModelForm):
|
||||
widget=FilteredSelectMultiple(verbose_name=_("Questions"), is_stacked=False),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Quiz
|
||||
fields = ["title_en"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuizAdminForm, self).__init__(*args, **kwargs)
|
||||
if self.instance.pk:
|
||||
self.fields[
|
||||
"questions"
|
||||
].initial = self.instance.question_set.all().select_subclasses()
|
||||
self.fields["questions"].initial = (
|
||||
self.instance.question_set.all().select_subclasses()
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
quiz = super(QuizAdminForm, self).save(commit=False)
|
||||
@ -48,20 +48,26 @@ class QuizAdminForm(TranslationModelForm):
|
||||
|
||||
|
||||
class QuizAdmin(TranslationAdmin):
|
||||
form = QuizAdminForm
|
||||
fields = ('title', 'description',)
|
||||
list_display = ("title",)
|
||||
# list_filter = ('category',)
|
||||
search_fields = (
|
||||
"description",
|
||||
"category",
|
||||
)
|
||||
pass
|
||||
# form = QuizAdminForm
|
||||
# fields = (
|
||||
# "title",
|
||||
# "description",
|
||||
# )
|
||||
# list_display = ("title",)
|
||||
# # list_filter = ('category',)
|
||||
# search_fields = (
|
||||
# "description",
|
||||
# "category",
|
||||
# )
|
||||
|
||||
|
||||
class MCQuestionAdmin(TranslationAdmin):
|
||||
list_display = ("content",)
|
||||
# list_filter = ('category',)
|
||||
fieldsets = [(u'figure' 'quiz' 'choice_order', {'fields': ("content","explanation")})]
|
||||
fieldsets = [
|
||||
("figure" "quiz" "choice_order", {"fields": ("content", "explanation")})
|
||||
]
|
||||
|
||||
search_fields = ("content", "explanation")
|
||||
filter_horizontal = ("quiz",)
|
||||
|
||||
@ -9,11 +9,7 @@ django-model-utils==4.3.1 # https://github.com/jazzband/django-model-utils
|
||||
django-crispy-forms==1.14.0 # https://github.com/django-crispy-forms/django-crispy-forms
|
||||
crispy-bootstrap5==0.7 # https://github.com/django-crispy-forms/crispy-bootstrap5
|
||||
django-filter==23.5 # https://github.com/carltongibson/django-filter
|
||||
django-modeltranslation==0.19.9 # https://github.com/Buren/django-modeltranslation
|
||||
|
||||
# Django REST Framework
|
||||
djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework
|
||||
django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers
|
||||
django-modeltranslation==0.18.11 # https://github.com/Buren/django-modeltranslation
|
||||
|
||||
# PDF generator
|
||||
reportlab==4.0.4
|
||||
|
||||
@ -2,4 +2,6 @@
|
||||
|
||||
# Code quality
|
||||
# ------------------------------------------------------------------------------
|
||||
black==22.12.0 # https://github.com/psf/black
|
||||
black==22.12.0 # https://github.com/psf/black
|
||||
factory-boy==3.3.1
|
||||
django-extensions==3.2.3
|
||||
@ -114,11 +114,11 @@ class TakenCourse(models.Model):
|
||||
def get_total(self):
|
||||
return sum(
|
||||
[
|
||||
self.assignment,
|
||||
self.mid_exam,
|
||||
self.quiz,
|
||||
self.attendance,
|
||||
self.final_exam,
|
||||
Decimal(self.assignment),
|
||||
Decimal(self.mid_exam),
|
||||
Decimal(self.quiz),
|
||||
Decimal(self.attendance),
|
||||
Decimal(self.final_exam),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -149,25 +149,18 @@ def add_score_for(request, id):
|
||||
obj.attendance = attendance # set current student attendance score
|
||||
obj.final_exam = final_exam # set current student final_exam score
|
||||
|
||||
obj.total = obj.get_total(
|
||||
assignment=assignment,
|
||||
mid_exam=mid_exam,
|
||||
quiz=quiz,
|
||||
attendance=attendance,
|
||||
final_exam=final_exam,
|
||||
)
|
||||
obj.grade = obj.get_grade(total=obj.total)
|
||||
obj.total = obj.get_total()
|
||||
obj.grade = obj.get_grade()
|
||||
|
||||
# obj.total = obj.get_total(assignment, mid_exam, quiz, attendance, final_exam)
|
||||
# obj.grade = obj.get_grade(assignment, mid_exam, quiz, attendance, final_exam)
|
||||
|
||||
obj.point = obj.get_point(grade=obj.grade)
|
||||
|
||||
obj.comment = obj.get_comment(grade=obj.grade)
|
||||
obj.point = obj.get_point()
|
||||
obj.comment = obj.get_comment()
|
||||
# obj.carry_over(obj.grade)
|
||||
# obj.is_repeating()
|
||||
obj.save()
|
||||
gpa = obj.calculate_gpa(total_credit_in_semester)
|
||||
gpa = obj.calculate_gpa()
|
||||
cgpa = obj.calculate_cgpa()
|
||||
|
||||
try:
|
||||
|
||||
@ -167,6 +167,3 @@ def generate_fake_accounts_data(
|
||||
print(f"Created {len(programs)} programs.")
|
||||
print(f"Created {len(students)} students.")
|
||||
print(f"Created {len(parents)} parents.")
|
||||
|
||||
|
||||
generate_fake_accounts_data(10, 10, 10)
|
||||
|
||||
@ -5,7 +5,7 @@ from typing import List
|
||||
from django.utils import timezone
|
||||
from faker import Faker
|
||||
from factory.django import DjangoModelFactory
|
||||
from factory import SubFactory, LazyAttribute, Iterator
|
||||
from factory import SubFactory, LazyAttribute, Iterator, LazyFunction
|
||||
from core.models import ActivityLog, NewsAndEvents, Session, Semester, SEMESTER, POST
|
||||
|
||||
# Set up Django environment
|
||||
@ -32,9 +32,11 @@ class NewsAndEventsFactory(DjangoModelFactory):
|
||||
|
||||
title: str = LazyAttribute(lambda x: fake.sentence(nb_words=4))
|
||||
summary: str = LazyAttribute(lambda x: fake.paragraph(nb_sentences=3))
|
||||
posted_as: str = fake.random_element(elements=[choice[0] for choice in POST])
|
||||
updated_date: timezone.datetime = fake.date_time_this_year()
|
||||
upload_time: timezone.datetime = fake.date_time_this_year()
|
||||
posted_as: str = LazyFunction(
|
||||
lambda: fake.random_element(elements=[choice[0] for choice in POST])
|
||||
)
|
||||
# updated_date: timezone.datetime = fake.date_time_this_year()
|
||||
# upload_time: timezone.datetime = fake.date_time_this_year()
|
||||
|
||||
|
||||
class SessionFactory(DjangoModelFactory):
|
||||
@ -50,7 +52,7 @@ class SessionFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Session
|
||||
|
||||
session: str = LazyAttribute(lambda x: fake.sentence(nb_words=2))
|
||||
session: str = LazyAttribute(lambda x: str(fake.random_int(min=2020, max=2030)))
|
||||
is_current_session: bool = fake.boolean(chance_of_getting_true=50)
|
||||
next_session_begins = LazyAttribute(lambda x: fake.future_datetime())
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import random
|
||||
from typing import Type
|
||||
from factory.django import DjangoModelFactory
|
||||
from factory import SubFactory, LazyAttribute, Iterator
|
||||
from faker import Faker
|
||||
|
||||
from django.conf import settings
|
||||
from course.models import (
|
||||
Program,
|
||||
Course,
|
||||
@ -10,7 +11,6 @@ from course.models import (
|
||||
Upload,
|
||||
UploadVideo,
|
||||
CourseOffer,
|
||||
SEMESTER,
|
||||
)
|
||||
from accounts.models import User, DepartmentHead
|
||||
from core.models import Session
|
||||
@ -73,7 +73,7 @@ class CourseFactory(DjangoModelFactory):
|
||||
program: Type[Program] = SubFactory(ProgramFactory)
|
||||
level: str = Iterator(["Beginner", "Intermediate", "Advanced"])
|
||||
year: int = LazyAttribute(lambda x: fake.random_int(min=1, max=4))
|
||||
semester: str = Iterator([choice[0] for choice in SEMESTER])
|
||||
semester: str = Iterator([choice[0] for choice in settings.SEMESTER_CHOICES])
|
||||
is_elective: bool = LazyAttribute(lambda x: fake.boolean())
|
||||
|
||||
|
||||
@ -153,6 +153,59 @@ class CourseOfferFactory(DjangoModelFactory):
|
||||
dep_head = SubFactory(DepartmentHeadFactory)
|
||||
|
||||
|
||||
def populate_course_allocation(num_allocations: int) -> None:
|
||||
"""
|
||||
Populate the CourseAllocation model with fake data.
|
||||
|
||||
Args:
|
||||
num_allocations (int): The number of CourseAllocation instances to generate.
|
||||
"""
|
||||
# Fetch all available courses and sessions
|
||||
courses = list(Course.objects.all())
|
||||
sessions = list(Session.objects.all())
|
||||
|
||||
if not courses:
|
||||
print("No courses found. Please add some courses before running this script.")
|
||||
return
|
||||
|
||||
if not sessions:
|
||||
print("No sessions found. Please add some sessions before running this script.")
|
||||
return
|
||||
|
||||
# Fetch all available users (lecturers)
|
||||
lecturers = list(
|
||||
User.objects.filter(is_lecturer=True)
|
||||
) # Assuming lecturers are lecturer
|
||||
|
||||
if not lecturers:
|
||||
print(
|
||||
"No lecturers found. Please add some lecturers before running this script."
|
||||
)
|
||||
return
|
||||
|
||||
for _ in range(num_allocations):
|
||||
lecturer = random.choice(lecturers)
|
||||
session = random.choice(sessions)
|
||||
|
||||
# Create a CourseAllocation instance
|
||||
allocation = CourseAllocation.objects.create(
|
||||
lecturer=lecturer,
|
||||
session=session,
|
||||
)
|
||||
|
||||
# Assign random courses to the course allocation
|
||||
random_courses = random.sample(
|
||||
courses, random.randint(1, len(courses))
|
||||
) # Pick 1 to n random courses
|
||||
allocation.courses.set(random_courses)
|
||||
|
||||
print(
|
||||
f"Created CourseAllocation for lecturer: {lecturer.username} with {len(random_courses)} courses."
|
||||
)
|
||||
|
||||
print(f"Successfully populated {num_allocations} CourseAllocation instances.")
|
||||
|
||||
|
||||
def generate_fake_course_data(
|
||||
num_programs: int,
|
||||
num_courses: int,
|
||||
@ -194,6 +247,3 @@ def generate_fake_course_data(
|
||||
# Generate fake course offers
|
||||
course_offers = CourseOfferFactory.create_batch(num_course_offers)
|
||||
print(f"Created {len(course_offers)} course offers.")
|
||||
|
||||
|
||||
generate_fake_course_data(10, 10, 10, 10, 10, 10)
|
||||
|
||||
2
static/css/style.min.css
vendored
2
static/css/style.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -18,6 +18,10 @@
|
||||
format("svg"); /* Legacy iOS */
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #f8d270;
|
||||
}
|
||||
|
||||
*,
|
||||
body {
|
||||
font-family: "Rubik", sans-serif;
|
||||
@ -49,9 +53,30 @@ body {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
// .btn {
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
/* Override the text selection highlight color */
|
||||
::selection {
|
||||
background-color: var(
|
||||
--primary
|
||||
); /* Custom background color for selected text */
|
||||
color: #000; /* Custom text color for selected text */
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--bs-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table .info {
|
||||
margin-left: -240px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 100%;
|
||||
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16),
|
||||
0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16),
|
||||
0px 2px 10px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.dim {
|
||||
/* For Internet Explorer */
|
||||
@ -61,9 +86,17 @@ body {
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
vertical-align: middle;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd; /* Add thin borders for separation */
|
||||
text-align: left;
|
||||
}
|
||||
tbody > tr > td > a {
|
||||
display: flex;
|
||||
@ -409,6 +442,7 @@ body {
|
||||
left: -300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1150px) {
|
||||
#side-nav .top-side {
|
||||
padding-top: 3rem;
|
||||
@ -489,11 +523,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--bs-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title-1 {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
@ -502,6 +531,7 @@ a {
|
||||
text-transform: capitalize;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 0.2em;
|
||||
|
||||
&::before {
|
||||
@ -1163,36 +1193,10 @@ a {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd; /* Add thin borders for separation */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.title-1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: red;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-picture {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
@ -1201,10 +1205,6 @@ a {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
table .info {
|
||||
margin-left: -240px;
|
||||
}
|
||||
|
||||
/* Specific to the .dashboard-description class */
|
||||
.dashboard-description strong {
|
||||
font-weight: 600;
|
||||
@ -1218,36 +1218,10 @@ table .info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd; /* Add thin borders for separation */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.title-1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: red;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.bg-light-warning {
|
||||
background-color: rgb(252, 217, 111) !important;
|
||||
}
|
||||
@ -1256,19 +1230,6 @@ a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.session-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.session {
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
right: 25px;
|
||||
z-index: 2;
|
||||
}
|
||||
.br-orange {
|
||||
border: 1px solid #fd7e14;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.class-item {
|
||||
display: block;
|
||||
border-left: 4px solid #6cbd45;
|
||||
@ -1277,31 +1238,19 @@ a {
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3);
|
||||
transition: 0.5s;
|
||||
}
|
||||
.class-item p {
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
color: #b4b4b4;
|
||||
transition: 0.5s;
|
||||
}
|
||||
.class-item a {
|
||||
padding: 2px;
|
||||
color: #343a40;
|
||||
text-decoration: none;
|
||||
transition: 0.5s;
|
||||
}
|
||||
.class-item:hover {
|
||||
transform: translateX(15px);
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 100%;
|
||||
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16),
|
||||
0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16),
|
||||
0px 2px 10px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.breadcrumb-item a {
|
||||
color: var(--bs-primary);
|
||||
&:hover {
|
||||
transform: translateX(15px);
|
||||
}
|
||||
p {
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
color: #b4b4b4;
|
||||
transition: 0.5s;
|
||||
}
|
||||
a {
|
||||
padding: 2px;
|
||||
color: #343a40;
|
||||
text-decoration: none;
|
||||
transition: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,12 +55,12 @@
|
||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu position-fixed">
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'staff_edit' pk=lecturer.pk %}"><i class="unstyled me-2 fas fa-edit"></i>{% trans 'Update' %}</a></li>
|
||||
<li><a class="dropdown-item" target="_blank" href="{% url 'profile_single' lecturer.id %}?download_pdf=1"><i class="unstyled me-2 fas fa-download"></i>{% trans 'Download PDF' %}</a></li>
|
||||
<li><a class="dropdown-item text-danger" href="{% url 'lecturer_delete' pk=lecturer.pk %}"><i class="unstyled me-2 fas fa-trash-alt"></i> {% trans 'Delete' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
{% endif %} -->
|
||||
|
||||
{% if user.is_lecturer %}
|
||||
<p class="fw-bold"><i class="fas fa-book-open"></i>{% trans 'My Courses' %}</p>
|
||||
<p class="fw-bold"><i class="fas fa-book-open"></i> {% trans 'My Courses' %}</p>
|
||||
{% if courses %}
|
||||
<ul>
|
||||
{% for course in courses %}
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu position-fixed">
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'student_edit' student.student.pk %}"><i class="unstyled me-2 fas fa-edit"></i>{% trans 'Update' %}</a></li>
|
||||
<li><a class="dropdown-item" target="_blank" href="{% url 'profile_single' student.student.id %}?download_pdf=1"><i class="unstyled me-2 fas fa-download"></i>{% trans 'Download PDF' %}</a></li>
|
||||
<li><a class="dropdown-item text-danger" href="{% url 'student_delete' student.pk %}"><i class="unstyled me-2 fas fa-trash-alt"></i>{% trans 'Delete' %}</a></li>
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{% trans 'Lecturer' %}</th>
|
||||
<th>C{% trans 'Courses' %}</th>
|
||||
<th>{% trans 'Courses' %}</th>
|
||||
{% if request.user.is_superuser %}
|
||||
<th>{% trans 'Action' %}</th>
|
||||
{% endif %}
|
||||
|
||||
@ -182,31 +182,18 @@
|
||||
|
||||
{% if not no_course_is_registered %}
|
||||
|
||||
<a class="btn btn-warning" href="{% url 'course_registration_form' %}" target="_blank" title="{% trans 'Print Registration Form' %}">
|
||||
<i class="fas fa-print"></i> {% trans 'Print Registerd Courses' %}
|
||||
</a>
|
||||
|
||||
<div class="col-md-12 p-0 bg-white">
|
||||
<p class="form-title"><b>{% trans 'Course Drop' %}</b>
|
||||
<div class="level-wrapper">
|
||||
<div class="info-text">{{ student.level }}</div>
|
||||
</div>
|
||||
</p>
|
||||
<p class="form-title"><b>{% trans 'Course Drop' %}</b></p>
|
||||
<div class="container">
|
||||
<p class="fw-bold">{{ student.level }}</p>
|
||||
<form action="{% url 'course_drop' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="d-flex justify-content-between mb-4">
|
||||
<button title="{% trans 'Save Score' %}" type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa fa-times"></i> {% trans 'Drop Selected' %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- <div>
|
||||
<a target="_blank" href="{% url 'course_registration_form' %}" class="btn btn-outline-white btn-rounded btn-sm px-2">
|
||||
<i class="fa fa-file-pdf-o" aria-hidden="true"></i> Print Registration Form
|
||||
</a>
|
||||
</div> -->
|
||||
|
||||
<div class="table-responsive p-0 px-2 mt-2">
|
||||
<div class="table-shadow">
|
||||
<table class="table">
|
||||
@ -225,7 +212,7 @@
|
||||
{% for course in registered_courses %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<input name="{{ course.pk }}" value="{{ course.courseUnit }}" type="checkbox">
|
||||
<input name="course_ids" value="{{ course.id }}" type="checkbox">
|
||||
</th>
|
||||
<td>{{ course.code }}</td>
|
||||
<td>{{ course.title }}</td>
|
||||
@ -270,6 +257,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-sm btn-secondary mt-3" href="{% url 'course_registration_form' %}" target="_blank" title="{% trans 'Print Registration Form' %}">
|
||||
<i class="fas fa-print"></i> {% trans 'Print Registerd Courses' %}
|
||||
</a>
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu position-fixed">
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'edit_program' pk=program.pk %}"><i class="unstyled me-2 fas fa-edit"></i>{% trans 'Update' %}</a></li>
|
||||
<li><a class="dropdown-item text-danger" href="{% url 'program_delete' pk=program.pk %}"><i class="unstyled me-2 fas fa-trash-alt"></i>{% trans 'Delete' %}</a></li>
|
||||
</ul>
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
data-bs-boundary="window" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v m-0"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu position-fixed">
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="{% url 'edit_course' slug=course.slug %}">
|
||||
<i class="unstyled me-2 fas fa-pencil-alt"></i> {% trans 'Edit' %}
|
||||
</a>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% extends 'registration/registration_base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'New Password | Learning management system %}{% endblock title %}
|
||||
{% block title %}{% trans 'New Password | Learning management system' %}{% endblock title %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
@ -19,54 +19,43 @@
|
||||
{% for object in object_list %}
|
||||
{% with object|class_name as klass %}
|
||||
{% if klass == "Program" %}
|
||||
<div class="session-wrapper">
|
||||
<div class="session"><div class="info-text bg-secondary text-light px-2 rounded-pill">{% trans 'Program' %}</div></div>
|
||||
</div>
|
||||
<div class="col-12 class-item">
|
||||
<!-- <p><b>Program of</b> {{ object }}</p> -->
|
||||
<h4><a href="{{ object.get_absolute_url }}"><b>{{ object.title}}</b></a></h4>
|
||||
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'Program' %}</span>
|
||||
<h5><a href="{{ object.get_absolute_url }}"><b>{{ object.title}}</b></a></h5>
|
||||
<p>{{ object.summary }}</p>
|
||||
</div><hr>
|
||||
|
||||
{% elif klass == "Course" %}
|
||||
<div class="session-wrapper">
|
||||
<div class="session"><div class="info-text bg-secondary text-light px-2 rounded-pill">{% trans 'Course' %}</div></div>
|
||||
</div>
|
||||
<div class="col-12 class-item">
|
||||
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'Course' %}</span>
|
||||
<p><b>{% trans 'Program of' %}</b> {{ object.program }}</p>
|
||||
<h4><a href="{{ object.get_absolute_url }}"><b>{{ object }}</b></a></h4>
|
||||
<h5><a href="{{ object.get_absolute_url }}"><b>{{ object }}</b></a></h5>
|
||||
<p>{{ object.summary }}</p>
|
||||
</div><hr>
|
||||
|
||||
{% elif klass == "NewsAndEvents" %}
|
||||
<div class="session-wrapper">
|
||||
<div class="session"><div class="info-text bg-secondary text-light px-2 rounded-pill">{% trans 'News And Events' %}</div></div>
|
||||
</div>
|
||||
<div class="col-12 class-item">
|
||||
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'News And Events' %}</span>
|
||||
<p><b>{% trans 'Date:' %} </b> {{ object.updated_date|timesince }} ago</p>
|
||||
<h4><a href="{{ object.get_absolute_url }}"><b>{{ object.title }}</b></a></h4>
|
||||
<h5><a href="{{ object.get_absolute_url }}"><b>{{ object.title }}</b></a></h5>
|
||||
<p>{{ object.summary }}</p>
|
||||
</div><hr>
|
||||
|
||||
{% elif klass == "Quiz" %}
|
||||
<div class="session-wrapper">
|
||||
<div class="session"><div class="info-text bg-secondary text-light px-2 rounded-pill">{% trans 'Quiz' %}</div></div>
|
||||
</div>
|
||||
<div class="col-12 class-item">
|
||||
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'Quiz' %}</span>
|
||||
<p>{{ object.category }} {% trans 'quiz' %}, <b>{% trans 'Course:' %}</b> {{ object.course }}</p>
|
||||
<h4><a href="{{ object.get_absolute_url }}"><b>{{ object.title }}</b></a></h4>
|
||||
<h5><a href="{{ object.get_absolute_url }}"><b>{{ object.title }}</b></a></h5>
|
||||
<p>{{ object.description }}</p>
|
||||
</div><hr>
|
||||
|
||||
{% else %}
|
||||
<div class="session-wrapper">
|
||||
<div class="session"><div class="info-text bg-secondary text-light px-2 rounded-pill">{% trans 'Program' %}</div></div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-8 offset-lg-4">
|
||||
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'Program' %}</span>
|
||||
<a href="{{ object.get_absolute_url }}" class="class-item d-flex">{{ object }} | {{ object|class_name }}</a>
|
||||
</div><hr>
|
||||
<div class="col-12 class-item">
|
||||
<h4><a href="{{ object.get_absolute_url }}">{{ object }} | <b>{{ object|class_name }}</b></a></h4>
|
||||
<h5><a href="{{ object.get_absolute_url }}">{{ object }} | <b>{{ object|class_name }}</b></a></h5>
|
||||
<p>{{ object.description }}</p>
|
||||
</div><hr>
|
||||
{% endif %}
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
<a href="{% url 'ass_results' %}"><i class="fa fa-list-ol"></i> {% trans 'Assesment Results' %}</a>
|
||||
</li>
|
||||
<li class="{% if request.path == cr %}active{% endif %}">
|
||||
<a href="{% url 'course_registration' %}"><i class="fas fa-plus"></i>{% trans 'Add' %} &{% trans 'Drop Course' %}</a>
|
||||
<a href="{% url 'course_registration' %}"><i class="fas fa-plus"></i>{% trans 'Add' %} & {% trans 'Drop Course' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<br />
|
||||
@ -136,7 +136,7 @@
|
||||
</form>
|
||||
|
||||
<p class="small m-0">
|
||||
{% trans 'Read our' %} <a href="#"> {% trans 'Privacy' %} </a> {% trans 'and' %} <a href="#"> {% trans 'Terms of use.' %}' </a>
|
||||
{% trans 'Read our' %} <a href="#"> {% trans 'Privacy' %} </a> {% trans 'and' %} <a href="#"> {% trans 'Terms of use.' %} </a>
|
||||
<br />SkyLearn © <script>document.write(new Date().getFullYear());</script>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user