Files
penracourses/sbas/views.py
T
Ross e4c6d9196c .
2022-09-04 11:22:31 +01:00

480 lines
14 KiB
Python

from reversion.views import RevisionMixin
from generic.mixins import SuperuserRequiredMixin
from django.views.generic.detail import DetailView
from generic.models import CidUser
from sbas.forms import CidUserAnswerForm, ExamAuthorForm, ExamForm
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
from django import forms
from django.utils import timezone
from django.forms.models import model_to_dict
# 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.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
from django.db.models.functions import Lower
from django.core.cache import cache
from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from .models import Question, Category, Exam, CidUserAnswer
from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView
from collections import defaultdict
import os
import base64
import mimetypes
import json
import statistics
import plotly.express as px
from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase, exam_inactive
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied
from .tables import QuestionTable, UserAnswerTable
from .filters import QuestionFilter, UserAnswerFilter
from .forms import (
QuestionForm,
ExamForm,
)
class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if self.request.user.groups.filter(name="sbas_checker").exists():
return obj
if self.request.user not in obj.author.all():
raise PermissionDenied() # or Http404
return obj
def question_list(request):
questions = Question.objects.all()
return render(request, "sbas/question_list.html", {"questions": questions})
@login_required
def question_detail(request, pk):
question = get_object_or_404(Question, pk=pk)
return render(request, "sbas/question_detail.html", {"question": question})
def active_exams(request):
exams = Exam.objects.all()
print(exams)
active_exams = []
for exam in exams:
if exam.active:
active_exams.append(exam)
return render(request, "sbas/available_exam_list.html", {"exams": active_exams})
def exam_scores_cid_user(request, pk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk)
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
questions = exam.exam_questions.all()
answers_and_marks = []
answers_marks = []
answers = []
view_all_results = False
if request.user.groups.filter(name="view_all_results").exists():
view_all_results = True
for q in questions:
# Get user answer
user_answer = q.cid_user_answers.filter(cid=cid).first()
if user_answer is None:
answer_score = 0
ans = ""
else:
answer_score = user_answer.get_answer_score()
ans = user_answer.get_answer()
correct_answer = q.get_correct_answer()
if not exam.publish_results and not view_all_results:
correct_answer = "*****"
answer_score = 0
answers.append(ans)
answers_marks.append(answer_score)
merged_ans = (ans, answer_score, correct_answer)
chosen_answer = q.get_answer_by_choice(ans)
answers_and_marks.append((q, *merged_ans, chosen_answer))
total_score = sum(answers_marks)
max_score = len(questions)
return render(
request,
"sbas/exam_scores_user.html",
{
"exam": exam,
"cid": cid,
"passcode": passcode,
"questions": questions,
"answers": answers,
"answers_marks": answers_marks,
"total_score": total_score,
"max_score": max_score,
"answers_and_marks": answers_and_marks,
"view_all_results": view_all_results,
},
)
def exam_start(request, pk):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
return exam_inactive(request, context={"exam": exam})
raise Http404("Exam not found")
return render(
request,
"sbas/exam_start.html",
{
"exam": exam,
},
)
def exam_take_overview(request, pk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
return exam_inactive(request, context={"exam": exam})
if not exam.check_cid_user(cid, passcode):
raise Http404("Error accessing exam")
questions = exam.exam_questions.all()
answers = CidUserAnswer.objects.filter(cid=cid, exam=exam)
answer_question_map = {}
for ans in answers:
answer_question_map[ans.question] = ans
question_answer_tuples = []
answer_count = 0
for q in questions:
if q in answer_question_map and answer_question_map[q].answer:
question_answer_tuples.append((q, answer_question_map[q]))
answer_count += 1
else:
question_answer_tuples.append((q, None))
c = CidUser.objects.filter(cid=cid).first()
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
return render(
request,
"sbas/exam_take_overview.html",
{
"exam": exam,
"cid": cid,
"question_answer_tuples": question_answer_tuples,
"answer_count": answer_count,
"exam_length": len(questions),
"passcode": passcode,
"cid_user_exam": cid_user_exam,
},
)
def exam_take(request, pk, sk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
raise Http404("Exam not found")
if not exam.check_cid_user(cid, passcode):
raise Http404("Error accessing exam")
c = CidUser.objects.filter(cid=cid).first()
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
question = exam.exam_questions.all()[sk]
exam_length = len(exam.exam_questions.all())
pos = exam.get_question_index(question)
answer = question.cid_user_answers.filter(cid=cid, exam=exam).first()
if request.method == "POST":
if answer:
form = CidUserAnswerForm(request.POST, instance=answer)
else:
form = CidUserAnswerForm(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.cid = cid
answer.question = question
answer.exam = exam
# answer.published_date = timezone.now()
answer.save()
cid_user_exam.end_time = timezone.now()
cid_user_exam.save()
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
if "next" in request.POST:
return redirect("sbas:exam_take", sk=pos + 1, **kwargs)
elif "previous" in request.POST:
return redirect("sbas:exam_take", sk=pos - 1, **kwargs)
elif "finish" in request.POST:
return redirect("sbas:exam_take_overview", **kwargs)
elif "goto" in request.POST:
return redirect(
"sbas:exam_take", sk=int(request.POST.get("goto")), **kwargs
)
else:
form = CidUserAnswerForm(instance=answer)
previous = -1
if sk > 0:
previous = sk - 1
next = sk + 1
if sk == exam_length - 1:
next = False
saved_answer = False
if answer is not None:
saved_answer = answer.answer
return render(
request,
"sbas/exam_take.html",
{
"form": form,
"cid": cid,
"exam": exam,
"question": question,
"next": next,
"previous": previous,
"exam_length": exam_length,
"pos": pos,
"saved_answer": saved_answer,
"passcode": passcode,
"cid_user_exam": cid_user_exam,
},
)
GenericExamViews = ExamViews(
Exam, Question, None, CidUserAnswer, "sbas", "sbas"
)
class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Question
table_class = QuestionTable
template_name = "question_table_view.html"
filterset_class = QuestionFilter
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["app_name"] = "sbas"
return context
class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
model = Question
form_class = QuestionForm
def get_form_kwargs(self):
kwargs = super(QuestionCreateBase, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(QuestionCreateBase, self).get_context_data(**kwargs)
#if self.request.POST:
# context["answer_formset"] = AnswerFormSet(
# self.request.POST, self.request.FILES
# )
#else:
# context["answer_formset"] = AnswerFormSet()
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
response = super().form_valid(form)
# 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("sbas:question_clone", pk=self.object.pk)
class QuestionCreate(QuestionCreateBase):
# initial = {'laterality': AnatomyQuestion.NONE}
def get_initial(self):
if "pk" in self.kwargs:
initial = super().get_initial()
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
initial["exams"] = [exam.id]
return initial
class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
model = Question
form_class = QuestionForm
def get_form_kwargs(self):
kwargs = super(QuestionUpdate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(QuestionUpdate, self).get_context_data(**kwargs)
return context
def form_valid(self, form):
# save exam orders (there must be a better way to do this)
#exam_orders = {}
#for exam in self.object.exams.all():
# exam_orders[exam] = list(exam.exam_questions.all())
# print(exam_orders[exam])
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
response = super().form_valid(form)
return response
class QuestionClone(QuestionCreateBase):
"""Clones a existing question"""
def get_initial(self):
# print(self.request)
old_object = get_object_or_404(Question, pk=self.kwargs["pk"])
initial_data = model_to_dict(old_object, exclude=["id"])
# We don't want to clone the exam details
# exams = old_object.exams.all().values_list("id", flat=True)
# initial_data["exams"] = list(exams)
return initial_data
class UserAnswerView(LoginRequiredMixin, DetailView):
model = CidUserAnswer
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = CidUserAnswer
table_class = UserAnswerTable
template_name = "sbas/user_answer_question_view.html"
filterset_class = UserAnswerFilter
class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
model = CidUserAnswer
template_name = "user_answer_delete.html"
success_url = reverse_lazy("sbas:user_answer_table_view")
# @user_passes_test(lambda u: u.is_superuser)
# def user_answer_delete_multiple(request):
# if request.is_ajax():
# print(request.POST["answer_ids"])
# answer_ids = json.loads(request.POST["answer_ids"])
#
# # We could probably delete them all at once....
# for id in answer_ids:
# CidUserAnswer.objects.get(pk=id).delete()
# return JsonResponse({"status": "success"})
# return JsonResponse({"status": "error"})
GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam)
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
model = Question
success_url = reverse_lazy("sbas:question_list")
class ExamCreate(ExamCreateBase):
model = Exam
form_class = ExamForm
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
model = Exam
form_class = ExamForm
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
model = Exam
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# # queryset = Exam.objects.all().order_by('name')
# serializer_class = ExamSerializer
# permission_classes = [permissions.IsAuthenticated]
#
# def get_queryset(self):
# """
# This view should return a list exams available to a user.
# """
# user = self.request.user
# if user.groups.filter(name="sbas_checker").exists():
# return Exam.objects.all()
#
# return Exam.objects.filter(author__id=user.id)
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
model = Exam
form_class = ExamAuthorForm
class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view"""