597 lines
18 KiB
Python
Executable File
597 lines
18 KiB
Python
Executable File
import json
|
|
from dal import autocomplete
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django import forms
|
|
|
|
# from django.contrib.auth.models import User
|
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
from django.contrib.auth.models import User
|
|
from django.core.exceptions import (
|
|
FieldError,
|
|
ObjectDoesNotExist,
|
|
PermissionDenied,
|
|
ViewDoesNotExist,
|
|
)
|
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
|
from django.views.generic.detail import DetailView
|
|
from generic.mixins import SuperuserRequiredMixin
|
|
|
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
|
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 .forms import (
|
|
CaseForm,
|
|
SeriesForm,
|
|
SeriesImageFormSet,
|
|
SeriesFormSet,
|
|
CaseDifferentialFormSet,
|
|
ExaminationForm,
|
|
SeriesFindingForm,
|
|
)
|
|
from .models import (
|
|
Case,
|
|
Condition,
|
|
Series,
|
|
Examination,
|
|
Finding,
|
|
Subspecialty,
|
|
SeriesFinding,
|
|
SeriesImage,
|
|
)
|
|
from .tables import AtlasTable, SeriesTable
|
|
from .filters import AtlasFilter, SeriesFilter
|
|
|
|
from django_tables2 import SingleTableView, SingleTableMixin
|
|
from django_filters.views import FilterView
|
|
|
|
from .decorators import (
|
|
user_is_author_or_atlas_checker,
|
|
user_is_author_or_atlas_checker_or_atlas_marker,
|
|
user_is_author_or_atlas_series_checker_or_atlas_marker,
|
|
user_is_atlas_checker,
|
|
user_is_author_or_atlas_series_checker,
|
|
user_is_atlas_marker,
|
|
user_is_author_or_atlas_checker_or_atlas_marker,
|
|
)
|
|
|
|
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 image_as_base64
|
|
|
|
import logging
|
|
from copy import deepcopy
|
|
from django.forms.models import model_to_dict
|
|
|
|
from generic.views import ExamCloneMixin, ExamViews
|
|
from reversion.views import RevisionMixin
|
|
import reversion
|
|
|
|
|
|
from rest_framework import viewsets
|
|
|
|
from zipview.views import BaseZipView
|
|
|
|
import os
|
|
|
|
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_checker").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
|
|
|
|
|
|
@login_required
|
|
@user_is_author_or_atlas_checker_or_atlas_marker
|
|
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})
|
|
|
|
|
|
@login_required
|
|
@user_is_author_or_atlas_series_checker_or_atlas_marker
|
|
def series_detail(request, pk):
|
|
series = get_object_or_404(Series, pk=pk)
|
|
|
|
# if request.user not in atlas.author.all():
|
|
# raise PermissionDenied
|
|
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},
|
|
)
|
|
|
|
|
|
@login_required
|
|
@user_is_author_or_atlas_checker
|
|
def author_detail(request, pk):
|
|
# logging.debug(Author.objects.all())
|
|
# author = get_object_or_404(Author, pk=pk)
|
|
author = User.objects.get(pk=pk)
|
|
|
|
atlas = Case.objects.filter(author=pk)
|
|
|
|
return render(
|
|
request, "atlas/category_detail.html", {"category": author, "atlas": atlas}
|
|
)
|
|
|
|
|
|
@user_is_author_or_atlas_checker
|
|
def author_list(request):
|
|
authors = User.objects.all()
|
|
|
|
return render(request, "atlas/author_list.html", {"authors": authors})
|
|
|
|
|
|
class AtlasDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
|
model = Case
|
|
success_url = reverse_lazy("atlas:case_view")
|
|
|
|
|
|
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_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, AuthorOrCheckerRequiredMixin, DeleteView):
|
|
model = Series
|
|
success_url = reverse_lazy("atlas:series_view")
|
|
|
|
|
|
# @login_required
|
|
# @user_is_author_or_atlas_checker
|
|
# def case_clone(request, pk):
|
|
# new_item = get_object_or_404(Case, pk=pk)
|
|
# new_item.pk = None # autogen a new pk (item_id)
|
|
# # new_item.name = "Copy of " + new_item.name #need to change uniques
|
|
#
|
|
# form = CaseForm(request.POST or None, instance=new_item)
|
|
#
|
|
# series_formset = SeriesFormSet()
|
|
#
|
|
# if form.is_valid():
|
|
# form.instance.author.add(request.user.id)
|
|
#
|
|
# # logger.debug(formset.is_valid())
|
|
# if series_formset.is_valid():
|
|
# response = super().form_valid(form)
|
|
# series_formset.instance = obj
|
|
# series_formset.save()
|
|
#
|
|
# return response
|
|
# else:
|
|
# return super().form_invalid(form)
|
|
#
|
|
# context = {
|
|
# "form": form,
|
|
# "series_formset": series_formset,
|
|
# # other context
|
|
# }
|
|
#
|
|
# return render(request, "atlas/case_form.html", context)
|
|
|
|
|
|
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_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 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 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 AtlasCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
|
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
|
|
)
|
|
context["casedifferential_formset"] = CaseDifferentialFormSet(
|
|
self.request.POST, self.request.FILES
|
|
)
|
|
context["series_formset"].full_clean()
|
|
context["casedifferential_formset"].full_clean()
|
|
else:
|
|
context["series_formset"] = SeriesFormSet()
|
|
context["casedifferential_formset"] = CaseDifferentialFormSet()
|
|
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)
|
|
|
|
context = self.get_context_data(form=form)
|
|
series_formset = context["series_formset"]
|
|
casedifferential_formset = context["casedifferential_formset"]
|
|
if series_formset.is_valid() and casedifferential_formset.is_valid():
|
|
response = super().form_valid(form)
|
|
series_formset.instance = self.object
|
|
series_formset.save()
|
|
casedifferential_formset.instance = self.object
|
|
casedifferential_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):
|
|
def get_initial(self):
|
|
# 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
|
|
# )
|
|
# context["series_formset"].full_clean()
|
|
# else:
|
|
# context["series_formset"] = SeriesFormSet(instance=self.object)
|
|
# 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"]
|
|
# # logger.debug(formset.is_valid())
|
|
# 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 AtlasClone(AtlasCreateBase):
|
|
"""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"])
|
|
initial_data = model_to_dict(old_object, exclude=["id"])
|
|
|
|
return initial_data
|
|
|
|
|
|
@login_required
|
|
@user_is_author_or_atlas_checker
|
|
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})
|
|
|
|
|
|
@login_required
|
|
def create_examination(request):
|
|
form = ExaminationForm(request.POST or None)
|
|
if form.is_valid():
|
|
instance = form.save()
|
|
return HttpResponse(
|
|
'<script>opener.closePopup(window, "%s", "%s", "#id_examination");</script>'
|
|
% (instance.pk, instance)
|
|
)
|
|
return render(
|
|
request, "atlas/create_simple.html", {"form": form, "name": "Examination"}
|
|
)
|
|
|
|
|
|
@csrf_exempt
|
|
def get_examination_id(request):
|
|
if request.is_ajax():
|
|
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 = AtlasTable
|
|
template_name = "atlas/view.html"
|
|
|
|
filterset_class = AtlasFilter
|
|
|
|
|
|
class SeriesView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
|
model = Series
|
|
table_class = SeriesTable
|
|
template_name = "atlas/view.html"
|
|
|
|
filterset_class = SeriesFilter
|
|
|
|
|
|
@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
|
|
@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)
|
|
|
|
|
|
class SeriesImagesZipView(SuperuserRequiredMixin, BaseZipView):
|
|
"""Download all images from an image series"""
|
|
|
|
def get_files(self):
|
|
series = Series.objects.get(pk=self.kwargs["pk"])
|
|
|
|
return [i.image.file for i in series.images.all()]
|
|
|
|
|
|
def create_series_findings(request):
|
|
# posts = Post.objects.all()
|
|
response_data = {}
|
|
|
|
if request.POST.get("action") == "post":
|
|
series_id = request.POST.get("series")
|
|
findings_ids = json.loads(request.POST.get("findings"))
|
|
description = request.POST.get("description")
|
|
annotation_json = request.POST.get("annotation_json")
|
|
viewport_json = request.POST.get("viewport_json")
|
|
|
|
series = Series.objects.get(pk=series_id)
|
|
findings = Finding.objects.filter(pk__in=findings_ids)
|
|
|
|
sf = SeriesFinding.objects.create(
|
|
series=series,
|
|
description=description,
|
|
annotation_json=annotation_json,
|
|
viewport_json=viewport_json,
|
|
)
|
|
sf.findings.set(findings)
|
|
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:
|
|
primary = qs.filter(name__icontains=self.q)
|
|
|
|
# synonyms = qs.filter(name__icontains=self.q, primary_name=False)
|
|
# for s in synonyms:
|
|
# primary.extend(s.synonym.filter(primary_name=True))
|
|
|
|
except FieldError:
|
|
return Condition.objects.none()
|
|
|
|
return primary
|