Milano
Milano

Reputation: 18745

How to properly recieve data from list of forms?

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 QuestionForms 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

Answers (1)

Lauren Caps
Lauren Caps

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

Related Questions