start taking more advantage of class based views
This commit is contained in:
@@ -127,6 +127,9 @@ class AnatomyQuestion(QuestionBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
permissions = ()
|
permissions = ()
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "anatomy"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# Get first answer
|
# Get first answer
|
||||||
return "{}/{}: {} [{}, {}]".format(
|
return "{}/{}: {} [{}, {}]".format(
|
||||||
@@ -410,6 +413,9 @@ class Exam(ExamBase):
|
|||||||
|
|
||||||
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="anatomy_exams")
|
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="anatomy_exams")
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "anatomy"
|
||||||
|
|
||||||
def get_exam_json(self, based=True):
|
def get_exam_json(self, based=True):
|
||||||
questions = self.get_questions()
|
questions = self.get_questions()
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
{% extends 'anatomy/exams.html' %}
|
{% extends 'anatomy/exams.html' %}
|
||||||
|
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'generic/exam_overview_headers.html' %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="anatomy">
|
<div class="anatomy">
|
||||||
{% include 'generic/exam_overview_headers.html' %}
|
|
||||||
|
|
||||||
<ol id="full-question-list" class="sortable">
|
<ol id="full-question-list" class="sortable">
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -71,7 +71,7 @@ from helpers.images import image_as_base64
|
|||||||
|
|
||||||
from django.template.defaulttags import register
|
from django.template.defaulttags import register
|
||||||
|
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||||
|
|
||||||
from rest_framework import viewsets, permissions
|
from rest_framework import viewsets, permissions
|
||||||
|
|
||||||
@@ -805,10 +805,16 @@ class ExamCreate(ExamCreateBase):
|
|||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
#def get_context_data(self, **kwargs):
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# context["can_edit"] = self.object.check_user_can_edit(self.request.user)
|
||||||
|
|
||||||
|
# return context
|
||||||
|
|
||||||
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||||
model = Exam
|
model = Exam
|
||||||
@@ -831,10 +837,12 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
|||||||
# return Exam.objects.filter(author__id=user.id)
|
# return Exam.objects.filter(author__id=user.id)
|
||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
|
|
||||||
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
|
GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
|
||||||
|
|||||||
+15
-2
@@ -5,7 +5,7 @@ import pathlib
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from django.http import Http404, HttpRequest
|
from django.http import Http404, HttpRequest
|
||||||
from generic.mixins import AuthorMixin
|
from generic.mixins import AuthorMixin, QuestionMixin
|
||||||
from rad.settings import REMOTE_URL
|
from rad.settings import REMOTE_URL
|
||||||
from django.db.models.fields.files import ImageField
|
from django.db.models.fields.files import ImageField
|
||||||
from django.db.models.fields.related import ForeignKey
|
from django.db.models.fields.related import ForeignKey
|
||||||
@@ -273,7 +273,7 @@ class Structure(SynMixin, models.Model):
|
|||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class Case(models.Model, AuthorMixin):
|
class Case(models.Model, AuthorMixin, QuestionMixin):
|
||||||
# class SubspecialtyChoices(models.TextChoices):
|
# class SubspecialtyChoices(models.TextChoices):
|
||||||
# BREAST = "BR", _("Breast")
|
# BREAST = "BR", _("Breast")
|
||||||
# CARDIAC = "CA", _("Cardiac")
|
# CARDIAC = "CA", _("Cardiac")
|
||||||
@@ -364,6 +364,9 @@ class Case(models.Model, AuthorMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "atlas"
|
||||||
|
|
||||||
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@@ -373,6 +376,10 @@ class Case(models.Model, AuthorMixin):
|
|||||||
# return f"{self.pk}: {self.title}"
|
# return f"{self.pk}: {self.title}"
|
||||||
return format_html("<a href='{}'>{}</a>", self.get_absolute_url(), str(self))
|
return format_html("<a href='{}'>{}</a>", self.get_absolute_url(), str(self))
|
||||||
|
|
||||||
|
#def get_base_template(self):
|
||||||
|
|
||||||
|
#def get_edit_template_links(self):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.pk}: {self.title}"
|
return f"{self.pk}: {self.title}"
|
||||||
|
|
||||||
@@ -773,6 +780,12 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
|||||||
default=VIEWER_MODE_CHOICES.BUILT_IN,
|
default=VIEWER_MODE_CHOICES.BUILT_IN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "atlas"
|
||||||
|
|
||||||
|
def get_link_headers(self):
|
||||||
|
return f"{self.get_app_name()}/collection_headers.html"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("atlas:collection_detail", kwargs={"pk": self.pk})
|
return reverse("atlas:collection_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
|||||||
@@ -3,33 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
{% include 'atlas/question_link_header.html' %}
|
||||||
<div class="floating-header">
|
|
||||||
<a href="{% url 'atlas:case_update' pk=case.pk %}" title="Edit the Case">Edit</a>
|
|
||||||
<a href="{% url 'atlas:case_clone' pk=case.pk %}"
|
|
||||||
title="Clone the Case (duplicate everything but the images)">Clone</a>
|
|
||||||
<a href="{% url 'atlas:case_delete' pk=case.pk %}" title="Delete the Case">Delete</a>
|
|
||||||
<a href="#"
|
|
||||||
onclick="return window.create_popup_window('{% url 'feedback_create' question_type='atlas' pk=case.pk %}')"> Add
|
|
||||||
Note</a>
|
|
||||||
<a href="{% url 'atlas:case_authors' case.pk %}" title="Edit Authors">Authors</a>
|
|
||||||
{% if request.user.is_superuser %}
|
|
||||||
<a href="{% url 'admin:atlas_case_change' case.id %}" title="Edit the Case using the admin interface">Admin Edit</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if collection %}
|
|
||||||
<div>
|
|
||||||
|
|
||||||
{% if previous %}
|
|
||||||
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:-1 %}">Previous question</a>
|
|
||||||
{% endif %}
|
|
||||||
Viewing question as part of collection: <a href="{% url 'atlas:collection_detail' collection.id %}">{{collection.name}}</a> [{{case_number|add:1}}/{{collection_length}}]
|
|
||||||
{% if next %}
|
|
||||||
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:1 %}">Next question</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% include 'atlas/case_display_block.html' %}
|
{% include 'atlas/case_display_block.html' %}
|
||||||
|
|||||||
@@ -49,6 +49,10 @@
|
|||||||
<!-- {{ form.media }} -->
|
<!-- {{ form.media }} -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if object %}
|
||||||
|
{% include "atlas/question_link_header.html" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h2>Submit Case</h2>
|
<h2>Submit Case</h2>
|
||||||
Use this form to create a atlas case. Existing associated image sets can be added using this form.
|
Use this form to create a atlas case. Existing associated image sets can be added using this form.
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<br/>Collection: {{collection.name}}-> <a href="{% url 'atlas:collection_detail' pk=collection.pk %}">Overview</a> /
|
<br/>Collection: {{collection.name}}-> <a href="{% url 'atlas:collection_detail' pk=collection.pk %}">Overview</a> /
|
||||||
<a href="{% url 'atlas:collection_mark_overview' collection.pk %}">Mark</a> /
|
<a href="{% url 'atlas:collection_mark_overview' collection.pk %}">Mark</a> /
|
||||||
<a href="{% url 'atlas:collection_scores_cid' collection.pk %}">Scores</a> /
|
<a href="{% url 'atlas:collection_scores_cid' collection.pk %}">Scores</a> /
|
||||||
<a href="{% url 'atlas:exam_cids' collection.pk %}">Candidates</a> /
|
<a href="{% url 'atlas:exam_cids' collection.pk %}">Candidates</a> /
|
||||||
<a href="{% url 'atlas:atlas_create_exam' pk=collection.pk %}">Add New Case</a> /
|
<a href="{% url 'atlas:atlas_create_exam' pk=collection.pk %}">Add New Case</a> /
|
||||||
<a href="{% url 'atlas:collection_authors' collection.pk %}" title="Edit Authors">Authors</a>
|
|
||||||
<div class="floating-header">
|
<div class="floating-header">
|
||||||
<a href="{% url 'atlas:exam_update' collection.id %}" title="Edit the Collection">Edit</a>
|
<a href="{% url 'atlas:exam_update' collection.id %}" title="Edit the Collection">Edit</a>
|
||||||
\ <a href="{% url 'atlas:exam_deleted' collection.id %}" title="Delete the Collection">Delete</a>
|
\ <a href="{% url 'atlas:exam_deleted' collection.id %}" title="Delete the Collection">Delete</a>
|
||||||
\ <a href="{% url 'atlas:exam_clone' collection.id %}" title="Clone the Collection">Clone</a>
|
\ <a href="{% url 'atlas:exam_clone' collection.id %}" title="Clone the Collection">Clone</a>
|
||||||
|
\ <a href="{% url 'atlas:collection_authors' collection.pk %}" title="Edit Authors">Authors</a>
|
||||||
|
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
\ <a href="{% url 'admin:atlas_casecollection_change' collection.id %}" title="Edit the Collection using the admin interface">Admin Edit</a>
|
\ <a href="{% url 'admin:atlas_casecollection_change' collection.id %}" title="Edit the Collection using the admin interface">Admin Edit</a>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="floating-header">
|
||||||
|
<a href="{% url 'atlas:case_update' pk=case.pk %}" title="Edit the Case">Edit</a>
|
||||||
|
<a href="{% url 'atlas:case_clone' pk=case.pk %}"
|
||||||
|
title="Clone the Case (duplicate everything but the images)">Clone</a>
|
||||||
|
<a href="{% url 'atlas:case_delete' pk=case.pk %}" title="Delete the Case">Delete</a>
|
||||||
|
<a href="#"
|
||||||
|
onclick="return window.create_popup_window('{% url 'feedback_create' question_type='atlas' pk=case.pk %}')"> Add
|
||||||
|
Note</a>
|
||||||
|
<a href="{% url 'atlas:case_authors' case.pk %}" title="Edit Authors">Authors</a>
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
<a href="{% url 'admin:atlas_case_change' case.id %}" title="Edit the Case using the admin interface">Admin Edit</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if collection %}
|
||||||
|
<div>
|
||||||
|
|
||||||
|
{% if previous %}
|
||||||
|
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:-1 %}">Previous question</a>
|
||||||
|
{% endif %}
|
||||||
|
Viewing question as part of collection: <a href="{% url 'atlas:collection_detail' collection.id %}">{{collection.name}}</a> [{{case_number|add:1}}/{{collection_length}}]
|
||||||
|
{% if next %}
|
||||||
|
<a href="{% url 'atlas:collection_case_view' collection.id case_number|add:1 %}">Next question</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
+11
-1
@@ -2247,7 +2247,17 @@ class CaseCollectionAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView)
|
|||||||
form_class = CaseCollectionAuthorForm
|
form_class = CaseCollectionAuthorForm
|
||||||
template_name = "author_form.html"
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CaseCollectionAuthorUpdate, self).get_context_data(**kwargs)
|
||||||
|
context["collection"] = context["object"]
|
||||||
|
return context
|
||||||
|
|
||||||
class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
|
class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
|
||||||
model = Case
|
model = Case
|
||||||
form_class = CaseAuthorForm
|
form_class = CaseAuthorForm
|
||||||
template_name = "author_form.html"
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CaseAuthorUpdate, self).get_context_data(**kwargs)
|
||||||
|
context["collection"] = context["object"]
|
||||||
|
return context
|
||||||
+24
-1
@@ -8,6 +8,29 @@ class SuperuserRequiredMixin(UserPassesTestMixin):
|
|||||||
def test_func(self):
|
def test_func(self):
|
||||||
return self.request.user.is_superuser
|
return self.request.user.is_superuser
|
||||||
|
|
||||||
|
class CheckCanEditMixin():
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
print(self.object)
|
||||||
|
print("1234", self.object.check_user_can_edit(self.request.user))
|
||||||
|
context["can_edit"] = self.object.check_user_can_edit(self.request.user)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionMixin():
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
raise NotImplementedError("You must implement get_app_name in the derived class")
|
||||||
|
|
||||||
|
def get_base_template(self):
|
||||||
|
return f"{self.get_app_name()}/base.html"
|
||||||
|
|
||||||
|
def get_link_headers(self):
|
||||||
|
return f"{self.get_app_name()}/question_link_header.html"
|
||||||
|
|
||||||
class AuthorMixin():
|
class AuthorMixin():
|
||||||
"""Mixin class for models that have authors
|
"""Mixin class for models that have authors
|
||||||
|
|
||||||
@@ -15,7 +38,7 @@ class AuthorMixin():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
author = models.ManyToManyField(User) # Corrected typing
|
author = models.ManyToManyField(User) # Corrected typing
|
||||||
|
|
||||||
def get_author_objects(self) -> List[User]: # Updated type hint
|
def get_author_objects(self) -> List[User]: # Updated type hint
|
||||||
"""Returns list of author objects"""
|
"""Returns list of author objects"""
|
||||||
authors = [i for i in self.author.all()]
|
authors = [i for i in self.author.all()]
|
||||||
|
|||||||
+11
-2
@@ -26,7 +26,7 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from generic.mixins import AuthorMixin
|
from generic.mixins import AuthorMixin, QuestionMixin
|
||||||
from helpers.images import get_image_hash, pretty_print_dicom
|
from helpers.images import get_image_hash, pretty_print_dicom
|
||||||
from rad.settings import REMOTE_URL
|
from rad.settings import REMOTE_URL
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
@@ -154,7 +154,7 @@ class Site(models.Model):
|
|||||||
return self.short_code
|
return self.short_code
|
||||||
|
|
||||||
|
|
||||||
class QuestionBase(models.Model, AuthorMixin):
|
class QuestionBase(models.Model, AuthorMixin, QuestionMixin):
|
||||||
authors_only = models.BooleanField(
|
authors_only = models.BooleanField(
|
||||||
help_text="If true only question authors will be able to view.",
|
help_text="If true only question authors will be able to view.",
|
||||||
default=False,
|
default=False,
|
||||||
@@ -580,6 +580,15 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
raise NotImplementedError("You must implement get_app_name in the derived class")
|
||||||
|
|
||||||
|
def get_base_template(self):
|
||||||
|
return f"{self.get_app_name()}/exams.html"
|
||||||
|
|
||||||
|
def get_link_headers(self):
|
||||||
|
return "generic/exam_link_headers.html"
|
||||||
|
|
||||||
def check_user_can_edit(self, user: User):
|
def check_user_can_edit(self, user: User):
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<br/>
|
||||||
|
{% if can_edit %}
|
||||||
|
<a href="{% url exam.get_app_name|add:':exam_update' exam.id %}" title="Edit the Exam">Edit</a>
|
||||||
|
\ <a href="{% url exam.get_app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
|
||||||
|
\ <a href="{% url exam.get_app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a>
|
||||||
|
\ <a href="{% url exam.get_app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
\ <a href="{% url 'admin:'|add:exam.get_app_name|add:'_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
|
||||||
|
{% endif %}
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{% if can_edit %}
|
{% include "generic/exam_link_headers.html" %}
|
||||||
<a href="{% url app_name|add:':exam_update' exam.id %}" title="Edit the Exam">Edit</a>
|
|
||||||
\ <a href="{% url app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
|
|
||||||
\ <a href="{% url app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a>
|
|
||||||
\ <a href="{% url app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if request.user.is_superuser %}
|
|
||||||
\ <a href="{% url 'admin:'|add:app_name|add:'_exam_change' exam.id %}" title="Edit the Exam using the admin interface">Admin Edit</a>
|
|
||||||
{% endif %}
|
|
||||||
<h1>Exam: {{ exam.name }}</h1>
|
<h1>Exam: {{ exam.name }}</h1>
|
||||||
|
|
||||||
{% include 'exam_notes.html' %}
|
{% include 'exam_notes.html' %}
|
||||||
@@ -15,7 +7,7 @@
|
|||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
Exam JSON may be out of date.
|
Exam JSON may be out of date.
|
||||||
|
|
||||||
<a href="{% url app_name|add:':exam_json_recreate' pk=exam.pk %}">Click here to force refresh</a>
|
<a href="{% url exam.get_app_name|add:':exam_json_recreate' pk=exam.pk %}">Click here to force refresh</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -44,15 +36,15 @@ Exam mode: {{ exam.exam_mode }}<br />
|
|||||||
Open access: {{ exam.open_access }}<br />
|
Open access: {{ exam.open_access }}<br />
|
||||||
|
|
||||||
<div class="parent-help" title="Click to enable / disable the exam">
|
<div class="parent-help" title="Click to enable / disable the exam">
|
||||||
Exam active: <input type="checkbox" id="exam-active-switch" {% if exam.active %}checked{% endif %} data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}"> <span class="help-text">[When checked the exam will be available to take in the test system]</span>
|
Exam active: <input type="checkbox" id="exam-active-switch" {% if exam.active %}checked{% endif %} data-posturl="{% url exam.get_app_name|add:':exam_toggle_active' pk=exam.pk %}"> <span class="help-text">[When checked the exam will be available to take in the test system]</span>
|
||||||
</div>
|
</div>
|
||||||
{% if exam.exam_mode %}
|
{% if exam.exam_mode %}
|
||||||
<div class="parent-help" title="Click to enable / disable the exam results">
|
<div class="parent-help" title="Click to enable / disable the exam results">
|
||||||
Publish results: <input type="checkbox" id="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}"
|
Publish results: <input type="checkbox" id="exam-publish-results-switch" data-posturl="{% url exam.get_app_name|add:':exam_toggle_results_published' pk=exam.pk %}"
|
||||||
{% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will be available to users on this site]</span>
|
{% if exam.publish_results %}checked{% endif %}> <span class="help-text">[When checked the exam results will be available to users on this site]</span>
|
||||||
</div>
|
</div>
|
||||||
{% if app_name == "anatomy" or app_name == "rapids" or app_name == "longs" %}
|
{% if exam.get_app_name == "anatomy" or exam.get_app_name == "rapids" or exam.get_app_name == "longs" %}
|
||||||
<p><a href="{% url app_name|add:':mark_overview' pk=exam.pk %}"><button>Mark exam</button></a></p>
|
<p><a href="{% url exam.get_app_name|add:':mark_overview' pk=exam.pk %}"><button>Mark exam</button></a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ class Long(QuestionBase):
|
|||||||
|
|
||||||
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "longs"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("longs:question_detail", kwargs={"pk": self.pk})
|
return reverse("longs:question_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
@@ -472,6 +475,9 @@ class Exam(ExamBase):
|
|||||||
|
|
||||||
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="longs_exams")
|
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="longs_exams")
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "longs"
|
||||||
|
|
||||||
def get_exam_question_json(self, question_id):
|
def get_exam_question_json(self, question_id):
|
||||||
q = get_object_or_404(Long, pk=question_id)
|
q = get_object_or_404(Long, pk=question_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
{% extends 'longs/exams.html' %}
|
{% extends 'longs/exams.html' %}
|
||||||
|
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'generic/exam_overview_headers.html' %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="longs">
|
<div class="longs">
|
||||||
{% include 'generic/exam_overview_headers.html' %}
|
|
||||||
|
|
||||||
|
|
||||||
<ol id="full-question-list" class="sortable">
|
<ol id="full-question-list" class="sortable">
|
||||||
|
|||||||
+4
-3
@@ -13,7 +13,7 @@ from django.core.exceptions import (
|
|||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||||
|
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
@@ -1130,7 +1130,7 @@ class ExamCreate(ExamCreateBase):
|
|||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
@@ -1142,9 +1142,10 @@ class ExamClone(ExamCloneMixin, ExamCreate):
|
|||||||
"""Clone exam view"""
|
"""Clone exam view"""
|
||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
|
||||||
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ class Question(QuestionBase):
|
|||||||
|
|
||||||
#notes = GenericRelation(QuestionNote)
|
#notes = GenericRelation(QuestionNote)
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "physics"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.stem
|
return self.stem
|
||||||
|
|
||||||
@@ -194,6 +197,9 @@ class Exam(ExamBase):
|
|||||||
def get_take_url(self):
|
def get_take_url(self):
|
||||||
return reverse("physics:exam_start", kwargs={"pk": self.pk})
|
return reverse("physics:exam_start", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "physics"
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class UserAnswer(UserAnswerBase):
|
class UserAnswer(UserAnswerBase):
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
{% extends 'physics/exams.html' %}
|
{% extends 'physics/exams.html' %}
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'generic/exam_overview_headers.html' %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="physics">
|
<div class="physics">
|
||||||
{% include 'generic/exam_overview_headers.html' %}
|
|
||||||
|
|
||||||
This exam will be available to take <a href="{% url 'physics:exam_start' pk=exam.pk %}">here</a> (when active).
|
This exam will be available to take <a href="{% url 'physics:exam_start' pk=exam.pk %}">here</a> (when active).
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -3,7 +3,7 @@ from django.utils import timezone
|
|||||||
from generic.models import CidUser
|
from generic.models import CidUser
|
||||||
from physics.decorators import user_is_author_or_physics_checker
|
from physics.decorators import user_is_author_or_physics_checker
|
||||||
from physics.filters import QuestionFilter, UserAnswerFilter
|
from physics.filters import QuestionFilter, UserAnswerFilter
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||||
from physics.tables import QuestionTable, UserAnswerTable
|
from physics.tables import QuestionTable, UserAnswerTable
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
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
|
||||||
@@ -339,7 +339,7 @@ class ExamCreate(ExamCreateBase):
|
|||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
@@ -353,10 +353,11 @@ class ExamClone(AuthorOrCheckerRequiredMixin, ExamCloneMixin, ExamCreate):
|
|||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(
|
class ExamAuthorUpdate(
|
||||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||||
):
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
|
||||||
class QuestionView(
|
class QuestionView(
|
||||||
|
|||||||
@@ -224,6 +224,9 @@ class Rapid(QuestionBase):
|
|||||||
default=False, help_text="Question has been scrapped and will not be shown"
|
default=False, help_text="Question has been scrapped and will not be shown"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "rapids"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("rapids:question_detail", kwargs={"pk": self.pk})
|
return reverse("rapids:question_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
@@ -652,6 +655,10 @@ class Exam(ExamBase):
|
|||||||
|
|
||||||
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="rapids_exams")
|
examcollection = models.ForeignKey(ExamCollection, blank=True, null=True, on_delete=models.SET_NULL, related_name="rapids_exams")
|
||||||
|
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "rapids"
|
||||||
|
|
||||||
def get_normal_abnormal_breakdown(self):
|
def get_normal_abnormal_breakdown(self):
|
||||||
# Inefficient but more extendible
|
# Inefficient but more extendible
|
||||||
questions = self.exam_questions.all()
|
questions = self.exam_questions.all()
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
{% extends 'rapids/exams.html' %}
|
{% extends 'rapids/exams.html' %}
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'generic/exam_overview_headers.html' %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="rapids">
|
<div class="rapids">
|
||||||
{% include 'generic/exam_overview_headers.html' %}
|
|
||||||
|
|
||||||
<a href="{% url 'rapids:mark_review' exam_pk=exam.pk sk=0 %}"><button>Review exam</button></a></p>
|
<a href="{% url 'rapids:mark_review' exam_pk=exam.pk sk=0 %}"><button>Review exam</button></a></p>
|
||||||
|
|
||||||
|
|||||||
+28
-21
@@ -90,7 +90,7 @@ from .serializers import (
|
|||||||
UserAnswerSerializer,
|
UserAnswerSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||||
|
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
|
|
||||||
@@ -213,12 +213,14 @@ def rapid_clone(request, pk):
|
|||||||
new_item.pk = None # autogen a new pk (item_id)
|
new_item.pk = None # autogen a new pk (item_id)
|
||||||
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
||||||
|
|
||||||
if request.user not in new_item.get_author_objects() and not request.user.is_superuser:
|
if (
|
||||||
|
request.user not in new_item.get_author_objects()
|
||||||
|
and not request.user.is_superuser
|
||||||
|
):
|
||||||
raise PermissionDenied() # or Http404
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
form = RapidForm(request.POST or None, instance=new_item)
|
form = RapidForm(request.POST or None, instance=new_item)
|
||||||
|
|
||||||
|
|
||||||
image_formset = ImageFormSet()
|
image_formset = ImageFormSet()
|
||||||
answer_formset = AnswerFormSet()
|
answer_formset = AnswerFormSet()
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ def rapid_clone(request, pk):
|
|||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": form,
|
||||||
"image_formset": image_formset,
|
"image_formset": image_formset,
|
||||||
"answer_formset": answer_formset
|
"answer_formset": answer_formset,
|
||||||
# other context
|
# other context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +333,7 @@ class RapidCreate(RapidCreateBase):
|
|||||||
# form.instance.author.add(self.request.user.id)
|
# form.instance.author.add(self.request.user.id)
|
||||||
# return super().form_valid(form)
|
# return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||||
model = Rapid
|
model = Rapid
|
||||||
form_class = RapidQuestionAnswerForm
|
form_class = RapidQuestionAnswerForm
|
||||||
@@ -353,7 +356,7 @@ class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView)
|
|||||||
self.object = form.save(commit=False)
|
self.object = form.save(commit=False)
|
||||||
self.object.save()
|
self.object.save()
|
||||||
|
|
||||||
#form.instance.author.add(self.request.user.id)
|
# form.instance.author.add(self.request.user.id)
|
||||||
|
|
||||||
context = self.get_context_data(form=form)
|
context = self.get_context_data(form=form)
|
||||||
formset = context["answer_formset"]
|
formset = context["answer_formset"]
|
||||||
@@ -367,9 +370,8 @@ class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView)
|
|||||||
else:
|
else:
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
class RapidUpdate(
|
|
||||||
RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
class RapidUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||||
):
|
|
||||||
model = Rapid
|
model = Rapid
|
||||||
form_class = RapidForm
|
form_class = RapidForm
|
||||||
|
|
||||||
@@ -728,13 +730,14 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
||||||
model = Rapid
|
model = Rapid
|
||||||
success_url = reverse_lazy("rapids:rapid_view")
|
success_url = reverse_lazy("rapids:rapid_view")
|
||||||
|
|
||||||
|
|
||||||
GenericExamViews = ExamViews(Exam, Rapid, Answer, UserAnswer, "rapids", "rapid", normalise_score=normaliseScore)
|
GenericExamViews = ExamViews(
|
||||||
|
Exam, Rapid, Answer, UserAnswer, "rapids", "rapid", normalise_score=normaliseScore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExamCreate(ExamCreateBase):
|
class ExamCreate(ExamCreateBase):
|
||||||
@@ -742,7 +745,7 @@ class ExamCreate(ExamCreateBase):
|
|||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
@@ -750,12 +753,17 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
|||||||
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||||
model = Exam
|
model = Exam
|
||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(
|
class ExamAuthorUpdate(
|
||||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
RevisionMixin,
|
||||||
|
CheckCanEditMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
AuthorRequiredMixin,
|
||||||
|
UpdateView,
|
||||||
):
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
|
template_name = "author_form.html"
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||||
@@ -798,24 +806,24 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if user.groups.filter(name="rapid_checker").exists():
|
if user.groups.filter(name="rapid_checker").exists():
|
||||||
return Exam.objects.filter(archive=False).order_by('name')
|
return Exam.objects.filter(archive=False).order_by("name")
|
||||||
|
|
||||||
return Exam.objects.filter(author__id=user.id, archive=False).order_by('name')
|
return Exam.objects.filter(author__id=user.id, archive=False).order_by("name")
|
||||||
|
|
||||||
|
|
||||||
#class QuestionAnswerViewSet(viewsets.ModelViewSet):
|
# class QuestionAnswerViewSet(viewsets.ModelViewSet):
|
||||||
# queryset = Answer.objects.all() # .order_by('name')
|
# queryset = Answer.objects.all() # .order_by('name')
|
||||||
# serializer_class = QuestionAnswerSerializer
|
# serializer_class = QuestionAnswerSerializer
|
||||||
# permission_classes = [permissions.IsAuthenticated]
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#class UserAnswerViewSet(viewsets.ModelViewSet):
|
# class UserAnswerViewSet(viewsets.ModelViewSet):
|
||||||
# queryset = UserAnswer.objects.all() # .order_by('name')
|
# queryset = UserAnswer.objects.all() # .order_by('name')
|
||||||
# serializer_class = UserAnswerSerializer
|
# serializer_class = UserAnswerSerializer
|
||||||
# permission_classes = [permissions.IsAuthenticated]
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
#class RapidViewSet(
|
# class RapidViewSet(
|
||||||
# RevisionMixin,
|
# RevisionMixin,
|
||||||
# mixins.CreateModelMixin,
|
# mixins.CreateModelMixin,
|
||||||
# mixins.RetrieveModelMixin,
|
# mixins.RetrieveModelMixin,
|
||||||
@@ -823,14 +831,13 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
|||||||
# mixins.ListModelMixin,
|
# mixins.ListModelMixin,
|
||||||
# mixins.DestroyModelMixin,
|
# mixins.DestroyModelMixin,
|
||||||
# viewsets.GenericViewSet,
|
# viewsets.GenericViewSet,
|
||||||
#):
|
# ):
|
||||||
# queryset = Rapid.objects.all() # .order_by('name')
|
# queryset = Rapid.objects.all() # .order_by('name')
|
||||||
# serializer_class = RapidSerializer
|
# serializer_class = RapidSerializer
|
||||||
# permission_classes = [permissions.IsAuthenticated]
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
# pagination_class = StandardResultsSetPagination
|
# pagination_class = StandardResultsSetPagination
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
|
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
|
||||||
# model = QuestionNote
|
# model = QuestionNote
|
||||||
# form_class = QuestionNoteForm
|
# form_class = QuestionNoteForm
|
||||||
@@ -968,4 +975,4 @@ class AnswerAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
if self.q:
|
if self.q:
|
||||||
qs = qs.filter(answer__istartswith=self.q)
|
qs = qs.filter(answer__istartswith=self.q)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ class Question(QuestionBase):
|
|||||||
if self.e_answer:
|
if self.e_answer:
|
||||||
self.e_answer = self.e_answer.strip()
|
self.e_answer = self.e_answer.strip()
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "sbas"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("sbas:question_detail", kwargs={"pk": self.pk})
|
return reverse("sbas:question_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
@@ -210,6 +213,9 @@ class Exam(ExamBase):
|
|||||||
def get_take_url(self):
|
def get_take_url(self):
|
||||||
return reverse("sbas:exam_start", kwargs={"pk": self.pk})
|
return reverse("sbas:exam_start", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_app_name(self):
|
||||||
|
return "sbas"
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class UserAnswer(UserAnswerBase):
|
class UserAnswer(UserAnswerBase):
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
{% extends 'sbas/exams.html' %}
|
{% extends 'sbas/exams.html' %}
|
||||||
|
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'generic/exam_overview_headers.html' %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<div class="sbas">
|
<div class="sbas">
|
||||||
{% include 'generic/exam_overview_headers.html' %}
|
|
||||||
|
|
||||||
This exam will be available to take <a href="{% url 'sbas:exam_start' pk=exam.pk %}">here</a> (when active).
|
This exam will be available to take <a href="{% url 'sbas:exam_start' pk=exam.pk %}">here</a> (when active).
|
||||||
|
|
||||||
@@ -47,17 +51,17 @@
|
|||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info('Exam state changed.')
|
toastr.info('Exam state changed.')
|
||||||
}
|
}
|
||||||
// show some message according to the response.
|
// show some message according to the response.
|
||||||
// For eg. A message box showing that the status has been changed
|
// For eg. A message box showing that the status has been changed
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
$("#exam-publish-results-switch").on("change", function () {
|
$("#exam-publish-results-switch").on("change", function () {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -71,17 +75,17 @@
|
|||||||
})
|
})
|
||||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||||
.done(function (data) {
|
.done(function (data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
toastr.info('Publish results state changed.')
|
toastr.info('Publish results state changed.')
|
||||||
}
|
}
|
||||||
// show some message according to the response.
|
// show some message according to the response.
|
||||||
// For eg. A message box showing that the status has been changed
|
// For eg. A message box showing that the status has been changed
|
||||||
})
|
})
|
||||||
.always(function () {
|
.always(function () {
|
||||||
console.log('[Done]');
|
console.log('[Done]');
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,5 @@
|
|||||||
from reversion.views import RevisionMixin
|
from reversion.views import RevisionMixin
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from generic.models import CidUser
|
from generic.models import CidUser
|
||||||
from sbas.forms import UserAnswerForm, ExamAuthorForm, ExamForm
|
from sbas.forms import UserAnswerForm, ExamAuthorForm, ExamForm
|
||||||
@@ -402,7 +402,7 @@ class ExamCreate(ExamCreateBase):
|
|||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
|
|
||||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamForm
|
form_class = ExamForm
|
||||||
|
|
||||||
@@ -428,11 +428,12 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
|||||||
|
|
||||||
|
|
||||||
class ExamAuthorUpdate(
|
class ExamAuthorUpdate(
|
||||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||||
):
|
):
|
||||||
model = Exam
|
model = Exam
|
||||||
form_class = ExamAuthorForm
|
form_class = ExamAuthorForm
|
||||||
|
|
||||||
|
template_name = "author_form.html"
|
||||||
|
|
||||||
class ExamClone(ExamCloneMixin, ExamCreate):
|
class ExamClone(ExamCloneMixin, ExamCreate):
|
||||||
"""Clone exam view"""
|
"""Clone exam view"""
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends object.get_base_template %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include object.get_link_headers %}
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h2>Edit Authors</h2>
|
<h2>Edit Authors</h2>
|
||||||
|
|||||||
@@ -4,8 +4,15 @@
|
|||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block navigation %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if object %}
|
||||||
|
{% include object.get_link_headers %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock navigation %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<h2>Update Exam</h2>
|
<h2>Update Exam</h2>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
Reference in New Issue
Block a user