661 lines
22 KiB
Python
661 lines
22 KiB
Python
from app.models import FCMDevice
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.http import JsonResponse, HttpResponse
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib import messages
|
|
from django.db.models import Count, Q, Avg
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from .models import (
|
|
Student,
|
|
Course,
|
|
CourseTracking,
|
|
Student_Leave,
|
|
Staff_leave,
|
|
Attendance,
|
|
AttendanceRecord,
|
|
StudentFeedback,
|
|
Routine,
|
|
Staff,
|
|
Notice,
|
|
Institute,
|
|
InstituteFeedback,
|
|
Subject,
|
|
Batch,
|
|
)
|
|
from app.admin import custom_admin_site
|
|
from django.views.decorators.http import require_http_methods, require_GET
|
|
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, store_otp, verify_otp, send_otp_sms
|
|
from django.conf import settings
|
|
from dotenv import load_dotenv
|
|
|
|
# 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,
|
|
)
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Create your views here.
|
|
|
|
|
|
# FireBase
|
|
def saveFCMToken(request, token):
|
|
try:
|
|
FCMDevice(token=token).save()
|
|
except:
|
|
pass
|
|
return JsonResponse({})
|
|
|
|
|
|
def showFirebaseJS(request):
|
|
data = f"""
|
|
importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-app-compat.js');
|
|
importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-messaging-compat.js');
|
|
|
|
firebase.initializeApp({{
|
|
apiKey: "{os.getenv('FIREBASE_API_KEY')}",
|
|
authDomain: "{os.getenv('FIREBASE_AUTH_DOMAIN')}",
|
|
projectId: "{os.getenv('FIREBASE_PROJECT_ID')}",
|
|
storageBucket: "{os.getenv('FIREBASE_STORAGE_BUCKET')}",
|
|
messagingSenderId: "{os.getenv('FIREBASE_MESSAGING_SENDER_ID')}",
|
|
appId: "{os.getenv('FIREBASE_APP_ID')}",
|
|
measurementId: "{os.getenv('FIREBASE_MEASUREMENT_ID')}"
|
|
}});
|
|
|
|
const messaging = firebase.messaging();
|
|
|
|
messaging.onBackgroundMessage((payload) => {{
|
|
const notificationTitle = payload.notification.title;
|
|
const notificationOptions = {{
|
|
body: payload.notification.body,
|
|
icon: payload.notification.icon
|
|
}};
|
|
|
|
return self.registration.showNotification(notificationTitle, notificationOptions);
|
|
}});
|
|
"""
|
|
return HttpResponse(data, content_type="text/javascript")
|
|
|
|
|
|
@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:
|
|
return redirect("studentDashboard")
|
|
elif "Teacher" in user_groups:
|
|
return redirect("teacherDashboard")
|
|
|
|
# Get current date and date ranges
|
|
today = timezone.now().date()
|
|
last_week = timezone.now() - timedelta(days=7)
|
|
last_month = timezone.now() - timedelta(days=30)
|
|
|
|
# Student Statistics
|
|
total_students = Student.objects.count()
|
|
active_students = Student.objects.filter(status="Active").count()
|
|
completed_students = Student.objects.filter(status="Completed").count()
|
|
on_leave_students = Student.objects.filter(status="Leave").count()
|
|
|
|
# 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 for {leave.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 for {leave.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)
|
|
|
|
|
|
@login_required
|
|
def update_profile(request):
|
|
if request.method == "POST":
|
|
user = request.user
|
|
name = request.POST.get("name")
|
|
email = request.POST.get("email")
|
|
phone = request.POST.get("phone")
|
|
address = request.POST.get("address")
|
|
gender = request.POST.get("gender")
|
|
dob = request.POST.get("dob")
|
|
|
|
# Validate data
|
|
if not all([name, email, phone]):
|
|
messages.error(request, "Name, email and phone are required fields.")
|
|
return redirect("studentDashboard")
|
|
|
|
# Update profile based on user type
|
|
try:
|
|
if hasattr(user, "student"):
|
|
student = user
|
|
student.name = name
|
|
student.email = email
|
|
student.phone = phone
|
|
student.address = address
|
|
student.gender = gender
|
|
if dob:
|
|
student.dob = dob
|
|
|
|
# Handle profile image
|
|
if "profile_image" in request.FILES:
|
|
image = request.FILES["profile_image"]
|
|
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 updated successfully.")
|
|
|
|
elif hasattr(user, "staff"):
|
|
staff = user
|
|
staff.name = name
|
|
staff.email = email
|
|
staff.phone = phone
|
|
staff.address = address
|
|
staff.gender = gender
|
|
if dob:
|
|
staff.dob = dob
|
|
|
|
# Handle profile image
|
|
if "profile_image" in request.FILES:
|
|
image = request.FILES["profile_image"]
|
|
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 updated successfully.")
|
|
|
|
else:
|
|
messages.error(request, "User profile not found.")
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Error updating profile: {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
|
|
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")
|
|
|
|
# Validate input
|
|
if not all([current_password, new_password, confirm_password]):
|
|
messages.error(request, "All password fields are required.")
|
|
return redirect("studentDashboard")
|
|
|
|
if new_password != confirm_password:
|
|
messages.error(request, "New passwords do not match.")
|
|
return redirect("studentDashboard")
|
|
|
|
# Check current password
|
|
if not user.check_password(current_password):
|
|
messages.error(request, "Current password is incorrect.")
|
|
return redirect("studentDashboard")
|
|
|
|
# Set new password
|
|
try:
|
|
user.set_password(new_password)
|
|
user.save()
|
|
messages.success(
|
|
request, "Password changed successfully. Please login again."
|
|
)
|
|
return redirect("login")
|
|
except Exception as 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"})
|
|
|
|
|
|
|