add (correct) support for logged in users to sba and physics exams
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
|
||||
<h2>Start exam: {{exam.name}}</h2>
|
||||
|
||||
{% if request.user.is_authenticated and valid_user %}
|
||||
<a href="{% url 'sbas:exam_take_user' pk=exam.pk sk=0 %}">Start Exam</a>
|
||||
{% else %}
|
||||
Enter your CID and passcode in the below boxes.<br />
|
||||
<p><input id="cid-box" type="text" value="Candidate ID"></p>
|
||||
<p><input id="passcode-box" type="text" value="Passcode"></p>
|
||||
@@ -44,4 +47,5 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -32,6 +32,11 @@ urlpatterns = [
|
||||
views.exam_take,
|
||||
name="exam_take",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/<int:sk>/take",
|
||||
views.exam_take,
|
||||
name="exam_take_user",
|
||||
),
|
||||
path("exam/<int:pk>/start", views.exam_start, name="exam_start"),
|
||||
path(
|
||||
"exam/<int:pk>/<str:cid>/<str:passcode>/finish",
|
||||
|
||||
+76
-46
@@ -24,7 +24,7 @@ from django.core.cache import cache
|
||||
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.http import Http404, HttpResponseBadRequest, JsonResponse
|
||||
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
|
||||
@@ -41,7 +41,16 @@ import json
|
||||
import statistics
|
||||
import plotly.express as px
|
||||
|
||||
from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase, exam_inactive
|
||||
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
|
||||
@@ -156,29 +165,29 @@ def exam_start(request, pk):
|
||||
|
||||
if not exam.active:
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
raise Http404("Exam not found")
|
||||
|
||||
return render(
|
||||
request,
|
||||
"sbas/exam_start.html",
|
||||
{
|
||||
"exam": exam,
|
||||
},
|
||||
{"exam": exam, "valid_user": exam.check_logged_in_user(request)},
|
||||
)
|
||||
|
||||
|
||||
def exam_take_overview(request, pk, cid, passcode):
|
||||
def exam_take_overview(request, pk, cid=None, passcode=None):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
|
||||
if not exam.check_cid_user(cid, passcode):
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
questions = exam.exam_questions.all()
|
||||
|
||||
answers = UserAnswer.objects.filter(cid=cid, exam=exam)
|
||||
if cid is not None:
|
||||
answers = UserAnswer.objects.filter(cid=cid, exam=exam)
|
||||
else:
|
||||
answers = UserAnswer.objects.filter(user=request.user, exam=exam)
|
||||
|
||||
answer_question_map = {}
|
||||
for ans in answers:
|
||||
@@ -193,8 +202,11 @@ def exam_take_overview(request, pk, cid, passcode):
|
||||
else:
|
||||
question_answer_tuples.append((q, None))
|
||||
|
||||
c = CidUser.objects.filter(cid=cid).first()
|
||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
||||
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,
|
||||
@@ -211,18 +223,20 @@ def exam_take_overview(request, pk, cid, passcode):
|
||||
)
|
||||
|
||||
|
||||
def exam_take(request, pk, sk, cid, passcode):
|
||||
|
||||
def exam_take(request, pk: int, sk: int, cid: str = None, passcode: str = None):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
raise Http404("Exam not found")
|
||||
|
||||
if not exam.check_cid_user(cid, passcode):
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
c = CidUser.objects.filter(cid=cid).first()
|
||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c)
|
||||
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]
|
||||
|
||||
@@ -230,7 +244,17 @@ def exam_take(request, pk, sk, cid, passcode):
|
||||
|
||||
pos = exam.get_question_index(question)
|
||||
|
||||
answer = question.cid_user_answers.filter(cid=cid, exam=exam).first()
|
||||
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()
|
||||
|
||||
previous = -1
|
||||
if sk > 0:
|
||||
previous = sk - 1
|
||||
next = sk + 1
|
||||
if sk == exam_length - 1:
|
||||
next = False
|
||||
|
||||
if request.method == "POST":
|
||||
if answer:
|
||||
@@ -239,7 +263,11 @@ def exam_take(request, pk, sk, cid, passcode):
|
||||
form = UserAnswerForm(request.POST)
|
||||
if form.is_valid():
|
||||
answer = form.save(commit=False)
|
||||
answer.cid = cid
|
||||
|
||||
if cid is not None:
|
||||
answer.cid = cid
|
||||
else:
|
||||
answer.user = request.user
|
||||
answer.question = question
|
||||
answer.exam = exam
|
||||
# answer.published_date = timezone.now()
|
||||
@@ -248,28 +276,28 @@ def exam_take(request, pk, sk, cid, passcode):
|
||||
cid_user_exam.end_time = timezone.now()
|
||||
cid_user_exam.save()
|
||||
|
||||
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
|
||||
if cid is not None:
|
||||
kwargs = {"pk": pk, "cid": cid, "passcode": passcode}
|
||||
take_url = "sbas:exam_take"
|
||||
finish_url = "sbas:exam_take_overview"
|
||||
else:
|
||||
kwargs = {"pk": pk}
|
||||
take_url = "sbas:exam_take_user"
|
||||
finish_url = "sbas:exam_take_overview_user"
|
||||
|
||||
if "next" in request.POST:
|
||||
return redirect("sbas:exam_take", sk=pos + 1, **kwargs)
|
||||
if not next:
|
||||
return HttpResponseBadRequest("Invalid request")
|
||||
return redirect(take_url, sk=pos + 1, **kwargs)
|
||||
elif "previous" in request.POST:
|
||||
return redirect("sbas:exam_take", sk=pos - 1, **kwargs)
|
||||
return redirect(take_url, sk=pos - 1, **kwargs)
|
||||
elif "finish" in request.POST:
|
||||
return redirect("sbas:exam_take_overview", **kwargs)
|
||||
return redirect(finish_url, **kwargs)
|
||||
elif "goto" in request.POST:
|
||||
return redirect(
|
||||
"sbas:exam_take", sk=int(request.POST.get("goto")), **kwargs
|
||||
)
|
||||
return redirect(take_url, sk=int(request.POST.get("goto")), **kwargs)
|
||||
else:
|
||||
form = UserAnswerForm(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
|
||||
@@ -293,9 +321,7 @@ def exam_take(request, pk, sk, cid, passcode):
|
||||
)
|
||||
|
||||
|
||||
GenericExamViews = ExamViews(
|
||||
Exam, Question, None, UserAnswer, "sbas", "sbas"
|
||||
)
|
||||
GenericExamViews = ExamViews(Exam, Question, None, UserAnswer, "sbas", "sbas")
|
||||
|
||||
|
||||
class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||
@@ -310,6 +336,7 @@ class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||
context["app_name"] = "sbas"
|
||||
return context
|
||||
|
||||
|
||||
class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||
model = Question
|
||||
form_class = QuestionForm
|
||||
@@ -321,22 +348,20 @@ class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(QuestionCreateBase, self).get_context_data(**kwargs)
|
||||
#if self.request.POST:
|
||||
# if self.request.POST:
|
||||
# context["answer_formset"] = AnswerFormSet(
|
||||
# self.request.POST, self.request.FILES
|
||||
# )
|
||||
#else:
|
||||
# else:
|
||||
# context["answer_formset"] = AnswerFormSet()
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
self.object = form.save(commit=False)
|
||||
self.object.save()
|
||||
|
||||
form.instance.author.add(self.request.user.id)
|
||||
|
||||
|
||||
response = super().form_valid(form)
|
||||
# If the normal submit button is pressed we save as normal
|
||||
if "submit" in self.request.POST:
|
||||
@@ -347,7 +372,6 @@ class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||
|
||||
|
||||
class QuestionCreate(QuestionCreateBase):
|
||||
|
||||
# initial = {'laterality': AnatomyQuestion.NONE}
|
||||
|
||||
def get_initial(self):
|
||||
@@ -359,6 +383,7 @@ class QuestionCreate(QuestionCreateBase):
|
||||
|
||||
return initial
|
||||
|
||||
|
||||
class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||
model = Question
|
||||
form_class = QuestionForm
|
||||
@@ -373,10 +398,9 @@ class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
# save exam orders (there must be a better way to do this)
|
||||
#exam_orders = {}
|
||||
#for exam in self.object.exams.all():
|
||||
# exam_orders = {}
|
||||
# for exam in self.object.exams.all():
|
||||
# exam_orders[exam] = list(exam.exam_questions.all())
|
||||
# print(exam_orders[exam])
|
||||
|
||||
@@ -389,6 +413,7 @@ class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||
response = super().form_valid(form)
|
||||
return response
|
||||
|
||||
|
||||
class QuestionClone(QuestionCreateBase):
|
||||
"""Clones a existing question"""
|
||||
|
||||
@@ -404,6 +429,7 @@ class QuestionClone(QuestionCreateBase):
|
||||
|
||||
return initial_data
|
||||
|
||||
|
||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||
model = UserAnswer
|
||||
|
||||
@@ -450,9 +476,11 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
model = Exam
|
||||
|
||||
|
||||
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
# # queryset = Exam.objects.all().order_by('name')
|
||||
# serializer_class = ExamSerializer
|
||||
@@ -468,11 +496,13 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
#
|
||||
# return Exam.objects.filter(author__id=user.id)
|
||||
|
||||
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||
|
||||
class ExamAuthorUpdate(
|
||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
|
||||
|
||||
|
||||
class ExamClone(ExamCloneMixin, ExamCreate):
|
||||
"""Clone exam view"""
|
||||
"""Clone exam view"""
|
||||
|
||||
Reference in New Issue
Block a user