From 5e572ef062e9d8272e3b62b348364e619ebdc85b Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 26 Mar 2025 01:59:23 +0545 Subject: [PATCH] updates --- app/admin.py | 104 +++++- ...tudentfeedback_unique_together_and_more.py | 21 ++ app/migrations/0006_institutefeedback.py | 35 ++ ...nt_is_superuser_alter_student_is_active.py | 23 ++ app/models.py | 51 ++- app/serializers.py | 132 +++++++ app/templatetags/__init__.py | 0 app/templatetags/app_filters.py | 11 + app/urls.py | 8 + app/views.py | 338 +++++++++++++++++- student_management_system/settings.py | 159 +++----- student_management_system/urls.py | 8 +- templates/dashboard.html | 225 ++++++++++++ templates/student_dashboard.html | 217 +++++++++++ templates/teacher_dashboard.html | 250 +++++++++++++ 15 files changed, 1445 insertions(+), 137 deletions(-) create mode 100644 app/migrations/0005_alter_studentfeedback_unique_together_and_more.py create mode 100644 app/migrations/0006_institutefeedback.py create mode 100644 app/migrations/0007_student_is_superuser_alter_student_is_active.py create mode 100644 app/serializers.py create mode 100644 app/templatetags/__init__.py create mode 100644 app/templatetags/app_filters.py create mode 100644 app/urls.py create mode 100644 templates/dashboard.html create mode 100644 templates/student_dashboard.html create mode 100644 templates/teacher_dashboard.html diff --git a/app/admin.py b/app/admin.py index ba32cee..4448edb 100644 --- a/app/admin.py +++ b/app/admin.py @@ -15,6 +15,7 @@ from app.models import ( Student_Leave, StudentFeedback, CourseTracking, + InstituteFeedback, ) from django.forms import ModelForm from django.contrib.auth.models import User, Group @@ -29,6 +30,7 @@ class CustomAdminSite(admin.AdminSite): app_list = super().get_app_list(request) model_map = { + # Authentication Models "User": { "group": "Authentication", "group_label": "authentication", @@ -37,22 +39,66 @@ class CustomAdminSite(admin.AdminSite): "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"}, + + # Organization Models + "Institute": { + "group": "Organization", + "group_label": "organization", + }, + "Batch": { + "group": "Organization", + "group_label": "organization", + }, + "Notice": { + "group": "Organization", + "group_label": "organization", + }, + "Course": { + "group": "Organization", + "group_label": "organization", + }, + "Subject": { + "group": "Organization", + "group_label": "organization", + }, + + # Human Management Models + "Student": { + "group": "Human Management", + "group_label": "human_management", + }, + "Staff": { + "group": "Human Management", + "group_label": "human_management", + }, + "Routine": { + "group": "Human Management", + "group_label": "human_management", + }, + "Staff_leave": { + "group": "Human Management", + "group_label": "human_management", + }, + "Student_Leave": { + "group": "Human Management", + "group_label": "human_management", + }, + "StudentFeedback": { + "group": "Human Management", + "group_label": "human_management", + }, + "CourseTracking": { + "group": "Human Management", + "group_label": "human_management", + }, "Attendance": { "group": "Human Management", "group_label": "human_management", }, + "InstituteFeedback": { + "group": "Human Management", + "group_label": "human_management", + }, } grouped_app_list = [{"name": "Main", "app_label": "dashboard", "models": []}] @@ -378,6 +424,40 @@ class CourseTrackingAdmin(admin.ModelAdmin): js = ("js/course_tracking_admin.js",) +@admin.register(InstituteFeedback, site=custom_admin_site) +class InstituteFeedbackAdmin(admin.ModelAdmin): + list_display = ('display_name', 'institute', 'feedback_type', 'rating', 'is_anonymous', 'is_public', 'created_at') + list_filter = ('feedback_type', 'rating', 'is_anonymous', 'is_public', 'created_at', 'institute') + search_fields = ('user__name', 'institute__name', 'feedback_text') + readonly_fields = ('created_at', 'updated_at') + date_hierarchy = 'created_at' + + def display_name(self, obj): + return obj.display_name + display_name.short_description = 'Student Name' + + fieldsets = ( + ('Feedback Information', { + 'fields': ('institute', 'user', 'feedback_type', 'rating', 'feedback_text') + }), + ('Privacy Settings', { + 'fields': ('is_anonymous', 'is_public'), + 'classes': ('collapse',) + }), + ('Timestamps', { + 'fields': ('created_at', 'updated_at'), + 'classes': ('collapse',) + }), + ) + + def get_queryset(self, request): + return super().get_queryset(request).select_related('user', 'institute') + + class Media: + css = { + 'all': ('admin/css/forms.css',) + } + js = ('admin/js/collapse.js',) custom_admin_site.register(User, UserAdmin) diff --git a/app/migrations/0005_alter_studentfeedback_unique_together_and_more.py b/app/migrations/0005_alter_studentfeedback_unique_together_and_more.py new file mode 100644 index 0000000..826b095 --- /dev/null +++ b/app/migrations/0005_alter_studentfeedback_unique_together_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.7 on 2025-03-25 19:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_alter_studentfeedback_options_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='studentfeedback', + unique_together={('student', 'teacher')}, + ), + migrations.RemoveField( + model_name='studentfeedback', + name='subject', + ), + ] diff --git a/app/migrations/0006_institutefeedback.py b/app/migrations/0006_institutefeedback.py new file mode 100644 index 0000000..b3cf33e --- /dev/null +++ b/app/migrations/0006_institutefeedback.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.7 on 2025-03-25 19:44 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0005_alter_studentfeedback_unique_together_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='InstituteFeedback', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('feedback_type', models.CharField(choices=[('General', 'General'), ('Facilities', 'Facilities'), ('Teaching', 'Teaching'), ('Infrastructure', 'Infrastructure'), ('Administration', 'Administration')], default='General', max_length=20)), + ('rating', models.IntegerField(choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])), + ('feedback_text', models.TextField()), + ('is_anonymous', models.BooleanField(default=False)), + ('is_public', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('institute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.institute')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='institute_feedbacks', to='app.student')), + ], + options={ + 'verbose_name': 'Institute Feedback', + 'verbose_name_plural': 'Institute Feedbacks', + 'ordering': ['-created_at'], + 'unique_together': {('user', 'institute', 'feedback_type')}, + }, + ), + ] diff --git a/app/migrations/0007_student_is_superuser_alter_student_is_active.py b/app/migrations/0007_student_is_superuser_alter_student_is_active.py new file mode 100644 index 0000000..06b0dd2 --- /dev/null +++ b/app/migrations/0007_student_is_superuser_alter_student_is_active.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.7 on 2025-03-25 20:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0006_institutefeedback'), + ] + + operations = [ + migrations.AddField( + model_name='student', + name='is_superuser', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='student', + name='is_active', + field=models.BooleanField(default=True), + ), + ] diff --git a/app/models.py b/app/models.py index af22620..4425b95 100644 --- a/app/models.py +++ b/app/models.py @@ -76,6 +76,11 @@ class Student(AbstractUser): username = None email = None + # Add these fields + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=True) # This ensures admin access + is_superuser = models.BooleanField(default=False) + user_permissions = models.ManyToManyField( "auth.Permission", verbose_name="user permissions", @@ -130,6 +135,11 @@ class Student(AbstractUser): """Check if student has any groups (permissions)""" return self.groups.exists() + @property + def is_authenticated(self): + """Required by Django's authentication system""" + return True + def has_perm(self, perm, obj=None): """Check if student has a specific permission""" if not self.is_active: @@ -431,7 +441,6 @@ class Student_Leave(models.Model): 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() @@ -439,13 +448,49 @@ class StudentFeedback(models.Model): updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return f"{self.student.name} - {self.subject.name} - {self.teacher.name}" + return f"{self.student.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 + unique_together = ['student', 'teacher'] # Updated unique constraint + + +class InstituteFeedback(models.Model): + FEEDBACK_TYPE_CHOICES = ( + ('General', 'General'), + ('Facilities', 'Facilities'), + ('Teaching', 'Teaching'), + ('Infrastructure', 'Infrastructure'), + ('Administration', 'Administration'), + ) + + institute = models.ForeignKey(Institute, on_delete=models.CASCADE, related_name='feedbacks') + user = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='institute_feedbacks') + feedback_type = models.CharField(max_length=20, choices=FEEDBACK_TYPE_CHOICES, default='General') + rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)]) # 1-5 rating + feedback_text = models.TextField() + is_anonymous = models.BooleanField(default=False) + is_public = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + if self.is_anonymous: + return f"Anonymous - {self.institute.name} - {self.feedback_type}" + return f"{self.user.name} - {self.institute.name} - {self.feedback_type}" + + class Meta: + verbose_name = "Institute Feedback" + verbose_name_plural = "Institute Feedbacks" + ordering = ['-created_at'] + unique_together = ['user', 'institute', 'feedback_type'] # One feedback per type per user + + @property + def display_name(self): + """Return anonymous if feedback is anonymous, otherwise return user's name""" + return 'Anonymous' if self.is_anonymous else self.user.name class CourseTracking(models.Model): diff --git a/app/serializers.py b/app/serializers.py new file mode 100644 index 0000000..d1901d0 --- /dev/null +++ b/app/serializers.py @@ -0,0 +1,132 @@ +import django.db +from rest_framework import serializers +from .models import (Attendance, AttendanceRecord, Batch, Course, Expenses, FCMDevice, Fee, + Institute, Routine, Salary, Staff, Student, StudentRoutine, Subtopic, Topic, Vendor) + +class FCMDeviceSerializer(serializers.ModelSerializer): + class Meta: + model = FCMDevice + fields = ['id', 'token'] + depth = 10 + +class InstituteSerializer(serializers.ModelSerializer): + class Meta: + model = Institute + depth = 10 + fields = ['id', 'name', 'phone', 'email', 'address', 'pan_no', 'reg_no', 'logo', 'description'] + + +class SubtopicSerializer(serializers.ModelSerializer): + class Meta: + model = Subtopic + depth = 10 + fields = ['id', 'name'] + + +class TopicSerializer(serializers.ModelSerializer): + subtopics = SubtopicSerializer(read_only=True, many=True).data + class Meta: + model = Topic + fields = ['id', 'name', 'subtopics'] + depth = 10 + + +class CourseSerializer(serializers.ModelSerializer): + topics = TopicSerializer(read_only=True, many=True).data + class Meta: + model = Course + depth = 3 + fields = ['id', 'name', 'amount', 'duration', 'duration_type', 'remarks', 'topics'] + + + +class StudentSerializer(serializers.ModelSerializer): + class Meta: + model = Student + depth = 10 + fields = [ + 'id', 'name', 'status', 'gender', 'birth_date', 'email', 'phone', + 'temporary_address', 'permanent_address', 'marital_status', 'parent_name', + 'parent_phone', 'citizenship_no', 'qualification', 'image', 'batches', 'courses', + 'subtotal', 'discount', 'total', 'paid', 'remaining', 'joining_date', 'file', 'remarks' + ] + + + +class BatchSerializer(serializers.ModelSerializer): + students = StudentSerializer(many=True).data + class Meta: + model = Batch + depth = 10 + fields = ['id', 'name', 'start_date', 'end_date', 'students'] + + + +class FeeSerializer(serializers.ModelSerializer): + class Meta: + model = Fee + fields = [ + 'id', 'student', 'old_total', 'old_paid', 'old_remaining', 'balance_amount', + 'payment_date', 'payment_method', 'total', 'paid', 'remaining', 'remarks' + ] + depth = 10 + +class VendorSerializer(serializers.ModelSerializer): + class Meta: + model = Vendor + depth = 10 + fields = ['id', 'name', 'address', 'phone'] + +class ExpensesSerializer(serializers.ModelSerializer): + class Meta: + model = Expenses + depth = 10 + fields = [ + 'id', 'name', 'amount', 'date', 'vendor', 'paid', 'credit', 'credit_date', 'remarks' + ] + +class SalarySerializer(serializers.ModelSerializer): + class Meta: + model = Salary + depth = 10 + fields = '__all__' + +class StaffSerializer(serializers.ModelSerializer): + salaries = SalarySerializer(many=True).data + class Meta: + model = Staff + depth = 10 + fields = '__all__' + # fields = [ + # 'id', 'name', 'gender', 'designation', 'birth_date', 'phone', 'email', + # 'temporary_address', 'permanent_address', 'marital_status', 'parent_name', + # 'parent_phone', 'citizenship_no', 'passport', 'image', 'monthly', 'pending_salary', 'remarks', 'salaries' + # ] + +class RoutineSerializer(serializers.ModelSerializer): + class Meta: + model = Routine + depth = 10 + fields = ['id', 'name', 'start_time', 'end_time', 'teacher'] + +class StudentRoutineSerializer(serializers.ModelSerializer): + class Meta: + model = StudentRoutine + depth = 10 + fields = ['id', 'student', 'routine', 'course', 'course_status'] + + +class AttendanceRecordSerializer(serializers.ModelSerializer): + class Meta: + model = AttendanceRecord + depth = 10 + fields = ['id', 'attendance', 'student', 'student_attend'] + + +class AttendanceSerializer(serializers.ModelSerializer): + records = AttendanceRecordSerializer(many=True).data + class Meta: + model = Attendance + depth = 1 + fields = ['id', 'date', 'routine', 'teacher', 'teacher_attend', 'class_status', 'records'] + diff --git a/app/templatetags/__init__.py b/app/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/templatetags/app_filters.py b/app/templatetags/app_filters.py new file mode 100644 index 0000000..58b88c4 --- /dev/null +++ b/app/templatetags/app_filters.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + +@register.filter +def get_range(value): + """ + Returns a range of numbers from 0 to value-1 + Used for creating star ratings + """ + return range(value) \ No newline at end of file diff --git a/app/urls.py b/app/urls.py new file mode 100644 index 0000000..582fe4f --- /dev/null +++ b/app/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from app import views + +urlpatterns = [ + path('dashboard/', views.dashboard, name='dashboard'), + path('student-dashboard/', views.studentDashboard, name='studentDashboard'), + path('teacher-dashboard/', views.teacherDashboard, name='teacherDashboard'), +] \ No newline at end of file diff --git a/app/views.py b/app/views.py index 7345c95..c09cba4 100644 --- a/app/views.py +++ b/app/views.py @@ -1,6 +1,19 @@ from app.models import FCMDevice -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 from django.http import JsonResponse, HttpResponse +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.db.models import Count, Q, Avg +from django.utils import timezone +from datetime import timedelta +from .models import ( + Student, Course, CourseTracking, + Student_Leave, Staff_leave, + Attendance, AttendanceRecord, + StudentFeedback, Routine, StudentRoutine, + Staff +) +from app.admin import custom_admin_site # Create your views here. @@ -19,13 +32,13 @@ def showFirebaseJS(request): importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-messaging-compat.js'); firebase.initializeApp({ - apiKey: "", - authDomain: "", - projectId: "", - storageBucket: "", - messagingSenderId: "", - appId: "", - measurementId: "" + 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(); @@ -42,3 +55,312 @@ def showFirebaseJS(request): }); """ return HttpResponse(data, content_type="text/javascript") + +@login_required +def dashboard(request): + user = request.user + user_groups = user.groups.values_list('name', flat=True) + if 'Student' in user_groups: + return redirect('studentDashboard') + elif 'Teacher' in user_groups: + return redirect('teacherDashboard') + # Get current date and date ranges + today = timezone.now().date() + last_week = today - timedelta(days=7) + last_month = today - timedelta(days=30) + + # Student Statistics + total_students = Student.objects.count() + active_students = Student.objects.filter(status='Active').count() + completed_students = Student.objects.filter(status='Completed').count() + on_leave_students = Student.objects.filter(status='Leave').count() + + # Course Statistics + total_courses = Course.objects.count() + active_courses = Course.objects.filter(student_trackings__progress_status='In Progress').distinct().count() + completed_courses = Course.objects.filter(student_trackings__progress_status='Completed').distinct().count() + + # Course Tracking Statistics + course_trackings = CourseTracking.objects.all() + in_progress_trackings = course_trackings.filter(progress_status='In Progress') + completed_trackings = course_trackings.filter(progress_status='Completed') + dropped_trackings = course_trackings.filter(progress_status='Dropped') + + # Recent Leave Requests + recent_student_leaves = Student_Leave.objects.filter( + created_at__gte=last_week + ).order_by('-created_at')[:5] + + recent_staff_leaves = Staff_leave.objects.filter( + created_at__gte=last_week + ).order_by('-created_at')[:5] + + # Attendance Statistics + today_attendance = Attendance.objects.filter(date=today).count() + today_present = AttendanceRecord.objects.filter( + attendance__date=today, + student_attend=True + ).count() + + # Recent Feedback + recent_feedback = StudentFeedback.objects.order_by('-created_at')[:5] + + # Course Progress Overview + course_progress = [] + for course in Course.objects.all(): + trackings = course.student_trackings.all() + if trackings.exists(): + avg_progress = trackings.aggregate(avg_progress=Avg('completion_percentage'))['avg_progress'] + course_progress.append({ + 'course': course, + 'total_students': trackings.count(), + 'in_progress': trackings.filter(progress_status='In Progress').count(), + 'completed': trackings.filter(progress_status='Completed').count(), + 'average_progress': round(avg_progress, 1) if avg_progress else 0 + }) + + # Monthly Statistics + monthly_stats = { + 'new_students': Student.objects.filter(joining_date__gte=last_month).count(), + 'completed_courses': completed_trackings.filter(actual_end_date__gte=last_month).count(), + 'attendance_rate': round( + (AttendanceRecord.objects.filter( + attendance__date__gte=last_month, + student_attend=True + ).count() / + AttendanceRecord.objects.filter( + attendance__date__gte=last_month + ).count() * 100) if AttendanceRecord.objects.filter( + attendance__date__gte=last_month + ).exists() else 0, + 1 + ) + } + + context = { + **custom_admin_site.each_context(request), # This adds the admin context, including the sidebar. + # Student Statistics + 'total_students': total_students, + 'active_students': active_students, + 'completed_students': completed_students, + 'on_leave_students': on_leave_students, + + # Course Statistics + 'total_courses': total_courses, + 'active_courses': active_courses, + 'completed_courses': completed_courses, + + # Course Tracking Statistics + 'in_progress_trackings': in_progress_trackings.count(), + 'completed_trackings': completed_trackings.count(), + 'dropped_trackings': dropped_trackings.count(), + + # Leave Requests + 'recent_student_leaves': recent_student_leaves, + 'recent_staff_leaves': recent_staff_leaves, + + # Attendance Statistics + 'today_attendance': today_attendance, + 'today_present': today_present, + 'attendance_rate': round( + (today_present / today_attendance * 100) if today_attendance > 0 else 0, + 1 + ), + + # Recent Feedback + 'recent_feedback': recent_feedback, + + # Course Progress + 'course_progress': course_progress, + + # Monthly Statistics + 'monthly_stats': monthly_stats, + } + + return render(request, 'dashboard.html', context) + + +@login_required +def studentDashboard(request): + """View to display the student dashboard with their personal information and progress""" + student = request.user + today = timezone.now().date() + last_week = today - timedelta(days=7) + last_month = today - timedelta(days=30) + + # Course Progress + course_trackings = CourseTracking.objects.filter(student=student) + total_courses = course_trackings.count() + completed_courses = course_trackings.filter(progress_status='Completed').count() + in_progress_courses = course_trackings.filter(progress_status='In Progress').count() + completion_percentage = (completed_courses / total_courses * 100) if total_courses > 0 else 0 + + # Course Details + course_details = {} + for tracking in course_trackings: + course_details[tracking.course.name] = { + 'start_date': tracking.start_date, + 'end_date': tracking.end_date, + 'completion_percentage': tracking.completion_percentage, + 'progress_status': tracking.progress_status, + 'book': tracking.course.book if hasattr(tracking.course, 'book') else None, + 'topics': [] # This will be populated if you have topic tracking + } + + # Attendance Statistics + attendance_records = AttendanceRecord.objects.filter(student=student) + total_classes = attendance_records.count() + classes_attended = attendance_records.filter(student_attend=True).count() + attendance_percentage = (classes_attended / total_classes * 100) if total_classes > 0 else 0 + + # Calculate attendance streaks + current_streak = 0 + longest_streak = 0 + temp_streak = 0 + + for record in attendance_records.order_by('-attendance__date'): + if record.student_attend: + temp_streak += 1 + if temp_streak > longest_streak: + longest_streak = temp_streak + if record.attendance.date >= last_week: + current_streak = temp_streak + else: + temp_streak = 0 + + # Recent Leave Requests + recent_leaves = Student_Leave.objects.filter( + student=student + ).order_by('-created_at')[:5] + + # Recent Feedback + recent_feedback = StudentFeedback.objects.filter( + student=student + ).order_by('-created_at')[:5] + + context = { + **custom_admin_site.each_context(request), # This adds the admin context, including the sidebar. + 'student': student, + 'title': 'Student Dashboard', + + # Course Summary + 'course_summary': { + 'total_courses': total_courses, + 'completed_courses': completed_courses, + 'ongoing_courses': in_progress_courses, + 'completion_percentage': completion_percentage, + 'details': course_details + }, + + # Attendance Summary + 'attendance_summary': { + 'total_classes': total_classes, + 'classes_attended': classes_attended, + 'attendance_percentage': attendance_percentage, + 'current_streak': current_streak, + 'longest_streak': longest_streak + }, + + # Recent Activity + 'recent_leaves': recent_leaves, + 'recent_feedback': recent_feedback, + } + + return render(request, 'student_dashboard.html', context) + +@login_required +def teacherDashboard(request): + """View to display the teacher dashboard with their classes and student information""" + teacher = Staff.objects.get(phone=request.user.phone) # Get the Staff object + today = timezone.now().date() + current_time = timezone.now().time() + + # Get today's routines through attendance + today_routines = Routine.objects.filter( + teacher=teacher, + attendances__date=today + ).order_by('start_time') + + # Process routines to include status and attendance + processed_routines = [] + for routine in today_routines: + attendance = routine.attendances.filter(date=today).first() + + total_students = StudentRoutine.objects.filter(routine=routine).count() + attendance_count = 0 if not attendance else AttendanceRecord.objects.filter( + attendance=attendance, + student_attend=True + ).count() + + processed_routines.append({ + 'name': routine.name, + 'start_time': routine.start_time, + 'end_time': routine.end_time, + 'is_completed': routine.end_time < current_time, + 'is_ongoing': routine.start_time <= current_time <= routine.end_time, + 'total_students': total_students, + 'attendance_count': attendance_count + }) + + # Calculate class statistics + total_classes = today_routines.count() + completed_classes = len([r for r in processed_routines if r['is_completed']]) + + # Get student statistics + total_students = StudentRoutine.objects.filter( + routine__teacher=teacher + ).values('student').distinct().count() + + # Calculate present students for today + present_students = AttendanceRecord.objects.filter( + attendance__routine__teacher=teacher, + attendance__date=today, + student_attend=True + ).count() + + # Get teacher's feedback statistics + feedback = StudentFeedback.objects.filter(teacher=teacher) + avg_rating = feedback.aggregate(Avg('rating'))['rating__avg'] or 0 + avg_rating_rounded = int(avg_rating) + remaining_stars = 5 - avg_rating_rounded + + # Get leave statistics + leaves = Staff_leave.objects.filter(staff=teacher) + pending_leaves = leaves.filter(status=0).count() + approved_leaves = leaves.filter(status=1).count() + rejected_leaves = leaves.filter(status=2).count() + + # Get recent activity + recent_leaves = leaves.order_by('-created_at')[:5] + recent_feedback = feedback.order_by('-created_at')[:5] + + context = { + **custom_admin_site.each_context(request), # This adds the admin context, including the sidebar. + 'teacher': teacher, + 'title': 'Teacher Dashboard', + + # Class Statistics + 'today_classes': total_classes, + 'completed_classes': completed_classes, + 'today_routines': processed_routines, + + # Student Statistics + 'total_students': total_students, + 'present_students': present_students, + + # Feedback Statistics + 'avg_rating': avg_rating, + 'avg_rating_rounded': avg_rating_rounded, + 'remaining_stars': remaining_stars, + + # Leave Statistics + 'pending_leaves': pending_leaves, + 'approved_leaves': approved_leaves, + 'rejected_leaves': rejected_leaves, + + # Recent Activity + 'recent_leaves': recent_leaves, + 'recent_feedback': recent_feedback, + } + + return render(request, 'teacher_dashboard.html', context) diff --git a/student_management_system/settings.py b/student_management_system/settings.py index 44fb2e7..64b90af 100644 --- a/student_management_system/settings.py +++ b/student_management_system/settings.py @@ -112,7 +112,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Asia/Kathmandu" USE_I18N = True @@ -123,147 +123,84 @@ 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", +] # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") + 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", + "authentication","organization","human_management" + "organization.institute", "organization.batch", "organization.course", + "human_management.student", "human_management.staff", "human_management.routine", + "human_management.staff_leave", "human_management.student_leave", + "human_management.studentfeedback", "human_management.coursetracking", + "human_management.attendance", "human_management.institutefeedback" ], - # Custom links to append to app groups, keyed on app name + "icons": { + "auth": "fas fa-users-cog", + "auth.user": "fas fa-user", + "auth.Group": "fas fa-users", + "app.Student": "fas fa-user-graduate", + "app.Course": "fas fa-book", + "app.Staff": "fas fa-chalkboard-teacher", + "app.Attendance": "fas fa-calendar-check", + "app.CourseTracking": "fas fa-chart-line", + "app.StudentFeedback": "fas fa-comment", + }, + "default_icon_parents": "fas fa-chevron-circle-right", + "default_icon_children": "fas fa-circle", + "related_modal_active": False, + "custom_css": None, + "custom_js": None, + "show_ui_builder": False, + "changeform_format": "horizontal_tabs", + "changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"}, + "language_chooser": False, "custom_links": { "app": [ { "name": "Dashboard", "url": "/app/dashboard", - "icon": "fas fa-circle", - "permissions": [], + "icon": "fas fa-tachometer-alt", + "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, @@ -284,14 +221,14 @@ JAZZMIN_UI_TWEAKS = { "sidebar_nav_compact_style": False, "sidebar_nav_legacy_style": False, "sidebar_nav_flat_style": False, - "theme": "lumen", + "theme": "default", "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", - }, + "primary": "btn-primary", + "secondary": "btn-secondary", + "info": "btn-info", + "warning": "btn-warning", + "danger": "btn-danger", + "success": "btn-success" + } } diff --git a/student_management_system/urls.py b/student_management_system/urls.py index 962a80b..ac6627c 100644 --- a/student_management_system/urls.py +++ b/student_management_system/urls.py @@ -17,9 +17,11 @@ 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("", custom_admin_site.urls, name="admin") -] + path("app/", include("app.urls")), + path("", custom_admin_site.urls), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..8a0e7c8 --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,225 @@ +{% extends "admin/base_site.html" %} +{% load static %} +{% load app_filters %} + + +{% block content %} +
+ +
+ +
+
+
+

{{ total_students }}

+

Total Students

+
+ Active: {{ active_students }} | + Completed: {{ completed_students }} | + On Leave: {{ on_leave_students }} +
+
+
+ +
+
+
+ + +
+
+
+

{{ total_courses }}

+

Course Statistics

+
+ Active: {{ active_courses }} | + Completed: {{ completed_courses }} +
+
+
+ +
+
+
+ + +
+
+
+

{{ in_progress_trackings }}

+

Course Progress

+
+ In Progress | + Completed: {{ completed_trackings }} | + Dropped: {{ dropped_trackings }} +
+
+
+ +
+
+
+ + +
+
+
+

{{ today_present }}/{{ today_attendance }}

+

Today's Attendance

+
+ Rate: {{ attendance_rate }}% +
+
+
+ +
+
+
+
+ + +
+ +
+
+
+

Course Progress Overview

+
+
+
+ + + + + + + + + + + + {% for progress in course_progress %} + + + + + + + + {% endfor %} + +
CourseTotal StudentsIn ProgressCompletedAvg Progress
{{ progress.course.name }}{{ progress.total_students }}{{ progress.in_progress }}{{ progress.completed }} +
+
+ {{ progress.average_progress }}% +
+
+
+
+
+
+
+ + +
+
+
+

Recent Activity

+
+
+ +
Recent Leave Requests
+
+
Students
+ {% for leave in recent_student_leaves %} +
+ {{ leave.created_at|date:"M d, Y" }} +
{{ leave.student.name }} - {{ leave.get_status_display }}
+
+ {% empty %} +

No recent student leave requests

+ {% endfor %} +
+ +
Staff
+ {% for leave in recent_staff_leaves %} +
+ {{ leave.created_at|date:"M d, Y" }} +
{{ leave.staff.name }} - {{ leave.get_status_display }}
+
+ {% empty %} +

No recent staff leave requests

+ {% endfor %} + + +
Recent Feedback
+ {% for feedback in recent_feedback %} +
+ {{ feedback.created_at|date:"M d, Y" }} +
{{ feedback.student.name }} - {{ feedback.subject.name }}
+
+ {% for i in feedback.rating|get_range %} + + {% endfor %} +
+
+ {% empty %} +

No recent feedback

+ {% endfor %} +
+
+
+
+ + +
+
+
+
+

Monthly Statistics

+
+
+
+
+
+ + + +
+ New Students + {{ monthly_stats.new_students }} +
+
+
+
+
+ + + +
+ Completed Courses + {{ monthly_stats.completed_courses }} +
+
+
+
+
+ + + +
+ Monthly Attendance Rate + {{ monthly_stats.attendance_rate }}% +
+
+
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/student_dashboard.html b/templates/student_dashboard.html new file mode 100644 index 0000000..22c7a1c --- /dev/null +++ b/templates/student_dashboard.html @@ -0,0 +1,217 @@ +{% extends 'admin/base_site.html' %} +{% load static %} + +{% block content %} +
+ +
+
+
+
+ {% if student.image %} + {{ student.name }} + {% endif %} +
+

Welcome, {{ student.name }}!

+

Student ID: {{ student.id }}

+
+
+
+
+
+ + +
+ +
+
+
+
+
+
Course Progress
+
{{ course_summary.completion_percentage|floatformat:1 }}%
+
+
+
+
+
+
+ Completed: {{ course_summary.completed_courses }}/{{ course_summary.total_courses }} + Ongoing: {{ course_summary.ongoing_courses }} +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Attendance Rate
+
{{ attendance_summary.attendance_percentage|floatformat:1 }}%
+
+
+
+
+
+
+ Present: {{ attendance_summary.classes_attended }}/{{ attendance_summary.total_classes }} +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Attendance Streaks
+
+ Current: {{ attendance_summary.current_streak }} days +
+
+ Best Streak: {{ attendance_summary.longest_streak }} days +
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
Course Details
+
+
+ {% for course_name, details in course_summary.details.items %} +
+
{{ course_name }}
+
+
+

Start Date: {{ details.start_date|default:'-' }}

+

End Date: {{ details.end_date|default:'-' }}

+

Status: {{ details.progress_status }}

+
+
+

Progress:

+
+
{{ details.completion_percentage }}%
+
+ {% if details.book %} + + {% endif %} +
+
+
+
+ {% empty %} +

No courses enrolled yet.

+ {% endfor %} +
+
+
+ + +
+ +
+
+
Recent Leave Requests
+
+
+ {% for leave in recent_leaves %} +
+
+ {{ leave.date }} + + {{ leave.get_status_display }} + +
+

{{ leave.message|truncatechars:100 }}

+
+
+ {% empty %} +

No recent leave requests.

+ {% endfor %} +
+
+ + +
+
+
Recent Feedback
+
+
+ {% for feedback in recent_feedback %} + + {% empty %} +

No recent feedback.

+ {% endfor %} +
+
+
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/templates/teacher_dashboard.html b/templates/teacher_dashboard.html new file mode 100644 index 0000000..e449724 --- /dev/null +++ b/templates/teacher_dashboard.html @@ -0,0 +1,250 @@ +{% extends 'admin/base_site.html' %} +{% load static %} + +{% block content %} +
+ +
+
+
+
+ {% if teacher.image %} + {{ teacher.name }} + {% endif %} +
+

Welcome, {{ teacher.name }}!

+

{{ teacher.designation }}

+
+
+
+
+
+ + +
+ +
+
+
+
+
+
Today's Classes
+
{{ today_classes }}
+
+ Completed: {{ completed_classes }} +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Total Students
+
{{ total_students }}
+
+ Present Today: {{ present_students }} +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Average Rating
+
+ {{ avg_rating|floatformat:1 }}/5.0 +
+
+
+ {% with ''|center:avg_rating_rounded as range %} + {% for _ in range %} + + {% endfor %} + {% endwith %} + {% with ''|center:remaining_stars as range %} + {% for _ in range %} + + {% endfor %} + {% endwith %} +
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Leave Requests
+
{{ pending_leaves }}
+
+ Approved: {{ approved_leaves }} | + Rejected: {{ rejected_leaves }} +
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
Today's Schedule
+
+
+ {% if today_routines %} +
+ + + + + + + + + + + {% for routine in today_routines %} + + + + + + + {% endfor %} + +
TimeCourseStatusAttendance
{{ routine.start_time|time:"g:i A" }} - {{ routine.end_time|time:"g:i A" }}{{ routine.name }} + {% if routine.is_completed %} + Completed + {% elif routine.is_ongoing %} + Ongoing + {% else %} + Upcoming + {% endif %} + {{ routine.attendance_count }}/{{ routine.total_students }}
+
+ {% else %} +

No classes scheduled for today.

+ {% endif %} +
+
+
+ + +
+ +
+
+
Recent Leave Requests
+
+
+ {% for leave in recent_leaves %} +
+
+ {{ leave.date }} + + {{ leave.get_status_display }} + +
+

{{ leave.message|truncatechars:100 }}

+
+
+ {% empty %} +

No recent leave requests.

+ {% endfor %} +
+
+ + +
+
+
Recent Feedback
+
+
+ {% for feedback in recent_feedback %} + + {% empty %} +

No recent feedback.

+ {% endfor %} +
+
+
+
+
+ + + +{% endblock %} \ No newline at end of file