diff --git a/accounts/admin.py b/accounts/admin.py index e7f44f6e..b1029136 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,19 +1,24 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from .models import User, Student, Parent +@admin.register(User) +class UserAdmin(BaseUserAdmin): + model = User -class UserAdmin(admin.ModelAdmin): - list_display = [ + list_display = ( "get_full_name", "username", "email", "is_active", "is_student", "is_lecturer", + "is_dep_head", "is_parent", "is_staff", - ] - search_fields = [ + ) + + search_fields = ( "username", "first_name", "last_name", @@ -22,14 +27,42 @@ class UserAdmin(admin.ModelAdmin): "is_lecturer", "is_parent", "is_staff", - ] + ) - class Meta: - managed = True - verbose_name = "User" - verbose_name_plural = "Users" + # Edit form fields + fieldsets = ( + (None, {"fields": ("username", "password")}), + ("Personal info", {"fields": ("first_name", "last_name", "email", "gender", "phone", "address", "picture", "program", "department")}), + ("Permissions", {"fields": ("is_staff", "is_active", "is_student", "is_lecturer", "is_parent", "is_dep_head")}), + ) + # Add form fields (important!) + add_fieldsets = ( + (None, { + "classes": ("wide",), + "fields": ( + "username", + "email", + "password1", + "password2", + "first_name", + "last_name", + "gender", + "phone", + "address", + "picture", + "program", + "department", + "is_staff", + "is_active", + "is_student", + "is_lecturer", + "is_parent", + "is_dep_head", # ✅ now visible on Add form + ), + }), + ) -admin.site.register(User, UserAdmin) admin.site.register(Student) admin.site.register(Parent) + diff --git a/accounts/decoratins.py b/accounts/decoratins.py new file mode 100644 index 00000000..e69de29b diff --git a/accounts/decorators.py b/accounts/decorators.py index 97059b30..fefd029d 100644 --- a/accounts/decorators.py +++ b/accounts/decorators.py @@ -1,5 +1,5 @@ from django.shortcuts import redirect - +from django.contrib import messages def admin_required( function=None, @@ -71,3 +71,13 @@ def wrapper(request, *args, **kwargs): return redirect(redirect_to) return wrapper if function else test_func + +def department_head_required(view_func): + def wrapper(request, *args, **kwargs): + if not request.user.is_authenticated: + return redirect("login") + if not request.user.is_dep_head: + messages.error(request, "Access denied") + return redirect("dashboard") + return view_func(request, *args, **kwargs) + return wrapper diff --git a/config/urls.py b/config/urls.py index 74aa797b..478d5ab7 100644 --- a/config/urls.py +++ b/config/urls.py @@ -11,6 +11,7 @@ urlpatterns = [ path("admin/", admin.site.urls), path("i18n/", include("django.conf.urls.i18n")), + path('courses/', include('course.urls')), ] urlpatterns += i18n_patterns( diff --git a/core/admin.py b/core/admin.py index 6bec2bf0..4f4769e2 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,10 @@ from django.contrib import admin from modeltranslation.admin import TranslationAdmin from .models import Session, Semester, NewsAndEvents +from django.contrib import admin +from .models import AcademicCalendar +admin.site.register(AcademicCalendar) class NewsAndEventsAdmin(TranslationAdmin): pass diff --git a/core/migrations/0004_academiccalendar.py b/core/migrations/0004_academiccalendar.py new file mode 100644 index 00000000..1ef419b4 --- /dev/null +++ b/core/migrations/0004_academiccalendar.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.8 on 2025-12-15 09:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_newsandevents_summary_es_newsandevents_summary_fr_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='AcademicCalendar', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('add_drop_start', models.DateField()), + ('add_drop_end', models.DateField()), + ], + ), + ] diff --git a/core/models.py b/core/models.py index b0881fde..09edc25c 100644 --- a/core/models.py +++ b/core/models.py @@ -91,3 +91,14 @@ class ActivityLog(models.Model): def __str__(self): return f"[{self.created_at}]{self.message}" + +class AcademicCalendar(models.Model): + add_drop_start = models.DateField() + add_drop_end = models.DateField() + + def is_add_drop_open(self): + today = timezone.now().date() + return self.add_drop_start <= today <= self.add_drop_end + + def __str__(self): + return "Academic Calendar" diff --git a/course/admin.py b/course/admin.py index 3a304115..c90b525c 100644 --- a/course/admin.py +++ b/course/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.contrib.auth.models import Group -from .models import Program, Course, CourseAllocation, Upload +from .models import Program, Course, CourseAllocation, Upload, Enrollment from modeltranslation.admin import TranslationAdmin class ProgramAdmin(TranslationAdmin): @@ -10,6 +10,10 @@ class CourseAdmin(TranslationAdmin): pass class UploadAdmin(TranslationAdmin): pass +@admin.register(Enrollment) +class EnrollmentAdmin(admin.ModelAdmin): + list_display = ('student', 'course', 'enrolled_on') + list_filter = ('course',) admin.site.register(Program, ProgramAdmin) admin.site.register(Course, CourseAdmin) diff --git a/course/migrations/0005_enrollment.py b/course/migrations/0005_enrollment.py new file mode 100644 index 00000000..9ef2c48d --- /dev/null +++ b/course/migrations/0005_enrollment.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.8 on 2025-12-15 12:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('course', '0004_alter_course_code_alter_course_credit_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Enrollment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('enrolled_on', models.DateTimeField(auto_now_add=True)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('student', 'course')}, + }, + ), + ] diff --git a/course/models.py b/course/models.py index 37377458..727e706a 100644 --- a/course/models.py +++ b/course/models.py @@ -254,3 +254,20 @@ class CourseOffer(models.Model): def __str__(self): return str(self.dep_head) + +class Enrollment(models.Model): + student = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE + ) + course = models.ForeignKey( + Course, + on_delete=models.CASCADE + ) + enrolled_on = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('student', 'course') + + def __str__(self): + return f"{self.student} -> {self.course}" diff --git a/course/templates/course/manage_courses.html b/course/templates/course/manage_courses.html new file mode 100644 index 00000000..982806dc --- /dev/null +++ b/course/templates/course/manage_courses.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block content %} +
{{ message }}
+{% endfor %} + +| Course | +Code | +Program | +
|---|---|---|
| {{ course.title }} | +{{ course.code }} | +{{ course.program }} | +