Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #12692 - Properly handle the extra clause of admin inline forms…

…ets. Also fixes #12703, second error.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12369 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9555f2be9dc7c5cc1aeb3f4a047550ccd9b0cb2c 1 parent f70a088
Jannis Leidel authored February 01, 2010
80  django/contrib/admin/media/js/inlines.js
@@ -20,29 +20,24 @@
20 20
 		var updateElementIndex = function(el, prefix, ndx) {
21 21
 			var id_regex = new RegExp("(" + prefix + "-\\d+)");
22 22
 			var replacement = prefix + "-" + ndx;
23  
-			if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
24  
-			if (el.id) el.id = el.id.replace(id_regex, replacement);
25  
-			if (el.name) el.name = el.name.replace(id_regex, replacement);
  23
+			if ($(el).attr("for")) {
  24
+				$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
  25
+			}
  26
+			if (el.id) {
  27
+				el.id = el.id.replace(id_regex, replacement);
  28
+			}
  29
+			if (el.name) {
  30
+				el.name = el.name.replace(id_regex, replacement);
  31
+			}
26 32
 		};
27 33
 		var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
28 34
 		var initialForms = $("#id_" + options.prefix + "-INITIAL_FORMS");
29  
-		var maxForms = parseInt(totalForms.val());
  35
+		var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS");
30 36
 		// only show the add button if we are allowed to add more items
31  
-		var showAddButton = (maxForms - parseInt(initialForms.val())) > 0;
  37
+		var showAddButton = ((maxForms.val() == 0) || ((maxForms.val()-totalForms.val()) > 0));
32 38
 		var selectedItems = this;
33 39
 		$(this).each(function(i) {
34 40
 			$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
35  
-			// hide the extras, but only if there were no form errors
36  
-			if (!$(".errornote").html()) {
37  
-				var relatedItems = $(selectedItems).not("." + options.emptyCssClass);
38  
-				extraRows = relatedItems.length;
39  
-				if (parseInt(initialForms.val()) >= 0) {
40  
-					$(relatedItems).slice(initialForms.val()).remove();
41  
-				} else {
42  
-					$(relatedItems).remove();
43  
-				}
44  
-				totalForms.val(parseInt(initialForms.val()));
45  
-			}
46 41
 		});
47 42
 		if ($(this).length && showAddButton) {
48 43
 			var addButton;
@@ -58,9 +53,8 @@
58 53
 				addButton = $(this).filter(":last").next().find("a");
59 54
 			}
60 55
 			addButton.click(function() {
61  
-				var totalForms = parseInt($("#id_" + options.prefix + "-TOTAL_FORMS").val());
62  
-				var initialForms = parseInt($("#id_" + options.prefix + "-INITIAL_FORMS").val());
63  
-				var nextIndex = totalForms + 1;
  56
+				var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
  57
+				var nextIndex = parseInt(totalForms.val()) + 1;
64 58
 				var template = $("#" + options.prefix + "-empty");
65 59
 				var row = template.clone(true).get(0);
66 60
 				$(row).removeClass(options.emptyCssClass).removeAttr("id").insertBefore($(template));
@@ -79,10 +73,13 @@
79 73
 					// last child element of the form's container:
80 74
 					$(row).children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
81 75
 				}
  76
+				$(row).find("input,select,textarea,label").each(function() {
  77
+					updateElementIndex(this, options.prefix, totalForms.val());
  78
+				});
82 79
 				// Update number of total forms
83  
-				$("#id_" + options.prefix + "-TOTAL_FORMS").val(nextIndex);
84  
-				// Hide add button in case we've hit the max
85  
-				if (maxForms <= nextIndex) {
  80
+				$(totalForms).val(nextIndex);
  81
+				// Hide add button in case we've hit the max, except we want to add infinitely
  82
+				if ((maxForms.val() != 0) && (maxForms.val() <= totalForms.val())) {
86 83
 					addButton.parent().hide();
87 84
 				}
88 85
 				// The delete button of each row triggers a bunch of other things
@@ -91,44 +88,45 @@
91 88
 					var row = $(this).parents("." + options.formCssClass);
92 89
 					row.remove();
93 90
 					// If a post-delete callback was provided, call it with the deleted form:
94  
-					if (options.removed) options.removed(row);
  91
+					if (options.removed) {
  92
+						options.removed(row);
  93
+					}
95 94
 					// Update the TOTAL_FORMS form count.
96 95
 					var forms = $("." + options.formCssClass);
97 96
 					$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
98 97
 					// Show add button again once we drop below max
99  
-					if (maxForms >= forms.length) {
  98
+					if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) {
100 99
 						addButton.parent().show();
101 100
 					}
102 101
 					// Also, update names and ids for all remaining form controls
103 102
 					// so they remain in sequence:
104  
-					for (var i=0, formCount=forms.length; i<formCount; i++) {
  103
+					for (var i=0, formCount=forms.length; i<formCount; i++)
  104
+					{
105 105
 						$(forms.get(i)).find("input,select,textarea,label").each(function() {
106 106
 							updateElementIndex(this, options.prefix, i);
107 107
 						});
108 108
 					}
109 109
 					return false;
110 110
 				});
111  
-				$(row).find("input,select,textarea,label").each(function() {
112  
-					updateElementIndex(this, options.prefix, totalForms);
113  
-				});
114 111
 				// If a post-add callback was supplied, call it with the added form:
115  
-				if (options.added) options.added($(row));
  112
+				if (options.added) {
  113
+					options.added($(row));
  114
+				}
116 115
 				return false;
117 116
 			});
118 117
 		}
119  
-		return $(this);
  118
+		return this;
120 119
 	}
121  
-
122 120
 	/* Setup plugin defaults */
123 121
 	$.fn.formset.defaults = {
124  
-		prefix: "form",					 // The form prefix for your django formset
125  
-		addText: "add another",			 // Text for the add link
126  
-		deleteText: "remove",			 // Text for the delete link
127  
-		addCssClass: "add-row",			 // CSS class applied to the add link
128  
-		deleteCssClass: "delete-row",	 // CSS class applied to the delete link
129  
-		emptyCssClass: "empty-row",		 // CSS class applied to the empty row
130  
-		formCssClass: "dynamic-form",	 // CSS class applied to each form in a formset
131  
-		added: null,					 // Function called each time a new form is added
132  
-		removed: null					 // Function called each time a form is deleted
  122
+		prefix: "form",					// The form prefix for your django formset
  123
+		addText: "add another",			// Text for the add link
  124
+		deleteText: "remove",			// Text for the delete link
  125
+		addCssClass: "add-row",			// CSS class applied to the add link
  126
+		deleteCssClass: "delete-row",	// CSS class applied to the delete link
  127
+		emptyCssClass: "empty-row",		// CSS class applied to the empty row
  128
+		formCssClass: "dynamic-form",	// CSS class applied to each form in a formset
  129
+		added: null,					// Function called each time a new form is added
  130
+		removed: null					// Function called each time a form is deleted
133 131
 	}
134  
-})(jQuery)
  132
+})(jQuery);
6  django/contrib/admin/media/js/inlines.min.js
... ...
@@ -1 +1,5 @@
1  
-(function(a){a.fn.formset=function(b){var k=a.extend({},a.fn.formset.defaults,b);var j=function(o,p,m){var l=new RegExp("("+p+"-\\d+)");var n=p+"-"+m;if(a(o).attr("for")){a(o).attr("for",a(o).attr("for").replace(l,n))}if(o.id){o.id=o.id.replace(l,n)}if(o.name){o.name=o.name.replace(l,n)}};var f=a("#id_"+k.prefix+"-TOTAL_FORMS");var c=a("#id_"+k.prefix+"-INITIAL_FORMS");var h=parseInt(f.val());var i=(h-parseInt(c.val()))>0;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('<tr class="'+k.addCssClass+'"><td colspan="'+d+'"><a href="javascript:void(0)">'+k.addText+"</a></tr>");g=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('<div class="'+k.addCssClass+'"><a href="javascript:void(0)">'+k.addText+"</a></div>");g=a(this).filter(":last").next().find("a")}g.click(function(){var o=parseInt(a("#id_"+k.prefix+"-TOTAL_FORMS").val());var n=parseInt(a("#id_"+k.prefix+"-INITIAL_FORMS").val());var l=o+1;var m=a("#"+k.prefix+"-empty");var p=m.clone(true).get(0);a(p).removeClass(k.emptyCssClass).removeAttr("id").insertBefore(a(m));a(p).html(a(p).html().replace(/__prefix__/g,l));a(p).addClass(k.formCssClass).attr("id",k.prefix+l);if(a(p).is("TR")){a(p).children(":last").append('<div><a class="'+k.deleteCssClass+'" href="javascript:void(0)">'+k.deleteText+"</a></div>")}else{if(a(p).is("UL")||a(p).is("OL")){a(p).append('<li><a class="'+k.deleteCssClass+'" href="javascript:void(0)">'+k.deleteText+"</a></li>")}else{a(p).children(":first").append('<span><a class="'+k.deleteCssClass+'" href="javascript:void(0)">'+k.deleteText+"</a></span>")}}a("#id_"+k.prefix+"-TOTAL_FORMS").val(l);if(h<=l){g.parent().hide()}a(p).find("a."+k.deleteCssClass).click(function(){var t=a(this).parents("."+k.formCssClass);t.remove();if(k.removed){k.removed(t)}var q=a("."+k.formCssClass);a("#id_"+k.prefix+"-TOTAL_FORMS").val(q.length);if(h>=q.length){g.parent().show()}for(var r=0,s=q.length;r<s;r++){a(q.get(r)).find("input,select,textarea,label").each(function(){j(this,k.prefix,r)})}return false});a(p).find("input,select,textarea,label").each(function(){j(this,k.prefix,o)});if(k.added){k.added(a(p))}return false})}return a(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);
  1
+(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");a("#id_"+b.prefix+"-INITIAL_FORMS");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS");f=h.val()==0||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)});
  2
+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())+
  3
+1,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);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="'+
  4
+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");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<m;k++)a(g.get(k)).find("input,select,textarea,label").each(function(){l(this,
  5
+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);
37  django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -19,24 +19,25 @@
19 19
 </div>
20 20
 
21 21
 <script type="text/javascript">
22  
-jQuery.noConflict();
23  
-jQuery(document).ready(function($) {
24  
-    var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
25  
-    updateInlineLabel = function(row) {
26  
-        $(rows).find(".inline_label").each(function(i) {
27  
-            var count = i + 1;
28  
-            $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
  22
+(function($) {
  23
+    $(document).ready(function() {
  24
+        var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
  25
+        updateInlineLabel = function(row) {
  26
+            $(rows).find(".inline_label").each(function(i) {
  27
+                var count = i + 1;
  28
+                $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
  29
+            });
  30
+        }
  31
+        $(rows).formset({
  32
+            prefix: "{{ inline_admin_formset.formset.prefix }}",
  33
+            addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
  34
+            formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
  35
+            deleteCssClass: "inline-deletelink",
  36
+            deleteText: "{% trans "Remove" %}",
  37
+            emptyCssClass: "empty-form",
  38
+            removed: updateInlineLabel,
  39
+            added: updateInlineLabel
29 40
         });
30  
-    }
31  
-    $(rows).formset({
32  
-        prefix: "{{ inline_admin_formset.formset.prefix }}",
33  
-        addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
34  
-        formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
35  
-        deleteCssClass: "inline-deletelink",
36  
-        deleteText: "{% trans "Remove" %}",
37  
-        emptyCssClass: "empty-form",
38  
-        removed: updateInlineLabel,
39  
-        added: updateInlineLabel
40 41
     });
41  
-});
  42
+})(jQuery.noConflict());
42 43
 </script>
37  django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -65,23 +65,24 @@
65 65
 </div>
66 66
 
67 67
 <script type="text/javascript">
68  
-jQuery.noConflict();
69  
-jQuery(document).ready(function($) {
70  
-    var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
71  
-    alternatingRows = function(row) {
72  
-        $(rows).not(".add-row").removeClass("row1 row2")
73  
-            .filter(":even").addClass("row1").end()
74  
-            .filter(rows + ":odd").addClass("row2");
75  
-    }
76  
-    $(rows).formset({
77  
-        prefix: "{{ inline_admin_formset.formset.prefix }}",
78  
-        addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
79  
-        formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
80  
-        deleteCssClass: "inline-deletelink",
81  
-        deleteText: "{% trans "Remove" %}",
82  
-        emptyCssClass: "empty-form",
83  
-        removed: alternatingRows,
84  
-        added: alternatingRows
  68
+(function($) {
  69
+    $(document).ready(function($) {
  70
+        var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
  71
+        alternatingRows = function(row) {
  72
+            $(rows).not(".add-row").removeClass("row1 row2")
  73
+                .filter(":even").addClass("row1").end()
  74
+                .filter(rows + ":odd").addClass("row2");
  75
+        }
  76
+        $(rows).formset({
  77
+            prefix: "{{ inline_admin_formset.formset.prefix }}",
  78
+            addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
  79
+            formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
  80
+            deleteCssClass: "inline-deletelink",
  81
+            deleteText: "{% trans "Remove" %}",
  82
+            emptyCssClass: "empty-form",
  83
+            removed: alternatingRows,
  84
+            added: alternatingRows
  85
+        });
85 86
     });
86  
-});
  87
+})(jQuery.noConflict());
87 88
 </script>
5  django/forms/formsets.py
@@ -12,6 +12,7 @@
12 12
 # special field names
13 13
 TOTAL_FORM_COUNT = 'TOTAL_FORMS'
14 14
 INITIAL_FORM_COUNT = 'INITIAL_FORMS'
  15
+MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
15 16
 ORDERING_FIELD_NAME = 'ORDER'
16 17
 DELETION_FIELD_NAME = 'DELETE'
17 18
 
@@ -24,6 +25,7 @@ class ManagementForm(Form):
24 25
     def __init__(self, *args, **kwargs):
25 26
         self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
26 27
         self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
  28
+        self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(widget=HiddenInput)
27 29
         super(ManagementForm, self).__init__(*args, **kwargs)
28 30
 
29 31
 class BaseFormSet(StrAndUnicode):
@@ -56,7 +58,8 @@ def _management_form(self):
56 58
         else:
57 59
             form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
58 60
                 TOTAL_FORM_COUNT: self.total_form_count(),
59  
-                INITIAL_FORM_COUNT: self.initial_form_count()
  61
+                INITIAL_FORM_COUNT: self.initial_form_count(),
  62
+                MAX_NUM_FORM_COUNT: self.max_num
60 63
             })
61 64
         return form
62 65
     management_form = property(_management_form)
43  tests/modeltests/model_formsets/models.py
@@ -200,6 +200,7 @@ def __unicode__(self):
200 200
 >>> data = {
201 201
 ...     'form-TOTAL_FORMS': '3', # the number of forms rendered
202 202
 ...     'form-INITIAL_FORMS': '0', # the number of forms with initial data
  203
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
203 204
 ...     'form-0-name': 'Charles Baudelaire',
204 205
 ...     'form-1-name': 'Arthur Rimbaud',
205 206
 ...     'form-2-name': '',
@@ -237,6 +238,7 @@ def __unicode__(self):
237 238
 >>> data = {
238 239
 ...     'form-TOTAL_FORMS': '3', # the number of forms rendered
239 240
 ...     'form-INITIAL_FORMS': '2', # the number of forms with initial data
  241
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
240 242
 ...     'form-0-id': '2',
241 243
 ...     'form-0-name': 'Arthur Rimbaud',
242 244
 ...     'form-1-id': '1',
@@ -280,6 +282,7 @@ def __unicode__(self):
280 282
 >>> data = {
281 283
 ...     'form-TOTAL_FORMS': '4', # the number of forms rendered
282 284
 ...     'form-INITIAL_FORMS': '3', # the number of forms with initial data
  285
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
283 286
 ...     'form-0-id': '2',
284 287
 ...     'form-0-name': 'Arthur Rimbaud',
285 288
 ...     'form-1-id': '1',
@@ -309,6 +312,7 @@ def __unicode__(self):
309 312
 >>> data = {
310 313
 ...     'form-TOTAL_FORMS': '4', # the number of forms rendered
311 314
 ...     'form-INITIAL_FORMS': '3', # the number of forms with initial data
  315
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
312 316
 ...     'form-0-id': '2',
313 317
 ...     'form-0-name': 'Walt Whitman',
314 318
 ...     'form-1-id': '1',
@@ -339,6 +343,7 @@ def __unicode__(self):
339 343
 >>> data = {
340 344
 ...     'form-TOTAL_FORMS': '2', # the number of forms rendered
341 345
 ...     'form-INITIAL_FORMS': '1', # the number of forms with initial data
  346
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
342 347
 ...     'form-0-id': '1',
343 348
 ...     'form-0-name': '2nd Tuesday of the Week Meeting',
344 349
 ...     'form-0-authors': [2, 1, 3, 4],
@@ -393,6 +398,7 @@ def __unicode__(self):
393 398
 >>> data = {
394 399
 ...     'form-TOTAL_FORMS': '3', # the number of forms rendered
395 400
 ...     'form-INITIAL_FORMS': '0', # the number of forms with initial data
  401
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
396 402
 ...     'form-0-name': 'Walt Whitman',
397 403
 ...     'form-1-name': 'Charles Baudelaire',
398 404
 ...     'form-2-name': '',
@@ -419,6 +425,7 @@ def __unicode__(self):
419 425
 >>> data = {
420 426
 ...     'form-TOTAL_FORMS': '1', # the number of forms rendered
421 427
 ...     'form-INITIAL_FORMS': '0', # the number of forms with initial data
  428
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
422 429
 ...     'form-0-author_ptr': '',
423 430
 ...     'form-0-name': 'Ernest Hemingway',
424 431
 ...     'form-0-write_speed': '10',
@@ -442,6 +449,7 @@ def __unicode__(self):
442 449
 >>> data = {
443 450
 ...     'form-TOTAL_FORMS': '2', # the number of forms rendered
444 451
 ...     'form-INITIAL_FORMS': '1', # the number of forms with initial data
  452
+...     'form-MAX_NUM_FORMS': '0', # the max number of forms
445 453
 ...     'form-0-author_ptr': hemingway_id,
446 454
 ...     'form-0-name': 'Ernest Hemingway',
447 455
 ...     'form-0-write_speed': '10',
@@ -476,6 +484,7 @@ def __unicode__(self):
476 484
 >>> data = {
477 485
 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
478 486
 ...     'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
  487
+...     'book_set-MAX_NUM_FORMS': '0', # the max number of forms
479 488
 ...     'book_set-0-title': 'Les Fleurs du Mal',
480 489
 ...     'book_set-1-title': '',
481 490
 ...     'book_set-2-title': '',
@@ -510,6 +519,7 @@ def __unicode__(self):
510 519
 >>> data = {
511 520
 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
512 521
 ...     'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
  522
+...     'book_set-MAX_NUM_FORMS': '0', # the max number of forms
513 523
 ...     'book_set-0-id': '1',
514 524
 ...     'book_set-0-title': 'Les Fleurs du Mal',
515 525
 ...     'book_set-1-title': 'Les Paradis Artificiels',
@@ -536,6 +546,7 @@ def __unicode__(self):
536 546
 >>> data = {
537 547
 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
538 548
 ...     'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
  549
+...     'book_set-MAX_NUM_FORMS': '0', # the max number of forms
539 550
 ...     'book_set-0-id': '1',
540 551
 ...     'book_set-0-title': 'Les Fleurs du Mal',
541 552
 ...     'book_set-1-id': '2',
@@ -573,6 +584,7 @@ def __unicode__(self):
573 584
 >>> data = {
574 585
 ...     'bookwithcustompk_set-TOTAL_FORMS': '1', # the number of forms rendered
575 586
 ...     'bookwithcustompk_set-INITIAL_FORMS': '0', # the number of forms with initial data
  587
+...     'bookwithcustompk_set-MAX_NUM_FORMS': '0', # the max number of forms
576 588
 ...     'bookwithcustompk_set-0-my_pk': '77777',
577 589
 ...     'bookwithcustompk_set-0-title': 'Les Fleurs du Mal',
578 590
 ... }
@@ -603,6 +615,7 @@ def __unicode__(self):
603 615
 >>> data = {
604 616
 ...     'alternatebook_set-TOTAL_FORMS': '1', # the number of forms rendered
605 617
 ...     'alternatebook_set-INITIAL_FORMS': '0', # the number of forms with initial data
  618
+...     'alternatebook_set-MAX_NUM_FORMS': '0', # the max number of forms
606 619
 ...     'alternatebook_set-0-title': 'Flowers of Evil',
607 620
 ...     'alternatebook_set-0-notes': 'English translation of Les Fleurs du Mal'
608 621
 ... }
@@ -631,6 +644,7 @@ def __unicode__(self):
631 644
 >>> data = {
632 645
 ...     'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
633 646
 ...     'poem_set-INITIAL_FORMS': '0', # the number of forms with initial data
  647
+...     'poem_set-MAX_NUM_FORMS': '0', # the max number of forms
634 648
 ...     'poem_set-0-name': 'The Cloud in Trousers',
635 649
 ...     'poem_set-1-name': 'I',
636 650
 ...     'poem_set-2-name': '',
@@ -659,6 +673,7 @@ def __unicode__(self):
659 673
 >>> data = {
660 674
 ...     'book_set-TOTAL_FORMS': '5', # the number of forms rendered
661 675
 ...     'book_set-INITIAL_FORMS': '3', # the number of forms with initial data
  676
+...     'book_set-MAX_NUM_FORMS': '0', # the max number of forms
662 677
 ...     'book_set-0-id': '1',
663 678
 ...     'book_set-0-title': 'Les Fleurs du Mal',
664 679
 ...     'book_set-1-id': '2',
@@ -682,6 +697,7 @@ def __unicode__(self):
682 697
 >>> data = {
683 698
 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
684 699
 ...     'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
  700
+...     'book_set-MAX_NUM_FORMS': '0', # the max number of forms
685 701
 ...     'book_set-0-id': '5',
686 702
 ...     'book_set-0-title': 'Flowers of Evil',
687 703
 ...     'book_set-1-title': 'Revue des deux mondes',
@@ -718,6 +734,7 @@ def __unicode__(self):
718 734
 >>> data = {
719 735
 ...     'owner_set-TOTAL_FORMS': '2',
720 736
 ...     'owner_set-INITIAL_FORMS': '0',
  737
+...     'owner_set-MAX_NUM_FORMS': '0',
721 738
 ...     'owner_set-0-auto_id': '',
722 739
 ...     'owner_set-0-name': u'Joe Perry',
723 740
 ...     'owner_set-1-auto_id': '',
@@ -739,6 +756,7 @@ def __unicode__(self):
739 756
 >>> data = {
740 757
 ...     'owner_set-TOTAL_FORMS': '3',
741 758
 ...     'owner_set-INITIAL_FORMS': '1',
  759
+...     'owner_set-MAX_NUM_FORMS': '0',
742 760
 ...     'owner_set-0-auto_id': u'1',
743 761
 ...     'owner_set-0-name': u'Joe Perry',
744 762
 ...     'owner_set-1-auto_id': '',
@@ -767,7 +785,8 @@ def __unicode__(self):
767 785
 
768 786
 >>> owner = Owner.objects.get(name=u'Joe Perry')
769 787
 >>> FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
770  
-
  788
+>>> FormSet.max_num
  789
+1
771 790
 >>> formset = FormSet(instance=owner)
772 791
 >>> for form in formset.forms:
773 792
 ...     print form.as_p()
@@ -776,6 +795,7 @@ def __unicode__(self):
776 795
 >>> data = {
777 796
 ...     'ownerprofile-TOTAL_FORMS': '1',
778 797
 ...     'ownerprofile-INITIAL_FORMS': '0',
  798
+...     'ownerprofile-MAX_NUM_FORMS': '1',
779 799
 ...     'ownerprofile-0-owner': '',
780 800
 ...     'ownerprofile-0-age': u'54',
781 801
 ... }
@@ -784,7 +804,6 @@ def __unicode__(self):
784 804
 True
785 805
 >>> formset.save()
786 806
 [<OwnerProfile: Joe Perry is 54>]
787  
-
788 807
 >>> formset = FormSet(instance=owner)
789 808
 >>> for form in formset.forms:
790 809
 ...     print form.as_p()
@@ -793,6 +812,7 @@ def __unicode__(self):
793 812
 >>> data = {
794 813
 ...     'ownerprofile-TOTAL_FORMS': '1',
795 814
 ...     'ownerprofile-INITIAL_FORMS': '1',
  815
+...     'ownerprofile-MAX_NUM_FORMS': '1',
796 816
 ...     'ownerprofile-0-owner': u'1',
797 817
 ...     'ownerprofile-0-age': u'55',
798 818
 ... }
@@ -805,6 +825,8 @@ def __unicode__(self):
805 825
 # ForeignKey with unique=True should enforce max_num=1
806 826
 
807 827
 >>> FormSet = inlineformset_factory(Place, Location, can_delete=False)
  828
+>>> FormSet.max_num
  829
+1
808 830
 >>> formset = FormSet(instance=place)
809 831
 >>> for form in formset.forms:
810 832
 ...     print form.as_p()
@@ -826,6 +848,7 @@ def __unicode__(self):
826 848
 >>> data = {
827 849
 ...     'form-TOTAL_FORMS': '1',
828 850
 ...     'form-INITIAL_FORMS': '0',
  851
+...     'form-MAX_NUM_FORMS': '0',
829 852
 ...     'form-0-slug': 'car-red',
830 853
 ... }
831 854
 >>> formset = FormSet(data)
@@ -837,6 +860,7 @@ def __unicode__(self):
837 860
 >>> data = {
838 861
 ...     'form-TOTAL_FORMS': '1',
839 862
 ...     'form-INITIAL_FORMS': '0',
  863
+...     'form-MAX_NUM_FORMS': '0',
840 864
 ...     'form-0-slug': 'car-red',
841 865
 ... }
842 866
 >>> formset = FormSet(data)
@@ -851,6 +875,7 @@ def __unicode__(self):
851 875
 >>> data = {
852 876
 ...     'form-TOTAL_FORMS': '1',
853 877
 ...     'form-INITIAL_FORMS': '0',
  878
+...     'form-MAX_NUM_FORMS': '0',
854 879
 ...     'form-0-price': u'12.00',
855 880
 ...     'form-0-quantity': '1',
856 881
 ... }
@@ -863,6 +888,7 @@ def __unicode__(self):
863 888
 >>> data = {
864 889
 ...     'form-TOTAL_FORMS': '1',
865 890
 ...     'form-INITIAL_FORMS': '0',
  891
+...     'form-MAX_NUM_FORMS': '0',
866 892
 ...     'form-0-price': u'12.00',
867 893
 ...     'form-0-quantity': '1',
868 894
 ... }
@@ -880,6 +906,7 @@ def __unicode__(self):
880 906
 >>> data = {
881 907
 ...     'revision_set-TOTAL_FORMS': '1',
882 908
 ...     'revision_set-INITIAL_FORMS': '0',
  909
+...     'revision_set-MAX_NUM_FORMS': '0',
883 910
 ...     'revision_set-0-repository': repository.pk,
884 911
 ...     'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
885 912
 ...     'revision_set-0-DELETE': '',
@@ -894,6 +921,7 @@ def __unicode__(self):
894 921
 >>> data = {
895 922
 ...     'revision_set-TOTAL_FORMS': '1',
896 923
 ...     'revision_set-INITIAL_FORMS': '0',
  924
+...     'revision_set-MAX_NUM_FORMS': '0',
897 925
 ...     'revision_set-0-repository': repository.pk,
898 926
 ...     'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
899 927
 ...     'revision_set-0-DELETE': '',
@@ -911,6 +939,7 @@ def __unicode__(self):
911 939
 >>> data = {
912 940
 ...     'revision_set-TOTAL_FORMS': '1',
913 941
 ...     'revision_set-INITIAL_FORMS': '0',
  942
+...     'revision_set-MAX_NUM_FORMS': '0',
914 943
 ...     'revision_set-0-repository': repository.pk,
915 944
 ...     'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
916 945
 ...     'revision_set-0-DELETE': '',
@@ -940,6 +969,7 @@ def __unicode__(self):
940 969
 >>> data = {
941 970
 ...     'membership_set-TOTAL_FORMS': '1',
942 971
 ...     'membership_set-INITIAL_FORMS': '0',
  972
+...     'membership_set-MAX_NUM_FORMS': '0',
943 973
 ...     'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
944 974
 ...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
945 975
 ...     'membership_set-0-karma': '',
@@ -954,6 +984,7 @@ def __unicode__(self):
954 984
 >>> filled_data = {
955 985
 ...     'membership_set-TOTAL_FORMS': '1',
956 986
 ...     'membership_set-INITIAL_FORMS': '0',
  987
+...     'membership_set-MAX_NUM_FORMS': '0',
957 988
 ...     'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')),
958 989
 ...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
959 990
 ...     'membership_set-0-karma': '',
@@ -976,6 +1007,7 @@ def __unicode__(self):
976 1007
 >>> data = {
977 1008
 ...     'membership_set-TOTAL_FORMS': '1',
978 1009
 ...     'membership_set-INITIAL_FORMS': '0',
  1010
+...     'membership_set-MAX_NUM_FORMS': '0',
979 1011
 ...     'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')),
980 1012
 ...     'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')),
981 1013
 ...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
@@ -1011,6 +1043,7 @@ def __unicode__(self):
1011 1043
 >>> data = {
1012 1044
 ...     'form-TOTAL_FORMS': 2,
1013 1045
 ...     'form-INITIAL_FORMS': 0,
  1046
+...     'form-MAX_NUM_FORMS': '0',
1014 1047
 ...     'form-0-slug': 'red_car',
1015 1048
 ...     'form-1-slug': 'red_car',
1016 1049
 ... }
@@ -1024,6 +1057,7 @@ def __unicode__(self):
1024 1057
 >>> data = {
1025 1058
 ...     'form-TOTAL_FORMS': 2,
1026 1059
 ...     'form-INITIAL_FORMS': 0,
  1060
+...     'form-MAX_NUM_FORMS': '0',
1027 1061
 ...     'form-0-price': '25',
1028 1062
 ...     'form-0-quantity': '7',
1029 1063
 ...     'form-1-price': '25',
@@ -1041,6 +1075,7 @@ def __unicode__(self):
1041 1075
 >>> data = {
1042 1076
 ...     'form-TOTAL_FORMS': '2',
1043 1077
 ...     'form-INITIAL_FORMS': '0',
  1078
+...     'form-MAX_NUM_FORMS': '0',
1044 1079
 ...     'form-0-price': '24',
1045 1080
 ...     'form-1-price': '24',
1046 1081
 ... }
@@ -1054,6 +1089,7 @@ def __unicode__(self):
1054 1089
 >>> data = {
1055 1090
 ...     'book_set-TOTAL_FORMS': '2',
1056 1091
 ...     'book_set-INITIAL_FORMS': '2',
  1092
+...     'book_set-MAX_NUM_FORMS': '0',
1057 1093
 ...
1058 1094
 ...     'book_set-0-title': 'The 2008 Election',
1059 1095
 ...     'book_set-0-author': str(author.id),
@@ -1075,6 +1111,7 @@ def __unicode__(self):
1075 1111
 >>> data = {
1076 1112
 ...     'form-TOTAL_FORMS': '2',
1077 1113
 ...     'form-INITIAL_FORMS': '0',
  1114
+...     'form-MAX_NUM_FORMS': '0',
1078 1115
 ...
1079 1116
 ...     'form-0-title': 'blah',
1080 1117
 ...     'form-0-slug': 'Morning',
@@ -1096,6 +1133,7 @@ def __unicode__(self):
1096 1133
 >>> data = {
1097 1134
 ...     'form-TOTAL_FORMS': '2',
1098 1135
 ...     'form-INITIAL_FORMS': '0',
  1136
+...     'form-MAX_NUM_FORMS': '0',
1099 1137
 ...
1100 1138
 ...     'form-0-title': 'foo',
1101 1139
 ...     'form-0-slug': 'Morning in Prague',
@@ -1115,6 +1153,7 @@ def __unicode__(self):
1115 1153
 >>> data = {
1116 1154
 ...     'form-TOTAL_FORMS': '2',
1117 1155
 ...     'form-INITIAL_FORMS': '0',
  1156
+...     'form-MAX_NUM_FORMS': '0',
1118 1157
 ...
1119 1158
 ...     'form-0-title': 'foo',
1120 1159
 ...     'form-0-slug': 'Morning in Prague',
3  tests/modeltests/model_formsets/tests.py
@@ -9,6 +9,7 @@ def test_deletion(self):
9 9
         data = {
10 10
             'form-TOTAL_FORMS': u'1',
11 11
             'form-INITIAL_FORMS': u'1',
  12
+            'form-MAX_NUM_FORMS': u'0',
12 13
             'form-0-id': str(poet.pk),
13 14
             'form-0-name': u'test',
14 15
             'form-0-DELETE': u'on',
@@ -27,6 +28,7 @@ def test_add_form_deletion_when_invalid(self):
27 28
         data = {
28 29
             'form-TOTAL_FORMS': u'1',
29 30
             'form-INITIAL_FORMS': u'0',
  31
+            'form-MAX_NUM_FORMS': u'0',
30 32
             'form-0-id': u'',
31 33
             'form-0-name': u'x' * 1000,
32 34
         }
@@ -53,6 +55,7 @@ def test_change_form_deletion_when_invalid(self):
53 55
         data = {
54 56
             'form-TOTAL_FORMS': u'1',
55 57
             'form-INITIAL_FORMS': u'1',
  58
+            'form-MAX_NUM_FORMS': u'0',
56 59
             'form-0-id': u'1',
57 60
             'form-0-name': u'x' * 1000,
58 61
         }
32  tests/regressiontests/admin_views/tests.py
@@ -87,6 +87,7 @@ def testBasicAddPost(self):
87 87
             # inline data
88 88
             "article_set-TOTAL_FORMS": u"3",
89 89
             "article_set-INITIAL_FORMS": u"0",
  90
+            "article_set-MAX_NUM_FORMS": u"0",
90 91
         }
91 92
         response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
92 93
         self.failUnlessEqual(response.status_code, 302) # redirect somewhere
@@ -97,6 +98,7 @@ def testBasicAddPost(self):
97 98
         # inline data
98 99
         "article_set-TOTAL_FORMS": u"6",
99 100
         "article_set-INITIAL_FORMS": u"3",
  101
+        "article_set-MAX_NUM_FORMS": u"0",
100 102
         "article_set-0-id": u"1",
101 103
         # there is no title in database, give one here or formset will fail.
102 104
         "article_set-0-title": u"Norske bostaver æøå skaper problemer",
@@ -864,6 +866,7 @@ def testUnicodeEdit(self):
864 866
             # inline data
865 867
             "chapter_set-TOTAL_FORMS": u"6",
866 868
             "chapter_set-INITIAL_FORMS": u"3",
  869
+            "chapter_set-MAX_NUM_FORMS": u"0",
867 870
             "chapter_set-0-id": u"1",
868 871
             "chapter_set-0-title": u"Norske bostaver æøå skaper problemer",
869 872
             "chapter_set-0-content": u"&lt;p&gt;Svært frustrerende med UnicodeDecodeError&lt;/p&gt;",
@@ -926,14 +929,14 @@ def test_custom_pk(self):
926 929
     def test_changelist_input_html(self):
927 930
         response = self.client.get('/test_admin/admin/admin_views/person/')
928 931
         # 2 inputs per object(the field and the hidden id field) = 6
929  
-        # 2 management hidden fields = 2
  932
+        # 3 management hidden fields = 3
930 933
         # 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
931 934
         # main form submit button = 1
932 935
         # search field and search submit button = 2
933 936
         # CSRF field = 1
934 937
         # field to track 'select all' across paginated views = 1
935  
-        # 6 + 2 + 4 + 1 + 2 + 1 + 1 = 17 inputs
936  
-        self.failUnlessEqual(response.content.count("<input"), 17)
  938
+        # 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
  939
+        self.failUnlessEqual(response.content.count("<input"), 18)
937 940
         # 1 select per object = 3 selects
938 941
         self.failUnlessEqual(response.content.count("<select"), 4)
939 942
 
@@ -941,6 +944,7 @@ def test_post_submission(self):
941 944
         data = {
942 945
             "form-TOTAL_FORMS": "3",
943 946
             "form-INITIAL_FORMS": "3",
  947
+            "form-MAX_NUM_FORMS": "0",
944 948
 
945 949
             "form-0-gender": "1",
946 950
             "form-0-id": "1",
@@ -961,6 +965,7 @@ def test_post_submission(self):
961 965
         data = {
962 966
             "form-TOTAL_FORMS": "2",
963 967
             "form-INITIAL_FORMS": "2",
  968
+            "form-MAX_NUM_FORMS": "0",
964 969
 
965 970
             "form-0-id": "1",
966 971
             "form-0-gender": "1",
@@ -978,6 +983,7 @@ def test_post_submission(self):
978 983
         data = {
979 984
             "form-TOTAL_FORMS": "1",
980 985
             "form-INITIAL_FORMS": "1",
  986
+            "form-MAX_NUM_FORMS": "0",
981 987
 
982 988
             "form-0-id": "1",
983 989
             "form-0-gender": "1"
@@ -998,6 +1004,7 @@ def test_list_editable_ordering(self):
998 1004
         data = {
999 1005
             "form-TOTAL_FORMS": "4",
1000 1006
             "form-INITIAL_FORMS": "4",
  1007
+            "form-MAX_NUM_FORMS": "0",
1001 1008
 
1002 1009
             "form-0-order": "14",
1003 1010
             "form-0-id": "1",
@@ -1069,9 +1076,11 @@ def testInline(self):
1069 1076
             # inline data
1070 1077
             "accounts-TOTAL_FORMS": u"1",
1071 1078
             "accounts-INITIAL_FORMS": u"0",
  1079
+            "accounts-MAX_NUM_FORMS": u"0",
1072 1080
             "accounts-0-username": foo_user,
1073 1081
             "accounts-2-TOTAL_FORMS": u"1",
1074 1082
             "accounts-2-INITIAL_FORMS": u"0",
  1083
+            "accounts-2-MAX_NUM_FORMS": u"0",
1075 1084
             "accounts-2-0-username": bar_user,
1076 1085
         }
1077 1086
 
@@ -1096,6 +1105,7 @@ def testInline(self):
1096 1105
 
1097 1106
             "accounts-TOTAL_FORMS": "2",
1098 1107
             "accounts-INITIAL_FORMS": u"1",
  1108
+            "accounts-MAX_NUM_FORMS": u"0",
1099 1109
 
1100 1110
             "accounts-0-username": "%s-1" % foo_user,
1101 1111
             "accounts-0-account_ptr": "1",
@@ -1103,6 +1113,7 @@ def testInline(self):
1103 1113
 
1104 1114
             "accounts-2-TOTAL_FORMS": u"2",
1105 1115
             "accounts-2-INITIAL_FORMS": u"1",
  1116
+            "accounts-2-MAX_NUM_FORMS": u"0",
1106 1117
 
1107 1118
             "accounts-2-0-username": "%s-1" % bar_user,
1108 1119
             "accounts-2-0-account_ptr": "2",
@@ -1348,6 +1359,7 @@ def test_inline_file_upload_edit_validation_error_post(self):
1348 1359
             "name": u"Test Gallery",
1349 1360
             "pictures-TOTAL_FORMS": u"2",
1350 1361
             "pictures-INITIAL_FORMS": u"1",
  1362
+            "pictures-MAX_NUM_FORMS": u"0",
1351 1363
             "pictures-0-id": u"1",
1352 1364
             "pictures-0-gallery": u"1",
1353 1365
             "pictures-0-name": "Test Picture",
@@ -1370,6 +1382,7 @@ def setUp(self):
1370 1382
 
1371 1383
             "widget_set-TOTAL_FORMS": "3",
1372 1384
             "widget_set-INITIAL_FORMS": u"0",
  1385
+            "widget_set-MAX_NUM_FORMS": u"0",
1373 1386
             "widget_set-0-id": "",
1374 1387
             "widget_set-0-owner": "1",
1375 1388
             "widget_set-0-name": "",
@@ -1382,6 +1395,7 @@ def setUp(self):
1382 1395
 
1383 1396
             "doohickey_set-TOTAL_FORMS": "3",
1384 1397
             "doohickey_set-INITIAL_FORMS": u"0",
  1398
+            "doohickey_set-MAX_NUM_FORMS": u"0",
1385 1399
             "doohickey_set-0-owner": "1",
1386 1400
             "doohickey_set-0-code": "",
1387 1401
             "doohickey_set-0-name": "",
@@ -1394,6 +1408,7 @@ def setUp(self):
1394 1408
 
1395 1409
             "grommet_set-TOTAL_FORMS": "3",
1396 1410
             "grommet_set-INITIAL_FORMS": u"0",
  1411
+            "grommet_set-MAX_NUM_FORMS": u"0",
1397 1412
             "grommet_set-0-code": "",
1398 1413
             "grommet_set-0-owner": "1",
1399 1414
             "grommet_set-0-name": "",
@@ -1406,6 +1421,7 @@ def setUp(self):
1406 1421
 
1407 1422
             "whatsit_set-TOTAL_FORMS": "3",
1408 1423
             "whatsit_set-INITIAL_FORMS": u"0",
  1424
+            "whatsit_set-MAX_NUM_FORMS": u"0",
1409 1425
             "whatsit_set-0-owner": "1",
1410 1426
             "whatsit_set-0-index": "",
1411 1427
             "whatsit_set-0-name": "",
@@ -1418,6 +1434,7 @@ def setUp(self):
1418 1434
 
1419 1435
             "fancydoodad_set-TOTAL_FORMS": "3",
1420 1436
             "fancydoodad_set-INITIAL_FORMS": u"0",
  1437
+            "fancydoodad_set-MAX_NUM_FORMS": u"0",
1421 1438
             "fancydoodad_set-0-doodad_ptr": "",
1422 1439
             "fancydoodad_set-0-owner": "1",
1423 1440
             "fancydoodad_set-0-name": "",
@@ -1433,6 +1450,7 @@ def setUp(self):
1433 1450
 
1434 1451
             "category_set-TOTAL_FORMS": "3",
1435 1452
             "category_set-INITIAL_FORMS": "0",
  1453
+            "category_set-MAX_NUM_FORMS": "0",
1436 1454
             "category_set-0-order": "",
1437 1455
             "category_set-0-id": "",
1438 1456
             "category_set-0-collector": "1",
@@ -1624,6 +1642,7 @@ def test_ordered_inline(self):
1624 1642
 
1625 1643
             "category_set-TOTAL_FORMS": "7",
1626 1644
             "category_set-INITIAL_FORMS": "4",
  1645
+            "category_set-MAX_NUM_FORMS": "0",
1627 1646
 
1628 1647
             "category_set-0-order": "14",
1629 1648
             "category_set-0-id": "1",
@@ -1750,9 +1769,9 @@ def test_readonly_get(self):
1750 1769
         response = self.client.get('/test_admin/admin/admin_views/post/add/')
1751 1770
         self.assertEqual(response.status_code, 200)
1752 1771
         self.assertNotContains(response, 'name="posted"')
1753  
-        # 3 fields + 2 submit buttons + 2 inline management form fields, + 2
1754  
-        # hidden fields for inlines + 1 field for the inline
1755  
-        self.assertEqual(response.content.count("input"), 10)
  1772
+        # 3 fields + 2 submit buttons + 4 inline management form fields, + 2
  1773
+        # hidden fields for inlines + 1 field for the inline + 2 empty form
  1774
+        self.assertEqual(response.content.count("input"), 14)
1756 1775
         self.assertContains(response, formats.localize(datetime.date.today()))
1757 1776
         self.assertContains(response,
1758 1777
             "<label>Awesomeness level:</label>")
@@ -1773,6 +1792,7 @@ def test_readonly_post(self):
1773 1792
             "content": "This is an incredible development.",
1774 1793
             "link_set-TOTAL_FORMS": "1",
1775 1794
             "link_set-INITIAL_FORMS": "0",
  1795
+            "link_set-MAX_NUM_FORMS": "0",
1776 1796
         }
1777 1797
         response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
1778 1798
         self.assertEqual(response.status_code, 302)
18  tests/regressiontests/forms/formsets.py
@@ -20,7 +20,7 @@
20 20
 
21 21
 >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
22 22
 >>> print formset
23  
-<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" />
  23
+<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" />
24 24
 <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
25 25
 <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
26 26
 
@@ -34,6 +34,7 @@
34 34
 >>> data = {
35 35
 ...     'choices-TOTAL_FORMS': '1', # the number of forms rendered
36 36
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  37
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
37 38
 ...     'choices-0-choice': 'Calexico',
38 39
 ...     'choices-0-votes': '100',
39 40
 ... }
@@ -60,6 +61,7 @@
60 61
 >>> data = {
61 62
 ...     'choices-TOTAL_FORMS': '1', # the number of forms rendered
62 63
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  64
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
63 65
 ...     'choices-0-choice': 'Calexico',
64 66
 ...     'choices-0-votes': '',
65 67
 ... }
@@ -90,6 +92,7 @@
90 92
 >>> data = {
91 93
 ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
92 94
 ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  95
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
93 96
 ...     'choices-0-choice': 'Calexico',
94 97
 ...     'choices-0-votes': '100',
95 98
 ...     'choices-1-choice': '',
@@ -111,6 +114,7 @@
111 114
 >>> data = {
112 115
 ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
113 116
 ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  117
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
114 118
 ...     'choices-0-choice': 'Calexico',
115 119
 ...     'choices-0-votes': '100',
116 120
 ...     'choices-1-choice': 'The Decemberists',
@@ -130,6 +134,7 @@
130 134
 >>> data = {
131 135
 ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
132 136
 ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  137
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
133 138
 ...     'choices-0-choice': '', # deleted value
134 139
 ...     'choices-0-votes': '', # deleted value
135 140
 ...     'choices-1-choice': '',
@@ -167,6 +172,7 @@
167 172
 >>> data = {
168 173
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
169 174
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  175
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
170 176
 ...     'choices-0-choice': '',
171 177
 ...     'choices-0-votes': '',
172 178
 ...     'choices-1-choice': '',
@@ -187,6 +193,7 @@
187 193
 >>> data = {
188 194
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
189 195
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  196
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
190 197
 ...     'choices-0-choice': 'Calexico',
191 198
 ...     'choices-0-votes': '100',
192 199
 ...     'choices-1-choice': '',
@@ -207,6 +214,7 @@
207 214
 >>> data = {
208 215
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
209 216
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  217
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
210 218
 ...     'choices-0-choice': 'Calexico',
211 219
 ...     'choices-0-votes': '100',
212 220
 ...     'choices-1-choice': 'The Decemberists',
@@ -274,6 +282,7 @@
274 282
 >>> data = {
275 283
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
276 284
 ...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data
  285
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
277 286
 ...     'choices-0-choice': 'Calexico',
278 287
 ...     'choices-0-votes': '100',
279 288
 ...     'choices-0-DELETE': '',
@@ -303,6 +312,7 @@
303 312
 >>> data = {
304 313
 ...     'check-TOTAL_FORMS': '3', # the number of forms rendered
305 314
 ...     'check-INITIAL_FORMS': '2', # the number of forms with initial data
  315
+...     'check-MAX_NUM_FORMS': '0', # max number of forms
306 316
 ...     'check-0-field': '200',
307 317
 ...     'check-0-DELETE': '',
308 318
 ...     'check-1-field': '50',
@@ -351,6 +361,7 @@
351 361
 >>> data = {
352 362
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
353 363
 ...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data
  364
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
354 365
 ...     'choices-0-choice': 'Calexico',
355 366
 ...     'choices-0-votes': '100',
356 367
 ...     'choices-0-ORDER': '1',
@@ -377,6 +388,7 @@
377 388
 >>> data = {
378 389
 ...     'choices-TOTAL_FORMS': '4', # the number of forms rendered
379 390
 ...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data
  391
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
380 392
 ...     'choices-0-choice': 'Calexico',
381 393
 ...     'choices-0-votes': '100',
382 394
 ...     'choices-0-ORDER': '1',
@@ -406,6 +418,7 @@
406 418
 >>> data = {
407 419
 ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
408 420
 ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  421
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
409 422
 ... }
410 423
 
411 424
 >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
@@ -450,6 +463,7 @@
450 463
 >>> data = {
451 464
 ...     'choices-TOTAL_FORMS': '4', # the number of forms rendered
452 465
 ...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data
  466
+...     'choices-MAX_NUM_FORMS': '0', # max number of forms
453 467
 ...     'choices-0-choice': 'Calexico',
454 468
 ...     'choices-0-votes': '100',
455 469
 ...     'choices-0-ORDER': '1',
@@ -508,6 +522,7 @@
508 522
 >>> data = {
509 523
 ...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered
510 524
 ...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
  525
+...     'drinks-MAX_NUM_FORMS': '0', # max number of forms
511 526
 ...     'drinks-0-name': 'Gin and Tonic',
512 527
 ...     'drinks-1-name': 'Gin and Tonic',
513 528
 ... }
@@ -529,6 +544,7 @@
529 544
 >>> data = {
530 545
 ...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered
531 546
 ...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
  547
+...     'drinks-MAX_NUM_FORMS': '0', # max number of forms
532 548
 ...     'drinks-0-name': 'Gin and Tonic',
533 549
 ...     'drinks-1-name': 'Bloody Mary',
534 550
 ... }
3  tests/regressiontests/generic_inline_admin/tests.py
@@ -58,6 +58,7 @@ def testBasicAddPost(self):
58 58
             # inline data
59 59
             "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"1",
60 60
             "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"0",
  61
+            "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
61 62
         }
62 63
         response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/add/', post_data)
63 64
         self.failUnlessEqual(response.status_code, 302) # redirect somewhere
@@ -71,6 +72,7 @@ def testBasicEditPost(self):
71 72
             # inline data
72 73
             "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"3",
73 74
             "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"2",
  75
+            "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
74 76
             "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.mp3_media_pk,
75 77
             "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3",
76 78
             "generic_inline_admin-media-content_type-object_id-1-id": u"%d" % self.png_media_pk,
@@ -192,6 +194,7 @@ def testAdd(self):
192 194
             # inline data
193 195
             "generic_inline_admin-phonenumber-content_type-object_id-TOTAL_FORMS": u"1",
194 196
             "generic_inline_admin-phonenumber-content_type-object_id-INITIAL_FORMS": u"0",
  197
+            "generic_inline_admin-phonenumber-content_type-object_id-MAX_NUM_FORMS": u"0",
195 198
             "generic_inline_admin-phonenumber-content_type-object_id-0-id": "",
196 199
             "generic_inline_admin-phonenumber-content_type-object_id-0-phone_number": "555-555-5555",
197 200
         }
4  tests/regressiontests/inline_formsets/tests.py
@@ -10,6 +10,7 @@ def test_deletion(self):
10 10
         data = {
11 11
             'poem_set-TOTAL_FORMS': u'1',
12 12
             'poem_set-INITIAL_FORMS': u'1',
  13
+            'poem_set-MAX_NUM_FORMS': u'0',
13 14
             'poem_set-0-id': str(poem.pk),
14 15
             'poem_set-0-poet': str(poet.pk),
15 16
             'poem_set-0-name': u'test',
@@ -30,6 +31,7 @@ def test_add_form_deletion_when_invalid(self):
30 31
         data = {
31 32
             'poem_set-TOTAL_FORMS': u'1',
32 33
             'poem_set-INITIAL_FORMS': u'0',
  34
+            'poem_set-MAX_NUM_FORMS': u'0',
33 35
             'poem_set-0-id': u'',
34 36
             'poem_set-0-poem': u'1',
35 37
             'poem_set-0-name': u'x' * 1000,
@@ -58,6 +60,7 @@ def test_change_form_deletion_when_invalid(self):
58 60
         data = {
59 61
             'poem_set-TOTAL_FORMS': u'1',
60 62
             'poem_set-INITIAL_FORMS': u'1',
  63
+            'poem_set-MAX_NUM_FORMS': u'0',
61 64
             'poem_set-0-id': u'1',
62 65
             'poem_set-0-poem': u'1',
63 66
             'poem_set-0-name': u'x' * 1000,
@@ -88,6 +91,7 @@ def test_save_new(self):
88 91
         data = {
89 92
             'child_set-TOTAL_FORMS': u'1',
90 93
             'child_set-INITIAL_FORMS': u'0',
  94
+            'child_set-MAX_NUM_FORMS': u'0',
91 95
             'child_set-0-name': u'child',
92 96
         }
93 97
         formset = ChildFormSet(data, instance=school)
6  tests/regressiontests/model_formsets_regress/tests.py
@@ -20,6 +20,7 @@ def test_formset_over_to_field(self):
20 20
             'username': u'apollo13',
21 21
             'usersite_set-TOTAL_FORMS': u'1',
22 22
             'usersite_set-INITIAL_FORMS': u'0',
  23
+            'usersite_set-MAX_NUM_FORMS': u'0',
23 24
             'usersite_set-0-data': u'10',
24 25
             'usersite_set-0-user': u'apollo13'
25 26
         }
@@ -43,6 +44,7 @@ def test_formset_over_to_field(self):
43 44
         data = {
44 45
             'usersite_set-TOTAL_FORMS': u'1',
45 46
             'usersite_set-INITIAL_FORMS': u'1',
  47
+            'usersite_set-MAX_NUM_FORMS': u'0',
46 48
             'usersite_set-0-id': unicode(usersite[0]['id']),
47 49
             'usersite_set-0-data': u'11',
48 50
             'usersite_set-0-user': u'apollo13'
@@ -60,6 +62,7 @@ def test_formset_over_to_field(self):
60 62
         data = {
61 63
             'usersite_set-TOTAL_FORMS': u'2',
62 64
             'usersite_set-INITIAL_FORMS': u'1',
  65
+            'usersite_set-MAX_NUM_FORMS': u'0',
63 66
             'usersite_set-0-id': unicode(usersite[0]['id']),
64 67
             'usersite_set-0-data': u'11',
65 68
             'usersite_set-0-user': u'apollo13',
@@ -92,6 +95,7 @@ def test_formset_over_inherited_model(self):
92 95
             'name': u"Guido's House of Pasta",
93 96
             'manager_set-TOTAL_FORMS': u'1',
94 97
             'manager_set-INITIAL_FORMS': u'0',
  98
+            'manager_set-MAX_NUM_FORMS': u'0',
95 99
             'manager_set-0-name': u'Guido Van Rossum'
96 100
         }
97 101
         restaurant = User()
@@ -113,6 +117,7 @@ def test_formset_over_inherited_model(self):
113 117
         data = {
114 118
             'manager_set-TOTAL_FORMS': u'1',
115 119
             'manager_set-INITIAL_FORMS': u'1',
  120
+            'manager_set-MAX_NUM_FORMS': u'0',
116 121
             'manager_set-0-id': unicode(manager[0]['id']),
117 122
             'manager_set-0-name': u'Terry Gilliam'
118 123
         }
@@ -128,6 +133,7 @@ def test_formset_over_inherited_model(self):
128 133
         data = {
129 134
             'manager_set-TOTAL_FORMS': u'2',
130 135
             'manager_set-INITIAL_FORMS': u'1',
  136
+            'manager_set-MAX_NUM_FORMS': u'0',
131 137
             'manager_set-0-id': unicode(manager[0]['id']),
132 138
             'manager_set-0-name': u'Terry Gilliam',
133 139
             'manager_set-1-name': u'John Cleese'

0 notes on commit 9555f2b

Please sign in to comment.
Something went wrong with that request. Please try again.