Merge branch 'master' of ssh://46.101.13.46:/home/ross/web/rad

This commit is contained in:
Ross
2022-09-06 10:03:57 +01:00
11 changed files with 513 additions and 110 deletions
+1 -1
View File
@@ -1891,7 +1891,7 @@ class GenericViewBase:
"rapids": "rapid_checker", "rapids": "rapid_checker",
"anatomy": "anatomy_checker", "anatomy": "anatomy_checker",
"longs": "longs_checker", "longs": "longs_checker",
"sbas": "sbas_checker", "sbas": "sba_checker",
"physics": "physics_checker", "physics": "physics_checker",
} }
+119 -1
View File
@@ -14,11 +14,14 @@ from .models import (
Question, Question,
CidUserAnswer, CidUserAnswer,
Exam, Exam,
Category
) )
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea from django.forms.widgets import RadioSelect, TextInput, Textarea
from tinymce.widgets import TinyMCE
class CidUserAnswerForm(ModelForm): class CidUserAnswerForm(ModelForm):
class Meta: class Meta:
@@ -36,4 +39,119 @@ class ExamForm(ExamFormMixin, ModelForm):
class ExamAuthorForm(ExamAuthorFormMixin): class ExamAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
model = Exam model = Exam
class QuestionForm(ModelForm):
# exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all())
class Media:
# Django also includes a few javascript files necessary
# for the operation of this form element. You need to
# include <script src="/admin/jsi18n"></script>
# in the template.
css = {
"all": ["css/widgets.css"],
}
# Adding this javascript is crucial
js = ["jsi18n.js", "tesseract.min.js"]
def __init__(self, *args, **kwargs):
self.user = kwargs.pop(
"user"
) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
if kwargs.get("instance"):
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault("initial", {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data.
initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]
ModelForm.__init__(self, *args, **kwargs)
super(QuestionForm, self).__init__(*args, **kwargs)
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
# self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
self.fields["category"] = ModelChoiceField(
required=True,
queryset=Category.objects.all(),
initial=1,
)
if self.user.groups.filter(name="sba_checker").exists():
exam_queryset = Exam.objects.all()
else:
exam_queryset = Exam.objects.filter(
author__id=self.user.id
) | Exam.objects.filter(open_access=True)
self.fields["exams"] = ModelMultipleChoiceField(
required=False,
queryset=exam_queryset,
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
)
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = ModelForm.save(self, False)
instance.save()
old_exams = instance.exams.all()
new_exams = self.cleaned_data["exams"]
for exam in old_exams:
if exam not in new_exams:
exam.exam_questions.remove(instance)
for exam in new_exams:
exam.exam_questions.add(instance)
self.save_m2m()
return instance
class Meta:
model = Question
fields = [
"stem",
"feedback",
"category",
"a_answer",
"a_feedback",
"b_answer",
"b_feedback",
"c_answer",
"c_feedback",
"d_answer",
"d_feedback",
"e_answer",
"e_feedback",
"best_answer",
]
widgets = {
# "normal": RadioSelect(
# choices=[(True, 'Yes'),
# (False, 'No')])
"stem" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
"feedback" : TinyMCE(attrs={'cols': 80, 'rows': 30}),
"a_answer" : TinyMCE(attrs={'cols': 80, 'rows': 5}, mce_attrs={'height': 140}),
"a_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 5}, mce_attrs={'height': 140}),
"b_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"c_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"d_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"e_answer" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"b_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"c_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"d_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
"e_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}),
}
#widgets = {
# "structure": autocomplete.ModelSelect2(url="anatomy:structure-autocomplete")
#}
+13
View File
@@ -124,6 +124,19 @@ class Question(models.Model):
self.e_answer, self.e_answer,
) )
def get_answer_by_choice(self, choice):
if choice == "a":
return self.a_answer
if choice == "b":
return self.b_answer
if choice == "c":
return self.c_answer
if choice == "d":
return self.d_answer
if choice == "e":
return self.e_answer
def get_correct_answer(self): def get_correct_answer(self):
if self.best_answer == "a": if self.best_answer == "a":
return self.a_answer return self.a_answer
+3 -1
View File
@@ -8,7 +8,9 @@
Sbas: Sbas:
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<a href="{% url 'sbas:exam_list' %}">Exams</a> / <a href="{% url 'sbas:exam_list' %}">Exams</a> /
<a href="{% url 'sbas:question_view' %}">Questions</a> <a href="{% url 'sbas:exam_create' %}" title="Create a new exam">Create Exam</a> /
<a href="{% url 'sbas:question_view' %}">Questions</a> /
<a href="{% url 'sbas:question_create' %}" title="Create a new question">Create Question</a>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
/ <a href="{% url 'sbas:user_answer_table_view' %}" title="User answers">Answers</a> / <a href="{% url 'sbas:user_answer_table_view' %}" title="User answers">Answers</a>
{% endif %} {% endif %}
+23 -16
View File
@@ -1,21 +1,28 @@
{% extends 'sbas/base.html' %} {% extends 'sbas/base.html' %}
{% block content %} {% block content %}
<div class="sbas"> <div class="sbas">
<h2>Exam: {{ exam.name }}</h2> <h2>Exam: {{ exam.name }}</h2>
<h3>Candidate: {{ cid }}</h3> <h3>Candidate: {{ cid }}</h3>
Answers: {% if not exam.publish_results %}
<ul> <div class="alert alert-info" role="alert"><h4>Exam results not published</h4>Submitted answers are visible below.</div>
{% for question, a, score, correct_answer in answers_and_marks %} {% endif %}
<li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question Answers:
{{forloop.counter}}</a> - {{ question.stem |safe}}</li> <ul>
<span>Correct answer: {{correct_answer|safe}} <br />{{a}} <span class="answer-{{score}}">(Score: {% for question, a, score, correct_answer, chosen_answer in answers_and_marks %}
{{score}})</span></span> <li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question
{% endfor %} {{forloop.counter}}</a> - {{ question.stem |safe}}</li>
</ul> <span>Correct answer: {{correct_answer|safe}} <br />Chosen answer: {{a}} {{chosen_answer|safe}}
<br /> Total mark: {{ total_score }} / {{max_score}} {% if exam.publish_results or view_all_results %}
<div> <span class="answer-{{score}}">(Score: {{score}})</span>
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a> {% endif %}
</span>
{% endfor %}
</ul>
<br /> Total mark: {{ total_score }} / {{max_score}}
<div>
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
</div>
</div> </div>
</div>
{% endblock %} {% endblock %}
+192 -83
View File
@@ -1,104 +1,213 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
CID: {{cid}} {% if request.user.is_authenticated %}
<div class="no-select"> User: {{request.user}}
<h2>{{exam.name}}: Question [{{pos|add:1}}/{{exam_length}}]</h2> {% else %}
{% if exam.publish_results %} CID: {{cid}}
<div class="alert alert-primary" role="alert">
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')">here</a>.
</div>
{% endif %} {% endif %}
<div id="timeup">
<div> Allocated time over.
<p>{{question.stem|safe}}</p>
</div> </div>
<ul class="sba-answer-list"> <div id="clockdiv">
<li data-ans="a">{{question.a_answer|safe}}</li> Time remaining:
<li data-ans="b">{{question.b_answer|safe}}</li> {% comment %} <div>
<li data-ans="c">{{question.c_answer|safe}}</li> <span class="days"></span>
<li data-ans="d">{{question.d_answer|safe}}</li> <div class="smalltext">Days</div>
<li data-ans="e">{{question.e_answer|safe}}</li> </div> {% endcomment %}
</ul> <div>
<span class="hours num"></span>
<span class="smalltext">Hours</span>
<div> </div>
<div>
<form method="POST" class="post-form">{% csrf_token %} <span class="minutes num"></span>
<div class="form-contents"> <span class="smalltext">Minutes</span>
{{form}} </div>
<div>
<span class="seconds num"></span>
<span class="smalltext">Seconds</span>
</div>
</div>
<div class="no-select">
<h2>{{exam.name}}: Question [{{pos|add:1}}/{{exam_length}}]</h2>
{% if exam.publish_results %}
<div class="alert alert-primary" role="alert">
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')">here</a>.
</div> </div>
{% endif %}
{% if previous > -1 %} <div>
<button type="submit" name="previous" class="save btn btn-default">Previous</button> <p>{{question.stem|safe}}</p>
{% endif %} </div>
{% if next %} <ul class="sba-answer-list">
<button type="submit" name="next" class="save btn btn-default">Next</button> <li data-ans="a">{{question.a_answer|safe}}</li>
{% else %} <li data-ans="b">{{question.b_answer|safe}}</li>
{% if not exam.publish_results %} <li data-ans="c">{{question.c_answer|safe}}</li>
<button type="submit" name="save" class="save btn btn-default">Save</button> <li data-ans="d">{{question.d_answer|safe}}</li>
{% endif %} <li data-ans="e">{{question.e_answer|safe}}</li>
{% endif %} </ul>
{% if exam.publish_results and question.feedback %}
<h3>Feedback</h3> <div>
<p>{{question.feedback|safe}}</p>
{% endif %} <form method="POST" class="post-form">{% csrf_token %}
<br /> <div class="form-contents">
<button type="submit" name="finish" class="save btn btn-default">Overview</button> {{form}}
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button> </div>
</form>
{% if previous > -1 %}
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
{% endif %}
{% if next %}
<button type="submit" name="next" class="save btn btn-default">Next</button>
{% else %}
{% if not exam.publish_results %}
<button type="submit" name="save" class="save btn btn-default">Save</button>
{% endif %}
{% endif %}
{% if exam.publish_results and question.feedback %}
<h3>Feedback</h3>
<p>{{question.feedback|safe}}</p>
{% endif %}
<br />
<button type="submit" name="finish" class="save btn btn-default">Overview</button>
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
</form>
</div>
</div> </div>
</div>
<h4>Questions</h4> <h4>Questions</h4>
<div id="menu-list"> <div id="menu-list">
</div> </div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
/* beautify ignore:start */ /* beautify ignore:start */
{% if not exam.publish_results %} {% if not exam.publish_results %}
$("ul.sba-answer-list li").each((n, el) => {
$(el).click((e) => {
console.log(1, el.dataset.ans);
$("#id_answer").val(el.dataset.ans);
$("ul.sba-answer-list li").removeClass("selected");
$(el).addClass("selected")
})
})
{% else %}
$("ul.sba-answer-list li[data-ans='{{question.best_answer}}']").addClass("correct");
{% endif %}
{% if saved_answer %} let time_limit = '{{exam.time_limit}}'
$("ul.sba-answer-list li[data-ans='{{saved_answer}}']").addClass("selected"); {% endif %} if (time_limit != "None") {
let end_time = new Date({{cid_user_exam.start_time|date:"U"}}*1000+parseInt('{{exam.time_limit}}')*1000);
for (let i = 0; i < {{ exam_length }}; i++) { initializeClock("clockdiv", end_time);
$("#menu-list").append($( }
`<button class="question-menu-item" name="goto-${i}" data-qn="${i}">${i+1}</button>`));
} $("ul.sba-answer-list li").each((n, el) => {
$("button.question-menu-item").on("click", (e) => { $(el).click((e) => {
console.log(e.currentTarget.dataset.qn); $("#id_answer").val(el.dataset.ans);
document.getElementById("goto-button").value = e.currentTarget.dataset.qn; $("ul.sba-answer-list li").removeClass("selected");
$("#goto-button").click(); $(el).addClass("selected")
}); })
/* beautify ignore:end */ })
}) {% else %}
</script> $("ul.sba-answer-list li[data-ans='{{question.best_answer}}']").addClass("correct");
{% endif %}
{% if saved_answer %}
$("ul.sba-answer-list li[data-ans='{{saved_answer}}']").addClass("selected"); {% endif %}
for (let i = 0; i < {{ exam_length }}; i++) {
$("#menu-list").append($(
`<button class="question-menu-item" name="goto-${i}" data-qn="${i}">${i+1}</button>`));
}
$("button.question-menu-item").on("click", (e) => {
console.log(e.currentTarget.dataset.qn);
document.getElementById("goto-button").value = e.currentTarget.dataset.qn;
$("#goto-button").click();
});
function getTimeRemaining(endtime) {
const total = Date.parse(endtime) - Date.parse(new Date());
const seconds = Math.floor((total / 1000) % 60);
const minutes = Math.floor((total / 1000 / 60) % 60);
const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
//const days = Math.floor(total / (1000 * 60 * 60 * 24));
return {
total,
//days,
hours,
minutes,
seconds
};
}
function initializeClock(id, endtime) {
console.log("Start clock", endtime)
const clock = document.getElementById(id);
clock.style.display = "block"
const daysSpan = clock.querySelector('.days');
const hoursSpan = clock.querySelector('.hours');
const minutesSpan = clock.querySelector('.minutes');
const secondsSpan = clock.querySelector('.seconds');
function updateClock() {
const t = getTimeRemaining(endtime);
//daysSpan.innerHTML = t.days;
hoursSpan.innerHTML = ('0' + t.hours).slice(-2);
minutesSpan.innerHTML = ('0' + t.minutes).slice(-2);
secondsSpan.innerHTML = ('0' + t.seconds).slice(-2);
if (t.total <= 0) {
clearInterval(timeinterval);
timeup.style.display = "block"
clock.style.display = "none"
}
}
const timeinterval = setInterval(updateClock, 1000);
updateClock();
}
/* beautify ignore:end */
})
</script>
{% endblock %} {% endblock %}
{% block css %} {% block css %}
<style type="text/css"> <style type="text/css">
.form-contents { .form-contents {
display: none; display: none;
} }
.selected { .selected {
border: 1px solid purple; border: 1px solid purple;
} }
</style>
#clockdiv{
font-family: sans-serif;
display: inline-block;
font-weight: 100;
text-align: center;
font-size: 12px;
display: none;
color: darkgray;
}
#clockdiv > div{
padding: 1px;
border-radius: 3px;
{% comment %} background: purple; {% endcomment %}
display: inline-block;
}
#clockdiv div > span.num{
color: white;
padding: 1px;
border-radius: 3px;
display: inline-block;
}
#timeup {
display: none;
color: red;
font-weight: 100;
text-align: center;
font-size: 12px;
}
</style>
{% endblock %} {% endblock %}
+3
View File
@@ -2,6 +2,9 @@
{% block content %} {% block content %}
<div class="question"> <div class="question">
<a href="{% url 'sbas:question_update' question.id %}" title="Edit the Question">Edit</a>
<a href="{% url 'sbas:question_clone' question.id %}" title="Clone the Question">Clone</a>
<a href="{% url 'sbas:question_delete' pk=question.pk %}" title="Delete the Question">Delete</a>
<a href="{% url 'admin:sbas_question_change' question.id %}" <a href="{% url 'admin:sbas_question_change' question.id %}"
title="Edit the Question using the admin interface">Admin Edit</a> title="Edit the Question using the admin interface">Admin Edit</a>
<a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')"> Add Note</a> <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='sbas' pk=question.pk %}')"> Add Note</a>
+24
View File
@@ -0,0 +1,24 @@
{% extends "sbas/base.html" %}
{% load static %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
<script type="text/javascript">
</script>
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Submit Question</h2>
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" class="submit-button" value="Submit" name="submit">
<input type="submit" class="submit-button" value="Submit and Clone" name="submit-clone">
</form>
{% endblock %}
+29 -3
View File
@@ -10,7 +10,20 @@ app_name = "sbas"
urlpatterns = [ urlpatterns = [
path("question/", views.QuestionView.as_view(), name="question_view"), path("question/", views.QuestionView.as_view(), name="question_view"),
path("question/<int:pk>/", views.question_detail, name="question_detail"), path("question/<int:pk>/", views.question_detail, name="question_detail"),
# path("question/create/", views.QuestionCreate.as_view(), name="anatomy_question_create"), path("question/create/", views.QuestionCreate.as_view(), name="question_create"),
path(
"question/<int:pk>/update",
views.QuestionUpdate.as_view(),
name="question_update",
),
path(
"question/<int:pk>/clone", views.QuestionClone.as_view(), name="question_clone"
),
path(
"question/<int:pk>/delete",
views.QuestionDelete.as_view(),
name="question_delete",
),
# path("question/<int:pk>/update", views.QuestionUpdate.as_view(), name="anatomy_question_update"), # 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>/<int:sk>/mark", views.mark, name="mark"),
# path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"), # path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
@@ -25,9 +38,12 @@ urlpatterns = [
views.exam_take_overview, views.exam_take_overview,
name="exam_take_overview", name="exam_take_overview",
), ),
path("exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"), path(
"exam/<int:pk>/authors", views.ExamAuthorUpdate.as_view(), name="exam_authors"
),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"), path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/available", views.active_exams, name="active_exams"), path("exam/available", views.active_exams, name="active_exams"),
path("exam/create", views.ExamUpdate.as_view(), name="exam_create"),
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"), path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"), path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"), # path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
@@ -45,7 +61,17 @@ urlpatterns = [
views.UserAnswerDelete.as_view(), views.UserAnswerDelete.as_view(),
name="user_answer_delete", name="user_answer_delete",
), ),
path(
"exam/<int:pk>/scores/<int:cid>/<str:passcode>/",
views.exam_scores_cid_user,
name="exam_scores_cid_user",
),
path(
"exam/<int:pk>/scores/",
views.exam_scores_cid_user,
name="exam_scores_user",
),
] ]
urlpatterns.extend(generic_view_urls(views.GenericViews)) urlpatterns.extend(generic_view_urls(views.GenericViews))
urlpatterns.extend(generic_exam_urls(views.GenericExamViews)) urlpatterns.extend(generic_exam_urls(views.GenericExamViews))
+105 -4
View File
@@ -7,6 +7,7 @@ from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django import forms from django import forms
from django.utils import timezone from django.utils import timezone
from django.forms.models import model_to_dict
# from django.contrib.auth.models import User # from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
@@ -40,7 +41,7 @@ import json
import statistics import statistics
import plotly.express as px import plotly.express as px
from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamCreateBase, ExamDeleteBase, ExamUpdateBase, ExamViews, GenericViewBase, exam_inactive
from rest_framework import viewsets, permissions from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
@@ -50,6 +51,11 @@ from django.core.exceptions import PermissionDenied
from .tables import QuestionTable, UserAnswerTable from .tables import QuestionTable, UserAnswerTable
from .filters import QuestionFilter, UserAnswerFilter from .filters import QuestionFilter, UserAnswerFilter
from .forms import (
QuestionForm,
ExamForm,
)
class AuthorOrCheckerRequiredMixin(object): class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
@@ -120,7 +126,8 @@ def exam_scores_cid_user(request, pk, cid, passcode):
answers_marks.append(answer_score) answers_marks.append(answer_score)
merged_ans = (ans, answer_score, correct_answer) merged_ans = (ans, answer_score, correct_answer)
answers_and_marks.append((q, *merged_ans)) chosen_answer = q.get_answer_by_choice(ans)
answers_and_marks.append((q, *merged_ans, chosen_answer))
total_score = sum(answers_marks) total_score = sum(answers_marks)
@@ -148,6 +155,7 @@ def exam_start(request, pk):
exam = get_object_or_404(Exam, pk=pk) exam = get_object_or_404(Exam, pk=pk)
if not exam.active: if not exam.active:
return exam_inactive(request, context={"exam": exam})
raise Http404("Exam not found") raise Http404("Exam not found")
return render( return render(
@@ -163,7 +171,7 @@ def exam_take_overview(request, pk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk) exam = get_object_or_404(Exam, pk=pk)
if not exam.active: if not exam.active:
raise Http404("Exam not found") return exam_inactive(request, context={"exam": exam})
if not exam.check_cid_user(cid, passcode): if not exam.check_cid_user(cid, passcode):
raise Http404("Error accessing exam") raise Http404("Error accessing exam")
@@ -299,9 +307,102 @@ class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["app_name"] = "anatomy" context["app_name"] = "sbas"
return context return context
class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
model = Question
form_class = QuestionForm
def get_form_kwargs(self):
kwargs = super(QuestionCreateBase, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(QuestionCreateBase, self).get_context_data(**kwargs)
#if self.request.POST:
# context["answer_formset"] = AnswerFormSet(
# self.request.POST, self.request.FILES
# )
#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:
return response
# else we redirect to the clone url
else:
return redirect("sbas:question_clone", pk=self.object.pk)
class QuestionCreate(QuestionCreateBase):
# initial = {'laterality': AnatomyQuestion.NONE}
def get_initial(self):
if "pk" in self.kwargs:
initial = super().get_initial()
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
initial["exams"] = [exam.id]
return initial
class QuestionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
model = Question
form_class = QuestionForm
def get_form_kwargs(self):
kwargs = super(QuestionUpdate, self).get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
def get_context_data(self, **kwargs):
context = super(QuestionUpdate, self).get_context_data(**kwargs)
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[exam] = list(exam.exam_questions.all())
# print(exam_orders[exam])
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
response = super().form_valid(form)
return response
class QuestionClone(QuestionCreateBase):
"""Clones a existing question"""
def get_initial(self):
# print(self.request)
old_object = get_object_or_404(Question, pk=self.kwargs["pk"])
initial_data = model_to_dict(old_object, exclude=["id"])
# We don't want to clone the exam details
# exams = old_object.exams.all().values_list("id", flat=True)
# initial_data["exams"] = list(exams)
return initial_data
class UserAnswerView(LoginRequiredMixin, DetailView): class UserAnswerView(LoginRequiredMixin, DetailView):
model = CidUserAnswer model = CidUserAnswer
+1 -1
View File
@@ -5,6 +5,6 @@
<h2>404 error</h2> <h2>404 error</h2>
{{ reason }} {{ reason }}
<h3>a {{resolved}}</h3> <h3>{{resolved}}</h3>
</div> </div>
{% endblock %} {% endblock %}