diff --git a/anatomy/static/css/anatomy.css b/anatomy/static/css/anatomy.css index ab2f6c02..a5c96ae7 100644 --- a/anatomy/static/css/anatomy.css +++ b/anatomy/static/css/anatomy.css @@ -458,4 +458,15 @@ td.user-answer-score-2::after { .temp-thumb { float:right; + } + + .long-answer pre { + white-space: pre-wrap; + font: inherit; + color: inherit; + } + + .long-answer .answer-heading { + font-style: italic; + font-weight: bold; } \ No newline at end of file diff --git a/longs/forms.py b/longs/forms.py index bcb5ff52..2eeff4b2 100755 --- a/longs/forms.py +++ b/longs/forms.py @@ -34,11 +34,10 @@ class LongAnswerForm(ModelForm): fields = ("answer_observations","answer_interpretation","answer_principle_diagnosis","answer_differential_diagnosis","answer_management",) -class MarkLongQuestionForm(Form): - # correct = forms.CharField(required=False) - # half_correct = forms.CharField(required=False) - # incorrect = forms.CharField(required=False) - marked_answers = CharField(required=False) +class MarkLongQuestionForm(ModelForm): + class Meta: + model = CidUserAnswer + fields = ["score"] class ExaminationForm(ModelForm): diff --git a/longs/migrations/0023_auto_20210216_1315.py b/longs/migrations/0023_auto_20210216_1315.py new file mode 100644 index 00000000..f2f241f2 --- /dev/null +++ b/longs/migrations/0023_auto_20210216_1315.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.3 on 2021-02-16 13:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('longs', '0022_long_mark_scheme'), + ] + + operations = [ + migrations.AlterField( + model_name='ciduseranswer', + name='score', + field=models.CharField(blank=True, choices=[('', 'Unmarked'), ('4', '4'), ('4.5', '4.5'), ('5', '5'), ('5.5', '5.5'), ('6', '6'), ('6.5', '6.5'), ('7', '7'), ('7.5', '7.5'), ('8', '8')], default='', max_length=3), + ), + ] diff --git a/longs/models.py b/longs/models.py index ec9d35af..c8cc5870 100644 --- a/longs/models.py +++ b/longs/models.py @@ -151,6 +151,7 @@ class Long(models.Model): answers = self.cid_user_answers.all() return [ans for ans in answers if not ans.is_marked()] + # def GetNonFeedbackQuestionImages(self): # return self.GetImages() @@ -422,6 +423,9 @@ class CidUserAnswer(models.Model): created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) + class Meta: + ordering = ['cid'] + def __str__(self): try: exam = self.exam diff --git a/longs/templates/longs/long_display_block.html b/longs/templates/longs/long_display_block.html index 0030720a..12f16caa 100755 --- a/longs/templates/longs/long_display_block.html +++ b/longs/templates/longs/long_display_block.html @@ -32,6 +32,8 @@

Principle Diagnosis: {{ question.model_principle_diagnosis }}

Differential Diagnosis: {{ question.model_differential_diagnosis }}

Managment: {{ question.model_management }}

+ +

Mark Scheme: {{ question.mark_scheme }}

Exam(s): {{question.GetExams}}

diff --git a/longs/templates/longs/mark.html b/longs/templates/longs/mark.html index 1c9c73c0..06a7e0b4 100644 --- a/longs/templates/longs/mark.html +++ b/longs/templates/longs/mark.html @@ -1,67 +1,43 @@ {% extends 'longs/exams.html' %} {% block content %} -

Marking question {{question_details.current}} of {{question_details.total}}

+View Edit Admin Edit -{% if question.normal %} -

This question is normal

-Answers will be automatically marked. -{% else %} -

This question is abnormal

-Answers marked as normal will be automatically marked. -{% endif %} -
-
-
-
{% csrf_token %} - {% if not question.normal %} - Click each answer to toggle through marks awarded (as per colour) -
- Unmarked: -
    - {% for answer in user_answers %} -
  • -
    {{ answer }}
    -
  • - {% endfor %} -
- Marked: -
    - {% for answer in correct_answers %} -
  • -
    {{ answer }}
    -
  • - {% endfor %} - {% for answer in half_mark_answers %} -
  • -
    {{ answer }}
    -
  • - {% endfor %} - {% for answer in incorrect_answers %} -
  • -
    {{ answer }}
    -
  • - {% endfor %} -
-
Key: 2 Marks, 1 - Mark, 0 Marks
-
- {% endif %} - {% if question_details.current > 1 %} - - {% endif %} - - {% if question_details.current >= question_details.total %} +

Marking question {{question_details.current}} of {{question_details.total}}

+
+ + {% if not user_answers %} + No answers to mark. + + {% else %} + Answers: +
    + {% for answer in user_answers %} +
  • + {{answer.cid}}: + {% if answer.is_marked %} + Score {{answer.get_answer_score}} {% else %} - - + Unmarked {% endif %} - - {{ form.as_p }} - -
  • + + + {% endfor %} +
+ {% endif %} +
+ {% if question_details.current > 1 %} + Previous + {% endif %} + {% if question_details.current >= question_details.total %} + {% else %} + Next + {% endif %} +
+ {% endblock %} \ No newline at end of file diff --git a/longs/templates/longs/mark_answer.html b/longs/templates/longs/mark_answer.html new file mode 100644 index 00000000..461e1d8f --- /dev/null +++ b/longs/templates/longs/mark_answer.html @@ -0,0 +1,45 @@ +{% extends 'longs/exams.html' %} + +{% block content %} +View +Edit Admin + Edit +

Marking question {{question_details.current}} of {{question_details.total}}

+
+
+ User observations: +
{{answer.answer_observations}}
+
+
+ User interpretation: +
{{answer.answer_interpretation}}
+
+
+ User principle diagnosis: +
{{answer.answer_principle_diagnosis}}
+
+
+ User differential diagnosis: +
{{answer.answer_differential_diagnosis}}
+
+
+ User management: +
{{answer.answer_management}}
+
+ +
+
+
{% csrf_token %} + {{ form.as_p }} + + {% if next_unmarked_id %} + + + {% else %} + (no more answers for this question to mark) + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/longs/templates/longs/long_detail.html b/longs/templates/longs/question_detail.html similarity index 100% rename from longs/templates/longs/long_detail.html rename to longs/templates/longs/question_detail.html diff --git a/longs/urls.py b/longs/urls.py index fc8379a9..b3d488ab 100755 --- a/longs/urls.py +++ b/longs/urls.py @@ -19,6 +19,7 @@ urlpatterns = [ # path("verified/", views.verified, name="verified"), # path("all_questions/", views.all_questions, name="all_questions"), path("question//scrap", views.long_scrap, name="long_scrap"), + path("exam////mark", views.mark_answer, name="mark_answer"), path("exam///mark", views.mark, name="mark"), path("exam//mark", views.LongExamViews.mark_overview, name="mark_overview"), # path("exam///", views.exam_take, name="exam_take"), diff --git a/longs/views.py b/longs/views.py index 4f5b187a..c8f47210 100755 --- a/longs/views.py +++ b/longs/views.py @@ -5,7 +5,7 @@ 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.contrib.auth.mixins import LoginRequiredMixin @@ -91,7 +91,7 @@ def long_detail(request, pk): # logging.debug(long.long_notes.first()) # logging.debug(long.subspecialty.first().name.all()) - return render(request, "longs/long_detail.html", {"question": long}) + return render(request, "longs/question_detail.html", {"question": long}) @login_required def long_series_detail(request, pk): @@ -139,7 +139,7 @@ def long_split(request, pk): # logging.debug(long.long_notes.first()) # logging.debug(long.subspecialty.first().name.all()) - return render(request, "longs/long_detail.html", {"question": long}) + return render(request, "longs/question_detail.html", {"question": long}) @login_required @@ -484,7 +484,7 @@ def long_scrap(request, pk): long.scrapped = not long.scrapped long.save() - return HttpResponseRedirect(reverse("longs:long_detail", args=(pk,))) + return HttpResponseRedirect(reverse("longs:question_detail", args=(pk,))) # @login_required @@ -656,6 +656,70 @@ def loadJsonAnswer(answer): return True +@login_required +def mark_answer(request, pk, sk, tk): + exam = get_object_or_404(Exam, pk=pk) + + questions = exam.exam_questions.all() + + n = sk + + question_details = { + "total": len(questions), + "current": n + 1, + } + + try: + question = questions[sk] + except IndexError: + raise Http404("Exam question does not exist") + + try: + answer = question.cid_user_answers.get(cid=tk) + except ObjectDoesNotExist: + raise Http404("User answer does not exist") + + try: + unmarked = question.GetUnmarkedAnswers() + next_unmarked_id = unmarked[0].cid + except IndexError: + next_unmarked_id = False + + if request.method == "POST": + + form = MarkLongQuestionForm(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=pk, sk=sk, tk=next_unmarked_id) + + # Extract score from form and save it to the object + answer.score = form.cleaned_data["score"] + answer.save() + + if "next" in request.POST: + return redirect("longs:mark_answer", pk=pk, sk=sk, tk=next_unmarked_id) + #elif "previous" in request.POST: + # return redirect("longs:mark", pk=pk, sk=n - 1) + + else: + form = MarkLongQuestionForm(initial={'score': answer.score}) + + + return render( + request, + "longs/mark_answer.html", + { + "exam": exam, + "form": form, + "answer": answer, + "question": question, + "question_details": question_details, + "next_unmarked_id": next_unmarked_id, + }, + ) # @user_passes_test(user_is_admin, login_url="/accounts/login") @login_required @@ -676,129 +740,17 @@ def mark(request, pk, sk): except IndexError: raise Http404("Exam question does not exist") - answers_dict = {} - for ans in question.answers.all(): - answers_dict[ans.answer_compare] = ans - - marked_answers_set = set(answers_dict.keys()) - - # correct_answers = [i.answer.lower() for i in question.answers.all()] - # half_correct_answers = [ - # i.answer.lower() for i in question.half_mark_answers.all() - # ] - # incorrect_answers = [ - # i.answer.lower() for i in question.incorrect_answers.all() - # ] - - unmarked_user_answers = question.GetUnmarkedAnswers() - - if request.method == "POST": - - form = MarkLongQuestionForm(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("longs:mark", pk=pk, sk=n + 1) - - cd = form.cleaned_data - # correct = cd.get("correct") - # half_correct = cd.get("half_correct") - # incorrect = cd.get("incorrect") - marked_answers = json.loads(cd.get("marked_answers")) - - ## This is mildly dangerous as we delete all answers - # question.answers.all().delete() - # question.half_mark_answers.all().delete() - # question.incorrect_answers.all().delete() - - # This should probably use json (or something else...) - # ajax..... - for ans in marked_answers["correct"]: - 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 = Answer.MarkOptions.CORRECT - a.save() - - # for ans in half_correct.split("--//--"): - for ans in marked_answers["half-correct"]: - 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 = Answer.MarkOptions.HALF_MARK - a.save() - - for ans in marked_answers["incorrect"]: - 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 = Answer.MarkOptions.INCORRECT - a.save() - # answer = form.save(commit=False) - # answer.user = request.user - # answer.question = question - # answer.published_date = timezone.now() - # answer.save() - if "next" in request.POST: - return redirect("longs:mark", pk=pk, sk=n + 1) - elif "previous" in request.POST: - return redirect("longs:mark", pk=pk, sk=n - 1) - - # Reset user answers (relies on the functional javascript) - unmarked_user_answers = set() - else: - form = MarkLongQuestionForm() - - 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) + user_answers = question.cid_user_answers.all() return render( request, "longs/mark.html", { "exam": exam, - "form": form, "question": question, "question_details": question_details, - "user_answers": unmarked_user_answers, - "correct_answers": correct_answers, - "half_mark_answers": half_mark_answers, - "incorrect_answers": incorrect_answers, + "user_answers": user_answers, }, )