Compare commits

...

28 Commits

Author SHA1 Message Date
Adil Mohak
d57f57bd50
Merge pull request #14 from liuxi28/fix/template-trans-tag
fix: correct trans tag syntax in password reset template
2025-04-21 12:58:02 +03:00
刘希
8d9bb883cf fix: correct syntax error in {% trans %} block in password_reset_confirm.html 2025-04-06 01:33:39 +08:00
Adil Mohak
233faaa638
Update README.md 2024-11-17 14:29:31 +03:00
Adil Mohak
9c0da926a0 Merge branch 'main' of github.com:SkyCascade/SkyLearn 2024-10-20 20:47:38 +03:00
Adil Mohak
b02e93a5bb Fix: test case conflicts 2024-10-20 20:47:17 +03:00
Adil Mohak
493887f794
Update django.yml 2024-10-20 18:35:20 +03:00
Adil Mohak
d93edbaa10
Update django.yml 2024-10-20 18:15:55 +03:00
Adil Mohak
2433975916
Create django.yml 2024-10-17 09:49:13 +03:00
Adil Mohak
c31a3718d4 Remove REST API 2024-10-13 01:14:07 +03:00
Adil Mohak
e3b9a5e660 Merge branch 'main' of github.com:SkyCascade/SkyLearn 2024-10-13 01:06:51 +03:00
Adil Mohak
6c32d16acb Update fake data generator 2024-10-13 01:06:35 +03:00
Adil Mohak
bdcdbddebe Minor update on data populator 2024-10-12 21:14:07 +03:00
Adil Mohak
f7b5667883
Update README.md 2024-10-09 17:50:00 +03:00
Adil Mohak
ee25eb6ed9
Update README.md 2024-10-06 23:06:31 +03:00
Adil Mohak
36b18b2674 Fix: quiz admin form #2 2024-10-06 22:48:22 +03:00
Adil Mohak
5d80d415fc Fix: dependency conflicts 2024-10-06 17:37:00 +03:00
Adil Mohak
900dc1fdb7 Minor update 2024-10-06 12:23:25 +03:00
Adil Mohak
97b2c8068b Update styles 2024-10-06 12:15:38 +03:00
Adil Mohak
84b25710bb Fix: student result form 2024-10-06 11:29:22 +03:00
Adil Mohak
6f082c08f7 Fix: course add and drop 2024-10-06 11:03:56 +03:00
Adil Mohak
53bb419566 Fix: course registration 2024-10-06 10:52:30 +03:00
Adil Mohak
f51e0bdadc Update TODO 2024-10-06 09:55:33 +03:00
Adil Mohak
9560962aa1
Update README.md 2024-10-06 09:13:24 +03:00
Adil Mohak
c79b9ba5d7
Merge pull request #5 from SkyCascade/fix/pylint
Remove duplicate django-modeltranslation
2024-10-06 08:52:51 +03:00
Adil Mohak
9cf456ddc1
Merge pull request #4 from SkyCascade/fix/pylint
Code format and best practices
2024-10-06 08:46:52 +03:00
Adil Mohak
e7fdc51e8d
Update README.md 2024-10-05 21:50:40 +03:00
Adil Mohak
acbe3c0783
Update README.md 2024-10-05 13:32:31 +03:00
Adil Mohak
3304e6f123
Update README.md 2024-10-05 13:22:35 +03:00
55 changed files with 305 additions and 441 deletions

38
.github/workflows/django.yml vendored Normal file
View 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

View File

@ -1,8 +1,12 @@
![image 9](https://github.com/user-attachments/assets/2cad1d7a-b5ab-4860-9f59-78db710e0289)
![Group 23](https://github.com/user-attachments/assets/4e84251a-27b0-462b-bd5e-fb0bcadc4694)
### The worlds 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

View File

@ -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**:

View File

@ -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__"

View File

@ -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"),
]

View File

@ -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

View File

@ -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/")

View File

@ -1,2 +1,2 @@
# from .test_decorators import AdminRequiredDecoratorTests
# __all__ = [""]
# accounts/tests/__init__.py
# This ensures that tests is treated as a package.

View File

@ -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="")

View File

@ -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")),
)

View File

View File

@ -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__'

View File

View File

View File

View File

View File

View File

@ -390,38 +390,78 @@ 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)
if request.method == "POST":
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")
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)
messages.success(request, "Courses registered successfully!")
return redirect("course_registration")
# 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,)
taken_course_ids = TakenCourse.objects.filter(student=student).values_list(
"course__id", flat=True
)
courses = Course.objects.filter(
program=student.program,
courses = (
Course.objects.filter(
program__pk=student.program.id,
level=student.level,
semester=current_semester.semester,
).exclude(id__in=taken_course_ids)
semester=current_semester,
)
.exclude(id__in=t)
.order_by("year")
)
all_courses = Course.objects.filter(
level=student.level, program__pk=student.program.id
)
registered_courses = Course.objects.filter(id__in=taken_course_ids)
all_courses = Course.objects.filter(level=student.level, program=student.program)
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 = {
"courses": courses,
"registered_courses": registered_courses,
"student": student,
"is_calender_on": True,
"all_courses_are_registered": all_courses_are_registered,
"no_course_is_registered": no_course_is_registered,
"current_semester": current_semester,
"all_courses_registered": all_courses.count() == registered_courses.count(),
"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)
@ -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()

View File

@ -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()

View File

@ -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",)

View File

View File

View File

View File

@ -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

View File

@ -3,3 +3,5 @@
# Code quality
# ------------------------------------------------------------------------------
black==22.12.0 # https://github.com/psf/black
factory-boy==3.3.1
django-extensions==3.2.3

View File

View File

View File

View File

@ -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),
]
)

View File

@ -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:

View File

@ -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)

View File

@ -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())

View File

@ -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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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;
&:hover {
transform: translateX(15px);
}
.class-item p {
p {
padding: 2px;
margin: 0;
color: #b4b4b4;
transition: 0.5s;
}
.class-item a {
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);
}

View File

@ -55,7 +55,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 '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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 &copy; <script>document.write(new Date().getFullYear());</script>
<br />
</p>