Forms#

How do I supply a custom parser for a field?#

Pass a callable to the parse member of the field:

form = Form(
    auto__model=Track,
    fields__index__parse=lambda field, string_value, **_: int(string_value[:-3]),
)

How do I make a field non-editable?#

There are two cases: A) non-editable and you want to show the value to the user, or B) non-editable but do not show it (“hardcoded”).

A) Show the value#

Pass a callable or bool to the editable member of the field:

form = Form(
    auto__model=Album,
    fields__name__editable=lambda request, **_: request.user.is_staff,
    fields__artist__editable=False,
)

For a normal user:

▼ Hide result

For a staff user:

▼ Hide result

B) Hardcode the value#

A common use case is to navigate to some object, then create a sub-object. In this example we have a url like /artists/Black Sabbath/, where the artist name is parsed into an Artist instance by an iommi path decoder.

Then under that we have /artists/Black Sabbath/create_album/, and in this form, we don’t want to make the user choose Black Sabbath again. We accomplish this with the hardcoded shortcut:

form = Form.create(
    auto__model=Album,
    fields__artist=Field.hardcoded(
        parsed_data=lambda params, **_: params.artist,
    ),
)
▼ Hide result

How do I make an entire form non-editable?#

This is a very common case so there’s a special syntax for this: pass a bool to the form:

form = Form.edit(
    auto__instance=album,
    editable=False,
)
▼ Hide result

How do I supply a custom validator?#

Pass a callable that has the arguments form, field, and parsed_data. Return a tuple (is_valid, 'error message if not valid').

form = Form.create(
    auto__model=Album,
    auto__include=['name'],
    fields__name__is_valid=lambda form, field, parsed_data, **_: (
        parsed_data == 'only this value is valid',
        'invalid!',
    ),
)
▼ Hide result

You can also raise ValidationError:

def name_is_valid(form, field, parsed_data, **_):
    if parsed_data != 'only this value is valid':
        raise ValidationError('invalid!')

form = Form.create(
    auto__model=Album,
    auto__include=['name'],
    fields__name__is_valid=name_is_valid,
)
▼ Hide result

How do I validate multiple fields together?#

Refine the post_validation hook on the form. It is run after all the individual fields validation has run. But note that it is run even if the individual fields validation was not successful.

How do I exclude a field?#

See How do I say which fields to include when creating a form from a model?

How do I say which fields to include when creating a form from a model?#

Form() has four methods to select which fields are included in the final form:

  1. the auto__include parameter: this is a list of strings for members of the model to use to generate the form.

  2. the auto__exclude parameter: the inverse of include. If you use this the form gets all the fields from the model excluding the ones with names you supply in exclude.

  3. for more advanced usages you can also pass the include parameter to a specific field like fields__my_field__include=True. Here you can supply either a bool or a callable like fields__my_field__include=lambda request, **_: request.user.is_staff.

  4. you can also add fields that are not present in the model by passing configuration like fields__foo__attr='bar__baz' (this means create a Field called foo that reads its data from bar.baz). You can either pass configuration data like that, or pass an entire Field instance.

How do I supply a custom initial value?#

Pass a value or callable to the initial member:

form = Form(
    auto__model=Album,
    fields__name__initial='Paranoid',
    fields__year__initial=lambda field, form, **_: 1970,
)
▼ Hide result

If there are GET parameters in the request, iommi will use them to fill in the appropriate fields. This is very handy for supplying links with partially filled in forms from just a link on another part of the site.

How do I set if a field is required?#

Normally this will be handled automatically by looking at the model definition, but sometimes you want a form to be more strict than the model. Pass a bool or a callable to the required member:

form = Form.create(
    auto__model=Album,
    fields__name__required=True,
    fields__year__required=lambda field, form, **_: True,
)
▼ Hide result

To show the field as required before posting, you can add a CSS class rendering to your style definition:

IOMMI_DEFAULT_STYLE = Style(
    bootstrap,
    Field__attrs__class__required=lambda field, **_: field.required,
)

…and this CSS added to your sites custom style sheet:

.required label:after {
    content: " *";
    color: red;
}

For the following result:

▼ Hide result

See the style docs for more information on defining a custom style for your project.

How do I change the order of the fields?#

You can change the order in your model definitions as this is what iommi uses. If that’s not practical you can use the after member. It’s either the name of a field or an index. There is a special value LAST to put a field last.

from iommi import LAST

form = Form(
    auto__model=Album,
    fields__name__after=LAST,
    fields__year__after='artist',
    fields__artist__after=0,
)
▼ Hide result

This will make the field order artist, year, name.

If there are multiple fields with the same index or name the order of the fields will be used to disambiguate.

How do I specify which model fields the search of a choice_queryset use?#

Form.choice_queryset uses the registered search fields for filtering and ordering. See Registrations for how to register one. If present it will default to a model field name.

In special cases you can override which attributes it uses for searching by specifying search_fields:

form = Form(
    auto__model=Album,
    fields__name__search_fields=('name', 'year'),
)

This last method is discouraged though, because it will mean searching behaves differently in different parts of your application for the same data.

How do I insert a CSS class or HTML attribute?#

See Attrs.

How do I override rendering of an entire field?#

Pass a template name:

form = Form(
    auto__model=Album,
    fields__year__template='my_template.html',
)
▼ Hide result

or a Template object:

form = Form(
    auto__model=Album,
    fields__year__template=Template('This is from the inline template'),
)
▼ Hide result

How do I override rendering of the input field?#

Pass a template name or a Template object to the input namespace:

form = Form(
    auto__model=Album,
    fields__year__input__template='my_template.html',
)
▼ Hide result
form = Form(
    auto__model=Album,
    fields__year__input__template=Template('This is from the inline template'),
)
▼ Hide result

How do I change how fields are rendered everywhere in my project?#

Define a custom style and override the appropriate fields. For example here is how you could change Field.date to use a text based input control (as opposed to the date picker that input type='date' uses).

my_style = Style(bootstrap, Field__shortcuts__date__input__attrs__type='text')

When you do that you will get English language relative date parsing (e.g. “yesterday”, “3 days ago”) for free, because iommi used to use a text based input control and the parser is applied no matter what (its just that when using the default date picker control it will always only see ISO-8601 dates).

▼ Hide result

How do I change where the form redirects to after completion?#

iommi by default redirects to .. after edit/create/delete. You can override this via two methods:

  • extra__redirect_to: a string with the url to redirect to. Relative URLs also work.

  • extra__redirect: a callable that gets at least the keyword arguments request, redirect_to, form.

Form that after create redirects to the edit page of the object:

form = Form.create(
    auto__model=Album,
    extra__redirect=lambda form, **_: HttpResponseRedirect(form.instance.get_absolute_url() + 'edit/'),
)

Form that after edit stays on the edit page:

form = Form.edit(
    auto__instance=album,
    extra__redirect_to='.',
)

How do I make a fields choices depend on another field?#

The contents of the form is sent with any AJAX requests, so we can access the value of the other fields to do the filtering:

def album_choices(form, **_):
    if form.fields.artist.value:
        return Album.objects.filter(artist=form.fields.artist.value)
    else:
        return Album.objects.all()
Form(
    auto__model=Track,
    fields__artist=Field.choice_queryset(
        attr=None,
        choices=Artist.objects.all(),
        after=0,
    ),
    fields__album__choices=album_choices,
)

How do I enable a reverse foreign key relationship?#

By default reverse foreign key relationships are hidden. To turn it on, pass include=True to the field. Note that these are read only, because the semantics of hijacking another models foreign keys would be quite weird.

f = Form(
    auto__instance=artist,
    fields__albums__include=True,
)
▼ Hide result

How do I set an initial value on a field that is not in the form?#

You do have to include the field, but you can make it not rendered by using the non_rendered shortcut and setting initial.

f = Form.create(
    auto__model=Album,
    fields__artist=Field.non_rendered(initial=black_sabbath),
    fields__year=Field.non_rendered(initial='1980'),
)
▼ Hide result

If you post this form you will get this object:

▼ Hide result

By default this will be non-editable, but you can allow editing (via the URL GET parameters) by setting editable=True.

f = Form.create(
    auto__model=Album,
    fields__artist=Field.non_rendered(initial=black_sabbath),
    fields__year=Field.non_rendered(
        initial='1980',
        editable=True,
    ),
)
► Show result

Accessing this create form with ?year=1999 in the title will create this object on submit:

▼ Hide result

How do I group fields?#

Use the group field:

form = Form(
    auto__model=Album,
    fields__year__group='metadata',
    fields__artist__group='metadata',
)
▼ Hide result

How do I show a reverse many-to-many relationship?#

By default reverse many-to-many relationships are hidden. To turn it on, pass include=True to the field:

form = Form(
    auto__model=Genre,
    instance=heavy_metal,
    fields__albums__include=True,
)
▼ Hide result

How do I nest multiple forms?#

class MyNestedForm(Form):
    edit_dio = Form.edit(auto__instance=Artist.objects.get(name='Black Sabbath'))
    create_artist = Form.create(auto__model=Artist)
    create_album = Form.create(auto__model=Album)

    class Meta:
        actions__submit__post_handler = save_nested_forms
▼ Hide result
heaven_and_hell = album

How do I use templates for fields?#

Sometimes field groups just aren’t enough and you may want to use a template to make your forms pretty:

class CommentForm(Form):
    class Meta:
        # language=html
        fields_template = Template(
            {{ fields.album.input }}
            <div class="row">
                <div class="col">
                    {{ fields.name }}
                </div>
                <div class="col">
                    {{ fields.email }}
                </div>
            </div>
            {{ fields.comment }}
        )

    name = Field()
    email = Field()
    comment = Field.textarea()
    album = Field.hardcoded(initial=heaven_and_hell)
▼ Hide result