测试gitnore
This commit is contained in:
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
+5
-5
@@ -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 %}
|
||||
|
||||
@@ -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 couldn’t 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 couldn’t 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
Reference in New Issue
Block a user