Merge branch 'master' of gitssh://web.xkjq.uk:30001/ross/rad

This commit is contained in:
Ross
2021-02-13 20:19:19 +00:00
25 changed files with 364 additions and 186 deletions
+17 -1
View File
@@ -1,6 +1,22 @@
import django_filters
from .models import AnatomyQuestion
from .models import AnatomyQuestion, CidUserAnswer
#from generic.filters import UserAnswerFilter
class AnatomyUserAnswerFilter(django_filters.FilterSet):
class Meta:
model= CidUserAnswer
fields = (
"cid",
"question",
"answer",
#"answer_compare",
"exam",
"created",
"updated",
)
class AnatomyQuestionFilter(django_filters.FilterSet):
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-02-09 22:39
import anatomy.models
import django.core.files.storage
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0028_auto_20210206_0905'),
]
operations = [
migrations.AlterField(
model_name='anatomyquestion',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/ross/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
),
]
-35
View File
@@ -310,41 +310,6 @@ class Exam(ExamBase):
return exam_json
# class UserAnswer(models.Model):
# question = models.ForeignKey(
# AnatomyQuestion, related_name="user_answers", on_delete=models.CASCADE
# )
# answer = models.TextField(max_length=500, blank=True)
# user = models.ForeignKey(
# settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
# )
# 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(
# Exam, related_name="exam", on_delete=models.CASCADE, null=True
# )
# flagged = models.BooleanField(default=False)
# created = models.DateTimeField(auto_now_add=True)
# updated = models.DateTimeField(auto_now=True)
# def __str__(self):
# try:
# exam = self.exam
# except (Exam.DoesNotExist, KeyError) as e:
# exam = "None"
# return "{}/{}/{}: {}".format(
# exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
# )
# def clean(self):
# if self.answer:
# self.answer = self.answer.strip()
class CidUserAnswer(models.Model):
"""User answers by candidate"""
+22 -1
View File
@@ -1,7 +1,7 @@
import django_tables2 as tables
from django_tables2.utils import A
from .models import AnatomyQuestion
from .models import AnatomyQuestion, CidUserAnswer
from django.utils.html import format_html
@@ -9,6 +9,27 @@ from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
#from generic.tables import UserAnswerTable
class AnatomyUserAnswerTable(tables.Table):
select = tables.CheckBoxColumn(accessor=("pk"))
delete = tables.LinkColumn(
"anatomy:anatomy_user_answer_delete", text="Delete", args=[A("pk")], orderable=False
)
class Meta:
model = CidUserAnswer
template_name = "django_tables2/bootstrap4.html"
fields = (
"cid",
"question",
"answer",
#"answer_compare",
"exam",
"created",
"updated",
)
class ImageColumn(tables.Column):
def render(self, value):
@@ -0,0 +1,4 @@
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" value="Confirm">
</form>
+2
View File
@@ -51,4 +51,6 @@ urlpatterns = [
path("examination/ajax/get_examination_id",
views.get_examination_id,
name="get_examination_id"),
path("user_answers/", views.AnatomyUserAnswerView.as_view(), name="anatomy_user_answer_view"),
path("user_answers/<int:pk>/delete", views.UserAnswerDelete.as_view(), name="anatomy_user_answer_delete"),
]
+16 -3
View File
@@ -46,8 +46,8 @@ from .models import (
from generic.models import Examination
from generic.views import ExamViews
from .tables import AnatomyQuestionTable
from .filters import AnatomyQuestionFilter
from .tables import AnatomyQuestionTable, AnatomyUserAnswerTable
from .filters import AnatomyQuestionFilter, AnatomyUserAnswerFilter
from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView
@@ -62,6 +62,8 @@ from helpers.images import image_as_base64
from django.template.defaulttags import register
from generic.mixins import SuperuserRequiredMixin
@register.filter
def get_item(dictionary, key):
@@ -919,6 +921,13 @@ class AnatomyQuestionView(SingleTableMixin, FilterView):
filterset_class = AnatomyQuestionFilter
class AnatomyUserAnswerView(SingleTableMixin, FilterView):
model = CidUserAnswer
table_class = AnatomyUserAnswerTable
template_name = "anatomy/anatomy_question_view.html"
filterset_class = AnatomyUserAnswerFilter
def question_save_annotation(request, pk):
if request.is_ajax() and request.method == "POST":
@@ -935,4 +944,8 @@ def question_save_annotation(request, pk):
return JsonResponse(data, status=400)
AnatomyExamViews = ExamViews(Exam, "anatomy", "anatomy", loadJsonAnswer)
AnatomyExamViews = ExamViews(Exam, "anatomy", "anatomy", loadJsonAnswer)
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
model = CidUserAnswer
success_url = reverse_lazy("anatomy:anatomy_user_answer_view")
+18
View File
@@ -0,0 +1,18 @@
import django_filters
#class UserAnswerFilter(django_filters.FilterSet):
# class Meta:
# #model = AnatomyQuestion
# abstract = True
# fields = (
# "cid",
# "question",
# "answer",
# #"answer_compare",
# "exam",
# "created",
# "updateded",
# "created_date",
# )
+6
View File
@@ -0,0 +1,6 @@
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperuserRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser
+47
View File
@@ -0,0 +1,47 @@
import django_tables2 as tables
from django_tables2.utils import A
from django.utils.html import format_html
from easy_thumbnails.files import get_thumbnailer
from easy_thumbnails.exceptions import InvalidImageFormatError
class ImageColumn(tables.Column):
def render(self, value):
try:
thumbnailer = get_thumbnailer(value)
return format_html('<img src="/media/{}" />', thumbnailer["exam-list"])
except InvalidImageFormatError:
return format_html('<span title="{}">Invalid image url<span>', value)
#class UserAnswerTable(tables.Table):
# #edit = tables.LinkColumn(
# # "anatomy:anatomy_question_update", text="Edit", args=[A("pk")], orderable=False
# #)
# #view = tables.LinkColumn(
# # "anatomy:question_detail", text="View", args=[A("pk")], orderable=False
# #)
# #image = ImageColumn("image", orderable=False)
# # clone = tables.LinkColumn('anatomy:anatomy_question_clone',
# # text='Clone',
# # args=[A('pk')],
# # orderable=False)
# #exams = tables.ManyToManyColumn(verbose_name="Exams")
#
# class Meta:
# #model = AnatomyQuestion
# abstract = True
# template_name = "django_tables2/bootstrap4.html"
# fields = (
# "cid",
# "question",
# "answer",
# #"answer_compare",
# "exam",
# "created",
# "updated",
# )
# #sequence = ("view")
+1 -1
View File
@@ -210,7 +210,7 @@ class ExamViews(View, LoginRequiredMixin):
return JsonResponse(active_exams)
@method_decorator(csrf_exempt)
def postExamAnswers(request):
def postExamAnswers(self, request):
if request.is_ajax and request.method == "POST":
n = 0
+3 -1
View File
@@ -21,6 +21,8 @@ def image_as_base64(image_file):
# encoded_string = base64.b64encode(img_f.read())
encoded_string = base64.b64encode(image_file.file.read())
mimetype, enc = mimetypes.guess_type(image_file.path)
if "dicom" in mimetype:
return "data:{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
return "data:image/{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
@@ -54,4 +56,4 @@ def pil_dicom_image(source, exif_orientation=True, **options):
#if exif_orientation:
# image = utils.exif_orientation(image)
return image
return image
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-02-11 18:27
import django.core.files.storage
from django.db import migrations, models
import longs.models
class Migration(migrations.Migration):
dependencies = [
('longs', '0013_auto_20210206_2147'),
]
operations = [
migrations.AlterField(
model_name='longseriesimage',
name='image',
field=models.FileField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/longs/', location='/home/ross/rad/media/longs/'), upload_to=longs.models.image_directory_path),
),
]
+61 -3
View File
@@ -15,9 +15,13 @@ 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
from anatomy.models import Modality
@@ -30,7 +34,7 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
image_storage = FileSystemStorage(
# Physical file location ROOT
location=u"{0}/longs/".format(settings.MEDIA_ROOT),
location=u"{0}longs/".format(settings.MEDIA_ROOT),
# Url for file
base_url=u"{0}longs/".format(settings.MEDIA_URL),
)
@@ -139,8 +143,11 @@ class Long(models.Model):
# return self.GetImages()
def test_image_validator(file):
pass
class LongSeriesImage(models.Model):
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
image = models.FileField(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
@@ -200,7 +207,7 @@ class LongSeries(models.Model):
return authors
def get_author_display(self):
return ", ".join(self.get_author_objects())
return ", ".join([i.username for i in self.get_author_objects()])
def get_examination(self):
"""Returns a comma seperated text list of regions"""
@@ -303,6 +310,57 @@ class Exam(ExamBase):
default=4500,
)
def get_exam_json(self):
questions = self.exam_questions.all()
exam_questions = defaultdict(dict)
exam_order = []
for q in questions:
exam_order.append(q.id)
# Loop through longimage associations
images = []
image_titles = []
for series in q.series.all():
image_array = []
for i in series.images.all():
image_array.append(image_as_base64(i.image))
#image_array.append(i.image.url)
images.append(image_array)
image_titles.append(series.get_examination())
exam_questions[q.id] = {
"title": q.history,
"images": images,
"image_titles": image_titles,
#"feedback_image": [],
#"annotations": [str(q.image_annotations)],
"type": "long",
}
#if feedback_images:
# exam_questions[q.id]["feedback_image"] = feedback_images
exam_json = {
"eid": "long/{}".format(self.id),
"cached": False,
"exam_type": "long",
"exam_name": self.name,
"exam_mode": True,
"exam_order": exam_order,
"questions": exam_questions,
}
if self.time_limit:
exam_json["exam_time"] = self.time_limit
return exam_json
class CidUserAnswer(models.Model):
"""User answers by candidate"""
+3
View File
@@ -31,6 +31,9 @@ class LongSeriesImageColumn(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)
+1 -1
View File
@@ -48,7 +48,7 @@ urlpatterns = [
path("exam/submit", views.LongExamViews.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.LongExamViews.exam_list, name="exam_list"),
path("exam/json/", views.LongExamViews.active_exams, name="active_exams"),
path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
path("exam/json/<int:pk>", views.LongExamViews.exam_json, name="exam_json"),
path(
"exam/json/<int:pk>/recreate",
views.LongExamViews.exam_json_recreate,
-69
View File
@@ -796,75 +796,6 @@ def mark(request, pk, sk):
)
def exam_json(request, pk):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
raise Http404("No available exam")
exam_json_cache = cache.get("longs_exam_json_{}".format(pk))
if exam_json_cache is not None and not exam.recreate_json:
exam_json_cache["cached"] = True
return JsonResponse(exam_json_cache)
questions = exam.exam_questions.all()
exam_questions = defaultdict(dict)
exam_order = []
for q in questions:
exam_order.append(q.id)
# Loop through longimage associations
images = []
feedback_images = []
for i in q.images.all():
if i.feedback_image == True:
feedback_images.append(image_as_base64(i.image))
# feedback_images.append(i.image.url)
else:
images.append(image_as_base64(i.image))
#images.append(i.image.url)
exam_questions[q.id] = {
"images": images,
#"feedback_image": [],
#"annotations": [str(q.image_annotations)],
"type": "long",
}
#if feedback_images:
# exam_questions[q.id]["feedback_image"] = feedback_images
exam_json = {
"eid": "long/{}".format(exam.id),
"cached": False,
"exam_type": "long",
"exam_name": exam.name,
"exam_mode": True,
"exam_order": exam_order,
"questions": exam_questions,
}
if exam.time_limit:
exam_json["exam_time"] = exam.time_limit
exam.recreate_json = False
exam.save()
cache.set("longs_exam_json_{}".format(pk), exam_json, 3600)
return JsonResponse(exam_json)
@login_required
def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk)
+7
View File
@@ -50,6 +50,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'debug_toolbar',
"tagulous",
"dbbackup",
]
MIDDLEWARE = [
@@ -199,8 +200,14 @@ THUMBNAIL_ALIASES = {
}
THUMBNAIL_SOURCE_GENERATORS = ('easy_thumbnails.source_generators.pil_image', 'helpers.images.pil_dicom_image')
DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
DBBACKUP_STORAGE_OPTIONS = {'location': os.path.join(BASE_DIR, '../backups')}
try:
from .settings_local import *
except ImportError:
pass
<<<<<<< HEAD
=======
>>>>>>> 5763aae287cbceee318f975495f7d9429517c88f
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-02-09 22:39
import django.core.files.storage
from django.db import migrations, models
import rapids.models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0012_auto_20210206_0905'),
]
operations = [
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/rad/media//rapids/'), upload_to=rapids.models.image_directory_path),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-02-11 20:34
import django.core.files.storage
from django.db import migrations, models
import rapids.models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0013_auto_20210209_2239'),
]
operations = [
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.FileField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/rad/media//rapids/'), upload_to=rapids.models.image_directory_path),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2021-02-11 20:35
import django.core.files.storage
from django.db import migrations, models
import rapids.models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0014_auto_20210211_2034'),
]
operations = [
migrations.AlterField(
model_name='rapidimage',
name='image',
field=models.FileField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/rad/media/rapids/'), upload_to=rapids.models.image_directory_path),
),
]
+53 -2
View File
@@ -16,13 +16,15 @@ from django.utils.html import mark_safe
from sortedm2m.fields import SortedManyToManyField
import string
from collections import defaultdict
from helpers.images import image_as_base64
from generic.models import Site, Condition, Sign, ExamBase
image_storage = FileSystemStorage(
# Physical file location ROOT
location=u"{0}/rapids/".format(settings.MEDIA_ROOT),
location=u"{0}rapids/".format(settings.MEDIA_ROOT),
# Url for file
base_url=u"{0}rapids/".format(settings.MEDIA_URL),
)
@@ -286,7 +288,7 @@ class RapidImage(models.Model):
rapid = models.ForeignKey(Rapid,
related_name="images",
on_delete=models.CASCADE)
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
image = models.FileField(upload_to=image_directory_path, storage=image_storage)
feedback_image = models.BooleanField(default=False)
@@ -350,6 +352,55 @@ class Exam(ExamBase):
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100
)
def get_exam_json(self):
questions = self.exam_questions.all()
exam_questions = defaultdict(dict)
exam_order = []
for q in questions:
exam_order.append(q.id)
# Loop through rapidimage associations
images = []
feedback_images = []
for i in q.images.all():
if i.feedback_image == True:
feedback_images.append(image_as_base64(i.image))
# feedback_images.append(i.image.url)
else:
images.append(image_as_base64(i.image))
#images.append(i.image.url)
exam_questions[q.id] = {
"images": images,
#"feedback_image": [],
#"annotations": [str(q.image_annotations)],
"type": "rapid",
}
#if feedback_images:
# exam_questions[q.id]["feedback_image"] = feedback_images
exam_json = {
"eid": "rapid/{}".format(self.id),
"cached": False,
"exam_type": "rapid",
"exam_name": self.name,
"exam_mode": True,
"exam_order": exam_order,
"questions": exam_questions,
}
if self.time_limit:
exam_json["exam_time"] = self.time_limit
return exam_json
class CidUserAnswer(models.Model):
"""User answers by candidate"""
+1 -1
View File
@@ -32,7 +32,7 @@ urlpatterns = [
path("exam/submit", views.RapidExamViews.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.RapidExamViews.exam_list, name="exam_list"),
path("exam/json/", views.RapidExamViews.active_exams, name="active_exams"),
path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
path("exam/json/<int:pk>", views.RapidExamViews.exam_json, name="exam_json"),
path("exam/json/<int:pk>/recreate", views.RapidExamViews.exam_json_recreate, name="exam_json_recreate"),
path("create/", views.RapidCreate.as_view(), name="rapid_create"),
path("create/defaults",
-67
View File
@@ -699,73 +699,6 @@ def mark(request, pk, sk):
)
def exam_json(request, pk):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
raise Http404("No available exam")
exam_json_cache = cache.get("rapids_exam_json_{}".format(pk))
if exam_json_cache is not None and not exam.recreate_json:
exam_json_cache["cached"] = True
return JsonResponse(exam_json_cache)
questions = exam.exam_questions.all()
exam_questions = defaultdict(dict)
exam_order = []
for q in questions:
exam_order.append(q.id)
# Loop through rapidimage associations
images = []
feedback_images = []
for i in q.images.all():
if i.feedback_image == True:
feedback_images.append(image_as_base64(i.image))
# feedback_images.append(i.image.url)
else:
images.append(image_as_base64(i.image))
#images.append(i.image.url)
exam_questions[q.id] = {
"images": images,
#"feedback_image": [],
#"annotations": [str(q.image_annotations)],
"type": "rapid",
}
#if feedback_images:
# exam_questions[q.id]["feedback_image"] = feedback_images
exam_json = {
"eid": "rapid/{}".format(exam.id),
"cached": False,
"exam_type": "rapid",
"exam_name": exam.name,
"exam_mode": True,
"exam_order": exam_order,
"questions": exam_questions,
}
if exam.time_limit:
exam_json["exam_time"] = exam.time_limit
exam.recreate_json = False
exam.save()
cache.set("rapids_exam_json_{}".format(pk), exam_json, 3600)
return JsonResponse(exam_json)
@login_required
def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk)
+2 -1
View File
@@ -14,4 +14,5 @@ psycopg2-binary
django-sortedm2m
pytesseract
opencv-python
pydicom
pydicom
django-dbbackup