from django.urls import reverse
|
from django.shortcuts import render, get_object_or_404, Http404
|
from .models import Exam, GeneratedPaper
|
from django.http import HttpResponse, HttpResponseRedirect
|
from django.template import loader
|
from django.views import generic
|
from .forms import ExamForm, UploadForm, GenerateForm
|
from django.contrib.auth.decorators import login_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
from django.utils.decorators import method_decorator
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django_ajax.decorators import ajax
|
from django.views.decorators.csrf import csrf_exempt
|
from django.conf import settings
|
from problem.models import Problem, ProblemGroup
|
import os
|
from django.http import JsonResponse
|
from scan.models import Scan, ProblemReport
|
import json
|
import mimetypes
|
from django.contrib.auth.models import User
|
from exam.models import matrix2old_format, old_format2matrix
|
from django.shortcuts import redirect
|
|
import aoi_gen.Exam as aoi_exam
|
from aoi_gen.BerkiParse import BerkiParse as Parser
|
from aoi_gen.Problem import ProblemSource
|
import locale
|
import numpy as np
|
from operator import itemgetter, attrgetter
|
|
|
def get_username_by_sid(sid):
|
try:
|
user = User.objects.get(username=sid)
|
user_data = "{} {}".format(user.first_name, user.last_name)
|
except:
|
user_data = ""
|
return user_data
|
|
|
# POSTPROCESS VIEW
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def postprocess(request, pk):
|
locale.setlocale(locale.LC_ALL, "")
|
exam = get_object_or_404(Exam, pk=pk)
|
template = loader.get_template("exam/postprocess.html")
|
scans = Scan.objects.filter(exam=exam, page_no=1).all()
|
student_id_list = [
|
{"student_id": scan.student_id, "scan_id": scan.pk} for scan in scans
|
]
|
student_id_list=sorted(student_id_list, key=itemgetter("student_id"))
|
name_list = []
|
for scan in scans:
|
username = get_username_by_sid(scan.student_id)
|
if username != "":
|
name_list.append({"student_name": username, "scan_id": scan.pk})
|
name_list=sorted(name_list, key=lambda k: locale.strxfrm(k["student_name"], ))
|
bad_list = []
|
for scan in scans:
|
debug = json.loads(scan.ocr_debug)
|
if len(debug["errors"]):
|
bad_list.append({"student_id": scan.student_id, "scan_id": scan.pk})
|
# output=', '.join([q.question_text for q in latest_question_list])
|
# return HttpResponse(output)
|
|
report_list = []
|
for scan in scans:
|
rep = ProblemReport.objects.filter(scan=scan).all()
|
for r in rep:
|
report_list.append({"student_id": scan.student_id, "scan_id": scan.pk})
|
context = {
|
"student_id_list": student_id_list,
|
"name_list": name_list,
|
"bad_list": bad_list,
|
"report_list": report_list,
|
}
|
return HttpResponse(template.render(context, request))
|
|
|
# @login_required
|
@method_decorator(staff_member_required, name="dispatch")
|
class ExamListView(LoginRequiredMixin, generic.ListView):
|
model = Exam
|
template_name = "exam/index.html"
|
|
def get_queryset(self):
|
return Exam.objects.filter(user=self.request.user) # Here add filtering
|
|
|
# @login_required
|
@method_decorator(staff_member_required, name="dispatch")
|
class ExamNewView(LoginRequiredMixin, generic.CreateView):
|
form_class = ExamForm
|
template_name = "exam/exam_new.html"
|
|
def get_success_url(self):
|
return reverse("examlist", args=())
|
|
|
# @login_required
|
@method_decorator(staff_member_required, name="dispatch")
|
class ExamDetailView(LoginRequiredMixin, generic.UpdateView, generic.DetailView):
|
model = Exam
|
form_class = ExamForm
|
template_name = "exam/exam_detail.html"
|
|
def get_success_url(self):
|
return reverse("examdetail", args=(self.object.pk,))
|
|
def get_context_data(self, **kwargs):
|
# Call the base implementation first to get the context
|
context = super(ExamDetailView, self).get_context_data(**kwargs)
|
# Create any data and add it to the context
|
context["some_data"] = "This is just some data"
|
return context
|
|
|
# @login_required
|
@method_decorator(staff_member_required, name="dispatch")
|
class GenerateExamView(LoginRequiredMixin, generic.UpdateView):
|
model = Exam
|
form_class = ExamForm
|
template_name = "exam/generate.html"
|
|
def get_success_url(self):
|
return reverse("examlist", args=())
|
|
|
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def statistics(request, pk):
|
exam = get_object_or_404(Exam, pk=pk)
|
try:
|
scans = Scan.objects.filter(exam=exam).all()
|
except:
|
pass
|
bins={0:0, 10:0,20:0,30:0,40:0,50:0,60:0,70:0,80:0,90:0,100:0}
|
for scan in scans:
|
p, g = scan.grade()
|
if p<0:
|
p=0
|
bins[int(np.floor(p/10)*10)]+=1
|
keys=str(list(bins.keys()))
|
values=str(list(bins.values()))
|
template = loader.get_template("exam/statistics.html")
|
context={'exam':{'id':pk},'keys':keys, 'values':values, 'fullexam': exam}
|
return HttpResponse(template.render(context, request))
|
|
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def get_exam_csv(request, pk):
|
try:
|
exam = Exam.objects.get(pk=pk)
|
except:
|
raise Http404
|
try:
|
scans= Scan.objects.filter(exam=exam, page_no=1).all()
|
except:
|
raise Http404
|
grades=[]
|
for scan in scans:
|
p,g=scan.grade()
|
sid=scan.student_id
|
username=get_username_by_sid(sid)
|
# try:
|
# user=User.objects.get(username=sid)
|
# except:
|
# user=None
|
# if(user is not None):
|
# user_last=user.last_name
|
# user_first=user.first_name
|
# else:
|
# user_last=""
|
# user_first=""
|
grades.append([sid, username, g, p])
|
locale.setlocale(locale.LC_ALL, "")
|
grades=sorted(grades, key=lambda k:locale.strxfrm(k[1]))
|
retval="SID, Student Name, Grade, Grade percentage\n"
|
for g in grades:
|
retval=retval+"{},{},{},{}\n".format(g[0],g[1], g[2], g[3])
|
|
mimetype="text/csv"
|
response=HttpResponse(retval, content_type=mimetype, charset='utf-8')
|
response["Content-Length"] = len(bytes(retval,encoding='utf8'))
|
response["Content-Disposition"] = "inline; filename=statistics.csv"
|
return response
|
|
|
|
@login_required
|
@staff_member_required
|
def setdefault(request, pk):
|
request.session['selected_exam']=pk
|
request.session['selected_exam_name']=str(Exam.objects.get(pk=pk))
|
return redirect(reverse("examlist", args=()))
|
|
|
# AJAX
|
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def addProblemToExam(request, pk):
|
import json
|
problem=Problem.objects.get(pk=pk)
|
exam_id=request.session.get('selected_exam')
|
try:
|
exam=Exam.objects.get(pk=exam_id)
|
except:
|
exam=None
|
exam.add_problem(problem)
|
return json.loads({'status': 'ok'})
|
|
@ajax
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def saveGrouping(request, pk):
|
import json
|
|
# article=Article.objects.get(id=article_id)
|
# if(article.amount>0):
|
# article.amount-=1
|
# article.save()
|
# data={'inner-fragments': { '#amount-'+str(article_id): article.amount } }
|
data = {"inner-fragments": {"#test": pk}}
|
print(json.loads(request.POST["data"]))
|
return data
|
|
|
@ajax
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def load_values(request, pk):
|
try:
|
scan = Scan.objects.get(pk=pk, page_no=1)
|
except:
|
print("CANNOT FIND PAPER ID")
|
return JsonResponse({"vpisna": "ERROR"})
|
|
# get number of pages
|
subscans = Scan.objects.filter(paper=scan.paper).all().order_by("page_no")
|
print(len(subscans))
|
answer_matrix = []
|
for ss in subscans:
|
answer_matrix = answer_matrix + eval(ss.answer_matrix)
|
answers = matrix2old_format(answer_matrix)
|
print(answers)
|
subpages = ""
|
for i, id in enumerate(subscans):
|
subpages += "<input type='button' value='{}' onclick='load_image_page({},{})' />".format(
|
i + 1, id.pk, i + 1
|
)
|
debugdata = json.loads(scan.ocr_debug)
|
try:
|
user = User.objects.get(username=scan.student_id)
|
user_data = "{} {}".format(user.first_name, user.last_name)
|
except:
|
user_data = ""
|
percentage,grade=scan.grade()
|
data = {
|
"vpisna": scan.student_id,
|
"crtna": debugdata["qr"], # str(scan.exam.pk) + " " + str(scan.paper.pk),
|
"ocena": grade,
|
"procent": percentage,
|
"opispritozbe": scan.ocr_debug,
|
"slika": '<a href="/exam/show_image/'
|
+ str(scan.pk)
|
+ '/1"><img src="/exam/show_image/'
|
+ str(scan.id)
|
+ '/1" width=800 height=600 border=0></a>',
|
"pages": subpages,
|
"odgovori": answers,
|
"pravilni": matrix2old_format(scan.paper.answer_matrix),
|
"ime": user_data,
|
"pritozba": ""
|
}
|
return JsonResponse(data)
|
|
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def save_values(request, pk):
|
#print("WAS HERE")
|
try:
|
scan = Scan.objects.get(pk=pk, page_no=1)
|
except:
|
return JsonResponse({"vpisna": "ERROR"})
|
scan.student_id = request.GET["vpisna"]
|
scan.answer_matrix = old_format2matrix(request.GET["odgovori"])
|
scan.save()
|
return JsonResponse({"vpisna": "OK"})
|
|
|
@login_required
|
@staff_member_required
|
@csrf_exempt
|
def show_image(request, pk, pageno):
|
filename = Scan.objects.get(pk=pk, page_no=pageno).scan_image.name
|
file_path = os.path.join(settings.MEDIA_ROOT, filename)
|
if os.path.exists(file_path):
|
with open(file_path, "rb") as fh:
|
file_mimetype = mimetypes.guess_type(file_path)
|
response = HttpResponse(fh.read(), content_type=file_mimetype)
|
response["Content-Length"] = os.stat(file_path).st_size
|
response["Content-Disposition"] = "inline; filename=" + os.path.basename(
|
file_path
|
)
|
return response
|
raise Http404
|
|
|
class Directory:
|
"""
|
Class deals with the paths where the simulation is run and data is stored.
|
"""
|
|
def __init__(self, maindir=".", simdir=""):
|
"""Initialization Directory() takes two optional parameters, namely maindir and simdir. Defaults to current directory. It sets local variables maindir and simdir accordingly."""
|
self.maindir = maindir
|
self.simdir = simdir
|
return
|
|
def fullpath(self):
|
"""
|
Method returns string of path where the data is stored. It combines values of maindir and simdir as maindir/simdir on Unix.
|
"""
|
return os.path.join(self.maindir, self.simdir)
|
|
def exists(self):
|
""" Method checks whether the directory specified by fullpath() exists. It return True/False on completion."""
|
path = self.fullpath()
|
if os.path.exists(path):
|
return True
|
else:
|
return False
|
|
def make(self):
|
""" Method make() creates directory. If it fails it exits the program with error message."""
|
try:
|
os.makedirs(self.fullpath())
|
except:
|
print("Cannot make directory " + self.fullpath() + "\n")
|
exit(1)
|
return
|
|
def makeifnotexist(self):
|
"""Method makeifnotexist() creates directory if it does not exist."""
|
if self.exists() == 0:
|
self.make()
|
return True
|
else:
|
return False
|
|
def remove(self):
|
"""Method remove() removes directory recursively. WARNING! No questions asked."""
|
if self.exists():
|
try:
|
os.rmdir(self.fullpath())
|
except:
|
print("Cannot remove directory " + self.fullpath() + "\n")
|
exit(1)
|
return
|
|
def goto(self):
|
"""
|
Method goto() moves current directory to the one specified by fullpath(). WARNING: when using the relative paths, do not call this function multiple times.
|
"""
|
try:
|
os.chdir(self.fullpath())
|
except:
|
print("Cannot go to directory " + self.fullpath() + "\n")
|
return
|
|
|
# @login_required
|
# @staff_member_required
|
# decorators are not required, since this is not a view!!!!!
|
def handle_uploaded_file(f, exam):
|
path = Directory(os.path.join(settings.BASE_DIR, "exams"), str(exam.pk))
|
path.makeifnotexist()
|
filename = str(f)
|
extension = filename.split(".")[-1]
|
with open(os.path.join(path.fullpath(), filename), "wb+") as destination:
|
for chunk in f.chunks():
|
destination.write(chunk)
|
|
if extension == "txt":
|
content = ""
|
with open(os.path.join(path.fullpath(), filename), "r", encoding='utf-8') as fd:
|
content = fd.read()
|
try:
|
p = Problem.objects.get(title=filename)
|
except:
|
p = Problem(title=filename)
|
p.content =content
|
p.save()
|
g = ProblemGroup(title=filename, problemhomegroup=True)
|
g.save()
|
g.problem.add(p)
|
|
exam.add_problem(p)
|
|
# Upload form
|
@login_required
|
@staff_member_required
|
def upload_file(request, pk):
|
exam = get_object_or_404(Exam, pk=pk)
|
|
if request.method == "POST":
|
form = UploadForm(request.POST, request.FILES)
|
if form.is_valid():
|
for f in request.FILES.getlist("file_field"):
|
handle_uploaded_file(f, exam)
|
return HttpResponseRedirect(reverse("examdetail", args=(pk,)))
|
else:
|
form = UploadForm()
|
context = {"form": form, "exam": exam}
|
return render(request, "exam/exam_upload.html", context)
|
|
|
# Generating the exams
|
|
|
@login_required
|
@staff_member_required
|
def generate_view(request, pk):
|
exam = get_object_or_404(Exam, pk=pk)
|
template = loader.get_template("exam/generate.html")
|
form = GenerateForm()
|
test_no=1
|
if request.method == "POST":
|
form = GenerateForm(request.POST)
|
if form.is_valid():
|
cd = form.cleaned_data
|
test_no = request.POST.get("number_of_tests", 1)
|
is_final = True if request.POST.get("final", False) == "on" else False
|
# now in the object cd, you have the form as a dictionary.
|
# a = cd.get('a')
|
# print(request.POST)
|
exam_settings = {
|
"exam_title": "{} iz predmeta {} ({})".format(
|
exam.type, exam.subject, exam.study_type
|
),
|
"date": exam.date.strftime("%d. %m. %Y"),
|
"faculty_name": exam.subject.faculty,
|
"faculty_id": exam.subject.faculty.pk,
|
"exam_id": exam.pk,
|
"last_line": exam.last_line,
|
"start_number": -1,
|
"sid_prefill": exam.subject.faculty.sid_mask,
|
}
|
student_list = exam.student_list
|
if student_list is not None and student_list!='':
|
student_list = [
|
student_line.split(",")
|
for student_line in exam.student_list.split("\n")
|
]
|
# else:
|
# student_list = [("64990162", "Samo Krneki")]
|
aoiexam = aoi_exam.Exam(
|
settings=exam_settings,
|
student_list=student_list,
|
number_of_papers=int(test_no),
|
template=os.path.join(settings.BASE_DIR, "textemplates"),
|
)
|
aoiexam.problem_source_list = []
|
for problem_groups in exam.problem_group.all():
|
problem_in_group_list=[]
|
for problem_data_source in problem_groups.problem.all():
|
par = Parser(problem_data_source.content)
|
par.parseSections()
|
problem_in_group_list.append({'id':problem_data_source.pk,'parsed_source':ProblemSource(parser=par)})
|
aoiexam.problem_source_list.append({
|
'amount': problem_groups.amount,
|
'problems': problem_in_group_list}
|
)
|
aoiexam.generatePapers()
|
exam.generated_tex = aoiexam.exam_to_template()
|
answer_matrix = aoiexam.get_answers()
|
#if exam.finalized == False:
|
#Here we had a bug. If it was finalized before, it didn't delete :).
|
GeneratedPaper.objects.filter(exam=exam).delete()
|
#End bug
|
for ser, (answer_paper, paper) in enumerate(
|
zip(answer_matrix, aoiexam.paper_list)
|
):
|
g = GeneratedPaper()
|
g.answer_matrix = answer_paper
|
if paper.student_id is not None:
|
g.student_id = paper.student_id[0]
|
g.serial_no = ser
|
g.exam = exam
|
g.save()
|
if is_final:
|
exam.finalized = True
|
else:
|
exam.finalized = False
|
exam.save()
|
form = GenerateForm() #This must be here. It's a dirty hack. If problems are regenerated, this assures new version will be visible!!!
|
form.initial['generated_tex'] = exam.generated_tex
|
form.initial['final'] = exam.finalized
|
form.initial['number_of_tests'] = test_no
|
|
context = {"form": form, "exam": exam}
|
return HttpResponse(template.render(context, request))
|