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
|
# 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! 👩💻👩💻
|
Let's enhance the project by contributing! 👩💻👩💻
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ Let's enhance the project by contributing! 👩💻👩💻
|
|||||||
- Pass marks can be set
|
- Pass marks can be set
|
||||||
- Multiple choice question type
|
- Multiple choice question type
|
||||||
- True/False 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 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
|
- 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
|
- 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
|
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
|
# References
|
||||||
|
|
||||||
|
|||||||
6
TODO.md
6
TODO.md
@ -1,6 +1,12 @@
|
|||||||
# TODO
|
# 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**:
|
- **Add and Drop**:
|
||||||
|
- Department head
|
||||||
- The add and drop page should only include courses offered by the 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.
|
- Add and drop date should be restricted by the school calendar.
|
||||||
- **Payment integration**:
|
- **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
|
# accounts/tests/__init__.py
|
||||||
# __all__ = [""]
|
# This ensures that tests is treated as a package.
|
||||||
|
|||||||
@ -51,7 +51,6 @@ DJANGO_APPS = [
|
|||||||
THIRD_PARTY_APPS = [
|
THIRD_PARTY_APPS = [
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"crispy_bootstrap5",
|
"crispy_bootstrap5",
|
||||||
"rest_framework",
|
|
||||||
"django_filters",
|
"django_filters",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -208,17 +207,6 @@ CRISPY_TEMPLATE_PACK = "bootstrap5"
|
|||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
LOGOUT_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
|
# Strip payment config
|
||||||
STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY", default="")
|
STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY", default="")
|
||||||
STRIPE_PUBLISHABLE_KEY = config("STRIPE_PUBLISHABLE_KEY", default="")
|
STRIPE_PUBLISHABLE_KEY = config("STRIPE_PUBLISHABLE_KEY", default="")
|
||||||
|
|||||||
@ -26,7 +26,6 @@ urlpatterns += i18n_patterns(
|
|||||||
path("search/", include("search.urls")),
|
path("search/", include("search.urls")),
|
||||||
path("quiz/", include("quiz.urls")),
|
path("quiz/", include("quiz.urls")),
|
||||||
path("payments/", include("payments.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
|
@login_required
|
||||||
@student_required
|
@student_required
|
||||||
def course_registration(request):
|
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":
|
if request.method == "POST":
|
||||||
course_ids = request.POST.getlist("course_ids")
|
student = Student.objects.get(student__pk=request.user.id)
|
||||||
for course_id in course_ids:
|
ids = ()
|
||||||
course = get_object_or_404(Course, pk=course_id)
|
data = request.POST.copy()
|
||||||
TakenCourse.objects.get_or_create(student=student, course=course)
|
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!")
|
messages.success(request, "Courses registered successfully!")
|
||||||
return redirect("course_registration")
|
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(
|
# student = Student.objects.get(student__pk=request.user.id)
|
||||||
"course__id", flat=True
|
student = get_object_or_404(Student, student__id=request.user.id)
|
||||||
)
|
taken_courses = TakenCourse.objects.filter(student__student__id=request.user.id)
|
||||||
courses = Course.objects.filter(
|
t = ()
|
||||||
program=student.program,
|
for i in taken_courses:
|
||||||
level=student.level,
|
t += (i.course.pk,)
|
||||||
semester=current_semester.semester,
|
|
||||||
).exclude(id__in=taken_course_ids)
|
|
||||||
|
|
||||||
registered_courses = Course.objects.filter(id__in=taken_course_ids)
|
courses = (
|
||||||
all_courses = Course.objects.filter(level=student.level, program=student.program)
|
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 = {
|
no_course_is_registered = False # Check if no course is registered
|
||||||
"courses": courses,
|
all_courses_are_registered = False
|
||||||
"registered_courses": registered_courses,
|
|
||||||
"student": student,
|
registered_courses = Course.objects.filter(level=student.level).filter(id__in=t)
|
||||||
"current_semester": current_semester,
|
if (
|
||||||
"all_courses_registered": all_courses.count() == registered_courses.count(),
|
registered_courses.count() == 0
|
||||||
}
|
): # Check if number of registered courses is 0
|
||||||
return render(request, "course/course_registration.html", context)
|
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
|
@login_required
|
||||||
@ -432,6 +472,7 @@ def course_drop(request):
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
student = get_object_or_404(Student, student__pk=request.user.id)
|
student = get_object_or_404(Student, student__pk=request.user.id)
|
||||||
course_ids = request.POST.getlist("course_ids")
|
course_ids = request.POST.getlist("course_ids")
|
||||||
|
print("course_ids", course_ids)
|
||||||
for course_id in course_ids:
|
for course_id in course_ids:
|
||||||
course = get_object_or_404(Course, pk=course_id)
|
course = get_object_or_404(Course, pk=course_id)
|
||||||
TakenCourse.objects.filter(student=student, course=course).delete()
|
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 QuizAdminForm(TranslationModelForm):
|
||||||
class Meta:
|
|
||||||
model = Quiz
|
|
||||||
exclude = []
|
|
||||||
|
|
||||||
questions = forms.ModelMultipleChoiceField(
|
questions = forms.ModelMultipleChoiceField(
|
||||||
queryset=Question.objects.all().select_subclasses(),
|
queryset=Question.objects.all().select_subclasses(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -32,12 +28,16 @@ class QuizAdminForm(TranslationModelForm):
|
|||||||
widget=FilteredSelectMultiple(verbose_name=_("Questions"), is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name=_("Questions"), is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Quiz
|
||||||
|
fields = ["title_en"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(QuizAdminForm, self).__init__(*args, **kwargs)
|
super(QuizAdminForm, self).__init__(*args, **kwargs)
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
self.fields[
|
self.fields["questions"].initial = (
|
||||||
"questions"
|
self.instance.question_set.all().select_subclasses()
|
||||||
].initial = self.instance.question_set.all().select_subclasses()
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
quiz = super(QuizAdminForm, self).save(commit=False)
|
quiz = super(QuizAdminForm, self).save(commit=False)
|
||||||
@ -48,20 +48,26 @@ class QuizAdminForm(TranslationModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class QuizAdmin(TranslationAdmin):
|
class QuizAdmin(TranslationAdmin):
|
||||||
form = QuizAdminForm
|
pass
|
||||||
fields = ('title', 'description',)
|
# form = QuizAdminForm
|
||||||
list_display = ("title",)
|
# fields = (
|
||||||
# list_filter = ('category',)
|
# "title",
|
||||||
search_fields = (
|
# "description",
|
||||||
"description",
|
# )
|
||||||
"category",
|
# list_display = ("title",)
|
||||||
)
|
# # list_filter = ('category',)
|
||||||
|
# search_fields = (
|
||||||
|
# "description",
|
||||||
|
# "category",
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
class MCQuestionAdmin(TranslationAdmin):
|
class MCQuestionAdmin(TranslationAdmin):
|
||||||
list_display = ("content",)
|
list_display = ("content",)
|
||||||
# list_filter = ('category',)
|
# list_filter = ('category',)
|
||||||
fieldsets = [(u'figure' 'quiz' 'choice_order', {'fields': ("content","explanation")})]
|
fieldsets = [
|
||||||
|
("figure" "quiz" "choice_order", {"fields": ("content", "explanation")})
|
||||||
|
]
|
||||||
|
|
||||||
search_fields = ("content", "explanation")
|
search_fields = ("content", "explanation")
|
||||||
filter_horizontal = ("quiz",)
|
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
|
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
|
crispy-bootstrap5==0.7 # https://github.com/django-crispy-forms/crispy-bootstrap5
|
||||||
django-filter==23.5 # https://github.com/carltongibson/django-filter
|
django-filter==23.5 # https://github.com/carltongibson/django-filter
|
||||||
django-modeltranslation==0.19.9 # https://github.com/Buren/django-modeltranslation
|
django-modeltranslation==0.18.11 # 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
|
|
||||||
|
|
||||||
# PDF generator
|
# PDF generator
|
||||||
reportlab==4.0.4
|
reportlab==4.0.4
|
||||||
|
|||||||
@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
# Code quality
|
# 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):
|
def get_total(self):
|
||||||
return sum(
|
return sum(
|
||||||
[
|
[
|
||||||
self.assignment,
|
Decimal(self.assignment),
|
||||||
self.mid_exam,
|
Decimal(self.mid_exam),
|
||||||
self.quiz,
|
Decimal(self.quiz),
|
||||||
self.attendance,
|
Decimal(self.attendance),
|
||||||
self.final_exam,
|
Decimal(self.final_exam),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -149,25 +149,18 @@ def add_score_for(request, id):
|
|||||||
obj.attendance = attendance # set current student attendance score
|
obj.attendance = attendance # set current student attendance score
|
||||||
obj.final_exam = final_exam # set current student final_exam score
|
obj.final_exam = final_exam # set current student final_exam score
|
||||||
|
|
||||||
obj.total = obj.get_total(
|
obj.total = obj.get_total()
|
||||||
assignment=assignment,
|
obj.grade = obj.get_grade()
|
||||||
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(assignment, mid_exam, quiz, attendance, final_exam)
|
# 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.grade = obj.get_grade(assignment, mid_exam, quiz, attendance, final_exam)
|
||||||
|
|
||||||
obj.point = obj.get_point(grade=obj.grade)
|
obj.point = obj.get_point()
|
||||||
|
obj.comment = obj.get_comment()
|
||||||
obj.comment = obj.get_comment(grade=obj.grade)
|
|
||||||
# obj.carry_over(obj.grade)
|
# obj.carry_over(obj.grade)
|
||||||
# obj.is_repeating()
|
# obj.is_repeating()
|
||||||
obj.save()
|
obj.save()
|
||||||
gpa = obj.calculate_gpa(total_credit_in_semester)
|
gpa = obj.calculate_gpa()
|
||||||
cgpa = obj.calculate_cgpa()
|
cgpa = obj.calculate_cgpa()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -167,6 +167,3 @@ def generate_fake_accounts_data(
|
|||||||
print(f"Created {len(programs)} programs.")
|
print(f"Created {len(programs)} programs.")
|
||||||
print(f"Created {len(students)} students.")
|
print(f"Created {len(students)} students.")
|
||||||
print(f"Created {len(parents)} parents.")
|
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 django.utils import timezone
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
from factory.django import DjangoModelFactory
|
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
|
from core.models import ActivityLog, NewsAndEvents, Session, Semester, SEMESTER, POST
|
||||||
|
|
||||||
# Set up Django environment
|
# Set up Django environment
|
||||||
@ -32,9 +32,11 @@ class NewsAndEventsFactory(DjangoModelFactory):
|
|||||||
|
|
||||||
title: str = LazyAttribute(lambda x: fake.sentence(nb_words=4))
|
title: str = LazyAttribute(lambda x: fake.sentence(nb_words=4))
|
||||||
summary: str = LazyAttribute(lambda x: fake.paragraph(nb_sentences=3))
|
summary: str = LazyAttribute(lambda x: fake.paragraph(nb_sentences=3))
|
||||||
posted_as: str = fake.random_element(elements=[choice[0] for choice in POST])
|
posted_as: str = LazyFunction(
|
||||||
updated_date: timezone.datetime = fake.date_time_this_year()
|
lambda: fake.random_element(elements=[choice[0] for choice in POST])
|
||||||
upload_time: timezone.datetime = fake.date_time_this_year()
|
)
|
||||||
|
# updated_date: timezone.datetime = fake.date_time_this_year()
|
||||||
|
# upload_time: timezone.datetime = fake.date_time_this_year()
|
||||||
|
|
||||||
|
|
||||||
class SessionFactory(DjangoModelFactory):
|
class SessionFactory(DjangoModelFactory):
|
||||||
@ -50,7 +52,7 @@ class SessionFactory(DjangoModelFactory):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Session
|
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)
|
is_current_session: bool = fake.boolean(chance_of_getting_true=50)
|
||||||
next_session_begins = LazyAttribute(lambda x: fake.future_datetime())
|
next_session_begins = LazyAttribute(lambda x: fake.future_datetime())
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
import random
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from factory.django import DjangoModelFactory
|
from factory.django import DjangoModelFactory
|
||||||
from factory import SubFactory, LazyAttribute, Iterator
|
from factory import SubFactory, LazyAttribute, Iterator
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
from django.conf import settings
|
||||||
from course.models import (
|
from course.models import (
|
||||||
Program,
|
Program,
|
||||||
Course,
|
Course,
|
||||||
@ -10,7 +11,6 @@ from course.models import (
|
|||||||
Upload,
|
Upload,
|
||||||
UploadVideo,
|
UploadVideo,
|
||||||
CourseOffer,
|
CourseOffer,
|
||||||
SEMESTER,
|
|
||||||
)
|
)
|
||||||
from accounts.models import User, DepartmentHead
|
from accounts.models import User, DepartmentHead
|
||||||
from core.models import Session
|
from core.models import Session
|
||||||
@ -73,7 +73,7 @@ class CourseFactory(DjangoModelFactory):
|
|||||||
program: Type[Program] = SubFactory(ProgramFactory)
|
program: Type[Program] = SubFactory(ProgramFactory)
|
||||||
level: str = Iterator(["Beginner", "Intermediate", "Advanced"])
|
level: str = Iterator(["Beginner", "Intermediate", "Advanced"])
|
||||||
year: int = LazyAttribute(lambda x: fake.random_int(min=1, max=4))
|
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())
|
is_elective: bool = LazyAttribute(lambda x: fake.boolean())
|
||||||
|
|
||||||
|
|
||||||
@ -153,6 +153,59 @@ class CourseOfferFactory(DjangoModelFactory):
|
|||||||
dep_head = SubFactory(DepartmentHeadFactory)
|
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(
|
def generate_fake_course_data(
|
||||||
num_programs: int,
|
num_programs: int,
|
||||||
num_courses: int,
|
num_courses: int,
|
||||||
@ -194,6 +247,3 @@ def generate_fake_course_data(
|
|||||||
# Generate fake course offers
|
# Generate fake course offers
|
||||||
course_offers = CourseOfferFactory.create_batch(num_course_offers)
|
course_offers = CourseOfferFactory.create_batch(num_course_offers)
|
||||||
print(f"Created {len(course_offers)} 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 */
|
format("svg"); /* Legacy iOS */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #f8d270;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
body {
|
body {
|
||||||
font-family: "Rubik", sans-serif;
|
font-family: "Rubik", sans-serif;
|
||||||
@ -49,9 +53,30 @@ body {
|
|||||||
background: #999;
|
background: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .btn {
|
/* Override the text selection highlight color */
|
||||||
// border-radius: 2px;
|
::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 {
|
.dim {
|
||||||
/* For Internet Explorer */
|
/* For Internet Explorer */
|
||||||
@ -61,9 +86,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd; /* Add thin borders for separation */
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
tbody > tr > td > a {
|
tbody > tr > td > a {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -409,6 +442,7 @@ body {
|
|||||||
left: -300px;
|
left: -300px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1150px) {
|
@media screen and (max-width: 1150px) {
|
||||||
#side-nav .top-side {
|
#side-nav .top-side {
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
@ -489,11 +523,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--bs-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-1 {
|
.title-1 {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -502,6 +531,7 @@ a {
|
|||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@ -1163,36 +1193,10 @@ a {
|
|||||||
vertical-align: middle;
|
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 {
|
.text-danger {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-picture {
|
.user-picture {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
@ -1201,10 +1205,6 @@ a {
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
table .info {
|
|
||||||
margin-left: -240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Specific to the .dashboard-description class */
|
/* Specific to the .dashboard-description class */
|
||||||
.dashboard-description strong {
|
.dashboard-description strong {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -1218,36 +1218,10 @@ table .info {
|
|||||||
margin-bottom: 15px;
|
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 {
|
.text-danger {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-light-warning {
|
.bg-light-warning {
|
||||||
background-color: rgb(252, 217, 111) !important;
|
background-color: rgb(252, 217, 111) !important;
|
||||||
}
|
}
|
||||||
@ -1256,19 +1230,6 @@ a {
|
|||||||
display: none;
|
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 {
|
.class-item {
|
||||||
display: block;
|
display: block;
|
||||||
border-left: 4px solid #6cbd45;
|
border-left: 4px solid #6cbd45;
|
||||||
@ -1277,31 +1238,19 @@ a {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3);
|
||||||
transition: 0.5s;
|
transition: 0.5s;
|
||||||
}
|
&:hover {
|
||||||
.class-item p {
|
transform: translateX(15px);
|
||||||
padding: 2px;
|
}
|
||||||
margin: 0;
|
p {
|
||||||
color: #b4b4b4;
|
padding: 2px;
|
||||||
transition: 0.5s;
|
margin: 0;
|
||||||
}
|
color: #b4b4b4;
|
||||||
.class-item a {
|
transition: 0.5s;
|
||||||
padding: 2px;
|
}
|
||||||
color: #343a40;
|
a {
|
||||||
text-decoration: none;
|
padding: 2px;
|
||||||
transition: 0.5s;
|
color: #343a40;
|
||||||
}
|
text-decoration: none;
|
||||||
.class-item:hover {
|
transition: 0.5s;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,12 +55,12 @@
|
|||||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="fa fa-ellipsis-vertical"></i>
|
<i class="fa fa-ellipsis-vertical"></i>
|
||||||
</button>
|
</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" 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" 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>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
{% endif %} -->
|
{% endif %} -->
|
||||||
|
|
||||||
{% if user.is_lecturer %}
|
{% 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 %}
|
{% if courses %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for course in courses %}
|
{% for course in courses %}
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="fa fa-ellipsis-vertical"></i>
|
<i class="fa fa-ellipsis-vertical"></i>
|
||||||
</button>
|
</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" 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" 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>
|
<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>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>{% trans 'Lecturer' %}</th>
|
<th>{% trans 'Lecturer' %}</th>
|
||||||
<th>C{% trans 'Courses' %}</th>
|
<th>{% trans 'Courses' %}</th>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<th>{% trans 'Action' %}</th>
|
<th>{% trans 'Action' %}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -182,31 +182,18 @@
|
|||||||
|
|
||||||
{% if not no_course_is_registered %}
|
{% 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">
|
<div class="col-md-12 p-0 bg-white">
|
||||||
<p class="form-title"><b>{% trans 'Course Drop' %}</b>
|
<p class="form-title"><b>{% trans 'Course Drop' %}</b></p>
|
||||||
<div class="level-wrapper">
|
|
||||||
<div class="info-text">{{ student.level }}</div>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<p class="fw-bold">{{ student.level }}</p>
|
||||||
<form action="{% url 'course_drop' %}" method="POST">
|
<form action="{% url 'course_drop' %}" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="d-flex justify-content-between mb-4">
|
<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' %}
|
<i class="fa fa-times"></i> {% trans 'Drop Selected' %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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-responsive p-0 px-2 mt-2">
|
||||||
<div class="table-shadow">
|
<div class="table-shadow">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@ -225,7 +212,7 @@
|
|||||||
{% for course in registered_courses %}
|
{% for course in registered_courses %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<input name="{{ course.pk }}" value="{{ course.courseUnit }}" type="checkbox">
|
<input name="course_ids" value="{{ course.id }}" type="checkbox">
|
||||||
</th>
|
</th>
|
||||||
<td>{{ course.code }}</td>
|
<td>{{ course.code }}</td>
|
||||||
<td>{{ course.title }}</td>
|
<td>{{ course.title }}</td>
|
||||||
@ -270,6 +257,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="fa fa-ellipsis-vertical"></i>
|
<i class="fa fa-ellipsis-vertical"></i>
|
||||||
</button>
|
</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" 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>
|
<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>
|
</ul>
|
||||||
|
|||||||
@ -77,7 +77,7 @@
|
|||||||
data-bs-boundary="window" aria-haspopup="true" aria-expanded="false">
|
data-bs-boundary="window" aria-haspopup="true" aria-expanded="false">
|
||||||
<i class="fas fa-ellipsis-v m-0"></i>
|
<i class="fas fa-ellipsis-v m-0"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu position-fixed">
|
<div class="dropdown-menu">
|
||||||
<a class="dropdown-item" href="{% url 'edit_course' slug=course.slug %}">
|
<a class="dropdown-item" href="{% url 'edit_course' slug=course.slug %}">
|
||||||
<i class="unstyled me-2 fas fa-pencil-alt"></i> {% trans 'Edit' %}
|
<i class="unstyled me-2 fas fa-pencil-alt"></i> {% trans 'Edit' %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{% extends 'registration/registration_base.html' %}
|
{% extends 'registration/registration_base.html' %}
|
||||||
{% load i18n %}
|
{% 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 %}
|
{% load crispy_forms_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|||||||
@ -19,54 +19,43 @@
|
|||||||
{% for object in object_list %}
|
{% for object in object_list %}
|
||||||
{% with object|class_name as klass %}
|
{% with object|class_name as klass %}
|
||||||
{% if klass == "Program" %}
|
{% 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">
|
<div class="col-12 class-item">
|
||||||
<!-- <p><b>Program of</b> {{ object }}</p> -->
|
<span class="bg-secondary text-light small px-2 rounded-pill">{% trans 'Program' %}</span>
|
||||||
<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>
|
<p>{{ object.summary }}</p>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
|
||||||
{% elif klass == "Course" %}
|
{% 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">
|
<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>
|
<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>
|
<p>{{ object.summary }}</p>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
|
||||||
{% elif klass == "NewsAndEvents" %}
|
{% 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">
|
<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>
|
<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>
|
<p>{{ object.summary }}</p>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
|
||||||
{% elif klass == "Quiz" %}
|
{% 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">
|
<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>
|
<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>
|
<p>{{ object.description }}</p>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
|
||||||
{% else %}
|
{% 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">
|
<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>
|
<a href="{{ object.get_absolute_url }}" class="class-item d-flex">{{ object }} | {{ object|class_name }}</a>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
<div class="col-12 class-item">
|
<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>
|
<p>{{ object.description }}</p>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
<a href="{% url 'ass_results' %}"><i class="fa fa-list-ol"></i> {% trans 'Assesment Results' %}</a>
|
<a href="{% url 'ass_results' %}"><i class="fa fa-list-ol"></i> {% trans 'Assesment Results' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="{% if request.path == cr %}active{% endif %}">
|
<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>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br />
|
<br />
|
||||||
@ -136,7 +136,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="small m-0">
|
<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 />SkyLearn © <script>document.write(new Date().getFullYear());</script>
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user