tidy up some longs related stuff

This commit is contained in:
Ross
2024-07-08 13:14:11 +01:00
parent bcfcd8da82
commit 19a9a27747
11 changed files with 140 additions and 44 deletions
+2 -2
View File
@@ -89,9 +89,9 @@ class AuthorOrCheckerRequiredMixin(object):
@register.filter
def get_item(dictionary, key):
def get_item(dictionary, key, default=None):
if dictionary is None:
return None
return default
return dictionary.get(key)
-1
View File
@@ -870,7 +870,6 @@ class ExamBase(ExamOrCollectionGenericBase):
abstract = True
def __str__(self):
print("TESTING")
if self.start_date and self.start_date is not None:
print(f"Start date: {self.start_date:%d %B %Y}")
return f"{self.name} ({self.start_date:%d %B %Y})"
+28 -7
View File
@@ -1860,8 +1860,6 @@ class ExamViews(View, LoginRequiredMixin):
return self.exam_question_json_unbased(request, pk, sk, cid, passcode)
def exam_question_json_unbased(self, request, pk, sk, cid=None, passcode=None):
print("TESTING")
print("**************************")
question = get_object_or_404(self.Question, pk=sk)
exam = get_object_or_404(self.Exam, pk=pk)
@@ -1951,7 +1949,7 @@ class ExamViews(View, LoginRequiredMixin):
).first()
# user_answer = cid_user_answers_q_map[q]
print("QUSETION", q)
feedback = None
if not user_answer or user_answer is None:
# skip if no answer
score, text = q.get_unanswered_mark_and_text()
@@ -1962,8 +1960,10 @@ class ExamViews(View, LoginRequiredMixin):
else:
ans = user_answer.get_answer()
answer_score = user_answer.get_answer_score()
print("ans", ans)
print("ans score", answer_score)
if self.app_name == "longs":
feedback = user_answer.candidate_feedback
match self.app_name:
case "sbas":
@@ -1998,6 +1998,10 @@ class ExamViews(View, LoginRequiredMixin):
answer_score = 0
answers.append(ans)
answers_marks.append(answer_score)
if self.app_name == "longs":
answers_and_marks.append((ans, answer_score, correct_answer, feedback))
else:
answers_and_marks.append((ans, answer_score, correct_answer))
print(answers_marks)
@@ -2072,7 +2076,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 = {}
@@ -2146,7 +2150,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":
@@ -2159,10 +2163,27 @@ 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 = {}
for user in user_answers_marks:
# For longs we have a default score of 3 (not 0) if unanswered
# so we need to check for those
if self.app_name == "longs":
for question in questions:
if question not in by_question:
continue
if user not in by_question[question]:
#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"]
+5 -2
View File
@@ -51,13 +51,16 @@ class LongAnswerForm(ModelForm):
class MarkLongQuestionSingleForm(ModelForm):
class Meta:
model = UserAnswer
fields = ["score"]
fields = ["score", "candidate_feedback"]
widgets = {
"candidate_feedback": Textarea(attrs={"rows": 3}),
}
class MarkLongQuestionDoubleForm(ModelForm):
class Meta:
model = AnswerMarks
fields = ["score", "mark_reason", "candidate_feedback"]
fields = ["score", "mark_reason"]
class ExaminationForm(ModelForm):
@@ -0,0 +1,22 @@
# Generated by Django 5.0.2 on 2024-07-08 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0024_alter_exam_end_date_alter_exam_exam_results_emailed_and_more'),
]
operations = [
migrations.RemoveField(
model_name='answermarks',
name='candidate_feedback',
),
migrations.AddField(
model_name='useranswer',
name='candidate_feedback',
field=models.TextField(blank=True, help_text='Feedback for the candidate', null=True),
),
]
+9 -7
View File
@@ -328,12 +328,13 @@ class Long(QuestionBase):
return url
def get_unanswered_mark_and_text(self) -> tuple[int, tuple[str, str, str, str, str]]:
def get_unanswered_mark_and_text(self) -> tuple[int, str]:
"""
Long cases are receive a mark between 4 and 8
Therefore unmarked = 4
This has changed to a 3 if unanswered
Therefore unmarked = 3
"""
return (4, ("Not answered",) * 5)
return (3, ("Not answered",) * 5)
# def GetNonFeedbackQuestionImages(self):
# return self.get_images()
@@ -612,6 +613,7 @@ class UserAnswer(UserAnswerBase):
class ScoreOptions(models.TextChoices):
UNMARKED = "", _("Unmarked")
#UNANSWERED = "3", _("Unanswered")
FOUR = "4", _("4")
FOUR_HALF = "4.5", _("4.5")
FIVE = "5", _("5")
@@ -630,6 +632,10 @@ class UserAnswer(UserAnswerBase):
help_text="The final score for the candidate",
)
candidate_feedback = models.TextField(
null=True, blank=True, help_text="Feedback for the candidate"
)
# mark = models.ManyToManyField(
# "AnswerMarks", related_name="user_answer", blank=True
# )
@@ -752,10 +758,6 @@ class AnswerMarks(models.Model):
help_text="Reason for the given mark - not visible to candidates",
)
candidate_feedback = models.TextField(
null=True, blank=True, help_text="Feedback for the candidate"
)
marker = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="marker", on_delete=models.CASCADE
)
+2
View File
@@ -2,6 +2,8 @@
{% extends 'generic/exam_scores_base.html' %}
{% block table_answers %}
aoeu
eu
{% for question in questions %}
<tr>
<td><a href="{% url 'anatomy:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
+22 -5
View File
@@ -16,10 +16,14 @@
<div>
{% comment %} <h4>Answers</h4> {% endcomment %}
<ul class="score-answer-list">
{% for ans, score, correct_answer in answers_and_marks %}
<li class="user-answer-li" data-question-number="{{forloop.counter}}"><b>Question {{forloop.counter}}</b> <span class="view-question-link-longs"
{% for ans, score, correct_answer, feedback in answers_and_marks %}
<li class="user-answer-li
{% if ans.0 == 'Not answered' %}
not-answered
{% endif %}
" data-question-number="{{forloop.counter}}"><b>Question {{forloop.counter}}</b> <span class="view-question-link-longs"
data-qn={{forloop.counter0}}>View</span>
<ul>
<ul class="">
<li class="Observations" data-qidn="1">Observations</br>
<pre>{{ans.0}}</pre>
</li>
@@ -35,8 +39,14 @@
<li class="Management" data-qidn="5">Management</br>
<pre>{{ans.4}}</pre>
</li>
{% if exam.publish_results %}
<span class="answer-score">{{score}}</span>
{% if view_all_results or exam.publish_results %}
Question score: <span class="answer-score">{{score}}</span>
{% if feedback %}
<p>Question feedback: {{feedback}}</p>
{% endif %}
{% endif %}
</ul>
</li>
@@ -47,6 +57,13 @@
{% include 'user_scores_footer.html' %}
</div>
{% endblock %}
{% block css %}
<style>
.not-answered {
opacity: 0.5;
}
</style>
{% endblock css %}
{% block js %}
<script>
$(document).ready(function () {
+2 -1
View File
@@ -1,4 +1,5 @@
{% extends 'longs/exams.html' %}
{% load crispy_forms_tags %}
{% block content %}
<a href="{% url 'longs:exam_question_detail' exam.id question_details.current %}" title="View the Question">View</a>
@@ -111,7 +112,7 @@
<div class="marking">
<form method="POST" class="post-form">{% csrf_token %}
<input type="hidden" name="form_id" value="mark_form">
{{ form.as_p }}
{{ form|crispy }}
<button type="submit" name="save" class="save btn btn-default" title="Save answer">Save</button>
{% if next_unmarked_id %}
<button type="submit" name="next" class="save btn btn-default"
+2 -2
View File
@@ -601,7 +601,7 @@ def test_exams(db, client):
cid_exam_scores_soup.find("div", {"class": "score-overview"})
.find("span", {"id": "total-score"})
.string
== "4 (3 unmarked)"
== "3 (3 unmarked)"
)
# Mark the questions
@@ -636,7 +636,7 @@ def test_exams(db, client):
cid_exam_scores_soup.find("div", {"class": "score-overview"})
.find("span", {"id": "total-score"})
.string
== "28.0"
== "27.0"
)
generated_questions.pop(-1).delete()
+39 -10
View File
@@ -75,7 +75,15 @@ from django.forms.models import model_to_dict
from longs.forms import LongCreationDefaultForm
from longs.models import LongCreationDefault
from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, SeriesImagesZipViewBase
from generic.views import (
AuthorRequiredMixin,
ExamCloneMixin,
ExamCreateBase,
ExamDeleteBase,
ExamUpdateBase,
ExamViews,
SeriesImagesZipViewBase,
)
from reversion.views import RevisionMixin
import reversion
@@ -271,7 +279,10 @@ def long_clone(request, pk):
new_item.pk = None # autogen a new pk (item_id)
# new_item.name = "Copy of " + new_item.name #need to change uniques
if request.user not in new_item.get_author_objects() and not request.user.is_superuser:
if (
request.user not in new_item.get_author_objects()
and not request.user.is_superuser
):
raise PermissionDenied() # or Http404
form = LongForm(request.POST or None, instance=new_item)
@@ -413,7 +424,11 @@ class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
def get_context_data(self, **kwargs):
context = super(LongCreateBase, self).get_context_data(**kwargs)
queryset = LongSeries.objects.select_related("modality", "examination", "plane").prefetch_related("long").filter(pk=62)#.values()
queryset = (
LongSeries.objects.select_related("modality", "examination", "plane")
.prefetch_related("long")
.filter(pk=62)
) # .values()
print("TEST")
print(queryset)
# queryset = LongSeries.objects.all()
@@ -624,7 +639,6 @@ class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView):
filterset_class = LongSeriesFilter
@user_is_long_marker
@reversion.create_revision()
def mark_answer_override(request, exam_id, question_number, cid):
@@ -704,6 +718,7 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
# 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:
@@ -724,7 +739,12 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
# return redirect("longs:mark", pk=exam_id, sk=n - 1)
else:
form = MarkLongQuestionSingleForm(initial={"score": answer.score})
form = MarkLongQuestionSingleForm(
initial={
"score": answer.score,
"candidate_feedback": answer.candidate_feedback,
}
)
else:
if request.method == "POST":
@@ -794,7 +814,10 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
marks = set(answer.mark.values_list("score", flat=True))
if len(marks) > 1 or override:
discrepancy_form = MarkLongQuestionSingleForm(
initial={"score": answer.score}
initial={
"score": answer.score,
"candidate_feedback": answer.candidate_feedback,
}
)
return render(
@@ -838,9 +861,7 @@ def mark(request, exam_id, sk):
user_answers = question.cid_user_answers.filter(exam__id=exam_id)
unmarked_count = user_answers.filter(
score=UserAnswer.ScoreOptions.UNMARKED
).count()
unmarked_count = user_answers.filter(score=UserAnswer.ScoreOptions.UNMARKED).count()
if exam.double_mark:
marker_unmarked_count = question.get_unmarked_user_answer_count(
@@ -1125,6 +1146,7 @@ def long_series_order_upload_filename(request, pk):
GenericExamViews = ExamViews(Exam, Long, None, UserAnswer, "longs", "long")
class ExamCreate(ExamCreateBase):
model = Exam
form_class = ExamForm
@@ -1138,11 +1160,18 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
model = Exam
class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view"""
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
class ExamAuthorUpdate(
RevisionMixin,
CheckCanEditMixin,
LoginRequiredMixin,
AuthorRequiredMixin,
UpdateView,
):
model = Exam
form_class = ExamAuthorForm
template_name = "author_form.html"