Files
penracourses/atlas/views.py
T
2025-02-24 11:53:36 +00:00

3009 lines
94 KiB
Python
Executable File

import json
import pprint
from typing import Any
from dal import autocomplete
from django.db.models.base import Model as Model
from django.db.models.query import QuerySet
from django.shortcuts import render, get_object_or_404, redirect
from django import forms
from django.utils import timezone
# 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 (
FieldError,
ObjectDoesNotExist,
PermissionDenied,
ViewDoesNotExist,
)
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import View
from django.views.generic.detail import DetailView
import pydicom
from pydicom.uid import generate_uid
from generic.mixins import SuperuserRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.views.generic import ListView
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from generic.models import CidUser, CidUserExam, CimarCase
from atlas.helpers import get_cases_available_to_user
from rad.settings import REMOTE_URL, CIMAR_USERNAME, CIMAR_PASSWORD
from .forms import (
AddCollectionToCaseForm,
CaseAuthorForm,
CaseCollectionAuthorForm,
CaseCollectionCaseFormSet,
CaseCollectionForm,
CaseDetailForm,
CaseForm,
CaseResourceFormSet,
CidQuestionAnswerForm,
CidReportAnswerForm,
CidReportAnswerMarkForm,
ConditionAutocompleteForm,
ConditionForm,
ExamGroupsForm,
FindingForm,
JsonAnswerForm,
PriorCaseForm,
QuestionSchemaForm,
ResourceForm,
SelfReviewForm,
SeriesAuthorForm,
SeriesForm,
SeriesImageFormSet,
SeriesFormSet,
SeriesFindingForm,
CaseDifferentialFormSet,
StructureForm,
UserQuestionAnswerForm,
UserReportAnswerForm,
)
from .models import (
Case,
CaseCollection,
CaseDetail,
CasePrior,
CidReportAnswer,
Condition,
Differential,
PathologicalProcess,
Presentation,
QuestionSchema,
Resource,
SelfReview,
Series,
Examination,
Finding,
Structure,
Subspecialty,
SeriesFinding,
SeriesImage,
UncategorisedDicom,
UserReportAnswer,
)
from .tables import (
CaseCollectionTable,
CaseTable,
ConditionTable,
FindingTable,
PathologicalProcessTable,
PresentationTable,
QuestionSchemaTable,
SeriesTable,
StructureTable,
SubspecialtyTable,
)
from .filters import (
CaseCollectionFilter,
CaseFilter,
ConditionFilter,
FindingFilter,
PathologicalProcessFilter,
PresentationFilter,
QuestionSchemaFilter,
SeriesFilter,
StructureFilter,
SubspecialtyFilter,
)
from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView
#from autocomplete import HTMXAutoComplete
from .decorators import (
user_is_author_or_atlas_editor,
user_has_case_view_access,
user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access,
user_is_atlas_editor,
user_is_author_or_atlas_series_checker,
user_is_atlas_marker,
user_is_collection_author_or_atlas_editor,
)
from collections import defaultdict
import json
import statistics
import plotly.express as px
from django.core.cache import cache
from django.conf import settings
from helpers.images import compare_dicom_datasets, image_as_base64, print_dicom
import logging
from copy import deepcopy
from django.forms.models import model_to_dict
from generic.views import (
AuthorRequiredMixin,
ExamCloneMixin,
ExamGroupsUpdateBase,
ExamViews,
SeriesImagesZipViewBase,
)
from reversion.views import RevisionMixin
import reversion
import os
import difflib
from helpers.cimar import CimarAPI
logger = logging.getLogger(__name__)
class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if (
self.request.user.groups.filter(name="atlas_editor").exists()
or self.request.user.is_superuser
):
return obj
if self.request.user not in obj.get_author_objects():
raise PermissionDenied() # or Http404
return obj
class AtlasEditorRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if (
self.request.user.groups.filter(name="atlas_editor").exists()
or self.request.user.is_superuser
):
return obj
raise PermissionDenied("You must be an atlas editor to do this.") # or Http404
@login_required
@user_has_case_view_access
def case_detail(request, pk):
case = get_object_or_404(Case, pk=pk)
# if request.user not in atlas.author.all():
# raise PermissionDenied
# logging.debug(atlas.subspecialty.first().name.all())
return render(request, "atlas/case_detail.html", {"case": case, "cimar_sid":request.user.userprofile.cimar_sid})
@login_required
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
def series_thumbnail_fail(request, pk):
return series_thumbnail(request, pk=pk, fail_loudly=True)
@login_required
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
def series_thumbnail(request, pk, fail_loudly=False):
series = get_object_or_404(Series, pk=pk)
thumbnail, _ = series.get_thumbnail(recreate=True, fail_loudly=fail_loudly)
return HttpResponse(thumbnail)
@login_required
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
def series_detail(request, pk, finding_pk=None):
series = get_object_or_404(Series, pk=pk)
can_edit = series.check_user_can_edit(request.user)
editing_finding = -1
if finding_pk is not None:
finding = get_object_or_404(SeriesFinding, pk=finding_pk)
series_finding_form = SeriesFindingForm(None, instance=finding)
editing_finding = finding.pk
else:
series_finding_form = SeriesFindingForm(series_id=series.id)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/series.html",
{
"series": series,
"series_finding_form": series_finding_form,
"editing_finding": editing_finding,
"can_edit": can_edit,
},
)
@login_required
# @user_is_atlas_editor
def question_schema_detail(request, pk: int):
question_schema = get_object_or_404(QuestionSchema, pk=pk)
example_form = question_schema.get_example_form()
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/question_schema_detail.html",
{"question_schema": question_schema, "example_form": example_form},
)
@login_required
# @user_is_atlas_editor
def question_schema_schemas(request):
question_schemas = QuestionSchema.objects.all()
return render(
request,
"atlas/question_schemas_preset.html",
{
"question_schemas": question_schemas,
},
)
@login_required
# @user_is_atlas_editor
def condition_detail(request, pk):
condition = get_object_or_404(Condition, pk=pk)
form = ConditionAutocompleteForm()
can_merge = False
if (
request.user.is_superuser
or request.user.groups.filter(name="atlas_editor").exists()
):
can_merge = True
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/condition_detail.html",
{"condition": condition, "form": form, "can_merge": can_merge},
)
# class GetConditionAutocomplete(HTMXAutoComplete):
# name = "conditions"
# multiselect = False
#
# class Meta:
# model = Condition
@login_required
@user_is_atlas_editor
def condition_merge(request, pk_to_merge):
if not request.htmx:
raise Http404
condition_to_merge = get_object_or_404(Condition, pk=pk_to_merge)
# condition_to_merge_into = get_object_or_404(Condition, pk=pk_to_merge_into)
print(condition_to_merge.case_set)
if condition_to_merge.rcr_curriculum:
return HttpResponse("You cannot merge rcr curriculum items")
condition_to_merge_into = request.POST.get("conditions")
if not condition_to_merge_into:
return HttpResponse("You need to select a condition to merge into.")
condition_to_merge_into = get_object_or_404(Condition, pk=condition_to_merge_into)
if condition_to_merge == condition_to_merge_into:
return HttpResponse("You cannot merge into the same condition.")
# move all relevant relationships
cases = condition_to_merge.case_set.all().values_list("id", flat=True)
condition_to_merge_into.case_set.add(cases)
# cases = Case.objects.filter(condition=condition_to_merge)
# cases.update(condition=condition_to_merge_into)
differentials = Differential.objects.filter(condition=condition_to_merge)
differentials.update(condition=condition_to_merge_into)
return HttpResponse("Merged")
# conditions = Condition.objects.all()
# return render(request, "atlas/condition_options.html", {"conditions": conditions})
@login_required
# @user_is_atlas_editor
def subspecialty_detail(request, pk):
subspecialty = get_object_or_404(Subspecialty, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/subspecialty_detail.html",
{
"subspecialty": subspecialty,
},
)
@login_required
# @user_is_atlas_editor
def presentation_detail(request, pk):
presentation = get_object_or_404(Presentation, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/presentation_detail.html",
{
"presentation": presentation,
},
)
@login_required
# @user_is_atlas_editor
def pathological_process_detail(request, pk):
pathological_process = get_object_or_404(PathologicalProcess, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/pathological_process_detail.html",
{
"pathological_process": pathological_process,
},
)
@login_required
# @user_is_atlas_editor
def structure_detail(request, pk):
structure = get_object_or_404(Structure, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/structure_detail.html",
{
"structure": structure,
},
)
@login_required
# @user_is_atlas_editor
def finding_detail(request, pk):
finding = get_object_or_404(Finding, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/finding_detail.html",
{
"finding": finding,
},
)
@login_required
def resource_detail(request, pk):
resource = get_object_or_404(Resource, pk=pk)
return render(request, "atlas/resource_detail.html", {"resource": resource})
@login_required
def resource_view(request, pk):
resource = get_object_or_404(Resource, pk=pk)
return HttpResponse(resource.get_display())
@login_required
def author_detail(request, pk):
# logging.debug(Author.objects.all())
# author = get_object_or_404(Author, pk=pk)
author = User.objects.get(pk=pk)
if request.user.pk == pk:
atlas = Case.objects.filter(author=pk)
else:
atlas = Case.objects.filter(author=pk, open_access=True)
return render(
request, "atlas/category_detail.html", {"category": author, "atlas": atlas}
)
@user_is_author_or_atlas_editor
def author_list(request):
authors = User.objects.all()
return render(request, "atlas/author_list.html", {"authors": authors})
def index(request):
return render(request, "atlas/index.html", {})
def user_collections(request):
collections = request.user.user_casecollection_exams.all()
return render(request, "atlas/user_collections.html", {"collections": collections})
def add_case_to_collection(request, collection_id):
if not request.htmx:
return Http404
collection = get_object_or_404(CaseCollection, pk=collection_id)
if request.method == "POST":
case = get_object_or_404(Case, pk=request.POST["case"])
if case in collection.cases.all():
return HttpResponse("Case already in collection")
print(case)
collection.add_case(case)
return HttpResponse("Case added to collection (refresh to see)")
# if request.method == "POST":
cases = get_cases_available_to_user(request.user)
return render(
request,
"atlas/add_case_to_collection.html",
{"cases": cases, "collection": collection},
)
@login_required
def user_uploads_series(request, series_instance_uid: str):
# We don't restrict to teh uploading user here. Should we?
dicoms = UncategorisedDicom.objects.filter(series_instance_uid=series_instance_uid)
if not dicoms:
raise Http404("Series not found")
return render(
request,
"atlas/user_upload_viewer.html",
{"dicoms": dicoms, "series_id": series_instance_uid},
)
@login_required
def new_uploads(request):
return render(request, "atlas/new_uploads.html", {})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def all_uploads(request, case_id: int | None = None):
return user_uploads(request, case_id, user_only=False)
@user_passes_test(lambda u: u.is_superuser)
def other_user_uploads(request, user_pk: int, case_id: int | None = None):
return user_uploads(request, case_id, user_only=False, user_pk=user_pk)
@login_required
def user_uploads(
request,
case_id: int | None = None,
user_only: bool = True,
user_pk: int | None = None,
):
user = request.user
if user_only:
dicoms = UncategorisedDicom.objects.filter(user=request.user)
elif user_pk is not None:
user = get_object_or_404(User, pk=user_pk)
dicoms = UncategorisedDicom.objects.filter(user=user)
else:
dicoms = UncategorisedDicom.objects.filter()
data = defaultdict(list)
for d in dicoms:
tags = d.basic_dicom_tags
if tags is None:
d.save()
tags = d.basic_dicom_tags
data[tags["SeriesInstanceUID"]].append((tags, d.created_date))
series_list = []
for series in data:
tags, date = data[series][0]
series_list.append((series, len(data[series]), tags, date))
series_list.sort(key=lambda l: l[-1])
case = False
if case_id is not None:
case = get_object_or_404(Case, pk=case_id)
return render(
request,
"atlas/user_uploads.html",
{"series_list": series_list, "case": case, "user": user},
)
class CaseDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = Case
success_url = reverse_lazy("atlas:case_view")
template_name = "confirm_delete.html"
# TODO fix permissions
class SeriesFindingDelete(RevisionMixin, PermissionRequiredMixin, DeleteView):
permission_required = "atlas.delete_seriesfinding"
model = SeriesFinding
template_name = "confirm_delete.html"
# success_url = reverse_lazy("atlas:case_view")
def get_object(self):
series_finding = super().get_object()
series = series_finding.series
if series.check_user_can_edit(self.request.user):
return series_finding
else:
raise PermissionDenied
def get_success_url(self):
pk = self.kwargs["pk"]
series_id = get_object_or_404(SeriesFinding, pk=pk).series.id
return reverse("atlas:series_detail", kwargs={"pk": series_id})
class SeriesDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
model = Series
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:series_view")
class CaseCollectionDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
model = CaseCollection
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:collection_view")
class ConditionDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
model = Condition
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:condition_view")
class FindingDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
model = Finding
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:finding_view")
class StructureDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
model = Structure
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:structure_view")
class ResourceDelete(LoginRequiredMixin, DeleteView):
model = Resource
template_name = "confirm_delete.html"
success_url = reverse_lazy("atlas:resource_view")
class ResourceCreate(LoginRequiredMixin, CreateView):
model = Resource
form_class = ResourceForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
class ResourceView(LoginRequiredMixin, ListView):
model = Resource
# form_class = ResourceForm
class CaseCollectionCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = CaseCollection
form_class = CaseCollectionForm
template_name = "atlas/casecollection_form.html"
def get_initial(self):
# print(self.request)
if "pk" in self.kwargs:
initial = super(CaseCollectionCreate, self).get_initial()
# case = get_object_or_404(Case, pk=self.kwargs["pk"])
# initial["case"] = case.id
return initial
def get_form_kwargs(self):
kwargs = super(CaseCollectionCreate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(CaseCollectionCreate, self).get_context_data(**kwargs)
if self.request.POST:
context["case_formset"] = CaseCollectionCaseFormSet(
self.request.POST,
self.request.FILES,
form_kwargs={"user": self.request.user},
)
context["case_formset"].full_clean()
else:
context["case_formset"] = CaseCollectionCaseFormSet(
form_kwargs={"user": self.request.user}
)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
case_formset = context["case_formset"]
if case_formset.is_valid():
response = super().form_valid(form)
case_formset.instance = self.object
case_formset.save()
return response
else:
return super().form_invalid(form)
class SeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = Series
form_class = SeriesForm
def get_initial(self):
# print(self.request)
if "pk" in self.kwargs:
initial = super().get_initial()
case = get_object_or_404(Case, pk=self.kwargs["pk"])
initial["case"] = case.id
return initial
def get_form_kwargs(self):
kwargs = super(SeriesCreate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(SeriesCreate, self).get_context_data(**kwargs)
if self.request.POST:
context["image_formset"] = SeriesImageFormSet(
self.request.POST, self.request.FILES
)
context["image_formset"].full_clean()
else:
context["image_formset"] = SeriesImageFormSet()
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
series_formset = context["image_formset"]
if series_formset.is_valid():
response = super().form_valid(form)
series_formset.instance = self.object
series_formset.save()
return response
else:
return super().form_invalid(form)
class CaseCollectionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
model = CaseCollection
form_class = CaseCollectionForm
template_name = "atlas/casecollection_form.html"
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_form_kwargs(self):
kwargs = super(CaseCollectionUpdate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(CaseCollectionUpdate, self).get_context_data(**kwargs)
if self.request.POST:
context["case_formset"] = CaseCollectionCaseFormSet(
self.request.POST,
self.request.FILES,
instance=self.object,
form_kwargs={"user": self.request.user, "collection": self.object},
)
context["case_formset"].full_clean()
else:
context["case_formset"] = CaseCollectionCaseFormSet(
instance=self.object,
form_kwargs={"user": self.request.user, "collection": self.object},
)
context["collection"] = context["casecollection"]
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
case_formset = context["case_formset"]
# logger.debug(formset.is_valid())
if case_formset.is_valid():
response = super().form_valid(form)
case_formset.instance = self.object
case_formset.save()
# restore exam orders
# for exam in self.object.exams.all():
# if exam in exam_orders and self.object in exam_orders[exam]:
# print(exam_orders[exam])
# exam.exam_questions.set(exam_orders[exam])
# exam.save()
return response
else:
return super().form_invalid(form)
class SeriesUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Series
form_class = SeriesForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_context_data(self, **kwargs):
context = super(SeriesUpdate, self).get_context_data(**kwargs)
if self.request.POST:
context["image_formset"] = SeriesImageFormSet(
self.request.POST, self.request.FILES, instance=self.object
)
context["image_formset"].full_clean()
else:
context["image_formset"] = SeriesImageFormSet(instance=self.object)
return context
def get_form_kwargs(self):
kwargs = super(SeriesUpdate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
image_formset = context["image_formset"]
# logger.debug(formset.is_valid())
if image_formset.is_valid():
response = super().form_valid(form)
image_formset.instance = self.object
image_formset.save()
return response
else:
return super().form_invalid(form)
class ResourceUpdate(LoginRequiredMixin, UpdateView):
model = Resource
form_class = ResourceForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
class ConditionUpdate(RevisionMixin, LoginRequiredMixin, UpdateView):
model = Condition
form_class = ConditionForm
class ConditionCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = Condition
form_class = ConditionForm
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
class FindingCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = Finding
form_class = FindingForm
class StructureUpdate(RevisionMixin, LoginRequiredMixin, UpdateView):
model = Structure
form_class = StructureForm
class StructureCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = Structure
form_class = StructureForm
class AtlasCreateBase(RevisionMixin, LoginRequiredMixin):
model = Case
form_class = CaseForm
def get_form_kwargs(self):
kwargs = super(AtlasCreateBase, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(AtlasCreateBase, self).get_context_data(**kwargs)
if self.request.POST:
context["series_formset"] = SeriesFormSet(
self.request.POST,
self.request.FILES,
form_kwargs={"user": self.request.user},
)
context["casedifferential_formset"] = CaseDifferentialFormSet(
self.request.POST, self.request.FILES
)
context["caseresource_formset"] = CaseResourceFormSet(
self.request.POST,
self.request.FILES,
form_kwargs={"user": self.request.user},
)
context["series_formset"].full_clean()
context["casedifferential_formset"].full_clean()
context["caseresource_formset"].full_clean()
else:
# context["series_formset"] = SeriesFormSet(instance=Case.objects.filter(author__id=self.request.user.id), queryset=Series.objects.filter(author__id=self.request.user.id))
context["series_formset"] = SeriesFormSet(
form_kwargs={"user": self.request.user}
)
context["casedifferential_formset"] = CaseDifferentialFormSet()
context["caseresource_formset"] = CaseResourceFormSet(
form_kwargs={"user": self.request.user}
)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
# Add exam objects
self.object.save()
form.instance.author.add(self.request.user.id)
# Check Cimar Case
#CimarAPI().get_study_by_uuid(self.object.cimar_uuid)
context = self.get_context_data(form=form)
series_formset = context["series_formset"]
casedifferential_formset = context["casedifferential_formset"]
caseresource_formset = context["caseresource_formset"]
if (
series_formset.is_valid()
and casedifferential_formset.is_valid()
and caseresource_formset.is_valid()
):
response = super().form_valid(form)
series_formset.instance = self.object
series_formset.save()
casedifferential_formset.instance = self.object
casedifferential_formset.save()
caseresource_formset.instance = self.object
caseresource_formset.save()
# If the normal submit button is pressed we save as normal
if "submit" in self.request.POST:
return response
# else we redirect to the clone url
else:
return redirect("atlas:case_clone", pk=self.object.pk)
else:
return super().form_invalid(form)
# @login_required
class AtlasCreate(AtlasCreateBase, CreateView):
def get_initial(self):
if "pk" in self.kwargs:
initial = super().get_initial()
exam = get_object_or_404(CaseCollection, pk=self.kwargs["pk"])
initial["exams"] = [exam.id]
return initial
## There has to be a better way...
# try:
# s = (i.pk for i in self.request.user.atlas_default.site.all())
# self.initial.update({"site": s})
# except AttributeError:
# pass
# return self.initial
class AtlasUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Case
form_class = CaseForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_form_kwargs(self):
kwargs = super(AtlasUpdate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(AtlasUpdate, self).get_context_data(**kwargs)
if self.request.POST:
context["series_formset"] = SeriesFormSet(
self.request.POST,
self.request.FILES,
instance=self.object,
form_kwargs={"user": self.request.user},
)
context["casedifferential_formset"] = CaseDifferentialFormSet(
self.request.POST, self.request.FILES, instance=self.object
)
context["caseresource_formset"] = CaseResourceFormSet(
self.request.POST,
self.request.FILES,
instance=self.object,
form_kwargs={"user": self.request.user},
)
context["series_formset"].full_clean()
context["casedifferential_formset"].full_clean()
context["caseresource_formset"].full_clean()
else:
context["series_formset"] = SeriesFormSet(
instance=self.object, form_kwargs={"user": self.request.user}
)
context["casedifferential_formset"] = CaseDifferentialFormSet(
instance=self.object
)
context["caseresource_formset"] = CaseResourceFormSet(
instance=self.object,
form_kwargs={"user": self.request.user},
)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
series_formset = context["series_formset"]
casedifferential_formset = context["casedifferential_formset"]
caseresource_formset = context["caseresource_formset"]
# logger.debug(formset.is_valid())
if (
series_formset.is_valid()
and casedifferential_formset.is_valid()
and caseresource_formset.is_valid()
):
response = super().form_valid(form)
series_formset.instance = self.object
series_formset.save()
casedifferential_formset.instance = self.object
casedifferential_formset.save()
caseresource_formset.instance = self.object
caseresource_formset.save()
return response
else:
return super().form_invalid(form)
def add_collection_to_case_form(request, case_id):
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)
return HttpResponse("Done")
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,)),
},
)
# class AddCollectionToCaseView(FormView):
# form_class = AddCollectionToCaseForm
# template_name = "generic_form.html"
#
# def form_valid(self, form):
# form.save()
# return super(AddCollectionToCaseView, self).form_valid(form)
#
# def get_form_kwargs(self):
# kwargs = super(AddCollectionToCaseView, self).get_form_kwargs()
# kwargs.update({"user": self.request.user})
# return kwargs
#
# def get_success_url(self) -> str:
# pk = self.kwargs["pk"]
# return reverse("atlas:case_detail", kwargs={"pk": pk})
class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
"""Clones a existing atlas"""
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_initial(self):
# print(self.request)
old_object = get_object_or_404(Case, pk=self.kwargs["pk"])
if (
self.request.user.groups.filter(name="atlas_editor").exists()
or self.request.user.is_superuser
):
pass
else:
if self.request.user not in old_object.get_author_objects():
raise PermissionDenied() # or Http404
else:
pass
initial_data = model_to_dict(old_object, exclude=["id", "previous_case"])
return initial_data
class AtlasCloneNext(AtlasClone):
"""Extends AtlasClone to set the previous_case field to the case being cloned"""
def get_initial(self):
initial_data = super().get_initial()
initial_data["previous_case"] = self.kwargs["pk"]
return initial_data
@login_required
@user_is_author_or_atlas_editor
def atlas_scrap(request, pk):
try:
atlas = Case.objects.get(pk=pk)
except Case.DoesNotExist:
raise Http404("Case does not exist")
atlas.scrapped = not atlas.scrapped
atlas.save()
return HttpResponseRedirect(reverse("atlas:case_detail", args=(pk,)))
# @login_required
# def edit_abnormality_popup(request):
# instance = get_object_or_404(Abnormality, pk = pk)
# form = AbnormalityForm(request.POST or None)
# if form.is_valid():
# instance = form.save()
# return HttpResponse('<script>opener.closePopup(window, "%s", "%s", "#id_abnormality");</script>' % (instance.pk, instance))
# return render(request, "atlas/create_simple.html", {'form': form})
@csrf_exempt
def get_examination_id(request):
if request.accepts("application/json")():
examination_name = request.GET["examination_name"]
examination_id = Examination.objects.get(name=examination_name).id
data = {
"examination_id": examination_id,
}
return HttpResponse(json.dumps(data), content_type="application/json")
return HttpResponse("/")
class CaseView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Case
table_class = CaseTable
template_name = "atlas/case_view.html"
filterset_class = CaseFilter
class SeriesView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Series
table_class = SeriesTable
template_name = "atlas/series_view.html"
filterset_class = SeriesFilter
class ConditionView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Condition
table_class = ConditionTable
template_name = "atlas/view.html"
filterset_class = ConditionFilter
class QuestionSchemaView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = QuestionSchema
table_class = QuestionSchemaTable
template_name = "atlas/view.html"
filterset_class = QuestionSchemaFilter
class SubspecialtyView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Subspecialty
table_class = SubspecialtyTable
template_name = "atlas/view.html"
filterset_class = SubspecialtyFilter
class PresentationView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Presentation
table_class = PresentationTable
template_name = "atlas/view.html"
filterset_class = PresentationFilter
class PathologicalProcessView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = PathologicalProcess
table_class = PathologicalProcessTable
template_name = "atlas/view.html"
filterset_class = PathologicalProcessFilter
class StructureView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Structure
table_class = StructureTable
template_name = "atlas/view.html"
filterset_class = StructureFilter
class FindingView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Finding
table_class = FindingTable
template_name = "atlas/view.html"
filterset_class = FindingFilter
@login_required
def case_order_dicom(request, pk):
if request.htmx:
case = get_object_or_404(Case, pk=pk)
fail = []
# TODO: don't try to order non stack series
for series in case.series.all():
try:
if request.user in series.get_author_objects():
series.order_by_dicom()
else:
fail.append(series.pk)
except:
fail.append(series.pk)
if fail:
return HttpResponse(
f"Unable to order series {','.join([str(i) for i in fail])}"
)
else:
return HttpResponse("Series reordered")
raise Http404
@login_required
@user_is_author_or_atlas_series_checker
def series_order_dicom(request, pk):
series = get_object_or_404(Series, pk=pk)
try:
series.order_by_dicom()
except:
return HttpResponse("<h1>Series does not appear to contain dicoms</h1>")
return redirect("atlas:series_detail", pk=pk)
@login_required
def image_diff(request):
if request.htmx:
image_ids = request.POST.getlist("image-id")
if len(image_ids) == 2:
first: SeriesImage = get_object_or_404(SeriesImage, pk=image_ids[0])
second: SeriesImage = get_object_or_404(SeriesImage, pk=image_ids[1])
first_ds = pydicom.read_file(first.image.file)
# del first_ds.PixelData
# j1 = first_ds.to_json_dict()
f = print_dicom(first_ds, join=False)
second_ds = pydicom.read_file(second.image.file)
# del second_ds.PixelData
# j2 = second_ds.to_json_dict()
s = print_dicom(second_ds, join=False)
diff = difflib.HtmlDiff().make_table(f, s)
dict_diff = pprint.pformat(compare_dicom_datasets(first_ds, second_ds))
# return HttpResponse(diff)
return HttpResponse(f"<pre>{dict_diff}</pre><br/>{diff}")
else:
ds_set = []
for pk in image_ids:
series_image = get_object_or_404(SeriesImage, pk=pk)
ds_set.append(pydicom.read_file(series_image.image.file))
dict_diff = pprint.pformat(compare_dicom_datasets(*ds_set))
return HttpResponse(f"<pre>{dict_diff}</pre>")
return HttpResponse("Fail")
@login_required
def series_image_dicom(request, pk):
image = get_object_or_404(SeriesImage, pk=pk)
return render(request, "atlas/series_image_dicom.html", {"image": image})
@login_required
@user_is_author_or_atlas_series_checker
def series_anonymise_dicom(request, pk):
series = get_object_or_404(Series, pk=pk)
series.anonymise_images()
return redirect("atlas:series_detail", pk=pk)
@login_required
@user_is_author_or_atlas_series_checker
def series_order_dicom_instance(request, pk):
series = get_object_or_404(Series, pk=pk)
try:
series.order_by_dicom("InstanceNumber")
except:
return HttpResponse(
"<h1>Series does not appear to contain dicoms (or field InstanceNumber)</h1>"
)
return redirect("atlas:series_detail", pk=pk)
@login_required
@user_is_author_or_atlas_series_checker
def series_order_dicom_SeriesInstanceUID(request, pk):
series = get_object_or_404(Series, pk=pk)
try:
series.order_by_dicom("SeriesInstanceUID")
except:
return HttpResponse(
"<h1>Series does not appear to contain dicoms (or field SeriesInstanceUID)</h1>"
)
return redirect("atlas:series_detail", pk=pk)
@login_required
@user_is_author_or_atlas_series_checker
def series_order_upload_filename(request, pk):
series = get_object_or_404(Series, pk=pk)
series.order_by_upload_filename()
return redirect("atlas:series_detail", pk=pk)
# Vendored in from BaseZipView
class SeriesImagesZipView(SeriesImagesZipViewBase, SuperuserRequiredMixin):
series_object = Series
# At the moment only the series owner / author can delete findings but anyone can create
def create_series_findings(request):
# posts = Post.objects.all()
response_data = {}
if request.POST.get("action") == "post":
series_finding_id = int(request.POST.get("series_finding_id"))
series_id = request.POST.get("series")
findings_ids = json.loads(request.POST.get("findings"))
structure_ids = json.loads(request.POST.get("structures"))
condition_ids = json.loads(request.POST.get("conditions"))
description = request.POST.get("description")
annotation_json = request.POST.get("annotation_json")
viewport_json = request.POST.get("viewport_json")
current_image_id_index = request.POST.get("current_image_id_index")
series = Series.objects.get(pk=series_id)
findings = Finding.objects.filter(pk__in=findings_ids)
structures = Structure.objects.filter(pk__in=structure_ids)
conditions = Condition.objects.filter(pk__in=condition_ids)
if series_finding_id > 0:
sf = get_object_or_404(SeriesFinding, pk=series_finding_id)
sf.series = series
sf.description = description
sf.annotation_json = annotation_json
sf.viewport_json = viewport_json
else:
sf = SeriesFinding.objects.create(
series=series,
description=description,
annotation_json=annotation_json,
viewport_json=viewport_json,
)
sf.findings.set(findings)
sf.structures.set(structures)
sf.conditions.set(conditions)
sf.current_image_id_index = current_image_id_index
sf.save()
return JsonResponse({"success": True})
return JsonResponse({"success": False})
return render(request, "create_post.html", {"posts": posts})
class ConditionAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Condition.objects.none()
qs = Condition.objects.all()
if self.q:
# This raises a fielderror which breaks creating a new item if not caught
try:
qs = qs.filter(name__icontains=self.q)
except FieldError:
return Condition.objects.none()
return qs
class ConditionAutocompleteRCRCurriculum(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Condition.objects.none()
qs = Condition.objects.filter(rcr_curriculum=True)
if self.q:
# This raises a fielderror which breaks creating a new item if not caught
try:
qs = qs.filter(name__icontains=self.q, rcr_curriculum=True)
except FieldError:
return Condition.objects.none()
return qs
class PresentationAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Presentation.objects.none()
qs = Presentation.objects.all()
if self.q:
# This raises a fielderror which breaks creating a new item if not caught
try:
qs = qs.filter(name__icontains=self.q)
except FieldError:
return Presentation.objects.none()
return qs
class FindingAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Finding.objects.none()
qs = Finding.objects.all()
if self.q:
# This raises a fielderror which breaks creating a new item if not caught
try:
qs = qs.filter(name__icontains=self.q)
except FieldError:
return Finding.objects.none()
return qs
class StructureAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return Structure.objects.none()
qs = Structure.objects.all()
if self.q:
# This raises a fielderror which breaks creating a new item if not caught
try:
qs = qs.filter(name__icontains=self.q)
except FieldError:
return Structure.objects.none()
return qs
@login_required
# @user_is_atlas_editor
def categories_list(request):
# condition = get_object_or_404(Condition, pk=pk)
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/categories_list.html",
# {
# "condition": condition,
# },
)
# def collection_view(request):
# collections = CaseCollection.objects.all()
#
# return render(
# request, "atlas/collection_view.html", {"collections": collections}
# )
class CollectionView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = CaseCollection
table_class = CaseCollectionTable
template_name = "atlas/view.html"
filterset_class = CaseCollectionFilter
@user_is_collection_author_or_atlas_editor
def collection_viva(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
cases = (
collection.cases.all()
.order_by("casedetail__sort_order")
.prefetch_related(
"series",
"series__images",
"series__contrast",
"series__plane",
"series__examination",
)
)
return render(
request,
"atlas/collection_viva.html",
{"collection": collection, "cases": cases, "can_edit": True},
)
@user_is_collection_author_or_atlas_editor
def collection_viva_case(request, pk, case_number):
collection = get_object_or_404(CaseCollection, pk=pk)
# cases = collection.cases.all().order_by("casedetail__sort_order")
current_case = collection.get_case_by_index(case_number)
return render(
request,
"atlas/collection_viva_case.html",
{"collection": collection, "current_case": current_case},
)
@user_is_collection_author_or_atlas_editor
def collection_viva_series(request, pk, series_id):
collection = get_object_or_404(CaseCollection, pk=pk)
series = get_object_or_404(Series, pk=series_id)
return render(
request,
"atlas/collection_viva_series.html",
{"collection": collection, "current_series": series},
)
@user_is_collection_author_or_atlas_editor
def collection_detail(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
# cases = collection.cases.all().order_by("casedetail__sort_order")
casedetails = CaseDetail.objects.filter(collection=collection).order_by(
"sort_order"
)
return render(
request,
"atlas/collection_detail.html",
{"collection": collection, "casesdetails": casedetails, "can_edit": True},
)
@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
Args:
request (_type_): _description_
pk (_type_): _description_
Returns:
_type_: _description_
"""
collection = get_object_or_404(CaseCollection, pk=pk)
valid_user = collection.check_logged_in_user(request)
# for now we only allow registered users....
if not valid_user:
raise PermissionDenied
cid_exam = None
if collection.collection_type == "REV" or valid_user:
cid_exam = collection.get_cid_user_exams(user_user=request.user).first()
template_variables = {
"collection": collection,
"valid_user": valid_user,
"cid_exam": cid_exam,
}
match collection.collection_type:
case x if x in ("REP", "QUE"):
return render(
request, "atlas/collection_take_start.html", template_variables
)
case "REV":
return render(
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.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"]
if not p.relation_text:
return HttpResponse(
"You need to enter text to describe the relationship between the cases"
)
p.save()
return HttpResponse(f"Case added")
else:
return HttpResponse("False")
raise Http404()
collection = case_detail.collection
added_priors = CasePrior.objects.filter(case_detail=case_detail)
added_relation_map = {}
for prior in added_priors:
added_relation_map[prior.prior_case] = prior.relation_text
case = case_detail.case
available_cases = case.get_all_prior_cases()
available_priors = []
for case in available_cases:
if case in added_relation_map:
available_priors.append((case, True, added_relation_map[case]))
else:
available_priors.append((case, False, ""))
form = PriorCaseForm()
case_number, case_count = collection.get_index_of_case(
case_detail.case, case_count=True
)
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,
"collection": collection,
"case": case_detail.case,
"previous": previous,
"next": next,
"collection_length": case_count,
"case_number": case_number,
"added_priors": added_priors,
"available_priors": available_priors,
},
)
@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)
collection = case_detail.collection
if not collection.collection_type == "QUE":
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
case_detail.question_answers = json.loads(request.POST.get("json_answer"))
case_detail.save()
example_form = JsonAnswerForm(
request.POST, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
# Called if the user saves the main form
elif request.POST.get("submit") == "save":
form = CaseDetailForm(request.POST, instance=case_detail)
if form.is_valid():
form.save()
# Add any additional logic or redirection here
# example_form = JsonAnswerForm(request.POST, question_schema=case_detail.question_schema)
post_data = request.POST.copy()
if (
case_detail.question_schema is not None
and case_detail.question_answers is not None
):
question_keys = set(case_detail.question_schema["properties"].keys())
answer_keys = set(case_detail.question_answers.keys())
answers = {}
for key, value in case_detail.question_answers.items():
if key in question_keys:
answers[key] = value
diff = question_keys - answer_keys
for key in diff:
answers[key] = ""
post_data["json_answer"] = json.dumps(answers)
case_detail.question_answers = answers
case_detail.save()
example_form = JsonAnswerForm(
post_data, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
# This shouldn't happen
else:
assert False
form = CaseDetailForm(request.POST, instance=case_detail)
else:
post_data = request.POST.copy()
post_data["json_answer"] = json.dumps(case_detail.question_answers)
example_form = JsonAnswerForm(
post_data, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
pass
# post_data = None
# if case_detail.question_schema is not None and case_detail.question_answers is not None:
# question_keys = set(case_detail.question_schema["properties"].keys())
# answer_keys = set(case_detail.question_answers.keys())
# answers = {}
# for key, value in case_detail.question_answers.items():
# if key in question_keys:
# answers[key] = value
# diff = question_keys - answer_keys
# for key in diff:
# answers[key] = ""
# post_data = request.POST.copy()
# post_data["json_answer"]= json.dumps(answers)
# case_detail.question_answers = answers
# example_form = JsonAnswerForm(post_data, question_schema=case_detail.question_schema)
# form = CaseDetailForm(instance=case_detail)
# blank_form = JsonAnswerForm(question_schema=case_detail.question_schema)
case_number, case_count = collection.get_index_of_case(
case_detail.case, case_count=True
)
previous = collection.get_previous_case(case_detail.case)
next = collection.get_next_case(case_detail.case)
return render(
request,
"atlas/collection_case_detail.html",
{
"case_detail": case_detail,
"form": form,
"example_form": example_form,
"collection": collection,
"case": case_detail.case,
"previous": previous,
"next": next,
"collection_length": case_count,
"case_number": case_number,
},
)
@user_is_collection_author_or_atlas_editor
def collection_mark_overview(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
if collection.collection_type == "REV":
raise Http404("Collection in review mode")
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case_details = CaseDetail.objects.filter(
case__in=cases, collection=collection
).prefetch_related("case")
answers = CidReportAnswer.objects.filter(question__in=case_details)
case_map = {}
for cd in case_details:
case_map[cd] = []
for answer in answers:
case_map[answer.question].append(answer)
return render(
request,
"atlas/collection_mark_overview.html",
{
"collection": collection,
"answers": answers,
"case_details": case_details,
"case_map": case_map,
},
)
@user_is_collection_author_or_atlas_editor
def collection_mark_question(request, pk, case_number):
collection = get_object_or_404(CaseCollection, pk=pk)
if collection.collection_type == "REV":
raise Http404("Collection in review mode")
case, case_count = collection.get_case_by_index(case_number, case_count=True)
previous = case_number > 0
next = case_number < (case_count - 1)
case_detail = CaseDetail.objects.filter(case=case, collection=collection)
answers = CidReportAnswer.objects.filter(question__in=case_detail)
answer_forms = []
if request.method == "POST":
for answer in answers:
if answer:
form = CidReportAnswerMarkForm(
request.POST, instance=answer, prefix=answer.id
)
else:
form = CidReportAnswerMarkForm(request.POST, prefix=answer.id)
if form.is_valid():
answer = form.save(commit=False)
# answer.cid = cid
# answer.question = case_detail
# answer.published_date = timezone.now()
answer.save()
kwargs = {"pk": pk}
if "save" in request.POST:
return redirect(
"atlas:collection_mark_question", case_number=case_number, **kwargs
)
if "next" in request.POST:
return redirect(
"atlas:collection_mark_question", case_number=case_number + 1, **kwargs
)
elif "previous" in request.POST:
return redirect(
"atlas:collection_mark_question", case_number=case_number - 1, **kwargs
)
elif "goto" in request.POST:
return redirect(
"atlas:collection_mark_question",
case_number=int(request.POST.get("goto")),
**kwargs,
)
else:
for answer in answers:
form = CidReportAnswerMarkForm(instance=answer, prefix=answer.pk)
answer_forms.append((answer, form))
series_list = case.series.all().prefetch_related("images", "examination", "plane")
return render(
request,
"atlas/collection_mark_question.html",
{
"collection": collection,
"series_list": series_list,
"answers": answers,
"case_detail": case_detail,
"case": case,
"case_number": case_number,
"answer_forms": answer_forms,
"previous": previous,
"next": next,
},
)
@user_is_collection_author_or_atlas_editor
def exam_cids(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
if (
request.user in collection.author.all()
or request.user.groups.filter(name="cid_user_manager").exists()
):
cid_users = collection.valid_cid_users.all()
cid_user_count = cid_users.count()
return render(
request,
"atlas/exam_cids.html",
{
"collection": collection,
"cid_users": cid_users,
"cid_user_count": cid_user_count,
},
)
raise PermissionDenied
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
):
"""
Summary page that shows which questions a user has attempted
(with / without answers)
Args:
request (_type_): _description_
pk (int): _description_
cid (int | None, optional): _description_. Defaults to None.
passcode (str | None, optional): _description_. Defaults to None.
Returns:
_type_: _description_
"""
collection = get_object_or_404(CaseCollection, pk=pk)
collection.check_user_can_take(cid, passcode, request.user)
cid_user_exam = collection.get_or_create_cid_user_exam(
cid=cid, user_user=request.user
)
if request.htmx:
if "finish" in request.POST:
cid_user_exam.completed = True
cid_user_exam.save()
collection.exam_user_status.create(
cid_user_exam=cid_user_exam,
status="submitted",
extra="manual submission",
)
return HttpResponse("True")
else:
raise Http404()
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case_details = CaseDetail.objects.filter(case__in=cases, collection=collection)
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:
answer_question_map[ans.question] = ans
question_answer_tuples = []
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
):
question_answer_tuples.append((cd, answer_question_map[cd], self_review))
answer_count += 1
else:
question_answer_tuples.append((cd, None, self_review))
return render(
request,
"atlas/collection_take_overview.html",
{
"collection": collection,
"cid": cid,
"question_answer_tuples": question_answer_tuples,
"answer_count": answer_count,
"collection_length": len(cases),
"passcode": passcode,
"cid_user_exam": cid_user_exam,
},
)
def collection_case_view_take_user_answers(request, pk: int, case_number: int):
return collection_case_view_take_answers(request, pk, case_number)
def collection_case_view_take_answers(
request, pk: int, case_number: int, cid=None, passcode=None
):
""" """
collection = get_object_or_404(CaseCollection, pk=pk)
collection.check_user_can_take(cid, passcode, request.user)
case, case_count = collection.get_case_by_index(case_number, case_count=True)
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)
return render(
request,
"atlas/collection_case_view_take_answers.html",
{
"collection": collection,
"case": case,
"case_detail": case_detail,
},
)
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
):
"""
Displays the page from which the user takes a case in the collection
Args:
request (_type_): _description_
pk (int): _description_
case_number (int): _description_
cid (_type_, optional): _description_. Defaults to None.
passcode (_type_, optional): _description_. Defaults to None.
Raises:
Http404: If user does not have access
Returns:
_type_: _description_
"""
collection = get_object_or_404(CaseCollection, pk=pk)
form = None
answer: None | CidReportAnswer | UserReportAnswer = None
collection.check_user_can_take(cid, passcode, request.user)
case, case_count = collection.get_case_by_index(case_number, case_count=True)
cid_user_exam = collection.get_or_create_cid_user_exam(
cid=cid, user_user=request.user
)
if cid_user_exam.start_time is None:
cid_user_exam.start_time = timezone.now()
cid_user_exam.save()
case_detail = CaseDetail.objects.get(case=case, collection=collection)
if not collection.review_only():
if collection.collection_type == "REP":
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
elif collection.collection_type == "QUE":
if cid is not None:
answer = case_detail.cidreportanswer_set.filter(cid=cid).first()
ReportAnswerForm = CidQuestionAnswerForm
else:
answer = case_detail.userreportanswer_set.filter(
user=request.user
).first()
ReportAnswerForm = UserQuestionAnswerForm
if request.method == "POST":
if collection.collection_type in ("REP", "QUE"):
if not collection.publish_results:
if answer:
form = ReportAnswerForm(
request.POST, instance=answer, case_detail=case_detail
)
else:
form = ReportAnswerForm(request.POST, case_detail=case_detail)
if not cid_user_exam.completed and not (
answer is not None and answer.completed
):
if form.is_valid():
answer = form.save(commit=False)
answer.set_cid_or_user(cid=cid, user=request.user)
answer.question = case_detail
# answer.published_date = timezone.now()
if "complete_case" in request.POST:
if not collection.self_review:
raise Http404("Self review not enabled")
answer.completed = True
answer.save()
cid_user_exam.end_time = timezone.now()
cid_user_exam.save()
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"
if "next" in request.POST:
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)
elif "finish" in request.POST:
return redirect(overview_url, **kwargs)
elif "goto" in request.POST:
return redirect(
redirect_url,
case_number=int(request.POST.get("goto")),
**kwargs,
)
else:
if collection.collection_type in ("REP", "QUE"):
form = ReportAnswerForm(instance=answer, case_detail=case_detail)
series_list = case.series.all().prefetch_related("images", "examination", "plane")
prior_cases = case_detail.caseprior_set.all()
series_to_load = []
for series in series_list:
series_to_load.append((series, False, ""))
for prior in prior_cases:
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)
# Set what details a user is / is not able to view when taking the case
# this can be configured via the collection edit menu
if (
collection.publish_results
or cid_user_exam.completed
or (
not collection.feedback_once_collection_complete
and answer is not None
and answer.completed
)
):
question_completed = True
show_title = collection.show_title_post
show_history = collection.show_history_post
show_description = collection.show_description_post
show_discussion = collection.show_discussion_post
show_report = collection.show_report_post
self_review = cid_user_exam.selfreview_set.filter(case=case)
resources = case.caseresource_set.all()
else:
question_completed = False
show_title = collection.show_title_pre
show_history = collection.show_history_pre
show_description = collection.show_description_pre
show_discussion = collection.show_discussion_pre
show_report = collection.show_report_pre
self_review = []
resources = case.caseresource_set.filter(pre_review=True)
return render(
request,
"atlas/collection_case_view_take.html",
{
"form": form,
"collection": collection,
"case": case,
"series_list": series_list,
"series_to_load": series_to_load,
"case_number": case_number,
"previous": previous,
"next": next,
"cid": cid,
"passcode": passcode,
"answer": answer,
"show_title": show_title,
"show_history": show_history,
"show_description": show_description,
"show_discussion": show_discussion,
"show_report": show_report,
"resources": resources,
"cid_user_exam": cid_user_exam,
"question_completed": question_completed,
"self_review": self_review,
},
)
@user_is_collection_author_or_atlas_editor
def collection_case_view(request, pk, case_number):
"""Edit/management overview of the case (as part of the collection)
Args:
request (_type_): _description_
pk (_type_): _description_
case_number (_type_): _description_
Raises:
Http404: _description_
Returns:
_type_: _description_
"""
collection = get_object_or_404(CaseCollection, pk=pk)
form = None
answer = None
case, case_count = collection.get_case_by_index(case_number, case_count=True)
series_list = case.series.all().prefetch_related("images", "examination", "plane")
previous = case_number > 0
next = case_number < (case_count - 1)
return render(
request,
"atlas/case_detail.html",
{
"form": form,
"collection": collection,
"collection_length": case_count,
"case": case,
"series_list": series_list,
"case_number": case_number,
"previous": previous,
"next": next,
"answer": answer,
"cimar_sid":request.user.userprofile.cimar_sid
},
)
def collection_dicom_json(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
return JsonResponse(collection.get_ohif_dicom_json())
@user_is_author_or_atlas_editor
def use_dates_as_descriptions(request, pk):
case = get_object_or_404(Case, pk=pk)
series: Series
for series in case.series.all():
series.use_date_as_description()
return HttpResponse("Done")
def collection_case_dicom_json(request, exam_id, case_id):
case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id)
priors = case_detail.caseprior_set.all()
return JsonResponse(case_detail.case.get_case_dicom_json(priors=priors))
def case_dicom_json(request, pk):
case = get_object_or_404(Case, pk=pk)
return JsonResponse(case.get_case_dicom_json())
def series_dicom_json(request, pk):
series = get_object_or_404(Series, pk=pk)
return JsonResponse(series.get_ohif_dicom_json())
@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)
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case_details = CaseDetail.objects.filter(
case__in=cases, collection=collection
).prefetch_related("case")
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)
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case_details = CaseDetail.objects.filter(
case__in=cases, collection=collection
).prefetch_related("case")
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):
collection = get_object_or_404(CaseCollection, pk=pk)
if not collection.exam_mode:
raise Http404("Collection not in exam mode")
user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
user_answers = defaultdict(list)
user_names = {}
# cid_passcodes = {}
by_question = defaultdict(dict)
unmarked = set()
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related()
case_details = CaseDetail.objects.filter(
case__in=cases, collection=collection
).prefetch_related("case")
cid_user_answers = CidReportAnswer.objects.filter(question__in=case_details)
cids = set()
case_list = list(cases)
# Loop through all candidates
for cid_user_answer in cid_user_answers:
# Convoluted (probably...)
cid = cid_user_answer.cid
# cid_passcodes[cid] = cid_user_answer.passcode
cids.add(cid)
s = cid_user_answer
user_names[cid] = cid
q = cid_user_answer.question.case
# if not s:
# # skip if no answer
# user_answers_marks[cid].append(0)
# user_answers[cid].append("")
# by_question[q].append(("", 0))
# continue
ans = s.answer
answer_score = s.get_answer_score()
if answer_score == "unmarked" or answer_score == None:
index = case_list.index(q)
unmarked.add(index)
user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_score)
user_answers_and_marks[cid].append((ans, answer_score))
by_question[q][cid] = (ans, answer_score, s.feedback)
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)]
)
user_scores_normalised[user] = user_scores[user]
# ignore scores of 0 for stats
user_scores_list = [i for i in user_scores.values() if i > 0]
max_score = len(cases)
if len(user_scores_list) < 1:
mean = 0
median = 0
mode = 0
fig_html = ""
else:
mean = statistics.mean(user_scores_list)
median = statistics.median(user_scores_list)
try:
mode = statistics.mode(user_scores_list)
except statistics.StatisticsError:
mode = "No unique mode"
df = user_scores_list
fig = px.histogram(
df,
x=0,
title="{}: distribution of scores".format(collection),
labels={"0": "Score"},
height=400,
width=600,
)
fig_html = fig.to_html()
collection.stats_mean = mean
collection.stats_median = median
collection.stats_mode = mode
collection.stats_candidates = len(user_scores_list)
collection.stats_max_possible = max_score
collection.stats_min = min(user_scores_list)
collection.stats_max = max(user_scores_list)
# collection.stats_graph = fig_html
collection.user_scores = user_scores
collection.save()
return render(
request,
f"atlas/collection_scores.html",
{
"cids": sorted(cids),
# "cid_passcodes": cid_passcodes,
"exam": collection,
"collection": collection,
"unmarked": unmarked,
"questions": cases,
"by_question": by_question,
"user_answers": dict(user_answers),
"user_answers_marks": dict(user_answers_marks),
"user_scores": user_scores,
"user_scores_normalised": user_scores_normalised,
"user_scores_list": user_scores_list,
"user_names": user_names,
"user_answers_and_marks": user_answers_and_marks,
"max_score": max_score,
"mean": mean,
"median": median,
"mode": mode,
"plot": fig_html,
},
)
GenericExamViews = ExamViews(
CaseCollection, Case, None, CidReportAnswer, "atlas", "casecollection"
)
class CollectionCloneMixin:
def get_initial(self):
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
initial_data = model_to_dict(old_object, exclude=["id"])
# We manually transfer the forign keys / m2m relationships
questions = old_object.cases.all().values_list("id", flat=True)
authors = old_object.author.all().values_list("id", flat=True)
self.cases = list(questions)
self.author = list(authors)
return initial_data
def form_valid(self, form):
object = form.save()
# Reapply these otherwise they get lost?
object.cases.set(self.cases)
object.author.set(self.author)
object.save()
return HttpResponseRedirect(object.get_absolute_url())
class CollectionClone(CollectionCloneMixin, CaseCollectionCreate):
"""Clone exam view"""
class SelfReviewDelete(DeleteView):
model = SelfReview
template_name = "confirm_delete.html"
# success_url = reverse_lazy("atlas:finding_view")
def get_initial(self):
user_exam_id = self.kwargs["user_exam_id"]
case_id = self.kwargs["case_id"]
review_id = self.kwargs["pk"]
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
self.case = get_object_or_404(Case, pk=case_id)
self.review = get_object_or_404(SelfReview, pk=review_id)
return {"user_exam": user_exam_id, "case": case_id, "review": review_id}
def get_context_data(self, **kwargs):
# This can also be accessed via view.**** in the template
context = super(SelfReviewDelete, self).get_context_data(**kwargs)
context["user_exam"] = self.user_exam
context["case"] = self.case
context["review"] = self.review
return context
def get_success_url(self) -> str:
return self.user_exam.exam.get_case_take_url(self.case)
class SelfReviewUpdate(UpdateView):
model = SelfReview
form_class = SelfReviewForm
def get_initial(self):
user_exam_id = self.kwargs["user_exam_id"]
case_id = self.kwargs["case_id"]
review_id = self.kwargs["pk"]
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
self.case = get_object_or_404(Case, pk=case_id)
self.review = get_object_or_404(SelfReview, pk=review_id)
return {"user_exam": user_exam_id, "case": case_id, "review": review_id}
def get_context_data(self, **kwargs):
# This can also be accessed via view.**** in the template
context = super(SelfReviewUpdate, self).get_context_data(**kwargs)
context["user_exam"] = self.user_exam
context["case"] = self.case
context["review"] = self.review
return context
def get_success_url(self) -> str:
return self.user_exam.exam.get_case_take_url(self.case)
# def get_object(self):
# return self.review
# return
# def get_initial(self):
# user_exam_id = self.kwargs["user_exam_id"]
# case_id = self.kwargs["case_id"]
# self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
# self.case = get_object_or_404(Case, pk=case_id)
# return {"user_exam": user_exam_id, "case": case_id}
class AddSelfReview(CreateView):
model = SelfReview
form_class = SelfReviewForm
# fields = ("author", "note_type", "note")
def get_form_kwargs(self):
kwargs = super(AddSelfReview, self).get_form_kwargs()
# update the kwargs for the form init method with yours
kwargs.update(self.kwargs) # self.kwargs contains all url conf params
# kwargs.update({"user": self.request.user})
return kwargs
def get_initial(self):
user_exam_id = self.kwargs["user_exam_id"]
case_id = self.kwargs["case_id"]
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
self.case = get_object_or_404(Case, pk=case_id)
return {"user_exam": user_exam_id, "case": case_id}
def get_context_data(self, **kwargs):
# This can also be accessed via view.**** in the template
context = super(AddSelfReview, self).get_context_data(**kwargs)
context["user_exam"] = self.user_exam
context["case"] = self.case
return context
def get_success_url(self) -> str:
return self.user_exam.exam.get_case_take_url(self.case)
# return reverse("atlas:collection_take_overview_user", kwargs={"pk": self.user_exam.collection.id })
# def form_valid(self, form):
# model = form.save(commit=False)
#
# model.save()
#
# response = super().form_valid(form)
#
# return response
@login_required
def uncategorised_dicoms(request):
dicoms = UncategorisedDicom.objects.filter(user=request.user)
data = []
for d in dicoms:
data.append((d.image.name, d.get_basic_dicom_tags()))
return JsonResponse(data, safe=False)
@login_required
def combine_series(request):
"""Combines a list of series into the first series in the list
NOTE: this will only migrate images
it will not migrate other models such as SeriesFinding or other details
"""
series_ids = request.POST.getlist("series-ids")
# series = get_object_or_404(Series, pk=series_id)
if len(series_ids) < 2:
return HttpResponse("Not enough series to combine")
base_series = None
series_to_add = []
for n, series_id in enumerate(series_ids):
series = get_object_or_404(Series, pk=series_id)
if not series.check_user_can_edit(request.user):
return HttpResponse("You do not have permission to edit one of the series")
if n == 0:
base_series = series
else:
series_to_add.append(series)
for series in series_to_add:
for image in series.images.all():
image.series = base_series
image.save()
series.delete()
return HttpResponse(f"Series {series_ids} combines")
# return HttpResponse("Fail")
class CaseCollectionAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
model = CaseCollection
form_class = CaseCollectionAuthorForm
template_name = "author_form.html"
def get_context_data(self, **kwargs):
context = super(CaseCollectionAuthorUpdate, self).get_context_data(**kwargs)
context["collection"] = context["object"]
return context
class ExamGroupsUpdate(ExamGroupsUpdateBase):
model = CaseCollection
form_class = ExamGroupsForm
def get_context_data(self, **kwargs):
context = super(ExamGroupsUpdate, self).get_context_data(**kwargs)
context["exam"] = context["object"]
return context
class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
model = Case
form_class = CaseAuthorForm
template_name = "author_form.html"
def get_context_data(self, **kwargs):
context = super(CaseAuthorUpdate, self).get_context_data(**kwargs)
context["collection"] = context["object"]
return context
class SeriesAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
model = Series
form_class = SeriesAuthorForm
template_name = "author_form.html"
def get_context_data(self, **kwargs):
context = super(SeriesAuthorUpdate, self).get_context_data(**kwargs)
context["collection"] = context["object"]
return context
def question_schemas_preset(request):
schemas = [
json.dumps(
{
"type": "object",
"title": "Questions",
"required": ["laterality", "diagnosis"],
"properties": {
"test": {
"type": "string",
"title": "test string",
"description": "What side is the lesion on?",
},
"diagnosis": {
"enum": [
"Meningioma",
"Schwannoma",
"Neurofibroma",
"Ependymoma",
],
"type": "string",
"title": "Diagnosis",
"description": "What is the diagnosis?",
},
"laterality": {
"enum": ["left", "right"],
"type": "string",
"title": "Laterality",
"widget": "radio",
"description": "What side is the lesion on?",
},
"laterality (copy 2)": {
"enum": ["left", "right"],
"type": "string",
"title": "Lateralityaoue",
"widget": "radio",
"description": "What side is the lesion on?",
},
},
}
),
]
return render(
request,
"atlas/question_schemas_preset.html",
{
"schemas": schemas,
},
)
def collection_question_schemas(request, exam_id: int):
collection = get_object_or_404(CaseCollection, pk=exam_id)
questions = collection.cases.all().prefetch_related()
case_details = (
CaseDetail.objects.filter(case__in=questions, collection=collection)
.prefetch_related("case")
.order_by("sort_order")
)
return render(
request,
"atlas/collection_question_schemas.html",
{
"collection": collection,
"case_details": case_details,
},
)
def collection_reset_answers_user_list(request, exam_id: int):
if request.htmx:
collection = get_object_or_404(CaseCollection, pk=exam_id)
cid_users = collection.cid_users.all()
if cid_users:
return render(request, "atlas/collection_reset_answers_user_list.html", {"collection": collection, "cid_users": cid_users})
else:
return HttpResponse("No user answers.")
else:
raise Http404("Invalid request")
def collection_reset_answers_user(request, exam_id: int, user_id: int):
if request.htmx:
collection = get_object_or_404(CaseCollection, pk=exam_id)
# Select all case details (answers are linked to these)
case_details = collection.casedetail_set.all().prefetch_related()
# Delete all answers
for case in case_details:
user_answers = case.userreportanswer_set.filter(user_id=user_id)
user_answers.delete()
collection.cid_users.filter(user_user_id=user_id).delete()
collection.exam_user_status.filter(cid_user_exam__user_user_id=user_id).delete()
return HttpResponse("Success")
else:
raise Http404("Invalid request")
def collection_reset_answers(request, exam_id: int):
if request.htmx:
collection = get_object_or_404(CaseCollection, pk=exam_id)
# Select all case details (answers are linked to these)
case_details = collection.casedetail_set.all().prefetch_related()
# Delete all answers
for case in case_details:
cid_answers = case.cidreportanswer_set.all()
cid_answers.delete()
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")
def push_case_to_cimar(request, case_id):
case = get_object_or_404(Case, pk=case_id)
api = CimarAPI()
api.login(username=CIMAR_USERNAME, password=CIMAR_PASSWORD)
# We use the same study_uid for all images in a case
study_uid = generate_uid()
for series in case.get_series():
for image in series.images.all():
data = api.upload_dicom(image.image.path, study_uid=study_uid)
cimar_uuid = api.get_study_by_study_uid(data["study_uid"])["uuid"]
case.cimar_uuid = cimar_uuid
case.save()
cimar_case, created = CimarCase.objects.get_or_create(uuid=cimar_uuid)
cimar_case.refresh_study()
return HttpResponse("Success")