switch to generic exam urls

This commit is contained in:
Ross
2022-07-04 19:00:38 +01:00
parent eb602616b8
commit 41991f1ebd
8 changed files with 315 additions and 451 deletions
+159 -5
View File
@@ -60,10 +60,14 @@ urlpatterns = [
name="group_email_resend",
),
path(
"cids/group/<int:pk>/update", views.CidUserGroupUpdate.as_view(), name="cid_group_update"
"cids/group/<int:pk>/update",
views.CidUserGroupUpdate.as_view(),
name="cid_group_update",
),
path(
"cids/group/<int:pk>/delete", views.CidUserGroupDelete.as_view(), name="cid_group_delete"
"cids/group/<int:pk>/delete",
views.CidUserGroupDelete.as_view(),
name="cid_group_delete",
),
path(
"cids/group/create", views.CidUserGroupCreate.as_view(), name="cid_group_create"
@@ -71,13 +75,163 @@ urlpatterns = [
path("user/group/", views.user_group_view, name="user_group_view"),
path("user/group/all", views.user_group_view_all, name="user_group_view_all"),
path(
"user/group/<int:pk>/update", views.UserUserGroupUpdate.as_view(), name="user_group_update"
"user/group/<int:pk>/update",
views.UserUserGroupUpdate.as_view(),
name="user_group_update",
),
path(
"user/group/<int:pk>/delete", views.UserUserGroupDelete.as_view(), name="user_group_delete"
"user/group/<int:pk>/delete",
views.UserUserGroupDelete.as_view(),
name="user_group_delete",
),
path(
"user/group/create", views.UserUserGroupCreate.as_view(), name="user_group_create"
"user/group/create",
views.UserUserGroupCreate.as_view(),
name="user_group_create",
),
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
]
def generic_view_urls(generic_views):
urlpatterns = [
path(
"user_answers/delete",
generic_views.user_answer_delete_multiple,
name="user_answer_delete_multiple",
),
path("author/<int:pk>/", generic_views.author_detail, name="author_detail"),
path("author/", generic_views.author_list, name="author_list"),
path(
"question/<int:pk>/", generic_views.question_detail, name="question_detail"
),
path(
"question/<int:pk>/user_answers",
generic_views.question_user_answers,
name="question_user_answers",
),
path(
"exam/<int:pk>/review",
generic_views.question_review,
name="question_review",
),
]
return urlpatterns
def generic_exam_urls(generic_exam_view):
urlpatterns = [
path("", generic_exam_view.index, name="index"),
path("exam/<int:pk>/", generic_exam_view.exam_overview, name="exam_overview"),
path(
"exam/<int:pk>/mark", generic_exam_view.mark_overview, name="mark_overview"
),
path(
"exam/<int:pk>/question/<int:sk>/",
generic_exam_view.exam_question_detail,
name="exam_question_detail",
),
path(
"exam/<int:exam_id>/user/<int:user_id>",
generic_exam_view.exam_user,
name="exam_user",
),
path(
"exam/<int:exam_id>/report/email",
generic_exam_view.exam_report_email,
name="exam_report_email",
),
path(
"exam/<int:exam_id>/report/email_unsent",
generic_exam_view.exam_report_email_unsent,
name="exam_report_email_unsent",
),
path(
"exam/<int:exam_id>/report/email/status",
generic_exam_view.exam_report_email_status,
name="exam_report_email_status",
),
path(
"exam/<int:exam_id>/report/<int:user_id>",
generic_exam_view.exam_user_report,
name="exam_user_report",
),
path(
"exam/<int:exam_id>/report/<int:user_id>/email",
generic_exam_view.exam_user_report_email,
name="exam_user_report_email",
),
path(
"exam/<int:pk>/json_edit",
generic_exam_view.exam_json_edit,
name="exam_json_edit",
),
path(
"exam/<int:pk>/scores",
generic_exam_view.exam_scores_cid,
name="exam_scores_cid",
),
path(
"exam/<int:pk>/scores/refresh",
generic_exam_view.exam_scores_refresh,
# cache_page(60 * 1)(exam_scores_cid),
name="exam_scores_refresh",
),
path(
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
generic_exam_view.exam_scores_cid_user,
name="exam_scores_cid_user",
),
path(
"exam/<int:pk>/scores/",
generic_exam_view.exam_scores_user,
name="exam_scores_user",
),
path(
"exam/<int:pk>/toggle_active",
generic_exam_view.exam_toggle_active,
name="exam_toggle_active",
),
path(
"exam/<int:pk>/toggle_results_published",
generic_exam_view.exam_toggle_results_published,
name="exam_toggle_results_published",
),
path(
"exam/submit",
generic_exam_view.postExamAnswers,
name="exam_answers_submit",
),
path("exam/", generic_exam_view.exam_list, name="exam_list"),
path("exam/all", generic_exam_view.exam_list_all, name="exam_list_all"),
path("exam/<int:exam_id>/cids", generic_exam_view.exam_cids, name="exam_cids"),
path(
"exam/<int:exam_id>/cids/edit",
generic_exam_view.exam_cids_edit,
name="exam_cids_edit",
),
path(
"exam/<int:exam_id>/users/edit",
generic_exam_view.exam_users_edit,
name="exam_users_edit",
),
path("exam/json/", generic_exam_view.active_exams, name="active_exams"),
path("exam/json/<int:pk>", generic_exam_view.exam_json, name="exam_json"),
path(
"exam/json/<int:pk>/unbased",
generic_exam_view.exam_json_unbased,
name="exam_json_unbased",
),
path(
"exam/json/<int:pk>/<int:sk>/unbased",
generic_exam_view.exam_question_json_unbased,
name="exam_question_json_unbased",
),
path(
"exam/json/<int:pk>/recreate",
generic_exam_view.exam_json_recreate,
name="exam_json_recreate",
),
]
return urlpatterns
+127 -22
View File
@@ -284,6 +284,7 @@ class ExamViews(View, LoginRequiredMixin):
cid_user_answer,
app,
question_type,
normalise_score=None,
):
self.Exam = exam
self.Question = question
@@ -291,6 +292,7 @@ class ExamViews(View, LoginRequiredMixin):
self.CidUserAnswer = cid_user_answer
self.app_name = app
self.question_type = question_type
self.normalise_score = normalise_score
# THis may be better than check_user_access below
# group_map = {"rapids" : "rapid_checker", "anatomy" : "anatomy_checker", "longs":"long_checker"}
@@ -562,7 +564,9 @@ class ExamViews(View, LoginRequiredMixin):
@method_decorator(login_required)
def exam_user(self, request, exam_id, user_id):
exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
exam = get_object_or_404(
self.Exam.objects.prefetch_related("author"), pk=exam_id
)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
@@ -573,10 +577,11 @@ class ExamViews(View, LoginRequiredMixin):
return JsonResponse(model_to_dict(user_exam))
@method_decorator(login_required)
def exam_report_email_status(self, request, exam_id):
exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
exam = get_object_or_404(
self.Exam.objects.prefetch_related("author"), pk=exam_id
)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
@@ -600,20 +605,21 @@ class ExamViews(View, LoginRequiredMixin):
},
)
return JsonResponse(status)
@method_decorator(login_required)
def exam_report_email_unsent(self, request, exam_id):
return self.exam_report_email(request, exam_id, unsent_only=True)
@method_decorator(login_required)
def exam_report_email(self, request, exam_id, unsent_only=False, users=None):
exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
exam = get_object_or_404(
self.Exam.objects.prefetch_related("author"), pk=exam_id
)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
raise PermissionDenied
additional_email = request.GET.get("additional_email", "")
# check addition email is valid
@@ -639,9 +645,10 @@ class ExamViews(View, LoginRequiredMixin):
if json.loads(user_exam.results_emailed_status)[0] == True:
continue
if additional_email:
email_results[user.pk] = exam.email_user_results(user, additional_emails=[additional_email])
email_results[user.pk] = exam.email_user_results(
user, additional_emails=[additional_email]
)
else:
email_results[user.pk] = exam.email_user_results(user)
email_results[user.pk].append(f"{time}")
@@ -651,7 +658,7 @@ class ExamViews(View, LoginRequiredMixin):
exam.exam_results_emailed = time
exam.save()
return JsonResponse(email_results)
@method_decorator(login_required)
@@ -660,24 +667,26 @@ class ExamViews(View, LoginRequiredMixin):
# print(Exam.objects.all())
# exams = Exam.objects.all()
# print("test", Exam.objects.all().get(id=pk))
#exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
# exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
#u = get_object_or_404(User, pk=user_id)
# u = get_object_or_404(User, pk=user_id)
#if request.user not in exam.author.all():
# if request.user not in exam.author.all():
# if not self.check_user_access(request.user, exam_id):
# raise PermissionDenied
#res = exam.email_user_results(u)
# res = exam.email_user_results(u)
#return HttpResponse(res)
# return HttpResponse(res)
@method_decorator(login_required)
def exam_user_report(self, request, exam_id, user_id):
# print(Exam.objects.all())
# exams = Exam.objects.all()
# print("test", Exam.objects.all().get(id=pk))
exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=exam_id)
exam = get_object_or_404(
self.Exam.objects.prefetch_related("author"), pk=exam_id
)
u = get_object_or_404(User, pk=user_id)
@@ -690,7 +699,6 @@ class ExamViews(View, LoginRequiredMixin):
return HttpResponse(res)
@method_decorator(login_required)
def exam_overview(self, request, pk):
# print(Exam.objects.all())
@@ -698,7 +706,6 @@ class ExamViews(View, LoginRequiredMixin):
# print("test", Exam.objects.all().get(id=pk))
exam = get_object_or_404(self.Exam.objects.prefetch_related("author"), pk=pk)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, pk):
raise PermissionDenied
@@ -1517,6 +1524,103 @@ class ExamViews(View, LoginRequiredMixin):
return JsonResponse(question_json)
@method_decorator(login_required)
def exam_scores_user(self, request, pk):
return self.exam_scores_cid_user(request, pk)
def exam_scores_cid_user(self, request, pk, cid=None, passcode=""):
exam = get_object_or_404(self.Exam, pk=pk)
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
# TODO:Need some kind of test for cid
questions = (
exam.exam_questions.all()
) # .prefetch_related("cid_user_answers", "answers")
# cid_user_answers = list(CidUserAnswer.objects.filter(cid=cid, exam__id=pk).prefetch_related("question"))
# cid_user_answers_q_map = {}
# for ans in cid_user_answers:
# cid_user_answers_q_map[ans.question] = ans
answers_and_marks = []
answers_marks = []
answers = []
view_all_results = False
if request.user.groups.filter(name="view_all_results").exists():
view_all_results = True
for q in questions:
# Get user answer
if cid is not None:
user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
else:
user_answer = q.cid_user_answers.filter(
user=request.user, exam__id=pk
).first()
# user_answer = cid_user_answers_q_map[q]
if not user_answer or user_answer is None:
# skip if no answer
answers_marks.append(0)
# answers.append("")
answer_score = 0
ans = "Not answered"
else:
if user_answer.normal:
ans = "Normal"
else:
ans = user_answer.answer
answer_score = user_answer.get_answer_score()
correct_answer = q.get_primary_answer()
if not exam.publish_results and not view_all_results:
correct_answer = "*****"
answer_score = 0
answers.append(ans)
answers_marks.append(answer_score)
answers_and_marks.append((ans, answer_score, correct_answer))
if "unmarked" in answers_marks:
answered = [i for i in answers_marks if type(i) == int]
total_score = sum(answered)
unmarked_number = len(answers_marks) - len(answered)
total_score = "{} ({} unmarked)".format(total_score, unmarked_number)
else:
total_score = sum(answers_marks)
max_score = len(questions) * 2
template_context = {
"exam": exam,
"cid": cid,
"passcode": passcode,
"questions": questions,
"answers": answers,
"answers_marks": answers_marks,
"total_score": total_score,
"max_score": max_score,
"answers_and_marks": answers_and_marks,
"view_all_results": view_all_results,
}
if self.normalise_score is not None:
normalised_score = self.normalise_score(total_score)
template_context["normalised_score"] = normalised_score
return render(
request,
f"{self.app_name}/exam_scores_user.html",
template_context,
)
def exam_scores_cid(self, request, pk):
exam = get_object_or_404(self.Exam, pk=pk)
@@ -1563,7 +1667,7 @@ class ExamViews(View, LoginRequiredMixin):
cids.add(cid)
else:
cid = f"u/{cid_user_answer.user.pk}"
#cids_user_id_map[cid] = cid_user_answer.user.username
# cids_user_id_map[cid] = cid_user_answer.user.username
name = f"{cid_user_answer.user.first_name} {cid_user_answer.user.last_name}"
if not name.strip():
name = cid_user_answer.user.username
@@ -1665,11 +1769,11 @@ class ExamViews(View, LoginRequiredMixin):
exam.stats_min = min(user_scores_list)
exam.stats_max = max(user_scores_list)
#exam.stats_graph = fig_html
# exam.stats_graph = fig_html
user_data = {}
for u in cids:
#uid = cids_user_id_map[u]
# uid = cids_user_id_map[u]
user_data[u] = {
"score": user_scores[u],
"normalised_score": user_scores_normalised[u],
@@ -1680,7 +1784,6 @@ class ExamViews(View, LoginRequiredMixin):
user_data[u]["callstates"] = str(counted_callstates)
user_answers_callstates_counted[u] = counted_callstates
exam.user_scores = user_data
exam.save()
@@ -1709,7 +1812,9 @@ class ExamViews(View, LoginRequiredMixin):
}
if self.app_name == "rapids":
template_variables["user_answers_callstates"] = user_answers_callstates_counted
template_variables[
"user_answers_callstates"
] = user_answers_callstates_counted
return render(
request,