Proposal: Improvements for django.forms

14 views
Skip to first unread message

pm13

unread,
Oct 16, 2008, 11:09:32 PM10/16/08
to Django developers
I would like to propose five quite isolated improvements for
django.forms. But I think it is better to write about them in one
email - otherwise there would not be complete answers for the
problems. (More specifically, proposal 5 is very useful for proposals
1 and 2. And proposals 3 a 4 are very useful for proposal 5). And the
improvements have common motive - it should be very easy to write very
powerful and very flexible forms.

I want only write about syntax. I have working patches for them but I
don't say they are perfect or ready (for example only the first of
them has tests).

1. Ordering and filtering of fields, fieldsets

There would be three meta attributes for Form - fieldsets, fields and
exclude. These attributes would be set with inner meta class:

class FormX(FirstForm, SecondForm):
class Meta:
fields = 'first_A', 'first_B', 'second_A'

class FormY(FirstForm, SecondForm):
class Meta:
exclude = 'first_C',

class FormZ(FirstForm, SecondForm):
class Meta:
fieldsets = (
{'fields': ('first_A', 'second_A')},
{'legend': 'More information', 'fields': (''first_B',
'first_C')},
)

Attribute "fieldsets" would be a list of dictionaries. Each dictionary
could have item fields, legend and attrs.

Atribute "fields" would be a list of field names. It would be quite
similar as in the current model forms - but the attribute would also
specified ordering.

Attribute "exclude" would be a list of field names. It would have the
same semantics as in the current model forms.

There would be no change in Form._html_output. But there would be two
new methods in Form for templates - has_fieldsets and fieldsets. The
second method would be a iterator of dictionaries with items attrs,
legend and fields. Item fields would be a list of bound fields
specified by fieldsets meta attributes, other items would be relevant
meta attributes.

Patch: http://code.djangoproject.com/attachment/ticket/6630/00-fieldsets.diff

2. Inlines

There would be a new meta attribute for Form - inlines. And it would
be possible to use inline in meta attribute fieldsets:

class FormX(FirstForm, SecondForm):
class Meta:
fields = 'first_A', 'first_B', 'second_A'
inlines = InlineA, InlineB

class FormZ(FirstForm, SecondForm):
class Meta:
fieldsets = (
{'fields': ('first_A', 'first_B', 'second_A')},
InlineA,
InlineB,
)

(These examples are equivalent.)

Attribute "inlines" would be a list of formset classes. And these
classes could be a part of "fieldsets" attribute.

Form would not be able to construct inline formset instances.
ModelForm would construct inline formset instances through one to many
model fields.

Methods Form.full_clean, Form.media, Form.is_multipart, ModelForm.save
and FormSet.full_clean would be changed to support forms with inline
formsets.

There would be no change in Form._html_output. But method
Form.fieldsets would be changed to iterate two kinds of dictionaries -
the first variant (for fieldsets) would have items order (order of the
fieldset between fieldsets), attrs, legend and fields. The second
variant (for formsets) would have items order (order of the formset
between formsets) and formset (an inline formset instance).

Patch: http://code.djangoproject.com/attachment/ticket/6632/01-inlines.diff

3. Meta attribute formfield_kwargs for model forms

There would be a new meta attribute for ModelForm - formfield_kwargs.
It is a generalization of the "widgets proposal":

class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = 'user', 'user_name', 'submit_date', 'comment',
formfield_kwargs = {
'user': {'queryset':
User.objects.exclude(is_superuser=True), 'attrs': {'class':
'important'}},
'user_name': {'help_text': 'for anonymous users'},
'submit_date': {'widget': SpecialDateWidget},
}

This meta attribute would be used as kwargs for
django.db.models.Form.formfield.

Patch: http://code.djangoproject.com/attachment/ticket/9385/02-formfield-kwargs.diff

4. Attributes "attrs", "template" and "legend" for init methods in
forms

Forms and fields would have two new attributes for their __init__
methods - "attrs" and "template". Formsets would have three new
attributes for their __init__ methods - "attrs", "template" and
"legend". These attributes would not be used in Form._html_output -
but there would be saved and it could be possible to use them in
templates:

{% for field in forms %}
% if field.template %}
{% include field.template %}
{% else %}
<tr {{ field.attrs|flatatt }}>
<th>{{ field.label_tag }}</th>
<td>{{ field.errors }} {{ field }} {{ field.help_text }}</
td>
</tr>
{% endif %}
{% endfor %}

Patch: http://code.djangoproject.com/attachment/ticket/9386/03-attrs-templates-legends.diff

5. Contrib application with form templates

There would be new contrib application with templates supporting all
previous proposals and also such improvements as css classes for
required fields or fields with errors. This application could be used
directly or as a pattern for someone's templates.

Patch: http://code.djangoproject.com/attachment/ticket/9387/04-formtemplates.diff

But this patch is only a proof of concept. Is is equivalent of
Form.as_table and TabularInline. For example, real application should
be properly namespaced, should support equivalents of Form.as_ul,
Form.as_p and StackedInline (the last one with recursive inlines) and
should be extensible:

{% extends "parts/formset.html" %}

{% block formset-header %}
<tr class="header">
<th>First column</th>
<th>Second column</th>
</tr>
{% endblock %}

David Cramer

unread,
Oct 18, 2008, 12:44:28 PM10/18/08
to Django developers
Some of these changes I think are very valuable, especially CSS
classes. The formfield_kwargs I don't think is the right approach, but
possibly a method which could be called, as a lot of times I'm
overriding __init__ and it's quite messy, just to change the queryset
for a form.

I would personally like to see every form row that's output using the
default methods be wrapped in a container, and have classnames for:

- Errors
- If Required
- Widget Type (including parent type, excluding "Field")

And, as_p should be as_div or some block element that can contain all
of this IMO also :)
> Patch:http://code.djangoproject.com/attachment/ticket/9385/02-formfield-kwa...
>
> 4. Attributes "attrs", "template" and "legend" for init methods in
> forms
>
> Forms and fields would have two new attributes for their __init__
> methods - "attrs" and "template". Formsets would have three new
> attributes for their __init__ methods - "attrs", "template" and
> "legend". These attributes would not be used in Form._html_output -
> but there would be saved and it could be possible to use them in
> templates:
>
> {% for field in forms %}
>     % if field.template %}
>         {% include field.template %}
>     {% else %}
>         <tr {{ field.attrs|flatatt }}>
>             <th>{{ field.label_tag }}</th>
>             <td>{{ field.errors }} {{ field }} {{ field.help_text }}</
> td>
>         </tr>
>     {% endif %}
> {% endfor %}
>
> Patch:http://code.djangoproject.com/attachment/ticket/9386/03-attrs-templat...
>
> 5. Contrib application with form templates
>
> There would be new contrib application with templates supporting all
> previous proposals and also such improvements as css classes for
> required fields or fields with errors. This application could be used
> directly or as a pattern for someone's templates.
>
> Patch:http://code.djangoproject.com/attachment/ticket/9387/04-formtemplates...

Jesse Young

unread,
Oct 18, 2008, 3:48:33 PM10/18/08
to Django developers
> 3. Meta attribute formfield_kwargs for model forms

It seems like the arguments to form fields can already be customized
fairly easily using formfield_callback? In your example, I think you
could accomplish the same thing using formfield_callback like this:

formfield_callback = lambda f: f.formfield(**{
'user': {'queryset': User.objects.exclude(is_superuser=True),
'attrs': {'class': 'important'}},
'user_name': {'help_text': 'for anonymous users'},
'submit_date': {'widget': SpecialDateWidget},
}.get(f.name, {}))

===

On a somewhat related note, I think it would be nice if you could call
mark_safe on the label attribute of a form field so it doesn't get
escaped in _html_output. Currently if you want to put html in labels,
it seems that you have to copy and paste the entirety of _html_output
just to change one line (label = escape(force_unicode(bf.label)))

pm13

unread,
Oct 20, 2008, 5:18:29 PM10/20/08
to Django developers
On Oct 18, 6:44 pm, David Cramer <dcra...@gmail.com> wrote:
> Some of these changes I think are very valuable, especially CSS
> classes. The formfield_kwargs I don't think is the right approach, but
> possibly a method which could be called, as a lot of times I'm
> overriding __init__ and it's quite messy, just to change the queryset
> for a form.

I think that partial customization of forms can be divided to two
groups:

1. Partial Customization which doesn't depend on current request
- form class or form instances could be customized
- it is specific for model forms (common forms has fields for it)
- it can be solved with a "plain attribute"

2. Partial customization which depends on current request
- only form instances could be customized
- it is not specific for model forms
- it cannot be solved with a "plain attribute"

I think these two problems are very different. My proposal is for the
first problem.

> I would personally like to see every form row that's output using the
> default methods be wrapped in a container, and have classnames for:
>
> - Errors
> - If Required
> - Widget Type (including parent type, excluding "Field")
>
> And, as_p should be as_div or some block element that can contain all
> of this IMO also :)

One of the main opinions behind the proposal is that is is very
difficult to generate forms in python. It is complex now - for simple
and not very flexible forms. But I think that forms should also
support:
- html class for row with required fields
- html class for row with errors
- html class giving type of field
- html class giving type of widget
- attributes specific for one (and only one) field row
- complete customization of one (and only one) field row
- fieldsets
- legends for fieldsets
- attributes specific for one (and only one) fieldset
- complete customization of one (and only one) fieldset
- inlines
- legends for inlines
- attributes specific for one (and only one) inline
- complete customization of one (and only one) inline
- recursive inlines

I think it is almost impossible to do it in python. I tried it for
fieldsets and inlines and I don't think it is good idea. It was very,
very complex and it broke many tests.

But there are templates for html code in django. So I propose to use
_html_output only for simple forms (and for tests). And add contrib
application which would support all these feature - by templates.
Almost all html generators (for example Form.as_table, Form.as_ul,
Form.as_p, BoundField.label_tag, Formset.as_table or ErrorList.as_ul)
would not be used there - there would be only one exception
"Widget.render" (because it is specific for each widget).

It would be possible to use these templates directly. Or use them as
"lego" for different default form.

But it would be also possible customize exactly one "component" (form,
field row, fieldset or formset) - only a little (by "attrs") or fully
(by "template").

pm13

unread,
Oct 20, 2008, 5:18:52 PM10/20/08
to Django developers
On Oct 18, 9:48 pm, Jesse Young <adu...@gmail.com> wrote:
> > 3. Meta attribute formfield_kwargs for model forms
>
> It seems like the arguments to form fields can already be customized
> fairly easily using formfield_callback? In your example, I think you
> could accomplish the same thing using formfield_callback like this:
>
>     formfield_callback = lambda f:  f.formfield(**{
>          'user': {'queryset': User.objects.exclude(is_superuser=True),
> 'attrs': {'class': 'important'}},
>          'user_name': {'help_text': 'for anonymous users'},
>          'submit_date': {'widget': SpecialDateWidget},
>     }.get(f.name, {}))

Yes it is possible - but it is much more complicated. But it was
discussed in different thread (Declarative syntax for widgets in
ModelForm), I only propose more general syntax.

http://groups.google.com/group/django-developers/browse_thread/thread/f879f383870b92c1/9769c9a449237047?lnk=gst&q=widgets#9769c9a449237047

> ===
>
> On a somewhat related note, I think it would be nice if you could call
> mark_safe on the label attribute of a form field so it doesn't get
> escaped in _html_output. Currently if you want to put html in labels,
> it seems that you have to copy and paste the entirety of _html_output
> just to change one line (label = escape(force_unicode(bf.label)))

I think it can be solved very easy - replace "escape" by
"conditional_escape". It seems to be a bug, not a feature. But
mark_safe would work with my proposal also - powerful and flexible
forms would be generated by templates, not by _html_output.

Malcolm Tredinnick

unread,
Oct 22, 2008, 3:38:52 AM10/22/08
to django-d...@googlegroups.com
I don't have particularly strong feelings about this, with the exception
of a few things noted below...

On Thu, 2008-10-16 at 20:09 -0700, pm13 wrote:
> I would like to propose five quite isolated improvements for
> django.forms. But I think it is better to write about them in one
> email - otherwise there would not be complete answers for the
> problems. (More specifically, proposal 5 is very useful for proposals
> 1 and 2. And proposals 3 a 4 are very useful for proposal 5). And the
> improvements have common motive - it should be very easy to write very
> powerful and very flexible forms.
>
> I want only write about syntax. I have working patches for them but I
> don't say they are perfect or ready (for example only the first of
> them has tests).
>
> 1. Ordering and filtering of fields, fieldsets

As a general note: Ordering really only makes sense for subclassing,
when you might apparently want to insert a new field in a particular
place in the existing fields' lists. I wouldn't want to put a lot of
effort into allowing changing the ordering of an existing form's fields,
since why wasn't the form created with the right order in the first
place. Creating a form is so easy, that micro-tweaking like that
shouldn't be necessary. Realise that changing the ordering of fields
actually slightly changes the validation, since a clean_FOO method might
no longer have some other field coming ahead of it in the cleaning
process.

I also would, as a general rule, prefer to avoid adding yet more Meta
inner classes to things. They really mess around with stuff like
subclassing and overriding and, if we can avoid them, better to do so.
Fieldsets are the only case where they appear to be unavoidable, though
(exluding fields and altering ordering can already be done fairly easily
and they shouldn't be that common that adding lots of infrastructure to
support them would be necessary in core). But I can't see any great API
alternative for fieldsets at the moment.

[...]

> 2. Inlines
>
> There would be a new meta attribute for Form - inlines. And it would
> be possible to use inline in meta attribute fieldsets:
>
> class FormX(FirstForm, SecondForm):
> class Meta:
> fields = 'first_A', 'first_B', 'second_A'
> inlines = InlineA, InlineB
>
> class FormZ(FirstForm, SecondForm):
> class Meta:
> fieldsets = (
> {'fields': ('first_A', 'first_B', 'second_A')},
> InlineA,
> InlineB,
> )
>
> (These examples are equivalent.)
>
> Attribute "inlines" would be a list of formset classes. And these
> classes could be a part of "fieldsets" attribute.

This I don't like. It's making Forms == Formsets at some level. Why are
you needing to use Forms to contain Formsets, instead of using Formsets
to contain Formsets? Formsets are made up from Forms, rather than
vice-versa. I'm not really understanding the use-case for this ("it's
another way to do it" isn't really a use-case). What's the problem
you're trying to solve here?

I realise you kind of avoid this confusion with the next paragraph...

> Form would not be able to construct inline formset instances.
> ModelForm would construct inline formset instances through one to many
> model fields.

... but it still feels like you're making something that is a Form
contain Formsets. Maybe the/a ModelForm constructor needs to optionally
return a proper Formset instead.

p,,,[


> 3. Meta attribute formfield_kwargs for model forms
>
> There would be a new meta attribute for ModelForm - formfield_kwargs.
> It is a generalization of the "widgets proposal":

I don't really like this one. You can already do this sort of stuff.

It feels like these proposals (not just this thread but other ones as
well) show that there are, in fact, many ways to work with forms. Since
they're all possible write now with a few helper methods, attempting to
add stuff to core to do the One True Way for forms would be fooling
ourselves. There just isn't a single right way here, or even an
obviously best way. So somebody will write and distribute MetaBaseForm
that can be used as a base class, somebody else will write
ExplicitWidgetForm that can be used as a base class, etc, and they all
can be done without core changes.

In general, I'm going to be against any radical changes in form
declarations because I just don't see any obvious winner yet and
experimentation is possible without core changes.

> 4. Attributes "attrs", "template" and "legend" for init methods in
> forms

Same argument as for the previous point. It's already possible just by
writing a base class that you subclass from instead of Form, so changes
to core shouldn't be necessary and it's not so clearly better than
alternative approaches to warrant inclusion in core as better than the
de-factor way to do things at the moment, to my mind.

I think you've got some interesting ideas, but I'm not sure they are
necessarily the easiest way to do things. We have to stop trying to
"solve" the forms issue and instead realise that what we have is
actually a very good base for the basic functionality. Wrapping
different APIs around that can then be done via subclasses and people
should definitely do that. It's exactly why forms don't need 47
different as_*() methods, for example, because if you need one of those
in particular, you write it in the common Form subclass that all you
specialised forms inherit from.

> 5. Contrib application with form templates

Definitely -1 here, just on the grounds that it's proposed for contrib
without having any real history behind it yet. Anything proposed for
contrib should already have a strong third-party following and history
of maintenance (with very, very few exceptions). So this should be
viable as an external application first before being included.

Regards,
Malcolm

Malcolm Tredinnick

unread,
Oct 22, 2008, 4:17:56 AM10/22/08
to django-d...@googlegroups.com

On Sat, 2008-10-18 at 12:48 -0700, Jesse Young wrote:
> > 3. Meta attribute formfield_kwargs for model forms
>
> It seems like the arguments to form fields can already be customized
> fairly easily using formfield_callback? In your example, I think you
> could accomplish the same thing using formfield_callback like this:
>
> formfield_callback = lambda f: f.formfield(**{
> 'user': {'queryset': User.objects.exclude(is_superuser=True),
> 'attrs': {'class': 'important'}},
> 'user_name': {'help_text': 'for anonymous users'},
> 'submit_date': {'widget': SpecialDateWidget},
> }.get(f.name, {}))
>
> ===
>
> On a somewhat related note,

You mean "on an entirely unrelated note except that it also has to do
with forms." :-)

> I think it would be nice if you could call
> mark_safe on the label attribute of a form field so it doesn't get
> escaped in _html_output. Currently if you want to put html in labels,
> it seems that you have to copy and paste the entirety of _html_output
> just to change one line (label = escape(force_unicode(bf.label)))

That sounds like a bug. Label attributes on form fields are not
use-generated. They're supplied by the developer (code writere), so they
should be treated as safe strings. Please open a ticket for this and
we'll fix it.

Regards,
Malcolm


Giuliani Vito Ivan

unread,
Oct 22, 2008, 4:29:54 AM10/22/08
to django-d...@googlegroups.com

There's already a ticket opened:
http://code.djangoproject.com/ticket/9111
Originally this was fixing only the escape of form errors, but someone
yesterday escaped labels too.

--
Giuliani Vito, Ivan
http://zeta-puppis.com

Giuliani Vito Ivan

unread,
Oct 22, 2008, 5:55:38 AM10/22/08
to django-d...@googlegroups.com
On Wed, Oct 22, 2008 at 07:17:56PM +1100, Malcolm Tredinnick wrote:
> > I think it would be nice if you could call
> > mark_safe on the label attribute of a form field so it doesn't get
> > escaped in _html_output. Currently if you want to put html in labels,
> > it seems that you have to copy and paste the entirety of _html_output
> > just to change one line (label = escape(force_unicode(bf.label)))
>
> That sounds like a bug. Label attributes on form fields are not
> use-generated. They're supplied by the developer (code writere), so they
> should be treated as safe strings. Please open a ticket for this and
> we'll fix it.

There's already a ticket opened (#9111). Originally this was fixing only
the escape of form errors, but someone yesterday updated the patch to
escape labels too.

(Sorry if this mail hit the list twice, I think I got caught in the spam
check)

Reply all
Reply to author
Forward
0 new messages