menu

Formsets and Inlines

PRO Only

Formsets and Inlines support based on the idea from the django-superform project. Formsets and Inlines are included and proceed as a normal django form fields.

This approach keeps your view code free from form-specific details and allows to use same templating technics from django-material form as for all rest fields.

Due to inactivity of django-superforms project, code absorbed into django-material and now supported as part of django-material Pro distribution.

Example:

To use Formset and Inlines field you have to inherit from material.forms.Form:

from django import forms
from django.forms import formset_factory
from material.forms import Form

class AddressForm(forms.Form):
    line_1 = forms.CharField(max_length=250)
    line_2 = forms.CharField(max_length=250)
    state = forms.CharField(max_length=100)
    city = forms.CharField(max_length=100)
    zipcode = forms.CharField(max_length=10)

    layout = Layout(
        'line_1',
        'line_2',
        'state',
        Row('city', 'zipcode'),
    )

AddressFormSet = formset_factory(AddressForm, extra=3, can_delete=True)


class SignupForm(Form):
    username = forms.CharField(max_length=50)
    first_name = forms.CharField(max_length=250)
    last_name = forms.CharField(max_length=250)
    emails = FormSetField(formset_class=EmailFormSet)
    addresses = FormSetField(formset_class=AddressFormSet)

    layout = Layout(
        'username',
        Row('first_name', 'last_name'),
        'emails',
        Stacked(1, 'addresses'),
    )

API

class material.forms.Form(*args, **kwargs)

The base class for all material forms. The goal of a material form is to behave just like a normal django form but is able to take composite fields, like FormField and FormSetField. Cleaning, validation, etc. should work totally transparent.

class material.forms.ModelForm(*args, **kwargs)

The ModelForm works like a Django ModelForm but has the capabilities of nesting like Form. Saving a ModelForm will also save all nested model forms as well.

class material.forms.FormField(form_class, kwargs=None, **field_kwargs)

A field that can be used to nest a form inside another form:

from django import forms
from material.forms import Form

class AddressForm(forms.Form):
    street = forms.CharField()
    city = forms.CharField()

class RegistrationForm(Form):
    first_name = forms.CharField()
    last_name = forms.CharField()
    address = FormField(AddressForm)

The fields will all have a prefix in their name so that the naming does not clash with other fields on the page. The name attribute of the input tag for the street field in this example will be: form-address-street. The name will change if you set a prefix on the material form:

form = RegistrationForm(prefix='registration')

Then the field name will be registration-form-address-street. You can pass the kwargs argument to the __init__ method in order to give keyword arguments that you want to pass through to the form when it is instaniated. So you could use this to pass in initial values:

class RegistrationForm(Form):
    address = FormField(AddressForm, kwargs={
        'initial': {'street': 'Stairway to Heaven 1'}
    })

But you can also use nested initial values which you pass into the material form:

RegistrationForm(initial={
    'address': {'street': 'Highway to Hell 666'}
})

The first method (using kwargs) will take precedence.

class material.forms.ModelFormField(form_class, kwargs=None, **field_kwargs)

This class is the to FormField what Django’s ModelForm is to Form. It has the same behaviour as FormField but will also save the nested form if the material form is saved. Here is an example:

from material.forms import ModelFormField

class EmailForm(forms.ModelForm):
    class Meta:
        model = EmailAddress
        fields = ('email',)
class UserForm(ModelForm):
    email = ModelFormField(EmailForm)
    class Meta:
        model = User
        fields = ('username',)
user_form = UserForm(
    {'username': 'john', 'form-email-email': 'john@example.com'})
if user_form.is_valid():
    user_form.save()

This will save the user_form and create a new instance of User model and it will also save the EmailForm and therefore create an instance of EmailAddress! However you usually want to use one of the exsting subclasses, like

ForeignKeyFormField or extend from ModelFormField class and override the get_instance() method. .. note:

Usually the :class:`~material.forms.fields.ModelFormField` is used
inside a :class:`~material.forms.ModelForm`. You actually
can use it within a :class:`~material.forms.Form`, but
since this form type does not have a ``save()`` method, you will need
to take care of saving the nested model form yourself.
class material.forms.FormSetField(formset_class, kwargs=None, **field_kwargs)

First argument is a formset class that is instantiated by this FormSetField. You can pass the kwargs argument to specify kwargs values that are used when the formset_class is instantiated.

class material.forms.InlineFormSetField(parent_model=None, model=None, formset_class=None, kwargs=None, **factory_kwargs)

The InlineFormSetField helps when you want to use a inline formset.

You can pass in either the keyword argument formset_class which is a ready to use formset that inherits from BaseInlineFormSet or was created by the inlineformset_factory. The other option is to provide the arguments that you would usually pass into the inlineformset_factory. The required arguments for that are:

model

The model class which should be represented by the forms in the formset.

parent_model

The parent model is the one that is referenced by the model in a foreignkey.

form (optional)

The model form that is used as a baseclass for the forms in the inline formset.

You can use the kwargs keyword argument to pass extra arguments for the formset that are passed through when the formset is instantiated. All other not mentioned keyword arguments, like extra, max_num etc. will be passed directly to the inlineformset_factory. Example:

class Gallery(models.Model):
    name = models.CharField(max_length=50)

class Image(models.Model):
    gallery = models.ForeignKey(Gallery)
    image = models.ImageField(...)

class GalleryForm(ModelFormWithFormSets):
    images = InlineFormSetField(
        parent_model=Gallery, model=Image, extra=1)

    class Meta:
        model = Gallery
        fields = ('name',)