Login

Multiple Model Forms feeding a single Form to use with a single FormView

Author:
AgustinLado
Posted:
October 2, 2014
Language:
Python
Version:
Not specified
Score:
1 (after 1 ratings)

This is a simple example of feeding multiple Forms into a single Form via its constructor method, to work with a single FormView and reap the benefits of Django's awesome Form validation system. It's a Form class that defines (or takes via an argument to its constructor) parent Forms (that can, for instance, be ModelForms, to take advantage of the automatic Field generation) and takes its fields from there.

An advanced user won't be impressed by this, so excuse if this snippet is out of place, but a rather inexperienced user such as myself might find it interesting and make him willing to explore Django's internals a bit more.

 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
class ActualForm(forms.Form):
    """
    This form's fields are the sum of the fields defined by its parent forms.

    By creating this form dynamically I'm able to take advantage of the
    ModelForms functionality of automatically setting the Model's fields
    while still ending up with a form that comprises multiple models.

    The Parent forms are specified in the :attr: parent_forms
    or received in the constructor as :arg: parent_forms
    """
    parent_forms = [SomeForm1, SomeForm2]

    def __init__(self, parent_forms=[], *args, **kwargs):
        super(ActualForm, self).__init__(*args, **kwargs)

        # If the class was instantiated with a list of parent Forms as
        # an argument, use said list. Else look for a hard-coded attribute.
        # This line is hilarious.
        parent_forms = parent_forms if parent_forms else self.parent_forms

        # Make a list of form fields. The list comprehension makes a
        # nested list that gets flattened by itertools' chain
        form_values = list(itertools.chain(
            *[form().fields.iteritems() for form in parent_forms]
        ))
        # Add the values of the parent forms to the actual form
        for k, v in form_values:
            self.fields[k] = v


# In Views:
class ActualFormView(FormView):
    """
    Use the Class Based View as always, but make sure to set
    form_class to be the dynamic Form.
    """
    form_class = ActualForm

    def form_valid(self, form):
        # Create objects. Do stuff.
        return super(ActualFormView, self).form_valid(form)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

AgustinLado (on October 2, 2014):

I got confused by an Internal Server Error after posting the snippet and tried again, failing to notice it had already been created. I'm sorry! Delete this duplicate, please.

#

astarrh (on July 30, 2017):

I am a fellow inexperienced user and this saved my life, basically. Thanks for sharing.

Be advised: in Python 3:

form().fields.iteritems()

should be changed to:

form().fields.items().

#

Nikey (on November 10, 2020):

In the line super(ActualForm, self).__init__(*args, **kwargs) I get the following error. TypeError: init() got an unexpected keyword argument 'instance'

Other places I've searched about it didn't help. Please help. Django 3.1 Python 3.9

#

Please login first before commenting.