diff options
Diffstat (limited to 'django/forms/formsets.py')
| -rw-r--r-- | django/forms/formsets.py | 238 |
1 files changed, 146 insertions, 92 deletions
diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 75b0646512..e5807e8688 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -5,17 +5,18 @@ from django.forms.renderers import get_default_renderer from django.forms.utils import ErrorList, RenderableFormMixin from django.forms.widgets import CheckboxInput, HiddenInput, NumberInput from django.utils.functional import cached_property -from django.utils.translation import gettext_lazy as _, ngettext +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext -__all__ = ('BaseFormSet', 'formset_factory', 'all_valid') +__all__ = ("BaseFormSet", "formset_factory", "all_valid") # special field names -TOTAL_FORM_COUNT = 'TOTAL_FORMS' -INITIAL_FORM_COUNT = 'INITIAL_FORMS' -MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS' -MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' -ORDERING_FIELD_NAME = 'ORDER' -DELETION_FIELD_NAME = 'DELETE' +TOTAL_FORM_COUNT = "TOTAL_FORMS" +INITIAL_FORM_COUNT = "INITIAL_FORMS" +MIN_NUM_FORM_COUNT = "MIN_NUM_FORMS" +MAX_NUM_FORM_COUNT = "MAX_NUM_FORMS" +ORDERING_FIELD_NAME = "ORDER" +DELETION_FIELD_NAME = "DELETE" # default minimum number of forms in a formset DEFAULT_MIN_NUM = 0 @@ -30,6 +31,7 @@ class ManagementForm(Form): new forms via JavaScript, you should increment the count field of this form as well. """ + TOTAL_FORMS = IntegerField(widget=HiddenInput) INITIAL_FORMS = IntegerField(widget=HiddenInput) # MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of the @@ -51,22 +53,31 @@ class BaseFormSet(RenderableFormMixin): """ A collection of instances of the same Form class. """ + deletion_widget = CheckboxInput ordering_widget = NumberInput default_error_messages = { - 'missing_management_form': _( - 'ManagementForm data is missing or has been tampered with. Missing fields: ' - '%(field_names)s. You may need to file a bug report if the issue persists.' + "missing_management_form": _( + "ManagementForm data is missing or has been tampered with. Missing fields: " + "%(field_names)s. You may need to file a bug report if the issue persists." ), } - template_name = 'django/forms/formsets/default.html' - template_name_p = 'django/forms/formsets/p.html' - template_name_table = 'django/forms/formsets/table.html' - template_name_ul = 'django/forms/formsets/ul.html' + template_name = "django/forms/formsets/default.html" + template_name_p = "django/forms/formsets/p.html" + template_name_table = "django/forms/formsets/table.html" + template_name_ul = "django/forms/formsets/ul.html" - def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, - initial=None, error_class=ErrorList, form_kwargs=None, - error_messages=None): + def __init__( + self, + data=None, + files=None, + auto_id="id_%s", + prefix=None, + initial=None, + error_class=ErrorList, + form_kwargs=None, + error_messages=None, + ): self.is_bound = data is not None or files is not None self.prefix = prefix or self.get_default_prefix() self.auto_id = auto_id @@ -80,7 +91,7 @@ class BaseFormSet(RenderableFormMixin): messages = {} for cls in reversed(type(self).__mro__): - messages.update(getattr(cls, 'default_error_messages', {})) + messages.update(getattr(cls, "default_error_messages", {})) if error_messages is not None: messages.update(error_messages) self.error_messages = messages @@ -105,14 +116,14 @@ class BaseFormSet(RenderableFormMixin): def __repr__(self): if self._errors is None: - is_valid = 'Unknown' + is_valid = "Unknown" else: is_valid = ( - self.is_bound and - not self._non_form_errors and - not any(form_errors for form_errors in self._errors) + self.is_bound + and not self._non_form_errors + and not any(form_errors for form_errors in self._errors) ) - return '<%s: bound=%s valid=%s total_forms=%s>' % ( + return "<%s: bound=%s valid=%s total_forms=%s>" % ( self.__class__.__qualname__, self.is_bound, is_valid, @@ -123,7 +134,12 @@ class BaseFormSet(RenderableFormMixin): def management_form(self): """Return the ManagementForm instance for this FormSet.""" if self.is_bound: - form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix, renderer=self.renderer) + form = ManagementForm( + self.data, + auto_id=self.auto_id, + prefix=self.prefix, + renderer=self.renderer, + ) form.full_clean() else: form = ManagementForm( @@ -146,7 +162,9 @@ class BaseFormSet(RenderableFormMixin): # count in the data; this is DoS protection to prevent clients # from forcing the server to instantiate arbitrary numbers of # forms - return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max) + return min( + self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max + ) else: initial_forms = self.initial_form_count() total_forms = max(initial_forms, self.min_num) + self.extra @@ -188,27 +206,27 @@ class BaseFormSet(RenderableFormMixin): def _construct_form(self, i, **kwargs): """Instantiate and return the i-th form instance in a formset.""" defaults = { - 'auto_id': self.auto_id, - 'prefix': self.add_prefix(i), - 'error_class': self.error_class, + "auto_id": self.auto_id, + "prefix": self.add_prefix(i), + "error_class": self.error_class, # Don't render the HTML 'required' attribute as it may cause # incorrect validation for extra, optional, and deleted # forms in the formset. - 'use_required_attribute': False, - 'renderer': self.renderer, + "use_required_attribute": False, + "renderer": self.renderer, } if self.is_bound: - defaults['data'] = self.data - defaults['files'] = self.files - if self.initial and 'initial' not in kwargs: + defaults["data"] = self.data + defaults["files"] = self.files + if self.initial and "initial" not in kwargs: try: - defaults['initial'] = self.initial[i] + defaults["initial"] = self.initial[i] except IndexError: pass # Allow extra forms to be empty, unless they're part of # the minimum forms. if i >= self.initial_form_count() and i >= self.min_num: - defaults['empty_permitted'] = True + defaults["empty_permitted"] = True defaults.update(kwargs) form = self.form(**defaults) self.add_fields(form, i) @@ -217,18 +235,18 @@ class BaseFormSet(RenderableFormMixin): @property def initial_forms(self): """Return a list of all the initial forms in this formset.""" - return self.forms[:self.initial_form_count()] + return self.forms[: self.initial_form_count()] @property def extra_forms(self): """Return a list of all the extra forms in this formset.""" - return self.forms[self.initial_form_count():] + return self.forms[self.initial_form_count() :] @property def empty_form(self): form = self.form( auto_id=self.auto_id, - prefix=self.add_prefix('__prefix__'), + prefix=self.add_prefix("__prefix__"), empty_permitted=True, use_required_attribute=False, **self.get_form_kwargs(None), @@ -243,7 +261,9 @@ class BaseFormSet(RenderableFormMixin): Return a list of form.cleaned_data dicts for every form in self.forms. """ if not self.is_valid(): - raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__) + raise AttributeError( + "'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__ + ) return [form.cleaned_data for form in self.forms] @property @@ -253,7 +273,7 @@ class BaseFormSet(RenderableFormMixin): return [] # construct _deleted_form_indexes which is just a list of form indexes # that have had their deletion widget set to True - if not hasattr(self, '_deleted_form_indexes'): + if not hasattr(self, "_deleted_form_indexes"): self._deleted_form_indexes = [] for i, form in enumerate(self.forms): # if this is an extra form and hasn't changed, don't consider it @@ -270,12 +290,14 @@ class BaseFormSet(RenderableFormMixin): Raise an AttributeError if ordering is not allowed. """ if not self.is_valid() or not self.can_order: - raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__) + raise AttributeError( + "'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__ + ) # Construct _ordering, which is a list of (form_index, order_field_value) # tuples. After constructing this list, we'll sort it by order_field_value # so we have a way to get to the form indexes in the order specified # by the form data. - if not hasattr(self, '_ordering'): + if not hasattr(self, "_ordering"): self._ordering = [] for i, form in enumerate(self.forms): # if this is an extra form and hasn't changed, don't consider it @@ -295,6 +317,7 @@ class BaseFormSet(RenderableFormMixin): if k[1] is None: return (1, 0) # +infinity, larger than any number return (0, k[1]) + self._ordering.sort(key=compare_ordering_key) # Return a list of form.cleaned_data dicts in the order specified by # the form data. @@ -302,7 +325,7 @@ class BaseFormSet(RenderableFormMixin): @classmethod def get_default_prefix(cls): - return 'form' + return "form" @classmethod def get_deletion_widget(cls): @@ -331,8 +354,9 @@ class BaseFormSet(RenderableFormMixin): def total_error_count(self): """Return the number of errors across all forms in the formset.""" - return len(self.non_form_errors()) +\ - sum(len(form_errors) for form_errors in self.errors) + return len(self.non_form_errors()) + sum( + len(form_errors) for form_errors in self.errors + ) def _should_delete_form(self, form): """Return whether or not the form was marked for deletion.""" @@ -346,10 +370,13 @@ class BaseFormSet(RenderableFormMixin): self.errors # List comprehension ensures is_valid() is called for all forms. # Forms due to be deleted shouldn't cause the formset to be invalid. - forms_valid = all([ - form.is_valid() for form in self.forms - if not (self.can_delete and self._should_delete_form(form)) - ]) + forms_valid = all( + [ + form.is_valid() + for form in self.forms + if not (self.can_delete and self._should_delete_form(form)) + ] + ) return forms_valid and not self.non_form_errors() def full_clean(self): @@ -358,7 +385,9 @@ class BaseFormSet(RenderableFormMixin): self._non_form_errors. """ self._errors = [] - self._non_form_errors = self.error_class(error_class='nonform', renderer=self.renderer) + self._non_form_errors = self.error_class( + error_class="nonform", renderer=self.renderer + ) empty_forms_count = 0 if not self.is_bound: # Stop further processing. @@ -366,14 +395,14 @@ class BaseFormSet(RenderableFormMixin): if not self.management_form.is_valid(): error = ValidationError( - self.error_messages['missing_management_form'], + self.error_messages["missing_management_form"], params={ - 'field_names': ', '.join( + "field_names": ", ".join( self.management_form.add_prefix(field_name) for field_name in self.management_form.errors ), }, - code='missing_management_form', + code="missing_management_form", ) self._non_form_errors.append(error) @@ -388,26 +417,43 @@ class BaseFormSet(RenderableFormMixin): continue self._errors.append(form_errors) try: - if (self.validate_max and - self.total_form_count() - len(self.deleted_forms) > self.max_num) or \ - self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max: - raise ValidationError(ngettext( - "Please submit at most %d form.", - "Please submit at most %d forms.", self.max_num) % self.max_num, - code='too_many_forms', + if ( + self.validate_max + and self.total_form_count() - len(self.deleted_forms) > self.max_num + ) or self.management_form.cleaned_data[ + TOTAL_FORM_COUNT + ] > self.absolute_max: + raise ValidationError( + ngettext( + "Please submit at most %d form.", + "Please submit at most %d forms.", + self.max_num, + ) + % self.max_num, + code="too_many_forms", + ) + if ( + self.validate_min + and self.total_form_count() + - len(self.deleted_forms) + - empty_forms_count + < self.min_num + ): + raise ValidationError( + ngettext( + "Please submit at least %d form.", + "Please submit at least %d forms.", + self.min_num, + ) + % self.min_num, + code="too_few_forms", ) - if (self.validate_min and - self.total_form_count() - len(self.deleted_forms) - empty_forms_count < self.min_num): - raise ValidationError(ngettext( - "Please submit at least %d form.", - "Please submit at least %d forms.", self.min_num) % self.min_num, - code='too_few_forms') # Give self.clean() a chance to do cross-form validation. self.clean() except ValidationError as e: self._non_form_errors = self.error_class( e.error_list, - error_class='nonform', + error_class="nonform", renderer=self.renderer, ) @@ -431,26 +477,26 @@ class BaseFormSet(RenderableFormMixin): # Only pre-fill the ordering field for initial forms. if index is not None and index < initial_form_count: form.fields[ORDERING_FIELD_NAME] = IntegerField( - label=_('Order'), + label=_("Order"), initial=index + 1, required=False, widget=self.get_ordering_widget(), ) else: form.fields[ORDERING_FIELD_NAME] = IntegerField( - label=_('Order'), + label=_("Order"), required=False, widget=self.get_ordering_widget(), ) if self.can_delete and (self.can_delete_extra or index < initial_form_count): form.fields[DELETION_FIELD_NAME] = BooleanField( - label=_('Delete'), + label=_("Delete"), required=False, widget=self.get_deletion_widget(), ) def add_prefix(self, index): - return '%s-%s' % (self.prefix, index) + return "%s-%s" % (self.prefix, index) def is_multipart(self): """ @@ -472,13 +518,23 @@ class BaseFormSet(RenderableFormMixin): return self.empty_form.media def get_context(self): - return {'formset': self} + return {"formset": self} -def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, - can_delete=False, max_num=None, validate_max=False, - min_num=None, validate_min=False, absolute_max=None, - can_delete_extra=True, renderer=None): +def formset_factory( + form, + formset=BaseFormSet, + extra=1, + can_order=False, + can_delete=False, + max_num=None, + validate_max=False, + min_num=None, + validate_min=False, + absolute_max=None, + can_delete_extra=True, + renderer=None, +): """Return a FormSet for the given form class.""" if min_num is None: min_num = DEFAULT_MIN_NUM @@ -490,23 +546,21 @@ def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, if absolute_max is None: absolute_max = max_num + DEFAULT_MAX_NUM if max_num > absolute_max: - raise ValueError( - "'absolute_max' must be greater or equal to 'max_num'." - ) + raise ValueError("'absolute_max' must be greater or equal to 'max_num'.") attrs = { - 'form': form, - 'extra': extra, - 'can_order': can_order, - 'can_delete': can_delete, - 'can_delete_extra': can_delete_extra, - 'min_num': min_num, - 'max_num': max_num, - 'absolute_max': absolute_max, - 'validate_min': validate_min, - 'validate_max': validate_max, - 'renderer': renderer or get_default_renderer(), + "form": form, + "extra": extra, + "can_order": can_order, + "can_delete": can_delete, + "can_delete_extra": can_delete_extra, + "min_num": min_num, + "max_num": max_num, + "absolute_max": absolute_max, + "validate_min": validate_min, + "validate_max": validate_max, + "renderer": renderer or get_default_renderer(), } - return type(form.__name__ + 'FormSet', (formset,), attrs) + return type(form.__name__ + "FormSet", (formset,), attrs) def all_valid(formsets): |
