Complex Formsets, Redux

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
forms.py
========
    from django.forms.formsets import Form, BaseFormSet, formset_factory, \
            ValidationError


    class QuestionForm(Form):
        """Form for a single question on a quiz"""
        def __init__(self, *args, **kwargs):
            # CODE TRICK #1
            # pass in a question from the formset
            # use the question to build the form
            # pop removes from dict, so we don't pass to the parent
            self.question = kwargs.pop('question')
            super(QuestionForm, self).__init__(*args, **kwargs)

            # CODE TRICK #2
            # add a non-declared field to fields
            # use an order_by clause if you care about order
            self.answers = self.question.answer_set.all(
                    ).order_by('id')
            self.fields['answers'] = forms.ModelChoiceField(
                    queryset=self.answers())


    class BaseQuizFormSet(BaseFormSet):
        def __init__(self, *args, **kwargs):
            # CODE TRICK #3 - same as #1:
            # pass in a valid quiz object from the view
            # pop removes arg, so we don't pass to the parent
            self.quiz = kwargs.pop('quiz')

            # CODE TRICK #4
            # set length of extras based on query
            # each question will fill one 'extra' slot
            # use an order_by clause if you care about order
            self.questions = self.quiz.question_set.all().order_by('id')
            self.extra = len(self.questions)
            if not self.extra:
                raise Http404('Badly configured quiz has no questions.')

            # call the parent constructor to finish __init__            
            super(BaseQuizFormSet, self).__init__(*args, **kwargs)

        def _construct_form(self, index, **kwargs):
            # CODE TRICK #5
            # know that _construct_form is where forms get added
            # we can take advantage of this fact to add our forms
            # add custom kwargs, using the index to retrieve a question
            # kwargs will be passed to our form class
            kwargs['question'] = self.questions[index]
            return super(BaseQuizFormSet, self)._construct_form(index, **kwargs)


    QuizFormSet = formset_factory(
        QuestionForm, formset=BaseQuizDynamicFormSet)


views.py
========

from django.http import Http404


    def quiz_form(request, quiz_id):
        try:
            quiz = Quiz.objects.get(pk=quiz_id)
        except Quiz.DoesNotExist:
            return Http404('Invalid quiz id.')
        if request.method == 'POST':
            formset = QuizFormSet(quiz=quiz, data=request.POST)
            answers = []
            if formset.is_valid():
                for form in formset.forms:
                    answers.append(str(int(form.is_correct())))
                return HttpResponseRedirect('%s?a=%s'
                        % (reverse('result-display',args=[quiz_id]), ''.join(answers)))
        else:
            formset = QuizFormSet(quiz=quiz)

        return render_to_response('quiz.html', locals())


template
========

Just change this:
    {% for form in forms %}
to this:
    {% for form in formset.forms %}

More like this

  1. Complex Formsets by smagala 5 years, 2 months ago
  2. Complex Form Preview by smagala 5 years ago
  3. Arbitrary length formset by Rupe 4 years, 7 months ago
  4. Class Feeds DRY TemplateTag by gmandx 3 years, 11 months ago
  5. "Dynamic" form with a hidden field by flaxter 5 years ago

Comments

ActionScripted (on February 15, 2011):

This snippet has made my evening. I've been crawling the search engines, #django IRC, etc. and no one was able to help at all (god bless 'em for trying).

Thanks to anyone and everyone who contributed, especially you smagala. I used this to create a set of formsets so that we had a voting system on our site where you have categories that have questions that have choices that are either something the user chooses from a drop-down or "nominates" by using an empty text box next to the question they'd like to nominate a choice for.

It's always easy to do things in Django once you learn how, but sometimes finding the how takes a few days... :D

#

(Forgotten your password?)