import json from django.shortcuts import render, get_object_or_404, redirect from django import forms # from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User from django.core.exceptions import ( ObjectDoesNotExist, PermissionDenied, ViewDoesNotExist, ) from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic.detail import DetailView from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic import ListView from django.views.decorators.csrf import csrf_exempt from django.urls import reverse_lazy, reverse from django.http import Http404, JsonResponse from django.http import HttpResponseRedirect, HttpResponse from .forms import ( ExamAuthorForm, ExamGroupsForm, ExamMarkerForm, LongForm, LongSeriesForm, LongSeriesImageFormSet, MarkLongQuestionDoubleForm, MarkLongQuestionSingleForm, SeriesFormSet, ExaminationForm, ExamForm, ) from .models import ( AnswerMarks, Long, LongSeries, Examination, Exam, UserAnswer, ) from .tables import LongTable, LongSeriesTable, UserAnswerTable from .filters import LongFilter, LongSeriesFilter, UserAnswerFilter from django_tables2 import SingleTableView, SingleTableMixin 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 collections import defaultdict import json import statistics import plotly.express as px from django.core.cache import cache from django.conf import settings from helpers.images import image_as_base64 import logging from copy import deepcopy from django.forms.models import model_to_dict from longs.forms import LongCreationDefaultForm from longs.models import LongCreationDefault from generic.views import ( AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamGroupsUpdateBase, ExamUpdateBase, ExamViews, GenericViewBase, RedirectMixin, SeriesImagesZipViewBase, UpdateQuestionMixin, ) from reversion.views import RevisionMixin import reversion from rest_framework import viewsets from .serializers import ExamSerializer from zipview.views import BaseZipView import os logger = logging.getLogger(__name__) def normaliseScore(score): if score >= 25.5 and score <= 28: return 4.5 elif score >= 28.5 and score <= 31: return 5 elif score >= 31.5 and score <= 34: return 5.5 elif score >= 34.5 and score <= 37: return 6 elif score >= 37.5 and score <= 40: return 6.5 elif score >= 40.5 and score <= 43: return 7 elif score >= 43.5 and score <= 46: return 7.5 elif score >= 46.5 and score <= 48: return 7.5 return 4 class AuthorOrCheckerRequiredMixin(object): def get_object(self, *args, **kwargs): obj = super().get_object(*args, **kwargs) if ( self.request.user.groups.filter(name="long_checker").exists() or self.request.user.is_superuser ): return obj if self.request.user in obj.get_author_objects(): return obj raise PermissionDenied() # or Http404 # def index(request): # other_longs = Long.objects.exclude(author=request.user.pk) # user_longs = Long.objects.filter(author=request.user.pk) # return render( # request, # "longs/index.html", # {"other_longs": other_longs, "user_longs": user_longs}, # ) @login_required @user_is_author_or_long_checker_or_long_marker def question_detail(request, pk): long = get_object_or_404(Long, pk=pk) # if request.user not in long.author.all(): # raise PermissionDenied # logging.debug(long.subspecialty.first().name.all()) can_edit = long.can_edit(request.user) return render( request, "longs/question_detail.html", {"question": long, "can_edit": can_edit} ) @login_required @user_is_author_or_long_series_checker_or_long_marker def series_detail(request, pk): series = get_object_or_404(LongSeries, pk=pk) # if request.user not in long.author.all(): # raise PermissionDenied # logging.debug(long.subspecialty.first().name.all()) return render(request, "longs/long_series.html", {"series": series}) @login_required @user_is_author_or_long_checker def long_split(request, pk): long = get_object_or_404(Long, pk=pk) images = long.images.all() old_abnormality = long.abnormality.all() old_region = long.region.all() old_examination = long.examination.all() old_site = long.site.all() old_author = long.author.all() if not images: raise Http404 for n in range(len(images) - 1): long.pk = None long.save() long.abnormality.set(old_abnormality) long.region.set(old_region) long.examination.set(old_examination) long.site.set(old_site) long.author.set(old_author) images[n].long = long images[n].save() # images[-1].long # if request.user not in long.author.all(): # raise PermissionDenied # logging.debug(long.subspecialty.first().name.all()) return render(request, "longs/question_detail.html", {"question": long}) @login_required @user_is_author_or_long_checker def author_detail(request, pk): # logging.debug(Author.objects.all()) # author = get_object_or_404(Author, pk=pk) author = User.objects.get(pk=pk) longs = Long.objects.filter(author=pk) return render( request, "longs/category_detail.html", {"category": author, "longs": longs} ) @user_is_author_or_long_checker def author_list(request): authors = User.objects.all() return render(request, "longs/author_list.html", {"authors": authors}) class LongCreationDefaultView(LoginRequiredMixin, UpdateView): model = LongCreationDefault form_class = LongCreationDefaultForm # fields = '__all__' # #fields = [ 'condition' ] # #initial = {'date_of_death': '05/01/2018'} # exclude = [ 'created_date', 'published_date' ] # def dispatch(self, request, *args, **kwargs): # """ # Overridden so we can make sure the `Long` instance exists # before going any further. # """ # self.pk = get_object_or_404(Long, pk=kwargs['pk']) # return super().dispatch(request, *args, **kwargs) def get_object(self, queryset=None): obj, create = LongCreationDefault.objects.get_or_create( author=self.request.user ) return obj def form_valid(self, form): model = form.save(commit=False) model.author = self.request.user model.save() response = super().form_valid(form) return response # form.instance.author.add(self.request.user.id) class LongDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): model = Long success_url = reverse_lazy("longs:long_view") class LongSeriesDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView): model = LongSeries success_url = reverse_lazy("longs:long_series_view") @login_required @user_is_author_or_long_checker def long_clone(request, pk): new_item = get_object_or_404(Long, pk=pk) new_item.pk = None # autogen a new pk (item_id) # new_item.name = "Copy of " + new_item.name #need to change uniques if ( request.user not in new_item.get_author_objects() and not request.user.is_superuser ): raise PermissionDenied() # or Http404 form = LongForm(request.POST or None, instance=new_item) series_formset = SeriesFormSet() if form.is_valid(): form.instance.author.add(request.user.id) # logger.debug(formset.is_valid()) if series_formset.is_valid(): response = super().form_valid(form) series_formset.instance = obj series_formset.save() return response else: return super().form_invalid(form) context = { "form": form, "series_formset": series_formset, # other context } return render(request, "longs/long_form.html", context) class LongSeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView): model = LongSeries form_class = LongSeriesForm def get_initial(self): # print(self.request) if "pk" in self.kwargs: initial = super().get_initial() long = get_object_or_404(Long, pk=self.kwargs["pk"]) initial["long"] = long.id return initial def get_form_kwargs(self): kwargs = super(LongSeriesCreate, self).get_form_kwargs() kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): context = super(LongSeriesCreate, self).get_context_data(**kwargs) if self.request.POST: context["image_formset"] = LongSeriesImageFormSet( self.request.POST, self.request.FILES ) context["image_formset"].full_clean() else: context["image_formset"] = LongSeriesImageFormSet() return context def form_valid(self, form): self.object = form.save(commit=False) self.object.save() form.instance.author.add(self.request.user.id) context = self.get_context_data(form=form) series_formset = context["image_formset"] if series_formset.is_valid(): response = super().form_valid(form) series_formset.instance = self.object series_formset.save() return response else: return super().form_invalid(form) class LongSeriesUpdate( RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView ): model = LongSeries form_class = LongSeriesForm # fields = '__all__' # #fields = [ 'condition' ] # #initial = {'date_of_death': '05/01/2018'} # exclude = [ 'created_date', 'published_date' ] def get_form_kwargs(self): kwargs = super(LongSeriesUpdate, self).get_form_kwargs() kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): context = super(LongSeriesUpdate, self).get_context_data(**kwargs) if self.request.POST: context["image_formset"] = LongSeriesImageFormSet( self.request.POST, self.request.FILES, instance=self.object ) context["image_formset"].full_clean() else: context["image_formset"] = LongSeriesImageFormSet(instance=self.object) return context def form_valid(self, form): self.object = form.save(commit=False) self.object.save() # Do this in the form.save() # new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True)) # self.object.exams.clear() # self.object.exams.add(*new_exams) form.instance.author.add(self.request.user.id) context = self.get_context_data(form=form) image_formset = context["image_formset"] # logger.debug(formset.is_valid()) if image_formset.is_valid(): response = super().form_valid(form) image_formset.instance = self.object image_formset.save() return response else: return super().form_invalid(form) class LongCreateBase(RevisionMixin, LoginRequiredMixin, CreateView): model = Long form_class = LongForm def get_form_kwargs(self): kwargs = super(LongCreateBase, self).get_form_kwargs() kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): context = super(LongCreateBase, self).get_context_data(**kwargs) queryset = ( LongSeries.objects.select_related("modality", "examination", "plane") .prefetch_related("long") .filter(pk=62) ) # .values() print("TEST") print(queryset) # queryset = LongSeries.objects.all() if self.request.POST: context["series_formset"] = SeriesFormSet( self.request.POST, self.request.FILES, queryset=queryset ) context["series_formset"].full_clean() else: context["series_formset"] = SeriesFormSet(queryset=queryset) return context def form_valid(self, form): self.object = form.save(commit=False) # Add exam objects self.object.save() # Do this in the form.save() # new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True)) # self.object.exams.clear() # self.object.exams.add(*new_exams) form.instance.author.add(self.request.user.id) context = self.get_context_data(form=form) series_formset = context["series_formset"] if series_formset.is_valid(): response = super().form_valid(form) series_formset.instance = self.object series_formset.save() # If the normal submit button is pressed we save as normal if "submit" in self.request.POST: return response # else we redirect to the clone url else: return redirect("longs:long_clone", pk=self.object.pk) else: return super().form_invalid(form) # @login_required class LongCreate(LongCreateBase): def get_initial(self): # There has to be a better way... try: s = (i.pk for i in self.request.user.long_default.site.all()) self.initial.update({"site": s}) except AttributeError: pass return self.initial # fields = '__all__' # #fields = [ 'condition' ] # #initial = {'date_of_death': '05/01/2018'} # exclude = [ 'created_date', 'published_date' ] # self.object = form.save(commit=False) # self.object.save() # form.instance.author.add(self.request.user.id) # return super().form_valid(form) class LongUpdate( UpdateQuestionMixin ): model = Long form_class = LongForm # fields = '__all__' # #fields = [ 'condition' ] # #initial = {'date_of_death': '05/01/2018'} # exclude = [ 'created_date', 'published_date' ] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.POST: context["series_formset"] = SeriesFormSet( self.request.POST, self.request.FILES, instance=self.object ) context["series_formset"].full_clean() else: context["series_formset"] = SeriesFormSet(instance=self.object) return context def form_valid(self, form): # save exam orders (there must be a better way to do this) exam_orders = {} exam: Exam for exam in self.object.exams.all(): exam_orders[exam] = list(exam.get_questions()) self.object = form.save(commit=False) self.object.save() # Do this in the form.save() # new_exams = list(form.cleaned_data['exams'].values_list("pk", flat=True)) # self.object.exams.clear() # self.object.exams.add(*new_exams) form.instance.author.add(self.request.user.id) context = self.get_context_data(form=form) series_formset = context["series_formset"] # logger.debug(formset.is_valid()) if series_formset.is_valid(): response = super().form_valid(form) series_formset.instance = self.object series_formset.save() # restore exam orders for exam in self.object.exams.all(): if exam in exam_orders and self.object in exam_orders[exam]: print(exam_orders[exam]) exam.exam_questions.set(exam_orders[exam]) exam.save() return response else: return super().form_invalid(form) class LongClone(LongCreateBase): """Clones a existing long""" # fields = '__all__' # #fields = [ 'condition' ] # #initial = {'date_of_death': '05/01/2018'} # exclude = [ 'created_date', 'published_date' ] def get_initial(self): # print(self.request) old_object = get_object_or_404(Long, pk=self.kwargs["pk"]) initial_data = model_to_dict(old_object, exclude=["id"]) return initial_data @login_required @user_is_author_or_long_checker def long_scrap(request, pk): try: long = Long.objects.get(pk=pk) except Long.DoesNotExist: raise Http404("Long does not exist") long.scrapped = not long.scrapped long.save() return HttpResponseRedirect(reverse("longs:question_detail", args=(pk,))) # @login_required # def edit_abnormality_popup(request): # instance = get_object_or_404(Abnormality, pk = pk) # form = AbnormalityForm(request.POST or None) # if form.is_valid(): # instance = form.save() # return HttpResponse('' % (instance.pk, instance)) # return render(request, "longs/create_simple.html", {'form': form}) @login_required def create_examination(request): form = ExaminationForm(request.POST or None) if form.is_valid(): instance = form.save() return HttpResponse( '' % (instance.pk, instance) ) return render( request, "longs/create_simple.html", {"form": form, "name": "Examination"} ) @csrf_exempt def get_examination_id(request): if request.accepts("application/json")(): examination_name = request.GET["examination_name"] examination_id = Examination.objects.get(name=examination_name).id data = { "examination_id": examination_id, } return HttpResponse(json.dumps(data), content_type="application/json") return HttpResponse("/") class LongView(LoginRequiredMixin, SingleTableMixin, FilterView): model = Long table_class = LongTable template_name = "longs/view.html" filterset_class = LongFilter class LongSeriesView(LoginRequiredMixin, SingleTableMixin, FilterView): model = LongSeries table_class = LongSeriesTable template_name = "longs/view.html" filterset_class = LongSeriesFilter # @user_is_long_marker def mark_answer_override(request, exam_id, question_number, cid): return mark_answer(request, exam_id, question_number, cid, override=True) # @user_is_long_marker @reversion.create_revision() def mark_answer(request, exam_id, question_number, answer_id, override=False): exam = get_object_or_404(Exam, pk=exam_id) if not GenericExamViews.check_user_marker_access(request.user, exam_id): raise PermissionDenied questions = exam.get_questions() n = question_number question_details = { "total": len(questions), "current": n + 1, } try: question = questions[question_number] except IndexError: raise Http404("Exam question does not exist") try: answer = question.cid_user_answers.get(pk=answer_id) except ObjectDoesNotExist: raise Http404("User answer does not exist") answer_list = list( question.cid_user_answers.filter(exam__id=exam_id).values_list("id", flat=True) ) answer_list.sort() print(answer_list) previous_answer_id = False next_answer_id = False if len(answer_list) > 1: if answer_list[0] == answer_id: next_answer_id = answer_list[1] elif answer_list[-1] == answer_id: previous_answer_id = answer_list[-2] else: print(answer_list.index(answer_id)) next_answer_id = answer_list[answer_list.index(answer_id) + 1] previous_answer_id = answer_list[answer_list.index(answer_id) - 1] try: if exam.double_mark: unmarked = question.get_unmarked_user_answers( exam_pk=exam.id, marker=request.user ) else: unmarked = question.get_unmarked_user_answers(exam_pk=exam.id) next_unmarked_id = unmarked[0].id if next_unmarked_id == answer_id: next_unmarked_id = unmarked[1].id except IndexError: next_unmarked_id = False # 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 request.method == "POST": form = MarkLongQuestionSingleForm(request.POST) if form.is_valid(): # If skip button is pressed skip the question without marking # Skip is problematic if we skip to the next unmarked answer # if "skip" in request.POST: # return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id) # Extract score from form and save it to the object answer.score = form.cleaned_data["score"] answer.candidate_feedback = form.cleaned_data["candidate_feedback"] answer.save() if "next" in request.POST: return redirect( "longs:mark_answer", exam_id=exam_id, question_number=question_number, answer_id=next_unmarked_id, ) if "save" in request.POST: return redirect( "longs:mark_answer", exam_id=exam_id, question_number=question_number, answer_id=answer_id, ) # elif "previous" in request.POST: # return redirect("longs:mark", pk=exam_id, sk=n - 1) else: form = MarkLongQuestionSingleForm( initial={ "score": answer.score, "candidate_feedback": answer.candidate_feedback, } ) else: if request.method == "POST": form = MarkLongQuestionDoubleForm(request.POST) if form.is_valid(): # If skip button is pressed skip the question without marking # Skip is problematic if we skip to the next unmarked answer # if "skip" in request.POST: # return redirect("longs:mark_answer", pk=exam_id, sk=question_number, cid=next_unmarked_id) mark_object = AnswerMarks.objects.get_or_create( user_answer=answer, marker=request.user )[0] # Extract score from form and save it to the object mark_object.score = form.cleaned_data["score"] mark_object.mark_reason = form.cleaned_data["mark_reason"] mark_object.candidate_feedback = form.cleaned_data["candidate_feedback"] mark_object.marker = request.user mark_object.save() # Form is saved, check if we have two marks (and if they match) if answer.mark.count() == 2: # if they do automatically update teh scoer marks = set(answer.mark.values_list("score", flat=True)) if len(marks) == 1: answer.score = marks.pop() else: answer.score = "" answer.save() if "next" in request.POST: return redirect( "longs:mark_answer", exam_id=exam_id, question_number=question_number, answer_id=next_unmarked_id, ) if "save" in request.POST: return redirect( "longs:mark_answer", exam_id=exam_id, question_number=question_number, answer_id=answer_id, ) # elif "previous" in request.POST: # return redirect("longs:mark", pk=exam_id, sk=n - 1) else: try: mark_object = AnswerMarks.objects.get( 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: form = MarkLongQuestionDoubleForm() discrepancy_form = False if answer.mark.count() == 2 or override: # if they do automatically update teh scoer marks = set(answer.mark.values_list("score", flat=True)) if len(marks) > 1 or override: discrepancy_form = MarkLongQuestionSingleForm( initial={ "score": answer.score, "candidate_feedback": answer.candidate_feedback, } ) return render( request, "longs/mark_answer.html", { "exam": exam, "form": form, "discrepancy_form": discrepancy_form, "answer": answer, "question": question, "question_details": question_details, "next_unmarked_id": next_unmarked_id, "unmarked": unmarked, "previous_answer_id": previous_answer_id, "next_answer_id": next_answer_id, "answer_id": answer_id, }, ) # @user_passes_test(user_is_admin, login_url="/accounts/login") @login_required # @user_is_long_marker def mark(request, exam_id, sk): exam = get_object_or_404(Exam, pk=exam_id) if not GenericExamViews.check_user_marker_access(request.user, exam_id): raise PermissionDenied questions = exam.get_questions() n = sk question_details = { "total": len(questions), "current": n + 1, } try: question = questions[sk] except IndexError: raise Http404("Exam question does not exist") user_answers = question.cid_user_answers.filter(exam__id=exam_id) unmarked_count = user_answers.filter(score=UserAnswer.ScoreOptions.UNMARKED).count() if exam.double_mark: marker_unmarked_count = question.get_unmarked_user_answer_count( exam.pk, marker=request.user ) return render( request, "longs/mark_question_double_overview.html", { "exam": exam, "question": question, "question_details": question_details, "user_answers": user_answers, "unmarked_count": unmarked_count, "marker_unmarked_count": marker_unmarked_count, }, ) else: return render( request, "longs/mark_question_single_overview.html", { "exam": exam, "question": question, "question_details": question_details, "user_answers": user_answers, "unmarked_count": unmarked_count, }, ) @login_required @user_is_author_or_long_series_checker def long_series_order_dicom(request, pk): series = get_object_or_404(LongSeries, pk=pk) try: series.order_by_dicom() except: return HttpResponse("

Series does not appear to contain dicoms

") return redirect("longs:series_detail", pk=pk) @login_required @user_is_author_or_long_series_checker def long_series_order_dicom_instance(request, pk): series = get_object_or_404(LongSeries, pk=pk) try: series.order_by_dicom("InstanceNumber") except: return HttpResponse( "

Series does not appear to contain dicoms (or field InstanceNumber)

" ) return redirect("longs:series_detail", pk=pk) @login_required @user_is_author_or_long_series_checker def long_series_order_dicom_SeriesInstanceUID(request, pk): series = get_object_or_404(LongSeries, pk=pk) try: series.order_by_dicom("SeriesInstanceUID") except: return HttpResponse( "

Series does not appear to contain dicoms (or field SeriesInstanceUID)

" ) return redirect("longs:series_detail", pk=pk) @login_required @user_is_author_or_long_series_checker def long_series_order_upload_filename(request, pk): series = get_object_or_404(LongSeries, pk=pk) series.order_by_upload_filename() return redirect("longs:series_detail", pk=pk) GenericExamViews = ExamViews(Exam, Long, None, UserAnswer, "longs", "long") GenericViews = GenericViewBase("longs", Long, UserAnswer, Exam) class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm class ExamUpdate(AuthorOrCheckerRequiredMixin, ExamUpdateBase): model = Exam form_class = ExamForm class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): model = Exam class ExamClone(ExamCloneMixin, ExamCreate): """Clone exam view""" class ExamAuthorUpdate( RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView, ): model = Exam form_class = ExamAuthorForm template_name = "author_form.html" class ExamMarkersUpdate( RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView, ): model = Exam form_class = ExamMarkerForm template_name = "markers_form.html" class ExamGroupsUpdate(ExamGroupsUpdateBase): model = Exam form_class = ExamGroupsForm # class ExamViewSet(RevisionMixin, viewsets.ModelViewSet): # queryset = Exam.objects.all().order_by("name") # serializer_class = ExamSerializer def question_json_unbased(request, pk): """ No (file based) caching is enabled for unbased quesitons """ print("UNBASED") question = get_object_or_404(Long, pk=pk) question_json = question.get_question_json(based=False) return JsonResponse(question_json) def question_json(request, pk): question = get_object_or_404(Long, pk=pk) # exam = get_object_or_404(self.Exam, pk=pk) # if not exam.active: # raise Http404("No available exam") path = "{0}longs/questions/{1}.json".format(settings.MEDIA_ROOT, pk) url = "{0}longs/questions/{1}.json".format(settings.MEDIA_URL, pk) if os.path.isfile(path) and not question.recreate_json: return redirect(url) question_json = question.get_question_json() question.recreate_json = False question.save() return redirect(question_json) @login_required @user_is_author_or_long_checker def question_json_recreate(request, pk): question = get_object_or_404(Long, pk=pk) question.recreate_json = True question.save() # We also have to invalidate any exams for exam in question.exams.all(): exam.recreate_json = True exam.save() # question.get_question_json() return redirect("longs:question_detail", pk=pk) class UserAnswerView(LoginRequiredMixin, DetailView): model = UserAnswer class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView): model = UserAnswer table_class = UserAnswerTable template_name = "longs/user_answer_question_view.html" filterset_class = UserAnswerFilter class UserAnswerDelete(SuperuserRequiredMixin, DeleteView): model = UserAnswer template_name = "user_answer_delete.html" success_url = reverse_lazy("longs:user_answer_table_view") @user_passes_test(lambda u: u.is_superuser) def user_answer_delete_multiple(request): if request.accepts("application/json")(): answer_ids = json.loads(request.POST["answer_ids"]) # We could probably delete them all at once.... for id in answer_ids: UserAnswer.objects.get(pk=id).delete() return JsonResponse({"status": "success"}) return JsonResponse({"status": "error"}) @user_passes_test(lambda u: u.is_superuser) def refresh_exam_question_json(request, pk): exam = Exam.objects.get(pk=pk) for question in exam.exam_questions.all(): question.recreate_json = True question.save() return redirect("longs:exam_overview", pk=pk) class SeriesImagesZipView(SeriesImagesZipViewBase, SuperuserRequiredMixin): series_object = LongSeries def question_review(request, pk): """ Return a json representation of the question when the exam is active """ if request.accepts("application/json")(): exam = get_object_or_404(Exam, pk=pk) if not exam.publish_results: raise Http404 question = exam.get_questions()[int(request.POST["question_number"])] question_json = question.get_question_json(based=False) return JsonResponse(question_json) return JsonResponse({"status": "error"})