Files
Student-Management-System-I…/app/admin.py
T

769 lines
27 KiB
Python

from django.contrib import admin
from app.models import (
Institute,
Course,
Subject,
Student,
Staff,
Batch,
Attendance,
AttendanceRecord,
Notice,
Routine,
Staff_leave,
Student_Leave,
StudentFeedback,
CourseTracking,
InstituteFeedback,
StaffInstituteFeedback,
FCMDevice,
)
from django.forms import ModelForm
from django.contrib.auth.models import User, Group
from django.contrib.auth.admin import UserAdmin, GroupAdmin
from app.forms import StaffAdminForm, StudentAdminForm
from django.urls import path
from django.http import JsonResponse
import json
from django.db import transaction
from django.db.models import Q
from datetime import datetime
# Register your models here.
class CustomAdminSite(admin.AdminSite):
def get_app_list(self, request):
# Original app list
app_list = super().get_app_list(request)
model_map = {
# Authentication Models
"User": {
"group": "Authentication",
"group_label": "authentication",
},
"Group": {
"group": "Authentication",
"group_label": "authentication",
},
# Organization Models
"Institute": {
"group": "Organization",
"group_label": "organization",
},
"Batch": {
"group": "Organization",
"group_label": "organization",
},
"Notice": {
"group": "Organization",
"group_label": "organization",
},
"Course": {
"group": "Organization",
"group_label": "organization",
},
"Subject": {
"group": "Organization",
"group_label": "organization",
},
# Human Management Models
"Student": {
"group": "Human Management",
"group_label": "human_management",
},
"Staff": {
"group": "Human Management",
"group_label": "human_management",
},
"Routine": {
"group": "Human Management",
"group_label": "human_management",
},
"Staff_leave": {
"group": "Human Management",
"group_label": "human_management",
},
"Student_Leave": {
"group": "Human Management",
"group_label": "human_management",
},
"StudentFeedback": {
"group": "Human Management",
"group_label": "human_management",
},
"CourseTracking": {
"group": "Human Management",
"group_label": "human_management",
},
"Attendance": {
"group": "Human Management",
"group_label": "human_management",
},
"InstituteFeedback": {
"group": "Human Management",
"group_label": "human_management",
},
"StaffInstituteFeedback": {
"group": "Human Management",
"group_label": "human_management",
},
}
grouped_app_list = [{"name": "Main", "app_label": "dashboard", "models": []}]
grouped_models = {}
for app in app_list:
for model in app["models"]:
model_name = model["object_name"]
if model_name in model_map:
group = model_map[model_name]["group"]
if group not in grouped_models:
grouped_models[group] = {
"name": group,
"app_label": model_map[model_name]["group_label"],
"models": [],
}
grouped_models[group]["models"].append(model)
dashboard_item = {
"name": "Dashboard",
"app_label": "dashboard",
"models": [
{
"name": "Dashboard",
"object_name": "Dashboard",
"perms": {"has_module_perms": True},
"admin_url": "/app/dashboard",
"view_only": True,
}
],
}
grouped_app_list = [dashboard_item] + list(grouped_models.values())
return grouped_app_list
# Create a custom admin site instance
custom_admin_site = CustomAdminSite(name="custom_admin")
@admin.register(Institute, site=custom_admin_site)
class InstituteModel(admin.ModelAdmin):
list_display = ("id", "name", "address", "phone")
search_fields = ("name", "phone")
list_filter = ("name", "phone")
advanced_filter_fields = ("name", "phone")
@admin.register(Course, site=custom_admin_site)
class CourseModel(admin.ModelAdmin):
list_display = ("id", "name", "duration", "duration_type", "get_total_duration")
search_fields = ("name", "duration", "duration_type")
list_filter = ("duration_type", "duration")
advanced_filter_fields = ("name", "duration", "duration_type")
def get_total_duration(self, obj):
if obj.duration_type == "Semester":
return f"{obj.duration} years ({obj.duration * 2} semesters)"
return f"{obj.duration} year{'s' if obj.duration > 1 else ''}"
get_total_duration.short_description = "Total Duration"
fieldsets = (
(None, {"fields": ("name",)}),
(
"Duration Settings",
{
"fields": ("duration", "duration_type"),
"description": "For yearly courses, duration is in years. For semester-based courses, duration is in years (2 semesters per year).",
},
),
)
@admin.register(Batch, site=custom_admin_site)
class BatchAdmin(admin.ModelAdmin):
list_display = ("name", "start_date", "end_date")
search_fields = ("name", "start_date", "end_date")
list_filter = ("name", "start_date", "end_date")
advanced_filter_fields = ("name", "start_date", "end_date")
@admin.register(Subject, site=custom_admin_site)
class SubjectModel(admin.ModelAdmin):
list_display = ("id", "name", "course", "get_period_display", "has_syllabus")
search_fields = ("name", "course__name")
list_filter = ("course", "semester_or_year")
advanced_filter_fields = ("name", "course", "semester_or_year")
def has_syllabus(self, obj):
return bool(obj.syllabus_pdf)
has_syllabus.boolean = True
has_syllabus.short_description = "Has Syllabus"
def get_period_display(self, obj):
if obj.course.duration_type == "Semester":
return f"Semester {obj.semester_or_year}"
return f"Year {obj.semester_or_year}"
get_period_display.short_description = "Period"
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["show_save_and_add_another"] = True
extra_context["show_save"] = False
return super().add_view(request, form_url, extra_context)
def response_add(self, request, obj, post_url_continue=None):
if "_addanother" in request.POST:
# Pre-fill the course and semester fields for the next entry
return self.response_post_save_add(request, obj)
return super().response_add(request, obj, post_url_continue)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if obj and obj.course:
if obj.course.duration_type == "Semester":
form.base_fields["semester_or_year"].label = "Semester"
form.base_fields[
"semester_or_year"
].help_text = f"Enter semester number (1-{obj.course.duration * 2})"
else:
form.base_fields["semester_or_year"].label = "Year"
form.base_fields[
"semester_or_year"
].help_text = f"Enter year number (1-{obj.course.duration})"
# Keep the last used course selected for next entry
if not obj and request.method == "GET":
if "course" in request.GET:
form.base_fields["course"].initial = request.GET["course"]
if "semester_or_year" in request.GET:
form.base_fields["semester_or_year"].initial = request.GET[
"semester_or_year"
]
return form
def response_post_save_add(self, request, obj):
from django.http import HttpResponseRedirect
from django.urls import reverse
opts = self.model._meta
url = reverse(f"admin:{opts.app_label}_{opts.model_name}_add")
return HttpResponseRedirect(
f"{url}?course={obj.course.id}&semester_or_year={obj.semester_or_year}"
)
@admin.register(Student, site=custom_admin_site)
class StudentAdmin(admin.ModelAdmin):
form = StudentAdminForm
list_display = ("id", "name", "phone", "course", "status")
search_fields = ("name", "phone", "course__name")
list_filter = ("course", "status", "gender", "joining_date")
advanced_filter_fields = ("name", "phone", "course", "batch", "status")
def has_view_permission(self, request, obj=None):
"""Check if user has view permission"""
return request.user.has_perm("app.view_student")
def has_add_permission(self, request):
"""Check if user has add permission"""
return request.user.has_perm("app.add_student")
def has_change_permission(self, request, obj=None):
"""Check if user has change permission"""
return request.user.has_perm("app.change_student")
def has_delete_permission(self, request, obj=None):
"""Check if user has delete permission"""
return request.user.has_perm("app.delete_student")
def has_module_permission(self, request):
"""Check if user has any permissions for this app"""
return request.user.has_module_perms("app")
def get_readonly_fields(self, request, obj=None):
if obj:
return self.readonly_fields + ("password",)
return self.readonly_fields
@admin.register(Staff, site=custom_admin_site)
class StaffAdmin(admin.ModelAdmin):
form = StaffAdminForm
list_display = ("id", "name", "phone", "designation", "joining_date")
search_fields = ("name", "phone", "designation")
list_filter = ("designation", "joining_date", "gender")
advanced_filter_fields = ("name", "phone", "designation")
readonly_fields = ("password",)
ordering = ("name",)
filter_horizontal = ("groups",)
def has_view_permission(self, request, obj=None):
"""Check if user has view permission"""
return request.user.has_perm("app.view_staff")
def has_add_permission(self, request):
"""Check if user has add permission"""
return request.user.has_perm("app.add_staff")
def has_change_permission(self, request, obj=None):
"""Check if user has change permission"""
return request.user.has_perm("app.change_staff")
def has_delete_permission(self, request, obj=None):
"""Check if user has delete permission"""
return request.user.has_perm("app.delete_staff")
def has_module_permission(self, request):
"""Check if user has any permissions for this app"""
return request.user.has_module_perms("app")
def get_readonly_fields(self, request, obj=None):
if obj:
return self.readonly_fields + ("password",)
return self.readonly_fields
class AttendanceRecordForm(ModelForm):
def clean(self):
cleaned_data = super().clean()
if not cleaned_data.get("student"):
if self.instance and self.instance.pk:
cleaned_data["student"] = self.instance.student
return cleaned_data
class Meta:
model = AttendanceRecord
fields = "__all__"
class AttendanceRecordInline(admin.TabularInline):
model = AttendanceRecord
form = AttendanceRecordForm
extra = 0
can_delete = False # Prevent deletion
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
return formset
@admin.register(Attendance, site=custom_admin_site)
class AttendanceAdmin(admin.ModelAdmin):
list_display = ["date", "routine", "class_status", "teacher", "teacher_attend"]
inlines = [AttendanceRecordInline]
search_fields = ("date", "routine__name", "teacher__name")
list_filter = ("date", "routine", "teacher")
advanced_filter_fields = ("date", "routine", "teacher")
def save_model(self, request, obj, form, change):
is_new = not obj.pk
super().save_model(request, obj, form, change)
if is_new and obj.routine:
students = Student.objects.filter(routines__routine=obj.routine).distinct()
for student in students:
AttendanceRecord.objects.create(
attendance=obj, student=student, student_attend=False
)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
# Preserve existing records
for instance in instances:
if instance.pk and not instance.student_id:
original = AttendanceRecord.objects.get(pk=instance.pk)
instance.student = original.student
instance.save()
formset.save_m2m()
@admin.register(Notice, site=custom_admin_site)
class NoticeAdmin(admin.ModelAdmin):
list_display = ("title", "message", "created_at")
search_fields = ("title", "message")
list_filter = ("title", "message")
advanced_filter_fields = ("title", "message")
@admin.register(Routine, site=custom_admin_site)
class RoutineAdmin(admin.ModelAdmin):
list_display = [
"course",
"subject",
"teacher",
"start_time",
"end_time",
"semester_or_year",
"is_active",
]
list_filter = ["is_active", "course", "subject", "teacher", "semester_or_year"]
search_fields = ["course__name", "subject__name", "teacher__name"]
ordering = ["start_time"]
change_form_template = "admin/app/routine/change_form.html"
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"add-multiple/",
self.admin_site.admin_view(self.save_routines),
name="routine-add-multiple",
),
]
return custom_urls + urls
def save_routines(self, request):
if request.method == "POST":
try:
data = json.loads(request.body)
routines = data.get("routines", [])
if not routines:
return JsonResponse(
{"success": False, "message": "No routines data provided"}
)
with (
transaction.atomic()
): # Use transaction to ensure all routines are created or none
created_routines = []
for index, routine_data in enumerate(routines):
try:
# Validate required fields
required_fields = [
"course",
"subject",
"teacher",
"start_time",
"end_time",
"semester_or_year",
]
missing_fields = [
field
for field in required_fields
if field not in routine_data
or not routine_data.get(field)
]
if missing_fields:
error_msg = f"Missing required fields: {', '.join(missing_fields)}"
raise ValueError(error_msg)
# Convert IDs to integers
try:
course_id = int(routine_data["course"])
subject_id = int(routine_data["subject"])
teacher_id = int(routine_data["teacher"])
semester = int(routine_data["semester_or_year"])
except (ValueError, TypeError) as e:
raise ValueError(f"Error parsing IDs: {str(e)}")
# Validate time format
try:
start_time = datetime.strptime(
routine_data["start_time"], "%H:%M"
).time()
end_time = datetime.strptime(
routine_data["end_time"], "%H:%M"
).time()
except ValueError as e:
raise ValueError(
"Invalid time format. Please use HH:MM format."
)
# Check for existing routine
existing_routine = Routine.objects.filter(
course_id=course_id,
subject_id=subject_id,
semester_or_year=semester,
start_time=start_time,
end_time=end_time,
).first()
if existing_routine:
# Update existing routine
existing_routine.teacher_id = teacher_id
existing_routine.save()
created_routines.append(existing_routine)
else:
# Create new routine
routine = Routine.objects.create(
course_id=course_id,
subject_id=subject_id,
teacher_id=teacher_id,
start_time=start_time,
end_time=end_time,
semester_or_year=semester,
is_active=True,
)
created_routines.append(routine)
except ValueError as e:
raise # Re-raise to trigger rollback
except Exception as e:
raise # Re-raise to trigger rollback
return JsonResponse(
{
"success": True,
"message": f"Successfully saved {len(created_routines)} routines",
"routines": [
{
"id": r.id,
"course": r.course.name,
"subject": r.subject.name,
"teacher": r.teacher.name,
"start_time": r.start_time.strftime("%H:%M"),
"end_time": r.end_time.strftime("%H:%M"),
"semester_or_year": r.semester_or_year,
}
for r in created_routines
],
}
)
except ValueError as e:
return JsonResponse({"success": False, "message": str(e)})
except json.JSONDecodeError as e:
return JsonResponse(
{"success": False, "message": f"Invalid JSON data: {str(e)}"}
)
except Exception as e:
return JsonResponse({"success": False, "message": str(e)})
return JsonResponse({"success": False, "message": "Invalid request method"})
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "subject":
if request.GET.get("course"):
kwargs["queryset"] = Subject.objects.filter(
course_id=request.GET.get("course"),
semester_or_year=request.GET.get("semester_or_year", 1),
)
else:
kwargs["queryset"] = Subject.objects.none()
elif db_field.name == "teacher":
kwargs["queryset"] = Staff.objects.filter(
Q(designation__icontains="Teacher")
| Q(designation__icontains="Staff")
| Q(designation__icontains="Principal")
| Q(designation__isnull=True)
| Q(designation="")
).order_by("name")
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if not obj: # Only for adding new routines
form.base_fields["course"].widget.attrs["class"] = "select2"
form.base_fields["subject"].widget.attrs["class"] = "select2"
form.base_fields["teacher"].widget.attrs["class"] = "select2"
form.base_fields["semester_or_year"].initial = 1
return form
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["show_save_and_add_another"] = True
return super().add_view(request, form_url, extra_context)
class Media:
css = {
"all": (
"https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css",
)
}
js = ("https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js",)
@admin.register(Staff_leave, site=custom_admin_site)
class StaffLeaveAdmin(admin.ModelAdmin):
list_display = ("staff", "date", "get_status_display", "created_at")
list_filter = ("status", "date", "created_at")
search_fields = ("staff__name", "message")
readonly_fields = ("created_at", "updated_at")
ordering = ("-created_at",)
raw_id_fields = ("staff",)
@admin.register(Student_Leave, site=custom_admin_site)
class StudentLeaveAdmin(admin.ModelAdmin):
list_display = ("student", "date", "get_status_display", "created_at")
list_filter = ("status", "date", "created_at")
search_fields = ("student__name", "message")
readonly_fields = ("created_at", "updated_at")
ordering = ("-created_at",)
raw_id_fields = ("student",)
@admin.register(StudentFeedback, site=custom_admin_site)
class StudentFeedbackAdmin(admin.ModelAdmin):
list_display = ("student", "rating", "created_at")
list_filter = ("rating", "created_at")
search_fields = ("student__name", "feedback_text")
readonly_fields = ("created_at", "updated_at")
ordering = ("-created_at",)
@admin.register(CourseTracking, site=custom_admin_site)
class CourseTrackingAdmin(admin.ModelAdmin):
list_display = (
"student",
"course",
"progress_status",
"completion_percentage",
"start_date",
"expected_end_date",
)
list_filter = ("progress_status", "course", "start_date", "expected_end_date")
search_fields = ("student__name", "course__name", "notes")
readonly_fields = ("enrollment_date", "created_at", "updated_at")
ordering = ("-created_at",)
raw_id_fields = ("student", "course")
list_per_page = 25
def has_view_permission(self, request, obj=None):
return request.user.has_perm("app.view_coursetracking")
def has_add_permission(self, request):
return request.user.has_perm("app.add_coursetracking")
def has_change_permission(self, request, obj=None):
return request.user.has_perm("app.change_coursetracking")
def has_delete_permission(self, request, obj=None):
return request.user.has_perm("app.delete_coursetracking")
def has_module_permission(self, request):
return request.user.has_module_perms("app")
@admin.register(InstituteFeedback, site=custom_admin_site)
class InstituteFeedbackAdmin(admin.ModelAdmin):
list_display = (
"display_name",
"institute",
"feedback_type",
"rating",
"is_anonymous",
"is_public",
"created_at",
)
list_filter = (
"feedback_type",
"rating",
"is_anonymous",
"is_public",
"created_at",
"institute",
)
search_fields = ("user__name", "institute__name", "feedback_text")
readonly_fields = ("created_at", "updated_at")
date_hierarchy = "created_at"
def display_name(self, obj):
return obj.display_name
display_name.short_description = "Student Name"
fieldsets = (
(None, {"fields": ("institute", "user")}),
(
"Feedback Details",
{
"fields": (
"feedback_type",
"rating",
"feedback_text",
"is_anonymous",
"is_public",
)
},
),
(
"Metadata",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
def get_queryset(self, request):
return super().get_queryset(request).select_related("user", "institute")
class Media:
css = {"all": ("admin/css/forms.css",)}
js = ("admin/js/collapse.js",)
@admin.register(StaffInstituteFeedback, site=custom_admin_site)
class StaffInstituteFeedbackAdmin(admin.ModelAdmin):
list_display = (
"display_name",
"institute",
"feedback_type",
"rating",
"is_anonymous",
"is_public",
"created_at",
)
list_filter = (
"feedback_type",
"rating",
"is_anonymous",
"is_public",
"created_at",
"institute",
)
search_fields = ("staff__name", "institute__name", "feedback_text")
readonly_fields = ("created_at", "updated_at")
date_hierarchy = "created_at"
def display_name(self, obj):
return obj.display_name
display_name.short_description = "Staff Name"
fieldsets = (
(None, {"fields": ("institute", "staff")}),
(
"Feedback Details",
{
"fields": (
"feedback_type",
"rating",
"feedback_text",
"is_anonymous",
"is_public",
)
},
),
(
"Metadata",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
def get_queryset(self, request):
return super().get_queryset(request).select_related("staff", "institute")
@admin.register(FCMDevice)
class FCMDeviceAdmin(admin.ModelAdmin):
list_display = ("token",)
search_fields = ("token",)
list_filter = ("token",)
advanced_filter_fields = ("token",)
custom_admin_site.register(User, UserAdmin)
custom_admin_site.register(Group, GroupAdmin)