Forms
-----
.. _Field.parse:
How do I supply a custom parser for a field?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pass a callable to the `parse` member of the field:
.. code-block:: python
form = Form(
auto__model=Track,
fields__index__parse=lambda field, string_value, **_: int(string_value[:-3]),
)
.. _Field.editable:
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:
.. code-block:: python
form = Form(
auto__model=Album,
fields__name__editable=lambda request, **_: request.user.is_staff,
fields__artist__editable=False,
)
For a normal user:
.. raw:: html
▼ Hide result
For a staff user:
.. raw:: html
▼ 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:
.. code-block:: python
form = Form.create(
auto__model=Album,
fields__artist=Field.hardcoded(
parsed_data=lambda params, **_: params.artist,
),
)
.. raw:: html
▼ Hide result
.. _Form.editable:
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:
.. code-block:: python
form = Form.edit(
auto__instance=album,
editable=False,
)
.. raw:: html
▼ Hide result
.. _Field.is_valid:
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')`.
.. code-block:: python
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!',
),
)
.. raw:: html
▼ Hide result
You can also raise `ValidationError`:
.. code-block:: python
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,
)
.. raw:: html
▼ 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.
.. _Field.initial:
How do I supply a custom initial value?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pass a value or callable to the `initial` member:
.. code-block:: python
form = Form(
auto__model=Album,
fields__name__initial='Paranoid',
fields__year__initial=lambda field, form, **_: 1970,
)
.. raw:: html
▼ 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.
.. _Field.required:
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:
.. code-block:: python
form = Form.create(
auto__model=Album,
fields__name__required=True,
fields__year__required=lambda field, form, **_: True,
)
.. raw:: html
▼ Hide result
To show the field as required before posting, you can add a CSS class rendering to your style definition:
.. code-block:: python
IOMMI_DEFAULT_STYLE = Style(
bootstrap,
Field__attrs__class__required=lambda field, **_: field.required,
)
...and this CSS added to your sites custom style sheet:
.. code-block:: css
.required label:after {
content: " *";
color: red;
}
For the following result:
.. raw:: html
▼ Hide result
See the style docs for more information on defining a custom style for your project.
.. _Field.after:
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.
.. code-block:: python
from iommi import LAST
form = Form(
auto__model=Album,
fields__name__after=LAST,
fields__year__after='artist',
fields__artist__after=0,
)
.. raw:: html
▼ 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.
.. _Field.search_fields:
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 :doc:`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`:
.. code-block:: python
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 :doc:`Attrs`.
.. _Field.template:
How do I override rendering of an entire field?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pass a template name:
.. code-block:: python
form = Form(
auto__model=Album,
fields__year__template='my_template.html',
)
.. raw:: html
▼ Hide result
or a `Template` object:
.. code-block:: python
form = Form(
auto__model=Album,
fields__year__template=Template('This is from the inline template'),
)
.. raw:: html
▼ Hide result
.. _Field.input:
How do I override rendering of the input field?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pass a template name or a `Template` object to the `input` namespace:
.. code-block:: python
form = Form(
auto__model=Album,
fields__year__input__template='my_template.html',
)
.. raw:: html
▼ Hide result
.. code-block:: python
form = Form(
auto__model=Album,
fields__year__input__template=Template('This is from the inline template'),
)
.. raw:: html
▼ 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).
.. code-block:: python
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).
.. raw:: html
▼ 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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
def album_choices(form, **_):
if form.fields.artist.value:
return Album.objects.filter(artist=form.fields.artist.value)
else:
return Album.objects.all()
.. code-block:: python
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.
.. code-block:: python
f = Form(
auto__instance=artist,
fields__albums__include=True,
)
.. raw:: html
▼ 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`.
.. code-block:: python
f = Form.create(
auto__model=Album,
fields__artist=Field.non_rendered(initial=black_sabbath),
fields__year=Field.non_rendered(initial='1980'),
)
.. raw:: html
▼ Hide result
If you post this form you will get this object:
.. raw:: html
▼ Hide result
By default this will be non-editable, but you can allow editing (via the
URL `GET` parameters) by setting `editable=True`.
.. code-block:: python
f = Form.create(
auto__model=Album,
fields__artist=Field.non_rendered(initial=black_sabbath),
fields__year=Field.non_rendered(
initial='1980',
editable=True,
),
)
.. raw:: html
► Show result
Accessing this create form with `?year=1999` in the title will create this object on submit:
.. raw:: html
▼ Hide result
How do I group fields?
~~~~~~~~~~~~~~~~~~~~~~
Use the `group` field:
.. code-block:: python
form = Form(
auto__model=Album,
fields__year__group='metadata',
fields__artist__group='metadata',
)
.. raw:: html
▼ 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:
.. code-block:: python
form = Form(
auto__model=Genre,
instance=heavy_metal,
fields__albums__include=True,
)
.. raw:: html
▼ Hide result
How do I nest multiple forms?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
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
.. raw:: html
▼ Hide result
.. code-block:: python
heaven_and_hell = album
.. _Form.fields_template:
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:
.. code-block:: python
class CommentForm(Form):
class Meta:
# language=html
fields_template = Template(
{{ fields.album.input }}
{{ fields.name }}
{{ fields.email }}
{{ fields.comment }}
)
name = Field()
email = Field()
comment = Field.textarea()
album = Field.hardcoded(initial=heaven_and_hell)
.. raw:: html
▼ Hide result