diff --git a/anatomy/models.py b/anatomy/models.py
index e8df503c..c65eec9a 100644
--- a/anatomy/models.py
+++ b/anatomy/models.py
@@ -329,6 +329,12 @@ class Answer(models.Model):
self.answer = self.answer.strip()
self.answer_compare = get_answer_compare(self.answer)
+ def can_edit(self, user):
+ """Check if user can edit the answer
+
+ This just mirrors the result from the attached question"""
+ return self.question.can_edit(user)
+
# def get_compare_string(self):
# s = self.answer.lower().strip()
# s = s.translate(str.maketrans('', '', string.punctuation))
diff --git a/anatomy/templates/anatomy/question_detail.html b/anatomy/templates/anatomy/question_detail.html
index c4aa5729..fe47d248 100644
--- a/anatomy/templates/anatomy/question_detail.html
+++ b/anatomy/templates/anatomy/question_detail.html
@@ -20,25 +20,40 @@
Question type: {{question.question_type}}
Primary answer: {{ question.get_primary_answer }}
-
-
- Answers:
-
-
- | Answer | Score |
- {% for answer in question.answers.all|dictsortreversed:"status" %}
-
- |
-
- {{ answer }}
-
- |
- | {{answer.status}} |
-
- {% endfor %}
-
-
+
+
+
+ Answers:
+
+
+ | Answer | Score |
+ {% for answer in question.answers.all|dictsortreversed:"status" %}
+
+ |
+
+ {{ answer }}
+
+ |
+ | {{answer.status}} |
+ {% if answer.proposed %}
+
+
+
+ |
+ {% endif %}
+
+
+
+ {% endfor %}
+
+
+
Answer help: {{ question.answer_help|safe }}
@@ -52,6 +67,10 @@
Exams: {% for exam in question.exams.all %}
{{ exam }}
{% endfor %}
+
+
Modality: {{ question.modality }}
@@ -148,4 +167,17 @@
}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
+
+{% block css %}
+
+{% endblock css %}
\ No newline at end of file
diff --git a/anatomy/urls.py b/anatomy/urls.py
index 806c9ef9..63ab6f93 100644
--- a/anatomy/urls.py
+++ b/anatomy/urls.py
@@ -47,6 +47,13 @@ urlpatterns = [
views.QuestionDelete.as_view(),
name="question_delete",
),
+ path(
+ "question//add_exam",
+ views.question_add_exam,
+ name="question_add_exam",
+ ),
+ path("answer//confirm", views.confirm_answer, name="confirm_answer"),
+ path("answer//delete", views.delete_answer, name="delete_answer"),
path("exam///mark", views.mark, name="mark"),
path("exam///mark/all", views.mark_all, name="mark_all"),
path(
diff --git a/anatomy/views.py b/anatomy/views.py
index ccac0bc6..5011d956 100644
--- a/anatomy/views.py
+++ b/anatomy/views.py
@@ -27,6 +27,8 @@ from django.http import HttpResponseRedirect, HttpResponse
from dal import autocomplete
from django.conf import settings
+from django.utils.html import format_html_join
+from django.utils.safestring import mark_safe
from .forms import (
@@ -851,3 +853,81 @@ GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view"""
+
+
+def confirm_answer(request, answer_id: int):
+ if request.htmx:
+ answer = get_object_or_404(Answer, pk=answer_id)
+
+ # Check if the user has permission to confirm the answer
+ if not answer.can_edit(request.user):
+ return HttpResponse("Invalid permissions", status=403)
+
+ answer.clean()
+ answer.proposed = False
+ answer.save()
+ return HttpResponse("Answer accepted")
+
+ raise PermissionDenied()
+
+def delete_answer(request, answer_id: int):
+ if request.htmx:
+ answer = get_object_or_404(Answer, pk=answer_id)
+
+ # Check if the user has permission to confirm the answer
+ if not answer.can_edit(request.user):
+ return HttpResponse("Invalid permissions", status=403)
+
+ answer.delete()
+ return HttpResponse("Answer deleted")
+ raise PermissionDenied()
+
+def question_add_exam(request, question_id: int):
+ if request.htmx:
+ question = get_object_or_404(AnatomyQuestion, pk=question_id)
+
+ if not question.can_edit(request.user):
+ return HttpResponse("Invalid permissions", status=403)
+
+
+ if request.POST:
+ exam_id = request.POST.get("exam_id", None)
+
+ if exam_id is None:
+ return HttpResponse("No exam id provided", status=400)
+
+ exam = get_object_or_404(Exam, pk=exam_id)
+
+ if request.POST.get("remove", False) == "true":
+ question.exams.remove(exam)
+ return HttpResponse("Question removed from exam.")
+ else:
+ question.exams.add(exam)
+
+ return HttpResponse("Question added to exam.")
+ else:
+ # Return a list of exams that we can add to the question
+
+ exams = Exam.objects.filter(author=request.user) | Exam.objects.filter(open_access=True)
+ exams = exams.difference(question.exams.all())
+
+ if not exams:
+ return HttpResponse("No exams available to add")
+
+ html = "Exams to add to question:
"
+ for exam in exams:
+ html = html + (f""
+ )
+ html = html + "
Exams to remove from question:
"
+ for exam in question.exams.all():
+ html = html + (f""
+ )
+ html = html + ""
+
+ return HttpResponse(mark_safe(html))
+
+
+ raise PermissionDenied()
\ No newline at end of file
diff --git a/generic/views.py b/generic/views.py
index 2ce2f02b..ea75b5ba 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -48,7 +48,12 @@ from atlas.models import CaseCollection, CaseDetail
from generic.decorators import user_is_cid_user_manager
from generic.filters import CidUserFilter, ExaminationFilter, SupervisorFilter
-from generic.tables import CidUserExamTable, CidUserTable, ExaminationTable, SupervisorTable
+from generic.tables import (
+ CidUserExamTable,
+ CidUserTable,
+ ExaminationTable,
+ SupervisorTable,
+)
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
import zipfile
@@ -79,6 +84,7 @@ from .models import (
ExamCollection,
ExamUserStatus,
Examination,
+ QuestionBase,
QuestionNote,
Supervisor,
UserGrades,
@@ -87,15 +93,31 @@ from .models import (
get_next_cid,
)
-from rapids.models import Rapid as RapidQuestion, ExamQuestionDetail as RapidsExamQuestionDetail
+from rapids.models import (
+ Rapid as RapidQuestion,
+ ExamQuestionDetail as RapidsExamQuestionDetail,
+)
from rapids.models import Exam as RapidsExam
-from longs.models import Long as LongQuestion, LongSeries, ExamQuestionDetail as LongsExamQuestionDetail
+from longs.models import (
+ Long as LongQuestion,
+ LongSeries,
+ ExamQuestionDetail as LongsExamQuestionDetail,
+)
from longs.models import Exam as LongsExam
-from anatomy.models import AnatomyQuestion as AnatomyQuestion, ExamQuestionDetail as AnatomyExamQuestionDetail
+from anatomy.models import (
+ AnatomyQuestion as AnatomyQuestion,
+ ExamQuestionDetail as AnatomyExamQuestionDetail,
+)
from anatomy.models import Exam as AnatomyExam
-from sbas.models import Question as SbasQuestion, ExamQuestionDetail as SbasExamQuestionDetail
+from sbas.models import (
+ Question as SbasQuestion,
+ ExamQuestionDetail as SbasExamQuestionDetail,
+)
from sbas.models import Exam as SbasExam
-from physics.models import Question as PhysicsQuestion, ExamQuestionDetail as PhysicsExamQuestionDetail
+from physics.models import (
+ Question as PhysicsQuestion,
+ ExamQuestionDetail as PhysicsExamQuestionDetail,
+)
from physics.models import Exam as PhysicsExam
from django.db.models import Case, When
@@ -112,13 +134,13 @@ import plotly.express as px
from django.db.models import Prefetch
-
-class RedirectMixin():
+class RedirectMixin:
def get_success_url(self) -> str:
if "redirect" in self.request.GET:
return self.request.GET["redirect"]
return super().get_success_url()
+
class AuthorRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
@@ -141,6 +163,7 @@ class CidManagerRequiredMixin(UserPassesTestMixin):
# return obj
# raise PermissionDenied() # or Http404
+
def get_exam_model_from_app_name(app_name: str) -> ExamBase:
EXAM_MAP = {
"physics": PhysicsExam,
@@ -471,7 +494,7 @@ class ExamViews(View, LoginRequiredMixin):
if (exam.open_access and exam.active) or user in exam.get_author_objects():
return True
- if marker and user in exam.markers.all():
+ if marker and user in exam.markers.all():
return True
if exam.authors_only:
@@ -535,7 +558,7 @@ class ExamViews(View, LoginRequiredMixin):
if exam_id is not None:
exam = get_object_or_404(self.Exam, pk=exam_id)
# Remove open_access check for the momement
- #if exam.open_access or user in exam.get_author_objects():
+ # if exam.open_access or user in exam.get_author_objects():
# return True
if user in exam.get_author_objects():
return True
@@ -687,13 +710,21 @@ class ExamViews(View, LoginRequiredMixin):
def exam_list(self, request, all=False):
if not self.check_user_access(request.user):
# raise PermissionDenied
- exam_list = self.Exam.objects.filter(author__id=request.user.id, exam_mode=True).order_by(
- "name"
- )
+ exam_list = self.Exam.objects.filter(
+ author__id=request.user.id, exam_mode=True
+ ).order_by("name")
- exam_list = exam_list | self.Exam.objects.filter(markers__id=request.user.id, exam_mode=True).order_by("name")
+ exam_list = exam_list | self.Exam.objects.filter(
+ markers__id=request.user.id, exam_mode=True
+ ).order_by("name")
else:
- exam_list = self.Exam.objects.prefetch_related("valid_user_users", "valid_cid_users").filter(exam_mode=True).order_by("name")
+ exam_list = (
+ self.Exam.objects.prefetch_related(
+ "valid_user_users", "valid_cid_users"
+ )
+ .filter(exam_mode=True)
+ .order_by("name")
+ )
if not all:
exam_list = exam_list.filter(archive=False).order_by("name")
@@ -743,7 +774,7 @@ class ExamViews(View, LoginRequiredMixin):
status = {}
for user in users:
- #user = User.objects.get(pk=u)
+ # user = User.objects.get(pk=u)
user_exam = exam.get_or_create_cid_user_exam(user_user=user)
if user_exam.results_emailed_status:
status[user] = json.loads(user_exam.results_emailed_status)
@@ -796,7 +827,7 @@ class ExamViews(View, LoginRequiredMixin):
email_results = {}
for user in users:
- #user = User.objects.get(pk=u)
+ # user = User.objects.get(pk=u)
user_exam = exam.get_or_create_cid_user_exam(user_user=user)
if unsent_only and user_exam.results_emailed_status:
@@ -904,7 +935,7 @@ class ExamViews(View, LoginRequiredMixin):
queryset=self.Answer.objects.filter(
proposed=False, status=self.Answer.MarkOptions.CORRECT
),
- to_attr="prefetched_primary_answer"
+ to_attr="prefetched_primary_answer",
# queryset=self.Answer.objects.filter(),
),
)
@@ -913,21 +944,20 @@ class ExamViews(View, LoginRequiredMixin):
elif self.app_name == "longs":
questions = (
exam.exam_questions.prefetch_related(
- #Prefetch(
+ # Prefetch(
# "series",
# queryset=LongSeries.objects.select_related(
# "modality", "examination", "plane", "contrast"
# ).prefetch_related(Prefetch("images")),
- #),
+ # ),
"author",
- #"examquestiondetail",
- #"longsseries",
+ # "examquestiondetail",
+ # "longsseries",
"series",
- #"seriesdetail",
+ # "seriesdetail",
"series__images",
"series__plane",
- "series__examination"
-
+ "series__examination",
# to_attr="prefetched_primary_answer"
# queryset=self.Answer.objects.filter(),
# "series",
@@ -948,7 +978,7 @@ class ExamViews(View, LoginRequiredMixin):
queryset=self.Answer.objects.filter(
proposed=False, status=self.Answer.MarkOptions.CORRECT
),
- to_attr="prefetched_primary_answer"
+ to_attr="prefetched_primary_answer",
# queryset=self.Answer.objects.filter(),
),
"images",
@@ -966,25 +996,29 @@ class ExamViews(View, LoginRequiredMixin):
# )
elif self.app_name == "physics":
questions = (
- exam.exam_questions.select_related("category").all()
+ exam.exam_questions.select_related("category")
+ .all()
.order_by("examquestiondetail__sort_order")
# .prefetch_related("images", "abnormality", "region", "examination")
)
else:
- questions = exam.exam_questions.select_related().all().order_by("examquestiondetail__sort_order")
+ questions = (
+ exam.exam_questions.select_related()
+ .all()
+ .order_by("examquestiondetail__sort_order")
+ )
question_number = len(questions)
cid_candidates = exam.valid_cid_users.count()
users_candidates = exam.valid_user_users.count()
- #question_orders = [q.examquestiondetail.sort_order for q in questions]
+ # question_orders = [q.examquestiondetail.sort_order for q in questions]
# Exam order can be missing when, this can be checked with
# exam.exam_questions.select_related().all().order_by("examquestiondetail__sort_order").values_list("examquestiondetail__sort_order")
# TODO decide if / how this should be flagged to a user (updating teh exam order will fix)
-
return render(
request,
"{}/exam_overview.html".format(self.app_name),
@@ -1049,10 +1083,12 @@ class ExamViews(View, LoginRequiredMixin):
if not request.user.is_superuser:
raise PermissionDenied
- cid_users = exam.valid_cid_users.all()#.order_by("cid")#.prefetch_related("*")
+ cid_users = (
+ exam.valid_cid_users.all()
+ ) # .order_by("cid")#.prefetch_related("*")
cid_user_count = len(cid_users)
- user_users = exam.valid_user_users.all()#.order_by("username")
+ user_users = exam.valid_user_users.all() # .order_by("username")
user_user_count = len(user_users)
user_exam_data = exam.cid_users.all().prefetch_related("cid_user", "user_user")
@@ -1072,7 +1108,7 @@ class ExamViews(View, LoginRequiredMixin):
return render(request, "exam_cids.html", context)
- #def exam_groups_edit(self, request, exam_id):
+ # def exam_groups_edit(self, request, exam_id):
# exam = get_object_or_404(self.Exam, pk=exam_id)
# if not request.user.groups.filter(name="cid_user_manager").exists():
@@ -1080,8 +1116,7 @@ class ExamViews(View, LoginRequiredMixin):
# if request.user not in exam.author.all():
# raise PermissionDenied
-
- #
+ #
# context = {
# "exam": exam,
@@ -1208,7 +1243,9 @@ class ExamViews(View, LoginRequiredMixin):
if cid is not None:
statuses = exam.exam_user_status.filter(cid_user_exam__cid_user__cid=cid)
elif user_id is not None:
- statuses = exam.exam_user_status.filter(cid_user_exam__user_user__id=user_id)
+ statuses = exam.exam_user_status.filter(
+ cid_user_exam__user_user__id=user_id
+ )
else:
statuses = exam.exam_user_status.all()
@@ -1332,7 +1369,8 @@ class ExamViews(View, LoginRequiredMixin):
)
case "longs":
question_detail = LongsExamQuestionDetail.objects.get(
- question=question, exam=exam)
+ question=question, exam=exam
+ )
case "physics":
question_detail = PhysicsExamQuestionDetail.objects.get(
question=question, exam=exam
@@ -1341,7 +1379,7 @@ class ExamViews(View, LoginRequiredMixin):
question_detail = SbasExamQuestionDetail.objects.get(
question=question, exam=exam
)
- case _:
+ case _:
data = {"status": "error"}
return JsonResponse(data, status=200)
question_detail.sort_order = n
@@ -1408,7 +1446,7 @@ class ExamViews(View, LoginRequiredMixin):
queryset=self.Answer.objects.filter(
proposed=False, status=self.Answer.MarkOptions.CORRECT
),
- to_attr="prefetched_primary_answer"
+ to_attr="prefetched_primary_answer",
# queryset=self.Answer.objects.filter(),
),
# Prefetch(
@@ -1490,15 +1528,17 @@ class ExamViews(View, LoginRequiredMixin):
)
@method_decorator(login_required)
- def exam_question_user_answer(self, request, pk: int, sk: int, c_or_u: str, user_or_cid: str):
+ def exam_question_user_answer(
+ self, request, pk: int, sk: int, c_or_u: str, user_or_cid: str
+ ):
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)
if c_or_u == "u":
- user = get_object_or_404(User,pk=user_or_cid)
+ user = get_object_or_404(User, pk=user_or_cid)
answer = exam.get_question_user_user_answer(sk, user)
elif c_or_u == "c":
- #cid_user = CidUser.objects.filter(cid=user_or_cid)
- answer =exam.get_question_cid_user_answer(sk, user_or_cid)
+ # cid_user = CidUser.objects.filter(cid=user_or_cid)
+ answer = exam.get_question_cid_user_answer(sk, user_or_cid)
else:
raise Http404
@@ -1510,7 +1550,7 @@ class ExamViews(View, LoginRequiredMixin):
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)
cid_user = CidUser.objects.filter(cid=cid)
- answer =exam.get_question_cid_user_answer(sk, cid_user)
+ answer = exam.get_question_cid_user_answer(sk, cid_user)
print(answer)
@@ -1522,12 +1562,12 @@ class ExamViews(View, LoginRequiredMixin):
if not self.check_user_access(request.user, pk, marker=True):
raise PermissionDenied
- #question = exam.get_questions()[sk]
+ # question = exam.get_questions()[sk]
question = exam.get_question_by_index(sk)
exam_length = len(exam.exam_questions.all())
- #pos = exam.get_question_index(question) + 1
+ # pos = exam.get_question_index(question) + 1
pos = sk + 1
previous = -1
@@ -1801,7 +1841,9 @@ class ExamViews(View, LoginRequiredMixin):
cid_user=c, start_time=t
)
exam.exam_user_status.create(
- cid_user_exam=cid_user_exam, status="submitted", extra="manual submission"
+ cid_user_exam=cid_user_exam,
+ status="submitted",
+ extra="manual submission",
)
cid_user_exam.end_time = timezone.now()
@@ -1831,7 +1873,9 @@ class ExamViews(View, LoginRequiredMixin):
user_id = None
else:
user_id = request.user.pk
- if exam.exam_mode and not exam.check_cid_user(cid, passcode, request.user, user_id):
+ if exam.exam_mode and not exam.check_cid_user(
+ cid, passcode, request.user, user_id
+ ):
raise Http404("No available exam")
# exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
@@ -2010,7 +2054,7 @@ class ExamViews(View, LoginRequiredMixin):
if not exam.check_cid_user(cid, passcode, request.user):
if not self.check_user_access(request.user, pk):
- #raise PermissionDenied
+ # raise PermissionDenied
raise Http404("Error accessing exam")
if user is not None:
@@ -2060,7 +2104,6 @@ class ExamViews(View, LoginRequiredMixin):
if self.app_name == "longs":
feedback = user_answer.candidate_feedback
-
match self.app_name:
case "sbas":
correct_answer = q.get_correct_answer()
@@ -2084,7 +2127,9 @@ class ExamViews(View, LoginRequiredMixin):
answers_marks.append(answer_score)
print(q.get_questions(), ans, answer_score, correct_answer)
- merged_ans = zip(q.get_questions(), ans, answer_score, correct_answer)
+ merged_ans = zip(
+ q.get_questions(), ans, answer_score, correct_answer
+ )
answers_and_marks.append((q, merged_ans))
case _:
correct_answer = q.get_primary_answer()
@@ -2096,7 +2141,9 @@ class ExamViews(View, LoginRequiredMixin):
answers_marks.append(answer_score)
if self.app_name == "longs":
- answers_and_marks.append((ans, answer_score, correct_answer, feedback))
+ answers_and_marks.append(
+ (ans, answer_score, correct_answer, feedback)
+ )
else:
answers_and_marks.append((ans, answer_score, correct_answer))
@@ -2110,7 +2157,9 @@ class ExamViews(View, LoginRequiredMixin):
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)
+ total_score = "{} ({} unmarked)".format(
+ total_score, unmarked_number
+ )
else:
total_score = sum(answers_marks)
@@ -2172,7 +2221,7 @@ class ExamViews(View, LoginRequiredMixin):
# user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
- #user_answers = defaultdict(list)
+ # user_answers = defaultdict(list)
# user_names = {}
# cid_passcodes = {}
@@ -2246,7 +2295,7 @@ class ExamViews(View, LoginRequiredMixin):
if answer_score == "unmarked":
index = exam.get_question_index(q)
unmarked.add(index)
- #user_answers[cid].append(ans)
+ # user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_score)
if self.app_name == "rapids":
@@ -2259,9 +2308,6 @@ class ExamViews(View, LoginRequiredMixin):
zipped_ans_scores = zip(ans, answer_score)
by_question[q][cid] = zipped_ans_scores
-
-
-
user_scores = {}
user_scores_normalised = {}
user_answer_count = {}
@@ -2274,11 +2320,10 @@ class ExamViews(View, LoginRequiredMixin):
# If either question or user do not exist we give a default
# not answered == score of 3
if user not in by_question[question]:
- #print("NOT in")
+ # print("NOT in")
by_question[question][user] = ("Not answered", 3.0)
user_answers_marks[user].append(3.0)
-
if self.app_name in ("rapids", "anatomy", "sbas", "longs"):
user_scores[user] = sum(
[i for i in user_answers_marks[user] if i != "unmarked"]
@@ -2313,7 +2358,7 @@ class ExamViews(View, LoginRequiredMixin):
mode = 0
fig_html = ""
else:
- #Exclude very low scores from statistics
+ # Exclude very low scores from statistics
lower_score_bound = max_score / 5
bounded_scores = [i for i in user_scores_list if i > lower_score_bound]
if bounded_scores:
@@ -2400,9 +2445,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,
@@ -2419,9 +2464,7 @@ class ExamViews(View, LoginRequiredMixin):
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
- template_variables = {
- "exam": exam
- }
+ template_variables = {"exam": exam}
return render(
request,
@@ -2476,7 +2519,7 @@ class GenericViewBase:
@method_decorator(login_required)
def question_detail(self, request, pk):
- question = get_object_or_404(self.question_object, pk=pk)
+ question: QuestionBase = get_object_or_404(self.question_object, pk=pk)
if not question.open_access:
if (
@@ -2499,7 +2542,12 @@ class GenericViewBase:
return render(
request,
f"{self.app_name}/question_detail.html",
- {"question": question, "view_feedback": view_feedback, "remote_url": settings.REMOTE_URL},
+ {
+ "question": question,
+ "view_feedback": view_feedback,
+ "remote_url": settings.REMOTE_URL,
+ "can_edit": question.can_edit(request.user),
+ },
)
# TODO: improve permissions on these
@@ -2514,15 +2562,22 @@ class GenericViewBase:
question = get_object_or_404(self.question_object, pk=pk)
if answer_compare is None:
- answers = self.cid_user_answer_object.objects.filter(question__id=question.pk).prefetch_related("question", "exam", "user")
+ answers = self.cid_user_answer_object.objects.filter(
+ question__id=question.pk
+ ).prefetch_related("question", "exam", "user")
else:
- answers = self.cid_user_answer_object.objects.filter(question__id=question.pk, answer_compare=answer_compare).prefetch_related("question", "exam", "user")
-
+ answers = self.cid_user_answer_object.objects.filter(
+ question__id=question.pk, answer_compare=answer_compare
+ ).prefetch_related("question", "exam", "user")
return render(
request,
f"{self.app_name}/question_user_answers.html",
- {"question": question, "answers": answers, "answer_compare": answer_compare},
+ {
+ "question": question,
+ "answers": answers,
+ "answer_compare": answer_compare,
+ },
)
@method_decorator(login_required)
@@ -2548,11 +2603,10 @@ class GenericViewBase:
)
-class ExamCloneMixin():
+class ExamCloneMixin:
def get_template_names(self) -> list[str]:
return "exam_clone_form.html"
- #return super().get_template_names()
-
+ # return super().get_template_names()
def get_initial(self):
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
@@ -2617,6 +2671,8 @@ class ExaminationAutocomplete(autocomplete.Select2QuerySetView):
return Examination.objects.none()
return qs
+
+
class SupervisorAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# TODO: we should probably filter this to only
@@ -2746,6 +2802,7 @@ def cid_group_view_all(request):
{"groups": groups, "view_all": True},
)
+
@user_is_cid_user_manager
def user_group_add_candidates_to_exams(request, group_id):
if request.htmx:
@@ -2764,13 +2821,12 @@ def user_group_add_candidates_to_exams(request, group_id):
else:
exam.valid_user_users.add(*group.users.all())
return HttpResponse(f"Candidates added")
- #exam.save()
-
-
+ # exam.save()
raise PermissionDenied() # or Http404
pass
+
@user_is_cid_user_manager
def user_group_view_detail(request, group_id):
group = get_object_or_404(UserUserGroup, pk=group_id)
@@ -2817,6 +2873,7 @@ def get_user_selection_from_request(request):
user_models = User.objects.filter(id__in=selected_users)
return user_models
+
@user_is_cid_user_manager
def user_not_trainee(request, user_id):
user = get_object_or_404(User, pk=user_id)
@@ -2824,7 +2881,10 @@ def user_not_trainee(request, user_id):
user.userprofile.peninsula_trainee = False
user.userprofile.save()
- return HttpResponse(f"{user.username} is no longer a trainee", content_type="text/plain")
+ return HttpResponse(
+ f"{user.username} is no longer a trainee", content_type="text/plain"
+ )
+
@user_is_cid_user_manager
def users_bulk_edit(request):
@@ -2863,8 +2923,7 @@ def users_bulk_edit(request):
# TODO: work out how to actually use hyperscript
html = (
html
- +
- f"""