This commit is contained in:
Ross
2021-11-24 22:24:58 +00:00
parent aae7a103c1
commit 1d1319429f
4 changed files with 219 additions and 105 deletions
+11 -6
View File
@@ -17,6 +17,7 @@ from atlas.models import (
from anatomy.models import Modality from anatomy.models import Modality
from generic.models import Examination, Condition, Sign from generic.models import Examination, Condition, Sign
# from generic.models import Examination, Site, Condition, Sign # from generic.models import Examination, Site, Condition, Sign
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
@@ -24,11 +25,13 @@ from django.forms.widgets import RadioSelect, TextInput, Textarea
from tinymce.widgets import TinyMCE from tinymce.widgets import TinyMCE
class ExaminationForm(ModelForm): class ExaminationForm(ModelForm):
class Meta: class Meta:
model = Examination model = Examination
fields = ["examination"] fields = ["examination"]
class SeriesForm(ModelForm): class SeriesForm(ModelForm):
class Media: class Media:
# Django also includes a few javascript files necessary # Django also includes a few javascript files necessary
@@ -76,7 +79,9 @@ class AtlasForm(ModelForm):
js = ["jsi18n.js", "tesseract.min.js"] js = ["jsi18n.js", "tesseract.min.js"]
def __init__(self, *args, **kwargs): 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 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"): if kwargs.get("instance"):
# We get the 'initial' keyword argument or initialize it # We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist. # as a dict if it didn't exist.
@@ -89,7 +94,6 @@ class AtlasForm(ModelForm):
super(AtlasForm, self).__init__(*args, **kwargs) super(AtlasForm, self).__init__(*args, **kwargs)
class Meta: class Meta:
model = Case model = Case
# fields = ['due_back'] # fields = ['due_back']
@@ -111,10 +115,10 @@ class AtlasForm(ModelForm):
# "normal": RadioSelect( # "normal": RadioSelect(
# choices=[(True, 'Yes'), # choices=[(True, 'Yes'),
# (False, 'No')]) # (False, 'No')])
"findings" : TinyMCE(attrs={'cols': 80, 'rows': 20}), "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
"mark_scheme" : TinyMCE(attrs={'cols': 80, 'rows': 30}), "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
"description" : Textarea(attrs={'cols': 80, 'rows': 5}), "description": Textarea(attrs={"cols": 80, "rows": 5}),
"history" : Textarea(attrs={'cols': 80, 'rows': 5}), "history": Textarea(attrs={"cols": 80, "rows": 5}),
} }
def save(self, commit=True): def save(self, commit=True):
@@ -140,6 +144,7 @@ class AtlasForm(ModelForm):
return instance return instance
SeriesFormSet = inlineformset_factory( SeriesFormSet = inlineformset_factory(
Case, Case,
Series.case.through, Series.case.through,
+9 -1
View File
@@ -197,7 +197,15 @@ SeriesFormSet = inlineformset_factory(
can_delete=True, can_delete=True,
extra=0, extra=0,
max_num=10, max_num=10,
field_classes="testing", )
LongFormSet = inlineformset_factory(
LongSeries,
Long,
exclude=[],
can_delete=True,
extra=0,
max_num=10,
) )
+147 -46
View File
@@ -5,7 +5,11 @@ from django import forms
# from django.contrib.auth.models import User # from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ViewDoesNotExist from django.core.exceptions import (
ObjectDoesNotExist,
PermissionDenied,
ViewDoesNotExist,
)
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
@@ -22,7 +26,9 @@ from django.http import HttpResponseRedirect, HttpResponse
from .forms import ( from .forms import (
LongForm, LongForm,
LongSeriesForm, LongSeriesImageFormSet, LongFormSet,
LongSeriesForm,
LongSeriesImageFormSet,
MarkLongQuestionDoubleForm, MarkLongQuestionDoubleForm,
MarkLongQuestionSingleForm, MarkLongQuestionSingleForm,
SeriesFormSet, SeriesFormSet,
@@ -30,7 +36,9 @@ from .forms import (
ExamForm, ExamForm,
) )
from .models import ( from .models import (
AnswerMarks, Long, LongSeries, AnswerMarks,
Long,
LongSeries,
Examination, Examination,
Exam, Exam,
CidUserAnswer, CidUserAnswer,
@@ -41,7 +49,15 @@ from .filters import LongFilter, LongSeriesFilter, UserAnswerFilter
from django_tables2 import SingleTableView, SingleTableMixin from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView from django_filters.views import FilterView
from .decorators import user_is_author_or_long_checker, user_is_author_or_long_checker_or_long_marker, user_is_author_or_long_series_checker_or_long_marker, user_is_long_checker, user_is_author_or_long_series_checker, user_is_long_marker, user_is_author_or_long_checker_or_long_marker from .decorators import (
user_is_author_or_long_checker,
user_is_author_or_long_checker_or_long_marker,
user_is_author_or_long_series_checker_or_long_marker,
user_is_long_checker,
user_is_author_or_long_series_checker,
user_is_long_marker,
user_is_author_or_long_checker_or_long_marker,
)
from collections import defaultdict from collections import defaultdict
import json import json
@@ -74,6 +90,7 @@ import os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def normaliseScore(score): def normaliseScore(score):
if score >= 25.5 and score <= 28: if score >= 25.5 and score <= 28:
return 4.5 return 4.5
@@ -98,7 +115,10 @@ def normaliseScore(score):
class AuthorOrCheckerRequiredMixin(object): class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs) obj = super().get_object(*args, **kwargs)
if self.request.user.groups.filter(name="long_checker").exists() or self.request.user.is_superuser: if (
self.request.user.groups.filter(name="long_checker").exists()
or self.request.user.is_superuser
):
return obj return obj
if self.request.user not in obj.get_author_objects(): if self.request.user not in obj.get_author_objects():
raise PermissionDenied() # or Http404 raise PermissionDenied() # or Http404
@@ -126,6 +146,7 @@ def question_detail(request, pk):
# logging.debug(long.subspecialty.first().name.all()) # logging.debug(long.subspecialty.first().name.all())
return render(request, "longs/question_detail.html", {"question": long}) return render(request, "longs/question_detail.html", {"question": long})
@login_required @login_required
@user_is_author_or_long_series_checker_or_long_marker @user_is_author_or_long_series_checker_or_long_marker
def long_series_detail(request, pk): def long_series_detail(request, pk):
@@ -137,6 +158,7 @@ def long_series_detail(request, pk):
# logging.debug(long.subspecialty.first().name.all()) # logging.debug(long.subspecialty.first().name.all())
return render(request, "longs/long_series.html", {"series": series}) return render(request, "longs/long_series.html", {"series": series})
@login_required @login_required
@user_is_author_or_long_checker @user_is_author_or_long_checker
def long_split(request, pk): def long_split(request, pk):
@@ -236,10 +258,12 @@ class LongDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = Long model = Long
success_url = reverse_lazy("longs:long_view") success_url = reverse_lazy("longs:long_view")
class LongSeriesDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): class LongSeriesDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = LongSeries model = LongSeries
success_url = reverse_lazy("longs:long_series_view") success_url = reverse_lazy("longs:long_series_view")
@login_required @login_required
@user_is_author_or_long_checker @user_is_author_or_long_checker
def long_clone(request, pk): def long_clone(request, pk):
@@ -272,6 +296,7 @@ def long_clone(request, pk):
return render(request, "longs/long_form.html", context) return render(request, "longs/long_form.html", context)
class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView): class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = LongSeries model = LongSeries
form_class = LongSeriesForm form_class = LongSeriesForm
@@ -282,7 +307,7 @@ class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
initial = super().get_initial() initial = super().get_initial()
long = get_object_or_404(Long, pk=self.kwargs["pk"]) long = get_object_or_404(Long, pk=self.kwargs["pk"])
initial['long'] = long.id initial["long"] = long.id
return initial return initial
@@ -294,8 +319,11 @@ class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
self.request.POST, self.request.FILES self.request.POST, self.request.FILES
) )
context["image_formset"].full_clean() context["image_formset"].full_clean()
context["long_formset"] = LongFormSet(self.request.POST, self.request.FILES)
context["long_formset"].full_clean()
else: else:
context["image_formset"] = LongSeriesImageFormSet() context["image_formset"] = LongSeriesImageFormSet()
context["leng_formset"] = LongFormSet()
return context return context
def form_valid(self, form): def form_valid(self, form):
@@ -307,16 +335,22 @@ class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
context = self.get_context_data(form=form) context = self.get_context_data(form=form)
series_formset = context["image_formset"] series_formset = context["image_formset"]
if series_formset.is_valid(): long_formset = context["long_formset"]
if series_formset.is_valid() and long_formset.is_valid():
response = super().form_valid(form) response = super().form_valid(form)
series_formset.instance = self.object series_formset.instance = self.object
series_formset.save() series_formset.save()
long_formset.instance = self.object
long_formset.save()
return response return response
else: else:
return super().form_invalid(form) return super().form_invalid(form)
class LongSeriesUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView):
class LongSeriesUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = LongSeries model = LongSeries
form_class = LongSeriesForm form_class = LongSeriesForm
@@ -358,6 +392,7 @@ class LongSeriesUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequire
else: else:
return super().form_invalid(form) return super().form_invalid(form)
class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView): class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
model = Long model = Long
form_class = LongForm form_class = LongForm
@@ -433,7 +468,9 @@ class LongCreate(LongCreateBase):
# return super().form_valid(form) # return super().form_valid(form)
class LongUpdate(RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView): class LongUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Long model = Long
form_class = LongForm form_class = LongForm
@@ -565,6 +602,7 @@ class LongView(LoginRequiredMixin, SingleTableMixin, FilterView):
filterset_class = LongFilter filterset_class = LongFilter
class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView): class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = LongSeries model = LongSeries
table_class = LongSeriesTable table_class = LongSeriesTable
@@ -605,14 +643,12 @@ def loadJsonAnswer(answer):
# Long cases seperate sections by qidn # Long cases seperate sections by qidn
qidn = answer["qidn"] qidn = answer["qidn"]
# If the user answer does not exist # If the user answer does not exist
if not exiting_answers: if not exiting_answers:
ans = CidUserAnswer(cid=answer["cid"]) ans = CidUserAnswer(cid=answer["cid"])
ans.question_id = answer["qid"] ans.question_id = answer["qid"]
ans.exam_id = eid ans.exam_id = eid
# If the answer already exists or we have started populating it # If the answer already exists or we have started populating it
else: else:
# Update an existing answer # Update an existing answer
@@ -638,6 +674,7 @@ def loadJsonAnswer(answer):
return True, None return True, None
@user_is_long_marker @user_is_long_marker
@reversion.create_revision() @reversion.create_revision()
def mark_answer_override(request, exam_id, question_number, cid): def mark_answer_override(request, exam_id, question_number, cid):
@@ -668,7 +705,9 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404("User answer does not exist") raise Http404("User answer does not exist")
cid_list = list(question.cid_user_answers.filter(exam__id=exam_id).values_list("cid", flat=True)) cid_list = list(
question.cid_user_answers.filter(exam__id=exam_id).values_list("cid", flat=True)
)
previous_cid = False previous_cid = False
next_cid = False next_cid = False
@@ -684,7 +723,9 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
try: try:
if exam.double_mark: if exam.double_mark:
unmarked = question.get_unmarked_user_answers(exam_pk=exam.id, marker=request.user) unmarked = question.get_unmarked_user_answers(
exam_pk=exam.id, marker=request.user
)
else: else:
unmarked = question.get_unmarked_user_answers(exam_pk=exam.id) unmarked = question.get_unmarked_user_answers(exam_pk=exam.id)
next_unmarked_id = unmarked[0].cid next_unmarked_id = unmarked[0].cid
@@ -695,7 +736,9 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
# We use different forms if the exam should be single or double marked # We use different forms if the exam should be single or double marked
if not exam.double_mark or (request.method == "POST" and request.POST['form_id'] == 'discrepancy_form'): if not exam.double_mark or (
request.method == "POST" and request.POST["form_id"] == "discrepancy_form"
):
if request.method == "POST": if request.method == "POST":
form = MarkLongQuestionSingleForm(request.POST) form = MarkLongQuestionSingleForm(request.POST)
@@ -710,14 +753,24 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
answer.save() answer.save()
if "next" in request.POST: if "next" in request.POST:
return redirect("longs:mark_answer", exam_id=exam_id, question_number=question_number, cid=next_unmarked_id) return redirect(
"longs:mark_answer",
exam_id=exam_id,
question_number=question_number,
cid=next_unmarked_id,
)
if "save" in request.POST: if "save" in request.POST:
return redirect("longs:mark_answer", exam_id=exam_id, question_number=question_number, cid=cid) return redirect(
"longs:mark_answer",
exam_id=exam_id,
question_number=question_number,
cid=cid,
)
# elif "previous" in request.POST: # elif "previous" in request.POST:
# return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1) # return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1)
else: else:
form = MarkLongQuestionSingleForm(initial={'score': answer.score}) form = MarkLongQuestionSingleForm(initial={"score": answer.score})
else: else:
if request.method == "POST": if request.method == "POST":
@@ -729,8 +782,9 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
# if "skip" in request.POST: # if "skip" in request.POST:
# return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id) # return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id)
mark_object = AnswerMarks.objects.get_or_create(
mark_object = AnswerMarks.objects.get_or_create(user_answer=answer, marker=request.user)[0] user_answer=answer, marker=request.user
)[0]
# Extract score from form and save it to the object # Extract score from form and save it to the object
mark_object.score = form.cleaned_data["score"] mark_object.score = form.cleaned_data["score"]
mark_object.mark_reason = form.cleaned_data["mark_reason"] mark_object.mark_reason = form.cleaned_data["mark_reason"]
@@ -748,19 +802,35 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
answer.score = "" answer.score = ""
answer.save() answer.save()
if "next" in request.POST: if "next" in request.POST:
return redirect("longs:mark_answer", exam_id=exam_id, question_number=question_number, cid=next_unmarked_id) return redirect(
"longs:mark_answer",
exam_id=exam_id,
question_number=question_number,
cid=next_unmarked_id,
)
if "save" in request.POST: if "save" in request.POST:
return redirect("longs:mark_answer", exam_id=exam_id, question_number=question_number, cid=cid) return redirect(
"longs:mark_answer",
exam_id=exam_id,
question_number=question_number,
cid=cid,
)
# elif "previous" in request.POST: # elif "previous" in request.POST:
# return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1) # return redirect("longs:mark_question_overview", pk=exam_id, sk=n - 1)
else: else:
try: try:
mark_object = AnswerMarks.objects.get(user_answer=answer, marker=request.user) mark_object = AnswerMarks.objects.get(
form = MarkLongQuestionDoubleForm(initial={'score': mark_object.score,'mark_reason': mark_object.mark_reason,'candidate_feedback': mark_object.candidate_feedback, }) user_answer=answer, marker=request.user
)
form = MarkLongQuestionDoubleForm(
initial={
"score": mark_object.score,
"mark_reason": mark_object.mark_reason,
"candidate_feedback": mark_object.candidate_feedback,
}
)
except AnswerMarks.DoesNotExist: except AnswerMarks.DoesNotExist:
form = MarkLongQuestionDoubleForm() form = MarkLongQuestionDoubleForm()
@@ -769,7 +839,9 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
# if they do automatically update teh scoer # if they do automatically update teh scoer
marks = set(answer.mark.values_list("score", flat=True)) marks = set(answer.mark.values_list("score", flat=True))
if len(marks) > 1 or override: if len(marks) > 1 or override:
discrepancy_form = MarkLongQuestionSingleForm(initial={'score': answer.score }) discrepancy_form = MarkLongQuestionSingleForm(
initial={"score": answer.score}
)
return render( return render(
request, request,
@@ -789,6 +861,7 @@ def mark_answer(request, exam_id, question_number, cid, override=False):
}, },
) )
# @user_passes_test(user_is_admin, login_url="/accounts/login") # @user_passes_test(user_is_admin, login_url="/accounts/login")
@login_required @login_required
@user_is_long_marker @user_is_long_marker
@@ -809,13 +882,16 @@ def mark_question_overview(request, exam_id, sk):
except IndexError: except IndexError:
raise Http404("Exam question does not exist") raise Http404("Exam question does not exist")
user_answers = question.cid_user_answers.filter(exam__id=exam_id) user_answers = question.cid_user_answers.filter(exam__id=exam_id)
unmarked_count = user_answers.filter(score=CidUserAnswer.ScoreOptions.UNMARKED).count() unmarked_count = user_answers.filter(
score=CidUserAnswer.ScoreOptions.UNMARKED
).count()
if exam.double_mark: if exam.double_mark:
marker_unmarked_count = question.get_unmarked_user_answer_count(exam.pk, marker=request.user) marker_unmarked_count = question.get_unmarked_user_answer_count(
exam.pk, marker=request.user
)
return render( return render(
request, request,
"longs/mark_question_double_overview.html", "longs/mark_question_double_overview.html",
@@ -893,13 +969,10 @@ def exam_scores_cid(request, pk):
user_scores = {} user_scores = {}
user_scores_normalised = {} user_scores_normalised = {}
for user in user_answers_marks: for user in user_answers_marks:
user_scores[user] = sum( user_scores[user] = sum([i for i in user_answers_marks[user] if i != ""])
[i for i in user_answers_marks[user] if i != ""] user_scores_normalised[user] = normaliseScore(
sum([i for i in user_answers_marks[user] if i != ""])
) )
user_scores_normalised[user] = normaliseScore(sum(
[i for i in user_answers_marks[user] if i != ""]
))
user_scores_list = list(user_scores.values()) user_scores_list = list(user_scores.values())
@@ -967,7 +1040,6 @@ def exam_scores_cid_user(request, pk, sk):
# answers = [] # answers = []
answer_text = [] answer_text = []
view_all_results = False view_all_results = False
if request.user.groups.filter(name="view_all_results").exists(): if request.user.groups.filter(name="view_all_results").exists():
view_all_results = True view_all_results = True
@@ -976,20 +1048,33 @@ def exam_scores_cid_user(request, pk, sk):
# Get user answer # Get user answer
user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first() user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
if not user_answer or user_answer is None: if not user_answer or user_answer is None:
# skip if no answer # skip if no answer
# answers_marks.append("") # answers_marks.append("")
# answers.append("") # answers.append("")
answer_score = 4 answer_score = 4
# ans = "Not answered" # ans = "Not answered"
answer_text.append((("Not answered"),("Not answered"),("Not answered"),("Not answered"),("Not answered"))) answer_text.append(
(
("Not answered"),
("Not answered"),
("Not answered"),
("Not answered"),
("Not answered"),
)
)
else: else:
answer_score = user_answer.get_answer_score() answer_score = user_answer.get_answer_score()
answer_text.append((user_answer.answer_observations, user_answer.answer_interpretation, user_answer.answer_principle_diagnosis, user_answer.answer_differential_diagnosis, user_answer.answer_management)) answer_text.append(
(
user_answer.answer_observations,
user_answer.answer_interpretation,
user_answer.answer_principle_diagnosis,
user_answer.answer_differential_diagnosis,
user_answer.answer_management,
)
)
if not exam.publish_results and not view_all_results: if not exam.publish_results and not view_all_results:
answer_score = 0 answer_score = 0
@@ -1025,6 +1110,7 @@ def exam_scores_cid_user(request, pk, sk):
}, },
) )
@login_required @login_required
@user_is_author_or_long_series_checker @user_is_author_or_long_series_checker
def long_series_order_dicom(request, pk): def long_series_order_dicom(request, pk):
@@ -1032,10 +1118,11 @@ def long_series_order_dicom(request, pk):
try: try:
series.order_by_dicom() series.order_by_dicom()
except: except:
return HttpResponse('<h1>Series does not appear to contain dicoms</h1>') return HttpResponse("<h1>Series does not appear to contain dicoms</h1>")
return redirect("longs:long_series_detail", pk=pk) return redirect("longs:long_series_detail", pk=pk)
@login_required @login_required
@user_is_author_or_long_series_checker @user_is_author_or_long_series_checker
def long_series_order_dicom_instance(request, pk): def long_series_order_dicom_instance(request, pk):
@@ -1043,10 +1130,13 @@ def long_series_order_dicom_instance(request, pk):
try: try:
series.order_by_dicom("InstanceNumber") series.order_by_dicom("InstanceNumber")
except: except:
return HttpResponse('<h1>Series does not appear to contain dicoms (or field InstanceNumber)</h1>') return HttpResponse(
"<h1>Series does not appear to contain dicoms (or field InstanceNumber)</h1>"
)
return redirect("longs:long_series_detail", pk=pk) return redirect("longs:long_series_detail", pk=pk)
@login_required @login_required
@user_is_author_or_long_series_checker @user_is_author_or_long_series_checker
def long_series_order_dicom_SeriesInstanceUID(request, pk): def long_series_order_dicom_SeriesInstanceUID(request, pk):
@@ -1054,10 +1144,13 @@ def long_series_order_dicom_SeriesInstanceUID(request, pk):
try: try:
series.order_by_dicom("SeriesInstanceUID") series.order_by_dicom("SeriesInstanceUID")
except: except:
return HttpResponse('<h1>Series does not appear to contain dicoms (or field SeriesInstanceUID)</h1>') return HttpResponse(
"<h1>Series does not appear to contain dicoms (or field SeriesInstanceUID)</h1>"
)
return redirect("longs:long_series_detail", pk=pk) return redirect("longs:long_series_detail", pk=pk)
@login_required @login_required
@user_is_author_or_long_series_checker @user_is_author_or_long_series_checker
def long_series_order_upload_filename(request, pk): def long_series_order_upload_filename(request, pk):
@@ -1066,6 +1159,7 @@ def long_series_order_upload_filename(request, pk):
return redirect("longs:long_series_detail", pk=pk) return redirect("longs:long_series_detail", pk=pk)
LongExamViews = ExamViews(Exam, Long, "longs", "long", loadJsonAnswer) LongExamViews = ExamViews(Exam, Long, "longs", "long", loadJsonAnswer)
@@ -1080,6 +1174,7 @@ class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
form.instance.author.add(self.request.user.id) form.instance.author.add(self.request.user.id)
return super().form_valid(form) return super().form_valid(form)
class ExamClone(ExamCloneMixin, ExamCreate): class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view""" """Clone exam view"""
@@ -1105,9 +1200,10 @@ class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
queryset = Exam.objects.all().order_by('name') queryset = Exam.objects.all().order_by("name")
serializer_class = ExamSerializer serializer_class = ExamSerializer
def question_json_unbased(request, pk): def question_json_unbased(request, pk):
""" """
No (file based) caching is enabled for unbased quesitons No (file based) caching is enabled for unbased quesitons
@@ -1117,6 +1213,7 @@ def question_json_unbased(request, pk):
question_json = question.get_question_json(based=False) question_json = question.get_question_json(based=False)
return JsonResponse(question_json) return JsonResponse(question_json)
def question_json(request, pk): def question_json(request, pk):
question = get_object_or_404(Long, pk=pk) question = get_object_or_404(Long, pk=pk)
# exam = get_object_or_404(self.Exam, pk=pk) # exam = get_object_or_404(self.Exam, pk=pk)
@@ -1137,6 +1234,7 @@ def question_json(request, pk):
return redirect(question_json) return redirect(question_json)
@login_required @login_required
@user_is_author_or_long_checker @user_is_author_or_long_checker
def question_json_recreate(request, pk): def question_json_recreate(request, pk):
@@ -1173,6 +1271,7 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
template_name = "user_answer_delete.html" template_name = "user_answer_delete.html"
success_url = reverse_lazy("longs:user_answer_table_view") success_url = reverse_lazy("longs:user_answer_table_view")
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
def user_answer_delete_multiple(request): def user_answer_delete_multiple(request):
if request.is_ajax(): if request.is_ajax():
@@ -1195,14 +1294,16 @@ def refresh_exam_question_json(request, pk):
return redirect("longs:exam_overview", pk=pk) return redirect("longs:exam_overview", pk=pk)
class SeriesImagesZipView(SuperuserRequiredMixin, BaseZipView): class SeriesImagesZipView(SuperuserRequiredMixin, BaseZipView):
"""Download all images from an image series""" """Download all images from an image series"""
def get_files(self): def get_files(self):
series = LongSeries.objects.get(pk=self.kwargs['pk']) series = LongSeries.objects.get(pk=self.kwargs["pk"])
return [i.image.file for i in series.images.all()] return [i.image.file for i in series.images.all()]
def question_review(request, pk): def question_review(request, pk):
""" """
Return a json representation of the question when the exam is active Return a json representation of the question when the exam is active