Form ==== Base class: :doc:`Part` Describe a Form. Example: .. code-block:: python class MyForm(Form): a = Field() b = Field.email() form = MyForm().bind(request=request) You can also create an instance of a form with this syntax if it's more convenient: .. code-block:: python form = Form( fields=dict( a=Field(), b=Field.email(), ), ).bind(request=request) In the common case the fields namespace will contain only instances of `Field`, but iommi actually supports arbitrary `Part` objects. For example: .. code-block:: python form = Form( fields=dict( # Display a and b inside a box box=html.div( attrs__class__box=True, children__a=Field(), children__b=Field.email(), ), # And c regularly c=Field(), ) ) So that writing the application logic (e.g. validation and post handlers) is independent of minor changes to the layout, after bind the `fields` namespace of the form will contain only instances of `Field` keyed by their `_name` independently of how deep they are in the hierarchy. Given the above, an appropriate post_handler would be: .. code-block:: python def post_handler(form, **_): if not form.is_valid(): return print(form.fields.a.value, form.fields.b.value, form.fields.c.value) # And not: # print(form.fields.box.a.value, form.fields.box.b.value, form.fields.c.value) Use of Form.layout: .. code-block:: python from django.contrib.auth import get_user_model from django.utils.translation import gettext_lazy from iommi._web_compat import Template class UserForm(Form): class Meta: auto__model = get_user_model() layout = Panel(dict( p_main=Panel.fieldset(dict( username=Panel.field(), p_fullname=Panel.row(dict( first_name=Panel.field(col__attrs__class__firstname=True), last_name=Panel.field(), )), # examples of custom html parts: p_foo=Panel.row(dict( p_bar=gettext_lazy('Some plain text'), p_baz=Template('{% load i18n %}{% translate "Foobar!" %}'), )), p_qux=Fragment(template='path/to/template.html'), p_quux=html.h3('Quux'), p_corge__template='path/to/another_template.html', )), p_permissions=Panel.fieldset(dict( # ... )), p_error=Panel.alert('Error!', level='error'), )) .. _form-save-callbacks: Save callbacks ~~~~~~~~~~~~~~ Saving a model in Django mirrors SQL quite closely, and `Form.create`/`Form.edit` have hooks for all the steps in a multi-step commit. They are called in this order: - `extra__new_instance`: called to create a new instance of the model. By default it just calls `form.model()`. - `extra__save`: called to save each `model_object`. By default it calls `model_object.save()`. Note that the keyword argument is `model_object`, *not* `instance`, and that it is called once per model when the form spans more than one (e.g. `auto__include=['name', 'artist__name']`), so always check `isinstance` before applying model specific behavior. - `extra__pre_save_all_but_related_fields` (only called for `Form.create`) - `extra__on_save_all_but_related_fields` (only called for `Form.create`) - `extra__pre_save` (before `instance.save()`) - `extra__on_save` (after `instance.save()`) Note that `Form.create` saves twice, because Django needs a pk before related fields can be written. The first save happens between `pre_save_all_but_related_fields` and `on_save_all_but_related_fields`, which means `extra__pre_save` runs *after* the row already exists and is too late to fill in a `NOT NULL` column. `Form.edit` saves once, before `extra__on_save`. After a POST completes, `extra__redirect` is called if present, otherwise `extra__redirect_to` determines where to redirect to. See :ref:`the cookbook ` for worked examples. Refinable members ----------------- `action_class` ^^^^^^^^^^^^^^ Type: `type[Action]` Cookbook: :ref:`custom-member-class` `actions` ^^^^^^^^^ Type: `Namespace` Cookbook: :ref:`form-actions` `actions_template`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `str | Template` Default: `iommi/form/actions.html` Cookbook: :ref:`form-actions` `after`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `int | str` See :ref:`after ` `assets` ^^^^^^^^ Type: `Namespace` See :ref:`assets ` `attr`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `str` See :ref:`attr ` `attrs`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: :doc:`Attrs` See :ref:`attributes ` Cookbook: :ref:`freetext-column` `auto` ^^^^^^ Type: :doc:`FormAutoConfig` See :ref:`auto ` Cookbook: :ref:`include-exclude-fields` :ref:`reverse-fk-form` `editable` ^^^^^^^^^^ Type: `bool` Default: `True` Cookbook: :ref:`field-non-editable` `endpoints` ^^^^^^^^^^^ Type: `Namespace` See :ref:`endpoints ` `errors` ^^^^^^^^ Type: `Errors` Cookbook: :ref:`field-hidden` `extra` ^^^^^^^ Type: `dict[str, Any]` See :ref:`extra ` Cookbook: :ref:`form-save-hooks` :ref:`form-redirect` `extra_evaluated` ^^^^^^^^^^^^^^^^^ Type: `dict[str, Any]` See :ref:`extra ` `extra_params` ^^^^^^^^^^^^^^ See :ref:`extra_params ` `field_group` ^^^^^^^^^^^^^ Type: `Namespace` Cookbook: :ref:`group-fields` `fields` ^^^^^^^^ Type: `Namespace` Cookbook: :ref:`dependent-fields` :ref:`nested-forms` `fields_template`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `str | Template` Cookbook: :ref:`fields-templates` `h_tag`     (:ref:`evaluated `) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `Fragment | str` See :ref:`title ` `include`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `bool` See :ref:`include <include>` `instance` ^^^^^^^^^^ Type: `Any` Cookbook: :ref:`form-save-hooks` :ref:`create-or-edit-forms` `iommi_style` ^^^^^^^^^^^^^ Type: `str | Style | None` See :ref:`iommi_style <iommi_style>` `layout`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `Panel | None` Cookbook: :ref:`field-write-to-instance` `layout_render_unused_fields`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `bool` Default: `False` Cookbook: :ref:`field-write-to-instance` `layout_template`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `str | Template | None` Default: `lambda form, **_: 'iommi/form/layout.html' if form.layout is not None else None` Cookbook: :ref:`field-write-to-instance` `member_class` ^^^^^^^^^^^^^^ Type: `type[Field]` Cookbook: :ref:`custom-member-class` `model`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `type[Model] | None` Cookbook: :ref:`form-model-introspection` `page_class` ^^^^^^^^^^^^ Type: `type[Page]` Cookbook: :ref:`custom-member-class` `post_validation` ^^^^^^^^^^^^^^^^^ Cookbook: :ref:`validate-multiple-fields-together` `read_nested_form_from_instance` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Read the nested forms instance from the parent forms instance. This is analogous to `Field.read_from_instance` but for nested forms. Cookbook: :ref:`nested-forms` `template`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `str | Template` Default: `iommi/form/form.html` See :ref:`template <template>` `title`     (:ref:`evaluated <evaluate>`) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type: `Fragment | str` See :ref:`title <title>` `write_nested_form_to_instance` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Write the nested_form to the instance. This is analogous to `Field.write_to_instance` but for nested forms. Cookbook: :ref:`nested-forms` Shortcuts --------- `Form.create` ^^^^^^^^^^^^^ Parent: Form.crud_ Defaults ++++++++ * `actions__submit__post_handler` * `iommi.form.create_object__post_handler` * `extra__is_create` * `True` `Form.create_or_edit` ^^^^^^^^^^^^^^^^^^^^^ Parent: Form.crud_ Defaults ++++++++ * `actions__submit__post_handler` * `iommi.form.create_or_edit_object__post_handler` * `extra__is_create` * `None` Cookbook: :ref:`create-or-edit-forms` `Form.crud` ^^^^^^^^^^^ Defaults ++++++++ * `extra__pre_save_all_but_related_fields` * `lambda **kwargs: None, # pragma: no mutate` * `extra__on_save_all_but_related_fields` * `lambda **kwargs: None, # pragma: no mutate` * `extra__pre_save` * `lambda **kwargs: None, # pragma: no mutate` * `extra__on_save` * `lambda **kwargs: None, # pragma: no mutate` * `extra__on_delete` * `lambda **kwargs: None, # pragma: no mutate` * `extra__redirect` * `lambda redirect_to, **_: HttpResponseRedirect(redirect_to)` * `extra__redirect_to` * `None` * `extra__crud_type` * `lambda form, **_: 'create' if form.instance is None else 'edit'` * `extra__new_instance` * `lambda form, **_: form.model()` * `auto` * `Namespace()` `Form.delete` ^^^^^^^^^^^^^ Parent: Form.crud_ Defaults ++++++++ * `actions__submit__call_target__attribute` * `delete` * `actions__submit__post_handler` * `iommi.form.delete_object__post_handler` * `extra__crud_type` * `delete` * `editable` * `False` * `fields__iommi_default_text` * `{'call_target': <class 'iommi.fragment.Fragment'>, 'include': <function Form.<lambda> at 0x7de47bad7f60>, 'after': 0, 'tag': 'p', 'children__text': <function Form.<lambda> at 0x7de47badc540>}` `Form.edit` ^^^^^^^^^^^ Parent: Form.crud_ Defaults ++++++++ * `actions__submit__post_handler` * `iommi.form.edit_object__post_handler` * `extra__is_create` * `False` Methods ------- `add_error` ^^^^^^^^^^^ Explicitly add an error message to the form's global error set. Example: .. code-block:: python def post_validation(form, **_): form.add_error('global error') form = Form.create( auto__model=Album, post_validation=post_validation, ) .. raw:: html <div class="iframe_collapse" onclick="toggle('f15fe554-150f-4240-8cf1-9cbe8b3cd12d', this)">▼ Hide result</div> <iframe id="f15fe554-150f-4240-8cf1-9cbe8b3cd12d" src="doc_includes/Form/test_base.html" style="background: white; display: ; width: 100%; min-height: 100px; border: 1px solid gray;"></iframe> <div class="show_structure_toggler"><span onclick="toggle_iframe_structure_overlay()">Toggle structure</span></div> `apply` ^^^^^^^ Write the new values specified in the form into the instance specified. `as_view` ^^^^^^^^^ `get_errors` ^^^^^^^^^^^^ Get a dict containing two keys: - `global` for errors global to the entire form. - `fields` for errors specific to fields. This is itself a dict with a key for each field. `get_field` ^^^^^^^^^^^ `get_nested_form` ^^^^^^^^^^^^^^^^^ `is_target` ^^^^^^^^^^^ `is_valid` ^^^^^^^^^^ Is the form valid? Can be called inside forms post_validation hook to determine if the individual fields were all valid. `next_value` ^^^^^^^^^^^^ `on_bind` ^^^^^^^^^ `on_refine_done` ^^^^^^^^^^^^^^^^ `own_evaluate_parameters` ^^^^^^^^^^^^^^^^^^^^^^^^^ `validate` ^^^^^^^^^^ Static methods -------------- `apply_field` ^^^^^^^^^^^^^ Class methods ------------- `fields_from_model` ^^^^^^^^^^^^^^^^^^^