diff --git a/atlas/models.py b/atlas/models.py index f20a08cf..9b0d54d8 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -555,6 +555,14 @@ class SeriesImage(SeriesImageBase): help_text="Reference to the object that has replaced this one.", ) + def get_file_size(self): + try: + return self.image.size + + # We catch this for when the image does not exist + except FileNotFoundError: + return 0 + def get_dicom_data(self): try: with pydicom.dcmread(self.image) as d: @@ -1066,6 +1074,13 @@ class CaseDetail(models.Model): """Returns the correct question answers as a json string""" return json.dumps(self.question_answers) + def get_user_answers(self, user): + """Returns the users answers as a json string""" + try: + return UserReportAnswer.objects.get(question=self, user=user) + except UserReportAnswer.DoesNotExist: + return None + class CasePrior(models.Model): case_detail = models.ForeignKey(CaseDetail, on_delete=models.CASCADE) prior_case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name="prior_case") diff --git a/atlas/templates/atlas/collection_headers.html b/atlas/templates/atlas/collection_headers.html index d3d443ed..890e00f1 100644 --- a/atlas/templates/atlas/collection_headers.html +++ b/atlas/templates/atlas/collection_headers.html @@ -1,5 +1,6 @@ {% if request.user.is_authenticated %}
Collection: {{collection.name}}-> Overview / + History / Mark / Scores / Candidates / diff --git a/atlas/templates/atlas/collection_history.html b/atlas/templates/atlas/collection_history.html new file mode 100644 index 00000000..91abe2a0 --- /dev/null +++ b/atlas/templates/atlas/collection_history.html @@ -0,0 +1,20 @@ +{% extends 'atlas/exams.html' %} + +{% block content %} +

{{collection.name}}

+ +

Users

+ + + + +{% endblock %} diff --git a/atlas/templates/atlas/collection_history_user.html b/atlas/templates/atlas/collection_history_user.html new file mode 100644 index 00000000..df23dd55 --- /dev/null +++ b/atlas/templates/atlas/collection_history_user.html @@ -0,0 +1,87 @@ +{% extends 'atlas/exams.html' %} + +{% block content %} +

Collection: {{collection.name}}

+ +

User: {{user}}

+ + + + +{% endblock %} + +{% block css %} + {{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/atlas/templates/atlas/series_viewer.html b/atlas/templates/atlas/series_viewer.html index 357a6f31..ab0a3b2b 100755 --- a/atlas/templates/atlas/series_viewer.html +++ b/atlas/templates/atlas/series_viewer.html @@ -102,7 +102,7 @@ {% if image.image %} - [{{image.image.size|filesizeformat}}] + [{{image.get_file_size|filesizeformat}}] {% endif %} {{image.image_md5_hash}} ({{image.is_dicom}}) {{image.image_blake3_hash}} diff --git a/atlas/urls.py b/atlas/urls.py index 34fe22a8..46c51482 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -85,6 +85,17 @@ urlpatterns = [ views.GenericExamViews.exam_cids, name="exam_cids", ), + path( + "collection//history", + views.collection_history, + name="collection_history", + ), + path( + "collection//history//user", + views.collection_history_user, + name="collection_history_user", + ), + path( "collection//user_status", views.GenericExamViews.exam_user_status, diff --git a/atlas/views.py b/atlas/views.py index 3cfe7893..9295e109 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -246,6 +246,7 @@ def question_schema_detail(request, pk: int): {"question_schema": question_schema, "example_form": example_form}, ) + @login_required # @user_is_atlas_editor def question_schema_schemas(request): @@ -259,6 +260,7 @@ def question_schema_schemas(request): }, ) + @login_required # @user_is_atlas_editor def condition_detail(request, pk): @@ -857,14 +859,17 @@ class QuestionSchemaCreate(LoginRequiredMixin, CreateView): model = QuestionSchema form_class = QuestionSchemaForm + class QuestionSchemaUpdate(LoginRequiredMixin, UpdateView): model = QuestionSchema form_class = QuestionSchemaForm + class QuestionSchemaDelete(AuthorOrCheckerRequiredMixin, DeleteView): model = QuestionSchema template_name = "confirm_delete.html" + class FindingUpdate(RevisionMixin, LoginRequiredMixin, UpdateView): model = Finding form_class = FindingForm @@ -1272,7 +1277,6 @@ def series_order_dicom(request, pk): @login_required def image_diff(request): if request.htmx: - image_ids = request.POST.getlist("image-id") if len(image_ids) == 2: @@ -1596,6 +1600,58 @@ def collection_detail(request, pk): ) +@user_is_collection_author_or_atlas_editor +def collection_history(request, exam_id: int): + """An overview page that shows who has attempted / finised the collection""" + collection = get_object_or_404(CaseCollection, pk=exam_id) + + casedetails = CaseDetail.objects.filter(collection=collection).order_by( + "sort_order" + ) + + userexams = collection.get_cid_user_exams() + + print(userexams) + + return render( + request, + "atlas/collection_history.html", + { + "collection": collection, + "casesdetails": casedetails, + "can_edit": True, + "userexams": userexams, + }, + ) + + +@user_is_collection_author_or_atlas_editor +def collection_history_user(request, exam_id: int, user_pk: int): + collection = get_object_or_404(CaseCollection, pk=exam_id) + + user = get_object_or_404(User, pk=user_pk) + + casedetails = CaseDetail.objects.filter( + collection=collection, + ).order_by("sort_order") + + print(user) + print(casedetails) + + user_answers = [(casedetail, casedetail.get_user_answers(user)) for casedetail in casedetails] + + return render( + request, + "atlas/collection_history_user.html", + { + "collection": collection, + "user": user, + "casedetails": casedetails, + "user_answers": user_answers, + }, + ) + + def collection_take_start(request, pk): """The starting page for taking / reviewing a case collection @@ -1630,23 +1686,29 @@ def collection_take_start(request, pk): request, "atlas/collection_review_start.html", template_variables ) + @user_is_collection_author_or_atlas_editor def collection_case_priors(request, exam_id, case_id): case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id) - if request.htmx: if "remove" in request.POST: - p = CasePrior.objects.get(case_detail=case_detail, prior_case=request.POST["remove"]) + p = CasePrior.objects.get( + case_detail=case_detail, prior_case=request.POST["remove"] + ) p.delete() return HttpResponse(f"Case removed") elif "prior_case_id" in request.POST: prior_case = Case.objects.get(pk=request.POST["prior_case_id"]) - p, created = CasePrior.objects.get_or_create(case_detail=case_detail, prior_case=prior_case) - p.relation_text=request.POST["relation"] + p, created = CasePrior.objects.get_or_create( + case_detail=case_detail, prior_case=prior_case + ) + p.relation_text = request.POST["relation"] if not p.relation_text: - return HttpResponse("You need to enter text to describe the relationship between the cases") + return HttpResponse( + "You need to enter text to describe the relationship between the cases" + ) p.save() @@ -1674,7 +1736,6 @@ def collection_case_priors(request, exam_id, case_id): else: available_priors.append((case, False, "")) - form = PriorCaseForm() case_number, case_count = collection.get_index_of_case( @@ -1684,14 +1745,13 @@ def collection_case_priors(request, exam_id, case_id): previous = collection.get_previous_case(case_detail.case) next = collection.get_next_case(case_detail.case) - return render( request, "atlas/collection_case_priors.html", { "case_detail": case_detail, "form": form, - #"example_form": example_form, + # "example_form": example_form, "collection": collection, "case": case_detail.case, "previous": previous, @@ -1703,6 +1763,7 @@ def collection_case_priors(request, exam_id, case_id): }, ) + @user_is_collection_author_or_atlas_editor def collection_case_details(request, exam_id, case_id): case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id) @@ -1713,7 +1774,6 @@ def collection_case_details(request, exam_id, case_id): raise Http404("Collection not in question mode") if request.method == "POST": - # Called if the user saves the correct answer form if request.POST.get("submit") == "answer": # answers = request.POST @@ -1986,7 +2046,9 @@ def collection_take_overview( cid_user_exam.save() collection.exam_user_status.create( - cid_user_exam=cid_user_exam, status="submitted", extra="manual submission" + cid_user_exam=cid_user_exam, + status="submitted", + extra="manual submission", ) return HttpResponse("True") @@ -2012,7 +2074,9 @@ def collection_take_overview( answer_count = 0 for cd in case_details: self_review = cid_user_exam.selfreview_set.filter(case=cd.case) - if cd in answer_question_map and (answer_question_map[cd].answer or answer_question_map[cd].json_answer): + if cd in answer_question_map and ( + answer_question_map[cd].answer or answer_question_map[cd].json_answer + ): question_answer_tuples.append((cd, answer_question_map[cd], self_review)) answer_count += 1 else: @@ -2192,8 +2256,6 @@ def collection_case_view_take( for series in prior.prior_case.get_series(): series_to_load.append((series, True, prior.relation_text)) - - previous = case_number > 0 next = case_number < (case_count - 1) @@ -2317,6 +2379,7 @@ def use_dates_as_descriptions(request, pk): return HttpResponse("Done") + def collection_case_dicom_json(request, exam_id, case_id): case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id) @@ -2715,6 +2778,7 @@ class CaseCollectionAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView) context["collection"] = context["object"] return context + class ExamGroupsUpdate(ExamGroupsUpdateBase): model = CaseCollection form_class = ExamGroupsForm @@ -2819,6 +2883,7 @@ def collection_question_schemas(request, exam_id: int): }, ) + def collection_reset_answers(request, exam_id: int): if request.htmx: collection = get_object_or_404(CaseCollection, pk=exam_id) @@ -2834,14 +2899,12 @@ def collection_reset_answers(request, exam_id: int): user_answers = case.userreportanswer_set.all() user_answers.delete() - # User statuses collection.exam_user_status.all().delete() # CidUserExams collection.cid_users.all().delete() return HttpResponse("Success") - else: - raise Http404("Invalid request") \ No newline at end of file + raise Http404("Invalid request") diff --git a/generic/models.py b/generic/models.py index 8098247b..edbbb17e 100644 --- a/generic/models.py +++ b/generic/models.py @@ -531,7 +531,7 @@ class SeriesBase(models.Model): size = 0 for i in images: - size += i.image.size + size += i.get_file_size() return size @@ -997,7 +997,6 @@ class ExamBase(ExamOrCollectionGenericBase): return User.objects.filter(pk__in=user_ids) - def generate_user_report(self, user): exam_text = [f"Candidate {user.first_name} [{user.email}]"] @@ -1478,6 +1477,12 @@ class CidUserExam(models.Model): return f"{self.exam} / {user}: {start_time} {end_time}" + def get_user_name(self) -> str: + if self.cid_user is None: + return self.user_user.username + else: + return "CID" + str(self.cid_user.cid) + CID_GROUP_EXAMS = ( ("SBAs", "sba_cid_user_groups"), @@ -1504,7 +1509,6 @@ class BaseUserGroup(models.Model): class CidUserGroup(BaseUserGroup): - def __str__(self) -> str: return str(self.name)