add sbas
This commit is contained in:
@@ -35,6 +35,7 @@ INSTALLED_APPS = [
|
||||
'physics',
|
||||
'rapids',
|
||||
'longs',
|
||||
'sbas',
|
||||
'wally',
|
||||
"reversion",
|
||||
"django_tables2",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SbaConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'sbas'
|
||||
@@ -0,0 +1,77 @@
|
||||
# Generated by Django 3.2.5 on 2021-07-28 18:07
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import sortedm2m.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('category', models.CharField(max_length=200)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stem', models.TextField(help_text='Stem of the question')),
|
||||
('a_answer', models.TextField(default=True, help_text='Answer text')),
|
||||
('a_feedback', models.TextField(blank=True, help_text='Feedback for answer')),
|
||||
('b_answer', models.TextField(default=True, help_text='Answer text')),
|
||||
('b_feedback', models.TextField(blank=True, help_text='Feedback for answer')),
|
||||
('c_answer', models.TextField(default=True, help_text='Answer text')),
|
||||
('c_feedback', models.TextField(blank=True, help_text='Feedback for answer')),
|
||||
('d_answer', models.TextField(default=True, help_text='Answer text')),
|
||||
('d_feedback', models.TextField(blank=True, help_text='Feedback for answer')),
|
||||
('e_answer', models.TextField(default=True, help_text='Answer text')),
|
||||
('e_feedback', models.TextField(blank=True, help_text='Feedback for answer')),
|
||||
('feedback', models.TextField(blank=True, help_text='General question feedback')),
|
||||
('best_answer', models.CharField(choices=[('a', 'answer_a'), ('b', 'answer_b'), ('c', 'answer_c'), ('d', 'answer_d'), ('e', 'answer_e')], help_text='The correct answer', max_length=20)),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('author', models.ManyToManyField(blank=True, help_text='Author(s) of question', related_name='sbas_authored_questions', to=settings.AUTH_USER_MODEL)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sbas.category')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Exam',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of the exam', max_length=200)),
|
||||
('active', models.BooleanField(default=False, help_text='If an exam should be available')),
|
||||
('exam_mode', models.BooleanField(default=False, help_text='If an exam should be taken in exam mode (users results will be submited to the server for marking)')),
|
||||
('publish_results', models.BooleanField(default=False, help_text='If an exams results should be available')),
|
||||
('json_creation_time', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('time_limit', models.IntegerField(blank=True, help_text='Exam time limit (in seconds)', null=True)),
|
||||
('author', models.ManyToManyField(blank=True, help_text='Author of exam', related_name='sba_exam_author', to=settings.AUTH_USER_MODEL)),
|
||||
('exam_questions', sortedm2m.fields.SortedManyToManyField(blank='true', help_text=None, related_name='exams', to='sbas.Question')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CidUserAnswer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('answer', models.CharField(choices=[('a', 'answer_a'), ('b', 'answer_b'), ('c', 'answer_c'), ('d', 'answer_d'), ('e', 'answer_e')], help_text='The correct answer', max_length=20)),
|
||||
('cid', models.IntegerField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('exam', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='sbas.exam')),
|
||||
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cid_user_answers', to='sbas.question')),
|
||||
],
|
||||
),
|
||||
]
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.conf import settings
|
||||
from django.utils.html import format_html
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from generic.models import ExamBase
|
||||
|
||||
import reversion
|
||||
|
||||
class Category(models.Model):
|
||||
category = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.category
|
||||
|
||||
class Question(models.Model):
|
||||
stem = models.TextField(
|
||||
blank=False,
|
||||
help_text="Stem of the question",
|
||||
)
|
||||
|
||||
answer_help_text = "Answer text"
|
||||
|
||||
BEST_ANSWER_CHOICES = (
|
||||
('a', 'answer_a'),
|
||||
('b', 'answer_b'),
|
||||
('c', 'answer_c'),
|
||||
('d', 'answer_d'),
|
||||
('e', 'answer_e'),
|
||||
)
|
||||
|
||||
# I doubt this is the best way to structure the model
|
||||
# (but it should work)
|
||||
a_answer = models.TextField(
|
||||
help_text=answer_help_text, default=True
|
||||
)
|
||||
a_feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
b_answer = models.TextField(
|
||||
help_text=answer_help_text, default=True
|
||||
)
|
||||
b_feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
c_answer = models.TextField(
|
||||
help_text=answer_help_text, default=True
|
||||
)
|
||||
c_feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
d_answer = models.TextField(
|
||||
help_text=answer_help_text, default=True
|
||||
)
|
||||
d_feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
e_answer = models.TextField(
|
||||
help_text=answer_help_text, default=True
|
||||
)
|
||||
e_feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
feedback = models.TextField(
|
||||
blank=True,
|
||||
help_text="General question feedback",
|
||||
)
|
||||
|
||||
best_answer = models.CharField(max_length=20, choices=BEST_ANSWER_CHOICES, help_text="The correct answer")
|
||||
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Author(s) of question",
|
||||
related_name="sbas_authored_questions",
|
||||
)
|
||||
|
||||
category = models.ForeignKey(
|
||||
Category, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.stem
|
||||
|
||||
def clean(self):
|
||||
if self.a_answer:
|
||||
self.a_answer = self.a_answer.strip()
|
||||
if self.b_answer:
|
||||
self.b_answer = self.b_answer.strip()
|
||||
if self.c_answer:
|
||||
self.c_answer = self.c_answer.strip()
|
||||
if self.d_answer:
|
||||
self.d_answer = self.d_answer.strip()
|
||||
if self.e_answer:
|
||||
self.e_answer = self.e_answer.strip()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("sbas:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_answers(self):
|
||||
return (self.a_answer, self.b_answer, self.c_answer, self.d_answer, self.e_answer)
|
||||
|
||||
def get_correct_answer(self):
|
||||
if self.best_answer == "a": return self.a_answer
|
||||
if self.best_answer == "b": return self.b_answer
|
||||
if self.best_answer == "c": return self.c_answer
|
||||
if self.best_answer == "d": return self.d_answer
|
||||
if self.best_answer == "e": return self.e_answer
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Exam(ExamBase):
|
||||
app_name = "sbas"
|
||||
|
||||
exam_questions = SortedManyToManyField(
|
||||
Question, related_name="exams", blank="true"
|
||||
)
|
||||
|
||||
time_limit = models.IntegerField(
|
||||
help_text="Exam time limit (in seconds)",
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
recreate_json = None
|
||||
|
||||
author = models.ManyToManyField(settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text='Author of exam',
|
||||
related_name="sba_exam_author")
|
||||
|
||||
|
||||
|
||||
class CidUserAnswer(models.Model):
|
||||
"""User answers by candidate"""
|
||||
|
||||
question = models.ForeignKey(
|
||||
Question, related_name="cid_user_answers", on_delete=models.CASCADE
|
||||
)
|
||||
answer = models.CharField(max_length=20, choices=Question.BEST_ANSWER_CHOICES, help_text="The correct answer")
|
||||
|
||||
cid = models.IntegerField(blank=True, null=True)
|
||||
|
||||
# Each user answer is associated with a particular exam
|
||||
exam = models.ForeignKey(
|
||||
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
|
||||
)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{}/{}/{}".format(self.cid, self.exam, self.question.pk)
|
||||
|
||||
def get_answer(self):
|
||||
return self.answer
|
||||
|
||||
def get_correct(self):
|
||||
return self.answer == self.question.best_answer
|
||||
|
||||
def get_score(self):
|
||||
if self.get_correct:
|
||||
return 1
|
||||
return 0
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Question [{{pos}}/{{exam_length}}]</h2>
|
||||
|
||||
<div>
|
||||
|
||||
{% if previous > -1 %}
|
||||
<a href="{% url 'sbas:exam_question_detail' exam.id previous cid %}">Previous question</a>
|
||||
{% endif %}
|
||||
{% if next %}
|
||||
<a href="{% url 'sbas:exam_question_detail' exam.id next cid %}">Next question</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
{% extends 'rapids/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% if exam %}
|
||||
<div>
|
||||
|
||||
{% if previous > -1 %}
|
||||
<a href="{% url 'rapids:exam_question_detail' exam.id previous %}">Previous question</a>
|
||||
{% endif %}
|
||||
This question is part of exam: <a href="{% url 'rapids:exam_overview' exam.id %}">{{exam.name}}</a> [{{pos}}/{{exam_length}}]
|
||||
{% if next %}
|
||||
<a href="{% url 'rapids:exam_question_detail' exam.id next %}">Next question</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'rapids:rapid_update' pk=question.pk %}" title="Edit the Rapid">Edit</a>
|
||||
<a href="{% url 'rapids:rapid_clone' pk=question.pk %}" title="Clone the Rapid (duplicate everything but the images)">Clone</a>
|
||||
<a href="{% url 'rapids:question_delete' pk=question.pk %}" title="Delete the Rapid">Delete</a>
|
||||
<a href="{% url 'rapids:rapid_add_note' pk=question.pk %}"> Add Note</a>
|
||||
{% if request.user.is_superuser %}
|
||||
<a href="{% url 'admin:rapids_rapid_change' question.id %}" title="Edit the Rapid using the admin interface">Admin Edit</a>
|
||||
{% endif %}
|
||||
{% include 'rapids/question_display_block.html' %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,30 @@
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
|
||||
app_name = "sbas"
|
||||
|
||||
urlpatterns = [
|
||||
# path('', views.question_list, name='question_list'),
|
||||
path("", views.SbasExamViews.index, name="index"),
|
||||
#path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||
#path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"),
|
||||
#path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"),
|
||||
#path("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
||||
#path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
|
||||
path("exam/<int:pk>/<int:sk>/<int:cid>/take", views.exam_take, name="exam_take"),
|
||||
path("exam/<int:pk>/start", views.exam_start, name="exam_start"),
|
||||
path("exam/<int:pk>/", views.SbasExamViews.exam_overview, name="exam_overview"),
|
||||
path("exam/<int:pk>/scores", views.exam_scores_cid,
|
||||
name="exam_scores_cid"),
|
||||
path("exam/<int:pk>/scores/<int:sk>/", views.exam_scores_cid_user,
|
||||
name="exam_scores_cid_user"),
|
||||
path("exam/<int:pk>/toggle_active", views.SbasExamViews.exam_toggle_active, name="exam_toggle_active"),
|
||||
path("exam/<int:pk>/toggle_results_published", views.SbasExamViews.exam_toggle_results_published, name="exam_toggle_results_published"),
|
||||
#path("exam/submit", views.PhysicsExamViews.postExamAnswers, name="exam_answers_submit"),
|
||||
path("exam/", views.SbasExamViews.exam_list, name="exam_list"),
|
||||
path("exam/available", views.active_exams, name="active_exams"),
|
||||
#path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
|
||||
#path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
|
||||
]
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
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
|
||||
|
||||
from generic.views import ExamViews
|
||||
|
||||
from rest_framework import viewsets, permissions
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
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
|
||||
def question_detail(request, pk):
|
||||
question = get_object_or_404(Question, pk=pk)
|
||||
return render(request, "physics/question_detail.html", {"question": question})
|
||||
|
||||
|
||||
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_answer()
|
||||
|
||||
answer_score = user_answer.get_score()
|
||||
|
||||
correct_answer = q.get_correct_answer()
|
||||
|
||||
if not exam.publish_results:
|
||||
correct_answer = "*****"
|
||||
answer_score = 0
|
||||
answers.append(ans)
|
||||
answers_marks.append(answer_score)
|
||||
|
||||
merged_ans = (ans, answer_score, correct_answer)
|
||||
answers_and_marks.append((q, merged_ans))
|
||||
|
||||
total_score = sum(sum(i) for i in answers_marks)
|
||||
|
||||
max_score = len(questions)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"sbas/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_start(request, pk):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
raise Http404("Exam not found")
|
||||
|
||||
return render(
|
||||
request,
|
||||
"sbas/exam_start.html",
|
||||
{
|
||||
"exam": exam,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def exam_take(request, pk, sk, cid):
|
||||
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
raise Http404("Exam not found")
|
||||
|
||||
question = exam.exam_questions.all()[sk]
|
||||
|
||||
exam_length = len(exam.exam_questions.all())
|
||||
|
||||
pos = exam.get_question_index(question) + 1
|
||||
|
||||
previous = -1
|
||||
if sk > 0:
|
||||
previous = sk - 1
|
||||
next = sk + 1
|
||||
if sk == exam_length - 1:
|
||||
next = False
|
||||
|
||||
|
||||
return render(
|
||||
request,
|
||||
"sbas/exam_take.html",
|
||||
{
|
||||
"cid": cid,
|
||||
"exam": exam,
|
||||
"questions": question,
|
||||
"next": next,
|
||||
"previous": previous,
|
||||
"exam_length": exam_length,
|
||||
"pos": pos,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def loadJsonAnswer(answer):
|
||||
pass
|
||||
|
||||
SbasExamViews = ExamViews(Exam, Question, "sbas", "sbas", loadJsonAnswer)
|
||||
Reference in New Issue
Block a user