shorts might now be functional?

This commit is contained in:
Ross
2025-04-28 12:15:31 +01:00
parent 405b17776f
commit 06e66ad52c
18 changed files with 901 additions and 234 deletions
+234 -129
View File
@@ -5,7 +5,11 @@ from django import forms
# from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.core.exceptions import (
ObjectDoesNotExist,
PermissionDenied,
ViewDoesNotExist,
)
from django.forms.models import model_to_dict
@@ -41,6 +45,7 @@ from atlas.models import Finding, Structure, Condition
from shorts.decorators import user_is_author_or_shorts_checker
from .models import (
AnswerMarks,
Exam,
Question,
QuestionFinding,
@@ -55,6 +60,8 @@ from .forms import (
ExamAuthorForm,
ExamGroupsForm,
ExamMarkerForm,
MarkQuestionDoubleForm,
MarkQuestionSingleForm,
QuestionFindingForm,
QuestionForm,
QuestionSampleAnswerForm,
@@ -342,11 +349,206 @@ def mark_all(request, exam_pk, sk):
def mark_review(request, exam_pk, sk):
return mark(request, exam_pk, sk, unmarked_exam_answers_only=False, review=True)
def mark_answer_override(request, exam_id, question_number, cid):
return mark_answer(request, exam_id, question_number, cid, override=True)
def mark_answer(request, exam_id, question_number, answer_id, override=False):
exam = get_object_or_404(Exam, pk=exam_id)
if not GenericExamViews.check_user_marker_access(request.user, exam_id):
raise PermissionDenied
questions = exam.get_questions()
n = question_number
question_details = {
"total": len(questions),
"current": n + 1,
}
try:
question = questions[question_number]
except IndexError:
raise Http404("Exam question does not exist")
try:
answer = question.cid_user_answers.get(pk=answer_id)
except ObjectDoesNotExist:
raise Http404("User answer does not exist")
answer_list = list(
question.cid_user_answers.filter(exam__id=exam_id).values_list("id", flat=True)
)
answer_list.sort()
previous_answer_id = False
next_answer_id = False
if len(answer_list) > 1:
if answer_list[0] == answer_id:
next_answer_id = answer_list[1]
elif answer_list[-1] == answer_id:
previous_answer_id = answer_list[-2]
else:
print(answer_list.index(answer_id))
next_answer_id = answer_list[answer_list.index(answer_id) + 1]
previous_answer_id = answer_list[answer_list.index(answer_id) - 1]
try:
if exam.double_mark:
unmarked = question.get_unmarked_user_answers(
exam_pk=exam.id, marker=request.user
)
else:
unmarked = question.get_unmarked_user_answers(exam_pk=exam.id)
next_unmarked_id = unmarked[0].id
if next_unmarked_id == answer_id:
next_unmarked_id = unmarked[1].id
except IndexError:
next_unmarked_id = False
# We use different forms if the exam should be single or double marked
if not exam.double_mark or (
request.method == "POST" and request.POST["form_id"] == "discrepancy_form"
):
if request.method == "POST":
form = MarkQuestionSingleForm(request.POST)
if form.is_valid():
# If skip button is pressed skip the question without marking
# Skip is problematic if we skip to the next unmarked answer
# if "skip" in request.POST:
# return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id)
# Extract score from form and save it to the object
answer.score = form.cleaned_data["score"]
answer.candidate_feedback = form.cleaned_data["candidate_feedback"]
answer.save()
if "next" in request.POST:
return redirect(
"shorts:mark_answer",
exam_id=exam_id,
question_number=question_number,
answer_id=next_unmarked_id,
)
if "save" in request.POST:
return redirect(
"shorts:mark_answer",
exam_id=exam_id,
question_number=question_number,
answer_id=answer_id,
)
# elif "previous" in request.POST:
# return redirect("longs:mark", pk=exam_id, sk=n - 1)
else:
form = MarkQuestionSingleForm(
initial={
"score": answer.score,
"candidate_feedback": answer.candidate_feedback,
}
)
else:
if request.method == "POST":
form = MarkQuestionDoubleForm(request.POST)
if form.is_valid():
# If skip button is pressed skip the question without marking
# Skip is problematic if we skip to the next unmarked answer
# if "skip" in request.POST:
# return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id)
mark_object = AnswerMarks.objects.get_or_create(
user_answer=answer, marker=request.user
)[0]
# Extract score from form and save it to the object
mark_object.score = form.cleaned_data["score"]
mark_object.mark_reason = form.cleaned_data["mark_reason"]
mark_object.candidate_feedback = form.cleaned_data["candidate_feedback"]
mark_object.marker = request.user
mark_object.save()
# Form is saved, check if we have two marks (and if they match)
if answer.mark.count() == 2:
# if they do automatically update teh scoer
marks = set(answer.mark.values_list("score", flat=True))
if len(marks) == 1:
answer.score = marks.pop()
else:
answer.score = ""
answer.save()
if "next" in request.POST:
return redirect(
"shorts:mark_answer",
exam_id=exam_id,
question_number=question_number,
answer_id=next_unmarked_id,
)
if "save" in request.POST:
return redirect(
"shorts:mark_answer",
exam_id=exam_id,
question_number=question_number,
answer_id=answer_id,
)
# elif "previous" in request.POST:
# return redirect("longs:mark", pk=exam_id, sk=n - 1)
else:
try:
mark_object = AnswerMarks.objects.get(
user_answer=answer, marker=request.user
)
form = MarkQuestionDoubleForm(
initial={
"score": mark_object.score,
"mark_reason": mark_object.mark_reason,
"candidate_feedback": mark_object.candidate_feedback,
}
)
except AnswerMarks.DoesNotExist:
form = MarkQuestionDoubleForm()
discrepancy_form = False
if answer.mark.count() == 2 or override:
# if they do automatically update teh scoer
marks = set(answer.mark.values_list("score", flat=True))
if len(marks) > 1 or override:
discrepancy_form = MarkQuestionSingleForm(
initial={
"score": answer.score,
"candidate_feedback": answer.candidate_feedback,
}
)
return render(
request,
"shorts/mark_answer.html",
{
"exam": exam,
"form": form,
"discrepancy_form": discrepancy_form,
"answer": answer,
"question": question,
"question_details": question_details,
"next_unmarked_id": next_unmarked_id,
"unmarked": unmarked,
"previous_answer_id": previous_answer_id,
"next_answer_id": next_answer_id,
"answer_id": answer_id,
},
)
# @user_passes_test(user_is_admin, login_url="/accounts/login")
@login_required
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
return HttpResponse("Not implemented")
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
exam = get_object_or_404(Exam, pk=exam_pk)
if not GenericExamViews.check_user_marker_access(request.user, exam_pk):
@@ -355,12 +557,6 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
mark_url = "shorts:mark"
if review:
mark_url = "shorts:mark_review"
elif not unmarked_exam_answers_only:
mark_url = "shorts:mark_all"
questions = exam.get_questions()
n = sk
@@ -375,129 +571,38 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
except IndexError:
raise Http404("Exam question does not exist")
if request.method == "POST":
user_answers = question.cid_user_answers.filter(exam__id=exam_pk)
form = MarkQuestionForm(request.POST)
# *******************************
# TODO: convert to JSON request
# *******************************
if form.is_valid():
# If skip button is pressed skip the question without marking
if "skip" in request.POST:
return redirect(mark_url, exam_pk=exam_pk, sk=n + 1)
unmarked_count = user_answers.filter(score__isnull=True).count()
cd = form.cleaned_data
marked_answers = json.loads(cd.get("marked_answers"))
# This should probably use json (or something else...)
# ajax.....
to_run = (
("correct", Answer.MarkOptions.CORRECT),
("half-correct", Answer.MarkOptions.HALF_MARK),
("incorrect", Answer.MarkOptions.INCORRECT),
)
for status_string, status in to_run:
for ans in marked_answers[status_string]:
ans = ans.strip()
if ans == "":
continue
a = Answer.objects.filter(
answer__iexact=ans, question_id=question.pk
).first()
if a is None:
a = Answer()
a.question_id = question.pk
a.answer = ans
a.status = status
a.save()
if "next" in request.POST:
return redirect(mark_url, exam_pk=exam_pk, sk=n + 1)
elif "previous" in request.POST:
return redirect(mark_url, exam_pk=exam_pk, sk=n - 1)
# Reset user answers (relies on the functional javascript)
unmarked_user_answers = set()
if exam.double_mark:
marker_unmarked_count = question.get_unmarked_user_answer_count(
exam.pk, marker=request.user
)
return render(
request,
"shorts/mark_question_double_overview.html",
{
"exam": exam,
"question": question,
"question_details": question_details,
"user_answers": user_answers,
"unmarked_count": unmarked_count,
"marker_unmarked_count": marker_unmarked_count,
},
)
else:
form = MarkQuestionForm()
# answers_dict = {}
# for ans in question.answers.all():
# answers_dict[ans.answer_compare] = ans
correct_answers = []
half_mark_answers = []
incorrect_answers = []
review_user_answers = []
unmarked_answers_bool = False
unmarked_user_answers = []
# this could be improved
if question.normal:
incorrect_answers = question.get_user_answers(exam_pk=exam_pk)
if "" in incorrect_answers:
incorrect_answers.remove("")
if "normal" in incorrect_answers:
incorrect_answers.remove("normal")
else:
if review:
unmarked_user_answers = question.get_user_answers(
exam_pk=exam_pk, include_normal=False
)
elif unmarked_exam_answers_only:
unmarked_user_answers = question.get_unmarked_user_answers(exam_pk=exam_pk)
else:
unmarked_user_answers = question.get_unmarked_user_answers()
if not unmarked_exam_answers_only:
correct_answers = question.answers.filter(status=Answer.MarkOptions.CORRECT)
half_mark_answers = question.answers.filter(
status=Answer.MarkOptions.HALF_MARK
)
incorrect_answers = question.answers.filter(
status=Answer.MarkOptions.INCORRECT
)
if review:
for ans in unmarked_user_answers:
# duplicated from user answer model....
marked_ans = question.answers.filter(answer_compare__iexact=ans).first()
mark = "unmarked"
if marked_ans is not None:
if marked_ans.status == Answer.MarkOptions.CORRECT:
mark = "correct"
elif marked_ans.status == Answer.MarkOptions.HALF_MARK:
mark = "half-correct"
elif marked_ans.status == Answer.MarkOptions.INCORRECT:
mark = "incorrect"
if mark == "unmarked":
unmarked_answers_bool = True
review_user_answers.append((ans, mark))
return render(
request,
"shorts/mark.html",
{
"exam": exam,
"form": form,
"review": review,
"question": question,
"question_number": n,
"question_details": question_details,
"unmarked_answers_bool": unmarked_answers_bool,
"user_answers": unmarked_user_answers,
"review_user_answers": review_user_answers,
"correct_answers": correct_answers,
"half_mark_answers": half_mark_answers,
"incorrect_answers": incorrect_answers,
"unmarked_exam_answers_only": unmarked_exam_answers_only,
},
)
return render(
request,
"shorts/mark_question_single_overview.html",
{
"exam": exam,
"question": question,
"question_details": question_details,
"user_answers": user_answers,
"unmarked_count": unmarked_count,
},
)
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):