class BaseFormSet
from django.forms import BaseFormSet
A collection of instances of the same Form class.
Ancestors (MRO)
- BaseFormSet
- RenderableFormMixin
- RenderableMixin
Descendants
Attributes
| Defined in | |
|---|---|
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.', 'too_many_forms': '', 'too_few_forms': ''}
|
BaseFormSet |
forms = <django.utils.functional.cached_property object at 0x10f3d2890>
|
BaseFormSet |
management_form = <django.utils.functional.cached_property object at 0x10f3d2820>
|
BaseFormSet |
template_name_div = 'django/forms/formsets/div.html'
|
BaseFormSet |
template_name_p = 'django/forms/formsets/p.html'
|
BaseFormSet |
template_name_table = 'django/forms/formsets/table.html'
|
BaseFormSet |
template_name_ul = 'django/forms/formsets/ul.html'
|
BaseFormSet |
Properties
def
cleaned_data():
¶
BaseFormSet
Getter
@property
def cleaned_data(self):
"""
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__
)
return [form.cleaned_data for form in self.forms]
def
deleted_forms():
¶
BaseFormSet
Getter
@property
def deleted_forms(self):
"""Return a list of forms that have been marked for deletion."""
if not self.is_valid() or not self.can_delete:
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"):
self._deleted_form_indexes = []
for i, form in enumerate(self.forms):
# If this is an extra form and hasn't changed, ignore it.
if i >= self.initial_form_count() and not form.has_changed():
continue
if self._should_delete_form(form):
self._deleted_form_indexes.append(i)
return [self.forms[i] for i in self._deleted_form_indexes]
def
empty_form():
¶
BaseFormSet
Getter
@property
def empty_form(self):
form_kwargs = {
**self.get_form_kwargs(None),
"auto_id": self.auto_id,
"prefix": self.add_prefix("__prefix__"),
"empty_permitted": True,
"use_required_attribute": False,
"renderer": self.form_renderer,
}
form = self.form(**form_kwargs)
self.add_fields(form, None)
return form
def
errors():
¶
BaseFormSet
Getter
@property
def errors(self):
"""Return a list of form.errors for every form in self.forms."""
if self._errors is None:
self.full_clean()
return self._errors
def
extra_forms():
¶
BaseFormSet
Getter
@property
def extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count() :]
def
initial_forms():
¶
BaseFormSet
Getter
@property
def initial_forms(self):
"""Return a list of all the initial forms in this formset."""
return self.forms[: self.initial_form_count()]
def
media():
¶
BaseFormSet
Getter
@property
def media(self):
# All the forms on a FormSet are the same, so you only need to
# interrogate the first form for media.
if self.forms:
return self.forms[0].media
else:
return self.empty_form.media
def
ordered_forms():
¶
BaseFormSet
Getter
@property
def ordered_forms(self):
"""
Return a list of form in the order specified by the incoming data.
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__
)
# 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"):
self._ordering = []
for i, form in enumerate(self.forms):
# If this is an extra form and hasn't changed, ignore it.
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 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.
# A sort function to order things numerically ascending, but
# None should be sorted below anything else. Allowing None as
# a comparison value makes it so we can leave ordering fields
# blank.
def compare_ordering_key(k):
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.
return [self.forms[i[0]] for i in self._ordering]
def
template_name():
¶
BaseFormSet
Getter
@property
def template_name(self):
return self.renderer.formset_template_name
Methods
def
_construct_form(self, i, **kwargs):
¶
BaseFormSet
Instantiate and return the i-th form instance in a formset.
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,
# 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.form_renderer,
}
if self.is_bound:
defaults["data"] = self.data
defaults["files"] = self.files
if self.initial and "initial" not in kwargs:
try:
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.update(kwargs)
form = self.form(**defaults)
self.add_fields(form, i)
return form
def
_should_delete_form(self, form):
¶
BaseFormSet
Return whether or not the form was marked for deletion.
def _should_delete_form(self, form):
"""Return whether or not the form was marked for deletion."""
return form.cleaned_data.get(DELETION_FIELD_NAME, False)
def
add_fields(self, form, index):
¶
BaseFormSet
A hook for adding extra fields on to each form instance.
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
initial_form_count = self.initial_form_count()
if self.can_order:
# 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"),
initial=index + 1,
required=False,
widget=self.get_ordering_widget(),
)
else:
form.fields[ORDERING_FIELD_NAME] = IntegerField(
label=_("Order"),
required=False,
widget=self.get_ordering_widget(),
)
if self.can_delete and (
self.can_delete_extra or (index is not None and index < initial_form_count)
):
form.fields[DELETION_FIELD_NAME] = BooleanField(
label=_("Delete"),
required=False,
widget=self.get_deletion_widget(),
)
def
add_prefix(self, index):
¶
BaseFormSet
def add_prefix(self, index):
return "%s-%s" % (self.prefix, index)
def
as_div(self):
¶
RenderableFormMixin
Render as <div> elements.
def as_div(self):
"""Render as <div> elements."""
return self.render(self.template_name_div)
def
as_p(self):
¶
RenderableFormMixin
Render as <p> elements.
def as_p(self):
"""Render as <p> elements."""
return self.render(self.template_name_p)
def
as_table(self):
¶
RenderableFormMixin
Render as <tr> elements excluding the surrounding <table> tag.
def as_table(self):
"""Render as <tr> elements excluding the surrounding <table> tag."""
return self.render(self.template_name_table)
def
as_ul(self):
¶
RenderableFormMixin
Render as <li> elements excluding the surrounding <ul> tag.
def as_ul(self):
"""Render as <li> elements excluding the surrounding <ul> tag."""
return self.render(self.template_name_ul)
def
clean(self):
¶
BaseFormSet
Hook for doing any extra formset-wide cleaning after Form.clean() has been called on every form. Any ValidationError raised by this method will not be associated with a particular form; it will be accessible via formset.non_form_errors()
def clean(self):
"""
Hook for doing any extra formset-wide cleaning after Form.clean() has
been called on every form. Any ValidationError raised by this method
will not be associated with a particular form; it will be accessible
via formset.non_form_errors()
"""
pass
def
full_clean(self):
¶
BaseFormSet
Clean all of self.data and populate self._errors and self._non_form_errors.
def full_clean(self):
"""
Clean all of self.data and populate self._errors and
self._non_form_errors.
"""
self._errors = []
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.
return
if not self.management_form.is_valid():
error = ValidationError(
self.error_messages["missing_management_form"],
params={
"field_names": ", ".join(
self.management_form.add_prefix(field_name)
for field_name in self.management_form.errors
),
},
code="missing_management_form",
)
self._non_form_errors.append(error)
for i, form in enumerate(self.forms):
# Empty forms are unchanged forms beyond those with initial data.
if not form.has_changed() and i >= self.initial_form_count():
empty_forms_count += 1
# Accessing errors calls full_clean() if necessary.
# _should_delete_form() requires cleaned_data.
form_errors = form.errors
if self.can_delete and self._should_delete_form(form):
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(
self.error_messages["too_many_forms"] % {"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(
self.error_messages["too_few_forms"] % {"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",
renderer=self.renderer,
)
def
get_context(self):
¶
BaseFormSet
def get_context(self):
return {"formset": self}
RenderableMixin
def get_context(self):
raise NotImplementedError(
"Subclasses of RenderableMixin must provide a get_context() method."
)
def
get_form_kwargs(self, index):
¶
BaseFormSet
Return additional keyword arguments for each individual formset form. index will be None if the form being constructed is a new empty form.
def get_form_kwargs(self, index):
"""
Return additional keyword arguments for each individual formset form.
index will be None if the form being constructed is a new empty
form.
"""
return self.form_kwargs.copy()
def
has_changed(self):
¶
BaseFormSet
Return True if data in any form differs from initial.
def has_changed(self):
"""Return True if data in any form differs from initial."""
return any(form.has_changed() for form in self)
def
initial_form_count(self):
¶
BaseFormSet
Return the number of forms that are required in this FormSet.
def initial_form_count(self):
"""Return the number of forms that are required in this FormSet."""
if self.is_bound:
return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
else:
# Use the length of the initial data if it's there, 0 otherwise.
initial_forms = len(self.initial) if self.initial else 0
return initial_forms
def
is_multipart(self):
¶
BaseFormSet
Return True if the formset needs to be multipart, i.e. it has FileInput, or False otherwise.
def is_multipart(self):
"""
Return True if the formset needs to be multipart, i.e. it
has FileInput, or False otherwise.
"""
if self.forms:
return self.forms[0].is_multipart()
else:
return self.empty_form.is_multipart()
def
is_valid(self):
¶
BaseFormSet
Return True if every form in self.forms is valid.
def is_valid(self):
"""Return True if every form in self.forms is valid."""
if not self.is_bound:
return False
# Accessing errors triggers a full clean the first time only.
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))
]
)
return forms_valid and not self.non_form_errors()
def
non_form_errors(self):
¶
BaseFormSet
Return an ErrorList of errors that aren't associated with a particular form -- i.e., from formset.clean(). Return an empty ErrorList if there are none.
def non_form_errors(self):
"""
Return an ErrorList of errors that aren't associated with a particular
form -- i.e., from formset.clean(). Return an empty ErrorList if there
are none.
"""
if self._non_form_errors is None:
self.full_clean()
return self._non_form_errors
def
render(self, template_name=None, context=None, renderer=None):
¶
RenderableMixin
def render(self, template_name=None, context=None, renderer=None):
renderer = renderer or self.renderer
template = template_name or self.template_name
context = context or self.get_context()
return mark_safe(renderer.render(template, context))
def
total_error_count(self):
¶
BaseFormSet
Return the number of errors across all forms in the formset.
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
)
def
total_form_count(self):
¶
BaseFormSet
Return the total number of forms in this FormSet.
def total_form_count(self):
"""Return the total number of forms in this FormSet."""
if self.is_bound:
# return absolute_max if it is lower than the actual total form
# 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
)
else:
initial_forms = self.initial_form_count()
total_forms = max(initial_forms, self.min_num) + 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