Fixed #13023 - Removed ambiguity with regard to the max_num option of formsets and as a result of admin inlines. Thanks to Gabriel Hurley for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12872 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-03-27 23:03:56 +00:00
parent 9df8d9c294
commit aba95dcc0b
13 changed files with 138 additions and 88 deletions

View File

@ -225,6 +225,7 @@ answer newbie questions, and generally made Django that much better:
John Huddleston <huddlej@wwu.edu> John Huddleston <huddlej@wwu.edu>
Rob Hudson <http://rob.cogit8.org/> Rob Hudson <http://rob.cogit8.org/>
Jason Huggins <http://www.jrandolph.com/blog/> Jason Huggins <http://www.jrandolph.com/blog/>
Gabriel Hurley <gabriel@strikeawe.com>
Hyun Mi Ae Hyun Mi Ae
Ibon <ibonso@gmail.com> Ibon <ibonso@gmail.com>
Tom Insam Tom Insam

View File

@ -32,8 +32,9 @@
}; };
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
// only show the add button if we are allowed to add more items // only show the add button if we are allowed to add more items,
var showAddButton = ((maxForms.val() == 0) || ((maxForms.val()-totalForms.val()) > 0)); // note that max_num = None translates to a blank string.
var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0;
$(this).each(function(i) { $(this).each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass); $(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
}); });
@ -77,7 +78,7 @@
// Update number of total forms // Update number of total forms
$(totalForms).val(nextIndex + 1); $(totalForms).val(nextIndex + 1);
// Hide add button in case we've hit the max, except we want to add infinitely // Hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() != 0) && (maxForms.val() <= totalForms.val())) { if ((maxForms.val() != '') && (maxForms.val() <= totalForms.val())) {
addButton.parent().hide(); addButton.parent().hide();
} }
// The delete button of each row triggers a bunch of other things // The delete button of each row triggers a bunch of other things
@ -93,7 +94,7 @@
var forms = $("." + options.formCssClass); var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// Show add button again once we drop below max // Show add button again once we drop below max
if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) { if ((maxForms.val() == '') || (maxForms.val() >= forms.length)) {
addButton.parent().show(); addButton.parent().show();
} }
// Also, update names and ids for all remaining form controls // Also, update names and ids for all remaining form controls

View File

@ -1,5 +1,5 @@
(function(a){a.fn.formset=function(f){var b=a.extend({},a.fn.formset.defaults,f),l=function(d,e,j){var c=new RegExp("("+e+"-\\d+)");e=e+"-"+j;a(d).attr("for")&&a(d).attr("for",a(d).attr("for").replace(c,e));if(d.id)d.id=d.id.replace(c,e);if(d.name)d.name=d.name.replace(c,e)};f=a("#id_"+b.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");f=h.val()==0||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)}); (function(a){a.fn.formset=function(f){var b=a.extend({},a.fn.formset.defaults,f),l=function(d,e,j){var c=new RegExp("("+e+"-\\d+)");e=e+"-"+j;a(d).attr("for")&&a(d).attr("for",a(d).attr("for").replace(c,e));if(d.id)d.id=d.id.replace(c,e);if(d.name)d.name=d.name.replace(c,e)};f=a("#id_"+b.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");f=h.val()==""||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)});
if(a(this).length&&f){var i;if(a(this).attr("tagName")=="TR"){f=this.eq(0).children().length;a(this).parent().append('<tr class="'+b.addCssClass+'"><td colspan="'+f+'"><a href="javascript:void(0)">'+b.addText+"</a></tr>");i=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('<div class="'+b.addCssClass+'"><a href="javascript:void(0)">'+b.addText+"</a></div>");i=a(this).filter(":last").next().find("a")}i.click(function(){var d=a("#id_"+b.prefix+"-TOTAL_FORMS"),e=parseInt(d.val()), if(a(this).length&&f){var i;if(a(this).attr("tagName")=="TR"){f=this.eq(0).children().length;a(this).parent().append('<tr class="'+b.addCssClass+'"><td colspan="'+f+'"><a href="javascript:void(0)">'+b.addText+"</a></tr>");i=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('<div class="'+b.addCssClass+'"><a href="javascript:void(0)">'+b.addText+"</a></div>");i=a(this).filter(":last").next().find("a")}i.click(function(){var d=a("#id_"+b.prefix+"-TOTAL_FORMS"),e=parseInt(d.val()),
j=a("#"+b.prefix+"-empty"),c=j.clone(true).get(0);a(c).removeClass(b.emptyCssClass).removeAttr("id").insertBefore(a(j));a(c).html(a(c).html().replace(/__prefix__/g,e));a(c).addClass(b.formCssClass).attr("id",b.prefix+(parseInt(e)+1));if(a(c).is("TR"))a(c).children(":last").append('<div><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></div>");else a(c).is("UL")||a(c).is("OL")?a(c).append('<li><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></li>"): j=a("#"+b.prefix+"-empty"),c=j.clone(true).get(0);a(c).removeClass(b.emptyCssClass).removeAttr("id").insertBefore(a(j));a(c).html(a(c).html().replace(/__prefix__/g,e));a(c).addClass(b.formCssClass).attr("id",b.prefix+(e+1));if(a(c).is("TR"))a(c).children(":last").append('<div><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></div>");else a(c).is("UL")||a(c).is("OL")?a(c).append('<li><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></li>"):
a(c).children(":first").append('<span><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");a(c).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,d.val())});a(d).val(e+1);h.val()!=0&&h.val()<=d.val()&&i.parent().hide();a(c).find("a."+b.deleteCssClass).click(function(){var g=a(this).parents("."+b.formCssClass);g.remove();b.removed&&b.removed(g);g=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(g.length);if(h.val()==0||h.val()>=g.length)i.parent().show(); a(c).children(":first").append('<span><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");a(c).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,d.val())});a(d).val(e+1);h.val()!=""&&h.val()<=d.val()&&i.parent().hide();a(c).find("a."+b.deleteCssClass).click(function(){var g=a(this).parents("."+b.formCssClass);g.remove();b.removed&&b.removed(g);g=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(g.length);if(h.val()==""||h.val()>=g.length)i.parent().show();
for(var k=0,m=g.length;k<m;k++)a(g.get(k)).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,k)});return false});b.added&&b.added(a(c));return false})}return this};a.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(jQuery); for(var k=0,m=g.length;k<m;k++)a(g.get(k)).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,k)});return false});b.added&&b.added(a(c));return false})}return this};a.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(jQuery);

View File

@ -1179,7 +1179,7 @@ class InlineModelAdmin(BaseModelAdmin):
fk_name = None fk_name = None
formset = BaseInlineFormSet formset = BaseInlineFormSet
extra = 3 extra = 3
max_num = 0 max_num = None
template = None template = None
verbose_name = None verbose_name = None
verbose_name_plural = None verbose_name_plural = None

View File

@ -170,11 +170,15 @@ def validate_inline(cls, parent, parent_model):
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True) fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
# extra = 3 # extra = 3
# max_num = 0 if not isinstance(getattr(cls, 'extra'), int):
for attr in ('extra', 'max_num'): raise ImproperlyConfigured("'%s.extra' should be a integer."
if not isinstance(getattr(cls, attr), int): % cls.__name__)
raise ImproperlyConfigured("'%s.%s' should be a integer."
% (cls.__name__, attr)) # max_num = None
max_num = getattr(cls, 'max_num', None)
if max_num is not None and not isinstance(max_num, int):
raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)."
% cls.__name__)
# formset # formset
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet): if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):

View File

@ -337,7 +337,7 @@ def generic_inlineformset_factory(model, form=ModelForm,
ct_field="content_type", fk_field="object_id", ct_field="content_type", fk_field="object_id",
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, max_num=None,
formfield_callback=lambda f: f.formfield()): formfield_callback=lambda f: f.formfield()):
""" """
Returns an ``GenericInlineFormSet`` for the given kwargs. Returns an ``GenericInlineFormSet`` for the given kwargs.

View File

@ -25,7 +25,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_NUM_FORM_COUNT] = IntegerField(widget=HiddenInput) self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs) super(ManagementForm, self).__init__(*args, **kwargs)
class BaseFormSet(StrAndUnicode): class BaseFormSet(StrAndUnicode):
@ -69,8 +69,13 @@ class BaseFormSet(StrAndUnicode):
if self.data or self.files: if self.data or self.files:
return self.management_form.cleaned_data[TOTAL_FORM_COUNT] return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
else: else:
total_forms = self.initial_form_count() + self.extra initial_forms = self.initial_form_count()
if total_forms > self.max_num > 0: total_forms = initial_forms + self.extra
# Allow all existing related objects/inlines to be displayed,
# but don't allow extra beyond max_num.
if initial_forms > self.max_num >= 0:
total_forms = initial_forms
elif total_forms > self.max_num >= 0:
total_forms = self.max_num total_forms = self.max_num
return total_forms return total_forms
@ -81,7 +86,7 @@ class BaseFormSet(StrAndUnicode):
else: else:
# Use the length of the inital data if it's there, 0 otherwise. # Use the length of the inital data if it's there, 0 otherwise.
initial_forms = self.initial and len(self.initial) or 0 initial_forms = self.initial and len(self.initial) or 0
if initial_forms > self.max_num > 0: if initial_forms > self.max_num >= 0:
initial_forms = self.max_num initial_forms = self.max_num
return initial_forms return initial_forms
@ -324,7 +329,7 @@ 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, max_num=0): can_delete=False, max_num=None):
"""Return a FormSet for the given form class.""" """Return a FormSet for the given form class."""
attrs = {'form': form, 'extra': extra, attrs = {'form': form, 'extra': extra,
'can_order': can_order, 'can_delete': can_delete, 'can_order': can_order, 'can_delete': can_delete,

View File

@ -448,10 +448,10 @@ class BaseModelFormSet(BaseFormSet):
if not qs.ordered: if not qs.ordered:
qs = qs.order_by(self.model._meta.pk.name) qs = qs.order_by(self.model._meta.pk.name)
if self.max_num > 0: # Removed queryset limiting here. As per discussion re: #13023
self._queryset = qs[:self.max_num] # on django-dev, max_num should not prevent existing
else: # related objects/inlines from being displayed.
self._queryset = qs self._queryset = qs
return self._queryset return self._queryset
def save_new(self, form, commit=True): def save_new(self, form, commit=True):
@ -649,7 +649,7 @@ class BaseModelFormSet(BaseFormSet):
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,
max_num=0, fields=None, exclude=None): max_num=None, fields=None, exclude=None):
""" """
Returns a FormSet class for the given Django model class. Returns a FormSet class for the given Django model class.
""" """
@ -799,7 +799,7 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
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, max_num=0, extra=3, can_order=False, can_delete=True, max_num=None,
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.

View File

@ -71,7 +71,7 @@ Limiting the maximum number of forms
------------------------------------ ------------------------------------
The ``max_num`` parameter to ``formset_factory`` gives you the ability to The ``max_num`` parameter to ``formset_factory`` gives you the ability to
force the maximum number of forms the formset will display:: limit the maximum number of empty forms the formset will display::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1) >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormset() >>> formset = ArticleFormset()
@ -80,8 +80,20 @@ force the maximum number of forms the formset will display::
<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-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> <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>
A ``max_num`` value of ``0`` (the default) puts no limit on the number forms .. versionchanged:: 1.2
displayed.
If the value of ``max_num`` is geater than the number of existing related
objects, up to ``extra`` additional blank forms will be added to the formset,
so long as the total number of forms does not exceed ``max_num``.
A ``max_num`` value of ``None`` (the default) puts no limit on the number of
forms displayed. Please note that the default value of ``max_num`` was changed
from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value.
.. versionadded:: 1.2
The dynamic "Add Another" link in the Django admin will not appear if
``max_num`` is less than the number of currently displayed forms.
Formset validation Formset validation
------------------ ------------------
@ -102,7 +114,7 @@ provide an invalid article::
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': u'2', ... 'form-TOTAL_FORMS': u'2',
... 'form-INITIAL_FORMS': u'0', ... 'form-INITIAL_FORMS': u'0',
... 'form-MAX_NUM_FORMS': u'0', ... 'form-MAX_NUM_FORMS': u'',
... 'form-0-title': u'Test', ... 'form-0-title': u'Test',
... 'form-0-pub_date': u'16 June 1904', ... 'form-0-pub_date': u'16 June 1904',
... 'form-1-title': u'Test', ... 'form-1-title': u'Test',
@ -190,7 +202,7 @@ is where you define your own validation that works at the formset level::
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': u'2', ... 'form-TOTAL_FORMS': u'2',
... 'form-INITIAL_FORMS': u'0', ... 'form-INITIAL_FORMS': u'0',
... 'form-MAX_NUM_FORMS': u'0', ... 'form-MAX_NUM_FORMS': u'',
... 'form-0-title': u'Test', ... 'form-0-title': u'Test',
... 'form-0-pub_date': u'16 June 1904', ... 'form-0-pub_date': u'16 June 1904',
... 'form-1-title': u'Test', ... 'form-1-title': u'Test',
@ -249,7 +261,7 @@ happen when the user changes these values::
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': u'3', ... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2', ... 'form-INITIAL_FORMS': u'2',
... 'form-MAX_NUM_FORMS': u'0', ... 'form-MAX_NUM_FORMS': u'',
... 'form-0-title': u'Article #1', ... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10', ... 'form-0-pub_date': u'2008-05-10',
... 'form-0-ORDER': u'2', ... 'form-0-ORDER': u'2',
@ -287,7 +299,7 @@ Lets create a formset with the ability to delete::
... ]) ... ])
>>> for form in formset.forms: >>> for form in formset.forms:
.... print form.as_table() .... print form.as_table()
<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" value="0" id="id_form-MAX_NUM_FORMS" /> <input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" 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" value="2008-05-10" id="id_form-0-pub_date" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr> <tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
@ -305,7 +317,7 @@ delete fields you can access them with ``deleted_forms``::
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': u'3', ... 'form-TOTAL_FORMS': u'3',
... 'form-INITIAL_FORMS': u'2', ... 'form-INITIAL_FORMS': u'2',
... 'form-MAX_NUM_FORMS': u'0', ... 'form-MAX_NUM_FORMS': u'',
... 'form-0-title': u'Article #1', ... 'form-0-title': u'Article #1',
... 'form-0-pub_date': u'2008-05-10', ... 'form-0-pub_date': u'2008-05-10',
... 'form-0-DELETE': u'on', ... 'form-0-DELETE': u'on',

View File

@ -557,7 +557,7 @@ with the ``Author`` model. It works just like a regular formset::
>>> formset = AuthorFormSet() >>> formset = AuthorFormSet()
>>> print formset >>> print formset
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" value="0" id="id_form-MAX_NUM_FORMS" /> <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr> <tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title"> <tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected="selected">---------</option> <option value="" selected="selected">---------</option>
@ -653,22 +653,24 @@ are saved properly.
Limiting the number of editable objects Limiting the number of editable objects
--------------------------------------- ---------------------------------------
.. versionchanged:: 1.2
As with regular formsets, you can use the ``max_num`` parameter to As with regular formsets, you can use the ``max_num`` parameter to
``modelformset_factory`` to limit the number of forms displayed. With ``modelformset_factory`` to limit the number of extra forms displayed.
model formsets, this property limits the query to select only the maximum
number of objects needed:: ``max_num`` does not prevent existing objects from being displayed::
>>> Author.objects.order_by('name') >>> Author.objects.order_by('name')
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>] [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1) >>> AuthorFormSet = modelformset_factory(Author, max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> formset.initial >>> [x.name for x in formset.get_queryset()]
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}] [u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman']
If the value of ``max_num`` is higher than the number of objects returned, up to If the value of ``max_num`` is geater than the number of existing related
``extra`` additional blank forms will be added to the formset, so long as the objects, up to ``extra`` additional blank forms will be added to the formset,
total number of forms does not exceed ``max_num``:: so long as the total number of forms does not exceed ``max_num``::
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2) >>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
@ -679,6 +681,11 @@ total number of forms does not exceed ``max_num``::
<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-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> <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>
.. versionchanged:: 1.2
A ``max_num`` value of ``None`` (the default) puts no limit on the number of
forms displayed.
Using a model formset in a view Using a model formset in a view
------------------------------- -------------------------------

View File

@ -200,7 +200,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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # 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': '',
@ -238,7 +238,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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # 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',
@ -282,7 +282,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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # 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',
@ -312,7 +312,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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # 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',
@ -343,7 +343,7 @@ Test the behavior of commit=False and save_m2m
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered ... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data ... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
... 'form-MAX_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # the max number of forms
... 'form-0-id': '1', ... 'form-0-id': '1',
... 'form-0-name': '2nd Tuesday of the Week Meeting', ... 'form-0-name': '2nd Tuesday of the Week Meeting',
... 'form-0-authors': [2, 1, 3, 4], ... 'form-0-authors': [2, 1, 3, 4],
@ -365,18 +365,38 @@ True
# delete the author we created to allow later tests to continue working. # delete the author we created to allow later tests to continue working.
>>> new_author.delete() >>> new_author.delete()
Test the behavior of max_num with model formsets. It should properly limit Test the behavior of max_num with model formsets. It should allow all existing
the queryset to reduce the amount of objects being pulled in when not being related objects/inlines for a given object to be displayed, but not allow
used. the creation of new inlines beyond max_num.
>>> qs = Author.objects.order_by('name') >>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = modelformset_factory(Author, max_num=2) >>> AuthorFormSet = modelformset_factory(Author, max_num=None, extra=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> len(formset.extra_forms)
3
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> len(formset.extra_forms)
1
>>> AuthorFormSet = modelformset_factory(Author, max_num=0, extra=3)
>>> formset = AuthorFormSet(queryset=qs)
>>> len(formset.extra_forms)
0
>>> AuthorFormSet = modelformset_factory(Author, max_num=None)
>>> formset = AuthorFormSet(queryset=qs) >>> formset = AuthorFormSet(queryset=qs)
>>> [x.name for x in formset.get_queryset()] >>> [x.name for x in formset.get_queryset()]
[u'Charles Baudelaire', u'Paul Verlaine'] [u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman']
>>> AuthorFormSet = modelformset_factory(Author, max_num=3) >>> AuthorFormSet = modelformset_factory(Author, max_num=0)
>>> formset = AuthorFormSet(queryset=qs)
>>> [x.name for x in formset.get_queryset()]
[u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman']
>>> AuthorFormSet = modelformset_factory(Author, max_num=4)
>>> formset = AuthorFormSet(queryset=qs) >>> formset = AuthorFormSet(queryset=qs)
>>> [x.name for x in formset.get_queryset()] >>> [x.name for x in formset.get_queryset()]
[u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman'] [u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman']
@ -398,7 +418,7 @@ used.
>>> 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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # the max number of forms
... 'form-0-name': 'Walt Whitman', ... 'form-0-name': 'Walt Whitman',
... 'form-1-name': 'Charles Baudelaire', ... 'form-1-name': 'Charles Baudelaire',
... 'form-2-name': '', ... 'form-2-name': '',
@ -425,7 +445,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '1', # the number of forms rendered ... 'form-TOTAL_FORMS': '1', # 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_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # the max number of forms
... 'form-0-author_ptr': '', ... 'form-0-author_ptr': '',
... 'form-0-name': 'Ernest Hemingway', ... 'form-0-name': 'Ernest Hemingway',
... 'form-0-write_speed': '10', ... 'form-0-write_speed': '10',
@ -449,7 +469,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered ... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data ... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
... 'form-MAX_NUM_FORMS': '0', # the max number of forms ... 'form-MAX_NUM_FORMS': '', # the max number of forms
... 'form-0-author_ptr': hemingway_id, ... 'form-0-author_ptr': hemingway_id,
... 'form-0-name': 'Ernest Hemingway', ... 'form-0-name': 'Ernest Hemingway',
... 'form-0-write_speed': '10', ... 'form-0-write_speed': '10',
@ -484,7 +504,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_NUM_FORMS': '0', # the max number of forms ... 'book_set-MAX_NUM_FORMS': '', # 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': '',
@ -519,7 +539,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_NUM_FORMS': '0', # the max number of forms ... 'book_set-MAX_NUM_FORMS': '', # 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': 'Les Paradis Artificiels', ... 'book_set-1-title': 'Les Paradis Artificiels',
@ -546,7 +566,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_NUM_FORMS': '0', # the max number of forms ... 'book_set-MAX_NUM_FORMS': '', # 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',
@ -584,7 +604,7 @@ Test inline formsets where the inline-edited object has a custom primary key tha
>>> data = { >>> data = {
... 'bookwithcustompk_set-TOTAL_FORMS': '1', # the number of forms rendered ... 'bookwithcustompk_set-TOTAL_FORMS': '1', # the number of forms rendered
... 'bookwithcustompk_set-INITIAL_FORMS': '0', # the number of forms with initial data ... 'bookwithcustompk_set-INITIAL_FORMS': '0', # the number of forms with initial data
... 'bookwithcustompk_set-MAX_NUM_FORMS': '0', # the max number of forms ... 'bookwithcustompk_set-MAX_NUM_FORMS': '', # the max number of forms
... 'bookwithcustompk_set-0-my_pk': '77777', ... 'bookwithcustompk_set-0-my_pk': '77777',
... 'bookwithcustompk_set-0-title': 'Les Fleurs du Mal', ... 'bookwithcustompk_set-0-title': 'Les Fleurs du Mal',
... } ... }
@ -615,7 +635,7 @@ has a non AutoField yet auto-created primary key.
>>> data = { >>> data = {
... 'alternatebook_set-TOTAL_FORMS': '1', # the number of forms rendered ... 'alternatebook_set-TOTAL_FORMS': '1', # the number of forms rendered
... 'alternatebook_set-INITIAL_FORMS': '0', # the number of forms with initial data ... 'alternatebook_set-INITIAL_FORMS': '0', # the number of forms with initial data
... 'alternatebook_set-MAX_NUM_FORMS': '0', # the max number of forms ... 'alternatebook_set-MAX_NUM_FORMS': '', # the max number of forms
... 'alternatebook_set-0-title': 'Flowers of Evil', ... 'alternatebook_set-0-title': 'Flowers of Evil',
... 'alternatebook_set-0-notes': 'English translation of Les Fleurs du Mal' ... 'alternatebook_set-0-notes': 'English translation of Les Fleurs du Mal'
... } ... }
@ -644,7 +664,7 @@ True
>>> data = { >>> data = {
... 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered ... 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'poem_set-INITIAL_FORMS': '0', # the number of forms with initial data ... 'poem_set-INITIAL_FORMS': '0', # the number of forms with initial data
... 'poem_set-MAX_NUM_FORMS': '0', # the max number of forms ... 'poem_set-MAX_NUM_FORMS': '', # the max number of forms
... 'poem_set-0-name': 'The Cloud in Trousers', ... 'poem_set-0-name': 'The Cloud in Trousers',
... 'poem_set-1-name': 'I', ... 'poem_set-1-name': 'I',
... 'poem_set-2-name': '', ... 'poem_set-2-name': '',
@ -673,7 +693,7 @@ We can provide a custom queryset to our InlineFormSet:
>>> data = { >>> data = {
... 'book_set-TOTAL_FORMS': '5', # the number of forms rendered ... 'book_set-TOTAL_FORMS': '5', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '3', # the number of forms with initial data ... 'book_set-INITIAL_FORMS': '3', # the number of forms with initial data
... 'book_set-MAX_NUM_FORMS': '0', # the max number of forms ... 'book_set-MAX_NUM_FORMS': '', # 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',
@ -697,7 +717,7 @@ True
>>> 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_NUM_FORMS': '0', # the max number of forms ... 'book_set-MAX_NUM_FORMS': '', # the max number of forms
... 'book_set-0-id': '5', ... 'book_set-0-id': '5',
... 'book_set-0-title': 'Flowers of Evil', ... 'book_set-0-title': 'Flowers of Evil',
... 'book_set-1-title': 'Revue des deux mondes', ... 'book_set-1-title': 'Revue des deux mondes',
@ -734,7 +754,7 @@ We need to ensure that it is displayed
>>> data = { >>> data = {
... 'owner_set-TOTAL_FORMS': '2', ... 'owner_set-TOTAL_FORMS': '2',
... 'owner_set-INITIAL_FORMS': '0', ... 'owner_set-INITIAL_FORMS': '0',
... 'owner_set-MAX_NUM_FORMS': '0', ... 'owner_set-MAX_NUM_FORMS': '',
... 'owner_set-0-auto_id': '', ... 'owner_set-0-auto_id': '',
... 'owner_set-0-name': u'Joe Perry', ... 'owner_set-0-name': u'Joe Perry',
... 'owner_set-1-auto_id': '', ... 'owner_set-1-auto_id': '',
@ -756,7 +776,7 @@ True
>>> data = { >>> data = {
... 'owner_set-TOTAL_FORMS': '3', ... 'owner_set-TOTAL_FORMS': '3',
... 'owner_set-INITIAL_FORMS': '1', ... 'owner_set-INITIAL_FORMS': '1',
... 'owner_set-MAX_NUM_FORMS': '0', ... 'owner_set-MAX_NUM_FORMS': '',
... 'owner_set-0-auto_id': u'1', ... 'owner_set-0-auto_id': u'1',
... 'owner_set-0-name': u'Joe Perry', ... 'owner_set-0-name': u'Joe Perry',
... 'owner_set-1-auto_id': '', ... 'owner_set-1-auto_id': '',
@ -848,7 +868,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '1', ... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-slug': 'car-red', ... 'form-0-slug': 'car-red',
... } ... }
>>> formset = FormSet(data) >>> formset = FormSet(data)
@ -860,7 +880,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '1', ... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-slug': 'car-red', ... 'form-0-slug': 'car-red',
... } ... }
>>> formset = FormSet(data) >>> formset = FormSet(data)
@ -875,7 +895,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '1', ... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-price': u'12.00', ... 'form-0-price': u'12.00',
... 'form-0-quantity': '1', ... 'form-0-quantity': '1',
... } ... }
@ -888,7 +908,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '1', ... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-price': u'12.00', ... 'form-0-price': u'12.00',
... 'form-0-quantity': '1', ... 'form-0-quantity': '1',
... } ... }
@ -906,7 +926,7 @@ False
>>> data = { >>> data = {
... 'revision_set-TOTAL_FORMS': '1', ... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0', ... 'revision_set-INITIAL_FORMS': '0',
... 'revision_set-MAX_NUM_FORMS': '0', ... 'revision_set-MAX_NUM_FORMS': '',
... 'revision_set-0-repository': repository.pk, ... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76', ... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '', ... 'revision_set-0-DELETE': '',
@ -921,7 +941,7 @@ True
>>> data = { >>> data = {
... 'revision_set-TOTAL_FORMS': '1', ... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0', ... 'revision_set-INITIAL_FORMS': '0',
... 'revision_set-MAX_NUM_FORMS': '0', ... 'revision_set-MAX_NUM_FORMS': '',
... 'revision_set-0-repository': repository.pk, ... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76', ... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '', ... 'revision_set-0-DELETE': '',
@ -939,7 +959,7 @@ False
>>> data = { >>> data = {
... 'revision_set-TOTAL_FORMS': '1', ... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0', ... 'revision_set-INITIAL_FORMS': '0',
... 'revision_set-MAX_NUM_FORMS': '0', ... 'revision_set-MAX_NUM_FORMS': '',
... 'revision_set-0-repository': repository.pk, ... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76', ... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '', ... 'revision_set-0-DELETE': '',
@ -969,7 +989,7 @@ False
>>> data = { >>> data = {
... 'membership_set-TOTAL_FORMS': '1', ... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0', ... 'membership_set-INITIAL_FORMS': '0',
... 'membership_set-MAX_NUM_FORMS': '0', ... 'membership_set-MAX_NUM_FORMS': '',
... 'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), ... 'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
... 'membership_set-0-karma': '', ... 'membership_set-0-karma': '',
@ -984,7 +1004,7 @@ True
>>> filled_data = { >>> filled_data = {
... 'membership_set-TOTAL_FORMS': '1', ... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0', ... 'membership_set-INITIAL_FORMS': '0',
... 'membership_set-MAX_NUM_FORMS': '0', ... 'membership_set-MAX_NUM_FORMS': '',
... 'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')), ... 'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')),
... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
... 'membership_set-0-karma': '', ... 'membership_set-0-karma': '',
@ -1007,7 +1027,7 @@ False
>>> data = { >>> data = {
... 'membership_set-TOTAL_FORMS': '1', ... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0', ... 'membership_set-INITIAL_FORMS': '0',
... 'membership_set-MAX_NUM_FORMS': '0', ... 'membership_set-MAX_NUM_FORMS': '',
... 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')), ... 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')),
... 'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')), ... 'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')),
... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
@ -1043,7 +1063,7 @@ True
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': 2, ... 'form-TOTAL_FORMS': 2,
... 'form-INITIAL_FORMS': 0, ... 'form-INITIAL_FORMS': 0,
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-slug': 'red_car', ... 'form-0-slug': 'red_car',
... 'form-1-slug': 'red_car', ... 'form-1-slug': 'red_car',
... } ... }
@ -1057,7 +1077,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': 2, ... 'form-TOTAL_FORMS': 2,
... 'form-INITIAL_FORMS': 0, ... 'form-INITIAL_FORMS': 0,
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-price': '25', ... 'form-0-price': '25',
... 'form-0-quantity': '7', ... 'form-0-quantity': '7',
... 'form-1-price': '25', ... 'form-1-price': '25',
@ -1075,7 +1095,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', ... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... 'form-0-price': '24', ... 'form-0-price': '24',
... 'form-1-price': '24', ... 'form-1-price': '24',
... } ... }
@ -1089,7 +1109,7 @@ True
>>> data = { >>> data = {
... 'book_set-TOTAL_FORMS': '2', ... 'book_set-TOTAL_FORMS': '2',
... 'book_set-INITIAL_FORMS': '2', ... 'book_set-INITIAL_FORMS': '2',
... 'book_set-MAX_NUM_FORMS': '0', ... 'book_set-MAX_NUM_FORMS': '',
... ...
... 'book_set-0-title': 'The 2008 Election', ... 'book_set-0-title': 'The 2008 Election',
... 'book_set-0-author': str(author.id), ... 'book_set-0-author': str(author.id),
@ -1111,7 +1131,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', ... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... ...
... 'form-0-title': 'blah', ... 'form-0-title': 'blah',
... 'form-0-slug': 'Morning', ... 'form-0-slug': 'Morning',
@ -1133,7 +1153,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', ... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... ...
... 'form-0-title': 'foo', ... 'form-0-title': 'foo',
... 'form-0-slug': 'Morning in Prague', ... 'form-0-slug': 'Morning in Prague',
@ -1153,7 +1173,7 @@ False
>>> data = { >>> data = {
... 'form-TOTAL_FORMS': '2', ... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0', ... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '0', ... 'form-MAX_NUM_FORMS': '',
... ...
... 'form-0-title': 'foo', ... 'form-0-title': 'foo',
... 'form-0-slug': 'Morning in Prague', ... 'form-0-slug': 'Morning in Prague',

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-MAX_NUM_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_NUM_FORMS" />
<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>

View File

@ -919,7 +919,7 @@ ImproperlyConfigured: 'ValidationTestInline.extra' should be a integer.
>>> validate(ValidationTestModelAdmin, ValidationTestModel) >>> validate(ValidationTestModelAdmin, ValidationTestModel)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ImproperlyConfigured: 'ValidationTestInline.max_num' should be a integer. ImproperlyConfigured: 'ValidationTestInline.max_num' should be an integer or None (default).
>>> class ValidationTestInline(TabularInline): >>> class ValidationTestInline(TabularInline):
... model = ValidationTestInlineModel ... model = ValidationTestInlineModel