newforms: compound or nested forms

184 views
Skip to first unread message

Jeroen van Dongen

unread,
Mar 9, 2007, 5:18:09 AM3/9/07
to Django developers
Hi all,

As far as I'm aware (from browsing the mailinglists and the newforms
code) newforms currently does not support compound or nested forms.
With this I mean something like (fairly dumbed-down example, but taken
from an app I'm currently working on):

class AddressForm(forms.Form):
street = forms.CharField()
city = forms.CharField()

class BusinessLocationForm(forms.SuperForm):
organisation = forms.CharField()
visiting_address = forms.SubForm(AddressForm)
postal_address = forms.SubForm(AddressForm, required=False)

Currently I could copy/past the fields of AddressForm directly into
the BusinessLocationForm. Or I'd have to create three separate forms
(using prefixes) in my view and deal with it manually (subclassing
BusinessLocationForm from AddressForm would only help if I'd only have
the need for one address).

In the past I've done some work with FormEncode, and that allows the
exact use-case I've sketched above. That worked well, so I thought,
may be I can add that functionality to newforms.

So currently I've a (more or less working) implementation of what I've
called a SuperForm, which can take either Fields or SubForms as shown
above. The resulting form instance then behaves like any normal form.
Clean_data becomes a dict of dicts so that you can easily take out all
data related to a particular subform. E.g. in the above case you would
get something like:
------------------------------------------------------------------------------------------------------------------
# create form instance
f = BusinessLocationForm()

# render the form, casauses all subforms to be rendered as well
f.as_table()

# the form elements are named as follows (using prefixes)
# organisation
# visiting_address-street
# visiting_address-city
# postal_address-street
# postal_address-city

# have user submit form and ...

# receive data and create bound form
f = BusinessLocationForm(request.POST)

# validate, all subforms are validated as well, unless they're not
required AND empty
# in which case they're ignored
if f.is_valid():
# assume AddressRecord and BusinessLocation are models closely
matching
# the presented forms
# create a address record for visiting_address
f.clean_data['visiting_address'] =
AddressRecord(f.clean_data['visiting_address'])
f.clean_data['postal_address'] =
AddressRecord(f.clean_data['postal_address'])
business_location = BusinessLocation(f.clean_data)
------------------------------------------------------------------------------------
The above example is very dumbed down but I guess you get the idea.
The code after "is_valid" could
perhaps be made more generic and put in a default SuperForm.save
method. But maybe that's trying to be too smart. In any case you could
put it there yourself, just to make the view code cleaner - in which
case you'd just do:

if f.is_valid():
f.save()

Apart from the SubForm "field" I've also created a FormList "field"
which takes a form definition, a min_count and a max_count which
allows for easy creation of a whole list of identical forms (think
edit_inline=models.STACKED).

The SuperForm class I refer to by the way, is in fact a container for
a number of forms that mimics the behaviour of a normal form. All
subforms are stored in a SortedDict. I've taken a copy of BaseForm and
reimplemented all methods to do the right thing to all subforms. If
you add normal Fields to a SuperForm, those are collected and turned
into another regular Form, which then in turn is added to the dict of
subforms maintained by SuperForm.

Since a SuperForm acts entirely as a normal Form you can also nest
SuperForms. So you could do:
-------------------------------------------------------------------------------------------------
class OrganisationForm(forms.SuperForm):
name = forms.CharField()
headquarters = forms.SubForm(BusinessLocationForm)
other_locations = forms.FormList(BusinessLocationForm,
min_count=1, max_count=10, required=False)

f = OrganisationForm(request.POST)
# clean_data['other_locations'] would be a list
for location_data in f.clean_data['other_locations']:
location = BusinessLocation(location)
location.save()
...
...
-------------------------------------------------------------------------------------------------

My questions now boil down to: Do any of you think of this as a
desirable feature, if so do you agree with the road I've taken? Is
anyone else already working on this?

If there's desire and no objections I'll produce a cleaned up version
and submit it in Trac. That could either be as e.g. contrib.superform
or as a "patch" against newforms - I put "patch" between quotes
because the existing newforms code is not touched in anyway, it's
totally "bolt-on" in the form of a number of new classes.

Rgds,
Jeroen

Thomas Steinacher

unread,
Mar 9, 2007, 5:46:05 AM3/9/07
to Django developers
+1 - Sub forms and form lists are great and IMHO very common. Please
submit the patch.

On Mar 9, 11:18 am, "Jeroen van Dongen" <baasbart...@gmail.com> wrote:
> Apart from the SubForm "field" I've also created a FormList "field"
> which takes a form definition, a min_count and a max_count which
> allows for easy creation of a whole list of identical forms (think
> edit_inline=models.STACKED).

How would you render a FormList in a template? Would it be possible to
render the FormList using JavaScript, to make it possible to add new
items by clicking on a JavaScript link? I did something similar:
http://eggdrop.ch/blog/2007/02/15/django-dynamicforms/ Maybe this
could be combined...

Thomas

Rubic

unread,
Mar 9, 2007, 1:48:31 PM3/9/07
to Django developers
Jeroen,

Right now my biggest time sink with newforms is dealing with dynamic
fields (in contrast to dynamic forms). I'd like to work up some
examples and present them as possible use cases to address, if you
think dynamic fields might fall within the scope of SuperForms.

--
Jeff Bauer
Rubicon, Inc.

Jeroen van Dongen

unread,
Mar 10, 2007, 7:47:19 AM3/10/07
to Django developers
I'm currently working to get a releasable version that I can post to
Trac - perhaps tomorrow.

Thomas: I've replied to you yesterday, but my reply does not seem to
show up. Longer story short: the javascript is not autogenerated yet,
but could be done. I'll first get a basic version in a releasable
state, adding this later then becomes easier as we all have the actual
code.

Jeff: could you elaborate a bit on what you mean with "dynamic
fields"?

Rgds,
Jeroen

Rubic

unread,
Mar 10, 2007, 6:56:54 PM3/10/07
to Django developers
On Mar 10, 6:47 am, "Jeroen van Dongen" <baasbart...@gmail.com> wrote:
> Jeff: could you elaborate a bit on what you mean with "dynamic
> fields"?

Assigning a field dynamically is what occurs
when you only know at runtime which (or how
many) fields must be displayed. This is not
simply a matter of using template logic to
hide some fields from being displayed, because
in certain situations you're also assigning
the field name dynamically -- either because
it's part of a list or maybe because it's table
driven.

Here are two snippets I posted that demonstrate
dynamic fields:

http://www.djangosnippets.org/snippets/27/
http://www.djangosnippets.org/snippets/82/

Jeroen van Dongen

unread,
Mar 11, 2007, 5:52:16 AM3/11/07
to Django developers
Jeff: the case in snippet 82 is covered with the FormList idea.
However, the case as presented in snippet 27 is not. And I'm in doubt
it's something that requires a special form class actually. What's to
prevent you from just creating a normal form and then altering the
required setting of one of the fields? Like this:

-------------------------------------------------
class MyForm(forms.Form):
A = forms.CharField()
B = forms.CharField()

def my_view(request):
f = MyForm()
if some_condition:
f.fields['B'].required = False
-----------------------------------------------
Both 'fields' and 'required' are public attributes.

or even:
----------------------------------------------
def my_view(request):
f = MyForm()
if some_condition:
del f.fields['B']
---------------------------------------------

Rgds,
Jeroen

> http://www.djangosnippets.org/snippets/27/http://www.djangosnippets.org/snippets/82/

Jeroen van Dongen

unread,
Mar 11, 2007, 1:36:59 PM3/11/07
to Django developers
An early version can be found at: http://code.djangoproject.com/ticket/3706

Rubic

unread,
Mar 12, 2007, 10:09:39 AM3/12/07
to Django developers
On Mar 11, 4:52 am, "Jeroen van Dongen" <baasbart...@gmail.com> wrote:
> Jeff: the case in snippet 82 is covered with the FormList idea.
> However, the case as presented in snippet 27 is not. And I'm in doubt
> it's something that requires a special form class actually.

I don't think I was arguing that #27 should be handled by SuperForms,
it was more a response to answering your question regarding dynamic
fields. Actually my first post was to ask if this issue even falls
within the scope of SuperForms. Based on your response, no.

Regarding #82: Thanks, I was hoping that FormList covered this use
case. Your patch (ticket #3706) mentions some known bugs with
FormList that will be addressed later this week.

Suggestion: Since your patch doesn't require any modification of
newforms, you could also post it to djangosnippets.org where it might
get more coverage from people who don't monitor Trac.

Thanks again and best regards.

Reply all
Reply to author
Forward
0 new messages