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 collections import defaultdict import os import base64 import mimetypes import json import statistics import plotly.express as px def question_list(request): questions = Question.objects.all() return render(request, "physics/question_list.html", {"questions": questions}) @login_required def index(request): exams = Exam.objects.all() return render(request, "physics/index.html", {"exams": exams}) @login_required 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 exam_list(request): exams = Exam.objects.all() return render(request, "physics/exam_list.html", {"exams": exams}) @login_required def exam_overview(request, pk): print(type(pk)) print(Exam.objects.all()) exams = Exam.objects.all() print("test", Exam.objects.all().get(id=pk)) exam = get_object_or_404(Exam, pk=pk) print("exam", exam) questions = exam.exam_questions.all() question_number = len(questions) return render( request, "physics/exam_overview.html", {"exam": exam, "questions": questions, "question_number": question_number}, ) def exam_toggle_results_published(request, pk): if request.is_ajax() and request.method == "POST": exam = get_object_or_404(Exam, pk=pk) exam.publish_results = ( True if request.POST.get("publish_results") == "true" else False ) exam.save() data = {"status": "success", "publish_results": exam.publish_results} return JsonResponse(data, status=200) else: data = {"status": "error"} return JsonResponse(data, status=400) def exam_toggle_active(request, pk): if request.is_ajax() and request.method == "POST": exam = get_object_or_404(Exam, pk=pk) exam.active = True if request.POST.get("active") == "true" else False exam.save() data = {"status": "success", "active": exam.active} return JsonResponse(data, status=200) else: data = {"status": "error"} return JsonResponse(data, status=400) 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}) @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_answers() answer_scores = s.get_answer_scores() user_answers[cid].append(ans) user_answers_marks[cid].append(answer_scores) user_answers_and_marks[cid].append((ans, answer_scores)) zipped_ans_scores = zip(ans, answer_scores) by_question[q].append(zipped_ans_scores) user_scores = {} for user in user_answers_marks: user_scores[user] = sum([sum(i) for i in 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, "physics/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 = [] for q in questions: # Get user answer user_answer = q.cid_user_answers.filter(cid=cid).first() ans = user_answer.get_answers() answer_scores = user_answer.get_answer_scores() correct_answers = q.get_answers() if not exam.publish_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, "questions": questions, "answers": answers, "answers_marks": answers_marks, "total_score": total_score, "max_score": max_score, "answers_and_marks": answers_and_marks, }, ) def exam_take(request, pk): exam = get_object_or_404(Exam, pk=pk) if not exam.active: raise Http404("Exam not found") questions = exam.exam_questions.all() return render( request, "physics/exam_take.html", { "exam": exam, "questions": questions, }, ) @csrf_exempt def postExamAnswers(request): if request.is_ajax and request.method == "POST": n = 0 # horrible but it works for k in request.POST.dict(): print(k) ret = loadJsonAnswer(json.loads(k)) if ret is not True: return ret # print(UserAnswer.objects.filter(exam__id=q["eid"])) # print(request.urlencode()) return JsonResponse({"success": True}) return JsonResponse({"success": False, "error": "Invalid data"}) 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 JsonResponse({"success": False, "error": "invalid"}) # The model should catch invalid data but this should be less intensive max_int = 10000000 if answer["cid"] > max_int or answer["eid"] > max_int: return JsonResponse({"success": False}) exam = get_object_or_404(Exam, pk=answer["eid"]) if not exam.active: return 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