From ee9b942893fe422f9f8ad823cadd75b6b99fb0e7 Mon Sep 17 00:00:00 2001 From: Ross Date: Sat, 21 May 2022 12:21:52 +0100 Subject: [PATCH] . --- anatomy/models.py | 2 +- anatomy/views.py | 78 +------------------------ atlas/models.py | 2 +- atlas/views.py | 2 +- generic/views.py | 108 ++++++++++++++++++++++++++++++---- longs/models.py | 2 +- longs/views.py | 71 +---------------------- physics/models.py | 2 +- physics/views.py | 112 ++++++++++++++++++------------------ rapids/models.py | 2 +- rapids/tests/test_models.py | 32 ++++++++++- rapids/views.py | 57 +----------------- sbas/models.py | 2 +- sbas/views.py | 6 +- 14 files changed, 194 insertions(+), 284 deletions(-) diff --git a/anatomy/models.py b/anatomy/models.py index 3448d031..8515252a 100644 --- a/anatomy/models.py +++ b/anatomy/models.py @@ -9,7 +9,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from sortedm2m.fields import SortedManyToManyField diff --git a/anatomy/views.py b/anatomy/views.py index 7d2bad65..250ec6f9 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -221,82 +221,6 @@ def answer_question(request, pk): # return JsonResponse(data) -def loadJsonAnswer(answer): - # Backend eid is just the object pk number - # in the frontend this is appended with the type - # e.g. anatomy/1 - - # TODO Update to only allow valid Candidates - eid = int(answer["eid"].split("/")[1]) - cid = int(answer["cid"]) - # As access is not restricted make sure the data appears valid - if (not isinstance(cid, int)) or ( - not isinstance(eid, int) or (not isinstance(answer["ans"], str)) - ): - return False, JsonResponse( - {"success": False, "error": "cid or eid or answers not defined"} - ) - - # The model should catch invalid data but this should be less intensive - max_int = 999999999999999999999 - if cid > max_int or eid > max_int: - return False, JsonResponse({"success": False, "error": "invalid cid"}) - - exam = get_object_or_404(Exam, pk=eid) - - - if not exam.check_cid_user(cid, answer["passcode"]): - return False, JsonResponse( - {"success": False, "error": "invalid cid / passcode"} - ) - - exiting_answers = CidUserAnswer.objects.filter( - question__id=answer["qid"], exam__id=eid, cid=cid - ) - - if not exiting_answers: - ans = CidUserAnswer(answer=answer["ans"], cid=cid) - ans.question_id = answer["qid"] - ans.exam_id = eid - ans.score = Answer.MarkOptions.UNMARKED - - ans.full_clean() - - ans.save() - else: - # Update an existing answer - # should never be more than one (famous last words) - ans = exiting_answers[0] - ans.answer = answer["ans"] - ans.score = Answer.MarkOptions.UNMARKED - ans.full_clean() - ans.save() - - return True, None - - -# @csrf_exempt -# def postExamAnswers(request): -# if request.is_ajax and request.method == "POST": -# -# n = 0 -# # horrible but it works -# #for k in request.POST.dict(): -# -# for answer in json.loads(request.POST.get("answers")): -# ret = loadJsonAnswer(answer) -# -# if ret is not True: -# return ret -# n = n + 1 -# -# # print(UserAnswer.objects.filter(exam__id=q["eid"])) -# # print(request.urlencode()) -# -# return JsonResponse({"success": True, "question_count": n}) -# return JsonResponse({"success": False, "error": "Invalid data"}) -# -# # return render(request, "anatomy/exam.html", {"exam" : exam, "question" : question}) @login_required @@ -1006,7 +930,7 @@ def question_save_annotation(request, pk): GenericExamViews = ExamViews( - Exam, AnatomyQuestion, Answer, CidUserAnswer, "anatomy", "anatomy", loadJsonAnswer + Exam, AnatomyQuestion, Answer, CidUserAnswer, "anatomy", "anatomy" ) diff --git a/atlas/models.py b/atlas/models.py index 062b9247..c7bf595a 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -21,7 +21,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.utils.html import mark_safe from django.core.exceptions import ValidationError diff --git a/atlas/views.py b/atlas/views.py index beb681de..13846df4 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -1556,5 +1556,5 @@ def collection_scores_cid(request, pk): ) GenericExamViews = ExamViews( - CaseCollection, Case, None, CidReportAnswer, "atlas", "casecollection", None + CaseCollection, Case, None, CidReportAnswer, "atlas", "casecollection" ) \ No newline at end of file diff --git a/generic/views.py b/generic/views.py index ff8bc420..0988e9e9 100644 --- a/generic/views.py +++ b/generic/views.py @@ -271,7 +271,6 @@ class ExamViews(View, LoginRequiredMixin): cid_user_answer, app, question_type, - loadJsonAnswer, ): self.Exam = exam self.Question = question @@ -279,7 +278,6 @@ class ExamViews(View, LoginRequiredMixin): self.CidUserAnswer = cid_user_answer self.app_name = app self.question_type = question_type - self.loadJsonAnswer = loadJsonAnswer # THis may be better than check_user_access below # group_map = {"rapids" : "rapid_checker", "anatomy" : "anatomy_checker", "longs":"long_checker"} @@ -1069,21 +1067,107 @@ class ExamViews(View, LoginRequiredMixin): {"success": False, "error": "cid or eid or answers not defined"} ) + exam = get_object_or_404(self.Exam, pk=eid) + + if not exam.check_cid_user(cid, answer["passcode"], user_id=uid): + return JsonResponse( + {"success": False, "error": "invalid cid / passcode"} + ) + + if uid: + existing_answers = self.CidUserAnswer.objects.filter( + question__id=answer["qid"], exam__id=eid, user__id=uid + ) + else: + existing_answers = self.CidUserAnswer.objects.filter( + question__id=answer["qid"], exam__id=eid, cid=cid + ) + posted_answer = answer["ans"] + + match self.app_name: + case "rapids": + + # Normal answers are just posted with the answer "Normal" + normal = False + if posted_answer == "Normal": + normal = True + + if not existing_answers: + if uid: + ans = self.CidUserAnswer(answer=posted_answer, normal=normal, user=User.objects.get(id=uid)) + else: + ans = self.CidUserAnswer(answer=posted_answer, normal=normal, cid=cid) + + ans.question_id = answer["qid"] + ans.exam_id = eid + else: + # Update an existing answer + # should never be more than one (famous last words) + ans = existing_answers[0] + if answer["qidn"] == "1": + ans.normal = normal + ans.answer = "" + elif answer["qidn"] == "2" and not ans.normal: + ans.answer = posted_answer + case "anatomy": + if not existing_answers: + if uid: + ans = self.CidUserAnswer(answer=posted_answer, uid=User.objects.get(id=uid)) + else: + ans = self.CidUserAnswer(answer=posted_answer, cid=cid) + + + ans.question_id = answer["qid"] + ans.exam_id = eid + ans.score = self.Answer.MarkOptions.UNMARKED + else: + # Update an existing answer + # should never be more than one (famous last words) + ans = existing_answers[0] + ans.answer = posted_answer + ans.score = self.Answer.MarkOptions.UNMARKED + case "longs": + # Long cases seperate sections by qidn + qidn = answer["qidn"] + + # If the user answer does not exist + if not existing_answers: + if uid: + ans = self.CidUserAnswer(user=User.objects.get(id=uid)) + else: + ans = self.CidUserAnswer(cid=cid) + ans.question_id = answer["qid"] + ans.exam_id = eid + + # If the answer already exists or we have started populating it + else: + # Update an existing answer + # should never be more than one (famous last words) + ans = existing_answers[0] + + if qidn == "1": + ans.answer_observations = posted_answer + elif qidn == "2": + ans.answer_interpretation = posted_answer + elif qidn == "3": + ans.answer_principle_diagnosis = posted_answer + elif qidn == "4": + ans.answer_differential_diagnosis = posted_answer + elif qidn == "5": + ans.answer_management = posted_answer + + # Mark as unmarked + ans.score = ans.ScoreOptions.UNMARKED + + ans.full_clean() + ans.save() + - ret_status, ret = self.loadJsonAnswer(answer, cid=cid, eid=eid, uid=uid) - # try: - # ret_status, ret = self.loadJsonAnswer(answer) - # except: - # # We catch the error here so users get an error (rather than 500) - # return JsonResponse({"success": False, "error": "Invalid data"}) # if ret is not True: # return ret - if ret_status: - n = n + 1 - else: - return ret + n = n + 1 # print(UserAnswer.objects.filter(exam__id=q["eid"])) # print(request.urlencode()) diff --git a/longs/models.py b/longs/models.py index a1b966f0..bec129d3 100644 --- a/longs/models.py +++ b/longs/models.py @@ -15,7 +15,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.utils.html import mark_safe from django.core.exceptions import ValidationError diff --git a/longs/views.py b/longs/views.py index 3d244998..6e8918af 100755 --- a/longs/views.py +++ b/longs/views.py @@ -615,75 +615,6 @@ class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView): filterset_class = LongSeriesFilter -def loadJsonAnswer(answer): - eid = int(answer["eid"].split("/")[1]) - cid = int(answer["cid"]) - - # As access is not restricted make sure the data appears valid - if (not isinstance(cid, int)) or ( - not isinstance(eid, int) or (not isinstance(answer["ans"], str)) - ): - return False, JsonResponse( - {"success": False, "error": "cid or eid or answers not defined"} - ) - - # The model should catch invalid data but this should be less intensive - max_int = 999999999999999999999 - if cid > max_int or eid > max_int: - return False, JsonResponse({"success": False, "error": "invalid cid"}) - - exam = get_object_or_404(Exam, pk=eid) - - if not exam.check_cid_user(cid, answer["passcode"]): - return False, JsonResponse( - {"success": False, "error": "invalid cid / passcode"} - ) - - # if not exam.active: - # return False, JsonResponse( - # {"success": False, "error": "No active exam: {}".format(eid)} - # ) - - exiting_answers = CidUserAnswer.objects.filter( - question__id=answer["qid"], exam__id=eid, cid=cid - ) - - posted_answer = answer["ans"] - - # Long cases seperate sections by qidn - qidn = answer["qidn"] - - # If the user answer does not exist - if not exiting_answers: - ans = CidUserAnswer(cid=cid) - ans.question_id = answer["qid"] - ans.exam_id = eid - - # If the answer already exists or we have started populating it - else: - # Update an existing answer - # should never be more than one (famous last words) - ans = exiting_answers[0] - - if qidn == "1": - ans.answer_observations = posted_answer - elif qidn == "2": - ans.answer_interpretation = posted_answer - elif qidn == "3": - ans.answer_principle_diagnosis = posted_answer - elif qidn == "4": - ans.answer_differential_diagnosis = posted_answer - elif qidn == "5": - ans.answer_management = posted_answer - - # Mark as unmarked - ans.score = ans.ScoreOptions.UNMARKED - - ans.full_clean() - ans.save() - - return True, None - @user_is_long_marker @reversion.create_revision() @@ -1178,7 +1109,7 @@ def long_series_order_upload_filename(request, pk): return redirect("longs:long_series_detail", pk=pk) -GenericExamViews = ExamViews(Exam, Long, None, CidUserAnswer, "longs", "long", loadJsonAnswer) +GenericExamViews = ExamViews(Exam, Long, None, CidUserAnswer, "longs", "long") class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView): diff --git a/physics/models.py b/physics/models.py index 6248e6b7..8a859306 100644 --- a/physics/models.py +++ b/physics/models.py @@ -8,7 +8,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from sortedm2m.fields import SortedManyToManyField diff --git a/physics/views.py b/physics/views.py index c29cc4d7..d7e57147 100644 --- a/physics/views.py +++ b/physics/views.py @@ -311,65 +311,65 @@ def exam_take(request, pk, sk, cid, passcode): ) -def loadJsonAnswer(answer): - # As access is not restricted make sure the data appears valid - if (not isinstance(answer["cid"], int)) or (not isinstance(answer["eid"], int)): - return False, JsonResponse({"success": False, "error": "invalid"}) - - # The model should catch invalid data but this should be less intensive - max_int = 2147483647 - if answer["cid"] > max_int or answer["eid"] > max_int: - return False, JsonResponse({"success": False, "error": "invalid candidate id"}) - - exam = get_object_or_404(Exam, pk=answer["eid"]) - - # if not exam.active: - # return False, JsonResponse( - # {"success": False, "error": "No active exam: {}".format(answer["eid"])} - # ) - - questions = defaultdict(dict) - - for q, n, a in answer["ans"]: - questions[q][n] = a - pass - - for qid in questions: - exiting_answers = CidUserAnswer.objects.filter( - question__id=qid, exam__id=answer["eid"], cid=answer["cid"] - ) - - a = questions[qid]["a"] - b = questions[qid]["b"] - c = questions[qid]["c"] - d = questions[qid]["d"] - e = questions[qid]["e"] - - if not exiting_answers: - ans = CidUserAnswer(a=a, b=b, c=c, d=d, e=e, cid=answer["cid"]) - ans.question_id = qid - ans.exam_id = answer["eid"] - - ans.full_clean() - - ans.save() - else: - # Update an existing answer - # should never be more than one (famous last words) - ans = exiting_answers[0] - ans.a = a - ans.b = b - ans.c = c - ans.d = d - ans.e = e - ans.full_clean() - ans.save() - - return True, None +#def loadJsonAnswer(answer): +# # As access is not restricted make sure the data appears valid +# if (not isinstance(answer["cid"], int)) or (not isinstance(answer["eid"], int)): +# return False, JsonResponse({"success": False, "error": "invalid"}) +# +# # The model should catch invalid data but this should be less intensive +# max_int = 2147483647 +# if answer["cid"] > max_int or answer["eid"] > max_int: +# return False, JsonResponse({"success": False, "error": "invalid candidate id"}) +# +# exam = get_object_or_404(Exam, pk=answer["eid"]) +# +# # if not exam.active: +# # return False, JsonResponse( +# # {"success": False, "error": "No active exam: {}".format(answer["eid"])} +# # ) +# +# questions = defaultdict(dict) +# +# for q, n, a in answer["ans"]: +# questions[q][n] = a +# pass +# +# for qid in questions: +# exiting_answers = CidUserAnswer.objects.filter( +# question__id=qid, exam__id=answer["eid"], cid=answer["cid"] +# ) +# +# a = questions[qid]["a"] +# b = questions[qid]["b"] +# c = questions[qid]["c"] +# d = questions[qid]["d"] +# e = questions[qid]["e"] +# +# if not exiting_answers: +# ans = CidUserAnswer(a=a, b=b, c=c, d=d, e=e, cid=answer["cid"]) +# ans.question_id = qid +# ans.exam_id = answer["eid"] +# +# ans.full_clean() +# +# ans.save() +# else: +# # Update an existing answer +# # should never be more than one (famous last words) +# ans = exiting_answers[0] +# ans.a = a +# ans.b = b +# ans.c = c +# ans.d = d +# ans.e = e +# ans.full_clean() +# ans.save() +# +# return True, None GenericExamViews = ExamViews( - Exam, Question, None, CidUserAnswer, "physics", "physics", loadJsonAnswer + Exam, Question, None, CidUserAnswer, "physics", "physics" ) GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) diff --git a/rapids/models.py b/rapids/models.py index fce04c10..6904b712 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -16,7 +16,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.utils.html import mark_safe from sortedm2m.fields import SortedManyToManyField diff --git a/rapids/tests/test_models.py b/rapids/tests/test_models.py index aaf8a200..5afc99ae 100644 --- a/rapids/tests/test_models.py +++ b/rapids/tests/test_models.py @@ -1,15 +1,21 @@ +from django.urls import reverse import pytest from rapids.models import Abnormality, Exam +from rapids.views import GenericExamViews #@pytest.mark.django_db def test_create_abnormality(db): abnormality = Abnormality.objects.create(name="Abnorm") assert abnormality.name == "Abnorm" +@pytest.fixture +def create_basic_user(db, django_user_model): + return django_user_model.objects.create_superuser("basicuser", "ross@xkjq.uk", "password") + @pytest.fixture def create_exam(db): - return Exam.objects.create(name="test exam") + return Exam.objects.create(name="test exam", exam_mode=True) def test_exam_creation(create_exam): exams = Exam.objects.filter(name="test exam") @@ -18,3 +24,27 @@ def test_exam_creation(create_exam): exam = exams.first() assert exam.time_limit == 35 * 60 + +def test_exam_list(rf, admin_user, create_exam): + request = rf.get('/rapids/exam/') + # Remember that when using RequestFactory, the request does not pass + # through middleware. If your view expects fields such as request.user + # to be set, you need to set them explicitly. + # The following line sets request.user to an admin user. + request.user = admin_user + response = GenericExamViews.exam_list(request) + #print(response) + assert response.status_code == 200 + +def test_exam_active_admin(rf, admin_user, create_exam, client): + request = rf.get(reverse('rapids:active_exams')) + request.user = admin_user + response = GenericExamViews.active_exams(request) + print(response.content) + assert response.status_code == 200 + +def test_exam_active_basic_user(create_basic_user, create_exam, client): + client.login(username="basicuser", password="password") + response = client.get(reverse('rapids:active_exams')) + print(response.content) + assert response.status_code == 200 \ No newline at end of file diff --git a/rapids/views.py b/rapids/views.py index 3457045c..8714118f 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -509,61 +509,6 @@ class RapidView(LoginRequiredMixin, SingleTableMixin, FilterView): filterset_class = RapidFilter -def loadJsonAnswer(answer, cid, eid, uid): - - exam = get_object_or_404(Exam, pk=eid) - - if not exam.check_cid_user(cid, answer["passcode"], user_id=uid): - return False, JsonResponse( - {"success": False, "error": "invalid cid / passcode"} - ) - - if uid: - exiting_answers = CidUserAnswer.objects.filter( - question__id=answer["qid"], exam__id=eid, user__id=uid - ) - else: - exiting_answers = CidUserAnswer.objects.filter( - question__id=answer["qid"], exam__id=eid, cid=cid - ) - - posted_answer = answer["ans"] - - ## qidn 1 does not hold answer data (but should always arrive prior to 2) - # if answer["qidn"] == "1": - # posted_answer = "" - - # Normal answers are just posted with the answer "Normal" - normal = False - if posted_answer == "Normal": - normal = True - - if not exiting_answers: - if uid: - ans = CidUserAnswer(answer=posted_answer, normal=normal, user=User.objects.get(id=uid)) - else: - ans = CidUserAnswer(answer=posted_answer, normal=normal, cid=cid) - - ans.question_id = answer["qid"] - ans.exam_id = eid - - ans.full_clean() - - ans.save() - else: - # Update an existing answer - # should never be more than one (famous last words) - ans = exiting_answers[0] - if answer["qidn"] == "1": - ans.normal = normal - ans.answer = "" - elif answer["qidn"] == "2" and not ans.normal: - ans.answer = posted_answer - ans.full_clean() - ans.save() - - return True, None - @login_required def mark_all(request, exam_pk, sk): @@ -818,7 +763,7 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView): GenericExamViews = ExamViews( - Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid", loadJsonAnswer + Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid" ) diff --git a/sbas/models.py b/sbas/models.py index e1672e3a..89dd796b 100644 --- a/sbas/models.py +++ b/sbas/models.py @@ -7,7 +7,7 @@ from django.utils.html import format_html from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from sortedm2m.fields import SortedManyToManyField diff --git a/sbas/views.py b/sbas/views.py index 358bacfa..cdfc73e8 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -285,12 +285,8 @@ def exam_take(request, pk, sk, cid, passcode): ) -def loadJsonAnswer(answer): - pass - - GenericExamViews = ExamViews( - Exam, Question, None, CidUserAnswer, "sbas", "sbas", loadJsonAnswer + Exam, Question, None, CidUserAnswer, "sbas", "sbas" )