Optimize exam retrieval by prefetching related data to reduce N+1 queries and enhance access checks for user permissions
This commit is contained in:
+53
-17
@@ -3044,7 +3044,12 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
cid: int | None = None,
|
cid: int | None = None,
|
||||||
passcode: str | None = None,
|
passcode: str | None = None,
|
||||||
):
|
):
|
||||||
exams = self.Exam.objects.filter(archive=False)
|
# Prefetch related relations used during the active_exams check to
|
||||||
|
# avoid N+1 queries when iterating many exams.
|
||||||
|
exams = (
|
||||||
|
self.Exam.objects.filter(archive=False)
|
||||||
|
.prefetch_related("valid_cid_users", "valid_user_users", "exam_questions")
|
||||||
|
)
|
||||||
|
|
||||||
active_exams = {"exams": []}
|
active_exams = {"exams": []}
|
||||||
|
|
||||||
@@ -3056,22 +3061,52 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
return JsonResponse({"status": "invalid"}, status=401)
|
return JsonResponse({"status": "invalid"}, status=401)
|
||||||
|
|
||||||
exam: ExamBase
|
exam: ExamBase
|
||||||
|
# Prepare cid/user quick lookup when cid/user provided to avoid DB hits
|
||||||
|
user_id = request.user.pk if hasattr(request, "user") and request.user.is_authenticated else None
|
||||||
|
|
||||||
for exam in exams:
|
for exam in exams:
|
||||||
if exam.active or self.check_user_access(request.user, exam.pk):
|
# Skip exams the user cannot see unless it's active
|
||||||
if exam.exam_mode and not exam.check_cid_user(
|
if not (exam.active or self.check_user_access(request.user, exam.pk)):
|
||||||
cid, passcode, user=request.user, user_id=request.user.pk
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if exam.json_creation_time:
|
# If exam is in exam_mode, ensure the cid/user is allowed. Use the
|
||||||
creation_time = exam.json_creation_time.isoformat()
|
# prefetched relations to perform in-memory checks rather than extra
|
||||||
else:
|
# DB queries via `check_cid_user` which issues its own queries.
|
||||||
creation_time = "None"
|
if exam.exam_mode:
|
||||||
|
allowed = False
|
||||||
|
# superusers always allowed
|
||||||
|
if hasattr(request, "user") and getattr(request.user, "is_superuser", False):
|
||||||
|
allowed = True
|
||||||
|
|
||||||
url = request.build_absolute_uri(
|
# open access allows any logged-in user
|
||||||
exam.get_json_url(cid=cid, passcode=passcode)
|
if not allowed and exam.exam_open_access and user_id is not None:
|
||||||
)
|
allowed = True
|
||||||
# hacky
|
|
||||||
|
# check valid_user_users (prefetched)
|
||||||
|
if not allowed and user_id is not None:
|
||||||
|
try:
|
||||||
|
valid_user_ids = {u.pk for u in exam.valid_user_users.all()}
|
||||||
|
if user_id in valid_user_ids:
|
||||||
|
allowed = True
|
||||||
|
except Exception:
|
||||||
|
allowed = False
|
||||||
|
|
||||||
|
# check CID users when cid provided
|
||||||
|
if not allowed and cid is not None:
|
||||||
|
try:
|
||||||
|
for cu in exam.valid_cid_users.all():
|
||||||
|
if getattr(cu, "cid", None) == cid and getattr(cu, "passcode", None) == passcode:
|
||||||
|
allowed = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
allowed = False
|
||||||
|
|
||||||
|
if not allowed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
creation_time = exam.json_creation_time.isoformat() if exam.json_creation_time else "None"
|
||||||
|
|
||||||
|
url = request.build_absolute_uri(exam.get_json_url(cid=cid, passcode=passcode))
|
||||||
if not based:
|
if not based:
|
||||||
url = url + "/unbased"
|
url = url + "/unbased"
|
||||||
|
|
||||||
@@ -3086,14 +3121,15 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
"exam_mode": exam.exam_mode,
|
"exam_mode": exam.exam_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Avoid generating question JSON in this endpoint; return existing ids
|
||||||
|
# to keep CPU usage low. Generation should be handled asynchronously
|
||||||
|
# or on-demand by the request that needs it.
|
||||||
if self.question_type == "long":
|
if self.question_type == "long":
|
||||||
h = {}
|
h = {}
|
||||||
for question in exam.exam_questions.all():
|
for question in exam.exam_questions.all():
|
||||||
# Generate json if needed
|
h[question.pk] = getattr(question, "question_json_id", None)
|
||||||
if question.json_creation_time is None:
|
|
||||||
question.get_question_json()
|
|
||||||
h[question.pk] = question.question_json_id
|
|
||||||
obj["multi_question_json"] = h
|
obj["multi_question_json"] = h
|
||||||
|
|
||||||
active_exams["exams"].append(obj)
|
active_exams["exams"].append(obj)
|
||||||
|
|
||||||
if json is False:
|
if json is False:
|
||||||
|
|||||||
Reference in New Issue
Block a user