Files
2025-04-03 16:30:28 +05:45

866 lines
31 KiB
Python

from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse, HttpResponse, FileResponse
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 django.conf import settings
from .models import (
Student,
Course,
CourseTracking,
Student_Leave,
Staff_leave,
Attendance,
AttendanceRecord,
StudentFeedback,
Routine,
Staff,
Notice,
Institute,
InstituteFeedback,
Subject,
Batch,
)
from app.admin import custom_admin_site
from django.views.decorators.http import require_http_methods, require_GET, require_POST
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
import json
import os
from django.db.models.base import transaction
from .utils import generate_otp, generate_secret_key, store_secret_key, verify_otp, send_otp_sms
from django.conf import settings
from dotenv import load_dotenv
from .firebase import save_fcm_token, get_firebase_js, FCMDevice
from django.template.loader import render_to_string
from django.views.static import serve
# Import views from studentviews.py
from .studentviews import (
studentDashboard,
request_leave,
submit_feedback,
submit_institute_feedback,
get_subject_files,
)
# Import views from staffviews.py
from .staffviews import (
teacherDashboard,
request_staff_leave,
submit_staff_institute_feedback,
save_attendance,
get_subject_schedule,
teacher_update_profile_picture,
teacher_change_password,
)
from django.contrib.auth.hashers import check_password
# Load environment variables
load_dotenv()
# Create your views here.
@login_required
def dashboard(request):
"""Main dashboard view that redirects to appropriate dashboard based on user role"""
user = request.user
user_groups = user.groups.values_list("name", flat=True)
# Redirect based on user role
if "Student" in user_groups:
print("Redirecting to student dashboard")
return redirect("studentDashboard")
elif "Teacher" in user_groups:
print("Redirecting to teacher dashboard")
return redirect("teacherDashboard")
elif user.is_superuser or user.is_staff:
# Admin users get the admin dashboard
print("Showing admin dashboard")
# Get current date and date ranges
today = timezone.now().date()
last_week = timezone.now() - timedelta(days=7)
last_month = timezone.now() - timedelta(days=30)
# Debug prints for user authentication
print(f"Is authenticated: {request.user.is_authenticated}")
print(f"Is staff: {request.user.is_staff}")
print(f"Is superuser: {request.user.is_superuser}")
print("-------------------------\n")
# Student Statistics - with debug prints
total_students = Student.objects.count()
print("\nDEBUG Student Statistics:")
print("-------------------------")
print(f"Total students (from count): {total_students}")
# Get all students and print their statuses
all_students = Student.objects.all()
print("\nAll students and their statuses:")
for student in all_students:
print(f"- {student.name}: {student.status}")
active_students = Student.objects.filter(status="Active").count()
print(f"\nActive students: {active_students}")
completed_students = Student.objects.filter(status="Completed").count()
print(f"Completed students: {completed_students}")
on_leave_students = Student.objects.filter(status="Leave").count()
print(f"Students on leave: {on_leave_students}")
print(f"\nSum of status counts: {active_students + completed_students + on_leave_students}")
print("-------------------------\n")
# Staff Statistics
total_staff = Staff.objects.count()
active_staff = Staff.objects.filter(is_active=True).count()
staff_on_leave = (
Staff_leave.objects.filter(status=1).values("staff").distinct().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]
# Pending Leave Requests
pending_student_leaves = Student_Leave.objects.filter(status=0)
pending_staff_leaves = Staff_leave.objects.filter(status=0).order_by("-created_at")
# Attendance Statistics
today_classes = Attendance.objects.filter(date=today)
total_classes = today_classes.count()
total_students_today = AttendanceRecord.objects.filter(attendance__in=today_classes).count()
students_present = AttendanceRecord.objects.filter(
attendance__in=today_classes, student_attend=True
).count()
# Recent Feedback
recent_feedback = StudentFeedback.objects.order_by("-created_at")[:5]
# Recent Institute Feedback
recent_institute_feedback = InstituteFeedback.objects.filter(
is_public=True
).order_by("-created_at")[:10]
# Get notices
notices = Notice.objects.all().order_by("-created_at")
# Get recent activities
recent_activities = []
# Add recent leaves
for leave in recent_student_leaves:
recent_activities.append(
{
"title": f"Student Leave Request",
"description": f"{leave.student.name} requested leave from {leave.start_date} to {leave.end_date}",
"timestamp": leave.created_at,
}
)
for leave in recent_staff_leaves:
recent_activities.append(
{
"title": f"Staff Leave Request",
"description": f"{leave.staff.name} requested leave from {leave.start_date} to {leave.end_date}",
"timestamp": leave.created_at,
}
)
# Add recent feedback
for feedback in recent_feedback:
recent_activities.append(
{
"title": f"New Feedback",
"description": f"Feedback received for {feedback.teacher.name if feedback.teacher else 'Institute'}",
"timestamp": feedback.created_at,
}
)
# Add recent institute feedback
for feedback in recent_institute_feedback:
recent_activities.append(
{
"title": f"Institute Feedback",
"description": f"New feedback from {feedback.display_name}",
"timestamp": feedback.created_at,
}
)
# Add recent notices
for notice in notices:
recent_activities.append(
{
"title": f"New Notice",
"description": notice.title,
"timestamp": notice.created_at,
}
)
# Sort all activities by timestamp
recent_activities.sort(key=lambda x: x["timestamp"], reverse=True)
# Take only the most recent 10 activities
recent_activities = recent_activities[:10]
# Teacher Ratings
teacher_ratings = []
teachers = Staff.objects.filter(
Q(designation__icontains="Teacher")
| Q(designation__icontains="Staff")
| Q(designation__icontains="Principal")
| Q(designation__isnull=True)
| Q(designation="")
)
for teacher in teachers:
feedback = StudentFeedback.objects.filter(teacher=teacher)
avg_rating = feedback.aggregate(Avg("rating"))["rating__avg"] or 0
total_reviews = feedback.count()
# Get total students taught by this teacher
total_students = (
AttendanceRecord.objects.filter(attendance__routine__teacher=teacher)
.values("student")
.distinct()
.count()
)
teacher_ratings.append(
{
"name": teacher.name,
"designation": teacher.designation or "Teacher",
"image": teacher.image,
"avg_rating": avg_rating,
"total_reviews": total_reviews,
"total_students": total_students,
}
)
# Sort teachers by average rating (highest first)
teacher_ratings.sort(key=lambda x: x["avg_rating"], reverse=True)
# 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(),
"new_staff": Staff.objects.filter(joining_date__gte=last_month).count(),
"attendance_rate": round(
(
AttendanceRecord.objects.filter(
attendance__date__gte=last_month.date(), student_attend=True
).count()
/ AttendanceRecord.objects.filter(
attendance__date__gte=last_month.date()
).count()
* 100
)
if AttendanceRecord.objects.filter(
attendance__date__gte=last_month.date()
).exists()
else 0
),
"leave_requests": Student_Leave.objects.filter(
created_at__gte=last_month
).count()
+ Staff_leave.objects.filter(created_at__gte=last_month).count(),
}
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,
# Staff Statistics
"total_staff": total_staff,
"active_staff": active_staff,
"staff_on_leave": staff_on_leave,
# 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,
"pending_student_leaves": pending_student_leaves,
"pending_staff_leaves": pending_staff_leaves,
# Attendance Statistics
"total_classes_today": total_classes,
"total_students_today": total_students_today,
"students_present": students_present,
"attendance_rate": round(
(students_present / total_students_today * 100) if total_students_today > 0 else 0
),
# Feedback
"recent_feedback": recent_feedback,
"recent_institute_feedback": recent_institute_feedback,
"teacher_ratings": teacher_ratings,
# Course Progress
"course_progress": course_progress,
# Monthly Statistics
"monthly_stats": monthly_stats,
"notices": notices, # Add notices to context
"recent_activities": recent_activities, # Add recent activities to context
}
return render(request, "dashboard.html", context)
else:
# If user doesn't belong to any specific group, try to determine user type
print("User doesn't belong to any specific group")
if hasattr(user, 'student'):
print("User has student attribute, redirecting to student dashboard")
return redirect("studentDashboard")
elif hasattr(user, 'staff'):
print("User has staff attribute, redirecting to teacher dashboard")
return redirect("teacherDashboard")
else:
print("Unknown user type, showing default dashboard")
return render(request, "dashboard.html", {"unknown_user_type": True})
@login_required
def update_profile(request):
if request.method == "POST":
user = request.user
user_groups = user.groups.values_list("name", flat=True)
# Handle profile image update
if "profile_image" in request.FILES:
try:
image = request.FILES["profile_image"]
if "Student" in user_groups:
student = Student.objects.get(id=user.id)
if student.image:
# Delete old image if updating
if os.path.isfile(student.image.path):
os.remove(student.image.path)
# Save new image
student.image = image
student.save()
messages.success(request, "Profile picture updated successfully.")
elif "Teacher" in user_groups:
staff = Staff.objects.get(id=user.id)
if staff.image:
# Delete old image if updating
if os.path.isfile(staff.image.path):
os.remove(staff.image.path)
# Save new image
staff.image = image
staff.save()
messages.success(request, "Profile picture updated successfully.")
else:
messages.error(request, "User profile not found. Please make sure you are logged in as a student or staff member.")
except (Student.DoesNotExist, Staff.DoesNotExist) as e:
messages.error(request, "User profile not found in the database.")
except Exception as e:
messages.error(request, f"Error updating profile picture: {str(e)}")
else:
messages.error(request, "No profile picture uploaded.")
# Redirect based on user type
if "Student" in user_groups:
return redirect("studentDashboard")
elif "Teacher" in user_groups:
return redirect("teacherDashboard")
else:
return redirect("dashboard")
return redirect("dashboard")
@login_required
def change_password(request):
if request.method == "POST":
user = request.user
current_password = request.POST.get("current_password")
new_password = request.POST.get("new_password")
confirm_password = request.POST.get("confirm_password")
# Check if this is an AJAX request
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
# Validate input
if not all([current_password, new_password, confirm_password]):
if is_ajax:
return JsonResponse({"success": False, "error": "All password fields are required."})
messages.error(request, "All password fields are required.")
return redirect("studentDashboard")
if new_password != confirm_password:
if is_ajax:
return JsonResponse({"success": False, "error": "New passwords do not match."})
messages.error(request, "New passwords do not match.")
return redirect("studentDashboard")
# Check current password
if not user.check_password(current_password):
if is_ajax:
return JsonResponse({"success": False, "error": "Current password is incorrect."})
messages.error(request, "Current password is incorrect.")
return redirect("studentDashboard")
# Set new password
try:
user.set_password(new_password)
user.save()
if is_ajax:
return JsonResponse({"success": True})
messages.success(
request, "Password changed successfully. Please login again."
)
return redirect("login")
except Exception as e:
if is_ajax:
return JsonResponse({"success": False, "error": f"Error changing password: {str(e)}"})
messages.error(request, f"Error changing password: {str(e)}")
# Redirect based on user type
if hasattr(user, "student"):
return redirect("studentDashboard")
elif hasattr(user, "staff"):
return redirect("teacherDashboard")
else:
return redirect("dashboard")
return redirect("dashboard")
@login_required
@require_GET
def get_subjects(request):
"""Get subjects for a course and semester"""
course_id = request.GET.get("course_id")
semester = request.GET.get("semester", 1)
if not course_id:
return JsonResponse({"success": False, "message": "Course ID is required"})
try:
subjects = Subject.objects.filter(
course_id=course_id, semester_or_year=semester
).values("id", "name")
return JsonResponse({"success": True, "subjects": list(subjects)})
except Exception as e:
return JsonResponse({"success": False, "message": str(e)})
@login_required
@require_GET
def get_teachers(request):
"""Get teachers for the institute"""
try:
teachers = Staff.objects.filter(
Q(designation__icontains="Teacher")
| Q(designation__icontains="Staff")
| Q(designation__icontains="Principal")
| Q(designation__isnull=True)
| Q(designation="")
).values("id", "name")
return JsonResponse({"success": True, "teachers": list(teachers)})
except Exception as e:
return JsonResponse({"success": False, "message": str(e)})
@login_required
@require_GET
def get_course_duration(request):
"""Get course duration details"""
course_id = request.GET.get("course_id")
if not course_id:
return JsonResponse({"success": False, "message": "Course ID is required"})
try:
course = Course.objects.get(id=course_id)
# Calculate number of semesters or years
total_periods = course.duration
if course.duration_type == "Semester":
total_periods = course.duration * 2 # 2 semesters per year
# Create a list of periods
periods = []
for i in range(1, total_periods + 1):
if course.duration_type == "Semester":
periods.append({"value": i, "label": f"Semester {i}"})
else:
periods.append({"value": i, "label": f"Year {i}"})
return JsonResponse(
{
"success": True,
"course": {
"id": course.id,
"name": course.name,
"duration": course.duration,
"duration_type": course.duration_type,
"total_periods": total_periods,
"periods": periods,
},
}
)
except Course.DoesNotExist:
return JsonResponse({"success": False, "message": "Course not found"})
except Exception as e:
return JsonResponse({"success": False, "message": str(e)})
@login_required
def approve_student_leave(request, leave_id):
"""View to approve a student leave request"""
if request.method == "POST":
leave = get_object_or_404(Student_Leave, id=leave_id)
leave.status = 1 # Approved
leave.save()
messages.success(
request, f"Leave request for {leave.student.name} has been approved."
)
return redirect("dashboard")
@login_required
def reject_student_leave(request, leave_id):
"""View to reject a student leave request"""
if request.method == "POST":
leave = get_object_or_404(Student_Leave, id=leave_id)
leave.status = 2 # Rejected
leave.save()
messages.warning(
request, f"Leave request for {leave.student.name} has been rejected."
)
return redirect("dashboard")
@login_required
def approve_staff_leave(request, leave_id):
"""View to approve a staff leave request"""
if request.method == "POST":
leave = get_object_or_404(Staff_leave, id=leave_id)
leave.status = 1 # Approved
leave.save()
messages.success(
request, f"Leave request for {leave.staff.name} has been approved."
)
return redirect("dashboard")
@login_required
def reject_staff_leave(request, leave_id):
"""View to reject a staff leave request"""
if request.method == "POST":
leave = get_object_or_404(Staff_leave, id=leave_id)
leave.status = 2 # Rejected
leave.save()
messages.warning(
request, f"Leave request for {leave.staff.name} has been rejected."
)
return redirect("dashboard")
@login_required
def add_notice(request):
if request.method == "POST":
title = request.POST.get("title")
message = request.POST.get("message")
image = request.FILES.get("image")
if title and message:
notice = Notice.objects.create(title=title, message=message, image=image)
messages.success(request, "Notice added successfully.")
else:
messages.error(request, "Title and message are required.")
return redirect("dashboard")
@login_required
def delete_notice(request, notice_id):
if request.method == "POST":
try:
notice = Notice.objects.get(id=notice_id)
notice.delete()
return JsonResponse({"success": True})
except Notice.DoesNotExist:
return JsonResponse({"success": False, "error": "Notice not found"})
return JsonResponse({"success": False, "error": "Invalid request method"})
@login_required
def student_dashboard(request):
student = request.user.student
today = timezone.now().date()
# Get attendance summary
attendance_records = AttendanceRecord.objects.filter(student=student)
total_classes = attendance_records.count()
attended_classes = attendance_records.filter(student_attend=True).count()
attendance_percentage = (attended_classes / total_classes * 100) if total_classes > 0 else 0
# Calculate current streak
current_streak = 0
longest_streak = 0
temp_streak = 1
for record in attendance_records.order_by('-attendance__date'):
if record.student_attend:
current_streak += 1
temp_streak += 1
longest_streak = max(longest_streak, temp_streak)
else:
break
attendance_summary = {
'total_classes': total_classes,
'attendance_percentage': attendance_percentage,
'current_streak': current_streak,
'longest_streak': longest_streak
}
# Get course tracking summary
course_trackings = CourseTracking.objects.filter(student=student)
course_summary = {
'total_courses': course_trackings.count(),
'completed_courses': course_trackings.filter(progress_status='Completed').count(),
'ongoing_courses': course_trackings.filter(progress_status='In Progress').count(),
'completion_percentage': (
sum(tracking.completion_percentage for tracking in course_trackings) / course_trackings.count()
if course_trackings.exists() else 0
)
}
# Get recent leaves
recent_leaves = Student_Leave.objects.filter(student=student).order_by('-created_at')[:5]
# Get recent feedback
recent_feedback = StudentFeedback.objects.filter(student=student).order_by('-created_at')[:5]
# Get notices
notices = Notice.objects.all().order_by('-created_at')[:5]
# Get attendance records
attendance_records = AttendanceRecord.objects.filter(student=student).order_by('-attendance__date')[:5]
# Get leave counts
pending_leaves = Student_Leave.objects.filter(student=student, status=0).count()
approved_leaves = Student_Leave.objects.filter(student=student, status=1).count()
rejected_leaves = Student_Leave.objects.filter(student=student, status=2).count()
# Get course details from course tracking
course_details = []
for tracking in course_trackings:
course_info = {
'name': tracking.course.name,
'start_date': tracking.start_date,
'duration': tracking.course.duration,
'duration_type': tracking.course.duration_type,
'current_semester': tracking.current_semester,
'progress_status': tracking.progress_status,
'completion_percentage': tracking.completion_percentage,
'semester_start_date': tracking.semester_start_date,
'semester_end_date': tracking.semester_end_date,
'expected_end_date': tracking.expected_end_date,
'actual_end_date': tracking.actual_end_date,
'remaining_days': tracking.remaining_days,
'total_remaining_days': tracking.total_remaining_days
}
course_details.append(course_info)
context = {
'student': student,
'attendance_summary': attendance_summary,
'course_summary': course_summary,
'recent_leaves': recent_leaves,
'recent_feedback': recent_feedback,
'notices': notices,
'attendance_records': attendance_records,
'pending_leaves': pending_leaves,
'approved_leaves': approved_leaves,
'rejected_leaves': rejected_leaves,
'course_details': course_details,
}
return render(request, 'student_dashboard.html', context)
@login_required
def teacher_dashboard(request):
teacher = request.user.teacher
today = timezone.now().date()
# Get today's routines
today_routines = Routine.objects.filter(
teacher=teacher,
day_of_week=today.weekday()
).order_by('start_time')
# Get attendance summary
attendance_records = Attendance.objects.filter(routine__teacher=teacher)
total_classes = attendance_records.count()
total_students = AttendanceRecord.objects.filter(attendance__routine__teacher=teacher).count()
present_students = AttendanceRecord.objects.filter(
attendance__routine__teacher=teacher,
student_attend=True
).count()
attendance_summary = {
'total_classes': total_classes,
'total_students': total_students,
'present_students': present_students,
'attendance_percentage': (present_students / total_students * 100) if total_students > 0 else 0
}
# Get recent leaves
recent_leaves = Student_Leave.objects.filter(
Q(status=0) | Q(status=1)
).order_by('-created_at')[:5]
# Get recent feedback
recent_feedback = StudentFeedback.objects.filter(teacher=teacher).order_by('-created_at')[:5]
# Get notices
notices = Notice.objects.all().order_by('-created_at')[:5]
# Get attendance records
attendance_records = Attendance.objects.filter(routine__teacher=teacher).order_by('-date')[:5]
# Get leave counts
pending_leaves = Student_Leave.objects.filter(status=0).count()
approved_leaves = Student_Leave.objects.filter(status=1).count()
rejected_leaves = Student_Leave.objects.filter(status=2).count()
context = {
'teacher': teacher,
'today_routines': today_routines,
'attendance_summary': attendance_summary,
'recent_leaves': recent_leaves,
'recent_feedback': recent_feedback,
'notices': notices,
'attendance_records': attendance_records,
'pending_leaves': pending_leaves,
'approved_leaves': approved_leaves,
'rejected_leaves': rejected_leaves,
}
return render(request, 'teacher_dashboard.html', context)
@login_required
@require_POST
def save_fcm_token(request):
try:
data = json.loads(request.body)
token = data.get('token')
if not token:
return JsonResponse({'error': 'Token is required'}, status=400)
user = request.user
user_groups = user.groups.values_list("name", flat=True)
# Save token to the appropriate user model
if "Student" in user_groups:
user.fcm_token = token
user.save()
elif "Teacher" in user_groups:
user.fcm_token = token
user.save()
# Update or create FCMDevice
device, created = FCMDevice.objects.update_or_create(
token=token,
defaults={'is_active': True}
)
return JsonResponse({
'status': 'success',
'device_id': device.id,
'created': created
})
except json.JSONDecodeError:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
def serve_firebase_sw(request):
"""Serve Firebase service worker with correct MIME type"""
# Get the path to the service worker in the static directory
sw_path = os.path.join(settings.STATICFILES_DIRS[0], 'firebase-messaging-sw.js')
# If the file doesn't exist in static directory, try staticfiles
if not os.path.exists(sw_path):
sw_path = os.path.join(settings.STATIC_ROOT, 'firebase-messaging-sw.js')
response = serve(request, 'firebase-messaging-sw.js', os.path.dirname(sw_path))
response['Content-Type'] = 'application/javascript'
return response
@login_required
def check_weak_password(request):
"""Check if the current user's password is '123'"""
user = request.user
is_weak = check_password('123', user.password)
return JsonResponse({'is_weak': is_weak})