Files
penracourses/physics/views.py
T
Ross fd597819d1 .
2021-01-12 18:16:44 +00:00

367 lines
9.8 KiB
Python

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",
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