Files
Student-Management-System-I…/app/parentviews.py
T
2025-04-15 04:20:26 +05:45

613 lines
23 KiB
Python

# Standard library imports
import json
import traceback
# Core Django imports
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
# Local app imports
from app.models import (
Batch,
Course,
CourseTracking,
InstituteFeedback,
ParentFeedback,
ParentInstituteFeedback,
Student,
StudentFeedback,
StudentLeave,
Subject,
TeacherParentMeeting,
AttendanceRecord,
Notice,
Routine,
)
@login_required
def parent_dashboard(request):
"""
View for the parent dashboard.
Requires user to be authenticated and belong to the Parent group.
"""
# Check if user is a parent
if not request.user.groups.filter(name="Parent").exists():
messages.error(request, "Only parents can access this dashboard.")
return redirect("login")
try:
parent = request.user
today = timezone.now().date()
# Get all students of the parent with their course trackings
students = (
parent.students.all()
.select_related("course")
.prefetch_related("course_trackings", "attendance_records")
)
# Get all meetings without student filtering
meetings = TeacherParentMeeting.objects.all().order_by(
"-meeting_date", "-meeting_time"
)
# Get attendance summary for all students
attendance_summary = {
"total_students": students.count(),
"students_present_today": 0,
"students_absent_today": 0,
"overall_attendance": 0,
}
total_attendance = 0
total_classes = 0
for student in students:
# Get today's attendance
today_attendance = AttendanceRecord.objects.filter(
student=student, attendance__date=today, student_attend=True
).count()
if today_attendance > 0:
attendance_summary["students_present_today"] += 1
else:
attendance_summary["students_absent_today"] += 1
# Get overall attendance
student_attendance = student.attendance_records.all()
total_attendance += student_attendance.filter(student_attend=True).count()
total_classes += student_attendance.count()
if total_classes > 0:
attendance_summary["overall_attendance"] = (
total_attendance / total_classes
) * 100
# Get course progress for all students
course_progress = []
for student in students:
active_tracking = student.course_trackings.filter(
progress_status="In Progress"
).first()
if active_tracking:
course_progress.append(
{
"student": student.name,
"course": active_tracking.course.name,
"progress": active_tracking.completion_percentage,
"status": active_tracking.progress_status,
}
)
# Get recent leaves for all students
recent_leaves = StudentLeave.objects.filter(student__in=students).order_by(
"-created_at"
)[:5]
# Get recent teacher feedback
teacher_feedback = (
ParentFeedback.objects.filter(parent=parent)
.select_related("teacher", "student")
.order_by("-created_at")[:5]
)
# Get recent institute feedback
institute_feedback = (
ParentInstituteFeedback.objects.filter(parent=parent)
.select_related("student")
.order_by("-created_at")[:5]
)
# Get notices
notices = Notice.objects.all().order_by("-created_at")[:5]
# Get routines for each student
student_data = []
for student in students:
student_info = {
"student": student,
"course_name": student.course.name if student.course else None,
"course_duration_type": student.course.duration_type
if student.course
else None,
"current_period": None,
"current_subjects": [],
"current_period_routines": [],
}
# Get active course tracking
active_tracking = student.course_trackings.filter(
progress_status="In Progress"
).first()
# Get leave counts
student_info["pending_leaves"] = student.leave_requests.filter(
status=0
).count()
student_info["approved_leaves"] = student.leave_requests.filter(
status=1
).count()
student_info["rejected_leaves"] = student.leave_requests.filter(
status=2
).count()
if active_tracking:
student_info["current_period"] = active_tracking.current_period
# Get routines for the current period
current_period_routines = list(
Routine.objects.filter(
course=active_tracking.course,
period_or_year=active_tracking.current_period,
is_active=True,
)
.select_related("subject", "teacher")
.order_by("start_time")
)
# Get current subjects for the student
current_subjects = list(
Subject.objects.filter(
course=active_tracking.course,
period_or_year=active_tracking.current_period,
).order_by("name")
)
print(f"Found {len(current_subjects)} subjects:")
for subject in current_subjects:
print(f"- {subject.name}")
print(f"\nFound {len(current_period_routines)} routines:")
for routine in current_period_routines:
print(
f"- {routine.subject.name} ({routine.start_time} - {routine.end_time})"
)
student_info["current_period_routines"] = current_period_routines
student_info["current_subjects"] = current_subjects
student_data.append(student_info)
context = {
"parent": parent,
"student_data": student_data,
"attendance_summary": attendance_summary,
"course_progress": course_progress,
"recent_leaves": recent_leaves,
"teacher_feedback": teacher_feedback,
"institute_feedback": institute_feedback,
"notices": notices,
"meetings": meetings,
"today": today,
}
return render(request, "parent/dashboard.html", context)
except Exception as e:
messages.error(request, f"Error loading dashboard: {str(e)}")
return redirect("login")
@login_required
def submit_parent_feedback(request):
"""View for parents to submit feedback about teachers"""
# Check if user is a parent
if not request.user.groups.filter(name="Parent").exists():
messages.error(request, "Only parents can submit feedback.")
return redirect("parent_dashboard")
parent = request.user
if request.method == "POST":
teacher_id = request.POST.get("teacher")
student_id = request.POST.get("student")
rating = request.POST.get("rating")
feedback_text = request.POST.get("feedback_text")
try:
teacher = Staff.objects.get(id=teacher_id)
student = Student.objects.get(id=student_id)
# Verify that the student belongs to this parent
if student not in parent.students.all():
messages.error(
request, "You can only submit feedback for your children."
)
return redirect("submit_parent_feedback")
# Create or update feedback
ParentFeedback.objects.update_or_create(
parent=parent,
teacher=teacher,
student=student,
defaults={
"rating": rating,
"feedback_text": feedback_text,
},
)
messages.success(request, "Feedback submitted successfully!")
return redirect("parent_dashboard")
except (Staff.DoesNotExist, Student.DoesNotExist):
messages.error(request, "Invalid teacher or student selected.")
except Exception as e:
messages.error(request, f"An error occurred: {str(e)}")
# Get list of teachers who have taught the parent's students
# Get all students of the parent
students = parent.students.all()
# Get all teachers who have routines for these students' courses
teachers = Staff.objects.filter(
routine__course__in=students.values_list("course_trackings__course", flat=True)
).distinct()
# Get list of students for the dropdown
context = {
"teachers": teachers,
"students": students,
}
return render(request, "parent/feedback.html", context)
@login_required
def submit_parent_institute_feedback(request):
"""View for parents to submit feedback about the institute"""
# Check if user is a parent
if not request.user.groups.filter(name="Parent").exists():
messages.error(request, "Only parents can submit feedback.")
return redirect("parent_dashboard")
parent = request.user
if request.method == "POST":
feedback_type = request.POST.get("feedback_type")
student_id = request.POST.get("student_id")
rating = request.POST.get("rating")
feedback_text = request.POST.get("feedback_text")
is_anonymous = request.POST.get("is_anonymous") == "on"
try:
student = Student.objects.get(id=student_id)
# Verify that the student belongs to this parent
if student not in parent.students.all():
messages.error(
request, "You can only submit feedback for your children."
)
return redirect("parent_dashboard")
# Convert to float first to handle half-stars
rating_value = float(rating)
if rating_value < 0.5 or rating_value > 5:
messages.error(request, "Rating must be between 0.5 and 5")
return redirect("parent_dashboard")
# Check if it's a valid half or full star rating
if rating_value % 0.5 != 0:
messages.error(request, "Rating must be a whole or half number")
return redirect("parent_dashboard")
# Get the institute (assuming one institute for now)
institute = Institute.objects.first()
if not institute:
# Create a default institute if none exists
institute = Institute.objects.create(
name="Default Institute",
phone="123456789",
email="default@example.com",
)
# Check if feedback already exists for this type
existing_feedback = ParentInstituteFeedback.objects.filter(
parent=parent,
institute=institute,
feedback_type=feedback_type,
).first()
if existing_feedback:
# Update existing feedback
existing_feedback.rating = rating_value
existing_feedback.feedback_text = feedback_text
existing_feedback.is_anonymous = is_anonymous
existing_feedback.save()
messages.success(request, "Institute feedback updated successfully")
else:
# Create new feedback
ParentInstituteFeedback.objects.create(
parent=parent,
institute=institute,
feedback_type=feedback_type,
rating=rating_value,
feedback_text=feedback_text,
is_anonymous=is_anonymous,
is_public=True, # Default to public feedback
)
messages.success(request, "Institute feedback submitted successfully")
except ValueError:
messages.error(request, "Invalid rating value")
except Exception as e:
messages.error(request, f"Error submitting institute feedback: {str(e)}")
return redirect("parent_dashboard")
@login_required
def parent_meetings(request):
"""View for redirecting to the parent dashboard meetings section"""
# Check if user is a parent
if not request.user.groups.filter(name="Parent").exists():
messages.error(request, "Only parents can access meetings.")
return redirect("parent_dashboard")
# Redirect to parent dashboard with meetings section highlighted
return redirect("parent_dashboard")
@login_required
def add_meeting(request):
"""Add a new parent-teacher meeting"""
if request.method == "POST":
try:
data = json.loads(request.body)
meeting = TeacherParentMeeting.objects.create(
meeting_date=data["meeting_date"],
meeting_time=data["meeting_time"],
duration=data["duration"],
is_online=data.get("is_online", False),
meeting_link=data.get("meeting_link", ""),
agenda=data.get("agenda", ""),
status="scheduled",
)
return JsonResponse({"success": True, "meeting_id": meeting.id})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
return JsonResponse({"success": False, "error": "Invalid request method"})
@login_required
def complete_meeting(request, meeting_id):
"""Mark a meeting as completed"""
if request.method == "POST":
try:
meeting = TeacherParentMeeting.objects.get(id=meeting_id)
meeting.status = "completed"
meeting.save()
return JsonResponse({"success": True})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
return JsonResponse({"success": False, "error": "Invalid request method"})
@login_required
def cancel_meeting(request, meeting_id):
"""Cancel a meeting"""
if request.method == "POST":
try:
data = json.loads(request.body)
meeting = TeacherParentMeeting.objects.get(id=meeting_id)
meeting.status = "cancelled"
meeting.cancellation_reason = data.get("reason", "")
meeting.save()
return JsonResponse({"success": True})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
return JsonResponse({"success": False, "error": "Invalid request method"})
@login_required
def reschedule_meeting(request, meeting_id):
"""Reschedule a meeting"""
if request.method == "POST":
try:
data = json.loads(request.body)
meeting = TeacherParentMeeting.objects.get(id=meeting_id)
meeting.meeting_date = data["meeting_date"]
meeting.meeting_time = data["meeting_time"]
meeting.status = "rescheduled"
meeting.save()
return JsonResponse({"success": True})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
return JsonResponse({"success": False, "error": "Invalid request method"})
@login_required
def get_meeting_notes(request, meeting_id):
"""Get meeting notes"""
try:
meeting = TeacherParentMeeting.objects.get(id=meeting_id)
return JsonResponse({"success": True, "notes": meeting.notes})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
@login_required
def get_meeting_agenda(request, meeting_id):
"""Get meeting agenda"""
try:
meeting = TeacherParentMeeting.objects.get(id=meeting_id)
return JsonResponse({"success": True, "agenda": meeting.agenda})
except Exception as e:
return JsonResponse({"success": False, "error": str(e)})
@login_required
def get_student_details(request, student_id):
"""API view to get student details for the modal"""
# Check if user belongs to Parent group
if not request.user.groups.filter(name="Parent").exists():
return JsonResponse({"error": "Unauthorized access"}, status=403)
try:
# Get the student
student = get_object_or_404(Student, id=student_id)
# Temporarily bypass strict parent-student relationship check
# This allows any parent to view any student for demonstration purposes
# In production, you would want to re-enable the stricter check
# Get attendance data
try:
attended_classes = student.attendance_records.filter(
student_attend=True
).count()
# Use period duration as the total, not the actual record count
total_period_days = 180 # Standard days in a semester/period
# Calculate rate based on semester days
attendance_rate = round((attended_classes / total_period_days * 100), 1)
# Also include the raw attendance numbers for display
attendance_data = {
"rate": attendance_rate,
"present": attended_classes,
"total": total_period_days,
"formatted": f"{attendance_rate}% ({attended_classes}/{total_period_days})",
}
except Exception as e:
print(f"Error getting attendance data: {str(e)}")
attendance_data = {
"rate": 0,
"present": 0,
"total": 180,
"formatted": "0% (0/180)",
}
# Get subject data
subjects_data = []
if student.course:
# Check if get_current_subjects method exists
if hasattr(student, "get_current_subjects") and callable(
getattr(student, "get_current_subjects")
):
subjects = student.get_current_subjects()
else:
# Fallback to getting subjects from course
subjects = Subject.objects.filter(
course=student.course, period_or_year=student.current_period
)
for subject in subjects:
# Check if get_subject_progress method exists
if hasattr(student, "get_subject_progress") and callable(
getattr(student, "get_subject_progress")
):
progress = student.get_subject_progress(subject.id)
else:
# Default progress value
progress = 0
teacher_name = "Not assigned"
if hasattr(subject, "teacher") and subject.teacher:
teacher_name = subject.teacher.name
subject_data = {
"name": subject.name,
"progress": progress,
"teacher_name": teacher_name,
}
subjects_data.append(subject_data)
# Get course timeline information
current_period_course = None
period_end_date = None
course_end_date = None
if student.course:
# Current period course name with proper period display
period_display = f"{'Semester' if student.course.duration_type == 'Semester' else 'Year'} {student.current_period}"
current_period_course = f"{student.course.name} - {period_display}"
# Use current academic year for dates instead of calculations
import datetime
current_year = timezone.now().year
current_month = timezone.now().month
# Set fixed dates based on current academic year and course type
if student.course.duration_type == "Semester":
if 1 <= current_month <= 7: # Jan-July: current period ends in May
period_end_date = f"May 31, {current_year}"
course_end_date = f"Dec 31, {current_year + student.course.duration - (student.current_period + 1) // 2}"
else: # Aug-Dec: current period ends in December
period_end_date = f"Dec 31, {current_year}"
course_end_date = f"May 31, {current_year + 1 + student.course.duration - (student.current_period + 1) // 2}"
else: # Year based
period_end_date = f"Dec 31, {current_year}"
course_end_date = f"Dec 31, {current_year + student.course.duration - student.current_period}"
# Provide fallback values if data extraction fails
if not current_period_course and student.course:
period_display = f"{'Semester' if student.course.duration_type == 'Semester' else 'Year'} {student.current_period}"
current_period_course = f"{student.course.name} - {period_display}"
if not period_end_date:
period_end_date = "Not available"
if not course_end_date:
course_end_date = "Not available"
# Prepare student data
student_data = {
"id": student.id,
"name": student.name,
"status": student.status,
"email": student.email,
"phone": student.phone,
"address": student.permanent_address
or student.temporary_address
or "Not provided",
"image_url": student.image.url
if student.image and hasattr(student.image, "url")
else None,
"course_name": student.course.name if student.course else None,
"course_duration_type": student.course.duration_type
if student.course
else None,
"current_period": student.current_period,
"attendance_rate": attendance_data,
"subjects": subjects_data,
"current_period_course": current_period_course,
"expected_end_date": period_end_date,
"course_end_date": course_end_date,
}
return JsonResponse(student_data)
except Student.DoesNotExist:
return JsonResponse({"error": "Student not found"}, status=404)
except Exception as e:
print(f"Error in get_student_details: {str(e)}")
print(traceback.format_exc())
return JsonResponse({"error": str(e)}, status=500)