At DjangoCon Europe 2011, Gregor Müllegger gave a great lightning talk about his work on a revised form rendering API:
http://www.scribd.com/doc/57270484/Djangocon-EU-2011-Revised-Form-Rendering-Lightning-Talk-by-Gregor-Mullegger I sat down with Gregor, Jannis, Russell, Alex, Ericflo, Andrew Godwin, and many other fine minds at the sprints to think about his proposal and help refine it. At the risk of starting a bikeshedding war, I'd like to share the results of the sprints and get some feedback on our proposed approach. I'm very pleased with the resulting API. It has one significant wart, but having chewed on the alternatives, I think there is consensus that this API represents a good compromise between purity and practical considerations. # Goals The existing rendering facilities for forms aren't bad, but they suffer from the following drawbacks: 1. All-or-nothing: if you'd like to customize one small part of form rendering, you have to go the whole hog and write out a ton of boilerplate. 2. "Frontend developers need not apply": large parts of form rendering happen in Python. If a frontend dev wants to customize certain aspects of form.as_p's output, they've got to bust out the python. 3. Prevents modularity and code re-use: with large bits of markup locked inside .py files, it's difficult for designers to share reusable form patterns. 4. DOCTYPE lock-in: designers are forced to use the XML style of tags, breaking validation of other doctypes when forms are present. The new form rendering API addresses these drawbacks, and as such, has the following desirable properties: 1. No backwards-incompatible changes: the new API coexists peacefully with the old one, allowing for a smooth transition and deprecation of the old formrendering API. 2. Simple beginner usage: if all you're comfortable using is {{ form.as_p }}, the new API retains a similarly brief approach. 3. Vastly better customizability: the new API provides an expressive and flexible means for specifying how forms should be rendered, and allows you to override just the parts that need changing. 4. Significant improvements to the rendering of form rows with multiple fields (for example, first and last name, or credit card and expiration). 5. Dogfooding: the new API does have some new tags, but a lot of the internals and examples for customization are just using the existing template system. This provides a lot of flexibility, but it also means that the new API should feel familiar to existing developers. 6. Modularity: it will be possible to produce reusable libraries of form rendering templates. # The anatomy of form rendering templates Forms are composed of three logical units: 1. A template which represents the form itself, including some form-specific configuration. 2. A template which represents a given "row" in a form. A row is an abstract concept, and doesn't have to represent a horizontal slice of anything (but usually does). The existing form rendering API produces rows in the shape of <p>'s, <li>'s, and <tr>'s. In the new form rendering API, Django supplies the formrow templates for all three of these row "types", but users can easily add new ones of these to create a formrow based on <div>'s, for example. 3. A template which represents a given widget in the form. These are the widgets which are currently provided by the framework, but supplied as templates instead of being buried in widgets.py. # Examples. I've prepared example templates for each of these; check out https://github.com/idangazit/formrendering after reading the following tutorial. These represent a good starting point for developing the form templates distributed with Django. # API Usage Tutorial The new API introduces the following template tags: - form - formconfig - formrow - formfield For the following examples, "myform" is a form instance present in the template's context. # {% form %} The new form rendering API introduces a {% form %} tag: {% form myform %} {# equivalent to the following line #} {% form myform using "forms/layouts/table.html" %} {% form myform using "forms/layouts/p.html" %} {% form myform using "forms/layouts/ul.html" %} In its simplest incarnation, it works like the existing form.as_XX methods. Users select a row type by means of the optional using argument. Django will need to supply the existing row types (table, p, ul) to ease the transition. Users can specify their own form templates using the same syntax: {% form myform using "fancy_div.html" %} {% form myotherform using "fancy_div.html" %} Like the include tag, form can pass context into its child template: {% form myform using "myform.html" with greeting="Hi!" %} {% form myform using "myform.html" with greeting="Hi!" only %} The form tag can also load a configuration inline, as follows: {% form myform using %} ... layout goes here ... {% endform %} The template contents that go inside a form block are identical to those that can be included from an external template file, as in the previous examples. The form tag has one more trick up its sleeve: it can take multiple forms as an argument: {% form myform1 myform2 using "forms/layouts/p.html" %} # Form Templates In the Django-supplied templates, these would be rendered sequentially, with form errors at the top of every form. However, users can write their own form templates which might mix fields from each form, and group both forms' errors at the top. The standard form layouts have a lot of commonality, and so they rely on template inheritance for DRYness: https://github.com/idangazit/formrendering/blob/master/forms/layouts/layout_base.html#L13-14 https://github.com/idangazit/formrendering/blob/master/forms/layouts/p.html Form layout templates are in the business of spitting out all of the fields in a form, one row at a time. This is usually accomplished with the {% formrow %} tag. ## {% formrow %} The formrow tag is responsible for producing a form's row, and is only valid within the scope of a form tag. The formrow tag takes an optional template. If a template is not specified, the default is used (table). Changing the template to produce <p>'s is accomplished thusly: {% for field in form %} {% formrow field using "forms/rows/p.html%} {% endfor %} Formrow, like form, can take multiple fields: {% formrow firstname lastname using "myfancynamerow.html" %} Django supplies the formrow templates for table, ul, and p. Like the form layout templates, they utilize inheritance to keep things DRY: https://github.com/idangazit/formrendering/blob/master/forms/rows/row_base.html https://github.com/idangazit/formrendering/blob/master/forms/rows/p.html https://github.com/idangazit/formrendering/blob/master/forms/rows/hidden.html Each form row is responsible for rendering the fields passed to it. ## {% formfield %} The formfield tag provides the ability to customize widget rendering for one or more fields. Usually, they are used in the context of a formrow template: https://github.com/idangazit/formrendering/blob/master/forms/rows/row_base.html#L11 The field templates are a replacement for the HTML currently buried in the render() methods of forms/widgets.py. Instead of building the HTML strings in python, widgets.py should avail itself of the templates to produce its output. The default usage of the tag will automatically select a relevant widget using the existing heuristics. Assuming a form with a CharField named "first_name": {% formfield first_name %} Will produce an <input> with the relevant properties. If customization is desired, users can write their own widget templates: {% formfield publish_date using "mywidgets/fancydatetimepicker.html" %} Bruno Renié's django-floppyforms (https://github.com/brutasse/django-floppyforms) is a lovely example of the kinds of things accomplishable with template-based form widgets. # Controlling default form rendering behavior Often, overriding a specific bit of form rendering behavior is desirable. For example: - You want to use a different widget for one specific field in your form. - You want to use a different widget for all fields of a specific type in your form. - You want to specify a different default row style for some or all fields in a form. To that end, there is the {% formconfig %} tag. It is only valid inside a {% form %} block (or when pulled in via a form layout template). It affects form state, and as such represents the one part of this proposal that I'm not entirely happy with. That being said, I like all of its other properties, and couldn't think of something better which didn't simply shunt the complexity to another location. The formconfig tag is multimodal: the first argument indicates which behavior to configure. If desired, new modes can be added in the future, and template designers need not hunt around the docs for new configuration arguments; they'd be grouped together under the {% form %} reference documentation. As shipped with Django, formconfig can operate on widgets and rows: {% formconfig widget widgets.Textarea for "comment" %} {% formconfig row using "forms/rows/ul.html" %} The first statement instructs the form to use a textarea widget for any formfield named "comment." The second instructs the form to use ul's as the default formrow template anytime a {% formrow field %} is encountered. These statements affect global state within a form, and can be reconfigured multiple times within a form layout: {% formconfig row using "forms/rows/p.html" %} {% formrow field1 %} {% formrow field2 %} {% formconfig row using "myrows/fancydiv.html" %} {% formrow field3 %} Similarly, default widgets can be set and reset: {% formconfig widget widgets.TextArea for forms.CharField %} {% formrow bio %} {% formrow interests %} {% formconfig widget widgets.TextInput for forms.CharField %} {% formrow hometown %} {% formrow zodiac_sign %} # Conclusion Hopefully, this proposal will stimulate discussion about how best to architect the form rendering API. I'd love to see more opinion from more frontend developers; if you have access to such people, please get them to check this out and weigh in. Don't forget to take a look at https://github.com/idangazit/formrendering, the templates are a helpful illustration of the possibilities. # Also, I want a pony This might be a good opportunity to change something I've long considered very un-Django-like in Django. Form rendering defaults to using tables for layout, which is semantically icky. How about we make the new default be p, or better yet, div? To that end, I've supplied div form layout & row templates. -- You received this message because you are subscribed to the Google Groups "Django developers" group. To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/LHX20vQru2sJ. To post to this group, send email to django-developers@googlegroups.com. To unsubscribe from this group, send email to django-developers+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.