Merge branch 'master' of ssh://46.101.13.46:/home/ross/web/rad
This commit is contained in:
+1
-1
@@ -1891,7 +1891,7 @@ class GenericViewBase:
|
||||
"rapids": "rapid_checker",
|
||||
"anatomy": "anatomy_checker",
|
||||
"longs": "longs_checker",
|
||||
"sbas": "sbas_checker",
|
||||
"sbas": "sba_checker",
|
||||
"physics": "physics_checker",
|
||||
}
|
||||
|
||||
|
||||
+119
-1
@@ -14,11 +14,14 @@ from .models import (
|
||||
Question,
|
||||
CidUserAnswer,
|
||||
Exam,
|
||||
Category
|
||||
)
|
||||
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||
|
||||
from tinymce.widgets import TinyMCE
|
||||
|
||||
|
||||
class CidUserAnswerForm(ModelForm):
|
||||
class Meta:
|
||||
@@ -36,4 +39,119 @@ class ExamForm(ExamFormMixin, ModelForm):
|
||||
|
||||
class ExamAuthorForm(ExamAuthorFormMixin):
|
||||
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")
|
||||
#}
|
||||
|
||||
@@ -124,6 +124,19 @@ class Question(models.Model):
|
||||
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):
|
||||
if self.best_answer == "a":
|
||||
return self.a_answer
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
Sbas:
|
||||
{% if request.user.is_authenticated %}
|
||||
<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 %}
|
||||
/ <a href="{% url 'sbas:user_answer_table_view' %}" title="User answers">Answers</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
{% extends 'sbas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sbas">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
Answers:
|
||||
<ul>
|
||||
{% for question, a, score, correct_answer in answers_and_marks %}
|
||||
<li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question
|
||||
{{forloop.counter}}</a> - {{ question.stem |safe}}</li>
|
||||
<span>Correct answer: {{correct_answer|safe}} <br />{{a}} <span class="answer-{{score}}">(Score:
|
||||
{{score}})</span></span>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||
<div>
|
||||
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
|
||||
<div class="sbas">
|
||||
<h2>Exam: {{ exam.name }}</h2>
|
||||
<h3>Candidate: {{ cid }}</h3>
|
||||
{% if not exam.publish_results %}
|
||||
<div class="alert alert-info" role="alert"><h4>Exam results not published</h4>Submitted answers are visible below.</div>
|
||||
{% endif %}
|
||||
Answers:
|
||||
<ul>
|
||||
{% for question, a, score, correct_answer, chosen_answer in answers_and_marks %}
|
||||
<li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question
|
||||
{{forloop.counter}}</a> - {{ question.stem |safe}}</li>
|
||||
<span>Correct answer: {{correct_answer|safe}} <br />Chosen answer: {{a}} {{chosen_answer|safe}}
|
||||
{% if exam.publish_results or view_all_results %}
|
||||
<span class="answer-{{score}}">(Score: {{score}})</span>
|
||||
{% 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>
|
||||
{% endblock %}
|
||||
@@ -1,104 +1,213 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
CID: {{cid}}
|
||||
<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>
|
||||
{% if request.user.is_authenticated %}
|
||||
User: {{request.user}}
|
||||
{% else %}
|
||||
CID: {{cid}}
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<p>{{question.stem|safe}}</p>
|
||||
<div id="timeup">
|
||||
Allocated time over.
|
||||
</div>
|
||||
<ul class="sba-answer-list">
|
||||
<li data-ans="a">{{question.a_answer|safe}}</li>
|
||||
<li data-ans="b">{{question.b_answer|safe}}</li>
|
||||
<li data-ans="c">{{question.c_answer|safe}}</li>
|
||||
<li data-ans="d">{{question.d_answer|safe}}</li>
|
||||
<li data-ans="e">{{question.e_answer|safe}}</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<form method="POST" class="post-form">{% csrf_token %}
|
||||
<div class="form-contents">
|
||||
{{form}}
|
||||
<div id="clockdiv">
|
||||
Time remaining:
|
||||
{% comment %} <div>
|
||||
<span class="days"></span>
|
||||
<div class="smalltext">Days</div>
|
||||
</div> {% endcomment %}
|
||||
<div>
|
||||
<span class="hours num"></span>
|
||||
<span class="smalltext">Hours</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="minutes num"></span>
|
||||
<span class="smalltext">Minutes</span>
|
||||
</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>
|
||||
{% endif %}
|
||||
|
||||
{% 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 %}
|
||||
<div>
|
||||
<p>{{question.stem|safe}}</p>
|
||||
</div>
|
||||
<ul class="sba-answer-list">
|
||||
<li data-ans="a">{{question.a_answer|safe}}</li>
|
||||
<li data-ans="b">{{question.b_answer|safe}}</li>
|
||||
<li data-ans="c">{{question.c_answer|safe}}</li>
|
||||
<li data-ans="d">{{question.d_answer|safe}}</li>
|
||||
<li data-ans="e">{{question.e_answer|safe}}</li>
|
||||
</ul>
|
||||
|
||||
{% 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>
|
||||
|
||||
<form method="POST" class="post-form">{% csrf_token %}
|
||||
<div class="form-contents">
|
||||
{{form}}
|
||||
</div>
|
||||
|
||||
{% 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>
|
||||
|
||||
<h4>Questions</h4>
|
||||
<div id="menu-list">
|
||||
<h4>Questions</h4>
|
||||
<div id="menu-list">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
/* beautify ignore:start */
|
||||
{% 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 %}
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
/* beautify ignore:start */
|
||||
{% if not exam.publish_results %}
|
||||
|
||||
{% if saved_answer %}
|
||||
$("ul.sba-answer-list li[data-ans='{{saved_answer}}']").addClass("selected"); {% endif %}
|
||||
let time_limit = '{{exam.time_limit}}'
|
||||
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++) {
|
||||
$("#menu-list").append($(
|
||||
`<button class="question-menu-item" name="goto-${i}" data-qn="${i}">${i+1}</button>`));
|
||||
initializeClock("clockdiv", end_time);
|
||||
}
|
||||
|
||||
}
|
||||
$("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();
|
||||
});
|
||||
/* beautify ignore:end */
|
||||
})
|
||||
</script>
|
||||
$("ul.sba-answer-list li").each((n, el) => {
|
||||
$(el).click((e) => {
|
||||
$("#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 %}
|
||||
$("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 %}
|
||||
{% block css %}
|
||||
<style type="text/css">
|
||||
.form-contents {
|
||||
display: none;
|
||||
}
|
||||
<style type="text/css">
|
||||
.form-contents {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selected {
|
||||
border: 1px solid purple;
|
||||
}
|
||||
</style>
|
||||
.selected {
|
||||
border: 1px solid purple;
|
||||
}
|
||||
|
||||
#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 %}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
{% block content %}
|
||||
<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 %}"
|
||||
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>
|
||||
|
||||
@@ -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
@@ -10,7 +10,20 @@ app_name = "sbas"
|
||||
urlpatterns = [
|
||||
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/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("exam/<int:pk>/<int:sk>/mark", views.mark, name="mark"),
|
||||
# path("exam/<int:pk>/mark", views.mark_overview, name="mark_overview"),
|
||||
@@ -25,9 +38,12 @@ urlpatterns = [
|
||||
views.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/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>/delete", views.ExamDelete.as_view(), name="exam_delete"),
|
||||
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
|
||||
@@ -45,7 +61,17 @@ urlpatterns = [
|
||||
views.UserAnswerDelete.as_view(),
|
||||
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_exam_urls(views.GenericExamViews))
|
||||
urlpatterns.extend(generic_exam_urls(views.GenericExamViews))
|
||||
|
||||
+105
-4
@@ -7,6 +7,7 @@ from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
# from django.contrib.auth.models import User
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
@@ -40,7 +41,7 @@ import json
|
||||
import statistics
|
||||
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.pagination import PageNumberPagination
|
||||
@@ -50,6 +51,11 @@ from django.core.exceptions import PermissionDenied
|
||||
from .tables import QuestionTable, UserAnswerTable
|
||||
from .filters import QuestionFilter, UserAnswerFilter
|
||||
|
||||
from .forms import (
|
||||
QuestionForm,
|
||||
ExamForm,
|
||||
)
|
||||
|
||||
|
||||
class AuthorOrCheckerRequiredMixin(object):
|
||||
def get_object(self, *args, **kwargs):
|
||||
@@ -120,7 +126,8 @@ def exam_scores_cid_user(request, pk, cid, passcode):
|
||||
answers_marks.append(answer_score)
|
||||
|
||||
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)
|
||||
|
||||
@@ -148,6 +155,7 @@ def exam_start(request, pk):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
raise Http404("Exam not found")
|
||||
|
||||
return render(
|
||||
@@ -163,7 +171,7 @@ def exam_take_overview(request, pk, cid, passcode):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
if not exam.active:
|
||||
raise Http404("Exam not found")
|
||||
return exam_inactive(request, context={"exam": exam})
|
||||
|
||||
if not exam.check_cid_user(cid, passcode):
|
||||
raise Http404("Error accessing exam")
|
||||
@@ -299,9 +307,102 @@ class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["app_name"] = "anatomy"
|
||||
context["app_name"] = "sbas"
|
||||
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):
|
||||
model = CidUserAnswer
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
<h2>404 error</h2>
|
||||
{{ reason }}
|
||||
|
||||
<h3>a {{resolved}}</h3>
|
||||
<h3>{{resolved}}</h3>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user