tidy up some longs related stuff
This commit is contained in:
+5
-2
@@ -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
@@ -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,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>
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
+45
-16
@@ -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,10 +424,14 @@ 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()
|
||||
# queryset = LongSeries.objects.all()
|
||||
if self.request.POST:
|
||||
context["series_formset"] = SeriesFormSet(
|
||||
self.request.POST, self.request.FILES, queryset=queryset
|
||||
@@ -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(
|
||||
@@ -872,9 +893,9 @@ def mark(request, exam_id, sk):
|
||||
)
|
||||
|
||||
|
||||
#@login_required
|
||||
#@user_is_long_marker
|
||||
#def exam_scores_all(request, pk):
|
||||
# @login_required
|
||||
# @user_is_long_marker
|
||||
# def exam_scores_all(request, pk):
|
||||
# exam = get_object_or_404(Exam, pk=pk)
|
||||
#
|
||||
# questions = exam.exam_questions.all()
|
||||
@@ -981,7 +1002,7 @@ def mark(request, exam_id, sk):
|
||||
# )
|
||||
|
||||
|
||||
#def exam_scores_cid_user(request, pk, cid, passcode):
|
||||
# def exam_scores_cid_user(request, pk, cid, passcode):
|
||||
# exam = get_object_or_404(Exam, pk=pk)
|
||||
#
|
||||
# # TODO:Need some kind of test for cid
|
||||
@@ -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,17 +1160,24 @@ 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"
|
||||
|
||||
|
||||
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
# queryset = Exam.objects.all().order_by("name")
|
||||
# serializer_class = ExamSerializer
|
||||
|
||||
|
||||
Reference in New Issue
Block a user