start atlas app
This commit is contained in:
Executable
+58
@@ -0,0 +1,58 @@
|
||||
from django.contrib import admin
|
||||
from .models import Case, Series, SeriesImage
|
||||
|
||||
import tagulous.admin
|
||||
|
||||
from django.forms import ModelForm
|
||||
|
||||
from reversion.admin import VersionAdmin
|
||||
from django.forms.widgets import RadioSelect
|
||||
|
||||
from tinymce.widgets import TinyMCE
|
||||
|
||||
# from generic.models import Examination, Sign, Site, Condition
|
||||
from generic.models import Examination, Sign, Condition
|
||||
|
||||
# Register your models here.
|
||||
# admin.site.register(Examination)
|
||||
# admin.site.register(Site)
|
||||
admin.site.register(SeriesImage)
|
||||
|
||||
|
||||
|
||||
class AtlasAdminForm(ModelForm):
|
||||
class Meta:
|
||||
model = Case
|
||||
|
||||
fields = [
|
||||
"description",
|
||||
"history",
|
||||
"sign",
|
||||
"findings",
|
||||
"author",
|
||||
"series",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
# "normal": RadioSelect(
|
||||
# choices=[(True, 'Yes'),
|
||||
# (False, 'No')])
|
||||
"findings" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
|
||||
"mark_scheme" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
|
||||
}
|
||||
|
||||
class AtlasAdmin(VersionAdmin):
|
||||
form = AtlasAdminForm
|
||||
|
||||
save_on_top = True
|
||||
save_as = True
|
||||
view_on_site = True
|
||||
|
||||
|
||||
admin.site.register(Case, AtlasAdmin)
|
||||
|
||||
|
||||
class SeriesAdmin(VersionAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(Series, SeriesAdmin)
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AtlasConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'atlas'
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from .models import Atlas, Series
|
||||
|
||||
def user_is_author_or_atlas_series_checker_or_atlas_marker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
series = Series.objects.get(pk=kwargs['pk'])
|
||||
if request.user in series.get_author_objects() or request.user.groups.filter(name='atlas_checker').exists() or request.user.groups.filter(name='atlas_marker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
|
||||
def user_is_author_or_atlas_series_checker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
series = Series.objects.get(pk=kwargs['pk'])
|
||||
if request.user in series.get_author_objects() or request.user.groups.filter(name='atlas_checker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
|
||||
def user_is_author_or_atlas_checker_or_atlas_marker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
atlas = Atlas.objects.get(pk=kwargs['pk'])
|
||||
if request.user in atlas.author.all() or request.user.groups.filter(name='atlas_checker').exists() or request.user.groups.filter(name='atlas_marker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
|
||||
def user_is_author_or_atlas_checker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
atlas = Atlas.objects.get(pk=kwargs['pk'])
|
||||
if request.user in atlas.author.all() or request.user.groups.filter(name='atlas_checker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
|
||||
def user_is_atlas_checker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
if request.user.groups.filter(name='atlas_checker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
|
||||
def user_is_atlas_marker(function):
|
||||
def wrap(request, *args, **kwargs):
|
||||
if request.user.groups.filter(name='atlas_marker').exists() or request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
wrap.__doc__ = function.__doc__
|
||||
wrap.__name__ = function.__name__
|
||||
return wrap
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
import django_filters
|
||||
|
||||
from .models import Case, Series
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
def get_authors(request):
|
||||
if request is None:
|
||||
return User.objects.none()
|
||||
|
||||
if not request.user.groups.filter(name="atlas_checker").exists():
|
||||
queryset = User.objects.filter(id=request.user.id)
|
||||
else:
|
||||
queryset = User.objects.all()
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class AtlasFilter(django_filters.FilterSet):
|
||||
author = django_filters.ModelMultipleChoiceFilter(queryset=get_authors, null_label="No author")
|
||||
class Meta:
|
||||
model = Case
|
||||
fields = (#"site",
|
||||
"created_date", "open_access", "author")
|
||||
|
||||
def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None):
|
||||
if not request.user.groups.filter(name="atlas_checker").exists():
|
||||
#queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id)
|
||||
queryset = queryset.filter(author__id=request.user.id)
|
||||
super(AtlasFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request)
|
||||
pass
|
||||
|
||||
class SeriesFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Series
|
||||
fields = ("modality","atlas", "examination", "author")
|
||||
|
||||
def __init__(self, data=None, queryset=None, prefix=None, strict=None, user=None, request=None):
|
||||
if not request.user.groups.filter(name="atlas_checker").exists():
|
||||
#queryset = queryset.filter(open_access=True) | queryset.filter(author__id=request.user.id)
|
||||
queryset = queryset.filter(author__id=request.user.id)
|
||||
super(SeriesFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request)
|
||||
pass
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
from django.forms import (
|
||||
Form,
|
||||
ModelForm,
|
||||
ModelMultipleChoiceField,
|
||||
ModelChoiceField,
|
||||
ChoiceField,
|
||||
CharField,
|
||||
)
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from atlas.models import (
|
||||
Case,
|
||||
Series,
|
||||
SeriesImage,
|
||||
)
|
||||
|
||||
from anatomy.models import Modality
|
||||
|
||||
from generic.models import Examination, Condition, Sign
|
||||
#from generic.models import Examination, Site, Condition, Sign
|
||||
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||
|
||||
from tinymce.widgets import TinyMCE
|
||||
|
||||
class ExaminationForm(ModelForm):
|
||||
class Meta:
|
||||
model = Examination
|
||||
fields = ["examination"]
|
||||
|
||||
class SeriesForm(ModelForm):
|
||||
class Media:
|
||||
# Django also includes a few javascript files necessary
|
||||
# for the operation of this form element. You need to
|
||||
# include <script src="/admin/jsi18n"></script>
|
||||
# in the template.
|
||||
css = {
|
||||
"all": ["css/widgets.css"],
|
||||
}
|
||||
# Adding this javascript is crucial
|
||||
js = ["jsi18n.js", "tesseract.min.js"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SeriesForm, self).__init__(*args, **kwargs)
|
||||
#self.fields["examination"] = ModelChoiceField(
|
||||
# queryset=Examination.objects.all(),
|
||||
# widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
|
||||
#)
|
||||
|
||||
self.fields["modality"] = ModelChoiceField(
|
||||
required=True,
|
||||
queryset=Modality.objects.all(),
|
||||
)
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = Series
|
||||
exclude = ["author"]
|
||||
|
||||
|
||||
class AtlasForm(ModelForm):
|
||||
|
||||
#exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False))
|
||||
|
||||
class Media:
|
||||
# Django also includes a few javascript files necessary
|
||||
# for the operation of this form element. You need to
|
||||
# include <script src="/admin/jsi18n"></script>
|
||||
# in the template.
|
||||
css = {
|
||||
"all": ["css/widgets.css"],
|
||||
}
|
||||
# Adding this javascript is crucial
|
||||
js = ["jsi18n.js", "tesseract.min.js"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user') # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||
if kwargs.get("instance"):
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
initial = kwargs.setdefault("initial", {})
|
||||
# The widget for a ModelMultipleChoiceField expects
|
||||
# a list of primary key for the selected data.
|
||||
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
|
||||
super(AtlasForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Case
|
||||
# fields = ['due_back']
|
||||
# fields = '__all__'
|
||||
fields = [
|
||||
#"examination",
|
||||
#"site",
|
||||
"description",
|
||||
"history",
|
||||
"findings",
|
||||
"condition",
|
||||
"sign",
|
||||
"open_access",
|
||||
]
|
||||
# fields = ['question', 'findings', 'subspecialty', 'references']
|
||||
widgets = {
|
||||
# "normal": RadioSelect(
|
||||
# choices=[(True, 'Yes'),
|
||||
# (False, 'No')])
|
||||
"findings" : TinyMCE(attrs={'cols': 80, 'rows': 20}),
|
||||
"mark_scheme" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
|
||||
"description" : Textarea(attrs={'cols': 80, 'rows': 5}),
|
||||
"history" : Textarea(attrs={'cols': 80, 'rows': 5}),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
# Get the unsaved Case instance
|
||||
instance = ModelForm.save(self, False)
|
||||
|
||||
# Prepare a 'save_m2m' method for the form,
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
old_save_m2m()
|
||||
#instance.exams.clear()
|
||||
#for exam in self.cleaned_data["exams"]:
|
||||
# # We need the id here
|
||||
# instance.exams.add(exam.pk)
|
||||
|
||||
self.save_m2m = save_m2m
|
||||
|
||||
# Do we need to save all changes now?
|
||||
# if commit:
|
||||
instance.save()
|
||||
self.save_m2m()
|
||||
|
||||
return instance
|
||||
|
||||
SeriesFormSet = inlineformset_factory(
|
||||
Case,
|
||||
Series.case.through,
|
||||
exclude=[],
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
max_num=10,
|
||||
field_classes="testing",
|
||||
)
|
||||
|
||||
|
||||
SeriesImageFormSet = inlineformset_factory(
|
||||
Series,
|
||||
SeriesImage,
|
||||
exclude=[],
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
max_num=2000,
|
||||
field_classes="testing",
|
||||
)
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
import json
|
||||
from rad.settings import REMOTE_URL
|
||||
from django.db.models.fields.files import ImageField
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
from django.db import models
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
import tagulous
|
||||
import tagulous.models
|
||||
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.conf import settings
|
||||
from django.utils.html import format_html
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
import string
|
||||
from collections import defaultdict
|
||||
from helpers.images import image_as_base64, pretty_print_dicom
|
||||
|
||||
from anatomy.models import Modality
|
||||
|
||||
from generic.models import Examination, Condition, Sign, ExamBase, Plane, Contrast, QuestionNote
|
||||
|
||||
# from generic.models import Examination, Site, Condition, Sign
|
||||
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
from easy_thumbnails.exceptions import InvalidImageFormatError
|
||||
|
||||
import pydicom
|
||||
import pydicom.errors
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
import reversion
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
|
||||
image_storage = FileSystemStorage(
|
||||
# Physical file location ROOT
|
||||
location=u"{0}atlas/".format(settings.MEDIA_ROOT),
|
||||
# Url for file
|
||||
base_url=u"{0}atlas/".format(settings.MEDIA_URL),
|
||||
)
|
||||
|
||||
|
||||
def image_directory_path(instance, filename):
|
||||
return u"atlas/picture/{0}".format(filename)
|
||||
|
||||
|
||||
def findMiddle(input_list):
|
||||
middle = float(len(input_list)) / 2
|
||||
if middle % 2 != 0:
|
||||
return input_list[int(middle - 0.5)]
|
||||
else:
|
||||
return input_list[int(middle)]
|
||||
return (input_list[int(middle)], input_list[int(middle - 1)])
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Case(models.Model):
|
||||
class SubspecialtyChoices(models.TextChoices):
|
||||
BREAST = 'BR', _('Breast')
|
||||
CARDIAC = 'CA', _('Cardiac')
|
||||
GASTRO = 'GI', _('Gastrointestinal and hepatobiliary')
|
||||
HEADNECK = 'HN', _('Head and Neck')
|
||||
MSK = 'MS', _('Musculoskeletal')
|
||||
NEURO = 'NE', _('Neuroradiology')
|
||||
OBSGYN = 'OG', _('Obstectric and Gynaecological')
|
||||
PAED = 'PA', _('Paediatric')
|
||||
URO = 'UR', _('Uroradiology')
|
||||
VASC = 'VA', _('Vascular')
|
||||
HAEMONC = 'HA', _('Haemotology and Oncology')
|
||||
|
||||
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||
# image = models.ImageField()
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Description of the case, for admin organisation, will not be visible when taking",
|
||||
)
|
||||
|
||||
history = models.TextField(null=True, blank=True)
|
||||
|
||||
findings = models.TextField(null=True, blank=True)
|
||||
|
||||
subpecialty = models.CharField(max_length=2, choices=SubspecialtyChoices.choices)
|
||||
|
||||
condition = tagulous.models.TagField(
|
||||
to=Condition,
|
||||
blank=True,
|
||||
help_text='Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes "..."',
|
||||
)
|
||||
|
||||
sign = tagulous.models.TagField(
|
||||
to=Sign, blank=True, help_text="Radiological signs in the question"
|
||||
)
|
||||
|
||||
verified = models.BooleanField(default=False)
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
published_date = models.DateTimeField(blank=True, null=True)
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Author of question",
|
||||
related_name="atlas_authored_questions",
|
||||
)
|
||||
|
||||
scrapped = models.BooleanField(
|
||||
default=False, help_text="Question has been scrapped and will not be shown"
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
series = SortedManyToManyField("Series", related_name="atlas")
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
|
||||
#question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("atlas:case_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_authors(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = ", ".join([i.username for i in self.author.all()])
|
||||
return authors
|
||||
|
||||
def get_author_objects(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = [i for i in self.author.all()]
|
||||
return authors
|
||||
|
||||
def __str__(self):
|
||||
examinations = [series.get_examination() for series in self.series.all()]
|
||||
return "{}/{} : {}".format(self.pk, self.description, ", ".join(examinations))
|
||||
|
||||
def get_images(self, findings=False):
|
||||
qs = self.images.all()
|
||||
|
||||
if not findings:
|
||||
images = [i.image for i in qs if not i.findings_image]
|
||||
else:
|
||||
images = [i.image for i in qs]
|
||||
|
||||
return images
|
||||
|
||||
def get_image_urls(self):
|
||||
return ",".join(
|
||||
["https://www.penracourses.org.uk{}".format(i.url) for i in self.get_images()]
|
||||
)
|
||||
|
||||
def get_image_url_array(self):
|
||||
return json.dumps(["https://www.penracourses.org.uk{}".format(i.url) for i in self.get_images()])
|
||||
|
||||
|
||||
class SeriesImage(models.Model):
|
||||
image = models.FileField(upload_to=image_directory_path)
|
||||
position = models.IntegerField(default=0)
|
||||
upload_filename = models.CharField(max_length=255, blank=True)
|
||||
series = models.ForeignKey(
|
||||
"Series", related_name="images", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['position']
|
||||
|
||||
def get_dicom_info(self):
|
||||
try:
|
||||
info = pretty_print_dicom(pydicom.read_file(self.image))
|
||||
except pydicom.errors.InvalidDicomError:
|
||||
info = "File is not a dicom."
|
||||
return(info)
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Series(models.Model):
|
||||
modality = models.ForeignKey(
|
||||
Modality, related_name="series_modality", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
examination = models.ForeignKey(
|
||||
Examination,
|
||||
help_text="Name of the examination",
|
||||
related_name="series_examination",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
)
|
||||
plane = models.ForeignKey(
|
||||
Plane,
|
||||
help_text="Plane of the examination",
|
||||
related_name="series_plane",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
contrast = models.ForeignKey(
|
||||
Contrast,
|
||||
help_text="MRI / CT contrast",
|
||||
related_name="series_contrast",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Description of stack, for admin organisation, will not be visible when taking",
|
||||
)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
related_name="series",
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if self.atlas:
|
||||
atlas_id = ", ".format([atlas.pk for atlas in self.atlas.all()])
|
||||
#atlas_id = self.atlas.pk
|
||||
else:
|
||||
atlas_id = "None"
|
||||
return "{}/{} : {} [{}]".format(self.pk,
|
||||
self.get_examination(), self.description, atlas_id
|
||||
)
|
||||
|
||||
def get_author_objects(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
if self.author:
|
||||
return self.author.all()
|
||||
else:
|
||||
return ["None"]
|
||||
if self.atlas:
|
||||
authors = [i for i in self.atlas.author.all()]
|
||||
else:
|
||||
authors = []
|
||||
|
||||
return authors
|
||||
|
||||
def get_author_display(self):
|
||||
return ", ".join([i.username for i in self.get_author_objects()])
|
||||
|
||||
def get_examination(self):
|
||||
"""Returns a comma seperated text list of regions"""
|
||||
return str(self.examination)
|
||||
|
||||
def get_examination_full(self):
|
||||
examination = ""
|
||||
plane = ""
|
||||
contrast = ""
|
||||
|
||||
if self.examination:
|
||||
examination = self.examination
|
||||
if self.plane:
|
||||
plane = " {}".format(self.plane)
|
||||
if self.contrast:
|
||||
contrast = " {}".format(self.contrast)
|
||||
return "{}{}{}".format(examination, plane, contrast)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("atlas:series_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_image_urls(self, findings=False):
|
||||
images = [
|
||||
"https://www.penracourses.org.uk{}".format(i.image.url)
|
||||
for i in self.images.all()
|
||||
]
|
||||
|
||||
return ",".join(images)
|
||||
|
||||
def get_image_url_array(self, findings=False):
|
||||
images = [
|
||||
"https://www.penracourses.org.uk{}".format(i.image.url)
|
||||
for i in self.images.all()
|
||||
]
|
||||
|
||||
return json.dumps(images)
|
||||
|
||||
def get_thumbnail(self):
|
||||
images = self.images.all()
|
||||
|
||||
if len(images) < 1:
|
||||
return "No images", 0
|
||||
|
||||
img = findMiddle(images).image
|
||||
try:
|
||||
thumbnailer = get_thumbnailer(img)
|
||||
thumbnail = thumbnailer["exam-list"]
|
||||
except InvalidImageFormatError:
|
||||
return format_html('<span title="{}">Invalid image url<span>', img), len(images)
|
||||
return format_html('<img src="/media/{}" />', thumbnail), len(images)
|
||||
|
||||
def get_block(self):
|
||||
examination = self.get_examination_full()
|
||||
thumb, image_number = self.get_thumbnail()
|
||||
return format_html(
|
||||
"<div>{}<br/>{}<br/>Images: {}</div>", examination, thumb, image_number
|
||||
)
|
||||
|
||||
def order_by_upload_filename(self):
|
||||
images = self.images.all()
|
||||
|
||||
filenames = []
|
||||
map = {}
|
||||
|
||||
for i in images:
|
||||
filenames.append(i.upload_filename)
|
||||
map[i.upload_filename] = i
|
||||
|
||||
filenames = sorted(filenames)
|
||||
|
||||
n = 1
|
||||
for f in filenames:
|
||||
i = map[f]
|
||||
i.position = n
|
||||
i.save()
|
||||
n = n + 1
|
||||
|
||||
|
||||
|
||||
def order_by_dicom(self, field="SliceLocation"):
|
||||
images = self.images.all()
|
||||
|
||||
files = []
|
||||
|
||||
for i in images:
|
||||
files.append((i, pydicom.dcmread(i.image.path)))
|
||||
|
||||
#print("file count: {}".format(len(files)))
|
||||
|
||||
# skip files with no SliceLocation (eg scout views)
|
||||
slices = []
|
||||
map = {}
|
||||
skipcount = 0
|
||||
for i, f in files:
|
||||
if hasattr(f, field):
|
||||
slices.append(f)
|
||||
#map[f.SliceLocation] = i
|
||||
map[f[field].value] = i
|
||||
else:
|
||||
skipcount = skipcount + 1
|
||||
|
||||
print("skipped, no {}: {}".format(field, skipcount))
|
||||
|
||||
# ensure they are in the correct order
|
||||
slices = sorted(slices, key=lambda s: s[field].value)
|
||||
|
||||
|
||||
#print(slices)
|
||||
n = 1
|
||||
for f in slices:
|
||||
i = map[f[field].value]
|
||||
i.position = n
|
||||
i.save()
|
||||
n = n + 1
|
||||
|
||||
def get_total_image_size(self):
|
||||
images = self.images.all()
|
||||
|
||||
size = 0
|
||||
|
||||
for i in images:
|
||||
size += i.image.size
|
||||
|
||||
return size
|
||||
@@ -0,0 +1,11 @@
|
||||
# serializers.py
|
||||
from rest_framework import serializers, permissions
|
||||
|
||||
|
||||
from .models import Exam
|
||||
|
||||
class ExamSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Exam
|
||||
fields = ('name', 'id', 'active', "publish_results")
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import A
|
||||
|
||||
from .models import Case, Series
|
||||
|
||||
from django.utils.html import format_html
|
||||
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
|
||||
from easy_thumbnails.exceptions import InvalidImageFormatError
|
||||
|
||||
|
||||
class AtlasImageColumn(tables.Column):
|
||||
def render(self, value):
|
||||
blocks =[]
|
||||
for obj in value.all():
|
||||
blocks.append(obj.get_block())
|
||||
return format_html("<span class='multi-image-block'>{}</span>".format("".join(blocks)))
|
||||
|
||||
obj = value.first()
|
||||
|
||||
if obj is None:
|
||||
return format_html("<span>No image<span>")
|
||||
|
||||
return obj.get_thumbnail()[0]
|
||||
|
||||
# image_object = obj.image
|
||||
# try:
|
||||
# thumbnailer = get_thumbnailer(image_object)
|
||||
# return format_html('<img src="/media/{}" />', thumbnailer["exam-list"])
|
||||
# except InvalidImageFormatError:
|
||||
# return format_html('<span title="{}">Invalid image url<span>', image_object)
|
||||
|
||||
|
||||
class SeriesImageColumn(tables.Column):
|
||||
def render(self, value):
|
||||
obj = value.first()
|
||||
|
||||
if obj is None:
|
||||
return "None"
|
||||
|
||||
image_object = obj.image
|
||||
try:
|
||||
thumbnailer = get_thumbnailer(image_object)
|
||||
return format_html('<img src="/media/{}" /><br/>[{}]', thumbnailer["exam-list"], value.count())
|
||||
except InvalidImageFormatError:
|
||||
return format_html('<span title="{}">Invalid image url<span><br/>[{}]', image_object, value.count())
|
||||
|
||||
|
||||
class AtlasTable(tables.Table):
|
||||
edit = tables.LinkColumn(
|
||||
"atlas:atlas_update", text="Edit", args=[A("pk")], orderable=False
|
||||
)
|
||||
view = tables.LinkColumn(
|
||||
"atlas:case_detail", text="View", args=[A("pk")], orderable=False
|
||||
)
|
||||
clone = tables.LinkColumn(
|
||||
"atlas:case_clone", text="Clone", args=[A("pk")], orderable=False
|
||||
)
|
||||
delete = tables.LinkColumn(
|
||||
"atlas:atlas_delete", text="Delete", args=[A("pk")], orderable=False
|
||||
)
|
||||
series = AtlasImageColumn("Images", orderable=False)
|
||||
|
||||
#series = tables.ManyToManyColumn(verbose_name="Exams")
|
||||
exams = tables.ManyToManyColumn(verbose_name="Exams")
|
||||
|
||||
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
|
||||
|
||||
class Meta:
|
||||
model = Case
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
fields = ("description", "history", "created_date", "author")
|
||||
sequence = ("view", "series", "exams")
|
||||
|
||||
class PopupLinkColumn(tables.Column):
|
||||
def render(self, value):
|
||||
return format_html("<span>test: {}<span>", value)
|
||||
obj = value.first()
|
||||
|
||||
if obj is None:
|
||||
return format_html("<span>No image<span>")
|
||||
|
||||
return obj.get_thumbnail()[0]
|
||||
|
||||
def create_link(test):
|
||||
print(test)
|
||||
return "Hello"
|
||||
|
||||
class SeriesTable(tables.Table):
|
||||
edit = tables.LinkColumn(
|
||||
"atlas:series_update", text="Edit", args=[A("pk")], orderable=False
|
||||
)
|
||||
view = tables.LinkColumn(
|
||||
"atlas:series_detail", text="View", args=[A("pk")], orderable=False
|
||||
)
|
||||
popup = tables.Column(empty_values=(), orderable=False)
|
||||
|
||||
images = SeriesImageColumn("Images", orderable=False)
|
||||
|
||||
delete = tables.LinkColumn(
|
||||
"atlas:series_delete", text="Delete", args=[A("pk")], orderable=False
|
||||
)
|
||||
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
|
||||
|
||||
class Meta:
|
||||
model = Series
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
fields = ("modality", "examination", "atlas", "description", "author")
|
||||
sequence = ("view", "popup", "images")
|
||||
|
||||
def render_popup(self, value, record):
|
||||
print(self)
|
||||
return format_html("""<a href="#" onclick="return window.create_popup_window('/atlas/series/{}', 'Series')" >Popup</a>""", record.pk)
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
Executable
+191
@@ -0,0 +1,191 @@
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
|
||||
app_name = "atlas"
|
||||
|
||||
urlpatterns = [
|
||||
# path('', views.question_list, name='question_list'),
|
||||
path("", views.AtlasExamViews.index, name="index"),
|
||||
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
||||
path("author/", views.author_list, name="author_list"),
|
||||
path("question/", views.CaseView.as_view(), name="atlas_view"),
|
||||
path("series/", views.SeriesView.as_view(), name="series_view"),
|
||||
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
||||
path(
|
||||
"series/<int:pk>/order_dicom",
|
||||
views.series_order_dicom,
|
||||
name="series_order_dicom",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/download",
|
||||
views.SeriesImagesZipView.as_view(),
|
||||
name="series_download",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/order_dicom_instance",
|
||||
views.series_order_dicom_instance,
|
||||
name="series_order_dicom_instance",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/order_dicom_SeriesInstanceUID",
|
||||
views.series_order_dicom_SeriesInstanceUID,
|
||||
name="series_order_dicom_SeriesInstanceUID",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/order_upload_filename",
|
||||
views.series_order_upload_filename,
|
||||
name="series_order_upload_filename",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/delete",
|
||||
views.SeriesDelete.as_view(),
|
||||
name="series_delete",
|
||||
),
|
||||
# path("unchecked/", views.unchecked_list, name="unchecked_list"),
|
||||
# path("verified/<int:pk>/", views.verified_detail, name="verified_detail"),
|
||||
path("question/<int:pk>/", views.case_detail, name="case_detail"),
|
||||
path("question/<int:pk>/json", views.question_json, name="question_json"),
|
||||
path(
|
||||
"question/<int:pk>/json/unbased",
|
||||
views.question_json_unbased,
|
||||
name="question_json_unbased",
|
||||
),
|
||||
path(
|
||||
"question/<int:pk>/json/recreate",
|
||||
views.question_json_recreate,
|
||||
name="question_json_recreate",
|
||||
),
|
||||
path("question/<int:pk>/split", views.atlas_split, name="atlas_split"),
|
||||
path("question/<int:pk>/clone", views.AtlasClone.as_view(), name="case_clone"),
|
||||
# path("verified/", views.verified, name="verified"),
|
||||
# path("all_questions/", views.all_questions, name="all_questions"),
|
||||
path("question/<int:pk>/scrap", views.atlas_scrap, name="atlas_scrap"),
|
||||
path("question/<int:pk>/delete", views.AtlasDelete.as_view(), name="atlas_delete"),
|
||||
path(
|
||||
"exam/<int:pk>/review",
|
||||
views.question_review,
|
||||
name="question_review",
|
||||
),
|
||||
path("exam/<int:exam_id>/<int:question_number>/<int:cid>/mark", views.mark_answer, name="mark_answer"),
|
||||
path("exam/<int:exam_id>/<int:question_number>/<int:cid>/mark_override", views.mark_answer_override, name="mark_answer_override"),
|
||||
path("exam/<int:exam_id>/<int:sk>/mark", views.mark_question_overview, name="mark_question_overview"),
|
||||
path("exam/<int:pk>/mark", views.AtlasExamViews.mark_overview, name="mark_overview"),
|
||||
# path("exam/<int:pk>/<int:sk>/", views.exam_take, name="exam_take"),
|
||||
path(
|
||||
"exam/<int:pk>/question/<int:sk>/",
|
||||
views.AtlasExamViews.exam_case_detail,
|
||||
name="exam_case_detail",
|
||||
),
|
||||
path("exam/<int:pk>/", views.AtlasExamViews.exam_overview, name="exam_overview"),
|
||||
path(
|
||||
"exam/<int:pk>/json_edit",
|
||||
views.AtlasExamViews.exam_json_edit,
|
||||
name="exam_json_edit",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/question_json_refresh",
|
||||
views.refresh_exam_question_json,
|
||||
name="refresh_exam_question_json",
|
||||
),
|
||||
path("exam/<int:pk>/scores", views.exam_scores_cid, name="exam_scores_cid"),
|
||||
path(
|
||||
"exam/<int:pk>/scores/<int:sk>/",
|
||||
views.exam_scores_cid_user,
|
||||
name="exam_scores_cid_user",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/toggle_active",
|
||||
views.AtlasExamViews.exam_toggle_active,
|
||||
name="exam_toggle_active",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/toggle_results_published",
|
||||
views.AtlasExamViews.exam_toggle_results_published,
|
||||
name="exam_toggle_results_published",
|
||||
),
|
||||
path(
|
||||
"exam/submit", views.AtlasExamViews.postExamAnswers, name="exam_answers_submit"
|
||||
),
|
||||
path("exam/", views.AtlasExamViews.exam_list, name="exam_list"),
|
||||
path("exam/all", views.AtlasExamViews.exam_list_all, name="exam_list_all"),
|
||||
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
|
||||
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
|
||||
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
|
||||
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||
path("exam/json/", views.AtlasExamViews.active_exams, name="active_exams"),
|
||||
path("exam/json/<int:pk>", views.AtlasExamViews.exam_json, name="exam_json"),
|
||||
path(
|
||||
"exam/json/<int:pk>/unbased",
|
||||
views.AtlasExamViews.exam_json_unbased,
|
||||
name="exam_json_unbased",
|
||||
),
|
||||
path(
|
||||
"exam/json/<int:pk>/<int:sk>",
|
||||
views.AtlasExamViews.exam_question_json,
|
||||
name="exam_question_json",
|
||||
),
|
||||
path(
|
||||
"exam/json/<int:pk>/unbased/<int:sk>",
|
||||
views.AtlasExamViews.exam_question_json_unbased,
|
||||
name="exam_question_json_unbased",
|
||||
),
|
||||
path(
|
||||
"exam/json/<int:pk>/recreate",
|
||||
views.AtlasExamViews.exam_json_recreate,
|
||||
name="exam_json_recreate",
|
||||
),
|
||||
path("create/", views.AtlasCreate.as_view(), name="create"),
|
||||
path("create/series", views.SeriesCreate.as_view(), name="series_create"),
|
||||
path(
|
||||
"create/series/<int:pk>",
|
||||
views.SeriesCreate.as_view(),
|
||||
name="series_id_create",
|
||||
),
|
||||
path(
|
||||
"create/defaults",
|
||||
views.AtlasCreationDefaultView.as_view(),
|
||||
name="create_defaults",
|
||||
),
|
||||
# path("region/create/", views.create_region, name="create_region"),
|
||||
# path("region/ajax/get_region_id", views.get_region_id, name="get_region_id"),
|
||||
# path("abnormality/create/", views.create_abnormality, name="create_abnormality"),
|
||||
# path(
|
||||
# "abnormality/ajax/get_abnormality_id",
|
||||
# views.get_abnormality_id,
|
||||
# name="get_abnormality_id",
|
||||
# ),
|
||||
path("examination/create/", views.create_examination, name="create_examination"),
|
||||
path(
|
||||
"examination/ajax/get_examination_id",
|
||||
views.get_examination_id,
|
||||
name="get_examination_id",
|
||||
),
|
||||
path(
|
||||
"question/<int:pk>/update",
|
||||
views.AtlasUpdate.as_view(),
|
||||
name="atlas_update",
|
||||
),
|
||||
path(
|
||||
"series/<int:pk>/update",
|
||||
views.SeriesUpdate.as_view(),
|
||||
name="series_update",
|
||||
),
|
||||
path(
|
||||
"user_answers/",
|
||||
views.UserAnswerTableView.as_view(),
|
||||
name="user_answer_table_view",
|
||||
),
|
||||
path(
|
||||
"user_answers/<int:pk>", views.UserAnswerView.as_view(), name="user_answer_view"
|
||||
),
|
||||
path(
|
||||
"user_answers/<int:pk>/delete",
|
||||
views.UserAnswerDelete.as_view(),
|
||||
name="user_answer_delete",
|
||||
),
|
||||
path(
|
||||
"user_answers/delete",
|
||||
views.user_answer_delete_multiple,
|
||||
name="user_answer_delete_multiple",
|
||||
),
|
||||
]
|
||||
Executable
+547
@@ -0,0 +1,547 @@
|
||||
import json
|
||||
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 (
|
||||
ObjectDoesNotExist,
|
||||
PermissionDenied,
|
||||
ViewDoesNotExist,
|
||||
)
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
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 (
|
||||
AtlasForm,
|
||||
SeriesForm,
|
||||
SeriesImageFormSet,
|
||||
MarkAtlasQuestionDoubleForm,
|
||||
MarkAtlasQuestionSingleForm,
|
||||
SeriesFormSet,
|
||||
ExaminationForm,
|
||||
ExamForm,
|
||||
)
|
||||
from .models import (
|
||||
Case,
|
||||
Series,
|
||||
Examination,
|
||||
)
|
||||
from .tables import AtlasTable, SeriesTable, UserAnswerTable
|
||||
from .filters import AtlasFilter, SeriesFilter, UserAnswerFilter
|
||||
|
||||
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 atlas.forms import AtlasCreationDefaultForm
|
||||
from atlas.models import AtlasCreationDefault
|
||||
|
||||
from generic.views import ExamCloneMixin, ExamViews
|
||||
from reversion.views import RevisionMixin
|
||||
import reversion
|
||||
|
||||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from .serializers import ExamSerializer
|
||||
|
||||
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):
|
||||
atlas = 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", {"question": atlas})
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker_or_atlas_marker
|
||||
def atlas_series_detail(request, pk):
|
||||
series = get_object_or_404(Series, pk=pk)
|
||||
|
||||
# if request.user not in atlas.author.all():
|
||||
# raise PermissionDenied
|
||||
|
||||
# logging.debug(atlas.subspecialty.first().name.all())
|
||||
return render(request, "atlas/atlas_series.html", {"series": series})
|
||||
|
||||
|
||||
@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:atlas_view")
|
||||
|
||||
|
||||
class SeriesDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||
model = Series
|
||||
success_url = reverse_lazy("atlas: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 = AtlasForm(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/atlas_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()
|
||||
|
||||
# Do this in the form.save()
|
||||
# new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True))
|
||||
# self.object.exams.clear()
|
||||
# self.object.exams.add(*new_exams)
|
||||
|
||||
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 = AtlasForm
|
||||
|
||||
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["series_formset"].full_clean()
|
||||
else:
|
||||
context["series_formset"] = SeriesFormSet()
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
self.object = form.save(commit=False)
|
||||
|
||||
# Add exam objects
|
||||
|
||||
self.object.save()
|
||||
|
||||
# Do this in the form.save()
|
||||
# new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True))
|
||||
# self.object.exams.clear()
|
||||
# self.object.exams.add(*new_exams)
|
||||
|
||||
form.instance.author.add(self.request.user.id)
|
||||
|
||||
context = self.get_context_data(form=form)
|
||||
series_formset = context["series_formset"]
|
||||
if series_formset.is_valid():
|
||||
response = super().form_valid(form)
|
||||
series_formset.instance = self.object
|
||||
series_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 = AtlasForm
|
||||
|
||||
# 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):
|
||||
# save exam orders (there must be a better way to do this)
|
||||
exam_orders = {}
|
||||
for exam in self.object.exams.all():
|
||||
exam_orders[exam] = list(exam.exam_questions.all())
|
||||
|
||||
self.object = form.save(commit=False)
|
||||
self.object.save()
|
||||
|
||||
# Do this in the form.save()
|
||||
# new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True))
|
||||
# self.object.exams.clear()
|
||||
# self.object.exams.add(*new_exams)
|
||||
|
||||
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()
|
||||
|
||||
# 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 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 AtlasView(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 atlas_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:atlas_series_detail", pk=pk)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker
|
||||
def atlas_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:atlas_series_detail", pk=pk)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker
|
||||
def atlas_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:atlas_series_detail", pk=pk)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker
|
||||
def atlas_series_order_upload_filename(request, pk):
|
||||
series = get_object_or_404(Series, pk=pk)
|
||||
series.order_by_upload_filename()
|
||||
|
||||
return redirect("atlas: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()]
|
||||
|
||||
Reference in New Issue
Block a user