diff --git a/django/contrib/admin/media/js/inlines.js b/django/contrib/admin/media/js/inlines.js
index c45ce58bd2b..aae2b294d0e 100644
--- a/django/contrib/admin/media/js/inlines.js
+++ b/django/contrib/admin/media/js/inlines.js
@@ -20,29 +20,24 @@
var updateElementIndex = function(el, prefix, ndx) {
var id_regex = new RegExp("(" + prefix + "-\\d+)");
var replacement = prefix + "-" + ndx;
- if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
- if (el.id) el.id = el.id.replace(id_regex, replacement);
- if (el.name) el.name = el.name.replace(id_regex, replacement);
+ if ($(el).attr("for")) {
+ $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
+ }
+ if (el.id) {
+ el.id = el.id.replace(id_regex, replacement);
+ }
+ if (el.name) {
+ el.name = el.name.replace(id_regex, replacement);
+ }
};
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var initialForms = $("#id_" + options.prefix + "-INITIAL_FORMS");
- var maxForms = parseInt(totalForms.val());
+ var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS");
// only show the add button if we are allowed to add more items
- var showAddButton = (maxForms - parseInt(initialForms.val())) > 0;
+ var showAddButton = ((maxForms.val() == 0) || ((maxForms.val()-totalForms.val()) > 0));
var selectedItems = this;
$(this).each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
- // hide the extras, but only if there were no form errors
- if (!$(".errornote").html()) {
- var relatedItems = $(selectedItems).not("." + options.emptyCssClass);
- extraRows = relatedItems.length;
- if (parseInt(initialForms.val()) >= 0) {
- $(relatedItems).slice(initialForms.val()).remove();
- } else {
- $(relatedItems).remove();
- }
- totalForms.val(parseInt(initialForms.val()));
- }
});
if ($(this).length && showAddButton) {
var addButton;
@@ -58,9 +53,8 @@
addButton = $(this).filter(":last").next().find("a");
}
addButton.click(function() {
- var totalForms = parseInt($("#id_" + options.prefix + "-TOTAL_FORMS").val());
- var initialForms = parseInt($("#id_" + options.prefix + "-INITIAL_FORMS").val());
- var nextIndex = totalForms + 1;
+ var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
+ var nextIndex = parseInt(totalForms.val()) + 1;
var template = $("#" + options.prefix + "-empty");
var row = template.clone(true).get(0);
$(row).removeClass(options.emptyCssClass).removeAttr("id").insertBefore($(template));
@@ -79,10 +73,13 @@
// last child element of the form's container:
$(row).children(":first").append('' + options.deleteText + "");
}
+ $(row).find("input,select,textarea,label").each(function() {
+ updateElementIndex(this, options.prefix, totalForms.val());
+ });
// Update number of total forms
- $("#id_" + options.prefix + "-TOTAL_FORMS").val(nextIndex);
- // Hide add button in case we've hit the max
- if (maxForms <= nextIndex) {
+ $(totalForms).val(nextIndex);
+ // Hide add button in case we've hit the max, except we want to add infinitely
+ if ((maxForms.val() != 0) && (maxForms.val() <= totalForms.val())) {
addButton.parent().hide();
}
// The delete button of each row triggers a bunch of other things
@@ -91,44 +88,45 @@
var row = $(this).parents("." + options.formCssClass);
row.remove();
// If a post-delete callback was provided, call it with the deleted form:
- if (options.removed) options.removed(row);
+ if (options.removed) {
+ options.removed(row);
+ }
// Update the TOTAL_FORMS form count.
var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// Show add button again once we drop below max
- if (maxForms >= forms.length) {
+ if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) {
addButton.parent().show();
}
// Also, update names and ids for all remaining form controls
// so they remain in sequence:
- for (var i=0, formCount=forms.length; i0;var e=this;a(this).each(function(m){a(this).not("."+k.emptyCssClass).addClass(k.formCssClass);if(!a(".errornote").html()){var l=a(e).not("."+k.emptyCssClass);extraRows=l.length;if(parseInt(c.val())>=0){a(l).slice(c.val()).remove()}else{a(l).remove()}f.val(parseInt(c.val()))}});if(a(this).length&&i){var g;if(a(this).attr("tagName")=="TR"){var d=this.eq(0).children().length;a(this).parent().append('
"):a(c).children(":first").append(''+b.deleteText+"");a(c).find("input,select,textarea,label").each(function(){l(this,b.prefix,d.val())});a(d).val(e);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();for(var k=0,m=g.length;k
\ No newline at end of file
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html
index 96ea6b6843a..5c69bad24d8 100644
--- a/django/contrib/admin/templates/admin/edit_inline/tabular.html
+++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -65,23 +65,24 @@
\ No newline at end of file
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index a86c18f1385..58af0ac23b8 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -12,6 +12,7 @@ __all__ = ('BaseFormSet', 'all_valid')
# special field names
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
+MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
@@ -24,6 +25,7 @@ class ManagementForm(Form):
def __init__(self, *args, **kwargs):
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
+ self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
class BaseFormSet(StrAndUnicode):
@@ -56,7 +58,8 @@ class BaseFormSet(StrAndUnicode):
else:
form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
TOTAL_FORM_COUNT: self.total_form_count(),
- INITIAL_FORM_COUNT: self.initial_form_count()
+ INITIAL_FORM_COUNT: self.initial_form_count(),
+ MAX_NUM_FORM_COUNT: self.max_num
})
return form
management_form = property(_management_form)
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
index 702523eb7c0..e8c3e98ac7c 100644
--- a/tests/modeltests/model_formsets/models.py
+++ b/tests/modeltests/model_formsets/models.py
@@ -200,6 +200,7 @@ __test__ = {'API_TESTS': """
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-name': 'Charles Baudelaire',
... 'form-1-name': 'Arthur Rimbaud',
... 'form-2-name': '',
@@ -237,6 +238,7 @@ them in alphabetical order by name.
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
@@ -280,6 +282,7 @@ deltetion, make sure we don't save that form.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
@@ -309,6 +312,7 @@ Let's edit a record to ensure save only returns that one record.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Walt Whitman',
... 'form-1-id': '1',
@@ -339,6 +343,7 @@ Test the behavior of commit=False and save_m2m
>>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-id': '1',
... 'form-0-name': '2nd Tuesday of the Week Meeting',
... 'form-0-authors': [2, 1, 3, 4],
@@ -393,6 +398,7 @@ used.
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-name': 'Walt Whitman',
... 'form-1-name': 'Charles Baudelaire',
... 'form-2-name': '',
@@ -419,6 +425,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': '1', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-author_ptr': '',
... 'form-0-name': 'Ernest Hemingway',
... 'form-0-write_speed': '10',
@@ -442,6 +449,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'form-MAX_NUM_FORMS': '0', # the max number of forms
... 'form-0-author_ptr': hemingway_id,
... 'form-0-name': 'Ernest Hemingway',
... 'form-0-write_speed': '10',
@@ -476,6 +484,7 @@ admin system's edit inline functionality works.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... '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-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': '',
... 'book_set-2-title': '',
@@ -510,6 +519,7 @@ book.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... '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-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': 'Les Paradis Artificiels',
@@ -536,6 +546,7 @@ This is used in the admin for save_as functionality.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... '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-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-id': '2',
@@ -573,6 +584,7 @@ Test inline formsets where the inline-edited object has a custom primary key tha
>>> data = {
... 'bookwithcustompk_set-TOTAL_FORMS': '1', # the number of forms rendered
... '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-0-my_pk': '77777',
... 'bookwithcustompk_set-0-title': 'Les Fleurs du Mal',
... }
@@ -603,6 +615,7 @@ has a non AutoField yet auto-created primary key.
>>> data = {
... 'alternatebook_set-TOTAL_FORMS': '1', # the number of forms rendered
... '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-0-title': 'Flowers of Evil',
... 'alternatebook_set-0-notes': 'English translation of Les Fleurs du Mal'
... }
@@ -631,6 +644,7 @@ True
>>> data = {
... 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
... '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-0-name': 'The Cloud in Trousers',
... 'poem_set-1-name': 'I',
... 'poem_set-2-name': '',
@@ -659,6 +673,7 @@ We can provide a custom queryset to our InlineFormSet:
>>> data = {
... 'book_set-TOTAL_FORMS': '5', # the number of forms rendered
... '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-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-id': '2',
@@ -682,6 +697,7 @@ True
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... '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-0-id': '5',
... 'book_set-0-title': 'Flowers of Evil',
... 'book_set-1-title': 'Revue des deux mondes',
@@ -718,6 +734,7 @@ We need to ensure that it is displayed
>>> data = {
... 'owner_set-TOTAL_FORMS': '2',
... 'owner_set-INITIAL_FORMS': '0',
+... 'owner_set-MAX_NUM_FORMS': '0',
... 'owner_set-0-auto_id': '',
... 'owner_set-0-name': u'Joe Perry',
... 'owner_set-1-auto_id': '',
@@ -739,6 +756,7 @@ True
>>> data = {
... 'owner_set-TOTAL_FORMS': '3',
... 'owner_set-INITIAL_FORMS': '1',
+... 'owner_set-MAX_NUM_FORMS': '0',
... 'owner_set-0-auto_id': u'1',
... 'owner_set-0-name': u'Joe Perry',
... 'owner_set-1-auto_id': '',
@@ -767,7 +785,8 @@ True
>>> owner = Owner.objects.get(name=u'Joe Perry')
>>> FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
-
+>>> FormSet.max_num
+1
>>> formset = FormSet(instance=owner)
>>> for form in formset.forms:
... print form.as_p()
@@ -776,6 +795,7 @@ True
>>> data = {
... 'ownerprofile-TOTAL_FORMS': '1',
... 'ownerprofile-INITIAL_FORMS': '0',
+... 'ownerprofile-MAX_NUM_FORMS': '1',
... 'ownerprofile-0-owner': '',
... 'ownerprofile-0-age': u'54',
... }
@@ -784,7 +804,6 @@ True
True
>>> formset.save()
[]
-
>>> formset = FormSet(instance=owner)
>>> for form in formset.forms:
... print form.as_p()
@@ -793,6 +812,7 @@ True
>>> data = {
... 'ownerprofile-TOTAL_FORMS': '1',
... 'ownerprofile-INITIAL_FORMS': '1',
+... 'ownerprofile-MAX_NUM_FORMS': '1',
... 'ownerprofile-0-owner': u'1',
... 'ownerprofile-0-age': u'55',
... }
@@ -805,6 +825,8 @@ True
# ForeignKey with unique=True should enforce max_num=1
>>> FormSet = inlineformset_factory(Place, Location, can_delete=False)
+>>> FormSet.max_num
+1
>>> formset = FormSet(instance=place)
>>> for form in formset.forms:
... print form.as_p()
@@ -826,6 +848,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-slug': 'car-red',
... }
>>> formset = FormSet(data)
@@ -837,6 +860,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-slug': 'car-red',
... }
>>> formset = FormSet(data)
@@ -851,6 +875,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-price': u'12.00',
... 'form-0-quantity': '1',
... }
@@ -863,6 +888,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-price': u'12.00',
... 'form-0-quantity': '1',
... }
@@ -880,6 +906,7 @@ False
>>> data = {
... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0',
+... 'revision_set-MAX_NUM_FORMS': '0',
... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '',
@@ -894,6 +921,7 @@ True
>>> data = {
... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0',
+... 'revision_set-MAX_NUM_FORMS': '0',
... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '',
@@ -911,6 +939,7 @@ False
>>> data = {
... 'revision_set-TOTAL_FORMS': '1',
... 'revision_set-INITIAL_FORMS': '0',
+... 'revision_set-MAX_NUM_FORMS': '0',
... 'revision_set-0-repository': repository.pk,
... 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
... 'revision_set-0-DELETE': '',
@@ -940,6 +969,7 @@ False
>>> data = {
... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0',
+... 'membership_set-MAX_NUM_FORMS': '0',
... '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': '',
@@ -954,6 +984,7 @@ True
>>> filled_data = {
... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0',
+... 'membership_set-MAX_NUM_FORMS': '0',
... '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')),
... 'membership_set-0-karma': '',
@@ -976,6 +1007,7 @@ False
>>> data = {
... 'membership_set-TOTAL_FORMS': '1',
... 'membership_set-INITIAL_FORMS': '0',
+... 'membership_set-MAX_NUM_FORMS': '0',
... 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')),
... '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')),
@@ -1011,6 +1043,7 @@ True
>>> data = {
... 'form-TOTAL_FORMS': 2,
... 'form-INITIAL_FORMS': 0,
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-slug': 'red_car',
... 'form-1-slug': 'red_car',
... }
@@ -1024,6 +1057,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': 2,
... 'form-INITIAL_FORMS': 0,
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-price': '25',
... 'form-0-quantity': '7',
... 'form-1-price': '25',
@@ -1041,6 +1075,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
... 'form-0-price': '24',
... 'form-1-price': '24',
... }
@@ -1054,6 +1089,7 @@ True
>>> data = {
... 'book_set-TOTAL_FORMS': '2',
... 'book_set-INITIAL_FORMS': '2',
+... 'book_set-MAX_NUM_FORMS': '0',
...
... 'book_set-0-title': 'The 2008 Election',
... 'book_set-0-author': str(author.id),
@@ -1075,6 +1111,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
...
... 'form-0-title': 'blah',
... 'form-0-slug': 'Morning',
@@ -1096,6 +1133,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
...
... 'form-0-title': 'foo',
... 'form-0-slug': 'Morning in Prague',
@@ -1115,6 +1153,7 @@ False
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
+... 'form-MAX_NUM_FORMS': '0',
...
... 'form-0-title': 'foo',
... 'form-0-slug': 'Morning in Prague',
diff --git a/tests/modeltests/model_formsets/tests.py b/tests/modeltests/model_formsets/tests.py
index d343fc72966..62489bad378 100644
--- a/tests/modeltests/model_formsets/tests.py
+++ b/tests/modeltests/model_formsets/tests.py
@@ -9,6 +9,7 @@ class DeletionTests(TestCase):
data = {
'form-TOTAL_FORMS': u'1',
'form-INITIAL_FORMS': u'1',
+ 'form-MAX_NUM_FORMS': u'0',
'form-0-id': str(poet.pk),
'form-0-name': u'test',
'form-0-DELETE': u'on',
@@ -27,6 +28,7 @@ class DeletionTests(TestCase):
data = {
'form-TOTAL_FORMS': u'1',
'form-INITIAL_FORMS': u'0',
+ 'form-MAX_NUM_FORMS': u'0',
'form-0-id': u'',
'form-0-name': u'x' * 1000,
}
@@ -53,6 +55,7 @@ class DeletionTests(TestCase):
data = {
'form-TOTAL_FORMS': u'1',
'form-INITIAL_FORMS': u'1',
+ 'form-MAX_NUM_FORMS': u'0',
'form-0-id': u'1',
'form-0-name': u'x' * 1000,
}
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 7fc52b3460a..d52a57f9ee3 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -87,6 +87,7 @@ class AdminViewBasicTest(TestCase):
# inline data
"article_set-TOTAL_FORMS": u"3",
"article_set-INITIAL_FORMS": u"0",
+ "article_set-MAX_NUM_FORMS": u"0",
}
response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
@@ -97,6 +98,7 @@ class AdminViewBasicTest(TestCase):
# inline data
"article_set-TOTAL_FORMS": u"6",
"article_set-INITIAL_FORMS": u"3",
+ "article_set-MAX_NUM_FORMS": u"0",
"article_set-0-id": u"1",
# there is no title in database, give one here or formset will fail.
"article_set-0-title": u"Norske bostaver æøå skaper problemer",
@@ -864,6 +866,7 @@ class AdminViewUnicodeTest(TestCase):
# inline data
"chapter_set-TOTAL_FORMS": u"6",
"chapter_set-INITIAL_FORMS": u"3",
+ "chapter_set-MAX_NUM_FORMS": u"0",
"chapter_set-0-id": u"1",
"chapter_set-0-title": u"Norske bostaver æøå skaper problemer",
"chapter_set-0-content": u"<p>Svært frustrerende med UnicodeDecodeError</p>",
@@ -926,14 +929,14 @@ class AdminViewListEditable(TestCase):
def test_changelist_input_html(self):
response = self.client.get('/test_admin/admin/admin_views/person/')
# 2 inputs per object(the field and the hidden id field) = 6
- # 2 management hidden fields = 2
+ # 3 management hidden fields = 3
# 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
# main form submit button = 1
# search field and search submit button = 2
# CSRF field = 1
# field to track 'select all' across paginated views = 1
- # 6 + 2 + 4 + 1 + 2 + 1 + 1 = 17 inputs
- self.failUnlessEqual(response.content.count("Awesomeness level:")
@@ -1773,6 +1792,7 @@ class ReadonlyTest(TestCase):
"content": "This is an incredible development.",
"link_set-TOTAL_FORMS": "1",
"link_set-INITIAL_FORMS": "0",
+ "link_set-MAX_NUM_FORMS": "0",
}
response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
self.assertEqual(response.status_code, 302)
diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py
index 7999710ec18..700ab7c358b 100644
--- a/tests/regressiontests/forms/formsets.py
+++ b/tests/regressiontests/forms/formsets.py
@@ -20,7 +20,7 @@ but we'll look at how to do so later.
>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
>>> print formset
-
+
Choice:
Votes:
@@ -34,6 +34,7 @@ the TOTAL_FORMS field appropriately.
>>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... }
@@ -60,6 +61,7 @@ any of the forms.
>>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '',
... }
@@ -90,6 +92,7 @@ Let's simulate what would happen if we submitted this form.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': '',
@@ -111,6 +114,7 @@ handle that later.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists',
@@ -130,6 +134,7 @@ handle that case later.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': '', # deleted value
... 'choices-0-votes': '', # deleted value
... 'choices-1-choice': '',
@@ -167,6 +172,7 @@ number of forms to be completed.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': '',
... 'choices-0-votes': '',
... 'choices-1-choice': '',
@@ -187,6 +193,7 @@ We can just fill out one of the forms.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': '',
@@ -207,6 +214,7 @@ And once again, if we try to partially complete a form, validation will fail.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists',
@@ -274,6 +282,7 @@ To delete something, we just need to set that form's special delete field to
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-DELETE': '',
@@ -303,6 +312,7 @@ it's going to be deleted.
>>> data = {
... 'check-TOTAL_FORMS': '3', # the number of forms rendered
... 'check-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'check-MAX_NUM_FORMS': '0', # max number of forms
... 'check-0-field': '200',
... 'check-0-DELETE': '',
... 'check-1-field': '50',
@@ -351,6 +361,7 @@ something at the front of the list, you'd need to set it's order to 0.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -377,6 +388,7 @@ they will be sorted below everything else.
>>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -406,6 +418,7 @@ Ordering should work with blank fieldsets.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... }
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
@@ -450,6 +463,7 @@ Let's delete Fergie, and put The Decemberists ahead of Calexico.
>>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'choices-MAX_NUM_FORMS': '0', # max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -508,6 +522,7 @@ We start out with a some duplicate data.
>>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'drinks-MAX_NUM_FORMS': '0', # max number of forms
... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Gin and Tonic',
... }
@@ -529,6 +544,7 @@ Make sure we didn't break the valid case.
>>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'drinks-MAX_NUM_FORMS': '0', # max number of forms
... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Bloody Mary',
... }
diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/regressiontests/generic_inline_admin/tests.py
index 21704cd8deb..6853a896a4a 100644
--- a/tests/regressiontests/generic_inline_admin/tests.py
+++ b/tests/regressiontests/generic_inline_admin/tests.py
@@ -58,6 +58,7 @@ class GenericAdminViewTest(TestCase):
# inline data
"generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"1",
"generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"0",
+ "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
}
response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/add/', post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
@@ -71,6 +72,7 @@ class GenericAdminViewTest(TestCase):
# inline data
"generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"3",
"generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"2",
+ "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
"generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.mp3_media_pk,
"generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3",
"generic_inline_admin-media-content_type-object_id-1-id": u"%d" % self.png_media_pk,
@@ -192,6 +194,7 @@ class GenericInlineAdminWithUniqueTogetherTest(TestCase):
# inline data
"generic_inline_admin-phonenumber-content_type-object_id-TOTAL_FORMS": u"1",
"generic_inline_admin-phonenumber-content_type-object_id-INITIAL_FORMS": u"0",
+ "generic_inline_admin-phonenumber-content_type-object_id-MAX_NUM_FORMS": u"0",
"generic_inline_admin-phonenumber-content_type-object_id-0-id": "",
"generic_inline_admin-phonenumber-content_type-object_id-0-phone_number": "555-555-5555",
}
diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py
index aef6b3f10a6..83d2fba1933 100644
--- a/tests/regressiontests/inline_formsets/tests.py
+++ b/tests/regressiontests/inline_formsets/tests.py
@@ -10,6 +10,7 @@ class DeletionTests(TestCase):
data = {
'poem_set-TOTAL_FORMS': u'1',
'poem_set-INITIAL_FORMS': u'1',
+ 'poem_set-MAX_NUM_FORMS': u'0',
'poem_set-0-id': str(poem.pk),
'poem_set-0-poet': str(poet.pk),
'poem_set-0-name': u'test',
@@ -30,6 +31,7 @@ class DeletionTests(TestCase):
data = {
'poem_set-TOTAL_FORMS': u'1',
'poem_set-INITIAL_FORMS': u'0',
+ 'poem_set-MAX_NUM_FORMS': u'0',
'poem_set-0-id': u'',
'poem_set-0-poem': u'1',
'poem_set-0-name': u'x' * 1000,
@@ -58,6 +60,7 @@ class DeletionTests(TestCase):
data = {
'poem_set-TOTAL_FORMS': u'1',
'poem_set-INITIAL_FORMS': u'1',
+ 'poem_set-MAX_NUM_FORMS': u'0',
'poem_set-0-id': u'1',
'poem_set-0-poem': u'1',
'poem_set-0-name': u'x' * 1000,
@@ -88,6 +91,7 @@ class DeletionTests(TestCase):
data = {
'child_set-TOTAL_FORMS': u'1',
'child_set-INITIAL_FORMS': u'0',
+ 'child_set-MAX_NUM_FORMS': u'0',
'child_set-0-name': u'child',
}
formset = ChildFormSet(data, instance=school)
diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py
index 4dba9fc19f9..61bc5143246 100644
--- a/tests/regressiontests/model_formsets_regress/tests.py
+++ b/tests/regressiontests/model_formsets_regress/tests.py
@@ -20,6 +20,7 @@ class InlineFormsetTests(TestCase):
'username': u'apollo13',
'usersite_set-TOTAL_FORMS': u'1',
'usersite_set-INITIAL_FORMS': u'0',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
'usersite_set-0-data': u'10',
'usersite_set-0-user': u'apollo13'
}
@@ -43,6 +44,7 @@ class InlineFormsetTests(TestCase):
data = {
'usersite_set-TOTAL_FORMS': u'1',
'usersite_set-INITIAL_FORMS': u'1',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
'usersite_set-0-id': unicode(usersite[0]['id']),
'usersite_set-0-data': u'11',
'usersite_set-0-user': u'apollo13'
@@ -60,6 +62,7 @@ class InlineFormsetTests(TestCase):
data = {
'usersite_set-TOTAL_FORMS': u'2',
'usersite_set-INITIAL_FORMS': u'1',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
'usersite_set-0-id': unicode(usersite[0]['id']),
'usersite_set-0-data': u'11',
'usersite_set-0-user': u'apollo13',
@@ -92,6 +95,7 @@ class InlineFormsetTests(TestCase):
'name': u"Guido's House of Pasta",
'manager_set-TOTAL_FORMS': u'1',
'manager_set-INITIAL_FORMS': u'0',
+ 'manager_set-MAX_NUM_FORMS': u'0',
'manager_set-0-name': u'Guido Van Rossum'
}
restaurant = User()
@@ -113,6 +117,7 @@ class InlineFormsetTests(TestCase):
data = {
'manager_set-TOTAL_FORMS': u'1',
'manager_set-INITIAL_FORMS': u'1',
+ 'manager_set-MAX_NUM_FORMS': u'0',
'manager_set-0-id': unicode(manager[0]['id']),
'manager_set-0-name': u'Terry Gilliam'
}
@@ -128,6 +133,7 @@ class InlineFormsetTests(TestCase):
data = {
'manager_set-TOTAL_FORMS': u'2',
'manager_set-INITIAL_FORMS': u'1',
+ 'manager_set-MAX_NUM_FORMS': u'0',
'manager_set-0-id': unicode(manager[0]['id']),
'manager_set-0-name': u'Terry Gilliam',
'manager_set-1-name': u'John Cleese'