Skip to content

Commit 8392a2b

Browse files
committed
Add GradeStatusAPI to retrieve course grade status and submission details
1 parent 52d734e commit 8392a2b

File tree

2 files changed

+164
-21
lines changed

2 files changed

+164
-21
lines changed

FusionIIIT/applications/examination/api/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@
3434
url(r'unique-course-reg-years/', views.UniqueRegistrationYearsView.as_view(), name="unique-course-reg-years"),
3535
url(r'unique-stu-grades-years/', views.UniqueStudentGradeYearsView.as_view(), name="unique-stu-grades-years"),
3636
url(r'^student/result_semesters/$', views.StudentSemesterListView.as_view(), name='get_student_semesters'),
37+
url(r'^grade_status/', views.GradeStatusAPI.as_view(), name='grade_status'),
3738
]

FusionIIIT/applications/examination/api/views.py

Lines changed: 163 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from decimal import Decimal, ROUND_HALF_UP
77
from applications.academic_procedures.models import(course_registration, course_replacement)
88
from applications.programme_curriculum.models import Course as Courses , Batch, CourseInstructor
9-
from applications.examination.models import(hidden_grades , ResultAnnouncement)
9+
from applications.examination.models import(hidden_grades , ResultAnnouncement, authentication)
1010
from applications.academic_information.models import(Student)
1111
from applications.online_cms.models import(Student_grades)
1212
from rest_framework import status
@@ -1163,25 +1163,19 @@ def post(self, request):
11631163
)
11641164

11651165

1166-
# Setup header rows: S. No, Roll No, and Name in columns A, B, and C.
1166+
# Setup header rows: S. No and Roll No in columns A and B.
11671167
ws["A1"] = "S. No"
11681168
ws["B1"] = "Roll No"
1169-
ws["C1"] = "Name"
11701169
for col in ("A", "B"):
11711170
cell = ws[col + "1"]
11721171
cell.alignment = Alignment(horizontal="center", vertical="center")
11731172
cell.font = Font(bold=True)
11741173
cell.fill = header_fill
1175-
cell = ws["C1"]
1176-
cell.alignment = Alignment(horizontal="center", vertical="center")
1177-
cell.font = Font(bold=True)
1178-
cell.fill = header_fill
11791174
ws.column_dimensions[get_column_letter(1)].width = 12
11801175
ws.column_dimensions[get_column_letter(2)].width = 18
1181-
ws.column_dimensions[get_column_letter(3)].width = 30
11821176

1183-
# Starting from column 4, add headers for each course (each course uses 2 columns for Grade and Remarks).
1184-
col_idx = 4
1177+
# Starting from column 3, add headers for each course (each course uses 2 columns for Grade and Remarks).
1178+
col_idx = 3
11851179
for course in courses:
11861180
# Merge cells for the course code header.
11871181
ws.merge_cells(start_row=1, start_column=col_idx, end_row=1, end_column=col_idx+1)
@@ -1268,19 +1262,11 @@ def post(self, request):
12681262

12691263
# Fill in student rows, starting from row 5.
12701264
row_idx = 5
1271-
User = get_user_model()
12721265
for idx, student in enumerate(students, start=1):
12731266
ws.cell(row=row_idx, column=1).value = idx
12741267
ws.cell(row=row_idx, column=2).value = student.id_id
1275-
1276-
try:
1277-
student_user = User.objects.get(username=student.id_id)
1278-
student_name = f"{student_user.first_name} {student_user.last_name}".strip() or student_user.username
1279-
except:
1280-
student_name = student.id_id
1281-
1282-
ws.cell(row=row_idx, column=3).value = student_name
1283-
ws.cell(row=row_idx, column=3).alignment = Alignment(horizontal="left", vertical="center")
1268+
for c in [1, 2]:
1269+
ws.cell(row=row_idx, column=c).alignment = Alignment(horizontal="center", vertical="center")
12841270

12851271
# Get the student’s grade records for the current semester.
12861272
student_grades = Student_grades.objects.filter(
@@ -1290,7 +1276,7 @@ def post(self, request):
12901276
semester=semester
12911277
)
12921278
grades_map = {g.course_id_id: g for g in student_grades}
1293-
col_ptr = 4
1279+
col_ptr = 3
12941280
for course in courses:
12951281
grade_entry = grades_map.get(course.id)
12961282
grade_val = grade_entry.grade if grade_entry else '-'
@@ -2857,6 +2843,162 @@ def get(self, request, *args, **kwargs):
28572843
return JsonResponse({"success": True, "semesters": semesters})
28582844

28592845

2846+
class GradeStatusAPI(APIView):
2847+
"""
2848+
API to get grade status for all courses in a given academic year and semester type.
2849+
Shows course information, professor name, and submission/verification status.
2850+
2851+
Expected Request:
2852+
POST /api/examination/grade_status/
2853+
Headers:
2854+
Authorization: Token <your_auth_token>
2855+
Body (JSON):
2856+
{
2857+
"Role": "acadadmin",
2858+
"academic_year": "2024-25",
2859+
"semester_type": "Odd Semester"
2860+
}
2861+
2862+
Response:
2863+
200 OK - List of courses with status information
2864+
403 Forbidden - Access denied
2865+
400 Bad Request - Missing required fields
2866+
"""
2867+
permission_classes = [IsAuthenticated]
2868+
2869+
def post(self, request):
2870+
role = request.data.get("Role")
2871+
academic_year = request.data.get("academic_year")
2872+
semester_type = request.data.get("semester_type")
2873+
2874+
# Role-based access control
2875+
if role not in ["acadadmin", "Dean Academic"]:
2876+
return Response(
2877+
{"success": False, "error": "Access denied."},
2878+
status=status.HTTP_403_FORBIDDEN
2879+
)
2880+
2881+
# Validate required parameters
2882+
if not academic_year or not semester_type:
2883+
return Response(
2884+
{"error": "Academic year and semester type are required."},
2885+
status=status.HTTP_400_BAD_REQUEST
2886+
)
2887+
2888+
try:
2889+
# Parse academic year to get working year
2890+
working_year, session = parse_academic_year(academic_year, semester_type)
2891+
2892+
# Get all courses that have registrations for this academic year and semester type
2893+
# Use values_list with flat=True for better performance
2894+
course_ids = course_registration.objects.filter(
2895+
session=academic_year,
2896+
semester_type=semester_type
2897+
).values_list('course_id', flat=True).distinct()
2898+
2899+
# Fetch courses with select_related for better performance
2900+
courses = Courses.objects.filter(id__in=course_ids).order_by('code')
2901+
2902+
# Bulk fetch all instructors to avoid N+1 queries
2903+
instructors_map = {}
2904+
instructors = CourseInstructor.objects.filter(
2905+
course_id__in=course_ids,
2906+
year=working_year,
2907+
semester_type=semester_type
2908+
).select_related()
2909+
2910+
for instructor in instructors:
2911+
instructors_map[instructor.course_id_id] = instructor
2912+
2913+
# Bulk fetch professor names to avoid individual User queries
2914+
instructor_ids = [inst.instructor_id_id for inst in instructors]
2915+
users_map = {}
2916+
if instructor_ids:
2917+
users = get_user_model().objects.filter(username__in=instructor_ids)
2918+
users_map = {
2919+
user.username: f"{user.first_name} {user.last_name}".strip()
2920+
for user in users
2921+
}
2922+
2923+
# Bulk fetch grade submission and verification status
2924+
submitted_courses = set(
2925+
Student_grades.objects.filter(
2926+
course_id__in=course_ids,
2927+
academic_year=academic_year,
2928+
semester_type=semester_type
2929+
).values_list('course_id', flat=True).distinct()
2930+
)
2931+
2932+
verified_courses = set(
2933+
Student_grades.objects.filter(
2934+
course_id__in=course_ids,
2935+
academic_year=academic_year,
2936+
semester_type=semester_type,
2937+
verified=True
2938+
).values_list('course_id', flat=True).distinct()
2939+
)
2940+
2941+
# Bulk fetch authentication records
2942+
auth_records_map = {}
2943+
auth_records = authentication.objects.filter(
2944+
course_id__in=course_ids,
2945+
course_year=working_year
2946+
)
2947+
2948+
for auth in auth_records:
2949+
auth_records_map[auth.course_id_id] = auth
2950+
2951+
# Build response data efficiently
2952+
grade_status_list = []
2953+
2954+
for course in courses:
2955+
# Get instructor information from pre-fetched data
2956+
instructor = instructors_map.get(course.id)
2957+
professor_name = "Not Assigned"
2958+
2959+
if instructor:
2960+
professor_name = users_map.get(
2961+
instructor.instructor_id_id,
2962+
instructor.instructor_id_id
2963+
)
2964+
2965+
# Determine status from pre-fetched sets
2966+
submitted = "Submitted" if course.id in submitted_courses else "Not Submitted"
2967+
verified = "Verified" if course.id in verified_courses else "Not Verified"
2968+
2969+
# Check validation status
2970+
validated = "Not Validated"
2971+
if course.id in verified_courses: # Only check if verified
2972+
auth_record = auth_records_map.get(course.id)
2973+
if (auth_record and auth_record.authenticator_1 and
2974+
auth_record.authenticator_2 and auth_record.authenticator_3):
2975+
validated = "Validated"
2976+
2977+
grade_status_list.append({
2978+
"course_code": course.code,
2979+
"course_name": course.name,
2980+
"course_id": course.id,
2981+
"professor_name": professor_name,
2982+
"submitted": submitted,
2983+
"verified": verified,
2984+
"validated": validated,
2985+
"credits": course.credit,
2986+
"version": course.version
2987+
})
2988+
2989+
return Response({
2990+
"success": True,
2991+
"grade_status": grade_status_list,
2992+
"academic_year": academic_year,
2993+
"semester_type": semester_type
2994+
}, status=status.HTTP_200_OK)
2995+
2996+
except Exception as e:
2997+
return Response(
2998+
{"error": f"An error occurred: {str(e)}"},
2999+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
3000+
)
3001+
28603002
class GenerateStudentResultPDFAPI(APIView):
28613003
"""
28623004
API endpoint to generate PDF report for student examination results

0 commit comments

Comments
 (0)