start taking more advantage of class based views

This commit is contained in:
Ross
2024-03-11 11:08:35 +00:00
parent 78e4ed871e
commit 0cb38f5c31
27 changed files with 244 additions and 104 deletions
+6
View File
@@ -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()
+5 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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})
+1 -27
View File
@@ -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' %}
+4
View File
@@ -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>
+11 -1
View File
@@ -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"
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
View File
@@ -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
@@ -15,7 +38,7 @@ class AuthorMixin():
"""
author = models.ManyToManyField(User) # Corrected typing
def get_author_objects(self) -> List[User]: # Updated type hint
"""Returns list of author objects"""
authors = [i for i in self.author.all()]
+11 -2
View File
@@ -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 %}
+6
View File
@@ -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)
+5 -1
View File
@@ -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
View File
@@ -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):
+6
View File
@@ -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):
+4 -1
View File
@@ -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
View File
@@ -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(
+7
View File
@@ -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()
+4 -1
View File
@@ -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>
+28 -21
View File
@@ -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
@@ -968,4 +975,4 @@ class AnswerAutocomplete(autocomplete.Select2QuerySetView):
if self.q:
qs = qs.filter(answer__istartswith=self.q)
return qs
return qs
+6
View File
@@ -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):
+19 -15
View File
@@ -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
View File
@@ -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"""
+8 -1
View File
@@ -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>
+8 -1
View File
@@ -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 %}