mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +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:
parent
132cf258cb
commit
e2b49c379a
@ -159,6 +159,24 @@ class BaseForm(StrAndUnicode):
|
|||||||
"""
|
"""
|
||||||
return self.errors.get(NON_FIELD_ERRORS, ErrorList())
|
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):
|
def full_clean(self):
|
||||||
"""
|
"""
|
||||||
Cleans all of self.data and populates self.__errors and self.clean_data.
|
Cleans all of self.data and populates self.__errors and self.clean_data.
|
||||||
|
154
django/newforms/formsets.py
Normal file
154
django/newforms/formsets.py
Normal 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
|
@ -2895,6 +2895,158 @@ True
|
|||||||
>>> p.clean_data
|
>>> p.clean_data
|
||||||
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
|
{'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 ################################################
|
# Forms with NullBooleanFields ################################################
|
||||||
|
|
||||||
NullBooleanField is a bit of a special case because its presentation (widget)
|
NullBooleanField is a bit of a special case because its presentation (widget)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user