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 ExamGroupsForm(ExamGroupsFormMixin):
|
||||||
class Meta(ExamGroupsFormMixin.Meta):
|
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(
|
urlpatterns.extend(
|
||||||
[
|
[
|
||||||
path("question/", views.QuestionView.as_view(), name="question_view"),
|
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("question/<int:pk>/", views.question_detail, name="question_detail"),
|
||||||
path("exam/<int:pk>/take_old", views.exam_take_old, name="exam_take_old"),
|
path("exam/<int:pk>/take_old", views.exam_take_old, name="exam_take_old"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
+57
-1
@@ -56,7 +56,14 @@ from generic.views import (
|
|||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
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 (
|
from .decorators import (
|
||||||
user_is_author_or_physics_checker,
|
user_is_author_or_physics_checker,
|
||||||
user_is_exam_author_or_physics_checker,
|
user_is_exam_author_or_physics_checker,
|
||||||
@@ -534,6 +541,55 @@ class QuestionView(
|
|||||||
return context
|
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):
|
class UserAnswerView(AuthorOrCheckerRequiredMixin, LoginRequiredMixin, DetailView):
|
||||||
model = UserAnswer
|
model = UserAnswer
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user