From d4696b8fee2e1921f02b97b068368a89e0e4c824 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 26 Mar 2025 00:17:10 +0545 Subject: [PATCH] updates --- app/admin.py | 416 ++++++++-- app/adminViews.py | 724 ---------------- app/apps.py | 4 +- app/backends.py | 82 ++ app/forms.py | 62 +- app/migrations/0001_initial.py | 419 ++++++---- ...tudentfeedback_unique_together_and_more.py | 28 + ...tion_teacher_specialization_on_and_more.py | 40 - .../0003_academicyear_course_subject.py | 64 -- app/migrations/0003_coursetracking.py | 37 + ..._alter_studentfeedback_options_and_more.py | 34 + ...ve_subject_description_subject_duration.py | 26 - ...05_student_academic_year_student_course.py | 36 - .../0006_attendance_attendancereport.py | 57 -- ...7_leavereportstudent_leavereportteacher.py | 48 -- ..._leavereportstudent_leave_date_and_more.py | 56 -- ...ssword_remove_student_username_and_more.py | 29 - ...10_alter_feedbackstudent_feedback_reply.py | 18 - ..._id_feedbackteacher_teacher_id_and_more.py | 23 - ...ademic_year_attendance_academic_year_id.py | 18 - ..._student_password_student_user_and_more.py | 72 -- ...t_user_remove_student_username_and_more.py | 29 - app/models.py | 779 +++++++++++++----- app/studentViews.py | 163 ---- app/teacherViews.py | 7 - app/urls.py | 121 --- app/views.py | 48 +- requirements.txt | 13 +- student_management_system/jazzmin.py | 56 -- student_management_system/settings.py | 198 ++++- student_management_system/urls.py | 15 +- templates/hod/add_academicyear.html | 135 --- templates/hod/add_course.html | 138 ---- templates/hod/add_student.html | 149 ---- templates/hod/add_subject.html | 54 -- templates/hod/add_teacher.html | 100 --- templates/hod/dashboard.html | 55 -- templates/hod/edit_academicyear.html | 94 --- templates/hod/edit_course.html | 152 ---- templates/hod/edit_student.html | 212 ----- templates/hod/edit_subject.html | 54 -- templates/hod/edit_teacher.html | 126 --- templates/hod/manage_academicyear.html | 181 ---- templates/hod/manage_course.html | 194 ----- templates/hod/manage_student.html | 65 -- templates/hod/manage_subject.html | 190 ----- templates/hod/manage_teacher.html | 43 - templates/hod/student_feedback.html | 144 ---- templates/hod/student_leave_view.html | 188 ----- templates/hod/teacher_feedback.html | 115 --- templates/hod/teacher_leave_view.html | 186 ----- templates/hod/view_attendance.html | 208 ----- templates/student/dashboard.html | 83 -- templates/teacher/dashboard.html | 83 -- 54 files changed, 1639 insertions(+), 5032 deletions(-) delete mode 100644 app/adminViews.py create mode 100644 app/backends.py create mode 100644 app/migrations/0002_alter_studentfeedback_unique_together_and_more.py delete mode 100644 app/migrations/0002_rename_specialization_teacher_specialization_on_and_more.py delete mode 100644 app/migrations/0003_academicyear_course_subject.py create mode 100644 app/migrations/0003_coursetracking.py create mode 100644 app/migrations/0004_alter_studentfeedback_options_and_more.py delete mode 100644 app/migrations/0004_remove_subject_description_subject_duration.py delete mode 100644 app/migrations/0005_student_academic_year_student_course.py delete mode 100644 app/migrations/0006_attendance_attendancereport.py delete mode 100644 app/migrations/0007_leavereportstudent_leavereportteacher.py delete mode 100644 app/migrations/0008_alter_leavereportstudent_leave_date_and_more.py delete mode 100644 app/migrations/0009_remove_student_password_remove_student_username_and_more.py delete mode 100644 app/migrations/0010_alter_feedbackstudent_feedback_reply.py delete mode 100644 app/migrations/0011_rename_staff_id_feedbackteacher_teacher_id_and_more.py delete mode 100644 app/migrations/0012_rename_academic_year_attendance_academic_year_id.py delete mode 100644 app/migrations/0013_student_fcm_token_student_password_student_user_and_more.py delete mode 100644 app/migrations/0014_remove_student_user_remove_student_username_and_more.py delete mode 100644 app/studentViews.py delete mode 100644 app/teacherViews.py delete mode 100644 app/urls.py delete mode 100644 student_management_system/jazzmin.py delete mode 100644 templates/hod/add_academicyear.html delete mode 100644 templates/hod/add_course.html delete mode 100644 templates/hod/add_student.html delete mode 100644 templates/hod/add_subject.html delete mode 100644 templates/hod/add_teacher.html delete mode 100644 templates/hod/dashboard.html delete mode 100644 templates/hod/edit_academicyear.html delete mode 100644 templates/hod/edit_course.html delete mode 100644 templates/hod/edit_student.html delete mode 100644 templates/hod/edit_subject.html delete mode 100644 templates/hod/edit_teacher.html delete mode 100644 templates/hod/manage_academicyear.html delete mode 100644 templates/hod/manage_course.html delete mode 100644 templates/hod/manage_student.html delete mode 100644 templates/hod/manage_subject.html delete mode 100644 templates/hod/manage_teacher.html delete mode 100644 templates/hod/student_feedback.html delete mode 100644 templates/hod/student_leave_view.html delete mode 100644 templates/hod/teacher_feedback.html delete mode 100644 templates/hod/teacher_leave_view.html delete mode 100644 templates/hod/view_attendance.html delete mode 100644 templates/student/dashboard.html delete mode 100644 templates/teacher/dashboard.html diff --git a/app/admin.py b/app/admin.py index edc6abc..ba32cee 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,92 +1,384 @@ from django.contrib import admin -from django.shortcuts import redirect -from django.urls import reverse from app.models import ( - Student, - Teacher, - AcademicYear, + Institute, Course, Subject, + Student, + Staff, + Batch, Attendance, - AttendanceReport, - LeaveReportStudent, - LeaveReportTeacher, - FeedbackStudent, - FeedbackTeacher, + AttendanceRecord, + Notice, + Routine, + StudentRoutine, + Staff_leave, + Student_Leave, + StudentFeedback, + CourseTracking, ) +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 # Register your models here. -@admin.register(LeaveReportStudent) -class LeaveReportStudentAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("student_leave_view")) +class CustomAdminSite(admin.AdminSite): + def get_app_list(self, request): + # Original app list + app_list = super().get_app_list(request) + + model_map = { + "User": { + "group": "Authentication", + "group_label": "authentication", + }, + "Group": { + "group": "Authentication", + "group_label": "authentication", + }, + "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"}, + "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", + }, + } + + 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) + + grouped_app_list = list(grouped_models.values()) + + return grouped_app_list -@admin.register(LeaveReportTeacher) -class LeaveReportTeacherAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("teacher_leave_view")) +# Create a custom admin site instance +custom_admin_site = CustomAdminSite(name="custom_admin") -@admin.register(FeedbackTeacher) -class FeedbackTeacherAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("teacher_feedback")) +@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(FeedbackStudent) -class FeedbackStudentAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("student_feedback_message")) +@admin.register(Course, site=custom_admin_site) +class CourseModel(admin.ModelAdmin): + list_display = ("id", "name", "duration", "duration_type") + search_fields = ("name", "duration", "duration_type") + list_filter = ("name", "duration", "duration_type") + advanced_filter_fields = ("name", "duration", "duration_type") -@admin.register(AcademicYear) -class AcademicYearAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("manage_academicyear")) +@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(Attendance) -class AttendanceAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("admin_view_attendance")) +@admin.register(Subject, site=custom_admin_site) +class SubjectModel(admin.ModelAdmin): + list_display = ("id", "name", "course", "semester_or_year") + search_fields = ("name", "course_id", "semester_or_year") + list_filter = ("name", "course_id", "semester_or_year") -@admin.register(AttendanceReport) -class AttendanceReportAdmin(admin.ModelAdmin): - pass - - -@admin.register(Student) +@admin.register(Student, site=custom_admin_site) class StudentAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("manage_student")) + list_display = ("id", "name", "status", "phone") + form = StudentAdminForm + search_fields = ( + "name", + "phone", + "courses", + "gender", + "joining_date", + ) + list_filter = ( + "name", + "phone", + "courses", + "gender", + "joining_date", + "status", + ) + advanced_filter_fields = ( + "name", + "phone", + "courses", + "gender", + "joining_date", + "status", + ) + 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_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 + + class Media: + js = ("js/student_admin.js",) -@admin.register(Teacher) -class TeacherAdmin(admin.ModelAdmin): - def changelist_view(self, request, extra_context=None): - return redirect(reverse("manage_teacher")) +@admin.register(Staff, site=custom_admin_site) +class StaffAdmin(admin.ModelAdmin): + list_display = ["name", "phone", "designation", "joining_date"] + form = StaffAdminForm + search_fields = ( + "name", + "phone", + "monthly_salary", + "designation", + "joining_date", + "gender", + ) + list_filter = ( + "name", + "phone", + "designation", + "joining_date", + "gender", + "groups", + ) + advanced_filter_fields = ( + "name", + "phone", + "designation", + "joining_date", + "gender", + "groups", + ) + 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 Media: + js = ("js/staff_admin.js",) -@admin.register(Course) -class CourseAdmin(admin.ModelAdmin): - list_display = ("id", "name", "course_type", "duration", "created_at", "updated_at") - search_fields = ("name",) - list_filter = ("course_type",) - readonly_fields = ("created_at", "updated_at") +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 - def changelist_view(self, request, extra_context=None): - return redirect(reverse("manage_course")) + class Meta: + model = AttendanceRecord + fields = "__all__" -@admin.register(Subject) -class SubjectAdmin(admin.ModelAdmin): - list_display = ("id", "name", "course", "duration", "created_at", "updated_at") - search_fields = ("name", "course__name") - list_filter = ("course__course_type", "course__duration") - readonly_fields = ("created_at", "updated_at") +class AttendanceRecordInline(admin.TabularInline): + model = AttendanceRecord + form = AttendanceRecordForm + extra = 0 + can_delete = False # Prevent deletion - def changelist_view(self, request, extra_context=None): - return redirect(reverse("manage_subject")) + 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() + + class Media: + js = ("js/attendance_admin.js",) + + +@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") + + class Media: + js = ("js/notice_admin.js",) + + +class StudentRoutineStackInline(admin.StackedInline): + model = StudentRoutine + extra = 0 + + +@admin.register(Routine, site=custom_admin_site) +class RoutineAdmin(admin.ModelAdmin): + inlines = [StudentRoutineStackInline] + list_display = ["name", "start_time", "end_time", "teacher"] + search_fields = ("name", "start_time", "end_time", "teacher__name") + list_filter = ("name", "start_time", "end_time", "teacher") + advanced_filter_fields = ("name", "start_time", "end_time", "teacher") + + class Media: + js = ("js/multiple_student_hover.js", "js/routine_admin.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") + + class Media: + js = ("js/course_tracking_admin.js",) + + + + +custom_admin_site.register(User, UserAdmin) +custom_admin_site.register(Group, GroupAdmin) diff --git a/app/adminViews.py b/app/adminViews.py deleted file mode 100644 index 72d9491..0000000 --- a/app/adminViews.py +++ /dev/null @@ -1,724 +0,0 @@ -import json -import requests -from django.contrib.admin import site -from django.contrib import messages -from django.core.files.storage import FileSystemStorage -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import render, redirect, get_object_or_404 -from django.urls import reverse -from django.views.decorators.csrf import csrf_exempt -from app.models import ( - Student, - Teacher, - Subject, - Course, - LeaveReportStudent, - LeaveReportTeacher, - Attendance, - AttendanceReport, - AcademicYear, - FeedbackStudent, - FeedbackTeacher, -) -from django.contrib.auth.decorators import login_required, user_passes_test -from app.forms import SubjectForm - -admin_site = site - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def admin_home(request): - student_count1 = Student.objects.all().count() - teacher_count = Teacher.objects.all().count() - subject_count = Subject.objects.all().count() - course_count = Course.objects.all().count() - course_all = Course.objects.all() - course_name_list = [] - subject_count_list = [] - student_count_list_in_course = [] - for course in course_all: - subjects = Subject.objects.filter(course_id=course.id).count() - students = Student.objects.filter(course_id=course.id).count() - course_name_list.append(course.name) - subject_count_list.append(subjects) - student_count_list_in_course.append(students) - - # subjects_all = Subject.objects.all() - # subject_list = [] - # student_count_list_in_subject = [] - # for subject in subjects_all: - # course = subject.course - # student_count = Student.objects.filter(course_id=course.id).count() - # subject_list.append(subject.name) - # student_count_list_in_subject.append(student_count) - - # teacher = Teacher.objects.all() - # attendance_present_list_teacher = [] - # attendance_absent_list_teacher = [] - # teacher_name_list = [] - # for teacher in teacher: - # subject_ids = Subject.objects.filter(teacher=teacher).values_list( - # "id", flat=True - # ) - # attendance = Attendance.objects.filter( - # subject_id__in=subject_ids, - # ).count() - # leaves = LeaveReportTeacher.objects.filter( - # teacher_id=teacher.id, leave_status=1 - # ).count() - # attendance_present_list_teacher.append(attendance) - # attendance_absent_list_teacher.append(leaves) - # teacher_name_list.append(teacher.first_name) - - # students_all = Student.objects.all() - # attendance_present_list_student = [] - # attendance_absent_list_student = [] - # student_name_list = [] - # for student in students_all: - # attendance = AttendanceReport.objects.filter( - # student_id=student.id, status=True - # ).count() - # absent = AttendanceReport.objects.filter( - # student_id=student.id, status=False - # ).count() - # leaves = LeaveReportStudent.objects.filter( - # student_id=student.id, leave_status=1 - # ).count() - # attendance_present_list_student.append(attendance) - # attendance_absent_list_student.append(leaves + absent) - # student_name_list.append(student.username) - - context = { - **admin_site.each_context(request), - "title": "Admin Dashboard", - "student_count": student_count1, - "teacher_count": teacher_count, - "subject_count": subject_count, - "course_count": course_count, - # "course_name_list": course_name_list, - "subject_count_list": subject_count_list, - # "student_count_list_in_course": student_count_list_in_course, - # "student_count_list_in_subject": student_count_list_in_subject, - # "subject_list": subject_list, - # "teacher_name_list": teacher_name_list, - # "attendance_present_list_teacher": attendance_present_list_teacher, - # "attendance_absent_list_teacher": attendance_absent_list_teacher, - # "student_name_list": student_name_list, - # "attendance_present_list_student": attendance_present_list_student, - # "attendance_absent_list_student": attendance_absent_list_student, - "opts": Student._meta, - } - return render(request, "hod/dashboard.html", context) - - -def add_course(request): - admin_site = site - if request.method == "POST": - name = request.POST.get("name") - course_type = request.POST.get("course_type") - duration = request.POST.get("duration") - - if not name or not course_type or not duration: - messages.error(request, "All fields are required!") - return redirect("add_course") - - try: - duration = int(duration) - if duration <= 0: - raise ValueError - except ValueError: - messages.error(request, "Duration must be a positive number!") - return redirect("add_course") - - if Course.objects.filter(name=name).exists(): - messages.error(request, "A course with this name already exists.") - return redirect("add_course") - - Course.objects.create(name=name, course_type=course_type, duration=duration) - messages.success(request, "Course added successfully!") - return redirect("add_course") - - context = { - **admin_site.each_context(request), - "title": "Add Course", - "COURSE_TYPE_CHOICES": Course.COURSE_TYPE_CHOICES, - "opts": Course._meta, - } - return render(request, "hod/add_course.html", context) - - -def manage_course(request): - courses = Course.objects.all() - context = { - **admin_site.each_context(request), - "courses": courses, - "title": "Manage Courses", - "opts": Course._meta, - } - return render(request, "hod/manage_course.html", context) - - -def edit_course(request, course_id): - course = get_object_or_404(Course, id=course_id) - - if request.method == "POST": - name = request.POST.get("name") - course_type = request.POST.get("course_type") - duration = request.POST.get("duration") - - # Validate unique name if it changed - if name != course.name and Course.objects.filter(name=name).exists(): - messages.error(request, "A course with this name already exists.") - return render( - request, "edit_course.html", {"course": course, "title": "Edit Course"} - ) - - try: - course.name = name - course.course_type = course_type - course.duration = duration - course.save() - - messages.success(request, "Course updated successfully.") - return redirect("manage_course") - - except Exception as e: - messages.error(request, f"Error updating course: {str(e)}") - context = { - **admin_site.each_context(request), - "title": "Edit Course", - "course": course, - } - return render(request, "hod/edit_course.html", context) - - -def delete_course(request, course_id): - course = Course.objects.get(id=course_id) - try: - course.delete() - messages.success(request, "Course Deleted Successfully.") - return redirect("manage_course") - except: - messages.error(request, "Failed to Delete Course.") - return redirect("manage_course") - - -def add_academicyear(request): - if request.method == "POST": - start_date = request.POST.get("start_date") - academic_year = AcademicYear.objects.create(start_date=start_date) - messages.success(request, "Academic Year added successfully.") - return redirect("manage_academicyear") - context = { - **admin_site.each_context(request), - "title": "Add Academic Year", - # "academic_year": academic_year, - "opts": AcademicYear._meta, - } - return render(request, "hod/add_academicyear.html", context) - - -def edit_academicyear(request, pk): - academic_year = get_object_or_404(AcademicYear, pk=pk) - if request.method == "POST": - start_date = request.POST.get("start_date") - - if start_date: - academic_year.start_date = start_date - academic_year.save() - messages.success(request, "Academic Year updated successfully.") - return redirect("manage_academicyear") - else: - messages.error(request, "Please provide a valid start date.") - context = { - **admin_site.each_context(request), - "title": "Edit Academic Year", - "opts": AcademicYear._meta, - } - return render(request, "hod/edit_academicyear.html", context) - - -def manage_academicyear(request): - context = { - **admin_site.each_context(request), - "academic_years": AcademicYear.objects.all(), - "title": "Manage Academic Year", - "opts": AcademicYear._meta, - } - return render(request, "hod/manage_academicyear.html", context) - - -def delete_academicyear(request, academic_year_id): - academic_year = AcademicYear.objects.get(id=academic_year_id) - try: - academic_year.delete() - messages.success(request, "AcademicYear Deleted Successfully.") - return redirect("manage_academicyear") - except: - messages.error(request, "Failed to Delete AcademicYear.") - return redirect("manage_academicyear") - - -def manage_subjects(request): - subjects = Subject.objects.all() - courses = Course.objects.all() - selected_course_id = request.GET.get("course") - selected_duration = request.GET.get("duration", "") - - if selected_course_id and selected_duration: - subjects = Subject.objects.filter( - course_id=selected_course_id, duration=selected_duration - ) - elif selected_course_id: - subjects = Subject.objects.filter(course_id=selected_course_id) - elif selected_duration: - subjects = Subject.objects.filter(duration=selected_duration) - else: - subjects = Subject.objects.all() - context = { - **admin_site.each_context(request), - "subjects": subjects, - "selected_course_id": selected_course_id, - "selected_duration": selected_duration, - "title": "Manage Subjects", - "courses": courses, - "opts": Subject._meta, - } - return render(request, "hod/manage_subject.html", context) - - -def add_subject(request): - courses = Course.objects.all() - if request.method == "POST": - form = SubjectForm(request.POST) - if form.is_valid(): - form.save() - return redirect("manage_subjects") - else: - form = SubjectForm() - - context = { - **admin_site.each_context(request), - "title": "Add Subject", - "courses": courses, - "form": form, - "opts": Subject._meta, - } - return render(request, "hod/add_subject.html", context) - - -def edit_subject(request, pk): - subject = get_object_or_404(Subject, pk=pk) - courses = Course.objects.all() - if request.method == "POST": - form = SubjectForm(request.POST, instance=subject) - if form.is_valid(): - form.save() - return redirect("manage_subject") - else: - form = SubjectForm(instance=subject) - context = { - **admin_site.each_context(request), - "title": "Add Subject", - "courses": courses, - "form": form, - "opts": Subject._meta, - } - - return render(request, "hod/edit_subject.html", context) - - -def delete_subject(request, subject_id): - subject = Subject.objects.get(id=subject_id) - try: - subject.delete() - messages.success(request, "Subject Deleted Successfully.") - return redirect("manage_subject") - except: - messages.error(request, "Failed to Delete Subject.") - return redirect("manage_subject") - - -def manage_student(request): - search_query = request.GET.get("search", "") - students = Student.objects.all() - - if search_query: - students = ( - students.filter(first_name__icontains=search_query) - | students.filter(last_name__icontains=search_query) - | students.filter(email__icontains=search_query) - ) - - context = { - **admin_site.each_context(request), - "title": "Manage Students", - "students": students, - "search_query": search_query, - } - return render(request, "hod/manage_student.html", context) - - -def add_student(request): - courses = Course.objects.all() - academic_years = AcademicYear.objects.all() - occupation_choices = dict(Student.occupation_choice) - relationship_choices = dict(Student.relationship_choice) - - if request.method == "POST": - first_name = request.POST.get("first_name") - middle_name = request.POST.get("middle_name") - last_name = request.POST.get("last_name") - email = request.POST.get("email") - phone = request.POST.get("phone") - dob = request.POST.get("dob") - gender = request.POST.get("gender") - course_id = request.POST.get("course") - academic_year_id = request.POST.get("academic_year") - address = request.POST.get("address") - father_name = request.POST.get("father_name") - father_occupation = request.POST.get("father_occupation") - mother_name = request.POST.get("mother_name") - mother_occupation = request.POST.get("mother_occupation") - guardian_name = request.POST.get("guardian_name") - guardian_number = request.POST.get("guardian_number") - parents_number = request.POST.get("parents_number") - relationship_with_student = request.POST.get("relationship_with_student") - - try: - course = get_object_or_404(Course, id=course_id) - academic_year = get_object_or_404(AcademicYear, id=academic_year_id) - - Student.objects.create( - first_name=first_name, - middle_name=middle_name, - last_name=last_name, - email=email, - phone=phone, - dob=dob, - gender=gender, - course=course, - academic_year=academic_year, - address=address, - father_name=father_name, - father_occupation=father_occupation, - mother_name=mother_name, - mother_occupation=mother_occupation, - guardian_name=guardian_name, - guardian_number=guardian_number, - parents_number=parents_number, - relationship_with_student=relationship_with_student, - ) - messages.success(request, "Student added successfully!") - return redirect("manage_student") - except Exception as e: - messages.error(request, f"Error adding student: {e}") - - context = { - **admin_site.each_context(request), - "title": "Add Student", - "courses": courses, - "academic_years": academic_years, - "occupation_choices": occupation_choices.items(), - "relationship_choices": relationship_choices.items(), - } - return render(request, "hod/add_student.html", context) - - -def edit_student(request, student_id): - student = get_object_or_404(Student, id=student_id) - courses = Course.objects.all() - academic_years = AcademicYear.objects.all() - occupation_choices = dict(Student.occupation_choice) - relationship_choices = dict(Student.relationship_choice) - - if request.method == "POST": - student.first_name = request.POST.get("first_name") - student.middle_name = request.POST.get("middle_name") - student.last_name = request.POST.get("last_name") - student.email = request.POST.get("email") - student.phone = request.POST.get("phone") - student.dob = request.POST.get("dob") - student.gender = request.POST.get("gender") - student.course = get_object_or_404(Course, id=request.POST.get("course")) - student.academic_year = get_object_or_404( - AcademicYear, id=request.POST.get("academic_year") - ) - student.father_occupation = request.POST.get("father_occupation") - student.relationship_with_student = request.POST.get( - "relationship_with_student" - ) - - try: - student.save() - messages.success(request, "Student updated successfully!") - return redirect("manage_student") - except Exception as e: - messages.error(request, f"Error updating student: {e}") - - context = { - **admin_site.each_context(request), - "title": "Edit Student", - "student": student, - "courses": courses, - "academic_years": academic_years, - "occupation_choices": occupation_choices.items(), - "relationship_choices": relationship_choices.items(), - } - return render(request, "hod/edit_student.html", context) - - -def delete_student(request, student_id): - student = get_object_or_404(Student, id=student_id) - student.delete() - messages.success(request, "Student deleted successfully!") - return redirect("manage_student") - - -def manage_teacher(request): - teachers = Teacher.objects.all() - context = { - **admin_site.each_context(request), - "title": "Manage Teachers", - "teachers": teachers, - } - return render(request, "hod/manage_teacher.html", context) - - -def add_teacher(request): - if request.method == "POST": - try: - Teacher.objects.create( - first_name=request.POST.get("first_name"), - middle_name=request.POST.get("middle_name"), - last_name=request.POST.get("last_name"), - email=request.POST.get("email"), - phone=request.POST.get("phone"), - dob=request.POST.get("dob"), - gender=request.POST.get("gender"), - address=request.POST.get("address"), - # username=request.POST.get("username"), - # password=request.POST.get("password"), - qualification=request.POST.get("qualification"), - specialization_on=request.POST.get("specialization_on"), - marital_status=request.POST.get("marital_status"), - date_of_joining=request.POST.get("date_of_joining"), - ) - messages.success(request, "Teacher added successfully!") - return redirect("manage_teacher") - except Exception as e: - messages.error(request, f"Error adding teacher: {e}") - - context = { - **admin_site.each_context(request), - "title": "Add Teacher", - "qualification_choices": dict(Teacher.qualification_choice).items(), - "marital_status_choices": dict(Teacher.marital_status_choice).items(), - } - return render(request, "hod/add_teacher.html", context) - - -def edit_teacher(request, teacher_id): - teacher = get_object_or_404(Teacher, id=teacher_id) - - if request.method == "POST": - try: - teacher.first_name = request.POST.get("first_name") - teacher.middle_name = request.POST.get("middle_name") - teacher.last_name = request.POST.get("last_name") - teacher.email = request.POST.get("email") - teacher.phone = request.POST.get("phone") - teacher.dob = request.POST.get("dob") - teacher.gender = request.POST.get("gender") - teacher.address = request.POST.get("address") - teacher.qualification = request.POST.get("qualification") - teacher.specialization_on = request.POST.get("specialization_on") - teacher.marital_status = request.POST.get("marital_status") - teacher.date_of_joining = request.POST.get("date_of_joining") - teacher.save() - - messages.success(request, "Teacher updated successfully!") - return redirect("manage_teacher") - except Exception as e: - messages.error(request, f"Error updating teacher: {e}") - - context = { - **admin_site.each_context(request), - "title": "Edit Teacher", - "teacher": teacher, - "qualification_choices": dict(Teacher.qualification_choice).items(), - "marital_status_choices": dict(Teacher.marital_status_choice).items(), - } - return render(request, "hod/edit_teacher.html", context) - - -def delete_teacher(request, teacher_id): - teacher = get_object_or_404(Teacher, id=teacher_id) - teacher.delete() - messages.success(request, "Teacher deleted successfully!") - return redirect("manage_teacher") - - -def student_feedback_message(request): - feedbacks = FeedbackStudent.objects.all() - context = { - **admin_site.each_context(request), - "title": "Student's Feedback", - "feedbacks": feedbacks, - } - return render(request, "hod/student_feedback.html", context) - - -@csrf_exempt -def student_feedback_message_reply(request): - feedback_id = request.POST.get("id") - feedback_reply = request.POST.get("reply") - - try: - feedback = FeedbackStudent.objects.get(id=feedback_id) - feedback.feedback_reply = feedback_reply - feedback.save() - return HttpResponse("True") - - except: - return HttpResponse("False") - - -def teacher_feedback(request): - feedbacks = FeedbackTeacher.objects.all() - context = { - **admin_site.each_context(request), - "title": "Staff's Feedback", - "feedbacks": feedbacks, - } - return render(request, "hod/teacher_feedback.html", context) - - -@csrf_exempt -def teacher_feedback_replied(request): - feedback_id = request.POST.get("id") - feedback_message = request.POST.get("message") - - try: - feedback = FeedbackTeacher.objects.get(id=feedback_id) - feedback.feedback_reply = feedback_message - feedback.save() - return HttpResponse("True") - except Exception: - return HttpResponse("False") - - -def student_leave_view(request): - leaves = LeaveReportStudent.objects.all() - context = { - **admin_site.each_context(request), - "title": "Student Application", - "leaves": leaves, - } - return render(request, "hod/student_leave_view.html", context) - - -def student_leave_approve(request, leave_id): - leave = LeaveReportStudent.objects.get(id=leave_id) - leave.leave_status = 1 - leave.save() - return HttpResponseRedirect(reverse("student_leave_view")) - - -def student_leave_reject(request, leave_id): - leave = LeaveReportStudent.objects.get(id=leave_id) - leave.leave_status = 2 - leave.save() - return HttpResponseRedirect(reverse("student_leave_view")) - - -def teacher_leave_view(request): - leaves = LeaveReportTeacher.objects.all() - context = { - **admin_site.each_context(request), - "title": "Teacher Application", - "leaves": leaves, - } - return render(request, "hod/teacher_leave_view.html", context) - - -def teacher_leave_approve(request, leave_id): - leave = LeaveReportTeacher.objects.get(id=leave_id) - leave.leave_status = 1 - leave.save() - return HttpResponseRedirect(reverse("teacher_leave_view")) - - -def teacher_leave_reject(request, leave_id): - leave = LeaveReportTeacher.objects.get(id=leave_id) - leave.leave_status = 2 - leave.save() - return HttpResponseRedirect(reverse("teacher_leave_view")) - - -def admin_view_attendance(request): - subjects = Subject.objects.all() - academic_years = AcademicYear.objects.all() - context = { - **admin_site.each_context(request), - "subjects": subjects, - "academic_years": academic_years, - } - return render(request, "hod/view_attendance.html", context) - - -@csrf_exempt -def admin_get_attendance_dates(request): - # Getting Values from Ajax POST 'Fetch Student' - subject_id = request.POST.get("subject") - academic_year = request.POST.get("academic_year_id") - - # Students enroll to Course, Course has Subjects - # Getting all data from subject model based on subject_id - subject_model = Subject.objects.get(id=subject_id) - - academic_model = AcademicYear.objects.get(id=academic_year) - - # students = Students.objects.filter(course_id=subject_model.course_id, session_year_id=session_model) - attendance = Attendance.objects.filter( - subject_id=subject_model, academic_year_id=academic_model - ) - - # Only Passing Student Id and Student Name Only - list_data = [] - - for attendance_single in attendance: - data_small = { - "id": attendance_single.id, - "attendance_date": str(attendance_single.attendance_date), - "academic_year_id": attendance_single.academic_year_id.id, - } - list_data.append(data_small) - - return JsonResponse( - json.dumps(list_data), content_type="application/json", safe=False - ) - - -@csrf_exempt -def admin_get_attendance_student(request): - # Getting Values from Ajax POST 'Fetch Student' - attendance_date = request.POST.get("attendance_date") - attendance = Attendance.objects.get(id=attendance_date) - - attendance_data = AttendanceReport.objects.filter(attendance_id=attendance) - # Only Passing Student Id and Student Name Only - list_data = [] - - for student in attendance_data: - data_small = { - "id": student.student_id.id, - "name": student.student_id.first_name + " " + student.student_id.last_name, - "status": student.status, - } - list_data.append(data_small) - - return JsonResponse( - json.dumps(list_data), content_type="application/json", safe=False - ) diff --git a/app/apps.py b/app/apps.py index bcfe39b..ed327d2 100644 --- a/app/apps.py +++ b/app/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class AppConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "app" + default_auto_field = 'django.db.models.BigAutoField' + name = 'app' diff --git a/app/backends.py b/app/backends.py new file mode 100644 index 0000000..7097874 --- /dev/null +++ b/app/backends.py @@ -0,0 +1,82 @@ +from django.contrib.auth.models import User +from django.contrib.auth.backends import ModelBackend +from .models import Staff, Student + +class MultiModelBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + """ + Authenticate the user based on username (phone for Student and Staff) and password. + """ + if username is None or password is None: + return None + + # Try authenticating as Staff + try: + staff = Staff.objects.get(phone=username) + if staff.check_password(password): + return staff + except Staff.DoesNotExist: + pass + + # Try authenticating as Student + try: + student = Student.objects.get(phone=username) + if student.check_password(password): + return student + except Student.DoesNotExist: + pass + + # Return None if no match + return None + + def get_user(self, user_id): + """ + Retrieve a user instance using their ID. + """ + for model in [Staff, Student]: + try: + return model.objects.get(pk=user_id) + except model.DoesNotExist: + continue + return None + + def get_group_permissions(self, user_obj, obj=None): + """ + Return a set of permission strings that the user has through their groups. + """ + if not user_obj.is_active or user_obj.is_anonymous or obj is not None: + return set() + + if isinstance(user_obj, (Staff, Student)): + if not hasattr(user_obj, '_group_perm_cache'): + perms = set() + for group in user_obj.groups.all(): + perms.update( + "%s.%s" % (ct, name) + for ct, name in group.permissions.values_list( + 'content_type__app_label', + 'codename' + ) + ) + user_obj._group_perm_cache = perms + return user_obj._group_perm_cache + else: + return super().get_group_permissions(user_obj, obj) + + def get_all_permissions(self, user_obj, obj=None): + if not user_obj.is_active or user_obj.is_anonymous or obj is not None: + return set() + + if isinstance(user_obj, (Staff, Student)): + return self.get_group_permissions(user_obj, obj) + else: + return super().get_all_permissions(user_obj, obj) + + def has_perm(self, user_obj, perm, obj=None): + if not user_obj.is_active: + return False + + if isinstance(user_obj, (Staff, Student)): + return perm in self.get_all_permissions(user_obj, obj) + else: + return super().has_perm(user_obj, perm, obj) diff --git a/app/forms.py b/app/forms.py index 20a99e4..4faadfb 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,17 +1,45 @@ -from django import forms -from .models import Subject, Course - - -class SubjectForm(forms.ModelForm): - class Meta: - model = Subject - fields = ["name", "course", "duration"] # Include 'name' field here - - # Optionally, you can add custom validation if needed. - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields[ - "course" - ].queryset = ( - Course.objects.all() - ) # Dynamically set the course queryset if needed +from django import forms +from django.contrib.auth.hashers import make_password +from .models import Staff, Student + + +class StudentAdminForm(forms.ModelForm): + new_password = forms.CharField( + required=False, + widget=forms.PasswordInput, + help_text="Enter a new password if you want to reset it. Leave blank otherwise.", + ) + + class Meta: + model = Student + fields = "__all__" + + def save(self, commit=True): + student = super().save(commit=False) + new_password = self.cleaned_data.get("new_password") + if new_password: + student.password = make_password(new_password) + if commit: + student.save() + return student + + +class StaffAdminForm(forms.ModelForm): + new_password = forms.CharField( + required=False, + widget=forms.PasswordInput, + help_text="Enter a new password if you want to reset it. Leave blank otherwise.", + ) + + class Meta: + model = Staff + fields = "__all__" + + def save(self, commit=True): + student = super().save(commit=False) + new_password = self.cleaned_data.get("new_password") + if new_password: + student.password = make_password(new_password) + if commit: + student.save() + return student diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index 2d6690f..5f3fe09 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 5.1.5 on 2025-01-16 16:52 +# Generated by Django 5.1.7 on 2025-03-25 17:13 -import django.core.validators +import django.contrib.auth.models +import django.db.models.deletion from django.db import migrations, models @@ -8,185 +9,257 @@ class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] operations = [ migrations.CreateModel( - name="Student", + name='Batch', fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("first_name", models.CharField(max_length=255)), - ( - "middle_name", - models.CharField(blank=True, max_length=255, null=True), - ), - ("last_name", models.CharField(max_length=255)), - ("username", models.CharField(max_length=255)), - ("password", models.CharField(max_length=255)), - ( - "phone", - models.CharField( - max_length=10, - validators=[ - django.core.validators.RegexValidator("^\\+?1?\\d{9,15}$") - ], - ), - ), - ("email", models.EmailField(max_length=255, unique=True)), - ("dob", models.DateField()), - ( - "gender", - models.CharField( - choices=[("M", "Male"), ("F", "Female")], max_length=1 - ), - ), - ("address", models.CharField(max_length=255)), - ("father_name", models.CharField(max_length=255)), - ("mother_name", models.CharField(max_length=255)), - ( - "parents_number", - models.CharField( - max_length=10, - validators=[ - django.core.validators.RegexValidator("^\\+?1?\\d{9,15}$") - ], - ), - ), - ( - "father_occupation", - models.CharField( - choices=[ - ("business", "Business"), - ("doctor", "Doctor"), - ("farmer", "Farmer"), - ("teacher", "teacher"), - ("public service", "Public Service"), - ("private service", "Private Service"), - ("shopkeeper", "Shopkeeper"), - ("driver", "Driver"), - ("housewife", "HouseWife"), - ("worker", "Worker"), - ("N/A", "N/A"), - ], - default="N/A", - max_length=45, - ), - ), - ( - "mother_occupation", - models.CharField( - choices=[ - ("business", "Business"), - ("doctor", "Doctor"), - ("farmer", "Farmer"), - ("teacher", "teacher"), - ("public service", "Public Service"), - ("private service", "Private Service"), - ("shopkeeper", "Shopkeeper"), - ("driver", "Driver"), - ("housewife", "HouseWife"), - ("worker", "Worker"), - ("N/A", "N/A"), - ], - default="N/A", - max_length=45, - ), - ), - ("guardian_name", models.CharField(max_length=255)), - ( - "guardian_number", - models.CharField( - max_length=10, - validators=[ - django.core.validators.RegexValidator("^\\+?1?\\d{9,15}$") - ], - ), - ), - ( - "relationship_with_student", - models.CharField( - choices=[ - ("father", "Father"), - ("mother", "Mother"), - ("brother", "Brother"), - ("sister", "Sister"), - ("uncle", "Uncle"), - ("aunt", "Aunt"), - ("grandfather", "Grandfather"), - ], - max_length=45, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now_add=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('start_date', models.DateField(blank=True, null=True)), + ('end_date', models.DateField(blank=True, null=True)), + ], + options={ + 'verbose_name': 'Batch', + 'verbose_name_plural': 'Batches', + }, + ), + migrations.CreateModel( + name='Course', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=500)), + ('duration', models.IntegerField(blank=True, default=1, null=True)), + ('duration_type', models.CharField(blank=True, choices=[('Year', 'Yearl Based'), ('Semester', 'Semester Based')], max_length=255, null=True)), + ], + options={ + 'verbose_name': 'Course', + 'verbose_name_plural': 'Courses', + }, + ), + migrations.CreateModel( + name='FCMDevice', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('token', models.TextField(unique=True)), ], ), migrations.CreateModel( - name="Teacher", + name='Institute', fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("first_name", models.CharField(max_length=255)), - ( - "middle_name", - models.CharField(blank=True, max_length=255, null=True), - ), - ("last_name", models.CharField(max_length=255)), - ( - "phone", - models.CharField( - max_length=10, - validators=[ - django.core.validators.RegexValidator("^\\+?1?\\d{9,15}$") - ], - ), - ), - ("email", models.EmailField(max_length=255, unique=True)), - ("dob", models.DateField()), - ( - "gender", - models.CharField( - choices=[("M", "Male"), ("F", "Female")], max_length=1 - ), - ), - ("address", models.CharField(max_length=255)), - ("username", models.CharField(max_length=255)), - ("password", models.CharField(max_length=255)), - ( - "qualification", - models.CharField( - choices=[ - ("BA", "BA"), - ("BBS", "BBS"), - ("BE", "BE"), - ("BEd", "BEd"), - ("BSc", "BSc"), - ("MA", "MA"), - ("MSc", "MSc"), - ("MBS", "MBS"), - ("ME", "ME"), - ("PhD", "PhD"), - ("Others", "Others"), - ], - max_length=45, - ), - ), - ("specialization", models.CharField(max_length=255)), - ( - "marital_status", - models.CharField( - choices=[ - ("married", "Married"), - ("widowed", "Widowed"), - ("separated", "Separated"), - ("divorced", "Divorced"), - ("single", "Single"), - ], - max_length=10, - ), - ), - ("date_of_joining", models.DateField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now_add=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('phone', models.CharField(blank=True, max_length=255, null=True)), + ('email', models.CharField(blank=True, max_length=255, null=True)), + ('address', models.CharField(blank=True, max_length=255, null=True)), + ('pan_no', models.CharField(blank=True, max_length=255, null=True)), + ('reg_no', models.CharField(blank=True, max_length=255, null=True)), + ('logo', models.ImageField(blank=True, null=True, upload_to='image')), + ('description', models.TextField(blank=True, null=True)), + ], + options={ + 'verbose_name': 'Institute', + 'verbose_name_plural': 'Institutes', + }, + ), + migrations.CreateModel( + name='Notice', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('image', models.ImageField(blank=True, null=True, upload_to='notice')), + ('message', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'verbose_name': 'Notice', + 'verbose_name_plural': 'Notices', + }, + ), + migrations.CreateModel( + name='Staff', + fields=[ + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_active', models.BooleanField(default=True)), + ('is_superuser', models.BooleanField(default=False)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('phone', models.CharField(blank=True, max_length=255, null=True, unique=True)), + ('designation', models.CharField(blank=True, max_length=255, null=True)), + ('gender', models.CharField(blank=True, choices=[('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')], max_length=255, null=True)), + ('birth_date', models.DateField(blank=True, null=True)), + ('email', models.CharField(blank=True, max_length=255, null=True)), + ('temporary_address', models.CharField(blank=True, max_length=255, null=True)), + ('permanent_address', models.CharField(blank=True, max_length=255, null=True)), + ('marital_status', models.CharField(blank=True, choices=[('Married', 'Married'), ('Unmarried', 'Unmarried'), ('Divorced', 'Divorced')], max_length=255, null=True)), + ('joining_date', models.DateField(blank=True, null=True)), + ('password', models.CharField(editable=False, max_length=128, null=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this staff belongs to.', related_name='staffs', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this staff.', related_name='staff_set', related_query_name='staff', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'Staff', + 'verbose_name_plural': 'Staffs', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), ], ), + migrations.CreateModel( + name='Routine', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('start_time', models.TimeField(blank=True, null=True)), + ('end_time', models.TimeField(blank=True, null=True)), + ('teacher', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='routines', to='app.staff')), + ], + options={ + 'verbose_name': 'Routine', + 'verbose_name_plural': 'Routines', + }, + ), + migrations.CreateModel( + name='Attendance', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('date', models.DateField()), + ('teacher_attend', models.BooleanField(default=True)), + ('class_status', models.BooleanField(default=True)), + ('routine', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='app.routine')), + ('teacher', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='app.staff')), + ], + options={ + 'verbose_name': 'Attendance', + 'verbose_name_plural': 'Attendances', + }, + ), + migrations.CreateModel( + name='Staff_leave', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('message', models.TextField()), + ('status', models.IntegerField(choices=[(0, 'Pending'), (1, 'Approved'), (2, 'Rejected')], default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('staff', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.staff')), + ], + options={ + 'verbose_name': 'Staff Leave', + 'verbose_name_plural': 'Staff Leaves', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='Student', + fields=[ + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('status', models.CharField(blank=True, choices=[('Active', 'Active'), ('Leave', 'Leave'), ('Completed', 'Completed')], max_length=255, null=True)), + ('gender', models.CharField(blank=True, choices=[('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')], max_length=255, null=True)), + ('birth_date', models.DateField(blank=True, null=True)), + ('email', models.CharField(blank=True, max_length=255, null=True)), + ('phone', models.CharField(blank=True, max_length=255, null=True, unique=True)), + ('temporary_address', models.CharField(blank=True, max_length=500, null=True)), + ('permanent_address', models.CharField(blank=True, max_length=500, null=True)), + ('parent_name', models.CharField(blank=True, max_length=255, null=True)), + ('parent_phone', models.CharField(blank=True, max_length=255, null=True)), + ('joining_date', models.DateField(blank=True, null=True)), + ('password', models.CharField(editable=False, max_length=128, null=True)), + ('courses', models.ManyToManyField(blank=True, related_name='students', to='app.course')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this student belongs to.', related_name='students', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this student.', related_name='student_set', related_query_name='student', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'Student', + 'verbose_name_plural': 'Students', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='AttendanceRecord', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('student_attend', models.BooleanField(default=False)), + ('attendance', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='records', to='app.attendance')), + ('student', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attendance_records', to='app.student')), + ], + ), + migrations.CreateModel( + name='Student_Leave', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('message', models.TextField()), + ('status', models.IntegerField(choices=[(0, 'Pending'), (1, 'Approved'), (2, 'Rejected')], default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), + ], + options={ + 'verbose_name': 'Student Leave', + 'verbose_name_plural': 'Student Leaves', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='StudentRoutine', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('course', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='app.course')), + ('routine', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='students', to='app.routine')), + ('student', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='routines', to='app.student')), + ], + ), + migrations.CreateModel( + name='Subject', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('semester_or_year', models.PositiveIntegerField()), + ('course', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='app.course')), + ], + ), + migrations.CreateModel( + name='StudentFeedback', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.IntegerField(choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])), + ('feedback_text', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.student')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.staff')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), + ], + options={ + 'verbose_name': 'Student Feedback', + 'verbose_name_plural': 'Student Feedbacks', + 'unique_together': {('student', 'subject', 'teacher')}, + }, + ), + migrations.CreateModel( + name='TeacherFeedback', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('feedback_text', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.staff')), + ], + options={ + 'verbose_name': 'Teacher Feedback', + 'verbose_name_plural': 'Teacher Feedbacks', + 'unique_together': {('teacher', 'subject')}, + }, + ), ] diff --git a/app/migrations/0002_alter_studentfeedback_unique_together_and_more.py b/app/migrations/0002_alter_studentfeedback_unique_together_and_more.py new file mode 100644 index 0000000..c7b952a --- /dev/null +++ b/app/migrations/0002_alter_studentfeedback_unique_together_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.1.7 on 2025-03-25 17:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='studentfeedback', + unique_together=set(), + ), + migrations.DeleteModel( + name='TeacherFeedback', + ), + migrations.RemoveField( + model_name='studentfeedback', + name='subject', + ), + migrations.RemoveField( + model_name='studentfeedback', + name='teacher', + ), + ] diff --git a/app/migrations/0002_rename_specialization_teacher_specialization_on_and_more.py b/app/migrations/0002_rename_specialization_teacher_specialization_on_and_more.py deleted file mode 100644 index 8555cb1..0000000 --- a/app/migrations/0002_rename_specialization_teacher_specialization_on_and_more.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 16:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0001_initial"), - ] - - operations = [ - migrations.RenameField( - model_name="teacher", - old_name="specialization", - new_name="specialization_on", - ), - migrations.AlterField( - model_name="teacher", - name="qualification", - field=models.CharField( - choices=[ - ("SLC", "SLC"), - ("Intermediate", "Intermediate"), - ("BA", "BA"), - ("BBS", "BBS"), - ("BE", "BE"), - ("BEd", "BEd"), - ("BSc", "BSc"), - ("MA", "MA"), - ("MSc", "MSc"), - ("MBS", "MBS"), - ("ME", "ME"), - ("PhD", "PhD"), - ("Others", "Others"), - ], - max_length=45, - ), - ), - ] diff --git a/app/migrations/0003_academicyear_course_subject.py b/app/migrations/0003_academicyear_course_subject.py deleted file mode 100644 index 0fe5aa9..0000000 --- a/app/migrations/0003_academicyear_course_subject.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:12 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0002_rename_specialization_teacher_specialization_on_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="AcademicYear", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("start_date", models.DateField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.CreateModel( - name="Course", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("name", models.CharField(max_length=255, unique=True)), - ( - "course_type", - models.CharField( - choices=[ - ("semester", "Semester-based"), - ("year", "Year-based"), - ], - max_length=10, - ), - ), - ( - "duration", - models.PositiveIntegerField(help_text="Duration in months"), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - ), - migrations.CreateModel( - name="Subject", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("name", models.CharField(max_length=255, unique=True)), - ("description", models.TextField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "course", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="subjects", - to="app.course", - ), - ), - ], - ), - ] diff --git a/app/migrations/0003_coursetracking.py b/app/migrations/0003_coursetracking.py new file mode 100644 index 0000000..735e412 --- /dev/null +++ b/app/migrations/0003_coursetracking.py @@ -0,0 +1,37 @@ +# Generated by Django 5.1.7 on 2025-03-25 18:18 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_alter_studentfeedback_unique_together_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='CourseTracking', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('enrollment_date', models.DateField(auto_now_add=True)), + ('start_date', models.DateField(blank=True, null=True)), + ('expected_end_date', models.DateField(blank=True, null=True)), + ('actual_end_date', models.DateField(blank=True, null=True)), + ('progress_status', models.CharField(choices=[('Not Started', 'Not Started'), ('In Progress', 'In Progress'), ('Completed', 'Completed'), ('Dropped', 'Dropped')], default='Not Started', max_length=20)), + ('completion_percentage', models.IntegerField(default=0, help_text='Percentage of course completion (0-100)')), + ('notes', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_trackings', to='app.course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_trackings', to='app.student')), + ], + options={ + 'verbose_name': 'Course Tracking', + 'verbose_name_plural': 'Course Trackings', + 'ordering': ['-created_at'], + 'unique_together': {('student', 'course')}, + }, + ), + ] diff --git a/app/migrations/0004_alter_studentfeedback_options_and_more.py b/app/migrations/0004_alter_studentfeedback_options_and_more.py new file mode 100644 index 0000000..d67251e --- /dev/null +++ b/app/migrations/0004_alter_studentfeedback_options_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.1.7 on 2025-03-25 18:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_coursetracking'), + ] + + operations = [ + migrations.AlterModelOptions( + name='studentfeedback', + options={'ordering': ['-created_at'], 'verbose_name': 'Student Feedback', 'verbose_name_plural': 'Student Feedbacks'}, + ), + migrations.AddField( + model_name='studentfeedback', + name='subject', + field=models.ForeignKey(default='1', on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.subject'), + preserve_default=False, + ), + migrations.AddField( + model_name='studentfeedback', + name='teacher', + field=models.ForeignKey(default='1', on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.staff'), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='studentfeedback', + unique_together={('student', 'subject', 'teacher')}, + ), + ] diff --git a/app/migrations/0004_remove_subject_description_subject_duration.py b/app/migrations/0004_remove_subject_description_subject_duration.py deleted file mode 100644 index 98e7897..0000000 --- a/app/migrations/0004_remove_subject_description_subject_duration.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0003_academicyear_course_subject"), - ] - - operations = [ - migrations.RemoveField( - model_name="subject", - name="description", - ), - migrations.AddField( - model_name="subject", - name="duration", - field=models.PositiveIntegerField( - default="1", - help_text="For semester-based courses, specify the semester number. For year-based courses, specify the year.", - ), - preserve_default=False, - ), - ] diff --git a/app/migrations/0005_student_academic_year_student_course.py b/app/migrations/0005_student_academic_year_student_course.py deleted file mode 100644 index 3af3ac7..0000000 --- a/app/migrations/0005_student_academic_year_student_course.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:35 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0004_remove_subject_description_subject_duration"), - ] - - operations = [ - migrations.AddField( - model_name="student", - name="academic_year", - field=models.ForeignKey( - default="1", - on_delete=django.db.models.deletion.CASCADE, - related_name="students", - to="app.academicyear", - ), - preserve_default=False, - ), - migrations.AddField( - model_name="student", - name="course", - field=models.ForeignKey( - default="1", - on_delete=django.db.models.deletion.CASCADE, - related_name="students", - to="app.course", - ), - preserve_default=False, - ), - ] diff --git a/app/migrations/0006_attendance_attendancereport.py b/app/migrations/0006_attendance_attendancereport.py deleted file mode 100644 index c07a907..0000000 --- a/app/migrations/0006_attendance_attendancereport.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:37 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0005_student_academic_year_student_course"), - ] - - operations = [ - migrations.CreateModel( - name="Attendance", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("attendance_date", models.DateField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "academic_year", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="app.academicyear", - ), - ), - ( - "subject_id", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, to="app.subject" - ), - ), - ], - ), - migrations.CreateModel( - name="AttendanceReport", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("status", models.BooleanField(default=False)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now_add=True)), - ( - "attendance_id", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="app.attendance" - ), - ), - ( - "student_id", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, to="app.student" - ), - ), - ], - ), - ] diff --git a/app/migrations/0007_leavereportstudent_leavereportteacher.py b/app/migrations/0007_leavereportstudent_leavereportteacher.py deleted file mode 100644 index 0010b39..0000000 --- a/app/migrations/0007_leavereportstudent_leavereportteacher.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:44 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0006_attendance_attendancereport"), - ] - - operations = [ - migrations.CreateModel( - name="LeaveReportStudent", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("leave_date", models.CharField(max_length=255)), - ("leave_message", models.TextField()), - ("leave_status", models.IntegerField(default=0)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "student_id", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="app.student" - ), - ), - ], - ), - migrations.CreateModel( - name="LeaveReportTeacher", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("leave_date", models.CharField(max_length=255)), - ("leave_message", models.TextField()), - ("leave_status", models.IntegerField(default=0)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "teacher_id", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="app.teacher" - ), - ), - ], - ), - ] diff --git a/app/migrations/0008_alter_leavereportstudent_leave_date_and_more.py b/app/migrations/0008_alter_leavereportstudent_leave_date_and_more.py deleted file mode 100644 index a749a6e..0000000 --- a/app/migrations/0008_alter_leavereportstudent_leave_date_and_more.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-16 17:46 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0007_leavereportstudent_leavereportteacher"), - ] - - operations = [ - migrations.AlterField( - model_name="leavereportstudent", - name="leave_date", - field=models.DateField(), - ), - migrations.AlterField( - model_name="leavereportteacher", - name="leave_date", - field=models.DateField(), - ), - migrations.CreateModel( - name="FeedBackStudent", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("feedback", models.TextField()), - ("feedback_reply", models.TextField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "student_id", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="app.student" - ), - ), - ], - ), - migrations.CreateModel( - name="FeedBackTeacher", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("feedback", models.TextField()), - ("feedback_reply", models.TextField()), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "staff_id", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="app.teacher" - ), - ), - ], - ), - ] diff --git a/app/migrations/0009_remove_student_password_remove_student_username_and_more.py b/app/migrations/0009_remove_student_password_remove_student_username_and_more.py deleted file mode 100644 index bab96fc..0000000 --- a/app/migrations/0009_remove_student_password_remove_student_username_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 06:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0008_alter_leavereportstudent_leave_date_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="student", - name="password", - ), - migrations.RemoveField( - model_name="student", - name="username", - ), - migrations.RemoveField( - model_name="teacher", - name="password", - ), - migrations.RemoveField( - model_name="teacher", - name="username", - ), - ] diff --git a/app/migrations/0010_alter_feedbackstudent_feedback_reply.py b/app/migrations/0010_alter_feedbackstudent_feedback_reply.py deleted file mode 100644 index 4421ca8..0000000 --- a/app/migrations/0010_alter_feedbackstudent_feedback_reply.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 07:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0009_remove_student_password_remove_student_username_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="feedbackstudent", - name="feedback_reply", - field=models.TextField(blank=True, null=True), - ), - ] diff --git a/app/migrations/0011_rename_staff_id_feedbackteacher_teacher_id_and_more.py b/app/migrations/0011_rename_staff_id_feedbackteacher_teacher_id_and_more.py deleted file mode 100644 index 667d5b0..0000000 --- a/app/migrations/0011_rename_staff_id_feedbackteacher_teacher_id_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 09:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0010_alter_feedbackstudent_feedback_reply"), - ] - - operations = [ - migrations.RenameField( - model_name="feedbackteacher", - old_name="staff_id", - new_name="teacher_id", - ), - migrations.AlterField( - model_name="feedbackteacher", - name="feedback_reply", - field=models.TextField(blank=True, null=True), - ), - ] diff --git a/app/migrations/0012_rename_academic_year_attendance_academic_year_id.py b/app/migrations/0012_rename_academic_year_attendance_academic_year_id.py deleted file mode 100644 index e322820..0000000 --- a/app/migrations/0012_rename_academic_year_attendance_academic_year_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 09:48 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0011_rename_staff_id_feedbackteacher_teacher_id_and_more"), - ] - - operations = [ - migrations.RenameField( - model_name="attendance", - old_name="academic_year", - new_name="academic_year_id", - ), - ] diff --git a/app/migrations/0013_student_fcm_token_student_password_student_user_and_more.py b/app/migrations/0013_student_fcm_token_student_password_student_user_and_more.py deleted file mode 100644 index d44d748..0000000 --- a/app/migrations/0013_student_fcm_token_student_password_student_user_and_more.py +++ /dev/null @@ -1,72 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 10:24 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0012_rename_academic_year_attendance_academic_year_id"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="student", - name="fcm_token", - field=models.TextField(default=""), - ), - migrations.AddField( - model_name="student", - name="password", - field=models.CharField(default="1", max_length=255), - preserve_default=False, - ), - migrations.AddField( - model_name="student", - name="user", - field=models.OneToOneField( - default="1", - on_delete=django.db.models.deletion.CASCADE, - related_name="student", - to=settings.AUTH_USER_MODEL, - ), - preserve_default=False, - ), - migrations.AddField( - model_name="student", - name="username", - field=models.CharField(default="1", max_length=255), - preserve_default=False, - ), - migrations.AddField( - model_name="teacher", - name="fcm_token", - field=models.TextField(default=""), - ), - migrations.AddField( - model_name="teacher", - name="password", - field=models.CharField(default="1", max_length=255), - preserve_default=False, - ), - migrations.AddField( - model_name="teacher", - name="user", - field=models.OneToOneField( - default="1", - on_delete=django.db.models.deletion.CASCADE, - related_name="teacher", - to=settings.AUTH_USER_MODEL, - ), - preserve_default=False, - ), - migrations.AddField( - model_name="teacher", - name="username", - field=models.CharField(default="1", max_length=255), - preserve_default=False, - ), - ] diff --git a/app/migrations/0014_remove_student_user_remove_student_username_and_more.py b/app/migrations/0014_remove_student_user_remove_student_username_and_more.py deleted file mode 100644 index b357d8a..0000000 --- a/app/migrations/0014_remove_student_user_remove_student_username_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-18 10:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("app", "0013_student_fcm_token_student_password_student_user_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="student", - name="user", - ), - migrations.RemoveField( - model_name="student", - name="username", - ), - migrations.RemoveField( - model_name="teacher", - name="user", - ), - migrations.RemoveField( - model_name="teacher", - name="username", - ), - ] diff --git a/app/models.py b/app/models.py index 75fc506..af22620 100644 --- a/app/models.py +++ b/app/models.py @@ -1,233 +1,608 @@ from django.db import models -from django.core.validators import RegexValidator, ValidationError -from django.contrib.auth.models import User +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import AbstractUser +from django.utils.safestring import mark_safe +from django.utils import timezone +from datetime import date +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.core.cache import cache + # Create your models here. +duration_types_choices = (("Year", "Yearl Based"), ("Semester", "Semester Based")) +student_status_choices = ( + ("Active", "Active"), + ("Leave", "Leave"), + ("Completed", "Completed"), +) +gender_choices = (("Male", "Male"), ("Female", "Female"), ("Other", "Other")) +marital_status_choices = ( + ("Married", "Married"), + ("Unmarried", "Unmarried"), + ("Divorced", "Divorced"), +) -class Student(models.Model): - id = models.AutoField(primary_key=True) - first_name = models.CharField(max_length=255) - middle_name = models.CharField(max_length=255, blank=True, null=True) - last_name = models.CharField(max_length=255) - course = models.ForeignKey( - "Course", on_delete=models.CASCADE, related_name="students" - ) - academic_year = models.ForeignKey( - "AcademicYear", on_delete=models.CASCADE, related_name="students" - ) - password = models.CharField(max_length=255) - fcm_token = models.TextField(default="") - phone = models.CharField( - max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] - ) - email = models.EmailField(max_length=255, unique=True) - dob = models.DateField() - gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) - address = models.CharField(max_length=255) - father_name = models.CharField(max_length=255) - mother_name = models.CharField(max_length=255) - parents_number = models.CharField( - max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] - ) - occupation_choice = ( - ("business", "Business"), - ("doctor", "Doctor"), - ("farmer", "Farmer"), - ("teacher", "teacher"), - ("public service", "Public Service"), - ("private service", "Private Service"), - ("shopkeeper", "Shopkeeper"), - ("driver", "Driver"), - ("housewife", "HouseWife"), - ("worker", "Worker"), - ("N/A", "N/A"), - ) - father_occupation = models.CharField( - choices=occupation_choice, max_length=45, default="N/A" - ) - mother_occupation = models.CharField( - choices=occupation_choice, max_length=45, default="N/A" - ) - guardian_name = models.CharField(max_length=255) - guardian_number = models.CharField( - max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] - ) - relationship_choice = ( - ("father", "Father"), - ("mother", "Mother"), - ("brother", "Brother"), - ("sister", "Sister"), - ("uncle", "Uncle"), - ("aunt", "Aunt"), - ("grandfather", "Grandfather"), - ) - relationship_with_student = models.CharField( - choices=relationship_choice, max_length=45 - ) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) - - -class Teacher(models.Model): - id = models.AutoField(primary_key=True) - first_name = models.CharField(max_length=255) - middle_name = models.CharField(max_length=255, blank=True, null=True) - last_name = models.CharField(max_length=255) - phone = models.CharField( - max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] - ) - email = models.EmailField(max_length=255, unique=True) - dob = models.DateField() - gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) - address = models.CharField(max_length=255) - fcm_token = models.TextField(default="") - password = models.CharField(max_length=255) - qualification_choice = ( - ("SLC", "SLC"), - ("Intermediate", "Intermediate"), - ("BA", "BA"), - ("BBS", "BBS"), - ("BE", "BE"), - ("BEd", "BEd"), - ("BSc", "BSc"), - ("MA", "MA"), - ("MSc", "MSc"), - ("MBS", "MBS"), - ("ME", "ME"), - ("PhD", "PhD"), - ("Others", "Others"), - ) - qualification = models.CharField(choices=qualification_choice, max_length=45) - specialization_on = models.CharField(max_length=255) - marital_status_choice = ( - ("married", "Married"), - ("widowed", "Widowed"), - ("separated", "Separated"), - ("divorced", "Divorced"), - ("single", "Single"), - ) - marital_status = models.CharField(choices=marital_status_choice, max_length=10) - date_of_joining = models.DateField() - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) +class Institute(models.Model): + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=255) + phone = models.CharField(max_length=255, null=True, blank=True) + email = models.CharField(max_length=255, null=True, blank=True) + address = models.CharField(max_length=255, null=True, blank=True) + pan_no = models.CharField(max_length=255, null=True, blank=True) + reg_no = models.CharField(max_length=255, null=True, blank=True) + logo = models.ImageField(upload_to="image", null=True, blank=True) + description = models.TextField(null=True, blank=True) def __str__(self): - return self.first_name + "" + self.middle_name + "" + self.last_name + return self.name - -class AcademicYear(models.Model): - id = models.AutoField(primary_key=True) - start_date = models.DateField() - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return str(self.start_date) + class Meta: + verbose_name = "Institute" + verbose_name_plural = "Institutes" class Course(models.Model): - SEMESTER = "semester" - YEAR = "year" - - COURSE_TYPE_CHOICES = [ - (SEMESTER, "Semester-based"), - (YEAR, "Year-based"), - ] - - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=255, unique=True) - course_type = models.CharField(max_length=10, choices=COURSE_TYPE_CHOICES) - duration = models.PositiveIntegerField(help_text="Duration in months") - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=500) + duration = models.IntegerField(default=1, null=True, blank=True) + duration_type = models.CharField( + max_length=255, choices=duration_types_choices, null=True, blank=True + ) def __str__(self): - return f"{self.name} ({self.get_course_type_display()})" + return self.name + + class Meta: + verbose_name = "Course" + verbose_name_plural = "Courses" class Subject(models.Model): id = models.AutoField(primary_key=True) - name = models.CharField(max_length=255, unique=True) - course = models.ForeignKey( - Course, on_delete=models.CASCADE, related_name="subjects" + name = models.CharField(max_length=255) + course = models.ForeignKey(Course, on_delete=models.CASCADE, default=1) + semester_or_year = models.PositiveIntegerField() + + def __str__(self): + return self.name + + +class Student(AbstractUser): + first_name = None + last_name = None + is_superuser = None + date_joined = None + username = None + email = None + + user_permissions = models.ManyToManyField( + "auth.Permission", + verbose_name="user permissions", + blank=True, + help_text="Specific permissions for this student.", + related_name="student_set", # This fixes the clash + related_query_name="student", ) - duration = models.PositiveIntegerField( - help_text="For semester-based courses, specify the semester number. For year-based courses, specify the year." + + # Similarly, let's also update groups to be explicit + groups = models.ManyToManyField( + "auth.Group", + verbose_name="groups", + blank=True, + help_text="The groups this student belongs to.", + related_name="students", # This matches your existing related_name ) + + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=255) + status = models.CharField( + max_length=255, choices=student_status_choices, null=True, blank=True + ) + gender = models.CharField( + max_length=255, choices=gender_choices, null=True, blank=True + ) + birth_date = models.DateField(null=True, blank=True) + email = models.CharField(max_length=255, null=True, blank=True) + phone = models.CharField(max_length=255, null=True, blank=True, unique=True) + temporary_address = models.CharField(max_length=500, null=True, blank=True) + permanent_address = models.CharField(max_length=500, null=True, blank=True) + parent_name = models.CharField(max_length=255, null=True, blank=True) + parent_phone = models.CharField(max_length=255, null=True, blank=True) + courses = models.ManyToManyField(Course, related_name="students", blank=True) + joining_date = models.DateField(null=True, blank=True) + password = models.CharField( + max_length=128, editable=False, null=True + ) # Editable is set to False to prevent direct editing + + USERNAME_FIELD = "phone" + REQUIRED_FIELDS = ["name"] # Add required fields + + def __str__(self): + return self.name + + def set_password(self, raw_password): + self.password = make_password(raw_password) + self.save(update_fields=["password"]) + + @property + def is_staff(self): + """Check if student has any groups (permissions)""" + return self.groups.exists() + + def has_perm(self, perm, obj=None): + """Check if student has a specific permission""" + if not self.is_active: + return False + + # Get permissions from groups + perms = set() + for group in self.groups.all(): + perms.update( + group.permissions.values_list("content_type__app_label", "codename") + ) + + # Convert permission string to app_label and codename + app_label, codename = perm.split(".") + return (app_label, codename) in perms + + def has_module_perms(self, app_label): + """Check if student has any permissions in the given app""" + if not self.is_active: + return False + + # Check if any group permissions match the app_label + for group in self.groups.all(): + if group.permissions.filter(content_type__app_label=app_label).exists(): + return True + return False + + class Meta: + verbose_name = "Student" + verbose_name_plural = "Students" + + +class Staff(AbstractUser): + first_name = None + last_name = None + date_joined = None + username = None + email = None + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=True) + is_superuser = models.BooleanField(default=False) + + user_permissions = models.ManyToManyField( + "auth.Permission", + verbose_name="user permissions", + blank=True, + help_text="Specific permissions for this staff.", + related_name="staff_set", # This fixes the clash + related_query_name="staff", + ) + + # Similarly, let's also update groups to be explicit + groups = models.ManyToManyField( + "auth.Group", + verbose_name="groups", + blank=True, + help_text="The groups this staff belongs to.", + related_name="staffs", # This matches your existing related_name + ) + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=255) + phone = models.CharField(max_length=255, null=True, blank=True, unique=True) + designation = models.CharField(max_length=255, null=True, blank=True) + gender = models.CharField( + max_length=255, choices=gender_choices, null=True, blank=True + ) + birth_date = models.DateField(null=True, blank=True) + email = models.CharField(max_length=255, null=True, blank=True) + temporary_address = models.CharField(max_length=255, null=True, blank=True) + permanent_address = models.CharField(max_length=255, null=True, blank=True) + marital_status = models.CharField( + max_length=255, choices=marital_status_choices, null=True, blank=True + ) + joining_date = models.DateField(null=True, blank=True) + password = models.CharField( + max_length=128, editable=False, null=True + ) # Editable is set to False to prevent direct editing + + USERNAME_FIELD = "phone" + REQUIRED_FIELDS = ["name"] # Add required fields + + def __str__(self): + return self.name + + def set_password(self, raw_password): + self.password = make_password(raw_password) + self.save(update_fields=["password"]) + + @property + def is_staff(self): + """Check if student has any groups (permissions)""" + return True + + @property + def is_authenticated(self): + return True + + def has_perm(self, perm, obj=None): + """Check if student has a specific permission""" + if not self.is_active: + return False + + # Get permissions from groups + perms = set() + for group in self.groups.all(): + perms.update( + group.permissions.values_list("content_type__app_label", "codename") + ) + + # Convert permission string to app_label and codename + app_label, codename = perm.split(".") + return (app_label, codename) in perms + + def has_module_perms(self, app_label): + """Check if student has any permissions in the given app""" + if not self.is_active: + return False + + # Check if any group permissions match the app_label + for group in self.groups.all(): + if group.permissions.filter(content_type__app_label=app_label).exists(): + return True + return False + + class Meta: + verbose_name = "Staff" + verbose_name_plural = "Staffs" + + +class Batch(models.Model): + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=255) + start_date = models.DateField(null=True, blank=True) + end_date = models.DateField(null=True, blank=True) + + def __str__(self): + return self.name + + class Meta: + verbose_name = "Batch" + verbose_name_plural = "Batches" + + +class Routine(models.Model): + id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=255, null=True, blank=True) + start_time = models.TimeField(null=True, blank=True) + end_time = models.TimeField(null=True, blank=True) + teacher = models.ForeignKey( + Staff, related_name="routines", on_delete=models.CASCADE, null=True, blank=True + ) + + def __str__(self): + return self.name + + class Meta: + verbose_name = "Routine" + verbose_name_plural = "Routines" + + +class StudentRoutine(models.Model): + id = models.BigAutoField(primary_key=True) + student = models.ForeignKey( + Student, related_name="routines", on_delete=models.CASCADE, null=True + ) + routine = models.ForeignKey( + Routine, related_name="students", on_delete=models.CASCADE, null=True + ) + course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, blank=True) + + def __str__(self): + return self.student.name + + +class Attendance(models.Model): + id = models.BigAutoField(primary_key=True) + date = models.DateField() + routine = models.ForeignKey( + Routine, + related_name="attendances", + on_delete=models.CASCADE, + null=True, + blank=True, + ) + teacher = models.ForeignKey( + Staff, + related_name="attendances", + on_delete=models.CASCADE, + null=True, + blank=True, + ) + teacher_attend = models.BooleanField(default=True) + class_status = models.BooleanField(default=True) + + def __str__(self): + return str(self.date) + + class Meta: + verbose_name = "Attendance" + verbose_name_plural = "Attendances" + + +class AttendanceRecord(models.Model): + id = models.BigAutoField(primary_key=True) + attendance = models.ForeignKey( + Attendance, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="records", + ) + student = models.ForeignKey( + Student, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="attendance_records", + ) + student_attend = models.BooleanField(default=False) + + # def __str__(self): + # return self.student.name + + +class FCMDevice(models.Model): + id = models.BigAutoField(primary_key=True) + token = models.TextField(unique=True) + + def __str__(self): + return self.token + + +class Notice(models.Model): + id = models.BigAutoField(primary_key=True) + title = models.CharField(max_length=255) + image = models.ImageField(upload_to="notice", blank=True, null=True) + message = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.title + + def save(self, *args, **kwargs): + send_push_notification( + self.title, self.message, FCMDevice.objects.values_list("token", flat=True) + ) + return super().save(*args, **kwargs) + + class Meta: + verbose_name = "Notice" + verbose_name_plural = "Notices" + + +class Staff_leave(models.Model): + LEAVE_STATUS_CHOICES = ( + (0, 'Pending'), + (1, 'Approved'), + (2, 'Rejected'), + ) + + staff = models.ForeignKey(Staff, on_delete=models.CASCADE) + date = models.DateField() + message = models.TextField() + status = models.IntegerField(choices=LEAVE_STATUS_CHOICES, default=0) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return f"{self.name} ({self.course.name}, Duration: {self.duration})" + return f"{self.staff.name} - {self.date}" + + class Meta: + verbose_name = "Staff Leave" + verbose_name_plural = "Staff Leaves" + ordering = ['-created_at'] + + +class Student_Leave(models.Model): + LEAVE_STATUS_CHOICES = ( + (0, 'Pending'), + (1, 'Approved'), + (2, 'Rejected'), + ) + + student = models.ForeignKey(Student, on_delete=models.CASCADE) + date = models.DateField() + message = models.TextField() + status = models.IntegerField(choices=LEAVE_STATUS_CHOICES, default=0) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.student.name} - {self.date}" + + class Meta: + verbose_name = "Student Leave" + verbose_name_plural = "Student Leaves" + ordering = ['-created_at'] + + +class StudentFeedback(models.Model): + student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='feedbacks') + subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='feedbacks') + teacher = models.ForeignKey(Staff, on_delete=models.CASCADE, related_name='feedbacks') + rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)]) # 1-5 rating + feedback_text = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.student.name} - {self.subject.name} - {self.teacher.name}" + + class Meta: + verbose_name = "Student Feedback" + verbose_name_plural = "Student Feedbacks" + ordering = ['-created_at'] + unique_together = ['student', 'subject', 'teacher'] # Prevent duplicate feedbacks + + +class CourseTracking(models.Model): + PROGRESS_STATUS_CHOICES = ( + ('Not Started', 'Not Started'), + ('In Progress', 'In Progress'), + ('Completed', 'Completed'), + ('Dropped', 'Dropped'), + ) + + student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='course_trackings') + course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='student_trackings') + enrollment_date = models.DateField(auto_now_add=True) + start_date = models.DateField(null=True, blank=True) + expected_end_date = models.DateField(null=True, blank=True) + actual_end_date = models.DateField(null=True, blank=True) + progress_status = models.CharField(max_length=20, choices=PROGRESS_STATUS_CHOICES, default='Not Started') + completion_percentage = models.IntegerField(default=0, help_text="Percentage of course completion (0-100)") + notes = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Course Tracking" + verbose_name_plural = "Course Trackings" + unique_together = ['student', 'course'] # Ensure one tracking per student per course + ordering = ['-created_at'] + + def __str__(self): + return f"{self.student.name} - {self.course.name}" def clean(self): - if ( - self.course.course_type == Course.SEMESTER - and self.duration > self.course.duration - ): - raise ValidationError( - f"Duration (semester) must not exceed the course's total semesters ({self.course.duration})." - ) - if ( - self.course.course_type == Course.YEAR - and self.duration > self.course.duration - ): - raise ValidationError( - f"Duration (year) must not exceed the course's total years ({self.course.duration})." - ) + from django.core.exceptions import ValidationError + if self.start_date and self.expected_end_date and self.start_date > self.expected_end_date: + raise ValidationError("Start date cannot be after expected end date") + if self.actual_end_date and self.start_date and self.actual_end_date < self.start_date: + raise ValidationError("Actual end date cannot be before start date") + + def calculate_completion_percentage(self): + if not self.start_date or not self.expected_end_date: + return 0 + + today = date.today() + + # If course is completed, return 100% + if self.progress_status == 'Completed': + return 100 + elif self.progress_status == 'Dropped': + return 0 + + # Calculate total duration in days + total_duration = (self.expected_end_date - self.start_date).days + if total_duration <= 0: + return 0 + + # Calculate elapsed time in days + elapsed_days = (today - self.start_date).days + if elapsed_days <= 0: + return 0 + + # Calculate percentage based on course duration type + if self.course.duration_type == 'Semester': + # For semester-based courses (6 months) + max_days = 180 # 6 months = 180 days + if elapsed_days >= max_days: + return 100 + else: # Yearly based + max_days = 365 # 1 year = 365 days + if elapsed_days >= max_days: + return 100 + + # Calculate percentage based on elapsed time vs total duration + percentage = min(100, int((elapsed_days / total_duration) * 100)) + return percentage + + def save(self, *args, **kwargs): + # Validate dates + self.clean() + + # Calculate completion percentage + self.completion_percentage = self.calculate_completion_percentage() + + # Update progress status based on completion percentage + if self.completion_percentage >= 100: + self.progress_status = 'Completed' + if not self.actual_end_date: + self.actual_end_date = date.today() + elif self.completion_percentage > 0: + self.progress_status = 'In Progress' + + super().save(*args, **kwargs) + + @property + def is_completed(self): + return self.progress_status == 'Completed' + + @property + def is_active(self): + return self.progress_status == 'In Progress' + + @property + def remaining_days(self): + if not self.expected_end_date: + return None + today = date.today() + remaining = (self.expected_end_date - today).days + return max(0, remaining) + + @property + def duration_type(self): + return self.course.duration_type + + @property + def total_duration_days(self): + if not self.start_date or not self.expected_end_date: + return None + return (self.expected_end_date - self.start_date).days + + def get_completion_percentage(self): + """ + Get completion percentage with caching + """ + return get_cached_percentage(self.id) + + def force_update_percentage(self): + """ + Force update the completion percentage and clear cache + """ + cache_key = f'course_tracking_percentage_{self.id}' + cache.delete(cache_key) + self.save() # This will recalculate and update the percentage -class Attendance(models.Model): # Subject Attendance - id = models.AutoField(primary_key=True) - subject_id = models.ForeignKey(Subject, on_delete=models.DO_NOTHING) - attendance_date = models.DateField() - academic_year_id = models.ForeignKey(AcademicYear, on_delete=models.CASCADE) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) +def get_cached_percentage(tracking_id): + """ + Get cached completion percentage for a course tracking + """ + cache_key = f'course_tracking_percentage_{tracking_id}' + percentage = cache.get(cache_key) + if percentage is None: + try: + tracking = CourseTracking.objects.get(id=tracking_id) + percentage = tracking.completion_percentage + cache.set(cache_key, percentage, 3600) + except CourseTracking.DoesNotExist: + return 0 + return percentage -class AttendanceReport(models.Model): # Individual Student Attendance - id = models.AutoField(primary_key=True) - student_id = models.ForeignKey(Student, on_delete=models.DO_NOTHING) - attendance_id = models.ForeignKey(Attendance, on_delete=models.CASCADE) - status = models.BooleanField(default=False) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) +@receiver(post_save, sender=CourseTracking) +def update_completion_percentage(sender, instance, **kwargs): + """ + Signal to update completion percentage after save + Also caches the percentage for 1 hour to avoid frequent recalculations + """ + cache_key = f'course_tracking_percentage_{instance.id}' + cache.set(cache_key, instance.completion_percentage, 3600) # Cache for 1 hour -class LeaveReportStudent(models.Model): - id = models.AutoField(primary_key=True) - student_id = models.ForeignKey(Student, on_delete=models.CASCADE) - leave_date = models.DateField() - leave_message = models.TextField() - leave_status = models.IntegerField(default=0) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - -class LeaveReportTeacher(models.Model): - id = models.AutoField(primary_key=True) - teacher_id = models.ForeignKey(Teacher, on_delete=models.CASCADE) - leave_date = models.DateField() - leave_message = models.TextField() - leave_status = models.IntegerField(default=0) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - -class FeedbackStudent(models.Model): - id = models.AutoField(primary_key=True) - student_id = models.ForeignKey(Student, on_delete=models.CASCADE) - feedback = models.TextField() - feedback_reply = models.TextField(blank=True, null=True) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - -class FeedbackTeacher(models.Model): - id = models.AutoField(primary_key=True) - teacher_id = models.ForeignKey(Teacher, on_delete=models.CASCADE) - feedback = models.TextField() - feedback_reply = models.TextField(blank=True, null=True) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) diff --git a/app/studentViews.py b/app/studentViews.py deleted file mode 100644 index a397429..0000000 --- a/app/studentViews.py +++ /dev/null @@ -1,163 +0,0 @@ -from django.contrib.auth.decorators import login_required - -from django.shortcuts import render - - -@login_required -def student_dashboard(request): - return render(request, "student/dashboard.html") # Student-specific page - - -# @login_required -# @student_required -# def student_view_attendance(request): -# student = Student.objects.get(admin=request.user.id) -# course = student.course_id -# subjects = Subject.objects.filter(course_id=course) -# student = Student.objects.get(admin=user) -# student_notifcation = Student.objects.get(admin=request.user.id) -# notifications = NotificationStudent.objects.filter( -# student_id=student_notifcation.id -# ) -# return render( -# request, -# "student_template/student_view_attendance.html", -# {"subjects": subjects, "student": student, "notifications": notifications}, -# ) -# -# -# @login_required -# @student_required -# def student_view_attendance_post(request): -# subject_id = request.POST.get("subject") -# start_date = request.POST.get("start_date") -# end_date = request.POST.get("end_date") -# -# start_data_parse = datetime.datetime.strptime(start_date, "%Y-%m-%d").date() -# end_data_parse = datetime.datetime.strptime(end_date, "%Y-%m-%d").date() -# subject_obj = Subjects.objects.get(id=subject_id) -# user_object = CustomUser.objects.get(id=request.user.id) -# stud_obj = Students.objects.get(admin=user_object) -# user = CustomUser.objects.get(id=request.user.id) -# student = Students.objects.get(admin=user) -# student_notifcation = Students.objects.get(admin=request.user.id) -# notifications = NotificationStudent.objects.filter( -# student_id=student_notifcation.id -# ) -# -# attendance = Attendance.objects.filter( -# attendance_date__range=(start_data_parse, end_data_parse), -# subject_id=subject_obj, -# ) -# attendance_reports = AttendanceReport.objects.filter( -# attendance_id__in=attendance, student_id=stud_obj -# ) -# return render( -# request, -# "student_template/student_attendance_data.html", -# { -# "attendance_reports": attendance_reports, -# "student": student, -# "notifications": notifications, -# }, -# ) -# -# -# @login_required -# @student_required -# def student_apply_leave(request): -# student_obj = Student.objects.get(admin=request.user.id) -# leave_data = LeaveReportStudent.objects.filter(student_id=student_obj) -# user = CustomUser.objects.get(id=request.user.id) -# student = Student.objects.get(admin=user) -# student_notifcation = Student.objects.get(admin=request.user.id) -# notifications = NotificationStudent.objects.filter( -# student_id=student_notifcation.id -# ) -# return render( -# request, -# "student/apply_leave.html", -# {"leave_data": leave_data, "student": student, "notifications": notifications}, -# ) -# -# -# @login_required -# @student_required -# def student_apply_leave_save(request): -# if request.method != "POST": -# return HttpResponseRedirect(reverse("student_apply_leave")) -# else: -# leave_start_date = request.POST.get("leave_start_date") -# leave_end_date = request.POST.get("leave_end_date") -# leave_date = request.POST.get("leave_date") -# leave_msg = request.POST.get("leave_msg") -# -# student_obj = Student.objects.get(admin=request.user.id) -# try: -# leave_report = LeaveReportStudent( -# student_id=student_obj, -# leave_start_date=leave_start_date, -# leave_end_date=leave_end_date, -# leave_message=leave_msg, -# leave_status=0, -# ) -# leave_report.save() -# messages.success(request, "Successfully Applied for Leave") -# return HttpResponseRedirect(reverse("student_apply_leave")) -# except: -# messages.error(request, "Failed To Apply for Leave") -# return HttpResponseRedirect(reverse("student_apply_leave")) -# -# -# @login_required -# @student_required -# def student_feedback(request): -# staff_id = Student.objects.get(admin=request.user.id) -# feedback_data = FeedBackStudent.objects.filter(student_id=staff_id) -# student = Student.objects.get(admin=user) -# student_notifcation = Student.objects.get(admin=request.user.id) -# notifications = NotificationStudent.objects.filter( -# student_id=student_notifcation.id -# ) -# return render( -# request, -# "student_template/student_feedback.html", -# { -# "feedback_data": feedback_data, -# "student": student, -# "notifications": notifications, -# }, -# ) -# -# -# @login_required -# @student_required -# def student_feedback_save(request): -# if request.method != "POST": -# return HttpResponseRedirect(reverse("student_feedback")) -# else: -# feedback_msg = request.POST.get("feedback_msg") -# -# student_obj = Student.objects.get(admin=request.user.id) -# try: -# feedback = FeedBackStudent( -# student_id=student_obj, feedback=feedback_msg, feedback_reply="" -# ) -# feedback.save() -# messages.success(request, "Feedback submitted for review") -# return HttpResponseRedirect(reverse("student_feedback")) -# except: -# messages.error(request, "Could not Submit") -# return HttpResponseRedirect(reverse("student_feedback")) -# -# -# @csrf_exempt -# def student_fcmtoken_save(request): -# token = request.POST.get("token") -# try: -# student = Student.objects.get(admin=request.user.id) -# student.fcm_token = token -# student.save() -# return HttpResponse("True") -# except: -# return HttpResponse("False") diff --git a/app/teacherViews.py b/app/teacherViews.py deleted file mode 100644 index a0a88b9..0000000 --- a/app/teacherViews.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.shortcuts import render -from django.contrib.auth.decorators import login_required - - -@login_required -def teacher_dashboard(request): - return render(request, "teacher/dashboard.html") diff --git a/app/urls.py b/app/urls.py deleted file mode 100644 index d457173..0000000 --- a/app/urls.py +++ /dev/null @@ -1,121 +0,0 @@ -from django.urls import path -from app import adminViews, studentViews, teacherViews - -urlpatterns = [ - path("dashboard/", adminViews.admin_home, name="dashboard"), - path("add_course/", adminViews.add_course, name="add_course"), - path("manage_course", adminViews.manage_course, name="manage_course"), - path("delete_course//", adminViews.delete_course, name="delete_course"), - path("edit_course//", adminViews.edit_course, name="edit_course"), - path("add_academicyear/", adminViews.add_academicyear, name="add_academicyear"), - path( - "manage_academicyear/", - adminViews.manage_academicyear, - name="manage_academicyear", - ), - path( - "delete_academicyear//", - adminViews.delete_academicyear, - name="delete_academicyear", - ), - path( - "edit_academicyears//", - adminViews.edit_academicyear, - name="edit_academicyear", - ), - path("manage_subject/", adminViews.manage_subjects, name="manage_subject"), - path("add_subject/", adminViews.add_subject, name="add_subject"), - path("edit_subject//", adminViews.edit_subject, name="edit_subject"), - path( - "delete_subject//", adminViews.delete_subject, name="delete_subject" - ), - path("manage_student/", adminViews.manage_student, name="manage_student"), - path("add_student/", adminViews.add_student, name="add_student"), - path( - "edit_student//", adminViews.edit_student, name="edit_student" - ), - path( - "delete_student//", - adminViews.delete_student, - name="delete_student", - ), - path("manage_teacher/", adminViews.manage_teacher, name="manage_teacher"), - path("add_teacher/", adminViews.add_teacher, name="add_teacher"), - path( - "edit_teacher//", adminViews.edit_teacher, name="edit_teacher" - ), - path( - "delete_teacher//", - adminViews.delete_teacher, - name="delete_teacher", - ), - path( - "student_feedback_message/", - adminViews.student_feedback_message, - name="student_feedback_message", - ), - path( - "student_feedback_message_reply/", - adminViews.student_feedback_message_reply, - name="student_feedback_message_reply", - ), - path( - "teacher_feedback/", - adminViews.teacher_feedback, - name="teacher_feedback", - ), - path( - "teacher_feedback_replied/", - adminViews.teacher_feedback_replied, - name="teacher_feedback_replied", - ), - path( - "student_leave_view/", adminViews.student_leave_view, name="student_leave_view" - ), - path( - "student_leave_approve//", - adminViews.student_leave_approve, - name="student_leave_approve", - ), - path( - "student_leave_reject//", - adminViews.student_leave_reject, - name="student_leave_reject", - ), - path( - "teacher_leave_view/", adminViews.teacher_leave_view, name="teacher_leave_view" - ), - path( - "teacher_leave_approve//", - adminViews.teacher_leave_approve, - name="teacher_leave_approve", - ), - path( - "teacher_leave_reject//", - adminViews.teacher_leave_reject, - name="teacher_leave_reject", - ), - path( - "admin_view_attendance/", - adminViews.admin_view_attendance, - name="admin_view_attendance", - ), - path( - "admin_get_attendance_dates/", - adminViews.admin_get_attendance_dates, - name="admin_get_attendance_dates", - ), - path( - "admin_get_attendance_student/", - adminViews.admin_get_attendance_student, - name="admin_get_attendance_student", - ), - # ── Student ─────────────────────────────────────────────────────────── - path( - "student_dashboard/", studentViews.student_dashboard, name="student_dashboard" - ), - # ── teacher ─────────────────────────────────────────────────────────── - path( - "teacher_dashboard/", teacherViews.teacher_dashboard, name="teacher_dashboard" - ), -] diff --git a/app/views.py b/app/views.py index 9724de7..8ebf4a2 100644 --- a/app/views.py +++ b/app/views.py @@ -1,6 +1,44 @@ -from django.contrib.auth import authenticate, login, logout -from django.http import HttpResponseRedirect, HttpResponse -from django.shortcuts import render, redirect -from django.contrib import messages -from django.contrib.auth.decorators import login_required +from app.models import FCMDevice +from django.shortcuts import render +from django.http import JsonResponse, HttpResponse # Create your views here. + + +# FireBase +def saveFCMToken(request, token): + try: + FCMDevice(token=token).save() + except: + pass + return JsonResponse({}) + + +def showFirebaseJS(request): + data = """ + importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-app-compat.js'); + importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-messaging-compat.js'); + + firebase.initializeApp({ + apiKey: "AIzaSyAVzOJh5v8_oZg4MoQkq0CzrAbeyHGn4z4", + authDomain: "institute-management-sys-a2d20.firebaseapp.com", + projectId: "institute-management-sys-a2d20", + storageBucket: "institute-management-sys-a2d20.firebasestorage.app", + messagingSenderId: "777235542100", + appId: "1:777235542100:web:4d0cb9553918cb3cc23dc1", + measurementId: "G-4QP9GYV0SJ" + }); + + const messaging = firebase.messaging(); + + messaging.onBackgroundMessage((payload) => { + + const notificationTitle = payload.notification.title; + const notificationOptions = { + body: payload.notification.body, + icon: payload.notification.icon + }; + + return self.registration.showNotification(notificationTitle, notificationOptions); + }); + """ + return HttpResponse(data, content_type="text/javascript") diff --git a/requirements.txt b/requirements.txt index 080e9be..1aea859 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,13 @@ asgiref==3.8.1 +certifi==2025.1.31 +charset-normalizer==3.4.1 Django==5.1.7 django-jazzmin==3.0.1 -pytz==2025.1 -setuptools==78.0.2 -sqlparse==0.5.3 -typing_extensions==4.12.2 +django-rest-framework==0.1.0 +djangorestframework==3.15.2 +idna==3.10 +pillow==11.1.0 +pytz==2025.2 requests==2.32.3 +sqlparse==0.5.3 +urllib3==2.3.0 diff --git a/student_management_system/jazzmin.py b/student_management_system/jazzmin.py deleted file mode 100644 index 4cff028..0000000 --- a/student_management_system/jazzmin.py +++ /dev/null @@ -1,56 +0,0 @@ -JAZZMIN_SETTINGS = { - "site_title": "Student Management System Admin", - "site_header": "Student Management System", - "site_brand": "SMS", - "site_logo": "/dist/img/logo.png", - "site_logo_classes": "img-circle", - "site_icon": "/dist/img/logo.png", - "welcome_sign": "Welcome to the Student Management System", - "copyright": "ADDRSSS", - ############ - # Top Menu # - ############ - "topmenu_links": [ - {"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]}, - {"name": "Dashboard", "url": "dashboard", "permissions": ["auth.view_user"]}, - ], - ############# - # Side Menu # - ############# - "show_sidebar": True, - "navigation_expanded": True, - # List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models) - "order_with_respect_to": ["auth", "dashboard", "app"], - "custom_links": { - "app": [ - { - "name": "Dashboard", - "url": "dashboard", - "icon": "fas fa-tachometer-alt", - }, - ], - }, - - - "icons": { - "auth": "fas fa-users-cog", - "auth.user": "fas fa-user", - "auth.group": "fas fa-users", - "app.student": "fas fa-user-graduate", - "app.teacher": "fas fa-chalkboard-teacher", - "app.course": "fas fa-book", - "app.subject": "fas fa-book-open", - "app.academicyear": "fas fa-calendar", - "app.attendance": "fas fa-calendar-check", - "app.attendancereport": "fas fa-clipboard-list", - "app.leavereportstudent": "fas fa-user-clock", - "app.leavereportteacher": "fas fa-clock", - "app.feedbackstudent": "fas fa-comment-alt", - "app.feedbackteacher": "fas fa-comments", - }, -} - -JAZZMIN_UI_TWEAKS = { - "theme": "lumen", - # "dark_mode_theme": "cyborg", -} diff --git a/student_management_system/settings.py b/student_management_system/settings.py index 88327e7..44fb2e7 100644 --- a/student_management_system/settings.py +++ b/student_management_system/settings.py @@ -1,7 +1,7 @@ """ Django settings for student_management_system project. -Generated by 'django-admin startproject' using Django 5.1.4. +Generated by 'django-admin startproject' using Django 5.1.6. For more information on this file, see https://docs.djangoproject.com/en/5.1/topics/settings/ @@ -11,25 +11,21 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ """ import os -from student_management_system.jazzmin import JAZZMIN_SETTINGS, JAZZMIN_UI_TWEAKS # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-)bp&!(x=q)##0hl&7r&h2x$u_3bvd7td)yq4y&_k)$)ce%pj&m" +SECRET_KEY = "django-insecure-r))8$j-a-kc641we86oiyxr&x$1e43c!7y4#w-r1s@$$q8dik8" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ["*"] +ALLOWED_HOSTS = [] -JAZZMIN_SETTINGS = JAZZMIN_SETTINGS -JAZZMIN_UI_TWEAKS = JAZZMIN_UI_TWEAKS # Application definition @@ -41,6 +37,7 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "rest_framework", "app", ] @@ -54,6 +51,11 @@ MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] +AUTHENTICATION_BACKENDS = [ + "app.backends.MultiModelBackend", + "django.contrib.auth.backends.ModelBackend", +] +REST_FRAMEWORK = {"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.AllowAny"]} ROOT_URLCONF = "student_management_system.urls" TEMPLATES = [ @@ -110,7 +112,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = "en-us" -TIME_ZONE = "Asia/Kathmandu" +TIME_ZONE = "UTC" USE_I18N = True @@ -121,21 +123,175 @@ USE_TZ = True # https://docs.djangoproject.com/en/5.1/howto/static-files/ STATIC_URL = "static/" -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "static"), -] -STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") - -STATICFILES_FINDERS = [ - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", -] - -MEDIA_URL = "/media/" -MEDIA_ROOT = os.path.join(BASE_DIR, "media") - # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +JAZZMIN_SETTINGS = { + # title of the window (Will default to current_admin_site.site_title if absent or None) + "site_title": "SMS", + # Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None) + "site_header": "SMS", + # Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None) + "site_brand": "SMS", + # Logo to use for your site, must be present in static files, used for brand on top left + # "site_logo": BASE_DIR / "static/img/logo.jpg", + # Logo to use for your site, must be present in static files, used for login form logo (defaults to site_logo) + "login_logo": None, + # Logo to use for login form in dark themes (defaults to login_logo) + "login_logo_dark": None, + # CSS classes that are applied to the logo above + "site_logo_classes": "img-circle", + # Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px) + # "site_icon": BASE_DIR / "static/img/logo.jpg", + # Welcome text on the login screen + "welcome_sign": "WELCOME TO SMS", + # Copyright on the footer + "copyright": "S4NKALP: SMS", + # List of model admins to search from the search bar, search bar omitted if excluded + # If you want to use a single search field you dont need to use a list, you can use a simple string + "search_model": ["app.Student", "app.Course"], + # Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user + # "user_avatar": get_user_avatar, + ############ + # Top Menu # + ############ + # Links to put along the top menu + "topmenu_links": [ + # Url that gets reversed (Permissions can be added) + {"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]}, + {"name": "Dashboard", "url": "/app/dashboard", "permissions": []}, + # model admin to link to (Permissions checked against model) + {"model": "app.Student"}, + # App with dropdown menu to all its models pages (Permissions checked against models) + {"app": "app"}, + ], + ############# + # User Menu # + ############# + # Additional links to include in the user menu on the top right ("app" url type is not allowed) + "usermenu_links": [ + # {"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True}, + {"model": "auth.user"} + ], + ############# + # Side Menu # + ############# + # Whether to display the side menu + "show_sidebar": True, + # Whether to aut expand the menu + "navigation_expanded": True, + # Hide these apps when generating side menu e.g (auth) + "hide_apps": [], + # Hide these models when generating side menu (e.g auth.user) + "hide_models": [], + # List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models) + "order_with_respect_to": [ + "dashboard", + "authentication", + "organization", + "human_management", + "transaction", + "report", + "organization.institute", + "organization.batch", + "organization.course", + "human_management.student", + "human_management.staff", + "human_management.vendor", + "human_management.routine", + "human_management.attendance", + "human_management.course_tracking", + "transaction.fee", + "transaction.salary", + "transaction.expenses", + ], + # Custom links to append to app groups, keyed on app name + "custom_links": { + "app": [ + { + "name": "Dashboard", + "url": "/app/dashboard", + "icon": "fas fa-circle", + "permissions": [], + } + ] + }, + # Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2 + # for the full list of 5.13.0 free icon classes + "icons": { + "auth": "fas fa-users-cog", + "auth.user": "fas fa-user", + "auth.Group": "fas fa-users", + }, + # Icons that are used when one is not manually specified + "default_icon_parents": "fas fa-chevron-circle-right", + "default_icon_children": "fas fa-circle", + ################# + # Related Modal # + ################# + # Use modals instead of popups + "related_modal_active": False, + ############# + # UI Tweaks # + ############# + # Relative paths to custom CSS/JS scripts (must be present in static files) + "custom_css": "css/main.css", + "custom_js": None, + # Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise) + "use_google_fonts_cdn": True, + # Whether to show the UI customizer on the sidebar + "show_ui_builder": False, + ############### + # Change view # + ############### + # Render out the change view as a single form, or in tabs, current options are + # - single + # - horizontal_tabs (default) + # - vertical_tabs + # - collapsible + # - carousel + "changeform_format": "horizontal_tabs", + # override change forms on a per modeladmin basis + "changeform_format_overrides": { + "auth.user": "collapsible", + "auth.group": "vertical_tabs", + }, + # Add a language dropdown into the admin + # "language_chooser": True, +} + + +JAZZMIN_UI_TWEAKS = { + "navbar_small_text": False, + "footer_small_text": False, + "body_small_text": False, + "brand_small_text": False, + "brand_colour": False, + "accent": "accent-primary", + "navbar": "navbar-dark", + "no_navbar_border": False, + "navbar_fixed": True, + "layout_boxed": False, + "footer_fixed": False, + "sidebar_fixed": True, + "sidebar": "sidebar-dark-primary", + "sidebar_nav_small_text": False, + "sidebar_disable_expand": False, + "sidebar_nav_child_indent": False, + "sidebar_nav_compact_style": False, + "sidebar_nav_legacy_style": False, + "sidebar_nav_flat_style": False, + "theme": "lumen", + "dark_mode_theme": None, + "button_classes": { + "primary": "btn-outline-primary", + "secondary": "btn-outline-secondary", + "info": "btn-outline-info", + "warning": "btn-outline-warning", + "danger": "btn-outline-danger", + "success": "btn-outline-success", + }, +} diff --git a/student_management_system/urls.py b/student_management_system/urls.py index 661260f..962a80b 100644 --- a/student_management_system/urls.py +++ b/student_management_system/urls.py @@ -17,14 +17,9 @@ Including another URLconf from django.contrib import admin from django.urls import path, include -from django.conf import settings -from django.conf.urls.static import static +from app.admin import custom_admin_site -urlpatterns = ( - [ - path("admin/", admin.site.urls), - path("", include("app.urls")), - ] - + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) - + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -) +urlpatterns = [ + # path("admin/", admin.site.urls), + path("", custom_admin_site.urls, name="admin") +] diff --git a/templates/hod/add_academicyear.html b/templates/hod/add_academicyear.html deleted file mode 100644 index fc017b3..0000000 --- a/templates/hod/add_academicyear.html +++ /dev/null @@ -1,135 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-
- {% csrf_token %} - -
- - -
Please provide a valid start date
-
- {% if messages %} -
- {% for message in messages %} -
- {{ message }} - -
- {% endfor %} -
- {% endif %} -
- - Cancel - - -
-
-
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/add_course.html b/templates/hod/add_course.html deleted file mode 100644 index 86b9ae1..0000000 --- a/templates/hod/add_course.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}Add Course{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-

{{ title|default:"Add New Course" }}

-
-
-
- {% csrf_token %} - -
- - -
Course name must be unique
-
Please enter a course name
-
- -
- - -
Please select a course type
-
- -
- - -
Specify the duration
-
Please enter a valid duration (minimum 1 month)
-
- {% if messages %} -
- {% for message in messages %} -
- {{ message }} - -
- {% endfor %} -
- {% endif %} -
- - Cancel - - -
-
-
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/add_student.html b/templates/hod/add_student.html deleted file mode 100644 index 11567eb..0000000 --- a/templates/hod/add_student.html +++ /dev/null @@ -1,149 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - -{% endblock %} -{% block content %} -
-

Add Student

-
- {% csrf_token %} -
- -
- - -
-
- - -
-
- - -
- -
- - -
-
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
-
- - -
-
- - -
-
-
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/hod/add_subject.html b/templates/hod/add_subject.html deleted file mode 100644 index dfb7e2c..0000000 --- a/templates/hod/add_subject.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}Add Subject{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-
- {% csrf_token %} - {{ form.as_p }} - -
-
-
-
-{% endblock %} diff --git a/templates/hod/add_teacher.html b/templates/hod/add_teacher.html deleted file mode 100644 index 06dc64e..0000000 --- a/templates/hod/add_teacher.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} -
-

{{ title }}

-
- {% csrf_token %} -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/hod/dashboard.html b/templates/hod/dashboard.html deleted file mode 100644 index dc081d5..0000000 --- a/templates/hod/dashboard.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} -
-
-
-
-
- -
-
-
Total Students
-

{{ student_count }}

-
-
-
- -
-
-
- -
-
-
Total Teachers
-

{{ teacher_count }}

-
-
-
- -
-
-
- -
-
-
Total Courses
-

{{ course_count }}

-
-
-
- -
-
-
- -
-
-
Total Subjects
-

{{ subject_count }}

-
-
-
- -
-
-{% endblock %} diff --git a/templates/hod/edit_academicyear.html b/templates/hod/edit_academicyear.html deleted file mode 100644 index f65c802..0000000 --- a/templates/hod/edit_academicyear.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-
- {% csrf_token %} -
- - - -
-
- -
-
-
-
-
-{% endblock %} diff --git a/templates/hod/edit_course.html b/templates/hod/edit_course.html deleted file mode 100644 index 55b5502..0000000 --- a/templates/hod/edit_course.html +++ /dev/null @@ -1,152 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-

Edit Course: {{ course.name }}

-
-
-
- {% csrf_token %} - -
- - -
Course name must be unique
-
Please enter a course name
-
- -
- - -
Please select a course type
-
- -
- - -
Specify the duration in months
-
Please enter a valid duration (minimum 1 month)
-
- {% if messages %} -
- {% for message in messages %} -
- {{ message }} - -
- {% endfor %} -
- {% endif %} -
- - Cancel - - -
-
-
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/edit_student.html b/templates/hod/edit_student.html deleted file mode 100644 index 00abaef..0000000 --- a/templates/hod/edit_student.html +++ /dev/null @@ -1,212 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} -
- -
- {% csrf_token %} -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/hod/edit_subject.html b/templates/hod/edit_subject.html deleted file mode 100644 index c750ca8..0000000 --- a/templates/hod/edit_subject.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}Edit Subject{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
-
- {% csrf_token %} - {{ form.as_p }} - -
-
-
-
-{% endblock %} diff --git a/templates/hod/edit_teacher.html b/templates/hod/edit_teacher.html deleted file mode 100644 index 771fc5c..0000000 --- a/templates/hod/edit_teacher.html +++ /dev/null @@ -1,126 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} -
-

Edit Teacher

-
- {% csrf_token %} -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/hod/manage_academicyear.html b/templates/hod/manage_academicyear.html deleted file mode 100644 index 520c9ba..0000000 --- a/templates/hod/manage_academicyear.html +++ /dev/null @@ -1,181 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
- -
-
- -
-
- {% if academic_years %} -
- - - - - - - - - - - {% for academic_year in academic_years %} - - - - - - - {% endfor %} - -
S.NStart DateCreated AtActions
{{ forloop.counter }}{{ academic_year.start_date }}{{ academic_year.created_at }} - - Edit - - - Delete - -
-
- {% else %} -
- -

No Academic Years Found

-

Start by adding your first academic year using the button above.

-
- {% endif %} -
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/manage_course.html b/templates/hod/manage_course.html deleted file mode 100644 index 7352948..0000000 --- a/templates/hod/manage_course.html +++ /dev/null @@ -1,194 +0,0 @@ -{% extends "admin/base_site.html" %} {% block page_title %}{{ title }} -{%endblock %} {% load static %} {% block extrastyle %} - - - -{% endblock %} {% block content %} - - -
-
-
- -
-
- -
-
- - {% if courses %} -
- - - - - - - - - - - - {% for course in courses %} - - - - - - - - {% endfor %} - -
S.NCourse NameCourse TypeDurationActions
{{ forloop.counter }}{{ course.name }}{{ course.get_course_type_display }} - {{ course.duration }} {% if course.course_type == 'semester' %} - Semesters {% else %} Years {% endif %} - - - Edit - - - Delete - -
-
- {% else %} -
- -

No Courses Found

-

- Start by adding your first course using the button above. -

-
- {% endif %} -
-
-
- -{% block extrajs %} - - -{% endblock %} {% endblock %} diff --git a/templates/hod/manage_student.html b/templates/hod/manage_student.html deleted file mode 100644 index 7dd536e..0000000 --- a/templates/hod/manage_student.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% block content %} -
-
-
-

Manage Students

-
- -
-
-
-
- -
-
- -
-
-
-
- - - - - - - - - - - - - - {% for student in students %} - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
S.NFull NameCourseAcademic YearPhoneEmailActions
{{ forloop.counter }}{{ student.first_name }}{{ student.course.name }}{{ student.academic_year.start_date|date:"Y" }}{{ student.phone }}{{ student.email }} - Edit - Delete -
No students found
-
-
-{% endblock %} diff --git a/templates/hod/manage_subject.html b/templates/hod/manage_subject.html deleted file mode 100644 index 78ba22a..0000000 --- a/templates/hod/manage_subject.html +++ /dev/null @@ -1,190 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
- -
-
- -
-
- {% if subjects %} -
- - - - - - - - - - - - {% for subject in subjects %} - - - - - - - - {% endfor %} - -
S.NSubject NameCourseDurationActions
{{ forloop.counter }}{{ subject.name }}{{ subject.course.name }} - {{ subject.duration }} - {% if subject.course.course_type == 'semester' %} - Semesters - {% else %} - Years - {% endif %} - - - Edit - - - Delete - -
-
- {% else %} -
- -

No Subjects Found

-

Start by adding your first subject using the button above.

-
- {% endif %} -
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/manage_teacher.html b/templates/hod/manage_teacher.html deleted file mode 100644 index cbd8cd2..0000000 --- a/templates/hod/manage_teacher.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} -
-

Manage Teachers

- Add Teacher - - - - - - - - - - - - - - - - {% for teacher in teachers %} - - - - - - - - - - - - {% endfor %} - -
S.NNameEmailPhoneGenderQualificationMarital StatusDate of JoiningActions
{{ forloop.counter }}{{ teacher.first_name }} {{ teacher.middle_name }} {{ teacher.last_name }}{{ teacher.email }}{{ teacher.phone }}{{ teacher.get_gender_display }}{{ teacher.get_qualification_display }}{{ teacher.get_marital_status_display }}{{ teacher.date_of_joining }} - Edit - Delete -
-
-{% endblock %} diff --git a/templates/hod/student_feedback.html b/templates/hod/student_feedback.html deleted file mode 100644 index d495376..0000000 --- a/templates/hod/student_feedback.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} - {% load static %} -
-
-
-
- -
-
-

Student Feedback

-
- -
- - - - - - - - - - - - {% for feedback in feedbacks %} - - - - - - - - - - - {% endfor %} -
S.NStudent NameStudent SessionMessageSended OnReply
{{ forloop.counter }}{{ feedback.student_id.first_name }}{{ feedback.student_id.academic_year.start_date }}{{ feedback.feedback }}{{ feedback.created_at }} - {% if feedback.feedback_reply == "" %} - - {% else %} - {{ feedback.feedback_reply }} - {% endif %} -
-
-
- -
-
-
- -
- {% comment %} Modal to Reply Feedback {% endcomment %} - - -{% endblock %} -{% comment %} Custom JS to Identify the Reply and Post Reply {% endcomment %} -{% block custom_js %} - -{% endblock custom_js %} diff --git a/templates/hod/student_leave_view.html b/templates/hod/student_leave_view.html deleted file mode 100644 index 79db973..0000000 --- a/templates/hod/student_leave_view.html +++ /dev/null @@ -1,188 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
- -
-
- -
-
- {% if leaves %} -
- - - - - - - - - - - - - {% for leave in leaves %} - - - - - - - - - {% endfor %} - -
S.NStudent NameLeave DateLeave MessageApplied OnActions
{{ forloop.counter }}{{ leave.student_id.first_name }}{{ leave.leave_date }}{{ leave.leave_message }}{{ leave.created_at }} - {% if leave.leave_status == 0 %} - - Approve - - - Reject - - {% elif leave.leave_status == 1 %} - - {% else %} - - {% endif %} -
-
- {% else %} -
- -

No Leave Applications Found

-

There are no leave applications to display at the moment.

-
- {% endif %} -
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/teacher_feedback.html b/templates/hod/teacher_feedback.html deleted file mode 100644 index dac239c..0000000 --- a/templates/hod/teacher_feedback.html +++ /dev/null @@ -1,115 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} - -
-
-
-
- -
-
-

Staff Feedback

-
- - -
- - - - - - - - - {% for feedback in feedbacks %} - - - - - - - - {% endfor %} -
S.NStaff NameMessageSended OnReply
{{ forloop.counter }}{{ feedback.teacher_id.first_name }} {{ feedback.teacher_id.last_name }}{{ feedback.feedback }}{{ feedback.created_at }} - {% if feedback.feedback_reply == "" %} - - {% else %} - {{ feedback.feedback_reply }} - {% endif %} -
-
-
- -
-
-
-
- - - -{% endblock %} -{% block custom_js %} - -{% endblock custom_js %} diff --git a/templates/hod/teacher_leave_view.html b/templates/hod/teacher_leave_view.html deleted file mode 100644 index 6a4a946..0000000 --- a/templates/hod/teacher_leave_view.html +++ /dev/null @@ -1,186 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block page_title %}{{ title }}{% endblock %} -{% load static %} -{% block extrastyle %} - - - -{% endblock %} -{% block content %} - -
-
-
- -
-
- -
-
- {% if leaves %} -
- - - - - - - - - - - - - {% for leave in leaves %} - - - - - - - - - {% endfor %} - -
S.NTeacher NameLeave DateLeave MessageApplied OnActions
{{ forloop.counter }}{{ leave.teacher_id.first_name }}{{ leave.teacher_id.middle_name }} {{ leave.teacher_id.last_name }}{{ leave.leave_date }}{{ leave.leave_message }}{{ leave.created_at }} - {% if leave.leave_status == 0 %} - - Approve - - - Reject - - {% elif leave.leave_status == 1 %} - - {% else %} - - {% endif %} -
-
- {% else %} -
- -

No Leave Applications Found

-

There are no leave applications to display at the moment.

-
- {% endif %} -
-
-
- {% block extrajs %} - - - {% endblock %} -{% endblock %} diff --git a/templates/hod/view_attendance.html b/templates/hod/view_attendance.html deleted file mode 100644 index 0184633..0000000 --- a/templates/hod/view_attendance.html +++ /dev/null @@ -1,208 +0,0 @@ -{% extends 'admin/base_site.html' %} -{% block content %} - {% load static %} -
-
-
-
- -
-
-

View Attendance

-
- - {% comment %} Display Messages {% endcomment %} - {% if messages %} -
-
- {% for message in messages %} - {% if message.tags == "error" %} - - {% elif message.tags == "success" %} - - {% endif %} - {% endfor %} -
-
- {% endif %} -
-
- - -
-
- - -
-
- - - -
- - -
- - - {% comment %} {% endcomment %} -
- {% comment %} Displaying Students Here {% endcomment %} - -
- -
-
- - -
-{% endblock %} -{% block custom_js %} - -{% endblock custom_js %} diff --git a/templates/student/dashboard.html b/templates/student/dashboard.html deleted file mode 100644 index 8252370..0000000 --- a/templates/student/dashboard.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block content %} -

Dashboard

-

Welcome

- -
-
-
-
Course
-

DCOM

-
-
-
-
-
Current Semester
-

5

-
-
-
-
-
Total Subject
-

12

-
-
-
- -
- -
- -
- -
- -{% endblock %} diff --git a/templates/teacher/dashboard.html b/templates/teacher/dashboard.html deleted file mode 100644 index 8252370..0000000 --- a/templates/teacher/dashboard.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "admin/base_site.html" %} -{% block content %} -

Dashboard

-

Welcome

- -
-
-
-
Course
-

DCOM

-
-
-
-
-
Current Semester
-

5

-
-
-
-
-
Total Subject
-

12

-
-
-
- -
- -
- -
- -
- -{% endblock %}