I suggest reading this proposal online: https://gist.github.com/898375
It's exactly the same as below but formated nicely.


GSoC 2011 Proposal - Revised form rendering
============================================

Hi my name is Gregor Müllegger. I'm a Computer Science student in Germany at
the University of Augsburg currently in the fourth year of my studies. I first
came to django shortly before 0.96 was released and a lots of awesomeness was
introduced with the magic removal branch.

I'm also doing some django freelancing work since 2008 to finance my studies
and attended DjangoCon EU in 2011. This year I would like to apply to
Google Summer of Code helping to improve Django with something that bugs me
since quite a while: It's builtin ability to render a form straight away into
HTML.

Motiviation
-----------

Why would I like to change the current behaviour? There are some reasons for
this:

1. It's hard to change the default rendering.

   It is very easy to use e.g. django's builtin rendering of a ``ul`` based
   output like ``{{ myform.as_ul }}``. But what if you want to use your own
   *layout* like ``{{ myform.as_dl }}``? You can create a new method on your
   existing forms, but that involves writing python code (out of the designers
   hand) and might not be possible for thirdparty forms.

2. Maybe the ``as_ul`` rendering is fine for me. But maybe I want to skip that
   unnecessary field that the thirdparty form is providing. The solution would
   be to write down every single field in your template, reimplementing the
   ``ul`` layout::

        <li>{{ form.field.label_tag }}: {{ form.field }}
            {{ form.field.help_text }} {{ form.field.errors }}</li>
        {# skipping field2 here #}
        <li>{{ form.field3.label_tag }}: {{ form.field3 }}
            {{ form.field3.help_text }} {{ form.field3.errors }}</li>
        ...

   We all love DRY, so this is not acceptable.

3. The designers I worked with are often interested on adding custom css class
   or an attribute to a form field. Most of the time this is really a pain to
   do if you don't have control over the python form code. Imagine a reusable
   app that ships with urls, views, forms. To add a single class you would
   need to: (a) overwrite the predefined url because you want (b) to specify
   an additional parameter for the view which is (c) your custom subclass of
   the thirdparty form::

       class ChangedAttributeForm(ThirdPartyForm):
           field = forms.CharField(max_length=50,
               widget=TextInput(attrs={'class': 'fancy-text-input'}))

   btw: This also violates the DRY principle since you have to redefine the
   used field type, it's attributes (like ``max_length=50``) and the widget.

   I want to tell the designer how he can do this without my help in the
   template.

Goals I want to accomplish
--------------------------

After showing some of the problems that I see, are here the higher goals I
want to achieve during the summer:

1. All of the rendering formats should be extracted from django's python source
   into a well defined structure of customizable templates.
2. Make it possible to reorder the use of form fields in the template without
   needing to write down the complete form.
3. Support for *chrome* that can be added dynamically in the template to a set
   of fields, changing some aspects of the form output.
4. Ensuring that the DRY principle is applyable to your form templates (in
   case of reordering/skipping some fields, like shown above)
5. Ensuring total backwards compatibility for ``{{ form.as_p }}`` etc. but
   deprecating it.
6. Converting the admin to use all of the new goodness. First, to make django
   eating its own dogfood. Second, to prove that the stuff I have developed is
   really a step forward and to show up big problems that occur in reallife
   scenarios, not taken into account in the blueprint.
7. Documenting all the newly introduced templatetags, the builtin chromes,
   how to write custom chrome, how to create your own form layout etc...

Let's get a bit more detailed. How do I want to implement these goals?

**1. No HTML in python source**

I will push the formating of ``as_table``, ``as_ul`` and ``as_p`` into a
specified template structure. A formating (e.g. ``ul``) will be called a
layout and will live in the template directory ``forms/layouts/<layout>/...``.
This directory will contain a single file (e.g. for the table layout)::

    forms/layouts/table/row.html

Creating a new layout is as simple as putting a new directory into your
template path and adding one file. Why putting the file into a directory? The
reason is because we can make the layout even more customizable. You are able
in the ``row.html`` to ``{% include %}`` a ``errors.html`` and a
``helptext.html`` that can be overriden by the designer without completely
rewriting the whole layout.

Ok, but how will this look in the template where I use the form? For this we
need a new template tag::

    {% form myform using layout "p" %}
               -- or --
    {% form myform using layout "my_custom_layout" %}

**2. Reordering/skipping fields**

Thats pretty straigh forward with the newly introduced template tag::

    {% form myform using fields "firstname" "lastname" "country" %}

You can list any fields you want, skipping some of them, reordering them etc.
Here is a more advanced example with using a layout and injecting fields of a
second form into another one::

    {% form myform using layout "p" and fields "firstname" "lastname" %}
    {% form fancyform.favourite_color using layout "p" %}
    {% form myform using layout "p" and fields "country" %}

What have we added to the syntax?

a. We can use a single field instead of a form as first argument
b. We can use multiple *rendering modifiers* after the keyword ``using``.

*Rendering modifiers* are the bits after ``using``. They have a name (like
``layout`` or ``fields``) and take as many arguments as they want. You can
seperate multiple modifiers with ``and``.

The goal will be to make these modifiers defineable. Which means that you
can create your own modifiers to support some of your extravagant form
rendering needs. To support this we will need to have a rendering modifier
*registry* or something similiar, where you can ideally load new ones with the
existing ``{% load %}`` tag. The same will apply for custom *chrome*,
described below.

**3. Chrome**

The principle of a *chrome* is a shameless plug of Russell's earlier
suggestions on a revised form rendering [1]. Let me quickly summarize what a
chrome might be able to do for you.

A chrome might be useful to change the rendering of a widget. For example you
have a simple ``<input type="text" />`` input for datetime field. But you want
to enhance it with a calendar popup (like in django's admin). You would simply
apply the ``calendar`` chrome to your field::

    {% form myform.birthday using calendar %}

So what's the difference here between a chrome and a rendering modifier? The
difference is only visible in the python level since chromes will be way
easier to implement.

They opperate only on fields and widgets not on a complete form. They won't
need to hassle with argument parsing since they have a common syntax that
will be::

    {% form <form instance> using <chrome name> [<list of arguments>]
[for <filtering fields>] %}

So a chrome is called with the ``<list of arguments>`` that are already
evaluated, previously beeing template variables or static strings. The chrome
is also called once for every field, which simplifies implementation again.
The template tag syntax also provides ways of limiting the use of the chrome
to a set of fields (but the chrome implementor doesn't need to care about
them). It's easier to explain this with some examples::

    Will add the attribute required to _all_ fields of the form (no
field filters specified):
    {% form myform using attribute "required" %}

    Calendar chrome for two specified fields:
    {% form myform using calendar for myform.birthday myform.member_since %}

    Adding the class "error" to all fields that have errors:
    {% form myform using class "error" for errors %}

    Calendar for all forms.DateTimeField fields in all of the used forms:
    {% formblock using calendar for type "DateTimeField" %}
        {% form myform %}
        {% form fancyform %}
        {% form another_form_with_lots_of_datetime_fields %}
    {% endformblock %}

    Autocomplete email addresses from your addressbook for all fields
that are using the EmailInput:
    {% form myform using autocomplete "/addressbook/emails/" for
widget "EmailInput" %}

A sample chrome implementation would look like::

    def attribute(bound_field, name, value=None):
        if value is None:
            value = ''
        bound_field.field.widget.attrs[name] = value

Look again at the example from above::

    {% form myform using attribute "placeholder" "Type in your name
..." for myform.firstname myform.lastname %}

This template tag will call the ``attribute`` chrome on both the fields
``firstname`` and ``lastname`` like::

    attribute(myform['firstname'], 'placeholder', 'Type in your name ...')
    attribute(myform['lastname'], 'placeholder', 'Type in your name ...')

There will also be the possibility of template only chromes which means that
you don't need any python code for some simple modifications.

Template based chromes will live in ``forms/chromes/<chrome name>.html`` and
will get the same arguments as the proposed signature of the python function:
``bound_field`` and ``args``. This makes things possible like creating
Javascript triggers after the widget::

    in the form template:

        {% form myform.client using autocomplete "/customers/" %}

    in forms/chromes/autocomplete.html:

        {{ bound_field }}
        {# ^--- will render the used widget as usual #}
        <script>
            ... javascript triggers ...
            autocomplete({ url: {{ args.0 }} });
        </script>

**4. Keeping your form templates DRY**

The example in **2.** is already much better than the current situation but it
still violates the DRY principle somehow. We repeat ourselfs by listing the
used layout three times. We can do better by grouping the modifiers with a
*formblock*::

    {% formblock using layout "p" %}
        {% form myform using fields "firstname" "lastname" %}
        {% form fancyform.favourite_color %}
        {% form myform using fields "country" %}
    {% endformblock %}

This will remember the modifiers and chromes that are used in the
``formblock`` and will apply them to all ``{% form %}`` tags that are used
inside.

**5. Backwards compatibility**

Backwards compatibility is a serious thing but straight forward in the case of
this proposal. We can fall back to use the internals of the ``{% form %}`` tag
while rendering the form via ``{{ myform }}`` or ``{{ myform.as_ul }}``. A bit
trickier is the use of ``{{ myform.field.label_tag }}`` and
``{{ myform.field.errors }}``. The proposal above doesn't include these cases.

But this is also possible to solve. Goal 1. suggests to refactor all HTML out
of the python source. This must include lables and errors as well. For this
case we would create some new templates::

    forms/layouts/default/label.html
    forms/layouts/default/errors.html

They get the bound field that is used passed in and can render there output like
the ``label_tag`` method and the ``errors`` attribute. In the template we would
use::

    {% form myform.birthday display errors %}
    instead of {{ myform.birthday.errors %}

    {% form myform.birthday display label %}
    instead of {{ myform.birthday.label_tag %}

Storing these templates in the layouts directory has also some nice side
effects. We can for example use some alternative styling of the labels and
errors based on the current layout::

    {% formblock using layout "plain" %}
        {% form myform.birthday display label field %}
           ^--- uses the "plain" layout to render the label and the
field definition
        {% form myform.birthday display errors using layout "ul" %}
           ^--- the specified "ul" layout overwrites the "p" layout from the
                formblock and displays a list of errors instead of errors
                seperated by <br> that might be used in the "p" layout.
        {% form myform.birthday display helptext %}
    {% endfor %}

**6. Admin integration**

The admin integration will be a real fun part for me. I already worked many
many hours with customizing the admin. Many clients want another widget to be
used here, adding some style changes to a field there etc.

All the stuffs is already possible ofcourse through custom widgets but we
still need to overwrite the forms in the admin code. A cool thing would be to
make the use of chromes easier here.

We will add some hooks to the ``ModelAdmin`` class that allow you to add
chromes to fields, maybe in a way like ``formfield_overrides`` works but
propably in a more flexible manner. This is already easy to use for most
designers since reading the ``admin.py`` file is usually very straight forward
and only requires some basic knowledges about assignments, lists and dicts.
However it would be even cooler to do this in the template.

First we will create a custom form layout for the admin. It's easy to
overwrite as described above so that you can change the look of the field
rows, errors, helptext pretty easily. Additionally there will be a block in
the ``change_form.html`` template that can be overridden by the user (the
example is showing the template ``admin/<appname>/change_form.html``)::

    {% extends "admin/change_form.html" %}

    {% block form %}
        {% form adminform using autocomplete "/customers/" for
adminform.client %}
    {% endblock %}

Besides the integration, there will be some need to convert the existing
gimmicks like calendar popup, "add new" icon next to ForeingKey dropdowns,
etc. into chrome implementations.

**7. Documentation**

Lots of work but nothing to specify here...

Media aka. JS/CSS
-----------------

One of the other mainpoints in the discussions I reviewed for this proposal was
the use of JS and CSS files that must be maintained somehow to display them
how we already do through the media framework (e.g. ``{{ form.media }}``).

The problem with this is that with the new template tag we can change some
of the widgets in the template and introducing new dependencies. Thats why I
would like to have an alternative for the ``using`` argument in the
``{% form %}`` tag.

If ``using`` is replaced with ``configure``, the ``{% form %}`` tag will _not_
output the HTML in the current place. However it will record and remember the
usage of widgets and fields to determine which media files are required. An
example template would look like::

    {% block extrahead %}
        {% form myform configure widget "CalendarInput" for myform.birthday %}
                       ^--- The new widget for the field birthday will be
                            recorded, but the form will not be rendered.
        {% formmedia css for myform %}
           ^--- Outputting all necessary css files.
    {% endblock %}

    {% block content %}
        {% form myform %}
           ^--- The form will be rendered as usual but with the
                "CalendarInput" widget that was specified in the other tag.
    {% endblock %}

    {% block extrajs %}
        {% formmedia js for myform %}
           ^--- Outputting all necessary js files at the end of the document.
    {% endblock %}

A shortform for the ``{% formmedia %}`` tag is available if one likes to load
the css and javascript next to each other: ``{% formmedia for myform %}``

I don't know if backwards compatibility is also necessary here. If you use the
new ``{% form %}`` tag you will also need to update your ``{{ form.media }}``
statements.
However we could still provide backwards compatibility by overwriting the
``media`` attribute of the form instance that we have modified with the
``{% form ... configure .. %}`` tag.

Estimates
---------

That's it so far with the proposal. In the following I will go a bit into the
timeline that I have in mind for the implementation.

1st week: Examing what unittests are available for the current form rendering
and making sure they are stable for testing backwards compatibility
during the project.

2nd week: Converting the current layouts into template based renderers, ensuring
backwards compatibility.

Goal: no HTML should be left now in the python source.

3rd week: I will attend DjangoCon EU

4th week: Starting to write tests and implementing the {% form %} tag
to be able to
emulate all the rendering that is currently possible.

5th week: Implementing the necessary rendering modifiers like "fields"
(limiting the
form to some selected fields) and the API for chrome.

6th week: Building the registry that is necessary to register your own rendering
modifiers and chrome.

7th week: Taking care of the media handling.

Goal: Project should be feature complete.

8th week: Converting the admin to use the new form rendering and
providing hooks for
applying chrome to some fields.

9th week: Integrating lessons learned from the admin especially in the sense of
making it easy to package chrome with a reusable app.

Goal: Code should be ready to be used in sample projects

10th & 11th week: Documentation and bugfixes

12th week: Finalizing

Unfortunatelly university is running in germany during the time that I will
work on the project. However based on my experience with last years of
university I think that I can provide about 30 hours per week for work on the
project, discussing questions and suggestions on the mailing list and so on.


So thanks a lot for reading so far, I really appreciate it and would love to
hear your feedback. Please keep in mind that the syntax is product of my own
taste and I haven't shown it to someone else yet. I'm confident that it will
be usable for most template authors and is also very flexible on the python
side for customizations. But there might still be a lot of room for critiques
and situations that I have not considered yet.

Gregor

(A formated version of this proposal is available at [2])


Addendum
--------

Most of my inspiration came from the *Revised form rendering* thread [1]
kicked off by Russell last year after DjangoCon EU. Thanks also goes to Jannis
and Bruno for giving me some advice and making clear what the current state of
template based widgets is and much more.

My first thought was to include template based widgets also in this proposal.
But Bruno has already made a great effort with developing
**django-floppyforms** that might also get merged into django. He is already
working on a patch [3].


* [1] 
http://groups.google.com/group/django-developers/browse_thread/thread/cbb3aee22a0f8918
* [2] https://gist.github.com/898375
* [3] https://github.com/brutasse/django/compare/15667-template-widgets

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
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