This commit is contained in:
Ross
2021-02-06 14:33:33 +00:00
parent d4b0ad6985
commit 666ec9013a
12 changed files with 205 additions and 80 deletions
@@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2021-02-06 09:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0027_anatomyquestion_examination'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='open_access',
field=models.BooleanField(default=True, help_text='If a question should be freely available to browse'),
),
migrations.AlterField(
model_name='anatomyquestion',
name='question_type',
field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.questiontype'),
),
]
+2 -2
View File
@@ -68,7 +68,7 @@ class AnatomyQuestion(models.Model):
# image = models.ImageField()
# feedback = models.TextField(null=True)
question_type = models.ForeignKey(
QuestionType, on_delete=models.SET_NULL, null=True
QuestionType, on_delete=models.SET_NULL, null=True, default=1
)
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
@@ -108,7 +108,7 @@ class AnatomyQuestion(models.Model):
created_date = models.DateTimeField(default=timezone.now)
open_access = models.BooleanField(
help_text="If an question should be freely available to browse", default=True
help_text="If a question should be freely available to browse", default=True
)
author = models.ManyToManyField(
@@ -0,0 +1,26 @@
# Generated by Django 3.1.3 on 2021-02-06 09:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('longs', '0009_auto_20210205_1146'),
]
operations = [
migrations.AddField(
model_name='longseries',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='long_series', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='long',
name='open_access',
field=models.BooleanField(default=True, help_text='If a question should be freely available to browse'),
),
]
+114 -64
View File
@@ -22,9 +22,11 @@ import string
from anatomy.models import Modality
from generic.models import Examination, Condition, Sign
#from generic.models import Examination, Site, Condition, Sign
# from generic.models import Examination, Site, Condition, Sign
from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
image_storage = FileSystemStorage(
# Physical file location ROOT
@@ -33,21 +35,23 @@ image_storage = FileSystemStorage(
base_url=u"{0}longs/".format(settings.MEDIA_URL),
)
def image_directory_path(instance, filename):
return u"picture/{0}".format(filename)
def findMiddle(input_list):
middle = float(len(input_list))/2
middle = float(len(input_list)) / 2
if middle % 2 != 0:
return input_list[int(middle - .5)]
return input_list[int(middle - 0.5)]
else:
return (input_list[int(middle)])
return (input_list[int(middle)], input_list[int(middle-1)])
return input_list[int(middle)]
return (input_list[int(middle)], input_list[int(middle - 1)])
class Long(models.Model):
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
#image = models.ImageField()
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
# image = models.ImageField()
history = models.TextField(null=True, blank=True)
feedback = models.TextField(null=True, blank=True)
@@ -55,12 +59,12 @@ class Long(models.Model):
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 \"...\""
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')
to=Sign, blank=True, help_text="Radiological signs in the question"
)
# Model answers
model_observations = models.TextField(null=True, blank=True)
@@ -69,9 +73,8 @@ class Long(models.Model):
model_differential_diagnosis = models.TextField(null=True, blank=True)
model_management = models.TextField(null=True, blank=True)
DEFAULT_SITE_ID = 1
#site = models.ManyToManyField(
# site = models.ManyToManyField(
# Site,
# blank=True,
# default=DEFAULT_SITE_ID,
@@ -80,22 +83,23 @@ class Long(models.Model):
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="long_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 an question should be freely available to browse", default=True
author = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
help_text="Author of question",
related_name="long_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
)
def get_absolute_url(self):
return reverse('longs:long_detail', kwargs={'pk': self.pk})
return reverse("longs:long_detail", kwargs={"pk": self.pk})
def get_authors(self):
"""Returns a comma seperated text list of authors"""
@@ -116,7 +120,6 @@ class Long(models.Model):
exams = ", ".join(e)
return exams
def GetImages(self, feedback=False):
qs = self.images.all()
@@ -128,28 +131,64 @@ class Long(models.Model):
return images
def GetImageUrls(self):
return ",".join(["http://penracourses.org.uk{}".format(i.url) for i in self.GetImages()])
#def GetNonFeedbackQuestionImages(self):
#return self.GetImages()
return ",".join(
["http://penracourses.org.uk{}".format(i.url) for i in self.GetImages()]
)
# def GetNonFeedbackQuestionImages(self):
# return self.GetImages()
class LongSeriesImage(models.Model):
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
position = models.IntegerField(default=0)
series = models.ForeignKey("LongSeries", related_name="images", on_delete=models.SET_NULL, null=True)
series = models.ForeignKey(
"LongSeries", related_name="images", on_delete=models.SET_NULL, null=True
)
class LongSeries(models.Model):
modality = models.ForeignKey(Modality, related_name="series_modality", on_delete=models.SET_NULL, null=True)
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)
long = models.ForeignKey("Long", help_text="The question this series should be associated with", related_name="series", on_delete=models.SET_NULL, null=True)
description = models.TextField(blank=True, help_text="Description of stack, for admin organisation, will not be visible when taking")
Examination,
help_text="Name of the examination",
related_name="series_examination",
on_delete=models.SET_NULL,
null=True,
)
long = models.ForeignKey(
"Long",
help_text="The question this series should be associated with",
related_name="series",
on_delete=models.SET_NULL,
null=True,
)
description = models.TextField(
blank=True,
help_text="Description of stack, for admin organisation, will not be visible when taking",
)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
blank=True,
null=True,
related_name="long_series",
on_delete=models.CASCADE,
)
def __str__(self):
return "{} : {} [{}]".format(self.get_examination(), self.description, self.long.pk)
return "{} : {} [{}]".format(
self.get_examination(), self.description, self.long.pk
)
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.long:
authors = [i for i in self.long.author.all()]
else:
@@ -157,15 +196,21 @@ class LongSeries(models.Model):
return authors
def get_author_display(self):
return ", ".join(self.get_author_objects())
def get_examination(self):
"""Returns a comma seperated text list of regions"""
return str(self.examination)
def get_absolute_url(self):
return reverse('longs:long_series_detail', kwargs={'pk': self.pk})
return reverse("longs:long_series_detail", kwargs={"pk": self.pk})
def get_image_urls(self, feedback=False):
images = ["http://penracourses.org.uk{}".format(i.image.url) for i in self.images.all()]
images = [
"http://penracourses.org.uk{}".format(i.image.url)
for i in self.images.all()
]
return ",".join(images)
@@ -173,21 +218,25 @@ class LongSeries(models.Model):
images = self.images.all()
if len(images) < 1:
return 'No images', 0
return "No images", 0
img = findMiddle(images).image
thumbnailer = get_thumbnailer(img)
try:
thumbnailer = get_thumbnailer(img)
except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', image_object)
thumbnail = thumbnailer["exam-list"]
return '<img src="/media/{}" />'.format(thumbnail), len(images)
def get_block(self):
examination = self.get_examination()
thumb, image_number = self.get_thumbnail()
return format_html('<div>{}<br/>{}<br/>Images: {}</div>', examination, thumb, image_number )
return format_html(
"<div>{}<br/>{}<br/>Images: {}</div>", examination, thumb, image_number
)
#class LongImage(models.Model):
# class LongImage(models.Model):
# long = models.ForeignKey(Long,
# related_name="images",
# on_delete=models.CASCADE)
@@ -207,47 +256,43 @@ class LongSeries(models.Model):
class Note(models.Model):
long = models.ForeignKey(Long,
related_name="long_notes",
on_delete=models.CASCADE,
null=True)
#author = models.ForeignKey(User,
author = models.ForeignKey( settings.AUTH_USER_MODEL,
related_name="long_notes",
on_delete=models.CASCADE)
long = models.ForeignKey(
Long, related_name="long_notes", on_delete=models.CASCADE, null=True
)
# author = models.ForeignKey(User,
author = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="long_notes", on_delete=models.CASCADE
)
note = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
self.pk = self.long_id
return reverse('longs:long_detail', kwargs={'pk': self.pk})
return reverse("longs:long_detail", kwargs={"pk": self.pk})
def __str__(self):
return "{} [{}] {}".format(self.long, self.author, self.created_on)
class LongCreationDefault(models.Model):
#author = models.OneToOneField(User,
# author = models.OneToOneField(User,
author = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name="long_default",
on_delete=models.CASCADE)
settings.AUTH_USER_MODEL, related_name="long_default", on_delete=models.CASCADE
)
#site = models.ManyToManyField(
# site = models.ManyToManyField(
# Site,
# blank=True,
# default=1,
# help_text="Default site to use when creating a new long")
def get_absolute_url(self):
return reverse('longs:long_create')
return reverse("longs:long_create")
class Exam(models.Model):
name = models.CharField(max_length=200)
exam_questions = SortedManyToManyField(
Long, related_name="exams", blank="true"
)
exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
active = models.BooleanField(
help_text="If an exam should be available", default=True
@@ -262,7 +307,8 @@ class Exam(models.Model):
)
time_limit = models.IntegerField(
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
default=2100,
)
def __str__(self):
@@ -280,6 +326,7 @@ class Exam(models.Model):
def get_question_index(self, question):
return list(self.exam_questions.all()).index(question)
class CidUserAnswer(models.Model):
"""User answers by candidate"""
@@ -287,7 +334,6 @@ class CidUserAnswer(models.Model):
Long, related_name="cid_user_answers", on_delete=models.CASCADE
)
# Answers
answer_observations = models.TextField(null=True, blank=True)
answer_interpretation = models.TextField(null=True, blank=True)
@@ -295,7 +341,11 @@ class CidUserAnswer(models.Model):
answer_differential_diagnosis = models.TextField(null=True, blank=True)
answer_management = models.TextField(null=True, blank=True)
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
cid = models.BigIntegerField(
blank=True,
null=True,
help_text="Candidate ID (limitied by BigIntegerField size)",
)
# Each user answer is associated with a particular exam
exam = models.ForeignKey(
@@ -323,7 +373,7 @@ class CidUserAnswer(models.Model):
self.answer = self.answer.strip()
s = self.answer.lower()
s = s.translate(str.maketrans('', '', string.punctuation))
s = s.translate(str.maketrans("", "", string.punctuation))
self.answer_compare = s
+10 -8
View File
@@ -17,12 +17,14 @@ class ImageColumn(tables.Column):
if obj is None:
return format_html('<span>No image<span>')
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)
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 LongTable(tables.Table):
@@ -38,11 +40,11 @@ class LongTable(tables.Table):
text='Clone',
args=[A('pk')],
orderable=False)
images = ImageColumn("images", orderable=False)
series = ImageColumn("images", orderable=False)
class Meta:
model = Long
template_name = "django_tables2/bootstrap4.html"
fields = ("normal", "abnormality", "region", "examination",
"laterality", "site", "created_date", "author")
sequence = ("view", "images")
sequence = ("view", "series")
+1 -1
View File
@@ -19,7 +19,7 @@ Longs:
{% if request.user.is_authenticated %}
<a href="{% url 'longs:exam_list' %}">Exams</a> /
<a href="{% url 'longs:long_view' %}">Questions</a> /
<a href="{% url 'longs:long_create' %}" title="Create a new question">Create Question</a>
<a href="{% url 'longs:long_create' %}" title="Create a new long case">Create Case</a> /
<a href="{% url 'longs:long_series_create' %}" title="Create a new image series">Create Series</a>
{% endif %}
{% comment %} </br>
@@ -18,7 +18,8 @@
>Popup</a>
</span>
{% endfor %}
<a href="{% url 'longs:long_series_id_create' pk=question.pk %}">Add series</a>
<span><a href="{% url 'longs:long_series_id_create' pk=question.pk %}">Add new series</a><br/>
<a href="{% url 'longs:long_series_id_create' pk=question.pk %}">Add existing series</a></span>
</div>
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
<p><b>Author(s):</b> {% for author in question.author.all %} <a
+1 -1
View File
@@ -30,7 +30,7 @@
{% endblock %}
{% block content %}
<h2>Submit Long Case</h2>
Use this form to create a long case. Once the case has been created it can be associated with image sets (series)
Use this form to create a long case. Associated image sets can be added using this form however the images will have to be uploaded once the case has been created.
<form action="" method="post" enctype="multipart/form-data" id="long-form">
{% csrf_token %}
@@ -1,4 +1,6 @@
<div>{{ series.modality}}, {{ series.examination }}</div>
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_urls }}" data-annotations=''></div>
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ series.get_image_urls }}" data-annotations=''></div>
<div>Author: {{ series.get_author_display }}</div>
+3 -1
View File
@@ -294,7 +294,7 @@ class LongSeriesCreate(LoginRequiredMixin, CreateView):
self.object = form.save(commit=False)
self.object.save()
#form.instance.author.add(self.request.user.id)
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
series_formset = context["image_formset"]
@@ -336,6 +336,8 @@ class LongSeriesUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateV
#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())
@@ -0,0 +1,18 @@
# Generated by Django 3.1.3 on 2021-02-06 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0011_auto_20210205_1056'),
]
operations = [
migrations.AlterField(
model_name='rapid',
name='open_access',
field=models.BooleanField(default=True, help_text='If a question should be freely available to browse'),
),
]
+1 -1
View File
@@ -168,7 +168,7 @@ class Rapid(models.Model):
help_text='Question has been scrapped and will not be shown')
open_access = models.BooleanField(
help_text="If an question should be freely available to browse", default=True
help_text="If a question should be freely available to browse", default=True
)