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
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
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.
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
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/
-------------------------------------------------
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/
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.