class BaseFormSet
from django.forms import BaseFormSet
A collection of instances of the same Form class.
Ancestors (MRO)
- BaseFormSet
Descendants
Attributes
Defined in | |
---|---|
forms = <django.utils.functional.cached_property object at 0x1105b6ed0>
|
BaseFormSet |
management_form = <django.utils.functional.cached_property object at 0x1105b6e90>
|
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 in range(0, self.total_form_count()):
form = self.forms[i]
# 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 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 = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
use_required_attribute=False,
**self.get_form_kwargs(None)
)
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 in range(0, self.total_form_count()):
form = self.forms[i]
# 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
# 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]
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,
}
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."""
if self.can_order:
# Only pre-fill the ordering field for initial forms.
if index is not None and index < self.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:
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
def
add_prefix(self, index):
¶
BaseFormSet
def add_prefix(self, index):
return '%s-%s' % (self.prefix, index)
def
as_p(self):
¶
BaseFormSet
Return this formset rendered as HTML <p>s.
def as_p(self):
"Return this formset rendered as HTML <p>s."
forms = ' '.join(form.as_p() for form in self)
return mark_safe(str(self.management_form) + '\n' + forms)
def
as_table(self):
¶
BaseFormSet
Return this formset rendered as HTML <tr>s -- excluding the <table></table>.
def as_table(self):
"Return this formset rendered as HTML <tr>s -- excluding the <table></table>."
# XXX: there is no semantic division between forms here, there
# probably should be. It might make sense to render each form as a
# table row with each field as a td.
forms = ' '.join(form.as_table() for form in self)
return mark_safe(str(self.management_form) + '\n' + forms)
def
as_ul(self):
¶
BaseFormSet
Return this formset rendered as HTML <li>s.
def as_ul(self):
"Return this formset rendered as HTML <li>s."
forms = ' '.join(form.as_ul() for form in self)
return mark_safe(str(self.management_form) + '\n' + forms)
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()
empty_forms_count = 0
if not self.is_bound: # Stop further processing.
return
for i in range(0, self.total_form_count()):
form = self.forms[i]
# 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(ngettext(
"Please submit %d or fewer forms.",
"Please submit %d or fewer 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 %d or more forms.",
"Please submit %d or more 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)
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
# We loop over every form.errors here rather than short circuiting on the
# first failure to make sure validation gets triggered for every form.
forms_valid = True
# This triggers a full clean.
self.errors
for i in range(0, self.total_form_count()):
form = self.forms[i]
if self.can_delete and self._should_delete_form(form):
# This form is going to be deleted so any of its errors
# shouldn't cause the entire formset to be invalid.
continue
forms_valid &= form.is_valid()
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
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