from generic.mixins import SuperuserRequiredMixin from django.views.generic.detail import DetailView from sbas.forms import CidUserAnswerForm from django.shortcuts import render, get_object_or_404, redirect from django.views.decorators.csrf import csrf_exempt 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.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 ExamViews, GenericViewBase 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 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}) @login_required def exam_scores_cid(request, pk): exam = get_object_or_404(Exam, pk=pk) questions = exam.exam_questions.all() cids = ( CidUserAnswer.objects.filter(question__in=questions) .order_by("cid") .values_list("cid", flat=True) .distinct() ) user_answers_and_marks = defaultdict(list) user_answers_marks = defaultdict(list) user_answers = defaultdict(list) user_names = {} by_question = defaultdict(list) unmarked = set() # Loop through all candidates for cid in cids: # Convoluted (probably...) user_names[cid] = cid for q in questions: # Get user answer s = q.cid_user_answers.filter(cid=cid).first() if not s: # skip if no answer user_answers_marks[cid].append(0) user_answers[cid].append("") by_question[q].append(("", 0)) continue ans = s.get_answer() answer_score = s.get_score() user_answers[cid].append(ans) user_answers_marks[cid].append(answer_score) user_answers_and_marks[cid].append((ans, answer_score)) by_question[q].append((ans, answer_score)) user_scores = {} for user in user_answers_marks: user_scores[user] = sum(user_answers_marks[user]) user_scores_list = list(user_scores.values()) if len(user_scores_list) < 1: mean = 0 median = 0 mode = 0 fig_html = "" else: mean = statistics.mean(user_scores_list) median = statistics.median(user_scores_list) try: mode = statistics.mode(user_scores_list) except statistics.StatisticsError: mode = "No unique mode" df = user_scores_list fig = px.histogram( df, x=0, title="{}: distribution of scores".format(exam), labels={"0": "Score"}, height=400, width=600, ) fig_html = fig.to_html() max_score = len(questions) return render( request, "sbas/exam_scores.html", { "cids": cids, "exam": exam, "unmarked": unmarked, "questions": questions, "by_question": by_question, "user_answers": dict(user_answers), "user_answers_marks": dict(user_answers_marks), "user_scores": user_scores, "user_scores_list": user_scores_list, "user_names": user_names, "user_answers_and_marks": user_answers_and_marks, "max_score": max_score, "mean": mean, "median": median, "mode": mode, "plot": fig_html, }, ) def exam_scores_cid_user(request, pk, sk): exam = get_object_or_404(Exam, pk=pk) # TODO:Need some kind of test for cid cid = sk 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_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) answers_and_marks.append((q, *merged_ans)) total_score = sum(answers_marks) max_score = len(questions) return render( request, "sbas/exam_scores_user.html", { "exam": exam, "cid": cid, "questions": questions, "answers": answers, "answers_marks": answers_marks, "total_score": total_score, "max_score": max_score, "answers_and_marks": answers_and_marks, }, ) def exam_start(request, pk): exam = get_object_or_404(Exam, pk=pk) if not exam.active: raise Http404("Exam not found") return render( request, "sbas/exam_start.html", { "exam": exam, }, ) def exam_finish(request, pk, cid): exam = get_object_or_404(Exam, pk=pk) 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)) if not exam.active: raise Http404("Exam not found") return render( request, "sbas/exam_finish.html", { "exam": exam, "cid": cid, "question_answer_tuples": question_answer_tuples, "answer_count": answer_count, "exam_length": len(questions), }, ) def exam_take(request, pk, sk, cid): exam = get_object_or_404(Exam, pk=pk) if not exam.active: raise Http404("Exam not found") 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() if "next" in request.POST: return redirect("sbas:exam_take", pk=pk, sk=pos + 1, cid=cid) elif "previous" in request.POST: return redirect("sbas:exam_take", pk=pk, sk=pos - 1, cid=cid) elif "finish" in request.POST: return redirect("sbas:exam_finish", pk=pk, cid=cid) elif "goto" in request.POST: return redirect("sbas:exam_take", pk=pk, sk=int(request.POST.get("goto")), cid=cid) 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, }, ) def loadJsonAnswer(answer): pass SbasExamViews = ExamViews(Exam, Question, "sbas", "sbas", loadJsonAnswer) class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView): model = Question table_class = QuestionTable template_name = "sbas/question_view.html" filterset_class = QuestionFilter 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)