from django.contrib.contenttypes.models import ContentType from django.utils import timezone from generic.models import CidUser from physics.decorators import user_is_author_or_physics_checker from physics.filters import QuestionFilter, UserAnswerFilter from generic.mixins import SuperuserRequiredMixin from physics.tables import QuestionTable, UserAnswerTable 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.detail import DetailView 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 django_filters.views import FilterView from django_tables2.views import SingleTableMixin from reversion.views import RevisionMixin from .models import Question, Category, Exam, CidUserAnswer 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 .forms import CidUserAnswerForm, ExamAuthorForm, ExamForm from .decorators import ( user_is_author_or_physics_checker, user_is_exam_author_or_physics_checker, ) class AuthorOrCheckerRequiredMixin(object): def get_object(self, *args, **kwargs): obj = super().get_object(*args, **kwargs) if self.request.user.groups.filter(name="physics_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, "physics/question_list.html", {"questions": questions}) @login_required @user_is_author_or_physics_checker def question_detail(request, pk): question = get_object_or_404(Question, pk=pk) return render(request, "physics/question_detail.html", {"question": question}) @login_required 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, "physics/available_exam_list.html", {"exams": active_exams}) def exam_scores_cid_user(request, pk, cid=None, passcode=None): exam = get_object_or_404(Exam, pk=pk) if cid is not None and 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 if cid is not None: user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first() else: user_answer = q.cid_user_answers.filter( user=request.user, exam__id=pk ).first() if user_answer is None: answer_scores = (0, 0, 0, 0, 0) ans = ("", "", "", "", "") else: ans = user_answer.get_answers() answer_scores = user_answer.get_answer_score() correct_answers = q.get_answers() if not exam.publish_results and not view_all_results: correct_answers = ("*****", "*****", "*****", "*****", "*****") answer_scores = (0, 0, 0, 0, 0) answers.append(ans) answers_marks.append(answer_scores) merged_ans = zip(q.get_questions(), ans, answer_scores, correct_answers) answers_and_marks.append((q, merged_ans)) total_score = sum(sum(i) for i in answers_marks) max_score = len(questions) * 5 return render( request, "physics/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_take_old(request, pk): exam = get_object_or_404(Exam, pk=pk) if not exam.active: raise exam_inactive(request) questions = exam.exam_questions.all() return render( request, "physics/exam_take_old.html", { "exam": exam, "questions": questions, }, ) def exam_start(request, pk): exam = get_object_or_404(Exam, pk=pk) if not exam.active: return exam_inactive(request, context={"exam": exam}) return render( request, "physics/exam_start.html", { "exam": exam, }, ) def exam_take_overview(request, pk, cid=None, passcode=None): exam = get_object_or_404(Exam, pk=pk) if cid is not None and not exam.check_cid_user(cid, passcode): raise Http404("Error accessing exam") if not exam.active: return exam_inactive(request, context={"exam": exam}) questions = exam.exam_questions.all() if cid is not None: answers = CidUserAnswer.objects.filter(cid=cid, exam=exam) else: answers = CidUserAnswer.objects.filter(user=request.user, 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: if q in answer_question_map: # might need to improve this question_answer_tuples.append((q, answer_question_map[q])) answer_count += 1 else: question_answer_tuples.append((q, None)) if cid is not None: c = CidUser.objects.filter(cid=cid).first() cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) else: cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) return render( request, "physics/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=None, passcode=None): exam = get_object_or_404(Exam, pk=pk) if not exam.active: return exam_inactive(request, context={"exam": exam}) if cid is not None and not exam.check_cid_user(cid, passcode): raise Http404("Error accessing exam") if cid is not None: c = CidUser.objects.filter(cid=cid).first() cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c) else: cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user) question = exam.exam_questions.all()[sk] exam_length = len(exam.exam_questions.all()) pos = exam.get_question_index(question) if cid is not None: answer = question.cid_user_answers.filter(cid=cid, exam=exam).first() else: answer = question.cid_user_answers.filter(user=request.user, exam=exam).first() if request.method == "POST": if answer: form = CidUserAnswerForm(request.POST, instance=answer) else: form = CidUserAnswerForm(request.POST) if form.is_valid() and not exam.publish_results: answer = form.save(commit=False) if cid is not None: answer.cid = cid else: answer.user = request.user answer.question = question answer.exam = exam # answer.published_date = timezone.now() answer.save() cid_user_exam.end_time = timezone.now() cid_user_exam.save() if cid is not None: kwargs = {"pk": pk, "cid": cid, "passcode": passcode} take_url = "physics:exam_take" finish_url = "physics:exam_take_overview" else: kwargs = {"pk": pk} take_url = "physics:exam_take_user" finish_url = "physics:exam_take_overview_user" if "next" in request.POST: return redirect(take_url, sk=pos + 1, **kwargs) elif "previous" in request.POST: return redirect(take_url, sk=pos - 1, **kwargs) elif "finish" in request.POST: return redirect(finish_url, **kwargs) elif "goto" in request.POST: return redirect(take_url, 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.a, answer.b, answer.c, answer.d, answer.e] return render( request, "physics/exam_take.html", { "form": form, "cid": cid, "exam": exam, "question": question, "next": next, "previous": previous, "exam_length": exam_length, "pos": pos, "passcode": passcode, "cid_user_exam": cid_user_exam, }, ) # def loadJsonAnswer(answer): # # As access is not restricted make sure the data appears valid # if (not isinstance(answer["cid"], int)) or (not isinstance(answer["eid"], int)): # return False, JsonResponse({"success": False, "error": "invalid"}) # # # The model should catch invalid data but this should be less intensive # max_int = 2147483647 # if answer["cid"] > max_int or answer["eid"] > max_int: # return False, JsonResponse({"success": False, "error": "invalid candidate id"}) # # exam = get_object_or_404(Exam, pk=answer["eid"]) # # # if not exam.active: # # return False, JsonResponse( # # {"success": False, "error": "No active exam: {}".format(answer["eid"])} # # ) # # questions = defaultdict(dict) # # for q, n, a in answer["ans"]: # questions[q][n] = a # pass # # for qid in questions: # exiting_answers = CidUserAnswer.objects.filter( # question__id=qid, exam__id=answer["eid"], cid=answer["cid"] # ) # # a = questions[qid]["a"] # b = questions[qid]["b"] # c = questions[qid]["c"] # d = questions[qid]["d"] # e = questions[qid]["e"] # # if not exiting_answers: # ans = CidUserAnswer(a=a, b=b, c=c, d=d, e=e, cid=answer["cid"]) # ans.question_id = qid # ans.exam_id = answer["eid"] # # ans.full_clean() # # ans.save() # else: # # Update an existing answer # # should never be more than one (famous last words) # ans = exiting_answers[0] # ans.a = a # ans.b = b # ans.c = c # ans.d = d # ans.e = e # ans.full_clean() # ans.save() # # return True, None GenericExamViews = ExamViews(Exam, Question, None, CidUserAnswer, "physics", "physics") GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) class ExamCreate(ExamCreateBase): model = Exam form_class = ExamForm class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin): model = Exam form_class = ExamForm class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase): model = Exam class ExamClone(AuthorOrCheckerRequiredMixin, ExamCloneMixin, ExamCreate): """Clone exam view""" class ExamAuthorUpdate( RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView ): model = Exam form_class = ExamAuthorForm class QuestionView( LoginRequiredMixin, SingleTableMixin, FilterView, AuthorOrCheckerRequiredMixin ): 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"] = "physics" return context class UserAnswerView(AuthorOrCheckerRequiredMixin, LoginRequiredMixin, DetailView): model = CidUserAnswer class UserAnswerTableView( AuthorOrCheckerRequiredMixin, LoginRequiredMixin, SingleTableMixin, FilterView ): model = CidUserAnswer table_class = UserAnswerTable template_name = "physics/user_answer_question_view.html" filterset_class = UserAnswerFilter class UserAnswerDelete(SuperuserRequiredMixin, DeleteView): model = CidUserAnswer template_name = "user_answer_delete.html" success_url = reverse_lazy("physics:user_answer_table_view")