docker setup

This commit is contained in:
AdrienLSH
2023-11-23 16:43:30 +01:00
parent fd19180e1d
commit f29003c66a
5410 changed files with 869440 additions and 0 deletions

View File

@ -0,0 +1,11 @@
"""
Django validation and HTML form handling.
"""
from django.core.exceptions import ValidationError # NOQA
from django.forms.boundfield import * # NOQA
from django.forms.fields import * # NOQA
from django.forms.forms import * # NOQA
from django.forms.formsets import * # NOQA
from django.forms.models import * # NOQA
from django.forms.widgets import * # NOQA

View File

@ -0,0 +1,344 @@
import re
from django.core.exceptions import ValidationError
from django.forms.utils import pretty_name
from django.forms.widgets import MultiWidget, Textarea, TextInput
from django.utils.functional import cached_property
from django.utils.html import format_html, html_safe
from django.utils.translation import gettext_lazy as _
__all__ = ("BoundField",)
@html_safe
class BoundField:
"A Field plus data"
def __init__(self, form, field, name):
self.form = form
self.field = field
self.name = name
self.html_name = form.add_prefix(name)
self.html_initial_name = form.add_initial_prefix(name)
self.html_initial_id = form.add_initial_prefix(self.auto_id)
if self.field.label is None:
self.label = pretty_name(name)
else:
self.label = self.field.label
self.help_text = field.help_text or ""
def __str__(self):
"""Render this field as an HTML widget."""
if self.field.show_hidden_initial:
return self.as_widget() + self.as_hidden(only_initial=True)
return self.as_widget()
@cached_property
def subwidgets(self):
"""
Most widgets yield a single subwidget, but others like RadioSelect and
CheckboxSelectMultiple produce one subwidget for each choice.
This property is cached so that only one database query occurs when
rendering ModelChoiceFields.
"""
id_ = self.field.widget.attrs.get("id") or self.auto_id
attrs = {"id": id_} if id_ else {}
attrs = self.build_widget_attrs(attrs)
return [
BoundWidget(self.field.widget, widget, self.form.renderer)
for widget in self.field.widget.subwidgets(
self.html_name, self.value(), attrs=attrs
)
]
def __bool__(self):
# BoundField evaluates to True even if it doesn't have subwidgets.
return True
def __iter__(self):
return iter(self.subwidgets)
def __len__(self):
return len(self.subwidgets)
def __getitem__(self, idx):
# Prevent unnecessary reevaluation when accessing BoundField's attrs
# from templates.
if not isinstance(idx, (int, slice)):
raise TypeError(
"BoundField indices must be integers or slices, not %s."
% type(idx).__name__
)
return self.subwidgets[idx]
@property
def errors(self):
"""
Return an ErrorList (empty if there are no errors) for this field.
"""
return self.form.errors.get(
self.name, self.form.error_class(renderer=self.form.renderer)
)
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
Render the field by rendering the passed widget, adding any HTML
attributes passed as attrs. If a widget isn't specified, use the
field's default widget.
"""
widget = widget or self.field.widget
if self.field.localize:
widget.is_localized = True
attrs = attrs or {}
attrs = self.build_widget_attrs(attrs, widget)
if self.auto_id and "id" not in widget.attrs:
attrs.setdefault(
"id", self.html_initial_id if only_initial else self.auto_id
)
if only_initial and self.html_initial_name in self.form.data:
# Propagate the hidden initial value.
value = self.form._widget_data_value(
self.field.hidden_widget(),
self.html_initial_name,
)
else:
value = self.value()
return widget.render(
name=self.html_initial_name if only_initial else self.html_name,
value=value,
attrs=attrs,
renderer=self.form.renderer,
)
def as_text(self, attrs=None, **kwargs):
"""
Return a string of HTML for representing this as an <input type="text">.
"""
return self.as_widget(TextInput(), attrs, **kwargs)
def as_textarea(self, attrs=None, **kwargs):
"""Return a string of HTML for representing this as a <textarea>."""
return self.as_widget(Textarea(), attrs, **kwargs)
def as_hidden(self, attrs=None, **kwargs):
"""
Return a string of HTML for representing this as an <input type="hidden">.
"""
return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
@property
def data(self):
"""
Return the data for this BoundField, or None if it wasn't given.
"""
return self.form._widget_data_value(self.field.widget, self.html_name)
def value(self):
"""
Return the value for this BoundField, using the initial value if
the form is not bound or the data otherwise.
"""
data = self.initial
if self.form.is_bound:
data = self.field.bound_data(self.data, data)
return self.field.prepare_value(data)
def _has_changed(self):
field = self.field
if field.show_hidden_initial:
hidden_widget = field.hidden_widget()
initial_value = self.form._widget_data_value(
hidden_widget,
self.html_initial_name,
)
try:
initial_value = field.to_python(initial_value)
except ValidationError:
# Always assume data has changed if validation fails.
return True
else:
initial_value = self.initial
return field.has_changed(initial_value, self.data)
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
"""
Wrap the given contents in a <label>, if the field has an ID attribute.
contents should be mark_safe'd to avoid HTML escaping. If contents
aren't given, use the field's HTML-escaped label.
If attrs are given, use them as HTML attributes on the <label> tag.
label_suffix overrides the form's label_suffix.
"""
contents = contents or self.label
if label_suffix is None:
label_suffix = (
self.field.label_suffix
if self.field.label_suffix is not None
else self.form.label_suffix
)
# Only add the suffix if the label does not end in punctuation.
# Translators: If found as last label character, these punctuation
# characters will prevent the default label_suffix to be appended to the label
if label_suffix and contents and contents[-1] not in _(":?.!"):
contents = format_html("{}{}", contents, label_suffix)
widget = self.field.widget
id_ = widget.attrs.get("id") or self.auto_id
if id_:
id_for_label = widget.id_for_label(id_)
if id_for_label:
attrs = {**(attrs or {}), "for": id_for_label}
if self.field.required and hasattr(self.form, "required_css_class"):
attrs = attrs or {}
if "class" in attrs:
attrs["class"] += " " + self.form.required_css_class
else:
attrs["class"] = self.form.required_css_class
context = {
"field": self,
"label": contents,
"attrs": attrs,
"use_tag": bool(id_),
"tag": tag or "label",
}
return self.form.render(self.form.template_name_label, context)
def legend_tag(self, contents=None, attrs=None, label_suffix=None):
"""
Wrap the given contents in a <legend>, if the field has an ID
attribute. Contents should be mark_safe'd to avoid HTML escaping. If
contents aren't given, use the field's HTML-escaped label.
If attrs are given, use them as HTML attributes on the <legend> tag.
label_suffix overrides the form's label_suffix.
"""
return self.label_tag(contents, attrs, label_suffix, tag="legend")
def css_classes(self, extra_classes=None):
"""
Return a string of space-separated CSS classes for this field.
"""
if hasattr(extra_classes, "split"):
extra_classes = extra_classes.split()
extra_classes = set(extra_classes or [])
if self.errors and hasattr(self.form, "error_css_class"):
extra_classes.add(self.form.error_css_class)
if self.field.required and hasattr(self.form, "required_css_class"):
extra_classes.add(self.form.required_css_class)
return " ".join(extra_classes)
@property
def is_hidden(self):
"""Return True if this BoundField's widget is hidden."""
return self.field.widget.is_hidden
@property
def auto_id(self):
"""
Calculate and return the ID attribute for this BoundField, if the
associated Form has specified auto_id. Return an empty string otherwise.
"""
auto_id = self.form.auto_id # Boolean or string
if auto_id and "%s" in str(auto_id):
return auto_id % self.html_name
elif auto_id:
return self.html_name
return ""
@property
def id_for_label(self):
"""
Wrapper around the field widget's `id_for_label` method.
Useful, for example, for focusing on this field regardless of whether
it has a single widget or a MultiWidget.
"""
widget = self.field.widget
id_ = widget.attrs.get("id") or self.auto_id
return widget.id_for_label(id_)
@cached_property
def initial(self):
return self.form.get_initial_for_field(self.field, self.name)
def build_widget_attrs(self, attrs, widget=None):
widget = widget or self.field.widget
attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
if (
widget.use_required_attribute(self.initial)
and self.field.required
and self.form.use_required_attribute
):
# MultiValueField has require_all_fields: if False, fall back
# on subfields.
if (
hasattr(self.field, "require_all_fields")
and not self.field.require_all_fields
and isinstance(self.field.widget, MultiWidget)
):
for subfield, subwidget in zip(self.field.fields, widget.widgets):
subwidget.attrs["required"] = (
subwidget.use_required_attribute(self.initial)
and subfield.required
)
else:
attrs["required"] = True
if self.field.disabled:
attrs["disabled"] = True
return attrs
@property
def widget_type(self):
return re.sub(
r"widget$|input$", "", self.field.widget.__class__.__name__.lower()
)
@property
def use_fieldset(self):
"""
Return the value of this BoundField widget's use_fieldset attribute.
"""
return self.field.widget.use_fieldset
@html_safe
class BoundWidget:
"""
A container class used for iterating over widgets. This is useful for
widgets that have choices. For example, the following can be used in a
template:
{% for radio in myform.beatles %}
<label for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
<span class="radio">{{ radio.tag }}</span>
</label>
{% endfor %}
"""
def __init__(self, parent_widget, data, renderer):
self.parent_widget = parent_widget
self.data = data
self.renderer = renderer
def __str__(self):
return self.tag(wrap_label=True)
def tag(self, wrap_label=False):
context = {"widget": {**self.data, "wrap_label": wrap_label}}
return self.parent_widget._render(self.template_name, context, self.renderer)
@property
def template_name(self):
if "template_name" in self.data:
return self.data["template_name"]
return self.parent_widget.template_name
@property
def id_for_label(self):
return self.data["attrs"].get("id")
@property
def choice_label(self):
return self.data["label"]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,539 @@
"""
Form classes
"""
import copy
import datetime
import warnings
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.forms.fields import Field, FileField
from django.forms.utils import ErrorDict, ErrorList, RenderableFormMixin
from django.forms.widgets import Media, MediaDefiningClass
from django.utils.datastructures import MultiValueDict
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property
from django.utils.html import conditional_escape
from django.utils.safestring import SafeString, mark_safe
from django.utils.translation import gettext as _
from .renderers import get_default_renderer
__all__ = ("BaseForm", "Form")
class DeclarativeFieldsMetaclass(MediaDefiningClass):
"""Collect Fields declared on the base classes."""
def __new__(mcs, name, bases, attrs):
# Collect fields from current class and remove them from attrs.
attrs["declared_fields"] = {
key: attrs.pop(key)
for key, value in list(attrs.items())
if isinstance(value, Field)
}
new_class = super().__new__(mcs, name, bases, attrs)
# Walk through the MRO.
declared_fields = {}
for base in reversed(new_class.__mro__):
# Collect fields from base class.
if hasattr(base, "declared_fields"):
declared_fields.update(base.declared_fields)
# Field shadowing.
for attr, value in base.__dict__.items():
if value is None and attr in declared_fields:
declared_fields.pop(attr)
new_class.base_fields = declared_fields
new_class.declared_fields = declared_fields
return new_class
class BaseForm(RenderableFormMixin):
"""
The main implementation of all the Form logic. Note that this class is
different than Form. See the comments by the Form class for more info. Any
improvements to the form API should be made to this class, not to the Form
class.
"""
default_renderer = None
field_order = None
prefix = None
use_required_attribute = True
template_name_div = "django/forms/div.html"
template_name_p = "django/forms/p.html"
template_name_table = "django/forms/table.html"
template_name_ul = "django/forms/ul.html"
template_name_label = "django/forms/label.html"
def __init__(
self,
data=None,
files=None,
auto_id="id_%s",
prefix=None,
initial=None,
error_class=ErrorList,
label_suffix=None,
empty_permitted=False,
field_order=None,
use_required_attribute=None,
renderer=None,
):
self.is_bound = data is not None or files is not None
self.data = MultiValueDict() if data is None else data
self.files = MultiValueDict() if files is None else files
self.auto_id = auto_id
if prefix is not None:
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
# Translators: This is the default suffix added to form field labels
self.label_suffix = label_suffix if label_suffix is not None else _(":")
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
self._bound_fields_cache = {}
self.order_fields(self.field_order if field_order is None else field_order)
if use_required_attribute is not None:
self.use_required_attribute = use_required_attribute
if self.empty_permitted and self.use_required_attribute:
raise ValueError(
"The empty_permitted and use_required_attribute arguments may "
"not both be True."
)
# Initialize form renderer. Use a global default if not specified
# either as an argument or as self.default_renderer.
if renderer is None:
if self.default_renderer is None:
renderer = get_default_renderer()
else:
renderer = self.default_renderer
if isinstance(self.default_renderer, type):
renderer = renderer()
self.renderer = renderer
def order_fields(self, field_order):
"""
Rearrange the fields according to field_order.
field_order is a list of field names specifying the order. Append fields
not included in the list in the default order for backward compatibility
with subclasses not overriding field_order. If field_order is None,
keep all fields in the order defined in the class. Ignore unknown
fields in field_order to allow disabling fields in form subclasses
without redefining ordering.
"""
if field_order is None:
return
fields = {}
for key in field_order:
try:
fields[key] = self.fields.pop(key)
except KeyError: # ignore unknown fields
pass
fields.update(self.fields) # add remaining fields in original order
self.fields = fields
def __repr__(self):
if self._errors is None:
is_valid = "Unknown"
else:
is_valid = self.is_bound and not self._errors
return "<%(cls)s bound=%(bound)s, valid=%(valid)s, fields=(%(fields)s)>" % {
"cls": self.__class__.__name__,
"bound": self.is_bound,
"valid": is_valid,
"fields": ";".join(self.fields),
}
def _bound_items(self):
"""Yield (name, bf) pairs, where bf is a BoundField object."""
for name in self.fields:
yield name, self[name]
def __iter__(self):
"""Yield the form's fields as BoundField objects."""
for name in self.fields:
yield self[name]
def __getitem__(self, name):
"""Return a BoundField with the given name."""
try:
field = self.fields[name]
except KeyError:
raise KeyError(
"Key '%s' not found in '%s'. Choices are: %s."
% (
name,
self.__class__.__name__,
", ".join(sorted(self.fields)),
)
)
if name not in self._bound_fields_cache:
self._bound_fields_cache[name] = field.get_bound_field(self, name)
return self._bound_fields_cache[name]
@property
def errors(self):
"""Return an ErrorDict for the data provided for the form."""
if self._errors is None:
self.full_clean()
return self._errors
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
def add_prefix(self, field_name):
"""
Return the field name with a prefix appended, if this Form has a
prefix set.
Subclasses may wish to override.
"""
return "%s-%s" % (self.prefix, field_name) if self.prefix else field_name
def add_initial_prefix(self, field_name):
"""Add an 'initial' prefix for checking dynamic initial values."""
return "initial-%s" % self.add_prefix(field_name)
def _widget_data_value(self, widget, html_name):
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
return widget.value_from_datadict(self.data, self.files, html_name)
def _html_output(
self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row
):
"Output HTML. Used by as_table(), as_ul(), as_p()."
warnings.warn(
"django.forms.BaseForm._html_output() is deprecated. "
"Please use .render() and .get_context() instead.",
RemovedInDjango50Warning,
stacklevel=2,
)
# Errors that should be displayed above all fields.
top_errors = self.non_field_errors().copy()
output, hidden_fields = [], []
for name, bf in self._bound_items():
field = bf.field
html_class_attr = ""
bf_errors = self.error_class(bf.errors)
if bf.is_hidden:
if bf_errors:
top_errors.extend(
[
_("(Hidden field %(name)s) %(error)s")
% {"name": name, "error": str(e)}
for e in bf_errors
]
)
hidden_fields.append(str(bf))
else:
# Create a 'class="..."' attribute if the row should have any
# CSS classes applied.
css_classes = bf.css_classes()
if css_classes:
html_class_attr = ' class="%s"' % css_classes
if errors_on_separate_row and bf_errors:
output.append(error_row % str(bf_errors))
if bf.label:
label = conditional_escape(bf.label)
label = bf.label_tag(label) or ""
else:
label = ""
if field.help_text:
help_text = help_text_html % field.help_text
else:
help_text = ""
output.append(
normal_row
% {
"errors": bf_errors,
"label": label,
"field": bf,
"help_text": help_text,
"html_class_attr": html_class_attr,
"css_classes": css_classes,
"field_name": bf.html_name,
}
)
if top_errors:
output.insert(0, error_row % top_errors)
if hidden_fields: # Insert any hidden fields in the last row.
str_hidden = "".join(hidden_fields)
if output:
last_row = output[-1]
# Chop off the trailing row_ender (e.g. '</td></tr>') and
# insert the hidden fields.
if not last_row.endswith(row_ender):
# This can happen in the as_p() case (and possibly others
# that users write): if there are only top errors, we may
# not be able to conscript the last row for our purposes,
# so insert a new, empty row.
last_row = normal_row % {
"errors": "",
"label": "",
"field": "",
"help_text": "",
"html_class_attr": html_class_attr,
"css_classes": "",
"field_name": "",
}
output.append(last_row)
output[-1] = last_row[: -len(row_ender)] + str_hidden + row_ender
else:
# If there aren't any rows in the output, just append the
# hidden fields.
output.append(str_hidden)
return mark_safe("\n".join(output))
@property
def template_name(self):
return self.renderer.form_template_name
def get_context(self):
fields = []
hidden_fields = []
top_errors = self.non_field_errors().copy()
for name, bf in self._bound_items():
bf_errors = self.error_class(bf.errors, renderer=self.renderer)
if bf.is_hidden:
if bf_errors:
top_errors += [
_("(Hidden field %(name)s) %(error)s")
% {"name": name, "error": str(e)}
for e in bf_errors
]
hidden_fields.append(bf)
else:
errors_str = str(bf_errors)
# RemovedInDjango50Warning.
if not isinstance(errors_str, SafeString):
warnings.warn(
f"Returning a plain string from "
f"{self.error_class.__name__} is deprecated. Please "
f"customize via the template system instead.",
RemovedInDjango50Warning,
)
errors_str = mark_safe(errors_str)
fields.append((bf, errors_str))
return {
"form": self,
"fields": fields,
"hidden_fields": hidden_fields,
"errors": top_errors,
}
def non_field_errors(self):
"""
Return an ErrorList of errors that aren't associated with a particular
field -- i.e., from Form.clean(). Return an empty ErrorList if there
are none.
"""
return self.errors.get(
NON_FIELD_ERRORS,
self.error_class(error_class="nonfield", renderer=self.renderer),
)
def add_error(self, field, error):
"""
Update the content of `self._errors`.
The `field` argument is the name of the field to which the errors
should be added. If it's None, treat the errors as NON_FIELD_ERRORS.
The `error` argument can be a single error, a list of errors, or a
dictionary that maps field names to lists of errors. An "error" can be
either a simple string or an instance of ValidationError with its
message attribute set and a "list or dictionary" can be an actual
`list` or `dict` or an instance of ValidationError with its
`error_list` or `error_dict` attribute set.
If `error` is a dictionary, the `field` argument *must* be None and
errors will be added to the fields that correspond to the keys of the
dictionary.
"""
if not isinstance(error, ValidationError):
# Normalize to ValidationError and let its constructor
# do the hard work of making sense of the input.
error = ValidationError(error)
if hasattr(error, "error_dict"):
if field is not None:
raise TypeError(
"The argument `field` must be `None` when the `error` "
"argument contains errors for multiple fields."
)
else:
error = error.error_dict
else:
error = {field or NON_FIELD_ERRORS: error.error_list}
for field, error_list in error.items():
if field not in self.errors:
if field != NON_FIELD_ERRORS and field not in self.fields:
raise ValueError(
"'%s' has no field named '%s'."
% (self.__class__.__name__, field)
)
if field == NON_FIELD_ERRORS:
self._errors[field] = self.error_class(
error_class="nonfield", renderer=self.renderer
)
else:
self._errors[field] = self.error_class(renderer=self.renderer)
self._errors[field].extend(error_list)
if field in self.cleaned_data:
del self.cleaned_data[field]
def has_error(self, field, code=None):
return field in self.errors and (
code is None
or any(error.code == code for error in self.errors.as_data()[field])
)
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
def _clean_fields(self):
for name, bf in self._bound_items():
field = bf.field
value = bf.initial if field.disabled else bf.data
try:
if isinstance(field, FileField):
value = field.clean(value, bf.initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, "clean_%s" % name):
value = getattr(self, "clean_%s" % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
def _post_clean(self):
"""
An internal hook for performing additional cleaning after form cleaning
is complete. Used for model validation in model forms.
"""
pass
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() has been
called on every field. Any ValidationError raised by this method will
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
"""
return self.cleaned_data
def has_changed(self):
"""Return True if data differs from initial."""
return bool(self.changed_data)
@cached_property
def changed_data(self):
return [name for name, bf in self._bound_items() if bf._has_changed()]
@property
def media(self):
"""Return all media required to render the widgets on this form."""
media = Media()
for field in self.fields.values():
media += field.widget.media
return media
def is_multipart(self):
"""
Return True if the form needs to be multipart-encoded, i.e. it has
FileInput, or False otherwise.
"""
return any(field.widget.needs_multipart_form for field in self.fields.values())
def hidden_fields(self):
"""
Return a list of all the BoundField objects that are hidden fields.
Useful for manual form layout in templates.
"""
return [field for field in self if field.is_hidden]
def visible_fields(self):
"""
Return a list of BoundField objects that aren't hidden fields.
The opposite of the hidden_fields() method.
"""
return [field for field in self if not field.is_hidden]
def get_initial_for_field(self, field, field_name):
"""
Return initial data for field on form. Use initial data from the form
or the field, in that order. Evaluate callable values.
"""
value = self.initial.get(field_name, field.initial)
if callable(value):
value = value()
# If this is an auto-generated default date, nix the microseconds
# for standardized handling. See #22502.
if (
isinstance(value, (datetime.datetime, datetime.time))
and not field.widget.supports_microseconds
):
value = value.replace(microsecond=0)
return value
class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
"A collection of Fields, plus their associated data."
# This is a separate class from BaseForm in order to abstract the way
# self.fields is specified. This class (Form) is the one that does the
# fancy metaclass stuff purely for the semantic sugar -- it allows one
# to define a form using declarative syntax.
# BaseForm itself has no way of designating self.fields.

View File

@ -0,0 +1,579 @@
from django.core.exceptions import ValidationError
from django.forms import Form
from django.forms.fields import BooleanField, IntegerField
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.
"""
template_name = "django/forms/div.html" # RemovedInDjango50Warning.
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
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.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.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 or get_default_renderer(),
}
return type(form.__name__ + "FormSet", (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])

View File

@ -0,0 +1 @@
{% for name, value in attrs.items() %}{% if value is not sameas False %} {{ name }}{% if value is not sameas True %}="{{ value }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1 @@
{% include "django/forms/table.html" %}

View File

@ -0,0 +1,24 @@
{{ errors }}
{% if errors and not fields %}
<div>{% for field in hidden_fields %}{{ field }}{% endfor %}</div>
{% endif %}
{% for field, errors in fields %}
<div{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
{% if field.use_fieldset %}
<fieldset>
{% if field.label %}{{ field.legend_tag() }}{% endif %}
{% else %}
{% if field.label %}{{ field.label_tag() }}{% endif %}
{% endif %}
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
{{ errors }}
{{ field }}
{% if field.use_fieldset %}</fieldset>{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</div>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/errors/dict/ul.html" %}

View File

@ -0,0 +1,3 @@
{% for field, errors in errors %}* {{ field }}
{% for error in errors %} * {{ error }}
{% endfor %}{% endfor %}

View File

@ -0,0 +1 @@
{% if errors %}<ul class="{{ error_class }}">{% for field, error in errors %}<li>{{ field }}{{ error }}</li>{% endfor %}</ul>{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/errors/list/ul.html" %}

View File

@ -0,0 +1,2 @@
{% for error in errors %}* {{ error }}
{% endfor %}

View File

@ -0,0 +1 @@
{% if errors %}<ul class="{{ error_class }}">{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_div() }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_p() }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_table() }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_ul() }}{% endfor %}

View File

@ -0,0 +1 @@
{% if use_tag %}<{{ tag }}{% if attrs %}{% include 'django/forms/attrs.html' %}{% endif %}>{{ label }}</{{ tag }}>{% else %}{{ label }}{% endif %}

View File

@ -0,0 +1,20 @@
{{ errors }}
{% if errors and not fields %}
<p>{% for field in hidden_fields %}{{ field }}{% endfor %}</p>
{% endif %}
{% for field, errors in fields %}
{{ errors }}
<p{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
{% if field.label %}{{ field.label_tag() }}{% endif %}
{{ field }}
{% if field.help_text %}
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</p>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1,29 @@
{% if errors %}
<tr>
<td colspan="2">
{{ errors }}
{% if not fields %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</td>
</tr>
{% endif %}
{% for field, errors in fields %}
<tr{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
<th>{% if field.label %}{{ field.label_tag() }}{% endif %}</th>
<td>
{{ errors }}
{{ field }}
{% if field.help_text %}
<br>
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</td>
</tr>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1,24 @@
{% if errors %}
<li>
{{ errors }}
{% if not fields %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</li>
{% endif %}
{% for field, errors in fields %}
<li{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}>
{{ errors }}
{% if field.label %}{{ field.label_tag() }}{% endif %}
{{ field }}
{% if field.help_text %}
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if loop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</li>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1 @@
{% for name, value in widget.attrs.items() %}{% if value is not sameas False %} {{ name }}{% if value is not sameas True %}="{{ value }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input_option.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiple_input.html" %}

View File

@ -0,0 +1,5 @@
{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
{{ widget.input_text }}:{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>

View File

@ -0,0 +1 @@
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiwidget.html" %}

View File

@ -0,0 +1,5 @@
{% set id = widget.attrs.id %}<div{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
<div><label>{{ group }}</label>{% endif %}{% for widget in options %}<div>
{% include widget.template_name %}</div>{% endfor %}{% if group %}
</div>{% endif %}{% endfor %}
</div>

View File

@ -0,0 +1 @@
{% for widget in widget.subwidgets -%}{% include widget.template_name %}{%- endfor %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiple_input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input_option.html" %}

View File

@ -0,0 +1,5 @@
<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
<optgroup label="{{ group_name }}">{% endif %}{% for widget in group_choices %}
{% include widget.template_name %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
</select>

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</option>

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1,2 @@
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,102 @@
import functools
from pathlib import Path
from django.conf import settings
from django.template.backends.django import DjangoTemplates
from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
@functools.lru_cache
def get_default_renderer():
renderer_class = import_string(settings.FORM_RENDERER)
return renderer_class()
class BaseRenderer:
# RemovedInDjango50Warning: When the deprecation ends, replace with
# form_template_name = "django/forms/div.html"
# formset_template_name = "django/forms/formsets/div.html"
form_template_name = "django/forms/default.html"
formset_template_name = "django/forms/formsets/default.html"
def get_template(self, template_name):
raise NotImplementedError("subclasses must implement get_template()")
def render(self, template_name, context, request=None):
template = self.get_template(template_name)
return template.render(context, request=request).strip()
class EngineMixin:
def get_template(self, template_name):
return self.engine.get_template(template_name)
@cached_property
def engine(self):
return self.backend(
{
"APP_DIRS": True,
"DIRS": [Path(__file__).parent / self.backend.app_dirname],
"NAME": "djangoforms",
"OPTIONS": {},
}
)
class DjangoTemplates(EngineMixin, BaseRenderer):
"""
Load Django templates from the built-in widget templates in
django/forms/templates and from apps' 'templates' directory.
"""
backend = DjangoTemplates
class Jinja2(EngineMixin, BaseRenderer):
"""
Load Jinja2 templates from the built-in widget templates in
django/forms/jinja2 and from apps' 'jinja2' directory.
"""
@cached_property
def backend(self):
from django.template.backends.jinja2 import Jinja2
return Jinja2
class DjangoDivFormRenderer(DjangoTemplates):
"""
Load Django templates from django/forms/templates and from apps'
'templates' directory and use the 'div.html' template to render forms and
formsets.
"""
# RemovedInDjango50Warning Deprecate this class in 5.0 and remove in 6.0.
form_template_name = "django/forms/div.html"
formset_template_name = "django/forms/formsets/div.html"
class Jinja2DivFormRenderer(Jinja2):
"""
Load Jinja2 templates from the built-in widget templates in
django/forms/jinja2 and from apps' 'jinja2' directory.
"""
# RemovedInDjango50Warning Deprecate this class in 5.0 and remove in 6.0.
form_template_name = "django/forms/div.html"
formset_template_name = "django/forms/formsets/div.html"
class TemplatesSetting(BaseRenderer):
"""
Load templates using template.loader.get_template() which is configured
based on settings.TEMPLATES.
"""
def get_template(self, template_name):
return get_template(template_name)

View File

@ -0,0 +1 @@
{% for name, value in attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1 @@
{% include "django/forms/table.html" %}

View File

@ -0,0 +1,24 @@
{{ errors }}
{% if errors and not fields %}
<div>{% for field in hidden_fields %}{{ field }}{% endfor %}</div>
{% endif %}
{% for field, errors in fields %}
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
{% if field.use_fieldset %}
<fieldset>
{% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %}
{% if field.label %}{{ field.label_tag }}{% endif %}
{% endif %}
{% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %}
{{ errors }}
{{ field }}
{% if field.use_fieldset %}</fieldset>{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</div>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/errors/dict/ul.html" %}

View File

@ -0,0 +1,3 @@
{% for field, errors in errors %}* {{ field }}
{% for error in errors %} * {{ error }}
{% endfor %}{% endfor %}

View File

@ -0,0 +1 @@
{% if errors %}<ul class="{{ error_class }}">{% for field, error in errors %}<li>{{ field }}{{ error }}</li>{% endfor %}</ul>{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/errors/list/ul.html" %}

View File

@ -0,0 +1,2 @@
{% for error in errors %}* {{ error }}
{% endfor %}

View File

@ -0,0 +1 @@
{% if errors %}<ul class="{{ error_class }}">{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_div }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_p }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_table }}{% endfor %}

View File

@ -0,0 +1 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_ul }}{% endfor %}

View File

@ -0,0 +1 @@
{% if use_tag %}<{{ tag }}{% include 'django/forms/attrs.html' %}>{{ label }}</{{ tag }}>{% else %}{{ label }}{% endif %}

View File

@ -0,0 +1,20 @@
{{ errors }}
{% if errors and not fields %}
<p>{% for field in hidden_fields %}{{ field }}{% endfor %}</p>
{% endif %}
{% for field, errors in fields %}
{{ errors }}
<p{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
{% if field.label %}{{ field.label_tag }}{% endif %}
{{ field }}
{% if field.help_text %}
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</p>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1,29 @@
{% if errors %}
<tr>
<td colspan="2">
{{ errors }}
{% if not fields %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</td>
</tr>
{% endif %}
{% for field, errors in fields %}
<tr{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
<th>{% if field.label %}{{ field.label_tag }}{% endif %}</th>
<td>
{{ errors }}
{{ field }}
{% if field.help_text %}
<br>
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</td>
</tr>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1,24 @@
{% if errors %}
<li>
{{ errors }}
{% if not fields %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</li>
{% endif %}
{% for field, errors in fields %}
<li{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
{{ errors }}
{% if field.label %}{{ field.label_tag }}{% endif %}
{{ field }}
{% if field.help_text %}
<span class="helptext">{{ field.help_text|safe }}</span>
{% endif %}
{% if forloop.last %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
</li>
{% endfor %}
{% if not fields and not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}

View File

@ -0,0 +1 @@
{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input_option.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiple_input.html" %}

View File

@ -0,0 +1,5 @@
{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
{{ widget.input_text }}:{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>

View File

@ -0,0 +1 @@
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiwidget.html" %}

View File

@ -0,0 +1,5 @@
{% with id=widget.attrs.id %}<div{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
<div><label>{{ group }}</label>{% endif %}{% for option in options %}<div>
{% include option.template_name with widget=option %}</div>{% endfor %}{% if group %}
</div>{% endif %}{% endfor %}
</div>{% endwith %}

View File

@ -0,0 +1 @@
{% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/multiple_input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input_option.html" %}

View File

@ -0,0 +1,5 @@
<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
<optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
</select>

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
<option value="{{ widget.value|stringformat:'s' }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</option>

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
{% include 'django/forms/widgets/multiwidget.html' %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1,2 @@
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View File

@ -0,0 +1,243 @@
import json
import warnings
from collections import UserList
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms.renderers import get_default_renderer
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.html import escape, format_html_join
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.utils.version import get_docs_version
def pretty_name(name):
"""Convert 'first_name' to 'First name'."""
if not name:
return ""
return name.replace("_", " ").capitalize()
def flatatt(attrs):
"""
Convert a dictionary of attributes to a single string.
The returned string will contain a leading space followed by key="value",
XML-style pairs. In the case of a boolean value, the key will appear
without a value. It is assumed that the keys do not need to be
XML-escaped. If the passed dictionary is empty, then return an empty
string.
The result is passed through 'mark_safe' (by way of 'format_html_join').
"""
key_value_attrs = []
boolean_attrs = []
for attr, value in attrs.items():
if isinstance(value, bool):
if value:
boolean_attrs.append((attr,))
elif value is not None:
key_value_attrs.append((attr, value))
return format_html_join("", ' {}="{}"', sorted(key_value_attrs)) + format_html_join(
"", " {}", sorted(boolean_attrs)
)
DEFAULT_TEMPLATE_DEPRECATION_MSG = (
'The "default.html" templates for forms and formsets will be removed. These were '
'proxies to the equivalent "table.html" templates, but the new "div.html" '
"templates will be the default from Django 5.0. Transitional renderers are "
"provided to allow you to opt-in to the new output style now. See "
"https://docs.djangoproject.com/en/%s/releases/4.1/ for more details"
% get_docs_version()
)
class RenderableMixin:
def get_context(self):
raise NotImplementedError(
"Subclasses of RenderableMixin must provide a get_context() method."
)
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()
if (
template == "django/forms/default.html"
or template == "django/forms/formsets/default.html"
):
warnings.warn(
DEFAULT_TEMPLATE_DEPRECATION_MSG, RemovedInDjango50Warning, stacklevel=2
)
return mark_safe(renderer.render(template, context))
__str__ = render
__html__ = render
class RenderableFormMixin(RenderableMixin):
def as_p(self):
"""Render as <p> elements."""
return self.render(self.template_name_p)
def as_table(self):
"""Render as <tr> elements excluding the surrounding <table> tag."""
return self.render(self.template_name_table)
def as_ul(self):
"""Render as <li> elements excluding the surrounding <ul> tag."""
return self.render(self.template_name_ul)
def as_div(self):
"""Render as <div> elements."""
return self.render(self.template_name_div)
class RenderableErrorMixin(RenderableMixin):
def as_json(self, escape_html=False):
return json.dumps(self.get_json_data(escape_html))
def as_text(self):
return self.render(self.template_name_text)
def as_ul(self):
return self.render(self.template_name_ul)
class ErrorDict(dict, RenderableErrorMixin):
"""
A collection of errors that knows how to display itself in various formats.
The dictionary keys are the field names, and the values are the errors.
"""
template_name = "django/forms/errors/dict/default.html"
template_name_text = "django/forms/errors/dict/text.txt"
template_name_ul = "django/forms/errors/dict/ul.html"
def __init__(self, *args, renderer=None, **kwargs):
super().__init__(*args, **kwargs)
self.renderer = renderer or get_default_renderer()
def as_data(self):
return {f: e.as_data() for f, e in self.items()}
def get_json_data(self, escape_html=False):
return {f: e.get_json_data(escape_html) for f, e in self.items()}
def get_context(self):
return {
"errors": self.items(),
"error_class": "errorlist",
}
class ErrorList(UserList, list, RenderableErrorMixin):
"""
A collection of errors that knows how to display itself in various formats.
"""
template_name = "django/forms/errors/list/default.html"
template_name_text = "django/forms/errors/list/text.txt"
template_name_ul = "django/forms/errors/list/ul.html"
def __init__(self, initlist=None, error_class=None, renderer=None):
super().__init__(initlist)
if error_class is None:
self.error_class = "errorlist"
else:
self.error_class = "errorlist {}".format(error_class)
self.renderer = renderer or get_default_renderer()
def as_data(self):
return ValidationError(self.data).error_list
def copy(self):
copy = super().copy()
copy.error_class = self.error_class
return copy
def get_json_data(self, escape_html=False):
errors = []
for error in self.as_data():
message = next(iter(error))
errors.append(
{
"message": escape(message) if escape_html else message,
"code": error.code or "",
}
)
return errors
def get_context(self):
return {
"errors": self,
"error_class": self.error_class,
}
def __repr__(self):
return repr(list(self))
def __contains__(self, item):
return item in list(self)
def __eq__(self, other):
return list(self) == other
def __getitem__(self, i):
error = self.data[i]
if isinstance(error, ValidationError):
return next(iter(error))
return error
def __reduce_ex__(self, *args, **kwargs):
# The `list` reduce function returns an iterator as the fourth element
# that is normally used for repopulating. Since we only inherit from
# `list` for `isinstance` backward compatibility (Refs #17413) we
# nullify this iterator as it would otherwise result in duplicate
# entries. (Refs #23594)
info = super(UserList, self).__reduce_ex__(*args, **kwargs)
return info[:3] + (None, None)
# Utilities for time zone support in DateTimeField et al.
def from_current_timezone(value):
"""
When time zone support is enabled, convert naive datetimes
entered in the current time zone to aware datetimes.
"""
if settings.USE_TZ and value is not None and timezone.is_naive(value):
current_timezone = timezone.get_current_timezone()
try:
if not timezone._is_pytz_zone(
current_timezone
) and timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
raise ValueError("Ambiguous or non-existent time.")
return timezone.make_aware(value, current_timezone)
except Exception as exc:
raise ValidationError(
_(
"%(datetime)s couldnt be interpreted "
"in time zone %(current_timezone)s; it "
"may be ambiguous or it may not exist."
),
code="ambiguous_timezone",
params={"datetime": value, "current_timezone": current_timezone},
) from exc
return value
def to_current_timezone(value):
"""
When time zone support is enabled, convert aware datetimes
to naive datetimes in the current time zone for display.
"""
if settings.USE_TZ and value is not None and timezone.is_aware(value):
return timezone.make_naive(value)
return value

Some files were not shown because too many files have changed in this diff Show More