Author: jezdez
Date: 2010-02-01 08:14:56 -0600 (Mon, 01 Feb 2010)
New Revision: 12369

Modified:
   django/trunk/django/contrib/admin/media/js/inlines.js
   django/trunk/django/contrib/admin/media/js/inlines.min.js
   django/trunk/django/contrib/admin/templates/admin/edit_inline/stacked.html
   django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html
   django/trunk/django/forms/formsets.py
   django/trunk/tests/modeltests/model_formsets/models.py
   django/trunk/tests/modeltests/model_formsets/tests.py
   django/trunk/tests/regressiontests/admin_views/tests.py
   django/trunk/tests/regressiontests/forms/formsets.py
   django/trunk/tests/regressiontests/generic_inline_admin/tests.py
   django/trunk/tests/regressiontests/inline_formsets/tests.py
   django/trunk/tests/regressiontests/model_formsets_regress/tests.py
Log:
Fixed #12692 - Properly handle the extra clause of admin inline formsets. Also 
fixes #12703, second error.

Modified: django/trunk/django/contrib/admin/media/js/inlines.js
===================================================================
--- django/trunk/django/contrib/admin/media/js/inlines.js       2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/django/contrib/admin/media/js/inlines.js       2010-02-01 
14:14:56 UTC (rev 12369)
@@ -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('<span><a class="' + options.deleteCssClass + 
'" href="javascript:void(0)">' + options.deleteText + "</a></span>");
                                }
+                               
$(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; 
i<formCount; i++) {
+                                       for (var i=0, formCount=forms.length; 
i<formCount; i++)
+                                       {
                                                
$(forms.get(i)).find("input,select,textarea,label").each(function() {
                                                        
updateElementIndex(this, options.prefix, i);
                                                });
                                        }
                                        return false;
                                });
-                               
$(row).find("input,select,textarea,label").each(function() {
-                                       updateElementIndex(this, 
options.prefix, totalForms);
-                               });
                                // If a post-add callback was supplied, call it 
with the added form:
-                               if (options.added) options.added($(row));
+                               if (options.added) {
+                                       options.added($(row));
+                               }
                                return false;
                        });
                }
-               return $(this);
+               return this;
        }
-
        /* Setup plugin defaults */
        $.fn.formset.defaults = {
-               prefix: "form",                                  // The form 
prefix for your django formset
-               addText: "add another",                  // Text for the add 
link
-               deleteText: "remove",                    // Text for the delete 
link
-               addCssClass: "add-row",                  // CSS class applied 
to the add link
-               deleteCssClass: "delete-row",    // CSS class applied to the 
delete link
-               emptyCssClass: "empty-row",              // CSS class applied 
to the empty row
-               formCssClass: "dynamic-form",    // CSS class applied to each 
form in a formset
-               added: null,                                     // Function 
called each time a new form is added
-               removed: null                                    // Function 
called each time a form is deleted
+               prefix: "form",                                 // The form 
prefix for your django formset
+               addText: "add another",                 // Text for the add link
+               deleteText: "remove",                   // Text for the delete 
link
+               addCssClass: "add-row",                 // CSS class applied to 
the add link
+               deleteCssClass: "delete-row",   // CSS class applied to the 
delete link
+               emptyCssClass: "empty-row",             // CSS class applied to 
the empty row
+               formCssClass: "dynamic-form",   // CSS class applied to each 
form in a formset
+               added: null,                                    // Function 
called each time a new form is added
+               removed: null                                   // Function 
called each time a form is deleted
        }
-})(jQuery)
+})(jQuery);

Modified: django/trunk/django/contrib/admin/media/js/inlines.min.js
===================================================================
--- django/trunk/django/contrib/admin/media/js/inlines.min.js   2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/django/contrib/admin/media/js/inlines.min.js   2010-02-01 
14:14:56 UTC (rev 12369)
@@ -1 +1,5 @@
-(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);
\ No newline at end of file
+(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)});
+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())+
+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="'+
+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,
+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);

Modified: 
django/trunk/django/contrib/admin/templates/admin/edit_inline/stacked.html
===================================================================
--- django/trunk/django/contrib/admin/templates/admin/edit_inline/stacked.html  
2010-02-01 14:13:47 UTC (rev 12368)
+++ django/trunk/django/contrib/admin/templates/admin/edit_inline/stacked.html  
2010-02-01 14:14:56 UTC (rev 12369)
@@ -19,24 +19,25 @@
 </div>
 
 <script type="text/javascript">
-jQuery.noConflict();
-jQuery(document).ready(function($) {
-    var rows = "#{{ inline_admin_formset.formset.prefix }}-group 
.inline-related";
-    updateInlineLabel = function(row) {
-        $(rows).find(".inline_label").each(function(i) {
-            var count = i + 1;
-            $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
+(function($) {
+    $(document).ready(function() {
+        var rows = "#{{ inline_admin_formset.formset.prefix }}-group 
.inline-related";
+        updateInlineLabel = function(row) {
+            $(rows).find(".inline_label").each(function(i) {
+                var count = i + 1;
+                $(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
+            });
+        }
+        $(rows).formset({
+            prefix: "{{ inline_admin_formset.formset.prefix }}",
+            addText: "{% blocktrans with 
inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ 
verbose_name }}{% endblocktrans %}",
+            formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
+            deleteCssClass: "inline-deletelink",
+            deleteText: "{% trans "Remove" %}",
+            emptyCssClass: "empty-form",
+            removed: updateInlineLabel,
+            added: updateInlineLabel
         });
-    }
-    $(rows).formset({
-        prefix: "{{ inline_admin_formset.formset.prefix }}",
-        addText: "{% blocktrans with 
inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ 
verbose_name }}{% endblocktrans %}",
-        formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
-        deleteCssClass: "inline-deletelink",
-        deleteText: "{% trans "Remove" %}",
-        emptyCssClass: "empty-form",
-        removed: updateInlineLabel,
-        added: updateInlineLabel
     });
-});
+})(jQuery.noConflict());
 </script>
\ No newline at end of file

Modified: 
django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html
===================================================================
--- django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html  
2010-02-01 14:13:47 UTC (rev 12368)
+++ django/trunk/django/contrib/admin/templates/admin/edit_inline/tabular.html  
2010-02-01 14:14:56 UTC (rev 12369)
@@ -65,23 +65,24 @@
 </div>
 
 <script type="text/javascript">
-jQuery.noConflict();
-jQuery(document).ready(function($) {
-    var rows = "#{{ inline_admin_formset.formset.prefix }}-group 
.tabular.inline-related tbody tr";
-    alternatingRows = function(row) {
-        $(rows).not(".add-row").removeClass("row1 row2")
-            .filter(":even").addClass("row1").end()
-            .filter(rows + ":odd").addClass("row2");
-    }
-    $(rows).formset({
-        prefix: "{{ inline_admin_formset.formset.prefix }}",
-        addText: "{% blocktrans with 
inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ 
verbose_name }}{% endblocktrans %}",
-        formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
-        deleteCssClass: "inline-deletelink",
-        deleteText: "{% trans "Remove" %}",
-        emptyCssClass: "empty-form",
-        removed: alternatingRows,
-        added: alternatingRows
+(function($) {
+    $(document).ready(function($) {
+        var rows = "#{{ inline_admin_formset.formset.prefix }}-group 
.tabular.inline-related tbody tr";
+        alternatingRows = function(row) {
+            $(rows).not(".add-row").removeClass("row1 row2")
+                .filter(":even").addClass("row1").end()
+                .filter(rows + ":odd").addClass("row2");
+        }
+        $(rows).formset({
+            prefix: "{{ inline_admin_formset.formset.prefix }}",
+            addText: "{% blocktrans with 
inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ 
verbose_name }}{% endblocktrans %}",
+            formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
+            deleteCssClass: "inline-deletelink",
+            deleteText: "{% trans "Remove" %}",
+            emptyCssClass: "empty-form",
+            removed: alternatingRows,
+            added: alternatingRows
+        });
     });
-});
+})(jQuery.noConflict());
 </script>
\ No newline at end of file

Modified: django/trunk/django/forms/formsets.py
===================================================================
--- django/trunk/django/forms/formsets.py       2010-02-01 14:13:47 UTC (rev 
12368)
+++ django/trunk/django/forms/formsets.py       2010-02-01 14:14:56 UTC (rev 
12369)
@@ -12,6 +12,7 @@
 # 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 @@
     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 @@
         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)

Modified: django/trunk/tests/modeltests/model_formsets/models.py
===================================================================
--- django/trunk/tests/modeltests/model_formsets/models.py      2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/tests/modeltests/model_formsets/models.py      2010-02-01 
14:14:56 UTC (rev 12369)
@@ -200,6 +200,7 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 
 >>> 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 @@
 >>> 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
 >>> formset.save()
 [<OwnerProfile: Joe Perry is 54>]
-
 >>> formset = FormSet(instance=owner)
 >>> for form in formset.forms:
 ...     print form.as_p()
@@ -793,6 +812,7 @@
 >>> 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 @@
 # 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> data = {
 ...     'form-TOTAL_FORMS': '2',
 ...     'form-INITIAL_FORMS': '0',
+...     'form-MAX_NUM_FORMS': '0',
 ...
 ...     'form-0-title': 'foo',
 ...     'form-0-slug': 'Morning in Prague',

Modified: django/trunk/tests/modeltests/model_formsets/tests.py
===================================================================
--- django/trunk/tests/modeltests/model_formsets/tests.py       2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/tests/modeltests/model_formsets/tests.py       2010-02-01 
14:14:56 UTC (rev 12369)
@@ -9,6 +9,7 @@
         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 @@
         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 @@
         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,
         }

Modified: django/trunk/tests/regressiontests/admin_views/tests.py
===================================================================
--- django/trunk/tests/regressiontests/admin_views/tests.py     2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/tests/regressiontests/admin_views/tests.py     2010-02-01 
14:14:56 UTC (rev 12369)
@@ -87,6 +87,7 @@
             # 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 @@
         # 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 @@
             # 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"&lt;p&gt;Svært frustrerende med 
UnicodeDecodeError&lt;/p&gt;",
@@ -926,14 +929,14 @@
     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("<input"), 17)
+        # 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
+        self.failUnlessEqual(response.content.count("<input"), 18)
         # 1 select per object = 3 selects
         self.failUnlessEqual(response.content.count("<select"), 4)
 
@@ -941,6 +944,7 @@
         data = {
             "form-TOTAL_FORMS": "3",
             "form-INITIAL_FORMS": "3",
+            "form-MAX_NUM_FORMS": "0",
 
             "form-0-gender": "1",
             "form-0-id": "1",
@@ -961,6 +965,7 @@
         data = {
             "form-TOTAL_FORMS": "2",
             "form-INITIAL_FORMS": "2",
+            "form-MAX_NUM_FORMS": "0",
 
             "form-0-id": "1",
             "form-0-gender": "1",
@@ -978,6 +983,7 @@
         data = {
             "form-TOTAL_FORMS": "1",
             "form-INITIAL_FORMS": "1",
+            "form-MAX_NUM_FORMS": "0",
 
             "form-0-id": "1",
             "form-0-gender": "1"
@@ -998,6 +1004,7 @@
         data = {
             "form-TOTAL_FORMS": "4",
             "form-INITIAL_FORMS": "4",
+            "form-MAX_NUM_FORMS": "0",
 
             "form-0-order": "14",
             "form-0-id": "1",
@@ -1069,9 +1076,11 @@
             # inline data
             "accounts-TOTAL_FORMS": u"1",
             "accounts-INITIAL_FORMS": u"0",
+            "accounts-MAX_NUM_FORMS": u"0",
             "accounts-0-username": foo_user,
             "accounts-2-TOTAL_FORMS": u"1",
             "accounts-2-INITIAL_FORMS": u"0",
+            "accounts-2-MAX_NUM_FORMS": u"0",
             "accounts-2-0-username": bar_user,
         }
 
@@ -1096,6 +1105,7 @@
 
             "accounts-TOTAL_FORMS": "2",
             "accounts-INITIAL_FORMS": u"1",
+            "accounts-MAX_NUM_FORMS": u"0",
 
             "accounts-0-username": "%s-1" % foo_user,
             "accounts-0-account_ptr": "1",
@@ -1103,6 +1113,7 @@
 
             "accounts-2-TOTAL_FORMS": u"2",
             "accounts-2-INITIAL_FORMS": u"1",
+            "accounts-2-MAX_NUM_FORMS": u"0",
 
             "accounts-2-0-username": "%s-1" % bar_user,
             "accounts-2-0-account_ptr": "2",
@@ -1348,6 +1359,7 @@
             "name": u"Test Gallery",
             "pictures-TOTAL_FORMS": u"2",
             "pictures-INITIAL_FORMS": u"1",
+            "pictures-MAX_NUM_FORMS": u"0",
             "pictures-0-id": u"1",
             "pictures-0-gallery": u"1",
             "pictures-0-name": "Test Picture",
@@ -1370,6 +1382,7 @@
 
             "widget_set-TOTAL_FORMS": "3",
             "widget_set-INITIAL_FORMS": u"0",
+            "widget_set-MAX_NUM_FORMS": u"0",
             "widget_set-0-id": "",
             "widget_set-0-owner": "1",
             "widget_set-0-name": "",
@@ -1382,6 +1395,7 @@
 
             "doohickey_set-TOTAL_FORMS": "3",
             "doohickey_set-INITIAL_FORMS": u"0",
+            "doohickey_set-MAX_NUM_FORMS": u"0",
             "doohickey_set-0-owner": "1",
             "doohickey_set-0-code": "",
             "doohickey_set-0-name": "",
@@ -1394,6 +1408,7 @@
 
             "grommet_set-TOTAL_FORMS": "3",
             "grommet_set-INITIAL_FORMS": u"0",
+            "grommet_set-MAX_NUM_FORMS": u"0",
             "grommet_set-0-code": "",
             "grommet_set-0-owner": "1",
             "grommet_set-0-name": "",
@@ -1406,6 +1421,7 @@
 
             "whatsit_set-TOTAL_FORMS": "3",
             "whatsit_set-INITIAL_FORMS": u"0",
+            "whatsit_set-MAX_NUM_FORMS": u"0",
             "whatsit_set-0-owner": "1",
             "whatsit_set-0-index": "",
             "whatsit_set-0-name": "",
@@ -1418,6 +1434,7 @@
 
             "fancydoodad_set-TOTAL_FORMS": "3",
             "fancydoodad_set-INITIAL_FORMS": u"0",
+            "fancydoodad_set-MAX_NUM_FORMS": u"0",
             "fancydoodad_set-0-doodad_ptr": "",
             "fancydoodad_set-0-owner": "1",
             "fancydoodad_set-0-name": "",
@@ -1433,6 +1450,7 @@
 
             "category_set-TOTAL_FORMS": "3",
             "category_set-INITIAL_FORMS": "0",
+            "category_set-MAX_NUM_FORMS": "0",
             "category_set-0-order": "",
             "category_set-0-id": "",
             "category_set-0-collector": "1",
@@ -1624,6 +1642,7 @@
 
             "category_set-TOTAL_FORMS": "7",
             "category_set-INITIAL_FORMS": "4",
+            "category_set-MAX_NUM_FORMS": "0",
 
             "category_set-0-order": "14",
             "category_set-0-id": "1",
@@ -1750,9 +1769,9 @@
         response = self.client.get('/test_admin/admin/admin_views/post/add/')
         self.assertEqual(response.status_code, 200)
         self.assertNotContains(response, 'name="posted"')
-        # 3 fields + 2 submit buttons + 2 inline management form fields, + 2
-        # hidden fields for inlines + 1 field for the inline
-        self.assertEqual(response.content.count("input"), 10)
+        # 3 fields + 2 submit buttons + 4 inline management form fields, + 2
+        # hidden fields for inlines + 1 field for the inline + 2 empty form
+        self.assertEqual(response.content.count("input"), 14)
         self.assertContains(response, formats.localize(datetime.date.today()))
         self.assertContains(response,
             "<label>Awesomeness level:</label>")
@@ -1773,6 +1792,7 @@
             "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)

Modified: django/trunk/tests/regressiontests/forms/formsets.py
===================================================================
--- django/trunk/tests/regressiontests/forms/formsets.py        2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/tests/regressiontests/forms/formsets.py        2010-02-01 
14:14:56 UTC (rev 12369)
@@ -20,7 +20,7 @@
 
 >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
 >>> print formset
-<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input 
type="hidden" name="choices-INITIAL_FORMS" value="0" />
+<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input 
type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" 
name="choices-MAX_NUM_FORMS" value="0" />
 <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>
 
@@ -34,6 +34,7 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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 @@
 >>> 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',
 ... }

Modified: django/trunk/tests/regressiontests/generic_inline_admin/tests.py
===================================================================
--- django/trunk/tests/regressiontests/generic_inline_admin/tests.py    
2010-02-01 14:13:47 UTC (rev 12368)
+++ django/trunk/tests/regressiontests/generic_inline_admin/tests.py    
2010-02-01 14:14:56 UTC (rev 12369)
@@ -58,6 +58,7 @@
             # 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 @@
             # 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 @@
             # 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",
         }

Modified: django/trunk/tests/regressiontests/inline_formsets/tests.py
===================================================================
--- django/trunk/tests/regressiontests/inline_formsets/tests.py 2010-02-01 
14:13:47 UTC (rev 12368)
+++ django/trunk/tests/regressiontests/inline_formsets/tests.py 2010-02-01 
14:14:56 UTC (rev 12369)
@@ -10,6 +10,7 @@
         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 @@
         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 @@
         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 @@
         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)

Modified: django/trunk/tests/regressiontests/model_formsets_regress/tests.py
===================================================================
--- django/trunk/tests/regressiontests/model_formsets_regress/tests.py  
2010-02-01 14:13:47 UTC (rev 12368)
+++ django/trunk/tests/regressiontests/model_formsets_regress/tests.py  
2010-02-01 14:14:56 UTC (rev 12369)
@@ -20,6 +20,7 @@
             '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 @@
         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 @@
         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 @@
             '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 @@
         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 @@
         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'

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to