Fieldsets for forms
|Reported by:||Petr Marhoun <petr.marhoun@…>||Owned by:||nobody|
|Cc:||apollo13, tolano, carljm, obeattie, mmitar@…, net147, django@…, bmispelon@…, riccardo.magliocchetti@…, lingfish||Triage Stage:||Accepted|
|Has patch:||yes||Needs documentation:||yes|
|Needs tests:||no||Patch needs improvement:||yes|
This patch and ticket try to add support for simple and flexible definitions of fieldsets in newforms. They would be defined through inner class "Meta" (as in models and ModelForm). There is an side effect - more flexible forms without fieldsets (for example #3111 could be fixed).
Form as a pool of fields
I see form as a pool of fields declared as class attributes in the form and its bases - in the reversed order from mro. In a model form fields from the last model (the first class in mro with a option "model" set) are before all declared fields.
From the pool some fields are selected - through options "fieldsets", "fields" and "exclude". There is a simple rule: If the option "fieldsets" is set, use it. Otherwise if the option "fields" is set, used it. Otherwise if the option "exclude" is set, used it. Otherwise use all fields from the pool.
If no option is set, fields are in the order from the pool. If the option "exclude" is set, all fields not in the option (which is a list of field names) are used in the order from the pool. If the option "fields" is set, all fields in the option (which is a list of field names) are used in the order from the option.
The option "fieldsets" is a dictionary with "fields" (which is a list of field names), "legend" (optional, text for the legend tag in the generated html), "attrs" (optional, html attributes for the fieldset tag in the generated html) and "html_output_method" (optional, described later). If the option is set, all fields from "fields" are used in the order from the value.
Inner class "Meta"
Forms can have inner class "Meta" with some options. Usual forms can have options "fieldsets", "fields" and "exclude", model forms can have all options from usual forms and also "model" and "formfield_for_dbfield".
Option "formfield_for_dbfield" can be function or method (it is normalized as a function) with two arguments - options and a dbfield. It should return a formfield for the dbfield (or nothing, if the dbfield should not be used). The default value is "lambda self, dbfield: dbfield.formfield()". It is called during the construction of base fields for the form.
In the last week there was a new documentation commit for model forms inheritance. I have decided for another approach (it was before it) so I would try to describe the difference.
I would prefer the "DRY" approach - it would be more simple to have one base form with non-default options that many forms inherits from it. But it is no problem to change it to the "Explicit" approach, other parts of the API and of the patch don't depend on it.
class FormOne(forms.Form): a = forms.CharField() class Meta: alpha = 'an option' class FormTwo(forms.Form): b = forms.IntegerField() class Meta: beta = 'another option' class FormThree(FormOne, FormTwo): a = forms.IntegerField() class Meta(FormOne.Meta, FormTwo.Meta): beta = 'last option'
class FormOne(forms.Form): a = forms.CharField() class Meta: alpha = 'an option' class FormTwo(forms.Form): b = forms.IntegerField() class Meta: beta = 'another option' class FormThree(FormOne, FormTwo): a = forms.IntegerField() class Meta: beta = 'last option'
Important changes in BaseForm
- Methods html_output, as_table, as_ul and as_p are changed to generate html described by options.
- There are three new methods for simplification of the html_output method (they can be overwrites by a subclass) - top_errors_html_output, fieldset_html_output, hidden_fields_html_output. The method fieldset_html_output could be replaced by a "html_output_method" from the option "fieldsets" - it would be not possible to have different generated html for different fieldsets without it.
- There are three new methods for template authors - has_fieldsets, first_fieldset_attributes and first_fieldset_legend_tag. They are necessary for general templates - they can't be parts of generated html because tags for the first table and first fieldset are not generated.
Important changes in Widget
- There is a new attribute row_attrs - it describes attributes which would be not used by a widget directly, but could be used as row attributes.
- There are new methods label_tag, for_table, for_ul and for_p - for flexibility of generated html and for simplification.
Important changes in BoundField
- There are new properties - label, help_text and row_attrs. Method label_tag is changed to be a property and to be more powerful and less flexible. These properties are used by BaseForm.html_output and they call new widget methods sometimes.
There are some backward-incompatible changes. It seems to be a long list - but it should be almost no difference for most developers.
- Semantics of ModelForm with options "fields" and "exclude" is changed - the options "exclude" is ignored now.
- There can be changes of fields ordering in forms with multiply inheritance - it should be more logical now.
- Inheritance for inner class "Meta" is changed - but it can be reverted.
- DeclarativeFieldsMetaclass is renamed to FormMetaclass.
- Argument formfield_callback from ModelFormMetaclass is replaced by the option "formfield_for_dbfield".
- Widgets with positional arguments could be broken - row_attrs are the second positional arguments now.
- Hidden fields have their own row - it is not possible to know where is a place for the hidden fields in the last row if a widget returns custom html.
- There is no "\n" between errors and paragraphs in the html generated by as_p methods - it is more consistent with the other output methods.
- Method BoundField.label_tag is changed so it could break code which used it directly - it simplifies code, method label_tag is complex and not documented.
I dislike BaseForm and BaseModelForm. Why they are necessary? In the past there were no fields in model forms - but it is not true now. What if Form and ModelForm have metaclasses and BaseForm and BaseModelForm would be only deprecated names for them?
More fields in a row
In newforms-admin it is possible to have more fields in a row. But I don't know how to describe syntax (attributes for the row and attributes for a field) and I don't know what to generate. There is a more general possibility in the patch - through methods for_table, for_ul and for_p from Widget.
Change History (38)
comment:1 Changed 8 years ago by Petr Marhoun <petr.marhoun@…>
- Needs documentation unset
- Needs tests unset
- Patch needs improvement unset
comment:4 Changed 8 years ago by edgarsj
- Keywords feature added
- Needs documentation set
- Patch needs improvement set
- Triage Stage changed from Unreviewed to Design decision needed
Changed 6 years ago by mitar
Changed 6 years ago by mitar
comment:18 Changed 5 years ago by carljm
- Triage Stage changed from Design decision needed to Accepted
comment:23 Changed 19 months ago by collinanderson
- Summary changed from Fieldsets for newforms to Fieldsets for forms