start-pack
This commit is contained in:
commit
3e1fa59b3d
5723 changed files with 757971 additions and 0 deletions
584
myenv/lib/python3.12/site-packages/django/forms/formsets.py
Normal file
584
myenv/lib/python3.12/site-packages/django/forms/formsets.py
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.forms.fields import BooleanField, IntegerField
|
||||
from django.forms.forms import Form
|
||||
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 _
|
||||
from django.utils.translation import ngettext_lazy
|
||||
|
||||
__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"
|
||||
|
||||
# default minimum number of forms in a formset
|
||||
DEFAULT_MIN_NUM = 0
|
||||
|
||||
# default maximum number of forms in a formset, to prevent memory exhaustion
|
||||
DEFAULT_MAX_NUM = 1000
|
||||
|
||||
|
||||
class ManagementForm(Form):
|
||||
"""
|
||||
Keep track of how many form instances are displayed on the page. If adding
|
||||
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
|
||||
# management form, but only for the convenience of client-side code. The
|
||||
# POST value of them returned from the client is not checked.
|
||||
MIN_NUM_FORMS = IntegerField(required=False, widget=HiddenInput)
|
||||
MAX_NUM_FORMS = IntegerField(required=False, widget=HiddenInput)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
# When the management form is invalid, we don't know how many forms
|
||||
# were submitted.
|
||||
cleaned_data.setdefault(TOTAL_FORM_COUNT, 0)
|
||||
cleaned_data.setdefault(INITIAL_FORM_COUNT, 0)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
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."
|
||||
),
|
||||
"too_many_forms": ngettext_lazy(
|
||||
"Please submit at most %(num)d form.",
|
||||
"Please submit at most %(num)d forms.",
|
||||
"num",
|
||||
),
|
||||
"too_few_forms": ngettext_lazy(
|
||||
"Please submit at least %(num)d form.",
|
||||
"Please submit at least %(num)d forms.",
|
||||
"num",
|
||||
),
|
||||
}
|
||||
|
||||
template_name_div = "django/forms/formsets/div.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,
|
||||
):
|
||||
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
|
||||
self.data = data or {}
|
||||
self.files = files or {}
|
||||
self.initial = initial
|
||||
self.form_kwargs = form_kwargs or {}
|
||||
self.error_class = error_class
|
||||
self._errors = None
|
||||
self._non_form_errors = None
|
||||
self.form_renderer = self.renderer
|
||||
self.renderer = self.renderer or get_default_renderer()
|
||||
|
||||
messages = {}
|
||||
for cls in reversed(type(self).__mro__):
|
||||
messages.update(getattr(cls, "default_error_messages", {}))
|
||||
if error_messages is not None:
|
||||
messages.update(error_messages)
|
||||
self.error_messages = messages
|
||||
|
||||
def __iter__(self):
|
||||
"""Yield the forms in the order they should be rendered."""
|
||||
return iter(self.forms)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return the form at the given index, based on the rendering order."""
|
||||
return self.forms[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.forms)
|
||||
|
||||
def __bool__(self):
|
||||
"""
|
||||
Return True since all formsets have a management form which is not
|
||||
included in the length.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
if self._errors is None:
|
||||
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)
|
||||
)
|
||||
return "<%s: bound=%s valid=%s total_forms=%s>" % (
|
||||
self.__class__.__qualname__,
|
||||
self.is_bound,
|
||||
is_valid,
|
||||
self.total_form_count(),
|
||||
)
|
||||
|
||||
@cached_property
|
||||
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.full_clean()
|
||||
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(),
|
||||
MIN_NUM_FORM_COUNT: self.min_num,
|
||||
MAX_NUM_FORM_COUNT: self.max_num,
|
||||
},
|
||||
renderer=self.renderer,
|
||||
)
|
||||
return form
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@cached_property
|
||||
def forms(self):
|
||||
"""Instantiate forms at first property access."""
|
||||
# DoS protection is included in total_form_count()
|
||||
return [
|
||||
self._construct_form(i, **self.get_form_kwargs(i))
|
||||
for i in range(self.total_form_count())
|
||||
]
|
||||
|
||||
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 _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
|
||||
|
||||
@property
|
||||
def initial_forms(self):
|
||||
"""Return a list of all the initial forms in this formset."""
|
||||
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() :]
|
||||
|
||||
@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
|
||||
|
||||
@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]
|
||||
|
||||
@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, 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]
|
||||
|
||||
@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, 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]
|
||||
|
||||
@classmethod
|
||||
def get_default_prefix(cls):
|
||||
return "form"
|
||||
|
||||
@classmethod
|
||||
def get_deletion_widget(cls):
|
||||
return cls.deletion_widget
|
||||
|
||||
@classmethod
|
||||
def get_ordering_widget(cls):
|
||||
return cls.ordering_widget
|
||||
|
||||
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
|
||||
|
||||
@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 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 _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 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 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 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 has_changed(self):
|
||||
"""Return True if data in any form differs from initial."""
|
||||
return any(form.has_changed() for form in self)
|
||||
|
||||
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):
|
||||
return "%s-%s" % (self.prefix, index)
|
||||
|
||||
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()
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def template_name(self):
|
||||
return self.renderer.formset_template_name
|
||||
|
||||
def get_context(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,
|
||||
):
|
||||
"""Return a FormSet for the given form class."""
|
||||
if min_num is None:
|
||||
min_num = DEFAULT_MIN_NUM
|
||||
if max_num is None:
|
||||
max_num = DEFAULT_MAX_NUM
|
||||
# absolute_max is a hard limit on forms instantiated, to prevent
|
||||
# memory-exhaustion attacks. Default to max_num + DEFAULT_MAX_NUM
|
||||
# (which is 2 * DEFAULT_MAX_NUM if max_num is None in the first place).
|
||||
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'.")
|
||||
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,
|
||||
}
|
||||
form_name = form.__name__
|
||||
if form_name.endswith("Form"):
|
||||
formset_name = form_name + "Set"
|
||||
else:
|
||||
formset_name = form_name + "FormSet"
|
||||
return type(formset_name, (formset,), attrs)
|
||||
|
||||
|
||||
def all_valid(formsets):
|
||||
"""Validate every formset and return True if all are valid."""
|
||||
# List comprehension ensures is_valid() is called for all formsets.
|
||||
return all([formset.is_valid() for formset in formsets])
|
||||
Loading…
Add table
Add a link
Reference in a new issue