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

newforms-admin: Fixed #6075 -- Implemented max_num on formsets and model formsets. Added a hook on InlineModelAdmin to customize in the admin interface.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7613 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Brian Rosner 2008-06-11 03:58:01 +00:00
parent edf396da59
commit 530670e27f
7 changed files with 170 additions and 26 deletions

View File

@ -699,6 +699,7 @@ class InlineModelAdmin(BaseModelAdmin):
fk_name = None fk_name = None
formset = BaseInlineFormset formset = BaseInlineFormset
extra = 3 extra = 3
max_num = 0
template = None template = None
verbose_name = None verbose_name = None
verbose_name_plural = None verbose_name_plural = None
@ -722,7 +723,7 @@ class InlineModelAdmin(BaseModelAdmin):
return inlineformset_factory(self.parent_model, self.model, return inlineformset_factory(self.parent_model, self.model,
form=self.form, formset=self.formset, fk_name=self.fk_name, form=self.form, formset=self.formset, fk_name=self.fk_name,
fields=fields, formfield_callback=self.formfield_for_dbfield, fields=fields, formfield_callback=self.formfield_for_dbfield,
extra=self.extra) extra=self.extra, max_num=self.max_num)
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets: if self.declared_fieldsets:

View File

@ -10,6 +10,7 @@ __all__ = ('BaseFormSet', 'all_valid')
# special field names # special field names
TOTAL_FORM_COUNT = 'TOTAL_FORMS' TOTAL_FORM_COUNT = 'TOTAL_FORMS'
INITIAL_FORM_COUNT = 'INITIAL_FORMS' INITIAL_FORM_COUNT = 'INITIAL_FORMS'
MAX_FORM_COUNT = 'MAX_FORMS'
ORDERING_FIELD_NAME = 'ORDER' ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE' DELETION_FIELD_NAME = 'DELETE'
@ -22,6 +23,7 @@ class ManagementForm(Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[MAX_FORM_COUNT] = IntegerField(widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs) super(ManagementForm, self).__init__(*args, **kwargs)
class BaseFormSet(StrAndUnicode): class BaseFormSet(StrAndUnicode):
@ -29,7 +31,7 @@ class BaseFormSet(StrAndUnicode):
A collection of instances of the same Form class. A collection of instances of the same Form class.
""" """
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList): initial=None, error_class=ErrorList):
self.is_bound = data is not None or files is not None self.is_bound = data is not None or files is not None
self.prefix = prefix or 'form' self.prefix = prefix or 'form'
self.auto_id = auto_id self.auto_id = auto_id
@ -45,18 +47,25 @@ class BaseFormSet(StrAndUnicode):
if self.management_form.is_valid(): if self.management_form.is_valid():
self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT] self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT]
self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT] self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT]
self._max_form_count = self.management_form.cleaned_data[MAX_FORM_COUNT]
else: else:
raise ValidationError('ManagementForm data is missing or has been tampered with') raise ValidationError('ManagementForm data is missing or has been tampered with')
else: else:
if initial: if initial:
self._initial_form_count = len(initial) self._initial_form_count = len(initial)
if self._initial_form_count > self._max_form_count and self._max_form_count > 0:
self._initial_form_count = self._max_form_count
self._total_form_count = self._initial_form_count + self.extra self._total_form_count = self._initial_form_count + self.extra
else: else:
self._initial_form_count = 0 self._initial_form_count = 0
self._total_form_count = self.extra self._total_form_count = self.extra
initial = {TOTAL_FORM_COUNT: self._total_form_count, INITIAL_FORM_COUNT: self._initial_form_count} if self._total_form_count > self._max_form_count and self._max_form_count > 0:
self._total_form_count = self._max_form_count
initial = {TOTAL_FORM_COUNT: self._total_form_count,
INITIAL_FORM_COUNT: self._initial_form_count,
MAX_FORM_COUNT: self._max_form_count}
self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix) self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix)
# construct the forms in the formset # construct the forms in the formset
self._construct_forms() self._construct_forms()
@ -266,9 +275,11 @@ class BaseFormSet(StrAndUnicode):
return mark_safe(u'\n'.join([unicode(self.management_form), forms])) return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
can_delete=False): can_delete=False, max_num=0):
"""Return a FormSet for the given form class.""" """Return a FormSet for the given form class."""
attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete} attrs = {'form': form, 'extra': extra,
'can_order': can_order, 'can_delete': can_delete,
'_max_form_count': max_num}
return type(form.__name__ + 'FormSet', (formset,), attrs) return type(form.__name__ + 'FormSet', (formset,), attrs)
def all_valid(formsets): def all_valid(formsets):

View File

@ -305,7 +305,11 @@ class BaseModelFormSet(BaseFormSet):
queryset=None, **kwargs): queryset=None, **kwargs):
self.queryset = queryset self.queryset = queryset
defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
defaults['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] if self._max_form_count > 0:
qs = self.get_queryset()[:self._max_form_count]
else:
qs = self.get_queryset()
defaults['initial'] = [model_to_dict(obj) for obj in qs]
defaults.update(kwargs) defaults.update(kwargs)
super(BaseModelFormSet, self).__init__(**defaults) super(BaseModelFormSet, self).__init__(**defaults)
@ -369,15 +373,16 @@ class BaseModelFormSet(BaseFormSet):
super(BaseModelFormSet, self).add_fields(form, index) super(BaseModelFormSet, self).add_fields(form, index)
def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
formset=BaseModelFormSet, formset=BaseModelFormSet,
extra=1, can_delete=False, can_order=False, extra=1, can_delete=False, can_order=False,
fields=None, exclude=None): max_num=0, fields=None, exclude=None):
""" """
Returns a FormSet class for the given Django model class. Returns a FormSet class for the given Django model class.
""" """
form = modelform_factory(model, form=form, fields=fields, exclude=exclude, form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback) formfield_callback=formfield_callback)
FormSet = formset_factory(form, formset, extra=extra, can_order=can_order, can_delete=can_delete) FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
can_order=can_order, can_delete=can_delete)
FormSet.model = model FormSet.model = model
return FormSet return FormSet
@ -395,9 +400,8 @@ class BaseInlineFormset(BaseModelFormSet):
super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name) super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name)
def _construct_forms(self): def _construct_forms(self):
from django.newforms.formsets import INITIAL_FORM_COUNT
if self.save_as_new: if self.save_as_new:
self._total_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT] self._total_form_count = self._initial_form_count
self._initial_form_count = 0 self._initial_form_count = 0
super(BaseInlineFormset, self)._construct_forms() super(BaseInlineFormset, self)._construct_forms()
@ -443,10 +447,10 @@ def _get_foreign_key(parent_model, model, fk_name=None):
def inlineformset_factory(parent_model, model, form=ModelForm, def inlineformset_factory(parent_model, model, form=ModelForm,
formset=BaseInlineFormset, fk_name=None, formset=BaseInlineFormset, fk_name=None,
fields=None, exclude=None, fields=None, exclude=None,
extra=3, can_order=False, can_delete=True, extra=3, can_order=False, can_delete=True, max_num=0,
formfield_callback=lambda f: f.formfield()): formfield_callback=lambda f: f.formfield()):
""" """
Returns an ``InlineFormset`` for the given kwargs. Returns an ``InlineFormset`` for the given kwargs.
@ -464,7 +468,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
formfield_callback=formfield_callback, formfield_callback=formfield_callback,
formset=formset, formset=formset,
extra=extra, can_delete=can_delete, can_order=can_order, extra=extra, can_delete=can_delete, can_order=can_order,
fields=fields, exclude=exclude) fields=fields, exclude=exclude, max_num=max_num)
FormSet.fk = fk FormSet.fk = fk
return FormSet return FormSet

View File

@ -449,6 +449,34 @@ model instances without any database interaction::
This gives you the ability to attach data to the instances before saving them This gives you the ability to attach data to the instances before saving them
to the database. to the database.
Limiting the number of objects editable
---------------------------------------
Similar to regular formsets you can use the ``max_num`` parameter to
``modelformset_factory`` to limit the number of forms displayed. With
model formsets this will properly limit the query to only select the maximum
number of objects needed::
>>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
If the value of ``max_num`` is less than the total objects returned it will
fill the rest with extra forms::
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
Using ``inlineformset_factory`` Using ``inlineformset_factory``
------------------------------- -------------------------------

View File

@ -2230,6 +2230,22 @@ There are now a total of three forms showing above. One for the initial data
that was passed in and two extra forms. Also note that we are passing in a that was passed in and two extra forms. Also note that we are passing in a
list of dictionaries as the initial data. list of dictionaries as the initial data.
Limiting the maximum number of forms
------------------------------------
The ``max_num`` parameter to ``formset_factory`` gives you the ability to
force the maximum number of forms the formset will display::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormset()
>>> for form in formset.forms:
... print form.as_table()
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
The default value of ``max_num`` is ``0`` which is the same as saying put no
limit on the number forms displayed.
Formset validation Formset validation
------------------ ------------------

View File

@ -31,6 +31,7 @@ __test__ = {'API_TESTS': """
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered ... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data ... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-name': 'Charles Baudelaire', ... 'form-0-name': 'Charles Baudelaire',
... 'form-1-name': 'Arthur Rimbaud', ... 'form-1-name': 'Arthur Rimbaud',
... 'form-2-name': '', ... 'form-2-name': '',
@ -68,6 +69,7 @@ them in alphabetical order by name.
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered ... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '2', # the number of forms with initial data ... 'form-INITIAL_FORMS': '2', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2', ... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud', ... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1', ... 'form-1-id': '1',
@ -111,6 +113,7 @@ deltetion, make sure we don't save that form.
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered ... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data ... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2', ... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud', ... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1', ... 'form-1-id': '1',
@ -140,6 +143,7 @@ Let's edit a record to ensure save only returns that one record.
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered ... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data ... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2', ... 'form-0-id': '2',
... 'form-0-name': 'Walt Whitman', ... 'form-0-name': 'Walt Whitman',
... 'form-1-id': '1', ... 'form-1-id': '1',
@ -158,6 +162,22 @@ True
>>> formset.save() >>> formset.save()
[<Author: Walt Whitman>] [<Author: Walt Whitman>]
Test the behavior of max_num with model formsets. It should properly limit
the queryset to reduce the amount of objects being pulled in when not being
used.
>>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = modelformset_factory(Author, max_num=2)
>>> formset = AuthorFormSet(queryset=qs)
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
>>> AuthorFormSet = modelformset_factory(Author, max_num=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> formset.initial
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}, {'id': 2, 'name': u'Walt Whitman'}]
# Inline Formsets ############################################################ # Inline Formsets ############################################################
We can also create a formset that is tied to a parent model. This is how the We can also create a formset that is tied to a parent model. This is how the
@ -178,6 +198,7 @@ admin system's edit inline functionality works.
>>> data = { >>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data ... 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-title': 'Les Fleurs du Mal', ... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': '', ... 'book_set-1-title': '',
... 'book_set-2-title': '', ... 'book_set-2-title': '',
@ -212,6 +233,7 @@ book.
>>> data = { >>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data ... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1', ... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal', ... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': 'Le Spleen de Paris', ... 'book_set-1-title': 'Le Spleen de Paris',
@ -238,6 +260,7 @@ This is used in the admin for save_as functionality.
>>> data = { >>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data ... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1', ... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal', ... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-id': '2', ... 'book_set-1-id': '2',

View File

@ -20,7 +20,7 @@ but we'll look at how to do so later.
>>> formset = ChoiceFormSet(auto_id=False, prefix='choices') >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
>>> print formset >>> print formset
<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /> <input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_FORMS" value="0" />
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr> <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr> <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
@ -34,6 +34,7 @@ the TOTAL_FORMS field appropriately.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered ... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... } ... }
@ -60,6 +61,7 @@ any of the forms.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered ... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '', ... 'choices-0-votes': '',
... } ... }
@ -90,6 +92,7 @@ Let's simulate what would happen if we submitted this form.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered ... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-1-choice': '', ... 'choices-1-choice': '',
@ -111,6 +114,7 @@ handle that later.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered ... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists', ... 'choices-1-choice': 'The Decemberists',
@ -130,6 +134,7 @@ handle that case later.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered ... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': '', # deleted value ... 'choices-0-choice': '', # deleted value
... 'choices-0-votes': '', # deleted value ... 'choices-0-votes': '', # deleted value
... 'choices-1-choice': '', ... 'choices-1-choice': '',
@ -167,6 +172,7 @@ number of forms to be completed.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered ... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': '', ... 'choices-0-choice': '',
... 'choices-0-votes': '', ... 'choices-0-votes': '',
... 'choices-1-choice': '', ... 'choices-1-choice': '',
@ -187,6 +193,7 @@ We can just fill out one of the forms.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered ... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-1-choice': '', ... 'choices-1-choice': '',
@ -207,6 +214,7 @@ And once again, if we try to partially complete a form, validation will fail.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered ... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists', ... 'choices-1-choice': 'The Decemberists',
@ -267,6 +275,7 @@ To delete something, we just need to set that form's special delete field to
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered ... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-0-DELETE': '', ... 'choices-0-DELETE': '',
@ -316,6 +325,7 @@ something at the front of the list, you'd need to set it's order to 0.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered ... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1', ... 'choices-0-ORDER': '1',
@ -342,6 +352,7 @@ they will be sorted below everything else.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered ... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1', ... 'choices-0-ORDER': '1',
@ -403,6 +414,7 @@ Let's delete Fergie, and put The Decemberists ahead of Calexico.
>>> data = { >>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered ... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data ... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico', ... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100', ... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1', ... 'choices-0-ORDER': '1',
@ -444,12 +456,7 @@ error if there are any duplicates.
... name = CharField() ... name = CharField()
... ...
>>> class FavoriteDrinksFormSet(BaseFormSet): >>> class BaseFavoriteDrinksFormSet(BaseFormSet):
... form = FavoriteDrinkForm
... extra = 2
... can_order = False
... can_delete = False
...
... def clean(self): ... def clean(self):
... seen_drinks = [] ... seen_drinks = []
... for drink in self.cleaned_data: ... for drink in self.cleaned_data:
@ -458,11 +465,15 @@ error if there are any duplicates.
... seen_drinks.append(drink['name']) ... seen_drinks.append(drink['name'])
... ...
>>> FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
... formset=BaseFavoriteDrinksFormSet, extra=3)
We start out with a some duplicate data. We start out with a some duplicate data.
>>> data = { >>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered ... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data ... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
... 'drinks-MAX_FORMS': '0', # the max number of forms
... 'drinks-0-name': 'Gin and Tonic', ... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Gin and Tonic', ... 'drinks-1-name': 'Gin and Tonic',
... } ... }
@ -484,6 +495,7 @@ Make sure we didn't break the valid case.
>>> data = { >>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered ... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data ... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
... 'drinks-MAX_FORMS': '0', # the max number of forms
... 'drinks-0-name': 'Gin and Tonic', ... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Bloody Mary', ... 'drinks-1-name': 'Bloody Mary',
... } ... }
@ -494,6 +506,55 @@ True
>>> for error in formset.non_form_errors(): >>> for error in formset.non_form_errors():
... print error ... print error
# Limiting the maximum number of forms ########################################
# Base case for max_num.
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
>>> formset = LimitedFavoriteDrinkFormSet()
>>> for form in formset.forms:
... print form
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
# Ensure the that max_num has no affect when extra is less than max_forms.
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
>>> formset = LimitedFavoriteDrinkFormSet()
>>> for form in formset.forms:
... print form
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
# max_num with initial data
# More initial forms than max_num will result in only the first max_num of
# them to be displayed with no extra forms.
>>> initial = [
... {'name': 'Gin Tonic'},
... {'name': 'Bloody Mary'},
... {'name': 'Jack and Coke'},
... ]
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
>>> for form in formset.forms:
... print form
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" value="Bloody Mary" id="id_form-1-name" /></td></tr>
# One form from initial and extra=3 with max_num=2 should result in the one
# initial form and one extra.
>>> initial = [
... {'name': 'Gin Tonic'},
... ]
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
>>> for form in formset.forms:
... print form
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
# Regression test for #6926 ################################################## # Regression test for #6926 ##################################################