From 272f0cad908b8199a759172ac3491924b4d413d8 Mon Sep 17 00:00:00 2001 From: Vikrant Kumar Date: Sun, 19 Jul 2026 11:38:17 +0530 Subject: [PATCH 1/2] Examination: gate acadadmin course access by selected role, not held designation - SubmitGradesProfAPI and UploadGradesProfAPI now treat a user as acadadmin only when acting as acadadmin (Role=acadadmin) AND holding the designation - Fixes faculty who also hold acadadmin seeing all courses / bypassing the instructor-ownership check while submitting as faculty --- FusionIIIT/applications/examination/api/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FusionIIIT/applications/examination/api/views.py b/FusionIIIT/applications/examination/api/views.py index 36c9ea863..f9d34bdcf 100644 --- a/FusionIIIT/applications/examination/api/views.py +++ b/FusionIIIT/applications/examination/api/views.py @@ -1729,7 +1729,8 @@ def post(self, request): working_year, _ = parse_academic_year(academic_year=academic_year, semester_type=semester_type) - if user_holds_role(request.user, "acadadmin"): + acting_as_acadadmin = role == "acadadmin" and user_holds_role(request.user, "acadadmin") + if acting_as_acadadmin: course_ids = ( course_registration.objects .filter(session=academic_year, semester_type=semester_type) @@ -1966,7 +1967,8 @@ def post(self, request): ) # 8) INSTRUCTOR‐OWNERSHIP CHECK (acadadmin may submit for any course) - if not user_holds_role(request.user, "acadadmin") and not CourseInstructor.objects.filter( + acting_as_acadadmin = role == "acadadmin" and user_holds_role(request.user, "acadadmin") + if not acting_as_acadadmin and not CourseInstructor.objects.filter( course_id_id=course_id, instructor_id_id=request.user.username, year=working_year From 1a67b97adeb42b6b35f92f400d86c9b1276c3707 Mon Sep 17 00:00:00 2001 From: Vikrant Kumar Date: Sun, 19 Jul 2026 11:56:38 +0530 Subject: [PATCH 2/2] Examination: match Summer CourseInstructor year (start year) in all lookups CourseInstructor stores Odd/Summer under the start year and Even under the end year, but parse_academic_year keys Summer on the end year (to match Student_grades). Add course_instructor_year() helper and use it for every CourseInstructor filter (submit dropdown, upload ownership, download grades, PDF, grade status) so a faculty's Summer courses are found. parse_academic_year and Student_grades storage are left unchanged. --- .../applications/examination/api/views.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/FusionIIIT/applications/examination/api/views.py b/FusionIIIT/applications/examination/api/views.py index f9d34bdcf..4c05c683d 100644 --- a/FusionIIIT/applications/examination/api/views.py +++ b/FusionIIIT/applications/examination/api/views.py @@ -227,6 +227,19 @@ def parse_academic_year(academic_year, semester_type): session = academic_year # Use the complete academic year string as session. return working_year, session +def course_instructor_year(academic_year, semester_type): + """ + Year used by CourseInstructor rows for a given academic year + semester type. + CourseInstructor stores Odd/Summer under the start year and Even under the end + year (see CourseInstructor.academic_year). This differs from parse_academic_year, + which keys Summer on the end year to match Student_grades — so use this helper + (not working_year) whenever filtering CourseInstructor by year. + """ + if semester_type == "Summer Semester": + return int(academic_year.split("-")[0].strip()) + working_year, _ = parse_academic_year(academic_year, semester_type) + return working_year + def is_valid_grade(grade: str, course_code: str) -> bool: """ Returns True if the grade is valid for the given course code. @@ -1741,7 +1754,7 @@ def post(self, request): else: unique_course_ids = ( CourseInstructor.objects - .filter(instructor_id_id=request.user.username, year=working_year, semester_type=semester_type) + .filter(instructor_id_id=request.user.username, year=course_instructor_year(academic_year, semester_type), semester_type=semester_type) .values("course_id_id") .distinct() .annotate(course_id_int=Cast("course_id_id", IntegerField())) @@ -1971,7 +1984,7 @@ def post(self, request): if not acting_as_acadadmin and not CourseInstructor.objects.filter( course_id_id=course_id, instructor_id_id=request.user.username, - year=working_year + year=course_instructor_year(academic_year, semester_type) ).exists(): return Response( {"error": "Access denied: you are not assigned as instructor for this course."}, @@ -2127,7 +2140,7 @@ def post(self, request): unique_course_ids = ( CourseInstructor.objects - .filter(instructor_id_id=instructor_id, year=working_year, semester_type=semester_type) + .filter(instructor_id_id=instructor_id, year=course_instructor_year(academic_year, semester_type), semester_type=semester_type) .values("course_id_id") .distinct() .annotate(course_id_int=Cast("course_id_id", IntegerField())) @@ -2221,13 +2234,13 @@ def post(self, request): if user_holds_role(request.user, "acadadmin"): ci = CourseInstructor.objects.filter( course_id_id=course_id, - year=working_year, + year=course_instructor_year(academic_year, semester_type), semester_type=semester_type, ) else: ci = CourseInstructor.objects.filter( course_id_id=course_id, - year=working_year, + year=course_instructor_year(academic_year, semester_type), semester_type=semester_type, instructor_id_id=request.user.username ) @@ -3541,7 +3554,7 @@ def post(self, request): instructors_map = {} instructors = CourseInstructor.objects.filter( course_id__in=course_ids, - year=working_year, + year=course_instructor_year(academic_year, semester_type), semester_type=semester_type ).select_related()