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:
parent
edf396da59
commit
530670e27f
@ -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:
|
||||||
|
@ -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,16 +47,23 @@ 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
|
||||||
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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``
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
@ -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
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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 ##################################################
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user