diff -r b758351d191f -r cc9b7e14412b web/lib/django/forms/formsets.py --- a/web/lib/django/forms/formsets.py Wed May 19 17:43:59 2010 +0200 +++ b/web/lib/django/forms/formsets.py Tue May 25 02:43:45 2010 +0200 @@ -1,16 +1,18 @@ from forms import Form +from django.core.exceptions import ValidationError from django.utils.encoding import StrAndUnicode from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ from fields import IntegerField, BooleanField from widgets import Media, HiddenInput -from util import ErrorList, ErrorDict, ValidationError +from util import ErrorList __all__ = ('BaseFormSet', 'all_valid') # special field names TOTAL_FORM_COUNT = 'TOTAL_FORMS' INITIAL_FORM_COUNT = 'INITIAL_FORMS' +MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' ORDERING_FIELD_NAME = 'ORDER' DELETION_FIELD_NAME = 'DELETE' @@ -23,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(required=False, widget=HiddenInput) super(ManagementForm, self).__init__(*args, **kwargs) class BaseFormSet(StrAndUnicode): @@ -55,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) @@ -65,8 +69,13 @@ 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 @@ -77,7 +86,7 @@ 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 @@ -118,6 +127,21 @@ return self.forms[self.initial_form_count():] extra_forms = property(_get_extra_forms) + def _get_empty_form(self, **kwargs): + defaults = { + 'auto_id': self.auto_id, + 'prefix': self.add_prefix('__prefix__'), + 'empty_permitted': True, + } + if self.data or self.files: + defaults['data'] = self.data + defaults['files'] = self.files + defaults.update(kwargs) + form = self.form(**defaults) + self.add_fields(form, None) + return form + empty_form = property(_get_empty_form) + # Maybe this should just go away? def _get_cleaned_data(self): """ @@ -144,7 +168,7 @@ # if this is an extra form and hasn't changed, don't consider it if i >= self.initial_form_count() and not form.has_changed(): continue - if form.cleaned_data[DELETION_FIELD_NAME]: + if self._should_delete_form(form): self._deleted_form_indexes.append(i) return [self.forms[i] for i in self._deleted_form_indexes] deleted_forms = property(_get_deleted_forms) @@ -168,7 +192,7 @@ if i >= self.initial_form_count() and not form.has_changed(): continue # don't add data marked for deletion to self.ordered_data - if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: + if self.can_delete and self._should_delete_form(form): continue self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) # After we're done populating self._ordering, sort it. @@ -212,6 +236,15 @@ return self._errors errors = property(_get_errors) + def _should_delete_form(self, form): + # The way we lookup the value of the deletion field here takes + # more code than we'd like, but the form's cleaned_data will + # not exist if the form is invalid. + field = form.fields[DELETION_FIELD_NAME] + raw_value = form._raw_value(DELETION_FIELD_NAME) + should_delete = field.clean(raw_value) + return should_delete + def is_valid(self): """ Returns True if form.errors is empty for every form in self.forms. @@ -224,13 +257,7 @@ for i in range(0, self.total_form_count()): form = self.forms[i] if self.can_delete: - # The way we lookup the value of the deletion field here takes - # more code than we'd like, but the form's cleaned_data will - # not exist if the form is invalid. - field = form.fields[DELETION_FIELD_NAME] - raw_value = form._raw_value(DELETION_FIELD_NAME) - should_delete = field.clean(raw_value) - if should_delete: + if self._should_delete_form(form): # This form is going to be deleted so any of its errors # should not cause the entire formset to be invalid. continue @@ -252,7 +279,7 @@ try: self.clean() except ValidationError, e: - self._non_form_errors = e.messages + self._non_form_errors = self.error_class(e.messages) def clean(self): """ @@ -267,7 +294,7 @@ """A hook for adding extra fields on to each form instance.""" if self.can_order: # Only pre-fill the ordering field for initial forms. - if index < self.initial_form_count(): + if index is not None and index < self.initial_form_count(): form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False) else: form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False) @@ -302,7 +329,7 @@ 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,