From c799f106d77264aecb0832944451ce2085829f7f Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 3 Jul 2023 13:06:08 +0100 Subject: [PATCH] add user_user support to atlas collections --- atlas/forms.py | 14 +- atlas/models.py | 10 ++ atlas/templates/atlas/collection_take.html | 4 + .../atlas/collection_take_overview.html | 22 ++- atlas/urls.py | 12 +- atlas/views.py | 150 +++++++++++------- generic/models.py | 8 +- physics/views.py | 16 +- sbas/views.py | 14 +- 9 files changed, 160 insertions(+), 90 deletions(-) diff --git a/atlas/forms.py b/atlas/forms.py index 4bdb6e27..2ad8edaa 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -28,6 +28,7 @@ from atlas.models import ( Condition, Structure, Subspecialty, + UserReportAnswer, ) from anatomy.models import Modality @@ -440,10 +441,8 @@ SeriesImageFormSet = inlineformset_factory( max_num=2000, ) - -class CidReportAnswerForm(ModelForm): +class BaseReportAnswerForm(ModelForm): class Meta: - model = CidReportAnswer fields = ["answer"] widgets = { @@ -460,9 +459,16 @@ class CidReportAnswerForm(ModelForm): # } def __init__(self, *args, **kwargs): - super(CidReportAnswerForm, self).__init__(*args, **kwargs) + super(BaseReportAnswerForm, self).__init__(*args, **kwargs) self.fields["answer"].required = False +class CidReportAnswerForm(BaseReportAnswerForm): + class Meta(BaseReportAnswerForm.Meta): + model = CidReportAnswer + +class UserReportAnswerForm(BaseReportAnswerForm): + class Meta(BaseReportAnswerForm.Meta): + model = UserReportAnswer class CidReportAnswerMarkForm(ModelForm): class Meta: diff --git a/atlas/models.py b/atlas/models.py index 191b72f4..1b2e835a 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -542,6 +542,16 @@ class BaseReportAnswer(models.Model): def get_answer_score(self): return self.score + def set_cid_or_user(self, cid=None, user=None): + if cid is not None: + self.cid = cid + elif user is not None: + self.user = user + else: + raise ValueError("No cid or user specified") + + + class Meta: abstract = True diff --git a/atlas/templates/atlas/collection_take.html b/atlas/templates/atlas/collection_take.html index c1dba53d..bd604c6a 100644 --- a/atlas/templates/atlas/collection_take.html +++ b/atlas/templates/atlas/collection_take.html @@ -4,6 +4,9 @@

{{collection.name}}

Start: {{exam.name}}

+ {% if request.user.is_authenticated and valid_user %} + Start + {% else %} Enter your CID and passcode in the below boxes.

@@ -45,5 +48,6 @@ }); }); + {% endif %} {% endblock %} diff --git a/atlas/templates/atlas/collection_take_overview.html b/atlas/templates/atlas/collection_take_overview.html index 4ed3baa6..e4815f97 100644 --- a/atlas/templates/atlas/collection_take_overview.html +++ b/atlas/templates/atlas/collection_take_overview.html @@ -1,7 +1,9 @@ {% extends 'base.html' %} {% block content %} - CID: {{cid}} + {% if cid is not None %} + CID: {{cid}} + {% endif %}

Collection: {{collection.name}}

@@ -9,9 +11,21 @@ {{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
diff --git a/atlas/urls.py b/atlas/urls.py index ddb46977..d7f7d01a 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -71,10 +71,15 @@ urlpatterns = [ name="collection_scores_cid", ), path( - "collection//take//", + "collection//take_overview//", views.collection_take_overview, name="collection_take_overview", ), + path( + "collection//take_overview", + views.collection_take_overview_user, + name="collection_take_overview_user", + ), path( "collection//", views.collection_case_view, @@ -85,6 +90,11 @@ urlpatterns = [ views.collection_case_view_take, name="collection_case_view_take", ), + path( + "collection///take/", + views.collection_case_view_take_user, + name="collection_case_view_take_user", + ), path( "collection///review", views.collection_case_view_review, diff --git a/atlas/views.py b/atlas/views.py index 4ed6a035..9cc98442 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -44,6 +44,7 @@ from .forms import ( SeriesFindingForm, CaseDifferentialFormSet, StructureForm, + UserReportAnswerForm, ) from .models import ( Case, @@ -60,6 +61,7 @@ from .models import ( Subspecialty, SeriesFinding, SeriesImage, + UserReportAnswer, ) from .tables import ( CaseCollectionTable, @@ -371,7 +373,6 @@ class CaseCollectionCreate(RevisionMixin, LoginRequiredMixin, CreateView): return context def form_valid(self, form): - self.object = form.save(commit=False) self.object.save() @@ -421,7 +422,6 @@ class SeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView): return context def form_valid(self, form): - self.object = form.save(commit=False) self.object.save() @@ -528,7 +528,6 @@ class SeriesUpdate( return kwargs def form_valid(self, form): - self.object = form.save(commit=False) self.object.save() @@ -613,7 +612,6 @@ class AtlasCreateBase(RevisionMixin, LoginRequiredMixin): return context def form_valid(self, form): - self.object = form.save(commit=False) # Add exam objects @@ -653,12 +651,12 @@ class AtlasCreate(AtlasCreateBase, CreateView): return initial ## There has to be a better way... - #try: + # try: # s = (i.pk for i in self.request.user.atlas_default.site.all()) # self.initial.update({"site": s}) - #except AttributeError: + # except AttributeError: # pass - #return self.initial + # return self.initial class AtlasUpdate( @@ -701,7 +699,6 @@ class AtlasUpdate( return context def form_valid(self, form): - self.object = form.save(commit=False) self.object.save() @@ -722,24 +719,30 @@ class AtlasUpdate( else: return super().form_invalid(form) + def add_collection_to_case_form(request, case_id): - if request.method == 'POST': + if request.method == "POST": form = AddCollectionToCaseForm(request.POST, user=request.user) if form.is_valid(): case = get_object_or_404(Case, pk=case_id) case.casecollection_set.add(*form.cleaned_data["casecollection"]) - #do_something_with_form_data(form.cleaned_data) + # do_something_with_form_data(form.cleaned_data) return HttpResponse("Done") - return render(request, 'web/htmx_contact_form_confirm.html') + return render(request, "web/htmx_contact_form_confirm.html") else: form = AddCollectionToCaseForm(user=request.user) - return render(request, 'generic_form.html', { - 'form': form, - 'target_url': reverse("atlas:case_collection_form", args=(case_id,)) - }) + return render( + request, + "generic_form.html", + { + "form": form, + "target_url": reverse("atlas:case_collection_form", args=(case_id,)), + }, + ) -#class AddCollectionToCaseView(FormView): + +# class AddCollectionToCaseView(FormView): # form_class = AddCollectionToCaseForm # template_name = "generic_form.html" # @@ -756,6 +759,7 @@ def add_collection_to_case_form(request, case_id): # pk = self.kwargs["pk"] # return reverse("atlas:case_detail", kwargs={"pk": pk}) + class AtlasClone(AtlasCreateBase, CreateView): """Clones a existing atlas""" @@ -1085,18 +1089,26 @@ def collection_detail(request, pk): cases = collection.cases.all().order_by("casedetail__sort_order") - return render(request, "atlas/collection_detail.html", {"collection": collection, "cases": cases}) + return render( + request, + "atlas/collection_detail.html", + {"collection": collection, "cases": cases}, + ) def collection_take(request, pk): collection = get_object_or_404(CaseCollection, pk=pk) + template_variables = { + "collection": collection, + "valid_user": collection.check_logged_in_user(request), + } + match collection.collection_type: case "REP": - return render(request, "atlas/collection_take.html", {"collection": collection}) + return render(request, "atlas/collection_take.html", template_variables) case "REV": - return render(request, "atlas/collection_review.html", {"collection": collection}) - + return render(request, "atlas/collection_review.html", template_variables) @user_is_collection_author_or_atlas_editor @@ -1234,7 +1246,13 @@ def exam_cids(request, pk): raise PermissionDenied -def collection_take_overview(request, pk, cid, passcode): +def collection_take_overview_user(request, pk): + return collection_take_overview(request, pk) + + +def collection_take_overview( + request, pk: int, cid: int | None = None, passcode: str | None = None +): collection = get_object_or_404(CaseCollection, pk=pk) if not collection.active: @@ -1246,7 +1264,11 @@ def collection_take_overview(request, pk, cid, passcode): cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related() case_details = CaseDetail.objects.filter(case__in=cases, collection=collection) - answers = CidReportAnswer.objects.filter(cid=cid, question__in=case_details) + + if cid is not None: + answers = CidReportAnswer.objects.filter(cid=cid, question__in=case_details) + else: + answers = UserReportAnswer.objects.filter(user=request.user, question__in=case_details) answer_question_map = {} for ans in answers: @@ -1261,8 +1283,7 @@ def collection_take_overview(request, pk, cid, passcode): else: question_answer_tuples.append((q, None)) - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = collection.get_or_create_cid_user_exam(cid_user=c) + cid_user_exam = collection.get_or_create_cid_user_exam(cid=cid, user_user=request.user) return render( request, @@ -1278,14 +1299,22 @@ def collection_take_overview(request, pk, cid, passcode): }, ) + def collection_case_view_review(request, pk: int, case_number: int): return collection_case_view_take(request, pk, case_number, None, None) -def collection_case_view_take(request, pk: int, case_number: int, cid=None, passcode=None): + +def collection_case_view_take_user(request, pk: int, case_number: int): + return collection_case_view_take(request, pk, case_number) + + +def collection_case_view_take( + request, pk: int, case_number: int, cid=None, passcode=None +): collection = get_object_or_404(CaseCollection, pk=pk) form = None - answer = None + answer: CidReportAnswer | UserReportAnswer if collection.collection_type != collection.COLLECTION_TYPE_CHOICES.REVIEW: if not collection.active: @@ -1294,32 +1323,36 @@ def collection_case_view_take(request, pk: int, case_number: int, cid=None, pass if cid is not None and not collection.check_cid_user(cid, passcode, request): raise Http404("Error accessing exam") - - if cid is not None: - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = collection.get_or_create_cid_user_exam(cid_user=c) - else: - cid_user_exam = collection.get_or_create_cid_user_exam(user_user=request.user) + cid_user_exam = collection.get_or_create_cid_user_exam( + cid=cid, user_user=request.user + ) cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related() case = cases[case_number] if collection.collection_type == "REP": - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = collection.get_or_create_cid_user_exam(cid_user=c) + cid_user_exam = collection.get_or_create_cid_user_exam( + cid=cid, user_user=request.user + ) case_detail = CaseDetail.objects.get(case=case, collection=collection) - answer = case_detail.cidreportanswer_set.filter(cid=cid).first() + + if cid is not None: + answer = case_detail.cidreportanswer_set.filter(cid=cid).first() + ReportAnswerForm = CidReportAnswerForm + else: + answer = case_detail.userreportanswer_set.filter(user=request.user).first() + ReportAnswerForm = UserReportAnswerForm if request.method == "POST": if collection.collection_type == "REP": if not collection.publish_results: if answer: - form = CidReportAnswerForm(request.POST, instance=answer) + form = ReportAnswerForm(request.POST, instance=answer) else: - form = CidReportAnswerForm(request.POST) + form = ReportAnswerForm(request.POST) if form.is_valid(): answer = form.save(commit=False) - answer.cid = cid + answer.set_cid_or_user(cid=cid, user=request.user) answer.question = case_detail # answer.published_date = timezone.now() answer.save() @@ -1327,24 +1360,25 @@ def collection_case_view_take(request, pk: int, case_number: int, cid=None, pass cid_user_exam.end_time = timezone.now() cid_user_exam.save() - kwargs = {"pk": pk, "cid": cid, "passcode": passcode} - redirect_url = "atlas:collection_case_view_take" + if cid is not None: + kwargs = {"pk": pk, "cid": cid, "passcode": passcode} + redirect_url = "atlas:collection_case_view_take" + overview_url = "atlas:collection_take_overview" + else: + kwargs = {"pk": pk} + redirect_url = "atlas:collection_case_view_take_user" + overview_url = "atlas:collection_take_overview_user" else: kwargs = {"pk": pk} redirect_url = "atlas:collection_case_view_review" if "next" in request.POST: - return redirect( - redirect_url, case_number=case_number + 1, **kwargs - ) + return redirect(redirect_url, case_number=case_number + 1, **kwargs) elif "previous" in request.POST: - return redirect( - redirect_url, case_number=case_number - 1, **kwargs - ) + return redirect(redirect_url, case_number=case_number - 1, **kwargs) elif "finish" in request.POST: - redirect_url = "atlas:collection_take" - return redirect(redirect_url, **kwargs) + return redirect(overview_url, **kwargs) elif "goto" in request.POST: return redirect( redirect_url, @@ -1352,7 +1386,8 @@ def collection_case_view_take(request, pk: int, case_number: int, cid=None, pass **kwargs, ) else: - form = CidReportAnswerForm(instance=answer) + if collection.collection_type == "REP": + form = ReportAnswerForm(instance=answer) series_list = case.series.all().prefetch_related("images", "examination", "plane") @@ -1431,6 +1466,7 @@ def collection_case_view(request, pk, case_number): }, ) + @user_is_collection_author_or_atlas_editor def delete_collection_cid_answers(request, exam_id, cid): collection = get_object_or_404(CaseCollection, pk=exam_id) @@ -1441,17 +1477,18 @@ def delete_collection_cid_answers(request, exam_id, cid): case__in=cases, collection=collection ).prefetch_related("case") - cid_user_answers = CidReportAnswer.objects.filter(question__in=case_details, cid=cid) - + cid_user_answers = CidReportAnswer.objects.filter( + question__in=case_details, cid=cid + ) temp = {} for ans in cid_user_answers: temp[ans.pk] = (ans.question.pk, ans.score, ans.cid, ans.answer) ans.delete() - return JsonResponse(temp) + @user_is_collection_author_or_atlas_editor def delete_collection_answers(request, exam_id): collection = get_object_or_404(CaseCollection, pk=exam_id) @@ -1464,15 +1501,14 @@ def delete_collection_answers(request, exam_id): cid_user_answers = CidReportAnswer.objects.filter(question__in=case_details) - temp = {} for ans in cid_user_answers: temp[ans.pk] = (ans.question.pk, ans.score, ans.cid, ans.answer) ans.delete() - return JsonResponse(temp) + # TODO: this needs a major cleanup @user_is_collection_author_or_atlas_editor def collection_scores_cid(request, pk): @@ -1541,7 +1577,6 @@ def collection_scores_cid(request, pk): user_scores = {} user_scores_normalised = {} for user in user_answers_marks: - user_scores[user] = sum( [i for i in user_answers_marks[user] if (i != "unmarked" and i != None)] ) @@ -1587,7 +1622,7 @@ def collection_scores_cid(request, pk): collection.stats_min = min(user_scores_list) collection.stats_max = max(user_scores_list) - #collection.stats_graph = fig_html + # collection.stats_graph = fig_html collection.user_scores = user_scores @@ -1619,6 +1654,7 @@ def collection_scores_cid(request, pk): }, ) + GenericExamViews = ExamViews( CaseCollection, Case, None, CidReportAnswer, "atlas", "casecollection" -) \ No newline at end of file +) diff --git a/generic/models.py b/generic/models.py index 2f5e6793..8f186166 100644 --- a/generic/models.py +++ b/generic/models.py @@ -470,12 +470,14 @@ class ExamCollectionGenericBase(models.Model): if c: return c - if user_user is not None: + elif user_user is not None: c = CidUserExam.objects.filter( content_type=content_type, object_id=self.pk, user_user=user_user ).first() if c: return c + else: + raise ValueError("Invalid cid / cid_user / user_user") if start_time is None: start_time = timezone.now() @@ -488,13 +490,15 @@ class ExamCollectionGenericBase(models.Model): start_time=start_time, ) - if user_user is not None: + elif user_user is not None: new = CidUserExam( content_type=content_type, object_id=self.pk, user_user=user_user, start_time=start_time, ) + else: + raise ValueError("Invalid cid / cid_user / user_user") new.save() return new diff --git a/physics/views.py b/physics/views.py index a2bef598..18a18d22 100644 --- a/physics/views.py +++ b/physics/views.py @@ -235,11 +235,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None): else: question_answer_tuples.append((q, None)) - if cid is not None: - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) - else: - cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) + cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user) return render( request, @@ -256,7 +252,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None): ) -def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None): +def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| None = None): exam = get_object_or_404(Exam, pk=pk) if not exam.active: @@ -265,11 +261,9 @@ def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None): if not exam.check_cid_user(cid, passcode, request): raise Http404("Error accessing exam") - if cid is not None: - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) - else: - cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) + print(cid, passcode, request.user) + + cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user) question = exam.exam_questions.all()[sk] diff --git a/sbas/views.py b/sbas/views.py index cd914eab..d121ee79 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -202,11 +202,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None): else: question_answer_tuples.append((q, None)) - if cid is not None: - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) - else: - cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) + cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user) return render( request, @@ -223,7 +219,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None): ) -def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None): +def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None): exam = get_object_or_404(Exam, pk=pk) if not exam.active: @@ -232,11 +228,7 @@ def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None): if not exam.check_cid_user(cid, passcode, request): raise Http404("Error accessing exam") - if cid is not None: - c = CidUser.objects.filter(cid=cid).first() - cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) - else: - cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) + cid_user_exam = exam.get_or_create_cid_user_exam(cid = cid, user_user=request.user) question = exam.exam_questions.all()[sk]