Add QuestionForm and related views: implement form for creating questions with user context and exam association
This commit is contained in:
+30
-1
@@ -47,4 +47,33 @@ class ExamMarkerForm(ExamMarkerFormMixin):
|
||||
|
||||
class ExamGroupsForm(ExamGroupsFormMixin):
|
||||
class Meta(ExamGroupsFormMixin.Meta):
|
||||
model = Exam
|
||||
model = Exam
|
||||
|
||||
|
||||
class QuestionForm(ModelForm):
|
||||
class Meta:
|
||||
model = Question
|
||||
fields = [
|
||||
"stem",
|
||||
"a",
|
||||
"a_answer",
|
||||
"a_feedback",
|
||||
"b",
|
||||
"b_answer",
|
||||
"b_feedback",
|
||||
"c",
|
||||
"c_answer",
|
||||
"c_feedback",
|
||||
"d",
|
||||
"d_answer",
|
||||
"d_feedback",
|
||||
"e",
|
||||
"e_answer",
|
||||
"e_feedback",
|
||||
"category",
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# accept an optional user kwarg for parity with other apps
|
||||
self.user = kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends "physics/base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-3">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Add question</h1>
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.helper %}
|
||||
{% crispy form form.helper %}
|
||||
{% else %}
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger small">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a href="javascript:history.back()" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -10,6 +10,8 @@ urlpatterns = []
|
||||
urlpatterns.extend(
|
||||
[
|
||||
path("question/", views.QuestionView.as_view(), name="question_view"),
|
||||
path("question/create/", views.QuestionCreate.as_view(), name="question_create"),
|
||||
path("question/create/exam/<int:pk>", views.QuestionCreate.as_view(), name="question_create_exam"),
|
||||
path("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||
path("exam/<int:pk>/take_old", views.exam_take_old, name="exam_take_old"),
|
||||
path(
|
||||
|
||||
+57
-1
@@ -56,7 +56,14 @@ from generic.views import (
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from .forms import ExamGroupsForm, ExamMarkerForm, UserAnswerForm, ExamAuthorForm, ExamForm
|
||||
from .forms import (
|
||||
ExamGroupsForm,
|
||||
ExamMarkerForm,
|
||||
UserAnswerForm,
|
||||
ExamAuthorForm,
|
||||
ExamForm,
|
||||
QuestionForm,
|
||||
)
|
||||
from .decorators import (
|
||||
user_is_author_or_physics_checker,
|
||||
user_is_exam_author_or_physics_checker,
|
||||
@@ -534,6 +541,55 @@ class QuestionView(
|
||||
return context
|
||||
|
||||
|
||||
class QuestionCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
|
||||
model = Question
|
||||
form_class = QuestionForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(QuestionCreateBase, self).get_form_kwargs()
|
||||
kwargs.update({"user": self.request.user})
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
self.object.save()
|
||||
|
||||
# add the current user as an author
|
||||
form.instance.author.add(self.request.user.id)
|
||||
|
||||
response = super().form_valid(form)
|
||||
return response
|
||||
|
||||
|
||||
class QuestionCreate(QuestionCreateBase):
|
||||
def get_initial(self):
|
||||
if "pk" in self.kwargs:
|
||||
initial = super().get_initial()
|
||||
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
|
||||
# When creating a question from an exam context we don't have an
|
||||
# 'exams' field on the ModelForm (exams is a reverse relation).
|
||||
# Store nothing in the initial data here; attachment is handled
|
||||
# in form_valid below.
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
# Let the base class save the object and set the author
|
||||
response = super().form_valid(form)
|
||||
|
||||
# If the create URL included an exam pk, add the new question to that exam
|
||||
if "pk" in self.kwargs:
|
||||
try:
|
||||
exam = get_object_or_404(Exam, pk=self.kwargs["pk"])
|
||||
# exam.exam_questions is a ManyToMany through relation; add is fine
|
||||
exam.exam_questions.add(self.object)
|
||||
except Exception:
|
||||
# Don't fail the whole request if attaching to the exam fails;
|
||||
# the question has already been created and author set.
|
||||
pass
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class UserAnswerView(AuthorOrCheckerRequiredMixin, LoginRequiredMixin, DetailView):
|
||||
model = UserAnswer
|
||||
|
||||
|
||||
Reference in New Issue
Block a user