diff options
| author | Jannis Leidel <jannis@leidel.info> | 2010-03-27 23:03:56 +0000 |
|---|---|---|
| committer | Jannis Leidel <jannis@leidel.info> | 2010-03-27 23:03:56 +0000 |
| commit | aba95dcc0b5370ffac3d3b701c3ca7782ee999c1 (patch) | |
| tree | b1bd806435d62fd4db98518e08dab0e6ce733c54 /django | |
| parent | 9df8d9c2946bf74288d410e2dc8917493b079b11 (diff) | |
Fixed #13023 - Removed ambiguity with regard to the max_num option of formsets and as a result of admin inlines. Thanks to Gabriel Hurley for the patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12872 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/admin/media/js/inlines.js | 9 | ||||
| -rw-r--r-- | django/contrib/admin/media/js/inlines.min.js | 6 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/validation.py | 14 | ||||
| -rw-r--r-- | django/contrib/contenttypes/generic.py | 2 | ||||
| -rw-r--r-- | django/forms/formsets.py | 15 | ||||
| -rw-r--r-- | django/forms/models.py | 12 |
7 files changed, 35 insertions, 25 deletions
diff --git a/django/contrib/admin/media/js/inlines.js b/django/contrib/admin/media/js/inlines.js index 7f12c0db3c..55118dfc4b 100644 --- a/django/contrib/admin/media/js/inlines.js +++ b/django/contrib/admin/media/js/inlines.js @@ -32,8 +32,9 @@ }; var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); - // only show the add button if we are allowed to add more items - var showAddButton = ((maxForms.val() == 0) || ((maxForms.val()-totalForms.val()) > 0)); + // only show the add button if we are allowed to add more items, + // note that max_num = None translates to a blank string. + var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0; $(this).each(function(i) { $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); }); @@ -77,7 +78,7 @@ // Update number of total forms $(totalForms).val(nextIndex + 1); // Hide add button in case we've hit the max, except we want to add infinitely - if ((maxForms.val() != 0) && (maxForms.val() <= totalForms.val())) { + if ((maxForms.val() != '') && (maxForms.val() <= totalForms.val())) { addButton.parent().hide(); } // The delete button of each row triggers a bunch of other things @@ -93,7 +94,7 @@ var forms = $("." + options.formCssClass); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); // Show add button again once we drop below max - if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) { + if ((maxForms.val() == '') || (maxForms.val() >= forms.length)) { addButton.parent().show(); } // Also, update names and ids for all remaining form controls diff --git a/django/contrib/admin/media/js/inlines.min.js b/django/contrib/admin/media/js/inlines.min.js index 6fc4f82b8d..29e048df54 100644 --- a/django/contrib/admin/media/js/inlines.min.js +++ b/django/contrib/admin/media/js/inlines.min.js @@ -1,5 +1,5 @@ -(function(a){a.fn.formset=function(f){var b=a.extend({},a.fn.formset.defaults,f),l=function(d,e,j){var c=new RegExp("("+e+"-\\d+)");e=e+"-"+j;a(d).attr("for")&&a(d).attr("for",a(d).attr("for").replace(c,e));if(d.id)d.id=d.id.replace(c,e);if(d.name)d.name=d.name.replace(c,e)};f=a("#id_"+b.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");f=h.val()==0||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)}); +(function(a){a.fn.formset=function(f){var b=a.extend({},a.fn.formset.defaults,f),l=function(d,e,j){var c=new RegExp("("+e+"-\\d+)");e=e+"-"+j;a(d).attr("for")&&a(d).attr("for",a(d).attr("for").replace(c,e));if(d.id)d.id=d.id.replace(c,e);if(d.name)d.name=d.name.replace(c,e)};f=a("#id_"+b.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var h=a("#id_"+b.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");f=h.val()==""||h.val()-f.val()>0;a(this).each(function(){a(this).not("."+b.emptyCssClass).addClass(b.formCssClass)}); if(a(this).length&&f){var i;if(a(this).attr("tagName")=="TR"){f=this.eq(0).children().length;a(this).parent().append('<tr class="'+b.addCssClass+'"><td colspan="'+f+'"><a href="javascript:void(0)">'+b.addText+"</a></tr>");i=a(this).parent().find("tr:last a")}else{a(this).filter(":last").after('<div class="'+b.addCssClass+'"><a href="javascript:void(0)">'+b.addText+"</a></div>");i=a(this).filter(":last").next().find("a")}i.click(function(){var d=a("#id_"+b.prefix+"-TOTAL_FORMS"),e=parseInt(d.val()), -j=a("#"+b.prefix+"-empty"),c=j.clone(true).get(0);a(c).removeClass(b.emptyCssClass).removeAttr("id").insertBefore(a(j));a(c).html(a(c).html().replace(/__prefix__/g,e));a(c).addClass(b.formCssClass).attr("id",b.prefix+(parseInt(e)+1));if(a(c).is("TR"))a(c).children(":last").append('<div><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></div>");else a(c).is("UL")||a(c).is("OL")?a(c).append('<li><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></li>"): -a(c).children(":first").append('<span><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");a(c).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,d.val())});a(d).val(e+1);h.val()!=0&&h.val()<=d.val()&&i.parent().hide();a(c).find("a."+b.deleteCssClass).click(function(){var g=a(this).parents("."+b.formCssClass);g.remove();b.removed&&b.removed(g);g=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(g.length);if(h.val()==0||h.val()>=g.length)i.parent().show(); +j=a("#"+b.prefix+"-empty"),c=j.clone(true).get(0);a(c).removeClass(b.emptyCssClass).removeAttr("id").insertBefore(a(j));a(c).html(a(c).html().replace(/__prefix__/g,e));a(c).addClass(b.formCssClass).attr("id",b.prefix+(e+1));if(a(c).is("TR"))a(c).children(":last").append('<div><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></div>");else a(c).is("UL")||a(c).is("OL")?a(c).append('<li><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></li>"): +a(c).children(":first").append('<span><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");a(c).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,d.val())});a(d).val(e+1);h.val()!=""&&h.val()<=d.val()&&i.parent().hide();a(c).find("a."+b.deleteCssClass).click(function(){var g=a(this).parents("."+b.formCssClass);g.remove();b.removed&&b.removed(g);g=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(g.length);if(h.val()==""||h.val()>=g.length)i.parent().show(); for(var k=0,m=g.length;k<m;k++)a(g.get(k)).find("input,select,textarea,label,a").each(function(){l(this,b.prefix,k)});return false});b.added&&b.added(a(c));return false})}return this};a.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(jQuery); diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index be91ffa41e..7d2904002e 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1179,7 +1179,7 @@ class InlineModelAdmin(BaseModelAdmin): fk_name = None formset = BaseInlineFormSet extra = 3 - max_num = 0 + max_num = None template = None verbose_name = None verbose_name_plural = None diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 6bcd52e061..bee28912af 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -170,11 +170,15 @@ def validate_inline(cls, parent, parent_model): fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True) # extra = 3 - # max_num = 0 - for attr in ('extra', 'max_num'): - if not isinstance(getattr(cls, attr), int): - raise ImproperlyConfigured("'%s.%s' should be a integer." - % (cls.__name__, attr)) + if not isinstance(getattr(cls, 'extra'), int): + raise ImproperlyConfigured("'%s.extra' should be a integer." + % cls.__name__) + + # max_num = None + max_num = getattr(cls, 'max_num', None) + if max_num is not None and not isinstance(max_num, int): + raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)." + % cls.__name__) # formset if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet): diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 5dffe2081d..dfc857d5f1 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -337,7 +337,7 @@ def generic_inlineformset_factory(model, form=ModelForm, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, - max_num=0, + max_num=None, formfield_callback=lambda f: f.formfield()): """ Returns an ``GenericInlineFormSet`` for the given kwargs. diff --git a/django/forms/formsets.py b/django/forms/formsets.py index ec14f813e9..3804f2b3c1 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -25,7 +25,7 @@ class ManagementForm(Form): def __init__(self, *args, **kwargs): self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) - self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(widget=HiddenInput) + self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput) super(ManagementForm, self).__init__(*args, **kwargs) class BaseFormSet(StrAndUnicode): @@ -69,8 +69,13 @@ class BaseFormSet(StrAndUnicode): if self.data or self.files: return self.management_form.cleaned_data[TOTAL_FORM_COUNT] else: - total_forms = self.initial_form_count() + self.extra - if total_forms > self.max_num > 0: + initial_forms = self.initial_form_count() + total_forms = initial_forms + self.extra + # Allow all existing related objects/inlines to be displayed, + # but don't allow extra beyond max_num. + if initial_forms > self.max_num >= 0: + total_forms = initial_forms + elif total_forms > self.max_num >= 0: total_forms = self.max_num return total_forms @@ -81,7 +86,7 @@ class BaseFormSet(StrAndUnicode): else: # Use the length of the inital data if it's there, 0 otherwise. initial_forms = self.initial and len(self.initial) or 0 - if initial_forms > self.max_num > 0: + if initial_forms > self.max_num >= 0: initial_forms = self.max_num return initial_forms @@ -324,7 +329,7 @@ class BaseFormSet(StrAndUnicode): return mark_safe(u'\n'.join([unicode(self.management_form), forms])) def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, - can_delete=False, max_num=0): + can_delete=False, max_num=None): """Return a FormSet for the given form class.""" attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, diff --git a/django/forms/models.py b/django/forms/models.py index 869138c922..608b87bd33 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -448,10 +448,10 @@ class BaseModelFormSet(BaseFormSet): if not qs.ordered: qs = qs.order_by(self.model._meta.pk.name) - if self.max_num > 0: - self._queryset = qs[:self.max_num] - else: - self._queryset = qs + # Removed queryset limiting here. As per discussion re: #13023 + # on django-dev, max_num should not prevent existing + # related objects/inlines from being displayed. + self._queryset = qs return self._queryset def save_new(self, form, commit=True): @@ -649,7 +649,7 @@ class BaseModelFormSet(BaseFormSet): def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, - max_num=0, fields=None, exclude=None): + max_num=None, fields=None, exclude=None): """ Returns a FormSet class for the given Django model class. """ @@ -799,7 +799,7 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): def inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, - extra=3, can_order=False, can_delete=True, max_num=0, + extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=lambda f: f.formfield()): """ Returns an ``InlineFormSet`` for the given kwargs. |
