Forms¶
How do I specify which fields to show?¶
Pass include=False
to hide the field or include=True
to show it. By default fields are shown, except the primary key field that is by default hidden. You can also pass a callable here like so:
Form.create(
auto__model=Album,
fields__name__include=
lambda request, **_: request.GET.get('some_parameter') == 'hello!',
)
This will show the field name
only if the GET parameter some_parameter
is set to hello!
.
To be more precise, include
turns off the entire field. See How do I make a field non-editable? and How do I create a hidden field?
Use auto__include
, to specify the complete list of fields you want:
form = Form.create(
auto__model=Album,
auto__include=['name', 'artist'],
)
Instead of using auto__include
, you can also use auto__exclude
to just exclude the fields you don’t want:
form = Form.create(
auto__model=Album,
auto__exclude=['year'],
)
There is also a config option default_included
which is by default True
, which is where iommi’s default behavior of showing all fields comes from. If you set it to False
fields are now opt-in:
form = Form.create(
auto__model=Album,
auto__default_included=False,
# Turn on only the name field
fields__name__include=True,
)
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:
For a staff user:
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,
),
)
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,
)
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!',
),
)
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,
)
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:
the
auto__include
parameter: this is a list of strings for members of the model to use to generate the form.the
auto__exclude
parameter: the inverse ofinclude
. If you use this the form gets all the fields from the model excluding the ones with names you supply inexclude
.for more advanced usages you can also pass the
include
parameter to a specific field likefields__my_field__include=True
. Here you can supply either abool
or a callable likefields__my_field__include=lambda request, **_: request.user.is_staff
.you can also add fields that are not present in the model by passing configuration like
fields__foo__attr='bar__baz'
(this means create aField
calledfoo
that reads its data frombar.baz
). You can either pass configuration data like that, or pass an entireField
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,
)
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,
)
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:
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,
)
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',
)
or a Template
object:
form = Form(
auto__model=Album,
fields__year__template=Template('This is from the inline template'),
)
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',
)
form = Form(
auto__model=Album,
fields__year__input__template=Template('This is from the inline template'),
)
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).
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 argumentsrequest
,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=black_sabbath,
fields__albums__include=True,
)
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'),
)
If you post this form you will get this object:
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,
),
)
Accessing this create form with ?year=1999
in the title will create this object on submit:
How do I group fields?¶
Use the group
field:
form = Form(
auto__model=Album,
fields__year__group='metadata',
fields__artist__group='metadata',
)
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,
)
How do I nest multiple forms?¶
You need to use the save_nested_forms
post handler to have a single save button for all the nested forms and edit tables:
class MyNestedForm(Form):
edit_ozzy = Form.edit(
auto__model=Artist,
instance=lambda **_: Artist.objects.get(name='Ozzy Osbourne'),
)
create_artist = Form.create(auto__model=Artist)
edit_albums = EditTable(
auto__model=Album,
auto__include=['name', 'year'],
columns__name__field__include=True,
)
class Meta:
actions__submit__post_handler = save_nested_forms
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(parsed_data=lambda **_: Album.objects.get(name='Heaven & Hell'))
How do I make a field that depends on the choice in another field?¶
This only works for cases when the choices are fetched via ajax, but this is also the common case:
def album_choices(form, **_):
if form.fields.artist.value:
return Album.objects.filter(artist=form.fields.artist.value)
else:
return Album.objects.all()
form = Form(
auto__model=Track,
# First choose an artist
fields__artist=Field.choice_queryset(
attr=None,
choices=Artist.objects.all(),
after=0,
),
# Then choose an album
fields__album__choices=album_choices,
)
How do I make a create or edit form?¶
If you don’t know until runtime if you want Form.create
or Form.edit
,
you can use the Form.create_or_edit
shortcut. Ff the instance
is None
it will become a create form, otherwise an edit form:
form = Form.create_or_edit(
auto__model=Album,
)
Using a lambda for a create form:
form = Form.create_or_edit(
auto__model=Album,
instance=lambda **_: None,
)
Now an edit form:
form = Form.create_or_edit(
auto__model=Album,
instance=lambda **_: Album.objects.first(),
)