Merge branch 'master' of gitssh://web.xkjq.uk:30001/ross/rad
This commit is contained in:
+17
-1
@@ -1,6 +1,22 @@
|
|||||||
import django_filters
|
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):
|
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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -310,41 +310,6 @@ class Exam(ExamBase):
|
|||||||
return exam_json
|
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):
|
class CidUserAnswer(models.Model):
|
||||||
"""User answers by candidate"""
|
"""User answers by candidate"""
|
||||||
|
|
||||||
|
|||||||
+22
-1
@@ -1,7 +1,7 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
|
|
||||||
from .models import AnatomyQuestion
|
from .models import AnatomyQuestion, CidUserAnswer
|
||||||
|
|
||||||
from django.utils.html import format_html
|
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 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):
|
class ImageColumn(tables.Column):
|
||||||
def render(self, value):
|
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>
|
||||||
@@ -51,4 +51,6 @@ urlpatterns = [
|
|||||||
path("examination/ajax/get_examination_id",
|
path("examination/ajax/get_examination_id",
|
||||||
views.get_examination_id,
|
views.get_examination_id,
|
||||||
name="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"),
|
||||||
]
|
]
|
||||||
|
|||||||
+15
-2
@@ -46,8 +46,8 @@ from .models import (
|
|||||||
from generic.models import Examination
|
from generic.models import Examination
|
||||||
from generic.views import ExamViews
|
from generic.views import ExamViews
|
||||||
|
|
||||||
from .tables import AnatomyQuestionTable
|
from .tables import AnatomyQuestionTable, AnatomyUserAnswerTable
|
||||||
from .filters import AnatomyQuestionFilter
|
from .filters import AnatomyQuestionFilter, AnatomyUserAnswerFilter
|
||||||
|
|
||||||
from django_tables2 import SingleTableView, SingleTableMixin
|
from django_tables2 import SingleTableView, SingleTableMixin
|
||||||
from django_filters.views import FilterView
|
from django_filters.views import FilterView
|
||||||
@@ -62,6 +62,8 @@ from helpers.images import image_as_base64
|
|||||||
|
|
||||||
from django.template.defaulttags import register
|
from django.template.defaulttags import register
|
||||||
|
|
||||||
|
from generic.mixins import SuperuserRequiredMixin
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_item(dictionary, key):
|
def get_item(dictionary, key):
|
||||||
@@ -919,6 +921,13 @@ class AnatomyQuestionView(SingleTableMixin, FilterView):
|
|||||||
|
|
||||||
filterset_class = AnatomyQuestionFilter
|
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):
|
def question_save_annotation(request, pk):
|
||||||
if request.is_ajax() and request.method == "POST":
|
if request.is_ajax() and request.method == "POST":
|
||||||
@@ -936,3 +945,7 @@ def question_save_annotation(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -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",
|
||||||
|
# )
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SuperuserRequiredMixin(UserPassesTestMixin):
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.is_superuser
|
||||||
@@ -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
@@ -210,7 +210,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
return JsonResponse(active_exams)
|
return JsonResponse(active_exams)
|
||||||
|
|
||||||
@method_decorator(csrf_exempt)
|
@method_decorator(csrf_exempt)
|
||||||
def postExamAnswers(request):
|
def postExamAnswers(self, request):
|
||||||
if request.is_ajax and request.method == "POST":
|
if request.is_ajax and request.method == "POST":
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ def image_as_base64(image_file):
|
|||||||
# encoded_string = base64.b64encode(img_f.read())
|
# encoded_string = base64.b64encode(img_f.read())
|
||||||
encoded_string = base64.b64encode(image_file.file.read())
|
encoded_string = base64.b64encode(image_file.file.read())
|
||||||
mimetype, enc = mimetypes.guess_type(image_file.path)
|
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"))
|
return "data:image/{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -15,9 +15,13 @@ from django.urls import reverse
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from sortedm2m.fields import SortedManyToManyField
|
from sortedm2m.fields import SortedManyToManyField
|
||||||
|
|
||||||
import string
|
import string
|
||||||
|
from collections import defaultdict
|
||||||
|
from helpers.images import image_as_base64
|
||||||
|
|
||||||
from anatomy.models import Modality
|
from anatomy.models import Modality
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
|
|||||||
|
|
||||||
image_storage = FileSystemStorage(
|
image_storage = FileSystemStorage(
|
||||||
# Physical file location ROOT
|
# Physical file location ROOT
|
||||||
location=u"{0}/longs/".format(settings.MEDIA_ROOT),
|
location=u"{0}longs/".format(settings.MEDIA_ROOT),
|
||||||
# Url for file
|
# Url for file
|
||||||
base_url=u"{0}longs/".format(settings.MEDIA_URL),
|
base_url=u"{0}longs/".format(settings.MEDIA_URL),
|
||||||
)
|
)
|
||||||
@@ -139,8 +143,11 @@ class Long(models.Model):
|
|||||||
# return self.GetImages()
|
# return self.GetImages()
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_validator(file):
|
||||||
|
pass
|
||||||
|
|
||||||
class LongSeriesImage(models.Model):
|
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)
|
position = models.IntegerField(default=0)
|
||||||
series = models.ForeignKey(
|
series = models.ForeignKey(
|
||||||
"LongSeries", related_name="images", on_delete=models.SET_NULL, null=True
|
"LongSeries", related_name="images", on_delete=models.SET_NULL, null=True
|
||||||
@@ -200,7 +207,7 @@ class LongSeries(models.Model):
|
|||||||
return authors
|
return authors
|
||||||
|
|
||||||
def get_author_display(self):
|
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):
|
def get_examination(self):
|
||||||
"""Returns a comma seperated text list of regions"""
|
"""Returns a comma seperated text list of regions"""
|
||||||
@@ -303,6 +310,57 @@ class Exam(ExamBase):
|
|||||||
default=4500,
|
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):
|
class CidUserAnswer(models.Model):
|
||||||
"""User answers by candidate"""
|
"""User answers by candidate"""
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class LongSeriesImageColumn(tables.Column):
|
|||||||
def render(self, value):
|
def render(self, value):
|
||||||
obj = value.first()
|
obj = value.first()
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
return "None"
|
||||||
|
|
||||||
image_object = obj.image
|
image_object = obj.image
|
||||||
try:
|
try:
|
||||||
thumbnailer = get_thumbnailer(image_object)
|
thumbnailer = get_thumbnailer(image_object)
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ urlpatterns = [
|
|||||||
path("exam/submit", views.LongExamViews.postExamAnswers, name="exam_answers_submit"),
|
path("exam/submit", views.LongExamViews.postExamAnswers, name="exam_answers_submit"),
|
||||||
path("exam/", views.LongExamViews.exam_list, name="exam_list"),
|
path("exam/", views.LongExamViews.exam_list, name="exam_list"),
|
||||||
path("exam/json/", views.LongExamViews.active_exams, name="active_exams"),
|
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(
|
path(
|
||||||
"exam/json/<int:pk>/recreate",
|
"exam/json/<int:pk>/recreate",
|
||||||
views.LongExamViews.exam_json_recreate,
|
views.LongExamViews.exam_json_recreate,
|
||||||
|
|||||||
@@ -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
|
@login_required
|
||||||
def exam_scores_cid(request, pk):
|
def exam_scores_cid(request, pk):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
"tagulous",
|
"tagulous",
|
||||||
|
"dbbackup",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -199,8 +200,14 @@ THUMBNAIL_ALIASES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
THUMBNAIL_SOURCE_GENERATORS = ('easy_thumbnails.source_generators.pil_image', 'helpers.images.pil_dicom_image')
|
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:
|
try:
|
||||||
from .settings_local import *
|
from .settings_local import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
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
@@ -16,13 +16,15 @@ from django.utils.html import mark_safe
|
|||||||
from sortedm2m.fields import SortedManyToManyField
|
from sortedm2m.fields import SortedManyToManyField
|
||||||
|
|
||||||
import string
|
import string
|
||||||
|
from collections import defaultdict
|
||||||
|
from helpers.images import image_as_base64
|
||||||
|
|
||||||
|
|
||||||
from generic.models import Site, Condition, Sign, ExamBase
|
from generic.models import Site, Condition, Sign, ExamBase
|
||||||
|
|
||||||
image_storage = FileSystemStorage(
|
image_storage = FileSystemStorage(
|
||||||
# Physical file location ROOT
|
# Physical file location ROOT
|
||||||
location=u"{0}/rapids/".format(settings.MEDIA_ROOT),
|
location=u"{0}rapids/".format(settings.MEDIA_ROOT),
|
||||||
# Url for file
|
# Url for file
|
||||||
base_url=u"{0}rapids/".format(settings.MEDIA_URL),
|
base_url=u"{0}rapids/".format(settings.MEDIA_URL),
|
||||||
)
|
)
|
||||||
@@ -286,7 +288,7 @@ class RapidImage(models.Model):
|
|||||||
rapid = models.ForeignKey(Rapid,
|
rapid = models.ForeignKey(Rapid,
|
||||||
related_name="images",
|
related_name="images",
|
||||||
on_delete=models.CASCADE)
|
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)
|
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
|
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):
|
class CidUserAnswer(models.Model):
|
||||||
"""User answers by candidate"""
|
"""User answers by candidate"""
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ urlpatterns = [
|
|||||||
path("exam/submit", views.RapidExamViews.postExamAnswers, name="exam_answers_submit"),
|
path("exam/submit", views.RapidExamViews.postExamAnswers, name="exam_answers_submit"),
|
||||||
path("exam/", views.RapidExamViews.exam_list, name="exam_list"),
|
path("exam/", views.RapidExamViews.exam_list, name="exam_list"),
|
||||||
path("exam/json/", views.RapidExamViews.active_exams, name="active_exams"),
|
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("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/", views.RapidCreate.as_view(), name="rapid_create"),
|
||||||
path("create/defaults",
|
path("create/defaults",
|
||||||
|
|||||||
@@ -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
|
@login_required
|
||||||
def exam_scores_cid(request, pk):
|
def exam_scores_cid(request, pk):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|||||||
@@ -15,3 +15,4 @@ django-sortedm2m
|
|||||||
pytesseract
|
pytesseract
|
||||||
opencv-python
|
opencv-python
|
||||||
pydicom
|
pydicom
|
||||||
|
django-dbbackup
|
||||||
|
|||||||
Reference in New Issue
Block a user