测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -1,19 +1,19 @@
import datetime
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.forms.utils import flatatt, pretty_name
from django.forms.widgets import Textarea, TextInput
from django.utils.functional import cached_property
from django.utils.html import format_html, html_safe
from django.utils.html import conditional_escape, format_html, html_safe
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
__all__ = ("BoundField",)
__all__ = ('BoundField',)
@html_safe
class BoundField:
"A Field plus data"
def __init__(self, form, field, name):
self.form = form
self.field = field
@@ -25,7 +25,7 @@ class BoundField:
self.label = pretty_name(name)
else:
self.label = self.field.label
self.help_text = field.help_text or ""
self.help_text = field.help_text or ''
def __str__(self):
"""Render this field as an HTML widget."""
@@ -42,14 +42,12 @@ class BoundField:
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 {}
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
)
for widget in self.field.widget.subwidgets(self.html_name, self.value(), attrs=attrs)
]
def __bool__(self):
@@ -67,7 +65,7 @@ class BoundField:
# from templates.
if not isinstance(idx, (int, slice)):
raise TypeError(
"BoundField indices must be integers or slices, not %s."
'BoundField indices must be integers or slices, not %s.'
% type(idx).__name__
)
return self.subwidgets[idx]
@@ -77,9 +75,7 @@ class BoundField:
"""
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)
)
return self.form.errors.get(self.name, self.form.error_class())
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
@@ -92,10 +88,8 @@ class BoundField:
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 self.auto_id and 'id' not in widget.attrs:
attrs.setdefault('id', self.html_initial_id if only_initial else self.auto_id)
return widget.render(
name=self.html_initial_name if only_initial else self.html_name,
value=self.value(),
@@ -124,7 +118,7 @@ class BoundField:
"""
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)
return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
def value(self):
"""
@@ -136,23 +130,6 @@ class BoundField:
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):
"""
Wrap the given contents in a <label>, if the field has an ID attribute.
@@ -165,48 +142,43 @@ class BoundField:
"""
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
)
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)
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
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 {}), '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
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_),
}
return self.form.render(self.form.template_name_label, context)
attrs['class'] = self.form.required_css_class
attrs = flatatt(attrs) if attrs else ''
contents = format_html('<label{}>{}</label>', attrs, contents)
else:
contents = conditional_escape(contents)
return mark_safe(contents)
def css_classes(self, extra_classes=None):
"""
Return a string of space-separated CSS classes for this field.
"""
if hasattr(extra_classes, "split"):
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"):
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"):
if self.field.required and hasattr(self.form, 'required_css_class'):
extra_classes.add(self.form.required_css_class)
return " ".join(extra_classes)
return ' '.join(extra_classes)
@property
def is_hidden(self):
@@ -220,11 +192,11 @@ class BoundField:
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):
if auto_id and '%s' in str(auto_id):
return auto_id % self.html_name
elif auto_id:
return self.html_name
return ""
return ''
@property
def id_for_label(self):
@@ -234,44 +206,31 @@ class BoundField:
it has a single widget or a MultiWidget.
"""
widget = self.field.widget
id_ = widget.attrs.get("id") or self.auto_id
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)
data = self.form.get_initial_for_field(self.field, self.name)
# If this is an auto-generated default date, nix the microseconds for
# standardized handling. See #22502.
if (isinstance(data, (datetime.datetime, datetime.time)) and
not self.field.widget.supports_microseconds):
data = data.replace(microsecond=0)
return data
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 widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute:
attrs['required'] = True
if self.field.disabled:
attrs["disabled"] = True
attrs['disabled'] = True
return attrs
@property
def widget_type(self):
return re.sub(
r"widget$|input$", "", self.field.widget.__class__.__name__.lower()
)
return re.sub(r'widget$|input$', '', self.field.widget.__class__.__name__.lower())
@html_safe
@@ -288,7 +247,6 @@ class BoundWidget:
</label>
{% endfor %}
"""
def __init__(self, parent_widget, data, renderer):
self.parent_widget = parent_widget
self.data = data
@@ -298,19 +256,19 @@ class BoundWidget:
return self.tag(wrap_label=True)
def tag(self, wrap_label=False):
context = {"widget": {**self.data, "wrap_label": wrap_label}}
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"]
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")
return 'id_%s_%s' % (self.data['name'], self.data['index'])
@property
def choice_label(self):
return self.data["label"]
return self.data['label']
+171 -282
View File
@@ -19,94 +19,45 @@ from django.core.exceptions import ValidationError
from django.forms.boundfield import BoundField
from django.forms.utils import from_current_timezone, to_current_timezone
from django.forms.widgets import (
FILE_INPUT_CONTRADICTION,
CheckboxInput,
ClearableFileInput,
DateInput,
DateTimeInput,
EmailInput,
FileInput,
HiddenInput,
MultipleHiddenInput,
NullBooleanSelect,
NumberInput,
Select,
SelectMultiple,
SplitDateTimeWidget,
SplitHiddenDateTimeWidget,
Textarea,
TextInput,
TimeInput,
URLInput,
FILE_INPUT_CONTRADICTION, CheckboxInput, ClearableFileInput, DateInput,
DateTimeInput, EmailInput, FileInput, HiddenInput, MultipleHiddenInput,
NullBooleanSelect, NumberInput, Select, SelectMultiple,
SplitDateTimeWidget, SplitHiddenDateTimeWidget, Textarea, TextInput,
TimeInput, URLInput,
)
from django.utils import formats
from django.utils.dateparse import parse_datetime, parse_duration
from django.utils.duration import duration_string
from django.utils.ipv6 import clean_ipv6_address
from django.utils.regex_helper import _lazy_re_compile
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from django.utils.translation import gettext_lazy as _, ngettext_lazy
__all__ = (
"Field",
"CharField",
"IntegerField",
"DateField",
"TimeField",
"DateTimeField",
"DurationField",
"RegexField",
"EmailField",
"FileField",
"ImageField",
"URLField",
"BooleanField",
"NullBooleanField",
"ChoiceField",
"MultipleChoiceField",
"ComboField",
"MultiValueField",
"FloatField",
"DecimalField",
"SplitDateTimeField",
"GenericIPAddressField",
"FilePathField",
"JSONField",
"SlugField",
"TypedChoiceField",
"TypedMultipleChoiceField",
"UUIDField",
'Field', 'CharField', 'IntegerField',
'DateField', 'TimeField', 'DateTimeField', 'DurationField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'GenericIPAddressField', 'FilePathField',
'JSONField', 'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField',
'UUIDField',
)
class Field:
widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = (
HiddenInput # Default widget to use when rendering this as "hidden".
)
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_validators = [] # Default set of validators
# Add an 'invalid' entry to default_error_message if you want a specific
# field error message not raised by the field validators.
default_error_messages = {
"required": _("This field is required."),
'required': _('This field is required.'),
}
empty_values = list(validators.EMPTY_VALUES)
def __init__(
self,
*,
required=True,
widget=None,
label=None,
initial=None,
help_text="",
error_messages=None,
show_hidden_initial=False,
validators=(),
localize=False,
disabled=False,
label_suffix=None,
):
def __init__(self, *, required=True, widget=None, label=None, initial=None,
help_text='', error_messages=None, show_hidden_initial=False,
validators=(), localize=False, disabled=False, label_suffix=None):
# required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should
@@ -158,7 +109,7 @@ class Field:
messages = {}
for c in reversed(self.__class__.__mro__):
messages.update(getattr(c, "default_error_messages", {}))
messages.update(getattr(c, 'default_error_messages', {}))
messages.update(error_messages or {})
self.error_messages = messages
@@ -174,7 +125,7 @@ class Field:
def validate(self, value):
if value in self.empty_values and self.required:
raise ValidationError(self.error_messages["required"], code="required")
raise ValidationError(self.error_messages['required'], code='required')
def run_validators(self, value):
if value in self.empty_values:
@@ -184,7 +135,7 @@ class Field:
try:
v(value)
except ValidationError as e:
if hasattr(e, "code") and e.code in self.error_messages:
if hasattr(e, 'code') and e.code in self.error_messages:
e.message = self.error_messages[e.code]
errors.extend(e.error_list)
if errors:
@@ -229,15 +180,15 @@ class Field:
return False
try:
data = self.to_python(data)
if hasattr(self, "_coerce"):
if hasattr(self, '_coerce'):
return self._coerce(data) != self._coerce(initial)
except ValidationError:
return True
# For purposes of seeing whether something has changed, None is
# the same as an empty string, if the data or initial value we get
# is None, replace it with ''.
initial_value = initial if initial is not None else ""
data_value = data if data is not None else ""
initial_value = initial if initial is not None else ''
data_value = data if data is not None else ''
return initial_value != data_value
def get_bound_field(self, form, field_name):
@@ -257,9 +208,7 @@ class Field:
class CharField(Field):
def __init__(
self, *, max_length=None, min_length=None, strip=True, empty_value="", **kwargs
):
def __init__(self, *, max_length=None, min_length=None, strip=True, empty_value='', **kwargs):
self.max_length = max_length
self.min_length = min_length
self.strip = strip
@@ -285,25 +234,25 @@ class CharField(Field):
attrs = super().widget_attrs(widget)
if self.max_length is not None and not widget.is_hidden:
# The HTML attribute is maxlength, not max_length.
attrs["maxlength"] = str(self.max_length)
attrs['maxlength'] = str(self.max_length)
if self.min_length is not None and not widget.is_hidden:
# The HTML attribute is minlength, not min_length.
attrs["minlength"] = str(self.min_length)
attrs['minlength'] = str(self.min_length)
return attrs
class IntegerField(Field):
widget = NumberInput
default_error_messages = {
"invalid": _("Enter a whole number."),
'invalid': _('Enter a whole number.'),
}
re_decimal = _lazy_re_compile(r"\.0*\s*$")
re_decimal = _lazy_re_compile(r'\.0*\s*$')
def __init__(self, *, max_value=None, min_value=None, **kwargs):
self.max_value, self.min_value = max_value, min_value
if kwargs.get("localize") and self.widget == NumberInput:
if kwargs.get('localize') and self.widget == NumberInput:
# Localized number input is not well supported on most browsers
kwargs.setdefault("widget", super().widget)
kwargs.setdefault('widget', super().widget)
super().__init__(**kwargs)
if max_value is not None:
@@ -323,24 +272,24 @@ class IntegerField(Field):
value = formats.sanitize_separators(value)
# Strip trailing decimal and zeros.
try:
value = int(self.re_decimal.sub("", str(value)))
value = int(self.re_decimal.sub('', str(value)))
except (ValueError, TypeError):
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
if isinstance(widget, NumberInput):
if self.min_value is not None:
attrs["min"] = self.min_value
attrs['min'] = self.min_value
if self.max_value is not None:
attrs["max"] = self.max_value
attrs['max'] = self.max_value
return attrs
class FloatField(IntegerField):
default_error_messages = {
"invalid": _("Enter a number."),
'invalid': _('Enter a number.'),
}
def to_python(self, value):
@@ -356,7 +305,7 @@ class FloatField(IntegerField):
try:
value = float(value)
except (ValueError, TypeError):
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
def validate(self, value):
@@ -364,29 +313,21 @@ class FloatField(IntegerField):
if value in self.empty_values:
return
if not math.isfinite(value):
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
if isinstance(widget, NumberInput) and "step" not in widget.attrs:
attrs.setdefault("step", "any")
if isinstance(widget, NumberInput) and 'step' not in widget.attrs:
attrs.setdefault('step', 'any')
return attrs
class DecimalField(IntegerField):
default_error_messages = {
"invalid": _("Enter a number."),
'invalid': _('Enter a number.'),
}
def __init__(
self,
*,
max_value=None,
min_value=None,
max_digits=None,
decimal_places=None,
**kwargs,
):
def __init__(self, *, max_value=None, min_value=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places
super().__init__(max_value=max_value, min_value=min_value, **kwargs)
self.validators.append(validators.DecimalValidator(max_digits, decimal_places))
@@ -402,37 +343,28 @@ class DecimalField(IntegerField):
return None
if self.localize:
value = formats.sanitize_separators(value)
value = str(value).strip()
try:
value = Decimal(str(value))
value = Decimal(value)
except DecimalException:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
def validate(self, value):
super().validate(value)
if value in self.empty_values:
return
if not value.is_finite():
raise ValidationError(
self.error_messages["invalid"],
code="invalid",
params={"value": value},
)
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
if isinstance(widget, NumberInput) and "step" not in widget.attrs:
if isinstance(widget, NumberInput) and 'step' not in widget.attrs:
if self.decimal_places is not None:
# Use exponential notation for small values since they might
# be parsed as 0 otherwise. ref #20765
step = str(Decimal(1).scaleb(-self.decimal_places)).lower()
else:
step = "any"
attrs.setdefault("step", step)
step = 'any'
attrs.setdefault('step', step)
return attrs
class BaseTemporalField(Field):
def __init__(self, *, input_formats=None, **kwargs):
super().__init__(**kwargs)
if input_formats is not None:
@@ -446,17 +378,17 @@ class BaseTemporalField(Field):
return self.strptime(value, format)
except (ValueError, TypeError):
continue
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
def strptime(self, value, format):
raise NotImplementedError("Subclasses must define this method.")
raise NotImplementedError('Subclasses must define this method.')
class DateField(BaseTemporalField):
widget = DateInput
input_formats = formats.get_format_lazy("DATE_INPUT_FORMATS")
input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS')
default_error_messages = {
"invalid": _("Enter a valid date."),
'invalid': _('Enter a valid date.'),
}
def to_python(self, value):
@@ -478,8 +410,10 @@ class DateField(BaseTemporalField):
class TimeField(BaseTemporalField):
widget = TimeInput
input_formats = formats.get_format_lazy("TIME_INPUT_FORMATS")
default_error_messages = {"invalid": _("Enter a valid time.")}
input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS')
default_error_messages = {
'invalid': _('Enter a valid time.')
}
def to_python(self, value):
"""
@@ -498,15 +432,15 @@ class TimeField(BaseTemporalField):
class DateTimeFormatsIterator:
def __iter__(self):
yield from formats.get_format("DATETIME_INPUT_FORMATS")
yield from formats.get_format("DATE_INPUT_FORMATS")
yield from formats.get_format('DATETIME_INPUT_FORMATS')
yield from formats.get_format('DATE_INPUT_FORMATS')
class DateTimeField(BaseTemporalField):
widget = DateTimeInput
input_formats = DateTimeFormatsIterator()
default_error_messages = {
"invalid": _("Enter a valid date/time."),
'invalid': _('Enter a valid date/time.'),
}
def prepare_value(self, value):
@@ -529,7 +463,7 @@ class DateTimeField(BaseTemporalField):
try:
result = parse_datetime(value.strip())
except ValueError:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
if not result:
result = super().to_python(value)
return from_current_timezone(result)
@@ -540,8 +474,8 @@ class DateTimeField(BaseTemporalField):
class DurationField(Field):
default_error_messages = {
"invalid": _("Enter a valid duration."),
"overflow": _("The number of days must be between {min_days} and {max_days}."),
'invalid': _('Enter a valid duration.'),
'overflow': _('The number of days must be between {min_days} and {max_days}.')
}
def prepare_value(self, value):
@@ -557,15 +491,12 @@ class DurationField(Field):
try:
value = parse_duration(str(value))
except OverflowError:
raise ValidationError(
self.error_messages["overflow"].format(
min_days=datetime.timedelta.min.days,
max_days=datetime.timedelta.max.days,
),
code="overflow",
)
raise ValidationError(self.error_messages['overflow'].format(
min_days=datetime.timedelta.min.days,
max_days=datetime.timedelta.max.days,
), code='overflow')
if value is None:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
@@ -574,7 +505,7 @@ class RegexField(CharField):
"""
regex can be either a string or a compiled regular expression object.
"""
kwargs.setdefault("strip", False)
kwargs.setdefault('strip', False)
super().__init__(**kwargs)
self._set_regex(regex)
@@ -585,10 +516,7 @@ class RegexField(CharField):
if isinstance(regex, str):
regex = re.compile(regex)
self._regex = regex
if (
hasattr(self, "_regex_validator")
and self._regex_validator in self.validators
):
if hasattr(self, '_regex_validator') and self._regex_validator in self.validators:
self.validators.remove(self._regex_validator)
self._regex_validator = validators.RegexValidator(regex=regex)
self.validators.append(self._regex_validator)
@@ -607,17 +535,14 @@ class EmailField(CharField):
class FileField(Field):
widget = ClearableFileInput
default_error_messages = {
"invalid": _("No file was submitted. Check the encoding type on the form."),
"missing": _("No file was submitted."),
"empty": _("The submitted file is empty."),
"max_length": ngettext_lazy(
"Ensure this filename has at most %(max)d character (it has %(length)d).",
"Ensure this filename has at most %(max)d characters (it has %(length)d).",
"max",
),
"contradiction": _(
"Please either submit a file or check the clear checkbox, not both."
),
'invalid': _("No file was submitted. Check the encoding type on the form."),
'missing': _("No file was submitted."),
'empty': _("The submitted file is empty."),
'max_length': ngettext_lazy(
'Ensure this filename has at most %(max)d character (it has %(length)d).',
'Ensure this filename has at most %(max)d characters (it has %(length)d).',
'max'),
'contradiction': _('Please either submit a file or check the clear checkbox, not both.')
}
def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs):
@@ -634,26 +559,22 @@ class FileField(Field):
file_name = data.name
file_size = data.size
except AttributeError:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
if self.max_length is not None and len(file_name) > self.max_length:
params = {"max": self.max_length, "length": len(file_name)}
raise ValidationError(
self.error_messages["max_length"], code="max_length", params=params
)
params = {'max': self.max_length, 'length': len(file_name)}
raise ValidationError(self.error_messages['max_length'], code='max_length', params=params)
if not file_name:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
if not self.allow_empty_file and not file_size:
raise ValidationError(self.error_messages["empty"], code="empty")
raise ValidationError(self.error_messages['empty'], code='empty')
return data
def clean(self, data, initial=None):
# If the widget got contradictory inputs, we raise a validation error
if data is FILE_INPUT_CONTRADICTION:
raise ValidationError(
self.error_messages["contradiction"], code="contradiction"
)
raise ValidationError(self.error_messages['contradiction'], code='contradiction')
# False means the field value should be cleared; further validation is
# not needed.
if data is False:
@@ -681,7 +602,7 @@ class FileField(Field):
class ImageField(FileField):
default_validators = [validators.validate_image_file_extension]
default_error_messages = {
"invalid_image": _(
'invalid_image': _(
"Upload a valid image. The file you uploaded was either not an "
"image or a corrupted image."
),
@@ -700,13 +621,13 @@ class ImageField(FileField):
# We need to get a file object for Pillow. We might have a path or we might
# have to read the data into memory.
if hasattr(data, "temporary_file_path"):
if hasattr(data, 'temporary_file_path'):
file = data.temporary_file_path()
else:
if hasattr(data, "read"):
if hasattr(data, 'read'):
file = BytesIO(data.read())
else:
file = BytesIO(data["content"])
file = BytesIO(data['content'])
try:
# load() could spot a truncated JPEG, but it loads the entire
@@ -723,24 +644,24 @@ class ImageField(FileField):
except Exception as exc:
# Pillow doesn't recognize it as an image.
raise ValidationError(
self.error_messages["invalid_image"],
code="invalid_image",
self.error_messages['invalid_image'],
code='invalid_image',
) from exc
if hasattr(f, "seek") and callable(f.seek):
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
return f
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
if isinstance(widget, FileInput) and "accept" not in widget.attrs:
attrs.setdefault("accept", "image/*")
if isinstance(widget, FileInput) and 'accept' not in widget.attrs:
attrs.setdefault('accept', 'image/*')
return attrs
class URLField(CharField):
widget = URLInput
default_error_messages = {
"invalid": _("Enter a valid URL."),
'invalid': _('Enter a valid URL.'),
}
default_validators = [validators.URLValidator()]
@@ -748,6 +669,7 @@ class URLField(CharField):
super().__init__(strip=True, **kwargs)
def to_python(self, value):
def split_url(url):
"""
Return a list of url parts via urlparse.urlsplit(), or raise
@@ -758,19 +680,19 @@ class URLField(CharField):
except ValueError:
# urlparse.urlsplit can raise a ValueError with some
# misformatted URLs.
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
value = super().to_python(value)
if value:
url_fields = split_url(value)
if not url_fields[0]:
# If no URL scheme given, assume http://
url_fields[0] = "http"
url_fields[0] = 'http'
if not url_fields[1]:
# Assume that if no domain is provided, that the path segment
# contains the domain.
url_fields[1] = url_fields[2]
url_fields[2] = ""
url_fields[2] = ''
# Rebuild the url_fields list, since the domain segment may now
# contain the path too.
url_fields = split_url(urlunsplit(url_fields))
@@ -787,7 +709,7 @@ class BooleanField(Field):
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly.
if isinstance(value, str) and value.lower() in ("false", "0"):
if isinstance(value, str) and value.lower() in ('false', '0'):
value = False
else:
value = bool(value)
@@ -795,7 +717,7 @@ class BooleanField(Field):
def validate(self, value):
if not value and self.required:
raise ValidationError(self.error_messages["required"], code="required")
raise ValidationError(self.error_messages['required'], code='required')
def has_changed(self, initial, data):
if self.disabled:
@@ -810,7 +732,6 @@ class NullBooleanField(BooleanField):
A field whose valid values are None, True, and False. Clean invalid values
to None.
"""
widget = NullBooleanSelect
def to_python(self, value):
@@ -822,9 +743,9 @@ class NullBooleanField(BooleanField):
the Booleanfield, this field must check for True because it doesn't
use the bool() function.
"""
if value in (True, "True", "true", "1"):
if value in (True, 'True', 'true', '1'):
return True
elif value in (False, "False", "false", "0"):
elif value in (False, 'False', 'false', '0'):
return False
else:
return None
@@ -844,9 +765,7 @@ class CallableChoiceIterator:
class ChoiceField(Field):
widget = Select
default_error_messages = {
"invalid_choice": _(
"Select a valid choice. %(value)s is not one of the available choices."
),
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
}
def __init__(self, *, choices=(), **kwargs):
@@ -877,7 +796,7 @@ class ChoiceField(Field):
def to_python(self, value):
"""Return a string."""
if value in self.empty_values:
return ""
return ''
return str(value)
def validate(self, value):
@@ -885,9 +804,9 @@ class ChoiceField(Field):
super().validate(value)
if value and not self.valid_value(value):
raise ValidationError(
self.error_messages["invalid_choice"],
code="invalid_choice",
params={"value": value},
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
def valid_value(self, value):
@@ -906,7 +825,7 @@ class ChoiceField(Field):
class TypedChoiceField(ChoiceField):
def __init__(self, *, coerce=lambda val: val, empty_value="", **kwargs):
def __init__(self, *, coerce=lambda val: val, empty_value='', **kwargs):
self.coerce = coerce
self.empty_value = empty_value
super().__init__(**kwargs)
@@ -921,9 +840,9 @@ class TypedChoiceField(ChoiceField):
value = self.coerce(value)
except (ValueError, TypeError, ValidationError):
raise ValidationError(
self.error_messages["invalid_choice"],
code="invalid_choice",
params={"value": value},
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
return value
@@ -936,32 +855,28 @@ class MultipleChoiceField(ChoiceField):
hidden_widget = MultipleHiddenInput
widget = SelectMultiple
default_error_messages = {
"invalid_choice": _(
"Select a valid choice. %(value)s is not one of the available choices."
),
"invalid_list": _("Enter a list of values."),
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
'invalid_list': _('Enter a list of values.'),
}
def to_python(self, value):
if not value:
return []
elif not isinstance(value, (list, tuple)):
raise ValidationError(
self.error_messages["invalid_list"], code="invalid_list"
)
raise ValidationError(self.error_messages['invalid_list'], code='invalid_list')
return [str(val) for val in value]
def validate(self, value):
"""Validate that the input is a list or tuple."""
if self.required and not value:
raise ValidationError(self.error_messages["required"], code="required")
raise ValidationError(self.error_messages['required'], code='required')
# Validate that each value in the value list is in self.choices.
for val in value:
if not self.valid_value(val):
raise ValidationError(
self.error_messages["invalid_choice"],
code="invalid_choice",
params={"value": val},
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': val},
)
def has_changed(self, initial, data):
@@ -981,7 +896,7 @@ class MultipleChoiceField(ChoiceField):
class TypedMultipleChoiceField(MultipleChoiceField):
def __init__(self, *, coerce=lambda val: val, **kwargs):
self.coerce = coerce
self.empty_value = kwargs.pop("empty_value", [])
self.empty_value = kwargs.pop('empty_value', [])
super().__init__(**kwargs)
def _coerce(self, value):
@@ -997,9 +912,9 @@ class TypedMultipleChoiceField(MultipleChoiceField):
new_value.append(self.coerce(choice))
except (ValueError, TypeError, ValidationError):
raise ValidationError(
self.error_messages["invalid_choice"],
code="invalid_choice",
params={"value": choice},
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': choice},
)
return new_value
@@ -1011,14 +926,13 @@ class TypedMultipleChoiceField(MultipleChoiceField):
if value != self.empty_value:
super().validate(value)
elif self.required:
raise ValidationError(self.error_messages["required"], code="required")
raise ValidationError(self.error_messages['required'], code='required')
class ComboField(Field):
"""
A Field whose clean() method calls multiple Field clean() methods.
"""
def __init__(self, fields, **kwargs):
super().__init__(**kwargs)
# Set 'required' to False on the individual fields, because the
@@ -1056,17 +970,17 @@ class MultiValueField(Field):
You'll probably want to use this with MultiWidget.
"""
default_error_messages = {
"invalid": _("Enter a list of values."),
"incomplete": _("Enter a complete value."),
'invalid': _('Enter a list of values.'),
'incomplete': _('Enter a complete value.'),
}
def __init__(self, fields, *, require_all_fields=True, **kwargs):
self.require_all_fields = require_all_fields
super().__init__(**kwargs)
for f in fields:
f.error_messages.setdefault("incomplete", self.error_messages["incomplete"])
f.error_messages.setdefault('incomplete',
self.error_messages['incomplete'])
if self.disabled:
f.disabled = True
if self.require_all_fields:
@@ -1100,13 +1014,11 @@ class MultiValueField(Field):
if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in self.empty_values]:
if self.required:
raise ValidationError(
self.error_messages["required"], code="required"
)
raise ValidationError(self.error_messages['required'], code='required')
else:
return self.compress([])
else:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
for i, field in enumerate(self.fields):
try:
field_value = value[i]
@@ -1117,15 +1029,13 @@ class MultiValueField(Field):
# Raise a 'required' error if the MultiValueField is
# required and any field is empty.
if self.required:
raise ValidationError(
self.error_messages["required"], code="required"
)
raise ValidationError(self.error_messages['required'], code='required')
elif field.required:
# Otherwise, add an 'incomplete' error to the list of
# collected errors and skip field cleaning, if a required
# field is empty.
if field.error_messages["incomplete"] not in errors:
errors.append(field.error_messages["incomplete"])
if field.error_messages['incomplete'] not in errors:
errors.append(field.error_messages['incomplete'])
continue
try:
clean_data.append(field.clean(field_value))
@@ -1151,13 +1061,13 @@ class MultiValueField(Field):
fields=(DateField(), TimeField()), this might return a datetime
object created by combining the date and time in data_list.
"""
raise NotImplementedError("Subclasses must implement this method.")
raise NotImplementedError('Subclasses must implement this method.')
def has_changed(self, initial, data):
if self.disabled:
return False
if initial is None:
initial = ["" for x in range(0, len(data))]
initial = ['' for x in range(0, len(data))]
else:
if not isinstance(initial, list):
initial = self.widget.decompress(initial)
@@ -1172,16 +1082,8 @@ class MultiValueField(Field):
class FilePathField(ChoiceField):
def __init__(
self,
path,
*,
match=None,
recursive=False,
allow_files=True,
allow_folders=False,
**kwargs,
):
def __init__(self, path, *, match=None, recursive=False, allow_files=True,
allow_folders=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
self.allow_files, self.allow_folders = allow_files, allow_folders
super().__init__(choices=(), **kwargs)
@@ -1203,22 +1105,20 @@ class FilePathField(ChoiceField):
self.choices.append((f, f.replace(path, "", 1)))
if self.allow_folders:
for f in sorted(dirs):
if f == "__pycache__":
if f == '__pycache__':
continue
if self.match is None or self.match_re.search(f):
f = os.path.join(root, f)
self.choices.append((f, f.replace(path, "", 1)))
else:
choices = []
with os.scandir(self.path) as entries:
for f in entries:
if f.name == "__pycache__":
continue
if (
(self.allow_files and f.is_file())
or (self.allow_folders and f.is_dir())
) and (self.match is None or self.match_re.search(f.name)):
choices.append((f.path, f.name))
for f in os.scandir(self.path):
if f.name == '__pycache__':
continue
if (((self.allow_files and f.is_file()) or
(self.allow_folders and f.is_dir())) and
(self.match is None or self.match_re.search(f.name))):
choices.append((f.path, f.name))
choices.sort(key=operator.itemgetter(1))
self.choices.extend(choices)
@@ -1229,26 +1129,22 @@ class SplitDateTimeField(MultiValueField):
widget = SplitDateTimeWidget
hidden_widget = SplitHiddenDateTimeWidget
default_error_messages = {
"invalid_date": _("Enter a valid date."),
"invalid_time": _("Enter a valid time."),
'invalid_date': _('Enter a valid date.'),
'invalid_time': _('Enter a valid time.'),
}
def __init__(self, *, input_date_formats=None, input_time_formats=None, **kwargs):
errors = self.default_error_messages.copy()
if "error_messages" in kwargs:
errors.update(kwargs["error_messages"])
localize = kwargs.get("localize", False)
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
localize = kwargs.get('localize', False)
fields = (
DateField(
input_formats=input_date_formats,
error_messages={"invalid": errors["invalid_date"]},
localize=localize,
),
TimeField(
input_formats=input_time_formats,
error_messages={"invalid": errors["invalid_time"]},
localize=localize,
),
DateField(input_formats=input_date_formats,
error_messages={'invalid': errors['invalid_date']},
localize=localize),
TimeField(input_formats=input_time_formats,
error_messages={'invalid': errors['invalid_time']},
localize=localize),
)
super().__init__(fields, **kwargs)
@@ -1257,31 +1153,25 @@ class SplitDateTimeField(MultiValueField):
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
if data_list[0] in self.empty_values:
raise ValidationError(
self.error_messages["invalid_date"], code="invalid_date"
)
raise ValidationError(self.error_messages['invalid_date'], code='invalid_date')
if data_list[1] in self.empty_values:
raise ValidationError(
self.error_messages["invalid_time"], code="invalid_time"
)
raise ValidationError(self.error_messages['invalid_time'], code='invalid_time')
result = datetime.datetime.combine(*data_list)
return from_current_timezone(result)
return None
class GenericIPAddressField(CharField):
def __init__(self, *, protocol="both", unpack_ipv4=False, **kwargs):
def __init__(self, *, protocol='both', unpack_ipv4=False, **kwargs):
self.unpack_ipv4 = unpack_ipv4
self.default_validators = validators.ip_address_validators(
protocol, unpack_ipv4
)[0]
self.default_validators = validators.ip_address_validators(protocol, unpack_ipv4)[0]
super().__init__(**kwargs)
def to_python(self, value):
if value in self.empty_values:
return ""
return ''
value = value.strip()
if value and ":" in value:
if value and ':' in value:
return clean_ipv6_address(value, self.unpack_ipv4)
return value
@@ -1298,7 +1188,7 @@ class SlugField(CharField):
class UUIDField(CharField):
default_error_messages = {
"invalid": _("Enter a valid UUID."),
'invalid': _('Enter a valid UUID.'),
}
def prepare_value(self, value):
@@ -1314,7 +1204,7 @@ class UUIDField(CharField):
try:
value = uuid.UUID(value)
except ValueError:
raise ValidationError(self.error_messages["invalid"], code="invalid")
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
@@ -1328,7 +1218,7 @@ class JSONString(str):
class JSONField(CharField):
default_error_messages = {
"invalid": _("Enter a valid JSON."),
'invalid': _('Enter a valid JSON.'),
}
widget = Textarea
@@ -1348,9 +1238,9 @@ class JSONField(CharField):
converted = json.loads(value, cls=self.decoder)
except json.JSONDecodeError:
raise ValidationError(
self.error_messages["invalid"],
code="invalid",
params={"value": value},
self.error_messages['invalid'],
code='invalid',
params={'value': value},
)
if isinstance(converted, str):
return JSONString(converted)
@@ -1360,8 +1250,6 @@ class JSONField(CharField):
def bound_data(self, data, initial):
if self.disabled:
return initial
if data is None:
return None
try:
return json.loads(data, cls=self.decoder)
except json.JSONDecodeError:
@@ -1377,6 +1265,7 @@ class JSONField(CharField):
return True
# For purposes of seeing whether something has changed, True isn't the
# same as 1 and the order of keys doesn't matter.
return json.dumps(initial, sort_keys=True, cls=self.encoder) != json.dumps(
self.to_python(data), sort_keys=True, cls=self.encoder
return (
json.dumps(initial, sort_keys=True, cls=self.encoder) !=
json.dumps(self.to_python(data), sort_keys=True, cls=self.encoder)
)
+129 -171
View File
@@ -3,33 +3,28 @@ 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.utils import ErrorDict, ErrorList
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.html import conditional_escape, html_safe
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from .renderers import get_default_renderer
__all__ = ("BaseForm", "Form")
__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())
attrs['declared_fields'] = {
key: attrs.pop(key) for key, value in list(attrs.items())
if isinstance(value, Field)
}
@@ -39,7 +34,7 @@ class DeclarativeFieldsMetaclass(MediaDefiningClass):
declared_fields = {}
for base in reversed(new_class.__mro__):
# Collect fields from base class.
if hasattr(base, "declared_fields"):
if hasattr(base, 'declared_fields'):
declared_fields.update(base.declared_fields)
# Field shadowing.
@@ -53,39 +48,22 @@ class DeclarativeFieldsMetaclass(MediaDefiningClass):
return new_class
class BaseForm(RenderableFormMixin):
@html_safe
class BaseForm:
"""
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 = "django/forms/default.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,
):
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
@@ -95,7 +73,7 @@ class BaseForm(RenderableFormMixin):
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.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.
@@ -113,8 +91,8 @@ class BaseForm(RenderableFormMixin):
if self.empty_permitted and self.use_required_attribute:
raise ValueError(
"The empty_permitted and use_required_attribute arguments may "
"not both be True."
'The empty_permitted and use_required_attribute arguments may '
'not both be True.'
)
# Initialize form renderer. Use a global default if not specified
@@ -150,48 +128,40 @@ class BaseForm(RenderableFormMixin):
fields.update(self.fields) # add remaining fields in original order
self.fields = fields
def __str__(self):
return self.as_table()
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),
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:
return self._bound_fields_cache[name]
except KeyError:
pass
try:
field = self.fields[name]
except KeyError:
raise KeyError(
"Key '%s' not found in '%s'. Choices are: %s."
% (
"Key '%s' not found in '%s'. Choices are: %s." % (
name,
self.__class__.__name__,
", ".join(sorted(self.fields)),
', '.join(sorted(self.fields)),
)
)
bound_field = field.get_bound_field(self, name)
self._bound_fields_cache[name] = bound_field
return bound_field
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):
@@ -211,45 +181,27 @@ class BaseForm(RenderableFormMixin):
Subclasses may wish to override.
"""
return "%s-%s" % (self.prefix, field_name) if self.prefix else field_name
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)
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
):
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 = ""
for name, field in self.fields.items():
html_class_attr = ''
bf = self[name]
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 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
@@ -263,33 +215,30 @@ class BaseForm(RenderableFormMixin):
if bf.label:
label = conditional_escape(bf.label)
label = bf.label_tag(label) or ""
label = bf.label_tag(label) or ''
else:
label = ""
label = ''
if field.help_text:
help_text = help_text_html % field.help_text
else:
help_text = ""
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,
}
)
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)
str_hidden = ''.join(hidden_fields)
if output:
last_row = output[-1]
# Chop off the trailing row_ender (e.g. '</td></tr>') and
@@ -299,55 +248,52 @@ class BaseForm(RenderableFormMixin):
# 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": "",
}
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
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))
return mark_safe('\n'.join(output))
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 as_table(self):
"Return this form rendered as HTML <tr>s -- excluding the <table></table>."
return self._html_output(
normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
error_row='<tr><td colspan="2">%s</td></tr>',
row_ender='</td></tr>',
help_text_html='<br><span class="helptext">%s</span>',
errors_on_separate_row=False,
)
def as_ul(self):
"Return this form rendered as HTML <li>s -- excluding the <ul></ul>."
return self._html_output(
normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
error_row='<li>%s</li>',
row_ender='</li>',
help_text_html=' <span class="helptext">%s</span>',
errors_on_separate_row=False,
)
def as_p(self):
"Return this form rendered as HTML <p>s."
return self._html_output(
normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
error_row='%s',
row_ender='</p>',
help_text_html=' <span class="helptext">%s</span>',
errors_on_separate_row=True,
)
def non_field_errors(self):
"""
@@ -355,10 +301,7 @@ class BaseForm(RenderableFormMixin):
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),
)
return self.errors.get(NON_FIELD_ERRORS, self.error_class(error_class='nonfield'))
def add_error(self, field, error):
"""
@@ -383,7 +326,7 @@ class BaseForm(RenderableFormMixin):
# do the hard work of making sense of the input.
error = ValidationError(error)
if hasattr(error, "error_dict"):
if hasattr(error, 'error_dict'):
if field is not None:
raise TypeError(
"The argument `field` must be `None` when the `error` "
@@ -398,23 +341,19 @@ class BaseForm(RenderableFormMixin):
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)
)
"'%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
)
self._errors[field] = self.error_class(error_class='nonfield')
else:
self._errors[field] = self.error_class(renderer=self.renderer)
self._errors[field] = self.error_class()
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])
code is None or
any(error.code == code for error in self.errors.as_data()[field])
)
def full_clean(self):
@@ -435,17 +374,23 @@ class BaseForm(RenderableFormMixin):
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
for name, field in self.fields.items():
# 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.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
value = field.clean(value, bf.initial)
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, "clean_%s" % name):
value = getattr(self, "clean_%s" % name)()
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)
@@ -481,7 +426,27 @@ class BaseForm(RenderableFormMixin):
@cached_property
def changed_data(self):
return [name for name, bf in self._bound_items() if bf._has_changed()]
data = []
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if not field.show_hidden_initial:
# Use the BoundField's initial as this is the value passed to
# the widget.
initial_value = self[name].initial
else:
initial_prefixed_name = self.add_initial_prefix(name)
hidden_widget = field.hidden_widget()
try:
initial_value = field.to_python(hidden_widget.value_from_datadict(
self.data, self.files, initial_prefixed_name))
except ValidationError:
# Always assume data has changed if validation fails.
data.append(name)
continue
if field.has_changed(initial_value, data_value):
data.append(name)
return data
@property
def media(self):
@@ -520,13 +485,6 @@ class BaseForm(RenderableFormMixin):
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
+115 -178
View File
@@ -1,22 +1,22 @@
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.forms.utils import ErrorList
from django.forms.widgets import HiddenInput, NumberInput
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from django.utils.html import html_safe
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _, ngettext
__all__ = ("BaseFormSet", "formset_factory", "all_valid")
__all__ = ('BaseFormSet', 'formset_factory', 'all_valid')
# special field names
TOTAL_FORM_COUNT = "TOTAL_FORMS"
INITIAL_FORM_COUNT = "INITIAL_FORMS"
MIN_NUM_FORM_COUNT = "MIN_NUM_FORMS"
MAX_NUM_FORM_COUNT = "MAX_NUM_FORMS"
ORDERING_FIELD_NAME = "ORDER"
DELETION_FIELD_NAME = "DELETE"
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
# default minimum number of forms in a formset
DEFAULT_MIN_NUM = 0
@@ -31,19 +31,14 @@ class ManagementForm(Form):
new forms via JavaScript, you should increment the count field of this form
as well.
"""
def __init__(self, *args, **kwargs):
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
self.base_fields[INITIAL_FORM_COUNT] = 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.
self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(
required=False, widget=HiddenInput
)
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(
required=False, widget=HiddenInput
)
self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
super().__init__(*args, **kwargs)
def clean(self):
@@ -55,35 +50,22 @@ class ManagementForm(Form):
return cleaned_data
class BaseFormSet(RenderableFormMixin):
@html_safe
class BaseFormSet:
"""
A collection of instances of the same Form class.
"""
deletion_widget = CheckboxInput
ordering_widget = NumberInput
default_error_messages = {
"missing_management_form": _(
"ManagementForm data is missing or has been tampered with. Missing fields: "
"%(field_names)s. You may need to file a bug report if the issue persists."
'missing_management_form': _(
'ManagementForm data is missing or has been tampered with. Missing fields: '
'%(field_names)s. You may need to file a bug report if the issue persists.'
),
}
template_name = "django/forms/formsets/default.html"
template_name_p = "django/forms/formsets/p.html"
template_name_table = "django/forms/formsets/table.html"
template_name_ul = "django/forms/formsets/ul.html"
def __init__(
self,
data=None,
files=None,
auto_id="id_%s",
prefix=None,
initial=None,
error_class=ErrorList,
form_kwargs=None,
error_messages=None,
):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, form_kwargs=None,
error_messages=None):
self.is_bound = data is not None or files is not None
self.prefix = prefix or self.get_default_prefix()
self.auto_id = auto_id
@@ -97,11 +79,14 @@ class BaseFormSet(RenderableFormMixin):
messages = {}
for cls in reversed(type(self).__mro__):
messages.update(getattr(cls, "default_error_messages", {}))
messages.update(getattr(cls, 'default_error_messages', {}))
if error_messages is not None:
messages.update(error_messages)
self.error_messages = messages
def __str__(self):
return self.as_table()
def __iter__(self):
"""Yield the forms in the order they should be rendered."""
return iter(self.forms)
@@ -124,25 +109,15 @@ class BaseFormSet(RenderableFormMixin):
def management_form(self):
"""Return the ManagementForm instance for this FormSet."""
if self.is_bound:
form = ManagementForm(
self.data,
auto_id=self.auto_id,
prefix=self.prefix,
renderer=self.renderer,
)
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
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,
)
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
})
return form
def total_form_count(self):
@@ -152,9 +127,7 @@ class BaseFormSet(RenderableFormMixin):
# count in the data; this is DoS protection to prevent clients
# from forcing the server to instantiate arbitrary numbers of
# forms
return min(
self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max
)
return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
else:
initial_forms = self.initial_form_count()
total_forms = max(initial_forms, self.min_num) + self.extra
@@ -196,27 +169,26 @@ class BaseFormSet(RenderableFormMixin):
def _construct_form(self, i, **kwargs):
"""Instantiate and return the i-th form instance in a formset."""
defaults = {
"auto_id": self.auto_id,
"prefix": self.add_prefix(i),
"error_class": self.error_class,
'auto_id': self.auto_id,
'prefix': self.add_prefix(i),
'error_class': self.error_class,
# Don't render the HTML 'required' attribute as it may cause
# incorrect validation for extra, optional, and deleted
# forms in the formset.
"use_required_attribute": False,
"renderer": self.renderer,
'use_required_attribute': False,
}
if self.is_bound:
defaults["data"] = self.data
defaults["files"] = self.files
if self.initial and "initial" not in kwargs:
defaults['data'] = self.data
defaults['files'] = self.files
if self.initial and 'initial' not in kwargs:
try:
defaults["initial"] = self.initial[i]
defaults['initial'] = self.initial[i]
except IndexError:
pass
# Allow extra forms to be empty, unless they're part of
# the minimum forms.
if i >= self.initial_form_count() and i >= self.min_num:
defaults["empty_permitted"] = True
defaults['empty_permitted'] = True
defaults.update(kwargs)
form = self.form(**defaults)
self.add_fields(form, i)
@@ -225,22 +197,21 @@ class BaseFormSet(RenderableFormMixin):
@property
def initial_forms(self):
"""Return a list of all the initial forms in this formset."""
return self.forms[: self.initial_form_count()]
return self.forms[:self.initial_form_count()]
@property
def extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count() :]
return self.forms[self.initial_form_count():]
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix("__prefix__"),
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
use_required_attribute=False,
**self.get_form_kwargs(None),
renderer=self.renderer,
**self.get_form_kwargs(None)
)
self.add_fields(form, None)
return form
@@ -251,9 +222,7 @@ class BaseFormSet(RenderableFormMixin):
Return a list of form.cleaned_data dicts for every form in self.forms.
"""
if not self.is_valid():
raise AttributeError(
"'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__
)
raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__)
return [form.cleaned_data for form in self.forms]
@property
@@ -263,7 +232,7 @@ class BaseFormSet(RenderableFormMixin):
return []
# construct _deleted_form_indexes which is just a list of form indexes
# that have had their deletion widget set to True
if not hasattr(self, "_deleted_form_indexes"):
if not hasattr(self, '_deleted_form_indexes'):
self._deleted_form_indexes = []
for i, form in enumerate(self.forms):
# if this is an extra form and hasn't changed, don't consider it
@@ -280,14 +249,12 @@ class BaseFormSet(RenderableFormMixin):
Raise an AttributeError if ordering is not allowed.
"""
if not self.is_valid() or not self.can_order:
raise AttributeError(
"'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__
)
raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
# Construct _ordering, which is a list of (form_index, order_field_value)
# tuples. After constructing this list, we'll sort it by order_field_value
# so we have a way to get to the form indexes in the order specified
# by the form data.
if not hasattr(self, "_ordering"):
if not hasattr(self, '_ordering'):
self._ordering = []
for i, form in enumerate(self.forms):
# if this is an extra form and hasn't changed, don't consider it
@@ -307,7 +274,6 @@ class BaseFormSet(RenderableFormMixin):
if k[1] is None:
return (1, 0) # +infinity, larger than any number
return (0, k[1])
self._ordering.sort(key=compare_ordering_key)
# Return a list of form.cleaned_data dicts in the order specified by
# the form data.
@@ -315,11 +281,7 @@ class BaseFormSet(RenderableFormMixin):
@classmethod
def get_default_prefix(cls):
return "form"
@classmethod
def get_deletion_widget(cls):
return cls.deletion_widget
return 'form'
@classmethod
def get_ordering_widget(cls):
@@ -344,9 +306,8 @@ class BaseFormSet(RenderableFormMixin):
def total_error_count(self):
"""Return the number of errors across all forms in the formset."""
return len(self.non_form_errors()) + sum(
len(form_errors) for form_errors in self.errors
)
return len(self.non_form_errors()) +\
sum(len(form_errors) for form_errors in self.errors)
def _should_delete_form(self, form):
"""Return whether or not the form was marked for deletion."""
@@ -360,13 +321,10 @@ class BaseFormSet(RenderableFormMixin):
self.errors
# List comprehension ensures is_valid() is called for all forms.
# Forms due to be deleted shouldn't cause the formset to be invalid.
forms_valid = all(
[
form.is_valid()
for form in self.forms
if not (self.can_delete and self._should_delete_form(form))
]
)
forms_valid = all([
form.is_valid() for form in self.forms
if not (self.can_delete and self._should_delete_form(form))
])
return forms_valid and not self.non_form_errors()
def full_clean(self):
@@ -375,9 +333,7 @@ class BaseFormSet(RenderableFormMixin):
self._non_form_errors.
"""
self._errors = []
self._non_form_errors = self.error_class(
error_class="nonform", renderer=self.renderer
)
self._non_form_errors = self.error_class()
empty_forms_count = 0
if not self.is_bound: # Stop further processing.
@@ -385,14 +341,14 @@ class BaseFormSet(RenderableFormMixin):
if not self.management_form.is_valid():
error = ValidationError(
self.error_messages["missing_management_form"],
self.error_messages['missing_management_form'],
params={
"field_names": ", ".join(
'field_names': ', '.join(
self.management_form.add_prefix(field_name)
for field_name in self.management_form.errors
),
},
code="missing_management_form",
code='missing_management_form',
)
self._non_form_errors.append(error)
@@ -407,45 +363,24 @@ class BaseFormSet(RenderableFormMixin):
continue
self._errors.append(form_errors)
try:
if (
self.validate_max
and self.total_form_count() - len(self.deleted_forms) > self.max_num
) or self.management_form.cleaned_data[
TOTAL_FORM_COUNT
] > self.absolute_max:
raise ValidationError(
ngettext(
"Please submit at most %d form.",
"Please submit at most %d forms.",
self.max_num,
)
% self.max_num,
code="too_many_forms",
)
if (
self.validate_min
and self.total_form_count()
- len(self.deleted_forms)
- empty_forms_count
< self.min_num
):
raise ValidationError(
ngettext(
"Please submit at least %d form.",
"Please submit at least %d forms.",
self.min_num,
)
% self.min_num,
code="too_few_forms",
if (self.validate_max and
self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
raise ValidationError(ngettext(
"Please submit at most %d form.",
"Please submit at most %d forms.", self.max_num) % self.max_num,
code='too_many_forms',
)
if (self.validate_min and
self.total_form_count() - len(self.deleted_forms) - empty_forms_count < self.min_num):
raise ValidationError(ngettext(
"Please submit at least %d form.",
"Please submit at least %d forms.", self.min_num) % self.min_num,
code='too_few_forms')
# 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,
)
self._non_form_errors = self.error_class(e.error_list)
def clean(self):
"""
@@ -467,26 +402,22 @@ class BaseFormSet(RenderableFormMixin):
# Only pre-fill the ordering field for initial forms.
if index is not None and index < initial_form_count:
form.fields[ORDERING_FIELD_NAME] = IntegerField(
label=_("Order"),
label=_('Order'),
initial=index + 1,
required=False,
widget=self.get_ordering_widget(),
)
else:
form.fields[ORDERING_FIELD_NAME] = IntegerField(
label=_("Order"),
label=_('Order'),
required=False,
widget=self.get_ordering_widget(),
)
if self.can_delete and (self.can_delete_extra or index < initial_form_count):
form.fields[DELETION_FIELD_NAME] = BooleanField(
label=_("Delete"),
required=False,
widget=self.get_deletion_widget(),
)
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
def add_prefix(self, index):
return "%s-%s" % (self.prefix, index)
return '%s-%s' % (self.prefix, index)
def is_multipart(self):
"""
@@ -507,24 +438,29 @@ class BaseFormSet(RenderableFormMixin):
else:
return self.empty_form.media
def get_context(self):
return {"formset": self}
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_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_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 formset_factory(
form,
formset=BaseFormSet,
extra=1,
can_order=False,
can_delete=False,
max_num=None,
validate_max=False,
min_num=None,
validate_min=False,
absolute_max=None,
can_delete_extra=True,
renderer=None,
):
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
can_delete=False, max_num=None, validate_max=False,
min_num=None, validate_min=False, absolute_max=None,
can_delete_extra=True):
"""Return a FormSet for the given form class."""
if min_num is None:
min_num = DEFAULT_MIN_NUM
@@ -536,21 +472,22 @@ def formset_factory(
if absolute_max is None:
absolute_max = max_num + DEFAULT_MAX_NUM
if max_num > absolute_max:
raise ValueError("'absolute_max' must be greater or equal to 'max_num'.")
raise ValueError(
"'absolute_max' must be greater or equal to 'max_num'."
)
attrs = {
"form": form,
"extra": extra,
"can_order": can_order,
"can_delete": can_delete,
"can_delete_extra": can_delete_extra,
"min_num": min_num,
"max_num": max_num,
"absolute_max": absolute_max,
"validate_min": validate_min,
"validate_max": validate_max,
"renderer": renderer or get_default_renderer(),
'form': form,
'extra': extra,
'can_order': can_order,
'can_delete': can_delete,
'can_delete_extra': can_delete_extra,
'min_num': min_num,
'max_num': max_num,
'absolute_max': absolute_max,
'validate_min': validate_min,
'validate_max': validate_max,
}
return type(form.__name__ + "FormSet", (formset,), attrs)
return type(form.__name__ + 'FormSet', (formset,), attrs)
def all_valid(formsets):
@@ -1 +0,0 @@
{% for name, value in attrs.items() %}{% if value is not sameas False %} {{ name }}{% if value is not sameas True %}="{{ value }}"{% endif %}{% endif %}{% endfor %}
@@ -1 +0,0 @@
{% include "django/forms/table.html" %}
@@ -1 +0,0 @@
{% include "django/forms/errors/dict/ul.html" %}
@@ -1,3 +0,0 @@
{% for field, errors in errors %}* {{ field }}
{% for error in errors %} * {{ error }}
{% endfor %}{% endfor %}
@@ -1 +0,0 @@
{% if errors %}<ul class="{{ error_class }}">{% for field, error in errors %}<li>{{ field }}{{ error }}</li>{% endfor %}</ul>{% endif %}
@@ -1 +0,0 @@
{% include "django/forms/errors/list/ul.html" %}
@@ -1,2 +0,0 @@
{% for error in errors %}* {{ error }}
{% endfor %}
@@ -1 +0,0 @@
{% if errors %}<ul class="{{ error_class }}">{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_p() }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_table() }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_ul() }}{% endfor %}
@@ -1 +0,0 @@
{% if use_tag %}<label{% if attrs %}{% include 'django/forms/attrs.html' %}{% endif %}>{{ label }}</label>{% else %}{{ label }}{% endif %}
@@ -1,20 +0,0 @@
{{ 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 %}
@@ -1,29 +0,0 @@
{% 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 %}
@@ -1,24 +0,0 @@
{% 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 %}
@@ -1,5 +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>
{% set id = widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for widget in options %}
<li>{% include widget.template_name %}</li>{% endfor %}{% if group %}
</ul></li>{% endif %}{% endfor %}
</ul>
File diff suppressed because it is too large Load Diff
@@ -7,6 +7,8 @@ from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
ROOT = Path(__file__).parent
@functools.lru_cache()
def get_default_renderer():
@@ -16,7 +18,7 @@ def get_default_renderer():
class BaseRenderer:
def get_template(self, template_name):
raise NotImplementedError("subclasses must implement get_template()")
raise NotImplementedError('subclasses must implement get_template()')
def render(self, template_name, context, request=None):
template = self.get_template(template_name)
@@ -29,14 +31,12 @@ class EngineMixin:
@cached_property
def engine(self):
return self.backend(
{
"APP_DIRS": True,
"DIRS": [Path(__file__).parent / self.backend.app_dirname],
"NAME": "djangoforms",
"OPTIONS": {},
}
)
return self.backend({
'APP_DIRS': True,
'DIRS': [ROOT / self.backend.app_dirname],
'NAME': 'djangoforms',
'OPTIONS': {},
})
class DjangoTemplates(EngineMixin, BaseRenderer):
@@ -44,7 +44,6 @@ class DjangoTemplates(EngineMixin, BaseRenderer):
Load Django templates from the built-in widget templates in
django/forms/templates and from apps' 'templates' directory.
"""
backend = DjangoTemplates
@@ -53,11 +52,9 @@ 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
@@ -66,6 +63,5 @@ 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)
@@ -1 +0,0 @@
{% for name, value in attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}
@@ -1 +0,0 @@
{% include "django/forms/table.html" %}
@@ -1 +0,0 @@
{% include "django/forms/errors/dict/ul.html" %}
@@ -1,3 +0,0 @@
{% for field, errors in errors %}* {{ field }}
{% for error in errors %} * {{ error }}
{% endfor %}{% endfor %}
@@ -1 +0,0 @@
{% if errors %}<ul class="{{ error_class }}">{% for field, error in errors %}<li>{{ field }}{{ error }}</li>{% endfor %}</ul>{% endif %}
@@ -1 +0,0 @@
{% include "django/forms/errors/list/ul.html" %}
@@ -1,2 +0,0 @@
{% for error in errors %}* {{ error }}
{% endfor %}
@@ -1 +0,0 @@
{% if errors %}<ul class="{{ error_class }}">{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_p }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_table }}{% endfor %}
@@ -1 +0,0 @@
{{ formset.management_form }}{% for form in formset %}{{ form.as_ul }}{% endfor %}
@@ -1 +0,0 @@
{% if use_tag %}<label{% include 'django/forms/attrs.html' %}>{{ label }}</label>{% else %}{{ label }}{% endif %}
@@ -1,20 +0,0 @@
{{ 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 %}
@@ -1,29 +0,0 @@
{% 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 %}
@@ -1,24 +0,0 @@
{% 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 %}
@@ -1,5 +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 %}
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
<li>{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
</ul></li>{% endif %}{% endfor %}
</ul>{% endwith %}
+65 -98
View File
@@ -3,18 +3,16 @@ 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.html import escape, format_html_join
from django.utils.safestring import mark_safe
from django.utils.html import escape, format_html, format_html_join, html_safe
from django.utils.translation import gettext_lazy as _
def pretty_name(name):
"""Convert 'first_name' to 'First name'."""
if not name:
return ""
return name.replace("_", " ").capitalize()
return ''
return name.replace('_', ' ').capitalize()
def flatatt(attrs):
@@ -37,99 +35,59 @@ def flatatt(attrs):
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)
return (
format_html_join('', ' {}="{}"', sorted(key_value_attrs)) +
format_html_join('', ' {}', sorted(boolean_attrs))
)
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):
return mark_safe(
(renderer or self.renderer).render(
template_name or self.template_name,
context or self.get_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)
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):
@html_safe
class ErrorDict(dict):
"""
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",
}
def as_json(self, escape_html=False):
return json.dumps(self.get_json_data(escape_html))
def as_ul(self):
if not self:
return ''
return format_html(
'<ul class="errorlist">{}</ul>',
format_html_join('', '<li>{}{}</li>', self.items())
)
def as_text(self):
output = []
for field, errors in self.items():
output.append('* %s' % field)
output.append('\n'.join(' * %s' % e for e in errors))
return '\n'.join(output)
def __str__(self):
return self.as_ul()
class ErrorList(UserList, list, RenderableErrorMixin):
@html_safe
class ErrorList(UserList, list):
"""
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):
def __init__(self, initlist=None, error_class=None):
super().__init__(initlist)
if error_class is None:
self.error_class = "errorlist"
self.error_class = 'errorlist'
else:
self.error_class = "errorlist {}".format(error_class)
self.renderer = renderer or get_default_renderer()
self.error_class = 'errorlist {}'.format(error_class)
def as_data(self):
return ValidationError(self.data).error_list
@@ -143,19 +101,30 @@ class ErrorList(UserList, list, RenderableErrorMixin):
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 "",
}
)
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 as_json(self, escape_html=False):
return json.dumps(self.get_json_data(escape_html))
def as_ul(self):
if not self.data:
return ''
return format_html(
'<ul class="{}">{}</ul>',
self.error_class,
format_html_join('', '<li>{}</li>', ((e,) for e in self))
)
def as_text(self):
return '\n'.join('* %s' % e for e in self)
def __str__(self):
return self.as_ul()
def __repr__(self):
return repr(list(self))
@@ -184,7 +153,6 @@ class ErrorList(UserList, list, RenderableErrorMixin):
# Utilities for time zone support in DateTimeField et al.
def from_current_timezone(value):
"""
When time zone support is enabled, convert naive datetimes
@@ -193,20 +161,19 @@ def from_current_timezone(value):
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.")
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},
_('%(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
File diff suppressed because it is too large Load Diff