Files
2025-04-20 17:46:30 +05:45

1930 lines
77 KiB
Python

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Avg, Q
from django.http import JsonResponse
from django.utils import timezone
from datetime import datetime, timedelta, time
from django.contrib import messages
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import Group
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from app.firebase import send_push_notification, FCMDevice
from app.models import (
Staff,
Student,
Course,
Subject,
Routine,
Attendance,
AttendanceRecord,
StudentFeedback,
CourseTracking,
TeacherParentMeeting,
Batch,
Parent,
Notice,
)
@login_required
def hod_dashboard(request):
"""HOD dashboard view showing department overview and management options"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Only HODs can access this dashboard.")
return redirect("hodDashboard")
# Get the HOD's department (course)
hod = request.user
if not hasattr(hod, "course") or not hod.course:
messages.error(request, "You are not assigned as HOD of any department.")
return redirect("hodDashboard")
course = hod.course
# Get department statistics
total_students = Student.objects.filter(course=course).count()
total_staff = Staff.objects.filter(course=course).count()
total_subjects = Subject.objects.filter(course=course).count()
# Get all subjects for the course
subjects = Subject.objects.filter(course=course).order_by('period_or_year', 'name')
# Get routines to find teachers for each subject
routines = Routine.objects.filter(course=course, is_active=True).select_related(
"subject", "teacher"
)
# Create a mapping of subject_id to teacher
teacher_map = {}
for routine in routines:
teacher_map[routine.subject_id] = routine.teacher
# Get all teachers who teach in this course through active routines
staff = Staff.objects.filter(
id__in=routines.values_list('teacher_id', flat=True)
).prefetch_related(
'routine_set',
'routine_set__subject'
).distinct()
# Filter routines for each staff member to only show subjects from this course
for staff_member in staff:
staff_member.filtered_subjects = Subject.objects.filter(
course=course,
id__in=staff_member.routine_set.filter(is_active=True).values_list('subject_id', flat=True)
)
# Get today's classes and their attendance
today = timezone.now().date()
today_classes = Routine.objects.filter(
course=course,
is_active=True
).select_related("subject", "teacher")
# Get attendance for today's classes
today_attendance = Attendance.objects.filter(
routine__course=course,
date=today
).select_related("routine")
# Create a dictionary of routine_id to attendance
attendance_map = {att.routine_id: att for att in today_attendance}
# Attach attendance to each class
for class_obj in today_classes:
class_obj.attendance = attendance_map.get(class_obj.id)
# Get all student progress records for display in the progress section
student_progress_all = (
CourseTracking.objects.filter(student__course=course)
.select_related("student")
.order_by("-completion_percentage")
)
# Calculate progress metrics
avg_completion = student_progress_all.aggregate(Avg('completion_percentage'))['completion_percentage__avg'] or 0
avg_completion = round(avg_completion, 1)
students_on_track = student_progress_all.filter(completion_percentage__gte=75).count()
students_at_risk = student_progress_all.filter(completion_percentage__lt=40).count()
# For the dashboard overview, still show just top 5
student_progress = student_progress_all[:5]
# Get all meetings
meetings = TeacherParentMeeting.objects.all().order_by("-meeting_date", "-meeting_time")
# Get recent feedback
recent_feedback = (
StudentFeedback.objects.filter(student__course=course)
.select_related("student", "teacher")
.order_by("-created_at")[:5]
)
# Get all students in the course
students = Student.objects.filter(course=course).order_by('name')
# Get all active batches
batches = Batch.objects.filter(is_active=True).order_by('-year')
# Get notices
notices = Notice.objects.all().order_by('-created_at')
context = {
"title": "HOD Dashboard",
"hod": hod,
"course": course,
"total_students": total_students,
"total_staff": total_staff,
"total_subjects": total_subjects,
"today_classes": today_classes,
"student_progress": student_progress,
"student_progress_all": student_progress_all, # All student progress records
"avg_completion": avg_completion,
"students_on_track": students_on_track,
"students_at_risk": students_at_risk,
"meetings": meetings, # Changed from upcoming_meetings to all meetings
"recent_feedback": recent_feedback,
"subjects": subjects,
"staff": staff,
"students": students,
"batches": batches,
"notices": notices,
"class_routines": routines, # Add routines to the context
}
return render(request, "hod/dashboard.html", context)
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
return redirect("hodDashboard")
@login_required
def add_subject(request):
"""Add a new subject to the HOD's course"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can add subjects."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
if request.method == "POST":
# Get form data
name = request.POST.get("name")
code = request.POST.get("code")
period_or_year = request.POST.get("period_or_year")
# Validate required fields
if not name or not period_or_year:
return JsonResponse({"success": False, "message": "Subject name and period/year are required."})
try:
period_or_year = int(period_or_year)
except ValueError:
return JsonResponse({"success": False, "message": "Invalid period/year value."})
# Validate period/year against course duration
max_periods = course.duration * 2 if course.duration_type == "Semester" else course.duration
if period_or_year < 1 or period_or_year > max_periods:
return JsonResponse({"success": False, "message": f"Period/Year must be between 1 and {max_periods}."})
# Create the subject
subject = Subject(
name=name,
code=code,
period_or_year=period_or_year,
course=course
)
# Handle syllabus PDF if provided
if request.FILES.get('syllabus_pdf'):
subject.syllabus_pdf = request.FILES['syllabus_pdf']
subject.save()
return JsonResponse({"success": True, "message": "Subject added successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def view_subject(request, subject_id):
"""View details of a specific subject"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view subject details."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get the subject and ensure it belongs to the HOD's course
subject = get_object_or_404(Subject, id=subject_id, course=hod.course)
# Get the assigned teacher from routine if any
teacher = None
routine = Routine.objects.filter(subject=subject, is_active=True).first()
if routine:
teacher = routine.teacher
# Prepare response data
response_data = {
"success": True,
"subject": {
"id": subject.id,
"name": subject.name,
"code": subject.code or "N/A",
"course": {
"id": subject.course.id,
"name": subject.course.name,
"duration_type": subject.course.duration_type,
"duration": subject.course.duration
},
"period_or_year": subject.period_or_year,
"teacher": {
"id": teacher.id,
"name": teacher.name
} if teacher else None,
"syllabus_pdf": subject.syllabus_pdf.url if subject.syllabus_pdf else None
}
}
return JsonResponse(response_data)
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def edit_subject(request, subject_id):
"""Edit an existing subject"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can edit subjects."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get the subject and ensure it belongs to the HOD's course
subject = get_object_or_404(Subject, id=subject_id, course=hod.course)
if request.method == "POST":
# Get form data
name = request.POST.get("name")
code = request.POST.get("code")
period_or_year = request.POST.get("period_or_year")
teacher_id = request.POST.get("teacher")
description = request.POST.get("description")
# Validate required fields
if not name or not period_or_year:
return JsonResponse({"success": False, "message": "Subject name and period/year are required."})
try:
period_or_year = int(period_or_year)
except ValueError:
return JsonResponse({"success": False, "message": "Invalid period/year value."})
# Validate period/year against course duration
max_periods = subject.course.duration * 2 if subject.course.duration_type == "Semester" else subject.course.duration
if period_or_year < 1 or period_or_year > max_periods:
return JsonResponse({"success": False, "message": f"Period/Year must be between 1 and {max_periods}."})
# Update subject details
subject.name = name
subject.code = code
subject.period_or_year = period_or_year
subject.description = description
# Handle syllabus PDF upload if provided
if request.FILES.get('syllabus_pdf'):
subject.syllabus_pdf = request.FILES['syllabus_pdf']
subject.save()
# Update or create routine for teacher assignment
if teacher_id:
try:
teacher = Staff.objects.get(id=teacher_id, course=hod.course)
routine, created = Routine.objects.get_or_create(
subject=subject,
course=hod.course,
defaults={
'teacher': teacher,
'is_active': True
}
)
if not created:
routine.teacher = teacher
routine.save()
except Staff.DoesNotExist:
return JsonResponse({"success": False, "message": "Invalid teacher selected."})
return JsonResponse({"success": True, "message": "Subject updated successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required(login_url='login')
def delete_subject(request, subject_id):
try:
subject = Subject.objects.get(id=subject_id)
# Check if the subject is being used in any routines
if Routine.objects.filter(subject=subject).exists():
messages.error(request, 'Cannot delete subject as it is being used in routines.')
return redirect('hodDashboard')
# Delete the subject
subject.delete()
messages.success(request, 'Subject deleted successfully.')
return redirect('hodDashboard')
except Subject.DoesNotExist:
messages.error(request, 'Subject not found.')
return redirect('hodDashboard')
except Exception as e:
messages.error(request, f'Error deleting subject: {str(e)}')
return redirect('hodDashboard')
@login_required
def add_staff(request):
"""Add a new staff member"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can add staff members."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
if request.method == "POST":
# Get form data
name = request.POST.get("name")
email = request.POST.get("email")
phone = request.POST.get("phone")
gender = request.POST.get("gender")
birth_date = request.POST.get("birth_date")
temporary_address = request.POST.get("temporary_address")
permanent_address = request.POST.get("permanent_address")
marital_status = request.POST.get("marital_status")
citizenship_no = request.POST.get("citizenship_no")
subject_ids = request.POST.getlist("subjects[]") # Updated to match the form field name
# Validate required fields
if not name or not phone:
return JsonResponse({"success": False, "message": "Name and phone are required fields."})
if not subject_ids:
return JsonResponse({"success": False, "message": "Please assign at least one subject."})
# Check if phone number already exists
if Staff.objects.filter(phone=phone).exists():
return JsonResponse({"success": False, "message": "Phone number already exists."})
# Create the staff member with default password and current date as joining date
staff = Staff(
name=name,
email=email,
phone=phone,
gender=gender,
birth_date=birth_date if birth_date else None,
temporary_address=temporary_address,
permanent_address=permanent_address,
marital_status=marital_status,
citizenship_no=citizenship_no,
password=make_password("123"), # Default password
is_active=True,
joining_date=timezone.now().date(), # Set joining date to current date
course=course, # Assign to HOD's course
designation="Teacher" # Set designation as Teacher
)
staff.save()
# Add to Teacher group
try:
teacher_group = Group.objects.get(name="Teacher")
staff.groups.add(teacher_group)
except Group.DoesNotExist:
# Create Teacher group if it doesn't exist
teacher_group = Group.objects.create(name="Teacher")
staff.groups.add(teacher_group)
# Create routines for assigned subjects with default time slots
default_start_time = time(10, 0) # 10:00 AM
default_end_time = time(11, 0) # 11:00 AM
for subject_id in subject_ids:
try:
subject = Subject.objects.get(id=subject_id, course=course)
Routine.objects.create(
teacher=staff,
subject=subject,
course=course,
period_or_year=subject.period_or_year,
start_time=default_start_time,
end_time=default_end_time,
is_active=True
)
except Subject.DoesNotExist:
continue
return JsonResponse({"success": True, "message": "Staff member added successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def view_staff(request, staff_id):
"""View details of a staff member"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view staff details."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
# Get the staff member and ensure they belong to the HOD's course
staff = get_object_or_404(Staff, id=staff_id)
# Get only the subjects from this course taught by this staff member
subjects = Subject.objects.filter(
course=course,
id__in=Routine.objects.filter(teacher=staff, is_active=True).values_list('subject_id', flat=True)
)
# Prepare response data with all staff fields
response_data = {
"success": True,
"staff": {
"id": staff.id,
"name": staff.name,
"email": staff.email,
"phone": staff.phone,
"designation": staff.designation,
"gender": staff.gender,
"birth_date": staff.birth_date.strftime("%Y-%m-%d") if staff.birth_date else None,
"temporary_address": staff.temporary_address,
"permanent_address": staff.permanent_address,
"marital_status": staff.marital_status,
"parent_name": staff.parent_name,
"parent_phone": staff.parent_phone,
"citizenship_no": staff.citizenship_no,
"passport": staff.passport,
"image": staff.image.url if staff.image else None,
"joining_date": staff.joining_date.strftime("%Y-%m-%d") if staff.joining_date else None,
"is_active": staff.is_active,
"subjects": [{"id": subject.id, "name": subject.name} for subject in subjects]
}
}
return JsonResponse(response_data)
except Exception as e:
print(f"Error in view_staff: {str(e)}") # Debug log
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def edit_staff(request, staff_id):
"""Edit an existing staff member"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can edit staff members."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get the staff member and ensure they belong to the HOD's course
staff = get_object_or_404(Staff, id=staff_id)
if request.method == "POST":
# Get form data
name = request.POST.get("name")
email = request.POST.get("email")
phone = request.POST.get("phone")
is_active = request.POST.get("is_active") == "true"
# Validate required fields
if not name or not phone:
return JsonResponse({"success": False, "message": "Name and phone are required."})
# Check if phone number already exists for other staff members
if Staff.objects.filter(phone=phone).exclude(id=staff_id).exists():
return JsonResponse({"success": False, "message": "Phone number already exists."})
# Update staff details
staff.name = name
staff.email = email
staff.phone = phone
staff.is_active = is_active
# Handle profile picture upload if provided
if request.FILES.get('image'):
staff.image = request.FILES['image']
staff.save()
return JsonResponse({"success": True, "message": "Staff member updated successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def delete_staff(request, staff_id):
"""Delete a staff member"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Only Head of Department can delete staff members")
return redirect("hodDashboard")
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
messages.error(request, "You are not assigned as HOD of any department")
return redirect("hodDashboard")
# Get the staff member
staff = get_object_or_404(Staff, id=staff_id)
# Check if staff belongs to HOD's course
if staff.course != hod.course:
messages.error(request, "You can only delete staff members from your department")
return redirect("hodDashboard")
# Delete all routines associated with the staff
staff_routines = Routine.objects.filter(teacher=staff)
staff_routines.delete()
# Store staff name for the success message
staff_name = staff.name
# Delete the staff member
staff.delete()
# Add success message
messages.success(request, f'Staff member {staff_name} has been deleted successfully')
# Redirect back to dashboard
return redirect("hodDashboard")
except Staff.DoesNotExist:
messages.error(request, "Staff member not found")
return redirect("hodDashboard")
except Exception as e:
messages.error(request, "An error occurred while deleting the staff member")
return redirect("hodDashboard")
@login_required
def add_student(request):
"""Add a new student to the HOD's course"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can add students."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
if request.method == "POST":
# Get form data
name = request.POST.get("name")
phone = request.POST.get("phone")
email = request.POST.get("email")
gender = request.POST.get("gender")
birth_date = request.POST.get("birth_date")
batch_id = request.POST.get("batch")
status = request.POST.get("status")
temporary_address = request.POST.get("temporary_address")
permanent_address = request.POST.get("permanent_address")
parent_name = request.POST.get("parent_name")
parent_phone = request.POST.get("parent_phone")
parent_address = request.POST.get("parent_address")
citizenship_no = request.POST.get("citizenship_no")
# Validate required fields
if not name or not phone or not batch_id or not status or not parent_phone:
return JsonResponse({"success": False, "message": "Name, phone, batch, status, and parent phone are required."})
# Check if phone already exists
if Student.objects.filter(phone=phone).exists():
return JsonResponse({"success": False, "message": "Phone number already exists."})
try:
# Get the batch
batch = Batch.objects.get(id=batch_id)
# Create or get parent account
try:
parent = Parent.objects.get(phone=parent_phone)
except Parent.DoesNotExist:
parent = Parent(
name=parent_name,
phone=parent_phone,
address=parent_address,
password=make_password("123") # Default password
)
parent.save()
# Add parent to Parent group
try:
parent_group = Group.objects.get(name="Parent")
parent.groups.add(parent_group)
except Group.DoesNotExist:
parent_group = Group.objects.create(name="Parent")
parent.groups.add(parent_group)
# Create the student
student = Student(
name=name,
phone=phone,
email=email,
gender=gender,
birth_date=birth_date if birth_date else None,
joining_date=batch.year if batch.year else timezone.now().date(),
temporary_address=temporary_address,
permanent_address=permanent_address,
parent_name=parent_name,
parent_phone=parent_phone,
citizenship_no=citizenship_no,
course=course,
status=status,
is_active=True,
password=make_password("123") # Set default password to "123"
)
# Save the student first to get an ID
student.save()
# Add student to batch
student.batches.add(batch)
# Add student to Student group
try:
student_group = Group.objects.get(name="Student")
student.groups.add(student_group)
except Group.DoesNotExist:
student_group = Group.objects.create(name="Student")
student.groups.add(student_group)
# Associate student with parent
parent.students.add(student)
return JsonResponse({
"success": True,
"message": "Student added successfully. Default password is: 123"
})
except Batch.DoesNotExist:
return JsonResponse({
"success": False,
"message": "Selected batch does not exist."
})
except Exception as e:
return JsonResponse({
"success": False,
"message": f"Error creating student: {str(e)}"
})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({
"success": False,
"message": f"An error occurred: {str(e)}"
})
@login_required
def get_student(request, student_id):
"""Get student details"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view student details."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get the student and ensure they belong to the HOD's course
student = get_object_or_404(Student, id=student_id)
# Prepare response data
response_data = {
"success": True,
"student": {
"id": student.id,
"name": student.name,
"email": student.email,
"phone": student.phone,
"gender": student.gender,
"birth_date": student.birth_date.strftime("%Y-%m-%d") if student.birth_date else None,
"joining_date": student.joining_date.strftime("%Y-%m-%d") if student.joining_date else None,
"temporary_address": student.temporary_address,
"permanent_address": student.permanent_address,
"parent_name": student.parent_name,
"parent_phone": student.parent_phone,
"citizenship_no": student.citizenship_no,
"image": student.image.url if student.image else None,
"course_name": student.course.name if student.course else None,
"current_period": student.current_period,
"is_active": student.is_active
}
}
return JsonResponse(response_data)
except Exception as e:
print(f"Error in get_student: {str(e)}") # Debug log
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def edit_student(request, student_id):
"""Edit an existing student"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can edit students."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get the student and ensure they belong to the HOD's course
student = get_object_or_404(Student, id=student_id, course=hod.course)
if request.method == "POST":
# Get form data
name = request.POST.get("name")
email = request.POST.get("email")
phone = request.POST.get("phone")
gender = request.POST.get("gender")
birth_date = request.POST.get("birth_date")
batch_id = request.POST.get("batch")
temporary_address = request.POST.get("temporary_address")
permanent_address = request.POST.get("permanent_address")
parent_name = request.POST.get("parent_name")
parent_phone = request.POST.get("parent_phone")
citizenship_no = request.POST.get("citizenship_no")
# Validate required fields
if not name or not phone:
return JsonResponse({"success": False, "message": "Name and phone are required fields."})
# Check if email or phone already exists for other students
if email and Student.objects.filter(email=email).exclude(id=student_id).exists():
return JsonResponse({"success": False, "message": "Email already exists."})
if Student.objects.filter(phone=phone).exclude(id=student_id).exists():
return JsonResponse({"success": False, "message": "Phone number already exists."})
# Update student details
student.name = name
student.email = email
student.phone = phone
student.gender = gender
student.birth_date = birth_date if birth_date else None
student.temporary_address = temporary_address
student.permanent_address = permanent_address
student.parent_name = parent_name
student.parent_phone = parent_phone
student.citizenship_no = citizenship_no
# Update batch and set joining date based on batch if provided
if batch_id:
try:
batch = Batch.objects.get(id=batch_id)
# Clear existing batches and add the new one
student.batches.clear()
student.batches.add(batch)
# Set joining date based on batch year
student.joining_date = batch.year if batch.year else timezone.now().date()
except Batch.DoesNotExist:
pass
# Handle profile picture upload if provided
if request.FILES.get('image'):
student.image = request.FILES['image']
student.save()
return JsonResponse({"success": True, "message": "Student updated successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def delete_student(request, student_id):
"""Delete a student"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Only HODs can delete students.")
return redirect('hodDashboard')
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
messages.error(request, "You are not assigned as HOD of any department.")
return redirect('hodDashboard')
# Get the student and ensure they belong to the HOD's course
student = get_object_or_404(Student, id=student_id, course=hod.course)
# Check if student has any attendance records
if AttendanceRecord.objects.filter(student=student).exists():
messages.error(request, "Cannot delete student as they have attendance records.")
return redirect('hodDashboard')
# Delete the student
student.delete()
messages.success(request, "Student deleted successfully.")
return redirect('hodDashboard')
except Student.DoesNotExist:
messages.error(request, "Student not found.")
return redirect('hodDashboard')
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
return redirect('hodDashboard')
@login_required
def add_notice(request):
"""Add a new notice and notify students and parents"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Only HODs can add notices.")
return redirect('hodDashboard')
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
messages.error(request, "You are not assigned as HOD of any department.")
return redirect('hodDashboard')
course = hod.course
if request.method == "POST":
title = request.POST.get("title")
message = request.POST.get("message")
# Validate required fields
if not title:
messages.error(request, "Title is required.")
return redirect('hodDashboard')
# Create notice
notice = Notice(
title=title,
message=message
)
# Handle image if provided
if request.FILES.get("image"):
notice.image = request.FILES.get("image")
# Handle file attachment if provided
if request.FILES.get("file"):
notice.file = request.FILES.get("file")
notice.save()
# Get students from the HOD's course with FCM tokens
students = Student.objects.filter(course=course, fcm_token__isnull=False)
# Get parents of these students with FCM tokens
student_ids = students.values_list('id', flat=True)
parents = Parent.objects.filter(students__in=student_ids, fcm_token__isnull=False).distinct()
# Collect all FCM tokens
tokens = []
# Add student tokens
student_tokens = students.values_list('fcm_token', flat=True)
tokens.extend(student_tokens)
# Add parent tokens
parent_tokens = parents.values_list('fcm_token', flat=True)
tokens.extend(parent_tokens)
# Remove any empty or null tokens
tokens = [token for token in tokens if token]
# Send push notifications if we have any tokens
if tokens:
try:
# Send notification to all tokens at once
success_count, failure_count, failed_tokens = send_push_notification(
"New Notice: {title}",
message[:100] + "..." if len(message) > 100 else message,
tokens
)
if failure_count > 0:
print(f"Failed to send notifications to {failure_count} devices")
print(f"Failed tokens: {failed_tokens}")
except Exception as e:
print(f"Error sending notifications: {str(e)}")
messages.success(request, "Notice added successfully and notifications sent.")
return redirect('hodDashboard')
return render(request, 'hod/dashboard.html')
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
return redirect('hodDashboard')
@login_required
def view_notice(request, notice_id):
"""View notice details"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Access denied.")
return redirect('hodDashboard')
# Get notice
notice = get_object_or_404(Notice, id=notice_id)
context = {
"notice": notice,
"title": "View Notice"
}
return render(request, 'hod/view_notice.html', context)
except Notice.DoesNotExist:
messages.error(request, "Notice not found.")
return redirect('hodDashboard')
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
return redirect('hodDashboard')
@login_required
def delete_notice(request, notice_id):
"""Delete a notice"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
messages.error(request, "Only HODs can delete notices.")
return redirect('hodDashboard')
# Get notice
notice = get_object_or_404(Notice, id=notice_id)
# Delete notice
notice.delete()
messages.success(request, "Notice deleted successfully.")
return redirect('hodDashboard')
except Notice.DoesNotExist:
messages.error(request, "Notice not found.")
return redirect('hodDashboard')
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
return redirect('hodDashboard')
@login_required
def add_meeting(request):
"""Add a new teacher-parent meeting and notify relevant users"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can schedule meetings."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
if request.method == "POST":
meeting_date = request.POST.get("meeting_date")
meeting_time = request.POST.get("meeting_time")
duration = request.POST.get("duration")
agenda = request.POST.get("agenda", "")
is_online = request.POST.get("is_online") == "true"
meeting_link = request.POST.get("meeting_link", "") or "" # Ensure never NULL
# Validate required fields
if not all([meeting_date, meeting_time, duration]):
return JsonResponse({"success": False, "message": "Date, time and duration are required."})
# Create meeting
meeting = TeacherParentMeeting(
meeting_date=meeting_date,
meeting_time=meeting_time,
duration=duration,
agenda=agenda,
is_online=is_online,
meeting_link=meeting_link, # Now guaranteed to be at least empty string
status="scheduled"
)
meeting.save()
# Get students from the HOD's course with FCM tokens
students = Student.objects.filter(course=course, fcm_token__isnull=False)
# Get parents of these students with FCM tokens
student_ids = students.values_list('id', flat=True)
parents = Parent.objects.filter(students__in=student_ids, fcm_token__isnull=False).distinct()
# Collect all FCM tokens
tokens = []
# Add student tokens
student_tokens = students.values_list('fcm_token', flat=True)
tokens.extend(student_tokens)
# Add parent tokens
parent_tokens = parents.values_list('fcm_token', flat=True)
tokens.extend(parent_tokens)
# Remove any empty or null tokens
tokens = [token for token in tokens if token]
# Send push notifications if we have any tokens
if tokens:
try:
# Send notification to all tokens at once
success_count, failure_count, failed_tokens = send_push_notification(
"New Meeting Scheduled",
f"A new meeting has been scheduled for {meeting_date} at {meeting_time}",
tokens
)
if failure_count > 0:
print(f"Failed to send notifications to {failure_count} devices")
print(f"Failed tokens: {failed_tokens}")
except Exception as e:
print(f"Error sending notifications: {str(e)}")
return JsonResponse({"success": True, "message": "Meeting scheduled successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_meeting(request, meeting_id):
"""Get meeting details"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view meeting details."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
# Prepare response data
response_data = {
"success": True,
"meeting": {
"id": meeting.id,
"meeting_date": meeting.meeting_date.strftime("%Y-%m-%d"),
"meeting_time": meeting.meeting_time.strftime("%H:%M"),
"duration": meeting.duration,
"status": meeting.status,
"status_display": meeting.get_status_display(),
"agenda": meeting.agenda,
"notes": meeting.notes,
"is_online": meeting.is_online,
"meeting_link": meeting.meeting_link,
"cancellation_reason": meeting.cancellation_reason,
"created_at": meeting.created_at.strftime("%Y-%m-%d %H:%M:%S")
}
}
return JsonResponse(response_data)
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def edit_meeting(request, meeting_id):
"""Edit an existing meeting"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can edit meetings."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
if request.method == "PUT":
meeting_date = request.POST.get("meeting_date")
meeting_time = request.POST.get("meeting_time")
duration = request.POST.get("duration")
agenda = request.POST.get("agenda")
notes = request.POST.get("notes", "") # Get notes field with empty string default
is_online = request.POST.get("is_online") == "true"
meeting_link = request.POST.get("meeting_link", "") # Default to empty string
# Validate required fields
if not all([meeting_date, meeting_time, duration]):
return JsonResponse({"error": "Date, time and duration are required"}, status=400)
# Update meeting
meeting.meeting_date = meeting_date
meeting.meeting_time = meeting_time
meeting.duration = duration
meeting.agenda = agenda
meeting.notes = notes # Save notes field
meeting.is_online = is_online
meeting.meeting_link = meeting_link # Always set meeting_link, even if empty
meeting.status = "rescheduled" if meeting.status == "scheduled" else meeting.status
meeting.save()
# Get students and parents to notify
students = Student.objects.filter(course=request.user.course, fcm_token__isnull=False)
student_ids = students.values_list('id', flat=True)
parents = Parent.objects.filter(students__in=student_ids, fcm_token__isnull=False).distinct()
# Collect FCM tokens
tokens = list(students.values_list('fcm_token', flat=True)) + list(parents.values_list('fcm_token', flat=True))
tokens = [token for token in tokens if token]
# Send notifications
if tokens:
try:
success_count, failure_count, failed_tokens = send_push_notification(
"Meeting Updated",
f"Meeting scheduled for {meeting_date} at {meeting_time} has been updated",
tokens
)
if failure_count > 0:
print(f"Failed to send notifications to {failure_count} devices")
except Exception as e:
print(f"Error sending notifications: {str(e)}")
return JsonResponse({"success": True, "message": "Meeting updated successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def cancel_meeting(request, meeting_id):
"""Cancel a meeting"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can cancel meetings."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
if request.method == "POST":
cancellation_reason = request.POST.get("cancellation_reason")
if not cancellation_reason:
return JsonResponse({"success": False, "message": "Cancellation reason is required."})
# Update meeting
meeting.status = "cancelled"
meeting.cancellation_reason = cancellation_reason
meeting.save()
# Get students and parents to notify
students = Student.objects.filter(course=request.user.course, fcm_token__isnull=False)
student_ids = students.values_list('id', flat=True)
parents = Parent.objects.filter(students__in=student_ids, fcm_token__isnull=False).distinct()
# Collect FCM tokens
tokens = list(students.values_list('fcm_token', flat=True)) + list(parents.values_list('fcm_token', flat=True))
tokens = [token for token in tokens if token]
# Send notifications
if tokens:
try:
success_count, failure_count, failed_tokens = send_push_notification(
"Meeting Cancelled",
f"Meeting scheduled for {meeting.meeting_date} at {meeting.meeting_time} has been cancelled",
tokens
)
if failure_count > 0:
print(f"Failed to send notifications to {failure_count} devices")
except Exception as e:
print(f"Error sending notifications: {str(e)}")
return JsonResponse({"success": True, "message": "Meeting cancelled successfully."})
return JsonResponse({"success": False, "message": "Invalid request method."})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_meeting_notes(request, meeting_id):
"""Get meeting notes"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view meeting notes."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
return JsonResponse({
"success": True,
"notes": meeting.notes or "No notes available"
})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_meeting_agenda(request, meeting_id):
"""Get meeting agenda"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view meeting agenda."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
return JsonResponse({
"success": True,
"agenda": meeting.agenda or "No agenda available"
})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_cancellation_reason(request, meeting_id):
"""Get meeting cancellation reason"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view cancellation reasons."})
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
if meeting.status != "cancelled":
return JsonResponse({"success": False, "message": "Meeting is not cancelled."})
return JsonResponse({
"success": True,
"reason": meeting.cancellation_reason or "No reason provided"
})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"success": False, "message": "Meeting not found."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
# API endpoints for meeting management
@login_required
@require_http_methods(["GET"])
def api_get_meeting(request, meeting_id):
"""API endpoint to get meeting details"""
try:
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
# Prepare response data
response_data = {
"id": meeting.id,
"meeting_date": meeting.meeting_date.strftime("%Y-%m-%d"),
"meeting_time": meeting.meeting_time.strftime("%H:%M"),
"duration": meeting.duration,
"status": meeting.status,
"status_display": meeting.get_status_display(),
"agenda": meeting.agenda,
"notes": meeting.notes,
"is_online": meeting.is_online,
"meeting_link": meeting.meeting_link,
"cancellation_reason": meeting.cancellation_reason,
"created_at": meeting.created_at.strftime("%Y-%m-%d %H:%M:%S")
}
return JsonResponse(response_data)
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"error": "Meeting not found"}, status=404)
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
@login_required
@csrf_exempt
@require_http_methods(["POST"])
def api_create_meeting(request):
"""API endpoint to create a new meeting"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"error": "Only HODs can schedule meetings"}, status=403)
# Get form data
meeting_date = request.POST.get("meeting_date")
meeting_time = request.POST.get("meeting_time")
duration = request.POST.get("duration")
agenda = request.POST.get("agenda", "")
is_online = request.POST.get("is_online") == "true"
meeting_link = request.POST.get("meeting_link", "") or "" # Ensure never NULL
# Validate required fields
if not all([meeting_date, meeting_time, duration]):
return JsonResponse({"error": "Date, time and duration are required"}, status=400)
# Create meeting
meeting = TeacherParentMeeting(
meeting_date=meeting_date,
meeting_time=meeting_time,
duration=duration,
agenda=agenda,
is_online=is_online,
meeting_link=meeting_link, # Now guaranteed to be at least empty string
status="scheduled"
)
meeting.save()
# Return success response
return JsonResponse({
"id": meeting.id,
"message": "Meeting scheduled successfully"
})
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
@login_required
@csrf_exempt
@require_http_methods(["PUT", "POST"])
def api_update_meeting(request, meeting_id):
"""API endpoint to update an existing meeting"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"error": "Only HODs can edit meetings"}, status=403)
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
# Get form data
meeting_date = request.POST.get("meeting_date")
meeting_time = request.POST.get("meeting_time")
duration = request.POST.get("duration")
agenda = request.POST.get("agenda")
notes = request.POST.get("notes", "") # Get notes field with empty string default
is_online = request.POST.get("is_online") == "true"
meeting_link = request.POST.get("meeting_link", "") # Default to empty string
# Validate required fields
if not all([meeting_date, meeting_time, duration]):
return JsonResponse({"error": "Date, time and duration are required"}, status=400)
# Update meeting
meeting.meeting_date = meeting_date
meeting.meeting_time = meeting_time
meeting.duration = duration
meeting.agenda = agenda
meeting.notes = notes # Save notes field
meeting.is_online = is_online
meeting.meeting_link = meeting_link # Always set meeting_link, even if empty
meeting.status = "rescheduled" if meeting.status == "scheduled" else meeting.status
meeting.save()
# Return success response
return JsonResponse({
"id": meeting.id,
"message": "Meeting updated successfully"
})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"error": "Meeting not found"}, status=404)
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
@login_required
@csrf_exempt
@require_http_methods(["POST"])
def api_cancel_meeting(request, meeting_id):
"""API endpoint to cancel a meeting"""
try:
# Check if user is HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"error": "Only HODs can cancel meetings"}, status=403)
# Get meeting
meeting = get_object_or_404(TeacherParentMeeting, id=meeting_id)
# Get cancellation reason
cancellation_reason = request.POST.get("cancellation_reason")
if not cancellation_reason:
return JsonResponse({"error": "Cancellation reason is required"}, status=400)
# Update meeting
meeting.status = "cancelled"
meeting.cancellation_reason = cancellation_reason
meeting.save()
# Return success response
return JsonResponse({
"id": meeting.id,
"message": "Meeting cancelled successfully"
})
except TeacherParentMeeting.DoesNotExist:
return JsonResponse({"error": "Meeting not found"}, status=404)
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
@login_required
@csrf_exempt
@require_http_methods(["POST"])
def api_create_routine(request):
"""API endpoint to create a new routine"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can create routines."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
# Get form data
subject_id = request.POST.get("subject_id")
teacher_id = request.POST.get("teacher_id")
start_time = request.POST.get("start_time")
end_time = request.POST.get("end_time")
period_or_year = request.POST.get("period_or_year")
# Validate required fields
if not subject_id or not teacher_id or not start_time or not end_time or not period_or_year:
return JsonResponse({"success": False, "message": "All fields are required."})
# Get subject and teacher objects
try:
subject = Subject.objects.get(id=subject_id, course=course)
teacher = Staff.objects.get(id=teacher_id)
except (Subject.DoesNotExist, Staff.DoesNotExist):
return JsonResponse({"success": False, "message": "Invalid subject or teacher."})
# Create the routine
routine = Routine(
course=course,
subject=subject,
teacher=teacher,
start_time=start_time,
end_time=end_time,
period_or_year=period_or_year,
is_active=True
)
routine.save()
return JsonResponse({
"success": True,
"message": "Routine created successfully.",
"routine_id": routine.id
})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
@require_http_methods(["GET"])
def api_get_routine(request, routine_id):
"""API endpoint to get routine details"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can view routine details."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get routine and ensure it belongs to the HOD's course
routine = get_object_or_404(Routine, id=routine_id, course=hod.course)
# Format time fields properly to ensure they can be used in the form's time inputs
start_time = routine.start_time.strftime("%H:%M") if routine.start_time else ""
end_time = routine.end_time.strftime("%H:%M") if routine.end_time else ""
# Prepare response data with detailed information
response_data = {
"success": True,
"routine": {
"id": routine.id,
"subject_id": routine.subject.id,
"subject_name": routine.subject.name,
"teacher_id": routine.teacher.id,
"teacher_name": routine.teacher.name,
"start_time": start_time,
"end_time": end_time,
"period_or_year": routine.period_or_year,
"is_active": routine.is_active
}
}
# Log the response for debugging
print(f"API response for routine {routine_id}: {response_data}")
return JsonResponse(response_data)
except Exception as e:
error_message = f"An error occurred while fetching routine {routine_id}: {str(e)}"
print(error_message)
return JsonResponse({"success": False, "message": error_message})
@login_required
@csrf_exempt
@require_http_methods(["PUT", "POST"])
def api_update_routine(request, routine_id):
"""API endpoint to update a routine"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can update routines."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get routine and ensure it belongs to the HOD's course
routine = get_object_or_404(Routine, id=routine_id, course=hod.course)
# Handle both PUT and POST methods
if request.method == 'PUT':
# For PUT requests, get data from request.POST or parse request.body
import json
try:
if request.body:
data = json.loads(request.body)
else:
data = request.POST.dict()
# Extract values from the data dictionary
subject_id = data.get("subject_id")
teacher_id = data.get("teacher_id")
start_time = data.get("start_time")
end_time = data.get("end_time")
period_or_year = data.get("period_or_year")
print(f"Received data: {data}")
except json.JSONDecodeError as e:
print(f"JSON decode error: {e}")
# Fallback to form data
subject_id = request.POST.get("subject_id")
teacher_id = request.POST.get("teacher_id")
start_time = request.POST.get("start_time")
end_time = request.POST.get("end_time")
period_or_year = request.POST.get("period_or_year")
else:
# For POST requests, use request.POST directly
subject_id = request.POST.get("subject_id")
teacher_id = request.POST.get("teacher_id")
start_time = request.POST.get("start_time")
end_time = request.POST.get("end_time")
period_or_year = request.POST.get("period_or_year")
# Log received data
print(f"Processing routine update: subject_id={subject_id}, teacher_id={teacher_id}, start_time={start_time}, end_time={end_time}")
# Validate required fields
if not subject_id or not teacher_id or not start_time or not end_time:
error_msg = "All fields are required."
print(f"Validation error: {error_msg}")
return JsonResponse({"success": False, "message": error_msg})
# Get subject and teacher objects
try:
subject = Subject.objects.get(id=subject_id, course=hod.course)
teacher = Staff.objects.get(id=teacher_id)
except (Subject.DoesNotExist, Staff.DoesNotExist) as e:
error_msg = "Invalid subject or teacher."
print(f"Database lookup error: {e}")
return JsonResponse({"success": False, "message": error_msg})
# Update routine
routine.subject = subject
routine.teacher = teacher
routine.start_time = start_time
routine.end_time = end_time
if period_or_year:
routine.period_or_year = period_or_year
routine.save()
print(f"Routine {routine_id} updated successfully")
return JsonResponse({"success": True, "message": "Routine updated successfully."})
except Exception as e:
error_msg = f"An error occurred: {str(e)}"
print(f"Error in api_update_routine: {error_msg}")
return JsonResponse({"success": False, "message": error_msg})
@login_required
@csrf_exempt
@require_http_methods(["DELETE", "POST"])
def api_delete_routine(request, routine_id):
"""API endpoint to delete a routine"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can delete routines."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
# Get routine and ensure it belongs to the HOD's course
routine = get_object_or_404(Routine, id=routine_id, course=hod.course)
# Delete the routine
routine.delete()
return JsonResponse({"success": True, "message": "Routine deleted successfully."})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_student_progress(request):
"""API endpoint to get all student progress data with filtering options"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can access progress data."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
# Get batch filter if provided
batch_id = request.GET.get('batch_id')
# Base query for student progress
progress_query = CourseTracking.objects.filter(
student__course=course
).select_related("student")
# Apply batch filter if provided
if batch_id:
try:
batch = Batch.objects.get(id=batch_id)
progress_query = progress_query.filter(student__batches=batch)
except Batch.DoesNotExist:
return JsonResponse({"success": False, "message": "Invalid batch selected."})
# Get all progress records ordered by completion percentage
progress_records = progress_query.order_by("-completion_percentage")
# Calculate metrics
total_students = progress_records.count()
avg_completion = progress_records.aggregate(Avg('completion_percentage'))['completion_percentage__avg'] or 0
avg_completion = round(avg_completion, 1)
students_on_track = progress_records.filter(completion_percentage__gte=75).count()
students_at_risk = progress_records.filter(completion_percentage__lt=40).count()
# Prepare progress data
progress_data = []
for progress in progress_records:
# Get batch names
batch_names = ", ".join([batch.name for batch in progress.student.batches.all()])
# Determine status
if progress.completion_percentage >= 75:
status = "On Track"
status_class = "success"
elif progress.completion_percentage >= 40:
status = "Needs Attention"
status_class = "warning"
else:
status = "At Risk"
status_class = "danger"
# Format student data
student_image = progress.student.image.url if progress.student.image else None
# Add to progress data list
progress_data.append({
"student_id": progress.student.id,
"student_name": progress.student.name,
"student_image": student_image,
"batch_names": batch_names,
"current_period": progress.student.current_period,
"completion_percentage": progress.completion_percentage,
"last_updated": progress.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
"status": status,
"status_class": status_class
})
# Prepare response
response_data = {
"success": True,
"total_students": total_students,
"avg_completion": avg_completion,
"students_on_track": students_on_track,
"students_at_risk": students_at_risk,
"progress_data": progress_data
}
return JsonResponse(response_data)
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def get_progress(request, progress_id):
"""API endpoint to get details of a specific student progress record"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can access progress data."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
# Get the progress record
try:
progress = CourseTracking.objects.get(id=progress_id, student__course=course)
except CourseTracking.DoesNotExist:
return JsonResponse({"success": False, "message": "Progress record not found."})
# Get student data
student = progress.student
# Format batch names
batch_names = [batch.name for batch in student.batches.all()]
# Prepare progress data
progress_data = {
"id": progress.id,
"completion_percentage": progress.completion_percentage,
"progress_status": progress.progress_status,
"notes": progress.notes or "",
"last_updated": progress.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
}
# Prepare student data
student_data = {
"id": student.id,
"name": student.name,
"image": student.image.url if student.image else None,
"current_period": student.current_period,
"batches": batch_names,
}
return JsonResponse({
"success": True,
"progress": progress_data,
"student": student_data
})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})
@login_required
def edit_progress(request, progress_id):
"""API endpoint to update a student progress record"""
try:
# Check if user is a HOD
if not request.user.groups.filter(name="HOD").exists():
return JsonResponse({"success": False, "message": "Only HODs can update progress data."})
# Get the HOD's course
hod = request.user
if not hasattr(hod, "course") or not hod.course:
return JsonResponse({"success": False, "message": "You are not assigned as HOD of any department."})
course = hod.course
# Check if this is a POST request
if request.method != 'POST':
return JsonResponse({"success": False, "message": "Invalid request method."})
# Get the progress record
try:
progress = CourseTracking.objects.get(id=progress_id, student__course=course)
except CourseTracking.DoesNotExist:
return JsonResponse({"success": False, "message": "Progress record not found."})
# Get form data
current_period = request.POST.get('current_period')
completion_percentage = request.POST.get('completion_percentage')
notes = request.POST.get('notes', '')
# Validate form data
if not current_period or not completion_percentage:
return JsonResponse({"success": False, "message": "Current period and completion percentage are required."})
try:
current_period = int(current_period)
completion_percentage = int(completion_percentage)
# Validate current period
if current_period < 1:
return JsonResponse({"success": False, "message": "Current period must be at least 1."})
# Maximum period based on course duration and type
max_period = progress.course.duration
if progress.course.duration_type == "Semester":
max_period *= 2 # Two semesters per year
if current_period > max_period:
return JsonResponse({"success": False, "message": f"Current period cannot exceed {max_period}."})
# Validate completion percentage
if completion_percentage < 0 or completion_percentage > 100:
return JsonResponse({"success": False, "message": "Completion percentage must be between 0 and 100."})
except ValueError:
return JsonResponse({"success": False, "message": "Invalid numeric values provided."})
# Update student's current period
student = progress.student
student.current_period = current_period
student.save(update_fields=['current_period'])
# Update progress record
progress.completion_percentage = completion_percentage
progress.notes = notes
progress.save(update_fields=['completion_percentage', 'notes'])
return JsonResponse({
"success": True,
"message": "Progress updated successfully."
})
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"})