diff --git a/anatomy/filters.py b/anatomy/filters.py index 8358550f..b5995911 100644 --- a/anatomy/filters.py +++ b/anatomy/filters.py @@ -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): diff --git a/anatomy/migrations/0029_auto_20210209_2239.py b/anatomy/migrations/0029_auto_20210209_2239.py new file mode 100644 index 00000000..60fc1770 --- /dev/null +++ b/anatomy/migrations/0029_auto_20210209_2239.py @@ -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), + ), + ] diff --git a/anatomy/models.py b/anatomy/models.py index cc3f74c0..4dc61b51 100644 --- a/anatomy/models.py +++ b/anatomy/models.py @@ -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""" diff --git a/anatomy/tables.py b/anatomy/tables.py index c3994922..181be430 100644 --- a/anatomy/tables.py +++ b/anatomy/tables.py @@ -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): diff --git a/anatomy/templates/anatomy/ciduseranswer_confirm_delete.html b/anatomy/templates/anatomy/ciduseranswer_confirm_delete.html new file mode 100644 index 00000000..41ddc888 --- /dev/null +++ b/anatomy/templates/anatomy/ciduseranswer_confirm_delete.html @@ -0,0 +1,4 @@ +
{% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ +
\ No newline at end of file diff --git a/anatomy/urls.py b/anatomy/urls.py index 0c4202fc..d27dd7a8 100644 --- a/anatomy/urls.py +++ b/anatomy/urls.py @@ -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//delete", views.UserAnswerDelete.as_view(), name="anatomy_user_answer_delete"), ] diff --git a/anatomy/views.py b/anatomy/views.py index f83dbfc9..bf303ab7 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -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) \ No newline at end of file +AnatomyExamViews = ExamViews(Exam, "anatomy", "anatomy", loadJsonAnswer) + +class UserAnswerDelete(SuperuserRequiredMixin, DeleteView): + model = CidUserAnswer + success_url = reverse_lazy("anatomy:anatomy_user_answer_view") \ No newline at end of file diff --git a/generic/filters.py b/generic/filters.py new file mode 100644 index 00000000..3eddff2e --- /dev/null +++ b/generic/filters.py @@ -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", +# ) \ No newline at end of file diff --git a/generic/mixins.py b/generic/mixins.py new file mode 100644 index 00000000..875aa998 --- /dev/null +++ b/generic/mixins.py @@ -0,0 +1,6 @@ +from django.contrib.auth.mixins import UserPassesTestMixin + + +class SuperuserRequiredMixin(UserPassesTestMixin): + def test_func(self): + return self.request.user.is_superuser \ No newline at end of file diff --git a/generic/tables.py b/generic/tables.py new file mode 100644 index 00000000..e87ae678 --- /dev/null +++ b/generic/tables.py @@ -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('', thumbnailer["exam-list"]) + except InvalidImageFormatError: + return format_html('Invalid image url', 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") \ No newline at end of file diff --git a/generic/views.py b/generic/views.py index a72fe095..ed517883 100644 --- a/generic/views.py +++ b/generic/views.py @@ -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 diff --git a/helpers/images.py b/helpers/images.py index b3344920..2712b028 100644 --- a/helpers/images.py +++ b/helpers/images.py @@ -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 \ No newline at end of file + return image diff --git a/longs/migrations/0014_auto_20210211_1827.py b/longs/migrations/0014_auto_20210211_1827.py new file mode 100644 index 00000000..414ef2f2 --- /dev/null +++ b/longs/migrations/0014_auto_20210211_1827.py @@ -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), + ), + ] diff --git a/longs/models.py b/longs/models.py index ee6b788b..40fd7934 100644 --- a/longs/models.py +++ b/longs/models.py @@ -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""" diff --git a/longs/tables.py b/longs/tables.py index 64c5a07a..b1119226 100755 --- a/longs/tables.py +++ b/longs/tables.py @@ -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) diff --git a/longs/urls.py b/longs/urls.py index 07aebd3d..fc8379a9 100755 --- a/longs/urls.py +++ b/longs/urls.py @@ -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/", views.exam_json, name="exam_json"), + path("exam/json/", views.LongExamViews.exam_json, name="exam_json"), path( "exam/json//recreate", views.LongExamViews.exam_json_recreate, diff --git a/longs/views.py b/longs/views.py index 500c7c5f..25797fa8 100755 --- a/longs/views.py +++ b/longs/views.py @@ -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) diff --git a/rad/settings.py b/rad/settings.py index ca177d50..05c3238d 100644 --- a/rad/settings.py +++ b/rad/settings.py @@ -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 diff --git a/rapids/migrations/0013_auto_20210209_2239.py b/rapids/migrations/0013_auto_20210209_2239.py new file mode 100644 index 00000000..31981c9a --- /dev/null +++ b/rapids/migrations/0013_auto_20210209_2239.py @@ -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), + ), + ] diff --git a/rapids/migrations/0014_auto_20210211_2034.py b/rapids/migrations/0014_auto_20210211_2034.py new file mode 100644 index 00000000..d6fc2db2 --- /dev/null +++ b/rapids/migrations/0014_auto_20210211_2034.py @@ -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), + ), + ] diff --git a/rapids/migrations/0015_auto_20210211_2035.py b/rapids/migrations/0015_auto_20210211_2035.py new file mode 100644 index 00000000..2743692c --- /dev/null +++ b/rapids/migrations/0015_auto_20210211_2035.py @@ -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), + ), + ] diff --git a/rapids/models.py b/rapids/models.py index 6c8edbce..75754638 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -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""" diff --git a/rapids/urls.py b/rapids/urls.py index a5df6aec..32acb33d 100755 --- a/rapids/urls.py +++ b/rapids/urls.py @@ -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/", views.exam_json, name="exam_json"), + path("exam/json/", views.RapidExamViews.exam_json, name="exam_json"), path("exam/json//recreate", views.RapidExamViews.exam_json_recreate, name="exam_json_recreate"), path("create/", views.RapidCreate.as_view(), name="rapid_create"), path("create/defaults", diff --git a/rapids/views.py b/rapids/views.py index 6e761d0c..ba4dab7e 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -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) diff --git a/requirements.txt b/requirements.txt index a77810be..7ecc51d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,5 @@ psycopg2-binary django-sortedm2m pytesseract opencv-python -pydicom \ No newline at end of file +pydicom +django-dbbackup