1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

newforms-admin: Initial implementation of FormSet.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4836 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2007-03-28 04:31:06 +00:00
parent 132cf258cb
commit e2b49c379a
3 changed files with 324 additions and 0 deletions

View File

@ -159,6 +159,24 @@ class BaseForm(StrAndUnicode):
"""
return self.errors.get(NON_FIELD_ERRORS, ErrorList())
def is_empty(self, exceptions=None):
"""
Returns True if this form has been bound and all fields that aren't
listed in exceptions are empty.
"""
# TODO: This could probably use some optimization
exceptions = exceptions or []
for name, field in self.fields.items():
if name in exceptions:
continue
# value_from_datadict() gets the data from the dictionary.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
if value not in (None, ''):
return False
return True
def full_clean(self):
"""
Cleans all of self.data and populates self.__errors and self.clean_data.

154
django/newforms/formsets.py Normal file
View File

@ -0,0 +1,154 @@
from django import newforms as forms
# special field names
FORM_COUNT_FIELD_NAME = 'COUNT'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
class ManagementForm(forms.Form):
"""
``ManagementForm`` is used to keep track of how many form instances
are displayed on the page. If adding new forms via javascript, you should
increment the count field of this form as well.
"""
def __init__(self, *args, **kwargs):
self.base_fields[FORM_COUNT_FIELD_NAME] = forms.IntegerField(widget=forms.HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
class FormSet(object):
"""A collection of instances of the same Form class."""
def __init__(self, form_class, data=None, auto_id='id_%s', prefix=None, initial=None):
self.form_class = form_class
self.prefix = prefix or 'form'
self.auto_id = auto_id
# initialization is different depending on whether we recieved data, initial, or nothing
if data:
self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
if self.management_form.is_valid():
form_count = self.management_form.clean_data[FORM_COUNT_FIELD_NAME]
else:
# not sure that ValidationError is the best thing to raise here
raise forms.ValidationError('ManagementForm data is missing or has been tampered with')
self.form_list = self._forms_for_data(data, form_count=form_count)
elif initial:
form_count = len(initial)
self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: form_count+1}, auto_id=self.auto_id, prefix=self.prefix)
self.form_list = self._forms_for_initial(initial, form_count=form_count)
else:
self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: 1}, auto_id=self.auto_id, prefix=self.prefix)
self.form_list = self._empty_forms(form_count=1)
# TODO: initialization needs some cleanup and some restructuring
# TODO: allow more than 1 extra blank form to be displayed
def _forms_for_data(self, data, form_count):
form_list = []
for i in range(0, form_count-1):
form_instance = self.form_class(data, auto_id=self.auto_id, prefix=self.add_prefix(i))
self.add_fields(form_instance, i)
form_list.append(form_instance)
# hackish, but if the last form stayed empty, replace it with a
# blank one. no 'data' or 'initial' arguments
form_instance = self.form_class(data, auto_id=self.auto_id, prefix=self.add_prefix(form_count-1))
if form_instance.is_empty():
form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(form_count-1))
self.add_fields(form_instance, form_count-1)
form_list.append(form_instance)
return form_list
def _forms_for_initial(self, initial, form_count):
form_list = []
# generate a form for each item in initial, plus one empty one
for i in range(0, form_count):
form_instance = self.form_class(initial=initial[i], auto_id=self.auto_id, prefix=self.add_prefix(i))
self.add_fields(form_instance, i)
form_list.append(form_instance)
# add 1 empty form
form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(i+1))
self.add_fields(form_instance, i+1)
form_list.append(form_instance)
return form_list
def _empty_forms(self, form_count):
form_list = []
# we only need one form, there's no inital data and no post data
form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(0))
form_list.append(form_instance)
return form_list
def get_forms(self):
return self.form_list
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
pass
def add_prefix(self, index):
return '%s-%s' % (self.prefix, index)
def _get_clean_data(self):
return self.get_clean_data()
def get_clean_data(self):
clean_data_list = []
for form in self.get_non_empty_forms():
clean_data_list.append(form.clean_data)
return clean_data_list
clean_data = property(_get_clean_data)
def is_valid(self):
for form in self.get_non_empty_forms():
if not form.is_valid():
return False
return True
def get_non_empty_forms(self):
"""Return all forms that aren't empty."""
return [form for form in self.form_list if not form.is_empty()]
class FormSetWithDeletion(FormSet):
"""A ``FormSet`` that handles deletion of forms."""
def add_fields(self, form, index):
"""Add a delete checkbox to each form."""
form.fields[DELETION_FIELD_NAME] = forms.BooleanField(label='Delete', required=False)
def get_clean_data(self):
self.deleted_data = []
clean_data_list = []
for form in self.get_non_empty_forms():
if form.clean_data[DELETION_FIELD_NAME]:
# stick data marked for deletetion in self.deleted_data
self.deleted_data.append(form.clean_data)
else:
clean_data_list.append(form.clean_data)
return clean_data_list
class FormSetWithOrdering(FormSet):
"""A ``FormSet`` that handles re-ordering of forms."""
def get_non_empty_forms(self):
return [form for form in self.form_list if not form.is_empty(exceptions=[ORDERING_FIELD_NAME])]
def add_fields(self, form, index):
"""Add an ordering field to each form."""
form.fields[ORDERING_FIELD_NAME] = forms.IntegerField(label='Order', initial=index+1)
def get_clean_data(self):
clean_data_list = []
for form in self.get_non_empty_forms():
clean_data_list.append(form.clean_data)
# sort clean_data by the 'ORDER' field
clean_data_list.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
return clean_data_list
def is_valid(self):
for form in self.get_non_empty_forms():
if not form.is_valid():
return False
return True
# TODO: handle deletion and ordering in the same FormSet
# TODO: model integration: form_for_instance and form_for_model type functions

View File

@ -2895,6 +2895,158 @@ True
>>> p.clean_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
# FormSets ####################################################################
FormSets allow you to create a bunch of instances of the same form class and
get back clean data as a list of dicts.
>>> from django.newforms import formsets
>>> class ChoiceForm(Form):
... choice = CharField()
... votes = IntegerField()
Create an empty form set
>>> form_set = formsets.FormSet(ChoiceForm, prefix='choices', auto_id=False)
>>> for form in form_set.get_forms():
... print form.as_ul()
<li>Choice: <input type="text" name="choices-0-choice" /></li>
<li>Votes: <input type="text" name="choices-0-votes" /></li>
Forms pre-filled with initial data.
>>> initial_data = [
... {'votes': 50, 'choice': u'The Doors', 'id': u'0'},
... {'votes': 51, 'choice': u'The Beatles', 'id': u'1'},
... ]
>>> form_set = formsets.FormSet(ChoiceForm, initial=initial_data, auto_id=False, prefix='choices')
>>> print form_set.management_form.as_ul()
<input type="hidden" name="choices-COUNT" value="3" />
>>> for form in form_set.get_forms(): # print pre-filled forms
... print form.as_ul()
<li>Choice: <input type="text" name="choices-0-choice" value="The Doors" /></li>
<li>Votes: <input type="text" name="choices-0-votes" value="50" /></li>
<li>Choice: <input type="text" name="choices-1-choice" value="The Beatles" /></li>
<li>Votes: <input type="text" name="choices-1-votes" value="51" /></li>
<li>Choice: <input type="text" name="choices-2-choice" /></li>
<li>Votes: <input type="text" name="choices-2-votes" /></li>
Tests for dealing with POSTed data
>>> data = {
... 'choices-COUNT': u'3', # the number of forms rendered
... 'choices-0-choice': u'The Doors',
... 'choices-0-votes': u'50',
... 'choices-1-choice': u'The Beatles',
... 'choices-1-votes': u'51',
... 'choices-2-choice': u'',
... 'choices-2-votes': u'',
... }
>>> form_set = formsets.FormSet(ChoiceForm, data, auto_id=False, prefix='choices')
>>> print form_set.is_valid()
True
>>> for data in form_set.clean_data:
... print data
{'votes': 50, 'choice': u'The Doors'}
{'votes': 51, 'choice': u'The Beatles'}
FormSet with deletion fields
>>> form_set = formsets.FormSetWithDeletion(ChoiceForm, initial=initial_data, auto_id=False, prefix='choices')
>>> for form in form_set.get_forms(): # print pre-filled forms
... print form.as_ul()
<li>Choice: <input type="text" name="choices-0-choice" value="The Doors" /></li>
<li>Votes: <input type="text" name="choices-0-votes" value="50" /></li>
<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
<li>Choice: <input type="text" name="choices-1-choice" value="The Beatles" /></li>
<li>Votes: <input type="text" name="choices-1-votes" value="51" /></li>
<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
<li>Choice: <input type="text" name="choices-2-choice" /></li>
<li>Votes: <input type="text" name="choices-2-votes" /></li>
<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
>>> data = {
... 'choices-COUNT': u'3', # the number of forms rendered
... 'choices-0-choice': u'Fergie',
... 'choices-0-votes': u'1000',
... 'choices-0-DELETE': u'on', # Delete this choice.
... 'choices-1-choice': u'The Decemberists',
... 'choices-1-votes': u'150',
... 'choices-2-choice': u'Calexico',
... 'choices-2-votes': u'90',
... }
>>> form_set = formsets.FormSetWithDeletion(ChoiceForm, data, auto_id=False, prefix='choices')
>>> print form_set.is_valid()
True
When we access form_set.clean_data, items marked for deletion won't be there,
but they *will* be in form_set.deleted_data
>>> for data in form_set.clean_data:
... print data
{'votes': 150, 'DELETE': False, 'choice': u'The Decemberists'}
{'votes': 90, 'DELETE': False, 'choice': u'Calexico'}
>>> for data in form_set.deleted_data:
... print data
{'votes': 1000, 'DELETE': True, 'choice': u'Fergie'}
FormSet with Ordering
>>> form_set = formsets.FormSetWithOrdering(ChoiceForm, initial=initial_data, auto_id=False, prefix='choices')
>>> for form in form_set.get_forms(): # print pre-filled forms
... print form.as_ul()
<li>Choice: <input type="text" name="choices-0-choice" value="The Doors" /></li>
<li>Votes: <input type="text" name="choices-0-votes" value="50" /></li>
<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
<li>Choice: <input type="text" name="choices-1-choice" value="The Beatles" /></li>
<li>Votes: <input type="text" name="choices-1-votes" value="51" /></li>
<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
<li>Choice: <input type="text" name="choices-2-choice" /></li>
<li>Votes: <input type="text" name="choices-2-votes" /></li>
<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
>>> data = {
... 'choices-COUNT': u'4', # the number of forms rendered
... 'choices-0-choice': u'Fergie',
... 'choices-0-votes': u'1000',
... 'choices-0-ORDER': u'3',
... 'choices-1-choice': u'The Decemberists',
... 'choices-1-votes': u'150',
... 'choices-1-ORDER': u'1',
... 'choices-2-choice': u'Calexico',
... 'choices-2-votes': u'90',
... 'choices-2-ORDER': u'2',
... 'choices-3-choice': u'',
... 'choices-3-votes': u'',
... 'choices-3-ORDER': u'4',
... }
>>> form_set = formsets.FormSetWithOrdering(ChoiceForm, data, auto_id=False, prefix='choices')
>>> print form_set.is_valid()
True
The form_set.clean_data will be in the correct order as specified by the
ORDER field from each form.
>>> for data in form_set.clean_data:
... print data
{'votes': 150, 'ORDER': 1, 'choice': u'The Decemberists'}
{'votes': 90, 'ORDER': 2, 'choice': u'Calexico'}
{'votes': 1000, 'ORDER': 3, 'choice': u'Fergie'}
# Forms with NullBooleanFields ################################################
NullBooleanField is a bit of a special case because its presentation (widget)