start taking more advantage of class based views
This commit is contained in:
@@ -127,6 +127,9 @@ class AnatomyQuestion(QuestionBase):
|
||||
class Meta:
|
||||
permissions = ()
|
||||
|
||||
def get_app_name(self):
|
||||
return "anatomy"
|
||||
|
||||
def __str__(self):
|
||||
# Get first answer
|
||||
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")
|
||||
|
||||
def get_app_name(self):
|
||||
return "anatomy"
|
||||
|
||||
def get_exam_json(self, based=True):
|
||||
questions = self.get_questions()
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{% extends 'anatomy/exams.html' %}
|
||||
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load thumbnail %}
|
||||
<div class="anatomy">
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
|
||||
<ol id="full-question-list" class="sortable">
|
||||
{% for question in questions %}
|
||||
|
||||
|
||||
+11
-3
@@ -71,7 +71,7 @@ from helpers.images import image_as_base64
|
||||
|
||||
from django.template.defaulttags import register
|
||||
|
||||
from generic.mixins import SuperuserRequiredMixin
|
||||
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||
|
||||
from rest_framework import viewsets, permissions
|
||||
|
||||
@@ -805,10 +805,16 @@ class ExamCreate(ExamCreateBase):
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
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):
|
||||
model = Exam
|
||||
@@ -831,10 +837,12 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
# return Exam.objects.filter(author__id=user.id)
|
||||
|
||||
|
||||
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
|
||||
template_name = "author_form.html"
|
||||
|
||||
|
||||
|
||||
GenericViews = GenericViewBase("anatomy", AnatomyQuestion, UserAnswer, Exam)
|
||||
|
||||
+15
-2
@@ -5,7 +5,7 @@ import pathlib
|
||||
from typing import Tuple
|
||||
|
||||
from django.http import Http404, HttpRequest
|
||||
from generic.mixins import AuthorMixin
|
||||
from generic.mixins import AuthorMixin, QuestionMixin
|
||||
from rad.settings import REMOTE_URL
|
||||
from django.db.models.fields.files import ImageField
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
@@ -273,7 +273,7 @@ class Structure(SynMixin, models.Model):
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Case(models.Model, AuthorMixin):
|
||||
class Case(models.Model, AuthorMixin, QuestionMixin):
|
||||
# class SubspecialtyChoices(models.TextChoices):
|
||||
# BREAST = "BR", _("Breast")
|
||||
# CARDIAC = "CA", _("Cardiac")
|
||||
@@ -364,6 +364,9 @@ class Case(models.Model, AuthorMixin):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def get_app_name(self):
|
||||
return "atlas"
|
||||
|
||||
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -373,6 +376,10 @@ class Case(models.Model, AuthorMixin):
|
||||
# return f"{self.pk}: {self.title}"
|
||||
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):
|
||||
return f"{self.pk}: {self.title}"
|
||||
|
||||
@@ -773,6 +780,12 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
||||
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):
|
||||
return reverse("atlas:collection_detail", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
@@ -3,33 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
|
||||
|
||||
<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/question_link_header.html' %}
|
||||
|
||||
|
||||
{% include 'atlas/case_display_block.html' %}
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
<!-- {{ form.media }} -->
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if object %}
|
||||
{% include "atlas/question_link_header.html" %}
|
||||
{% endif %}
|
||||
|
||||
<h2>Submit Case</h2>
|
||||
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 %}
|
||||
<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_scores_cid' collection.pk %}">Scores</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:collection_authors' collection.pk %}" title="Edit Authors">Authors</a>
|
||||
<div class="floating-header">
|
||||
<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_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 %}
|
||||
\ <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>
|
||||
@@ -2247,7 +2247,17 @@ class CaseCollectionAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView)
|
||||
form_class = CaseCollectionAuthorForm
|
||||
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):
|
||||
model = Case
|
||||
form_class = CaseAuthorForm
|
||||
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
|
||||
@@ -8,6 +8,29 @@ class SuperuserRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
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():
|
||||
"""Mixin class for models that have authors
|
||||
|
||||
|
||||
+11
-2
@@ -26,7 +26,7 @@ from django.contrib.auth.models import User
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
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 rad.settings import REMOTE_URL
|
||||
from django.utils.html import format_html
|
||||
@@ -154,7 +154,7 @@ class Site(models.Model):
|
||||
return self.short_code
|
||||
|
||||
|
||||
class QuestionBase(models.Model, AuthorMixin):
|
||||
class QuestionBase(models.Model, AuthorMixin, QuestionMixin):
|
||||
authors_only = models.BooleanField(
|
||||
help_text="If true only question authors will be able to view.",
|
||||
default=False,
|
||||
@@ -580,6 +580,15 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
||||
class Meta:
|
||||
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):
|
||||
if user.is_superuser:
|
||||
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 %}
|
||||
<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 %}
|
||||
{% include "generic/exam_link_headers.html" %}
|
||||
<h1>Exam: {{ exam.name }}</h1>
|
||||
|
||||
{% include 'exam_notes.html' %}
|
||||
@@ -15,7 +7,7 @@
|
||||
<div class="alert alert-warning" role="alert">
|
||||
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>
|
||||
{% endif %}
|
||||
|
||||
@@ -44,15 +36,15 @@ Exam mode: {{ exam.exam_mode }}<br />
|
||||
Open access: {{ exam.open_access }}<br />
|
||||
|
||||
<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>
|
||||
{% if exam.exam_mode %}
|
||||
<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>
|
||||
</div>
|
||||
{% if app_name == "anatomy" or app_name == "rapids" or app_name == "longs" %}
|
||||
<p><a href="{% url app_name|add:':mark_overview' pk=exam.pk %}"><button>Mark exam</button></a></p>
|
||||
{% if exam.get_app_name == "anatomy" or exam.get_app_name == "rapids" or exam.get_app_name == "longs" %}
|
||||
<p><a href="{% url exam.get_app_name|add:':mark_overview' pk=exam.pk %}"><button>Mark exam</button></a></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ class Long(QuestionBase):
|
||||
|
||||
# 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):
|
||||
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")
|
||||
|
||||
def get_app_name(self):
|
||||
return "longs"
|
||||
|
||||
def get_exam_question_json(self, question_id):
|
||||
q = get_object_or_404(Long, pk=question_id)
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{% extends 'longs/exams.html' %}
|
||||
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load thumbnail %}
|
||||
<div class="longs">
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
|
||||
|
||||
<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.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 import ListView
|
||||
@@ -1130,7 +1130,7 @@ class ExamCreate(ExamCreateBase):
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
|
||||
@@ -1142,9 +1142,10 @@ class ExamClone(ExamCloneMixin, ExamCreate):
|
||||
"""Clone exam view"""
|
||||
|
||||
|
||||
class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
template_name = "author_form.html"
|
||||
|
||||
|
||||
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
|
||||
@@ -93,6 +93,9 @@ class Question(QuestionBase):
|
||||
|
||||
#notes = GenericRelation(QuestionNote)
|
||||
|
||||
def get_app_name(self):
|
||||
return "physics"
|
||||
|
||||
def __str__(self):
|
||||
return self.stem
|
||||
|
||||
@@ -194,6 +197,9 @@ class Exam(ExamBase):
|
||||
def get_take_url(self):
|
||||
return reverse("physics:exam_start", kwargs={"pk": self.pk})
|
||||
|
||||
def get_app_name(self):
|
||||
return "physics"
|
||||
|
||||
|
||||
@reversion.register
|
||||
class UserAnswer(UserAnswerBase):
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
{% extends 'physics/exams.html' %}
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load thumbnail %}
|
||||
<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).
|
||||
|
||||
|
||||
+4
-3
@@ -3,7 +3,7 @@ from django.utils import timezone
|
||||
from generic.models import CidUser
|
||||
from physics.decorators import user_is_author_or_physics_checker
|
||||
from physics.filters import QuestionFilter, UserAnswerFilter
|
||||
from generic.mixins import SuperuserRequiredMixin
|
||||
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||
from physics.tables import QuestionTable, UserAnswerTable
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@@ -339,7 +339,7 @@ class ExamCreate(ExamCreateBase):
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
|
||||
@@ -353,10 +353,11 @@ class ExamClone(AuthorOrCheckerRequiredMixin, ExamCloneMixin, ExamCreate):
|
||||
|
||||
|
||||
class ExamAuthorUpdate(
|
||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
template_name = "author_form.html"
|
||||
|
||||
|
||||
class QuestionView(
|
||||
|
||||
@@ -224,6 +224,9 @@ class Rapid(QuestionBase):
|
||||
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):
|
||||
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")
|
||||
|
||||
|
||||
def get_app_name(self):
|
||||
return "rapids"
|
||||
|
||||
def get_normal_abnormal_breakdown(self):
|
||||
# Inefficient but more extendible
|
||||
questions = self.exam_questions.all()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
{% extends 'rapids/exams.html' %}
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load thumbnail %}
|
||||
<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>
|
||||
|
||||
|
||||
+27
-20
@@ -90,7 +90,7 @@ from .serializers import (
|
||||
UserAnswerSerializer,
|
||||
)
|
||||
|
||||
from generic.mixins import SuperuserRequiredMixin
|
||||
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||
|
||||
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.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
|
||||
|
||||
form = RapidForm(request.POST or None, instance=new_item)
|
||||
|
||||
|
||||
image_formset = ImageFormSet()
|
||||
answer_formset = AnswerFormSet()
|
||||
|
||||
@@ -240,7 +242,7 @@ def rapid_clone(request, pk):
|
||||
context = {
|
||||
"form": form,
|
||||
"image_formset": image_formset,
|
||||
"answer_formset": answer_formset
|
||||
"answer_formset": answer_formset,
|
||||
# other context
|
||||
}
|
||||
|
||||
@@ -331,6 +333,7 @@ class RapidCreate(RapidCreateBase):
|
||||
# form.instance.author.add(self.request.user.id)
|
||||
# return super().form_valid(form)
|
||||
|
||||
|
||||
class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||
model = Rapid
|
||||
form_class = RapidQuestionAnswerForm
|
||||
@@ -353,7 +356,7 @@ class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView)
|
||||
self.object = form.save(commit=False)
|
||||
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)
|
||||
formset = context["answer_formset"]
|
||||
@@ -367,9 +370,8 @@ class RapidAnswerUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView)
|
||||
else:
|
||||
return super().form_invalid(form)
|
||||
|
||||
class RapidUpdate(
|
||||
RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView
|
||||
):
|
||||
|
||||
class RapidUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
|
||||
model = Rapid
|
||||
form_class = RapidForm
|
||||
|
||||
@@ -728,13 +730,14 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
|
||||
)
|
||||
|
||||
|
||||
|
||||
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
|
||||
model = Rapid
|
||||
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):
|
||||
@@ -742,7 +745,7 @@ class ExamCreate(ExamCreateBase):
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
|
||||
@@ -750,12 +753,17 @@ class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
model = Exam
|
||||
|
||||
|
||||
class ExamAuthorUpdate(
|
||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
RevisionMixin,
|
||||
CheckCanEditMixin,
|
||||
LoginRequiredMixin,
|
||||
AuthorRequiredMixin,
|
||||
UpdateView,
|
||||
):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
|
||||
template_name = "author_form.html"
|
||||
|
||||
|
||||
class UserAnswerView(LoginRequiredMixin, DetailView):
|
||||
@@ -798,24 +806,24 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
"""
|
||||
user = self.request.user
|
||||
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')
|
||||
# serializer_class = QuestionAnswerSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
#
|
||||
#
|
||||
#class UserAnswerViewSet(viewsets.ModelViewSet):
|
||||
# class UserAnswerViewSet(viewsets.ModelViewSet):
|
||||
# queryset = UserAnswer.objects.all() # .order_by('name')
|
||||
# serializer_class = UserAnswerSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
|
||||
#class RapidViewSet(
|
||||
# class RapidViewSet(
|
||||
# RevisionMixin,
|
||||
# mixins.CreateModelMixin,
|
||||
# mixins.RetrieveModelMixin,
|
||||
@@ -823,14 +831,13 @@ class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
|
||||
# mixins.ListModelMixin,
|
||||
# mixins.DestroyModelMixin,
|
||||
# viewsets.GenericViewSet,
|
||||
#):
|
||||
# ):
|
||||
# queryset = Rapid.objects.all() # .order_by('name')
|
||||
# serializer_class = RapidSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
# pagination_class = StandardResultsSetPagination
|
||||
|
||||
|
||||
|
||||
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
|
||||
# model = QuestionNote
|
||||
# form_class = QuestionNoteForm
|
||||
|
||||
@@ -108,6 +108,9 @@ class Question(QuestionBase):
|
||||
if self.e_answer:
|
||||
self.e_answer = self.e_answer.strip()
|
||||
|
||||
def get_app_name(self):
|
||||
return "sbas"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("sbas:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
@@ -210,6 +213,9 @@ class Exam(ExamBase):
|
||||
def get_take_url(self):
|
||||
return reverse("sbas:exam_start", kwargs={"pk": self.pk})
|
||||
|
||||
def get_app_name(self):
|
||||
return "sbas"
|
||||
|
||||
|
||||
@reversion.register
|
||||
class UserAnswer(UserAnswerBase):
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{% extends 'sbas/exams.html' %}
|
||||
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include 'generic/exam_overview_headers.html' %}
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load thumbnail %}
|
||||
<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).
|
||||
|
||||
@@ -47,17 +51,17 @@
|
||||
})
|
||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||
.done(function (data) {
|
||||
console.log(data);
|
||||
console.log(data);
|
||||
|
||||
if (data.status == "success") {
|
||||
toastr.info('Exam state changed.')
|
||||
}
|
||||
if (data.status == "success") {
|
||||
toastr.info('Exam state changed.')
|
||||
}
|
||||
// show some message according to the response.
|
||||
// For eg. A message box showing that the status has been changed
|
||||
})
|
||||
})
|
||||
.always(function () {
|
||||
console.log('[Done]');
|
||||
})
|
||||
console.log('[Done]');
|
||||
})
|
||||
})
|
||||
$("#exam-publish-results-switch").on("change", function () {
|
||||
$.ajax({
|
||||
@@ -71,17 +75,17 @@
|
||||
})
|
||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||
.done(function (data) {
|
||||
console.log(data);
|
||||
console.log(data);
|
||||
|
||||
if (data.status == "success") {
|
||||
toastr.info('Publish results state changed.')
|
||||
}
|
||||
if (data.status == "success") {
|
||||
toastr.info('Publish results state changed.')
|
||||
}
|
||||
// show some message according to the response.
|
||||
// For eg. A message box showing that the status has been changed
|
||||
})
|
||||
})
|
||||
.always(function () {
|
||||
console.log('[Done]');
|
||||
})
|
||||
console.log('[Done]');
|
||||
})
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
from reversion.views import RevisionMixin
|
||||
from generic.mixins import SuperuserRequiredMixin
|
||||
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
||||
from django.views.generic.detail import DetailView
|
||||
from generic.models import CidUser
|
||||
from sbas.forms import UserAnswerForm, ExamAuthorForm, ExamForm
|
||||
@@ -402,7 +402,7 @@ class ExamCreate(ExamCreateBase):
|
||||
form_class = ExamForm
|
||||
|
||||
|
||||
class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
class ExamUpdate(CheckCanEditMixin, ExamUpdateBase, AuthorOrCheckerRequiredMixin):
|
||||
model = Exam
|
||||
form_class = ExamForm
|
||||
|
||||
@@ -428,11 +428,12 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
|
||||
|
||||
|
||||
class ExamAuthorUpdate(
|
||||
RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
|
||||
):
|
||||
model = Exam
|
||||
form_class = ExamAuthorForm
|
||||
|
||||
template_name = "author_form.html"
|
||||
|
||||
class ExamClone(ExamCloneMixin, ExamCreate):
|
||||
"""Clone exam view"""
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% extends object.get_base_template %}
|
||||
|
||||
{% block js %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% include object.get_link_headers %}
|
||||
{% endblock navigation %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Edit Authors</h2>
|
||||
|
||||
@@ -4,8 +4,15 @@
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block navigation %}
|
||||
{{ block.super }}
|
||||
{% if object %}
|
||||
{% include object.get_link_headers %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock navigation %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Update Exam</h2>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
Reference in New Issue
Block a user