summaryrefslogtreecommitdiff
path: root/django/forms/formsets.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/forms/formsets.py')
-rw-r--r--django/forms/formsets.py238
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):