Reputation: 18745
I'm trying to create a Quiz
in Django. There are multiple questions and QuestionForm
. In a view, I get set of Questions
and create a list of QuestionForm
s for each object. Then this list is sent into the template and render text and form for each question separately.
This way is very uncomfortable and I think that there must be a more simple and straightforward way to do that.
The main flow:
When user takes a test, the Sitting
object is created. This object holds information about user, quiz and preloaded questions. When user answeres the quiz, there is created SittingQuestion
objects which holds information about question and user answer.
As you can see, I've added a parameter name = question-id
for each form and the result of each form is answer-id
. I have to parse id's and create object's using this id's.
I would appreciate any help to avoid parsing especially.
I'm attaching a QuestionForm
and a view
:
QuestionForm
class QuestionForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
choice_list = [(x.pk, x.text) for x in question.get_answers_list()]
self.fields["question-{}".format(question.id)] = forms.ChoiceField(choices=choice_list,
widget=forms.RadioSelect)
View
def take_quiz(request, id):
if request.method == 'GET':
sitting = models.Sitting.objects.create(quiz=quiz, user=request.user)
sitting.load_questions()
formset = []
for q in sitting.sitting_questions.all():
formset.append((q.question, forms.QuestionForm(q.question)))
return render(request, 'quiz/quiz.html', context={'formset': formset})
quiz = get_object_or_404(models.LanguageQuiz, pk=id)
sitting = get_object_or_404(Sitting,user=request.user,quiz=quiz)
if request.method == 'POST':
for question in request.POST:
question_id = question.split('-')[1]
question_object = get_object_or_404(Question,id=question_id)
answer_id = request.POST[question_id][0]
answer_object = get_object_or_404(Answer,id=answer_id)
SittingQuestion.objects.create(sitting=sitting,question=question_object,answer=answer_object)
MODELS
class LanguageQuiz(models.Model):
name = models.CharField(max_length=40)
language = models.OneToOneField(sfl_models.Language)
max_questions = models.IntegerField()
time_to_repeat_in_days = models.PositiveIntegerField(default=0)
def __str__(self):
return '{} test'.format(self.name)
def __unicode__(self):
return self.__str__()
class Question(models.Model):
language_quiz = models.ForeignKey(LanguageQuiz,related_name='questions')
text = models.TextField()
def get_answers_list(self):
return self.answers.all()
class Answer(models.Model):
question = models.ForeignKey(Question,related_name='answers',on_delete=models.CASCADE)
text = models.TextField()
correct = models.BooleanField()
class Sitting(models.Model):
user = models.ForeignKey(sfl_models.User, related_name='test_sitting')
quiz = models.ForeignKey(LanguageQuiz)
date_opened = models.DateTimeField(auto_now_add=True)
date_closed = models.DateTimeField(null=True)
closed = models.BooleanField(default=0)
class Meta:
unique_together = ('user','quiz')
def __str__(self):
return 'Sitting - user: {}; quiz: {}'.format(self.user, self.quiz)
def load_questions(self):
questions = random.sample(self.quiz.questions.all(),min(self.quiz.max_questions,len(self.quiz.questions.all())))
for question in questions:
SittingQuestion.objects.create(question=question,sitting=self)
class SittingQuestion(models.Model):
sitting = models.ForeignKey(Sitting, related_name='sitting_questions', on_delete=models.CASCADE)
question = models.ForeignKey(Question, related_name='sitting_questions')
answer = models.ForeignKey(Answer,null=True, blank=True)
Upvotes: 0
Views: 102
Reputation: 26
Here is one possible improvement to the design:
Instead of QuestionForm
, make a QuizForm
. Pass sitting.sitting_questions.all()
to your form, and make each one its own ChoiceField
. This will make it much easier to process your form. Once you initialize whatever variables you need, handling in the view is usually as simple as this:
if request.method == 'POST':
form = QuizForm(request.POST)
if form.is_valid():
# whatever you'd like to do
else: # GET
form = QuizForm(list_of_questions)
There is no need to parse to get the question id
, you can just call question.id
or question.pk
.
some elaboration:
class QuizForm(forms.Form):
def __init__(self, questions, *args, **kwargs):
super(QuizForm, self).__init__(*args, **kwargs)
for question in questions:
choice_list = [("QUESTION TEXT", question.text)]
choice_list.append([(x.pk, x.text) for x in question.get_answers_list()])
self.fields["question-{}".format(question.id) = forms.ChoiceField(
choices=choice_list,
widget=forms.RadioSelect
)
Update: How to add Question text before options.
If your QuizForm has fields which are all questions, then when you iterate over your field you will get these question fields: {% for question in form %}
. Since question
is a field and not the actual Question
object, I admit you can't simply access question.text
in the loop. However, you can decide to add question.text
to the choices field (a little hack-ish, but functional), I've included this possibility above. Then try something like this:
{% for question in form %}
{% for id, text in question.field.choices %}
{% if id == 'QUESTION TEXT' %}
{{ text }}
{% else %}
<!-- render however you like -->
{% endif %}
{% endfor %}
{% endfor %}
For rendering buttons: https://docs.djangoproject.com/es/1.9/ref/forms/widgets/#radioselect I think there are a lot of similar questions on SO already for how to render the choices.
Upvotes: 1