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.

Reply via email to