summaryrefslogtreecommitdiff
path: root/django/forms
diff options
context:
space:
mode:
Diffstat (limited to 'django/forms')
-rw-r--r--django/forms/boundfield.py113
-rw-r--r--django/forms/fields.py437
-rw-r--r--django/forms/forms.py175
-rw-r--r--django/forms/formsets.py238
-rw-r--r--django/forms/models.py727
-rw-r--r--django/forms/renderers.py20
-rw-r--r--django/forms/utils.py79
-rw-r--r--django/forms/widgets.py514
8 files changed, 1414 insertions, 889 deletions
diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py
index 62d1823d57..e83160645e 100644
--- a/django/forms/boundfield.py
+++ b/django/forms/boundfield.py
@@ -7,12 +7,13 @@ from django.utils.functional import cached_property
from django.utils.html import format_html, html_safe
from django.utils.translation import gettext_lazy as _
-__all__ = ('BoundField',)
+__all__ = ("BoundField",)
@html_safe
class BoundField:
"A Field plus data"
+
def __init__(self, form, field, name):
self.form = form
self.field = field
@@ -24,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."""
@@ -41,12 +42,14 @@ 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):
@@ -64,7 +67,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]
@@ -74,7 +77,9 @@ 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(renderer=self.form.renderer)
+ )
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
@@ -87,8 +92,10 @@ 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(),
@@ -134,7 +141,8 @@ class BoundField:
if field.show_hidden_initial:
hidden_widget = field.hidden_widget()
initial_value = self.form._widget_data_value(
- hidden_widget, self.html_initial_name,
+ hidden_widget,
+ self.html_initial_name,
)
try:
initial_value = field.to_python(initial_value)
@@ -157,31 +165,34 @@ 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
+ attrs["class"] = self.form.required_css_class
context = {
- 'field': self,
- 'label': contents,
- 'attrs': attrs,
- 'use_tag': bool(id_),
- 'tag': tag or 'label',
+ "field": self,
+ "label": contents,
+ "attrs": attrs,
+ "use_tag": bool(id_),
+ "tag": tag or "label",
}
return self.form.render(self.form.template_name_label, context)
@@ -195,20 +206,20 @@ class BoundField:
label_suffix overrides the form's label_suffix.
"""
- return self.label_tag(contents, attrs, label_suffix, tag='legend')
+ return self.label_tag(contents, attrs, label_suffix, tag="legend")
def css_classes(self, extra_classes=None):
"""
Return a string of space-separated CSS classes for this field.
"""
- if hasattr(extra_classes, 'split'):
+ 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):
@@ -222,11 +233,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):
@@ -236,7 +247,7 @@ 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
@@ -246,25 +257,34 @@ class BoundField:
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:
+ 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)
+ 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
+ subwidget.attrs["required"] = (
+ subwidget.use_required_attribute(self.initial)
+ and subfield.required
+ )
else:
- attrs['required'] = True
+ 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
@@ -281,6 +301,7 @@ class BoundWidget:
</label>
{% endfor %}
"""
+
def __init__(self, parent_widget, data, renderer):
self.parent_widget = parent_widget
self.data = data
@@ -290,19 +311,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 self.data["attrs"].get("id")
@property
def choice_label(self):
- return self.data['label']
+ return self.data["label"]
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 65d6a9ec82..a7031936dd 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -19,45 +19,94 @@ 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 _, ngettext_lazy
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import 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
@@ -109,7 +158,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
@@ -125,7 +174,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:
@@ -135,7 +184,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:
@@ -180,15 +229,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):
@@ -208,7 +257,9 @@ 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
@@ -234,25 +285,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:
@@ -272,24 +323,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):
@@ -305,7 +356,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):
@@ -313,21 +364,29 @@ 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))
@@ -346,7 +405,7 @@ class DecimalField(IntegerField):
try:
value = Decimal(str(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):
@@ -355,26 +414,25 @@ class DecimalField(IntegerField):
return
if not value.is_finite():
raise ValidationError(
- self.error_messages['invalid'],
- code='invalid',
- params={'value': value},
+ 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:
@@ -388,17 +446,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):
@@ -420,10 +478,8 @@ 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):
"""
@@ -442,15 +498,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):
@@ -473,7 +529,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)
@@ -484,8 +540,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):
@@ -501,12 +557,15 @@ 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
@@ -515,7 +574,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)
@@ -526,7 +585,10 @@ 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)
@@ -545,14 +607,17 @@ 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):
@@ -569,22 +634,26 @@ 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:
@@ -612,7 +681,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."
),
@@ -631,13 +700,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
@@ -654,24 +723,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()]
@@ -679,7 +748,6 @@ 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
@@ -690,19 +758,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))
@@ -719,7 +787,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)
@@ -727,7 +795,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:
@@ -742,6 +810,7 @@ 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):
@@ -753,9 +822,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
@@ -775,7 +844,9 @@ 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):
@@ -806,7 +877,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):
@@ -814,9 +885,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):
@@ -835,7 +906,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)
@@ -850,9 +921,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
@@ -865,28 +936,32 @@ 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):
@@ -906,7 +981,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):
@@ -922,9 +997,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
@@ -936,13 +1011,14 @@ 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
@@ -980,17 +1056,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:
@@ -1024,11 +1100,13 @@ 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]
@@ -1039,13 +1117,15 @@ 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))
@@ -1071,13 +1151,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)
@@ -1092,8 +1172,16 @@ 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)
@@ -1115,7 +1203,7 @@ 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)
@@ -1124,12 +1212,12 @@ class FilePathField(ChoiceField):
choices = []
with os.scandir(self.path) as entries:
for f in entries:
- if f.name == '__pycache__':
+ 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))):
+ 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)
@@ -1141,22 +1229,26 @@ 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)
@@ -1165,25 +1257,31 @@ 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
@@ -1200,7 +1298,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):
@@ -1216,7 +1314,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
@@ -1230,7 +1328,7 @@ class JSONString(str):
class JSONField(CharField):
default_error_messages = {
- 'invalid': _('Enter a valid JSON.'),
+ "invalid": _("Enter a valid JSON."),
}
widget = Textarea
@@ -1250,9 +1348,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)
@@ -1279,7 +1377,6 @@ 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
)
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 589b4693fd..952b974130 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -19,15 +19,17 @@ 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)
}
@@ -37,7 +39,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.
@@ -58,20 +60,32 @@ class BaseForm(RenderableFormMixin):
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'
+ 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
@@ -81,7 +95,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.
@@ -99,8 +113,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
@@ -141,11 +155,11 @@ class BaseForm(RenderableFormMixin):
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):
@@ -168,10 +182,11 @@ class BaseForm(RenderableFormMixin):
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)
@@ -196,11 +211,11 @@ 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.
@@ -208,11 +223,13 @@ class BaseForm(RenderableFormMixin):
# 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.',
+ "django.forms.BaseForm._html_output() is deprecated. "
+ "Please use .render() and .get_context() instead.",
RemovedInDjango50Warning,
stacklevel=2,
)
@@ -222,13 +239,17 @@ class BaseForm(RenderableFormMixin):
for name, bf in self._bound_items():
field = bf.field
- html_class_attr = ''
+ html_class_attr = ""
bf_errors = self.error_class(bf.errors)
if bf.is_hidden:
if bf_errors:
top_errors.extend(
- [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)}
- for e in bf_errors])
+ [
+ _("(Hidden 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
@@ -242,30 +263,33 @@ 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
@@ -275,22 +299,22 @@ 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 = []
@@ -301,7 +325,8 @@ class BaseForm(RenderableFormMixin):
if bf.is_hidden:
if bf_errors:
top_errors += [
- _('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)}
+ _("(Hidden field %(name)s) %(error)s")
+ % {"name": name, "error": str(e)}
for e in bf_errors
]
hidden_fields.append(bf)
@@ -310,18 +335,18 @@ class BaseForm(RenderableFormMixin):
# 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.',
+ 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,
+ "form": self,
+ "fields": fields,
+ "hidden_fields": hidden_fields,
+ "errors": top_errors,
}
def non_field_errors(self):
@@ -332,7 +357,7 @@ class BaseForm(RenderableFormMixin):
"""
return self.errors.get(
NON_FIELD_ERRORS,
- self.error_class(error_class='nonfield', renderer=self.renderer),
+ self.error_class(error_class="nonfield", renderer=self.renderer),
)
def add_error(self, field, error):
@@ -358,7 +383,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` "
@@ -373,9 +398,13 @@ 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", renderer=self.renderer
+ )
else:
self._errors[field] = self.error_class(renderer=self.renderer)
self._errors[field].extend(error_list)
@@ -384,8 +413,8 @@ class BaseForm(RenderableFormMixin):
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):
@@ -415,8 +444,8 @@ class BaseForm(RenderableFormMixin):
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)
@@ -493,8 +522,10 @@ class BaseForm(RenderableFormMixin):
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):
+ if (
+ isinstance(value, (datetime.datetime, datetime.time))
+ and not field.widget.supports_microseconds
+ ):
value = value.replace(microsecond=0)
return value
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 75b0646512..e5807e8688 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -5,17 +5,18 @@ from django.forms.renderers import get_default_renderer
from django.forms.utils import ErrorList, RenderableFormMixin
from django.forms.widgets import CheckboxInput, HiddenInput, NumberInput
from django.utils.functional import cached_property
-from django.utils.translation import gettext_lazy as _, ngettext
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import 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
@@ -30,6 +31,7 @@ class ManagementForm(Form):
new forms via JavaScript, you should increment the count field of this form
as well.
"""
+
TOTAL_FORMS = IntegerField(widget=HiddenInput)
INITIAL_FORMS = IntegerField(widget=HiddenInput)
# MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of the
@@ -51,22 +53,31 @@ class BaseFormSet(RenderableFormMixin):
"""
A collection of instances of the same Form class.
"""
+
deletion_widget = CheckboxInput
ordering_widget = NumberInput
default_error_messages = {
- 'missing_management_form': _(
- 'ManagementForm data is missing or has been tampered with. Missing fields: '
- '%(field_names)s. You may need to file a bug report if the issue persists.'
+ "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'
+ 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
@@ -80,7 +91,7 @@ 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
@@ -105,14 +116,14 @@ class BaseFormSet(RenderableFormMixin):
def __repr__(self):
if self._errors is None:
- is_valid = 'Unknown'
+ is_valid = "Unknown"
else:
is_valid = (
- self.is_bound and
- not self._non_form_errors and
- not any(form_errors for form_errors in self._errors)
+ self.is_bound
+ and not self._non_form_errors
+ and not any(form_errors for form_errors in self._errors)
)
- return '<%s: bound=%s valid=%s total_forms=%s>' % (
+ return "<%s: bound=%s valid=%s total_forms=%s>" % (
self.__class__.__qualname__,
self.is_bound,
is_valid,
@@ -123,7 +134,12 @@ 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,
+ renderer=self.renderer,
+ )
form.full_clean()
else:
form = ManagementForm(
@@ -146,7 +162,9 @@ 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
@@ -188,27 +206,27 @@ 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,
+ "renderer": self.renderer,
}
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)
@@ -217,18 +235,18 @@ 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),
@@ -243,7 +261,9 @@ 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
@@ -253,7 +273,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
@@ -270,12 +290,14 @@ 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
@@ -295,6 +317,7 @@ 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.
@@ -302,7 +325,7 @@ class BaseFormSet(RenderableFormMixin):
@classmethod
def get_default_prefix(cls):
- return 'form'
+ return "form"
@classmethod
def get_deletion_widget(cls):
@@ -331,8 +354,9 @@ 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."""
@@ -346,10 +370,13 @@ 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):
@@ -358,7 +385,9 @@ 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(
+ error_class="nonform", renderer=self.renderer
+ )
empty_forms_count = 0
if not self.is_bound: # Stop further processing.
@@ -366,14 +395,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)
@@ -388,26 +417,43 @@ 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_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_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',
+ error_class="nonform",
renderer=self.renderer,
)
@@ -431,26 +477,26 @@ 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'),
+ label=_("Delete"),
required=False,
widget=self.get_deletion_widget(),
)
def add_prefix(self, index):
- return '%s-%s' % (self.prefix, index)
+ return "%s-%s" % (self.prefix, index)
def is_multipart(self):
"""
@@ -472,13 +518,23 @@ class BaseFormSet(RenderableFormMixin):
return self.empty_form.media
def get_context(self):
- return {'formset': self}
+ return {"formset": self}
-def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
- can_delete=False, max_num=None, validate_max=False,
- min_num=None, validate_min=False, absolute_max=None,
- can_delete_extra=True, renderer=None):
+def formset_factory(
+ form,
+ formset=BaseFormSet,
+ extra=1,
+ can_order=False,
+ can_delete=False,
+ max_num=None,
+ validate_max=False,
+ min_num=None,
+ validate_min=False,
+ absolute_max=None,
+ can_delete_extra=True,
+ renderer=None,
+):
"""Return a FormSet for the given form class."""
if min_num is None:
min_num = DEFAULT_MIN_NUM
@@ -490,23 +546,21 @@ def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
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,
+ "renderer": renderer or get_default_renderer(),
}
- return type(form.__name__ + 'FormSet', (formset,), attrs)
+ return type(form.__name__ + "FormSet", (formset,), attrs)
def all_valid(formsets):
diff --git a/django/forms/models.py b/django/forms/models.py
index 19a5cb142a..a55af8eeb6 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -5,26 +5,41 @@ and database field objects.
from itertools import chain
from django.core.exceptions import (
- NON_FIELD_ERRORS, FieldError, ImproperlyConfigured, ValidationError,
+ NON_FIELD_ERRORS,
+ FieldError,
+ ImproperlyConfigured,
+ ValidationError,
)
from django.forms.fields import ChoiceField, Field
from django.forms.forms import BaseForm, DeclarativeFieldsMetaclass
from django.forms.formsets import BaseFormSet, formset_factory
from django.forms.utils import ErrorList
from django.forms.widgets import (
- HiddenInput, MultipleHiddenInput, RadioSelect, SelectMultiple,
+ HiddenInput,
+ MultipleHiddenInput,
+ RadioSelect,
+ SelectMultiple,
)
from django.utils.text import capfirst, get_text_list
-from django.utils.translation import gettext, gettext_lazy as _
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
__all__ = (
- 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
- 'ModelChoiceField', 'ModelMultipleChoiceField', 'ALL_FIELDS',
- 'BaseModelFormSet', 'modelformset_factory', 'BaseInlineFormSet',
- 'inlineformset_factory', 'modelform_factory',
+ "ModelForm",
+ "BaseModelForm",
+ "model_to_dict",
+ "fields_for_model",
+ "ModelChoiceField",
+ "ModelMultipleChoiceField",
+ "ALL_FIELDS",
+ "BaseModelFormSet",
+ "modelformset_factory",
+ "BaseInlineFormSet",
+ "inlineformset_factory",
+ "modelform_factory",
)
-ALL_FIELDS = '__all__'
+ALL_FIELDS = "__all__"
def construct_instance(form, instance, fields=None, exclude=None):
@@ -33,13 +48,17 @@ def construct_instance(form, instance, fields=None, exclude=None):
``cleaned_data``, but do not save the returned instance to the database.
"""
from django.db import models
+
opts = instance._meta
cleaned_data = form.cleaned_data
file_field_list = []
for f in opts.fields:
- if not f.editable or isinstance(f, models.AutoField) \
- or f.name not in cleaned_data:
+ if (
+ not f.editable
+ or isinstance(f, models.AutoField)
+ or f.name not in cleaned_data
+ ):
continue
if fields is not None and f.name not in fields:
continue
@@ -48,9 +67,11 @@ def construct_instance(form, instance, fields=None, exclude=None):
# Leave defaults for fields that aren't in POST data, except for
# checkbox inputs because they don't appear in POST data if not checked.
if (
- f.has_default() and
- form[f.name].field.widget.value_omitted_from_data(form.data, form.files, form.add_prefix(f.name)) and
- cleaned_data.get(f.name) in form[f.name].field.empty_values
+ f.has_default()
+ and form[f.name].field.widget.value_omitted_from_data(
+ form.data, form.files, form.add_prefix(f.name)
+ )
+ and cleaned_data.get(f.name) in form[f.name].field.empty_values
):
continue
# Defer saving file-type fields until after the other fields, so a
@@ -68,6 +89,7 @@ def construct_instance(form, instance, fields=None, exclude=None):
# ModelForms #################################################################
+
def model_to_dict(instance, fields=None, exclude=None):
"""
Return a dict containing the data in ``instance`` suitable for passing as
@@ -83,7 +105,7 @@ def model_to_dict(instance, fields=None, exclude=None):
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
- if not getattr(f, 'editable', False):
+ if not getattr(f, "editable", False):
continue
if fields is not None and f.name not in fields:
continue
@@ -96,23 +118,34 @@ def model_to_dict(instance, fields=None, exclude=None):
def apply_limit_choices_to_to_formfield(formfield):
"""Apply limit_choices_to to the formfield's queryset if needed."""
from django.db.models import Exists, OuterRef, Q
- if hasattr(formfield, 'queryset') and hasattr(formfield, 'get_limit_choices_to'):
+
+ if hasattr(formfield, "queryset") and hasattr(formfield, "get_limit_choices_to"):
limit_choices_to = formfield.get_limit_choices_to()
if limit_choices_to:
complex_filter = limit_choices_to
if not isinstance(complex_filter, Q):
complex_filter = Q(**limit_choices_to)
- complex_filter &= Q(pk=OuterRef('pk'))
+ complex_filter &= Q(pk=OuterRef("pk"))
# Use Exists() to avoid potential duplicates.
formfield.queryset = formfield.queryset.filter(
Exists(formfield.queryset.model._base_manager.filter(complex_filter)),
)
-def fields_for_model(model, fields=None, exclude=None, widgets=None,
- formfield_callback=None, localized_fields=None,
- labels=None, help_texts=None, error_messages=None,
- field_classes=None, *, apply_limit_choices_to=True):
+def fields_for_model(
+ model,
+ fields=None,
+ exclude=None,
+ widgets=None,
+ formfield_callback=None,
+ localized_fields=None,
+ labels=None,
+ help_texts=None,
+ error_messages=None,
+ field_classes=None,
+ *,
+ apply_limit_choices_to=True,
+):
"""
Return a dictionary containing form fields for the given model.
@@ -148,14 +181,22 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
opts = model._meta
# Avoid circular import
from django.db.models import Field as ModelField
- sortable_private_fields = [f for f in opts.private_fields if isinstance(f, ModelField)]
- for f in sorted(chain(opts.concrete_fields, sortable_private_fields, opts.many_to_many)):
- if not getattr(f, 'editable', False):
- if (fields is not None and f.name in fields and
- (exclude is None or f.name not in exclude)):
+
+ sortable_private_fields = [
+ f for f in opts.private_fields if isinstance(f, ModelField)
+ ]
+ for f in sorted(
+ chain(opts.concrete_fields, sortable_private_fields, opts.many_to_many)
+ ):
+ if not getattr(f, "editable", False):
+ if (
+ fields is not None
+ and f.name in fields
+ and (exclude is None or f.name not in exclude)
+ ):
raise FieldError(
- "'%s' cannot be specified for %s model form as it is a non-editable field" % (
- f.name, model.__name__)
+ "'%s' cannot be specified for %s model form as it is a non-editable field"
+ % (f.name, model.__name__)
)
continue
if fields is not None and f.name not in fields:
@@ -165,22 +206,24 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
kwargs = {}
if widgets and f.name in widgets:
- kwargs['widget'] = widgets[f.name]
- if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields):
- kwargs['localize'] = True
+ kwargs["widget"] = widgets[f.name]
+ if localized_fields == ALL_FIELDS or (
+ localized_fields and f.name in localized_fields
+ ):
+ kwargs["localize"] = True
if labels and f.name in labels:
- kwargs['label'] = labels[f.name]
+ kwargs["label"] = labels[f.name]
if help_texts and f.name in help_texts:
- kwargs['help_text'] = help_texts[f.name]
+ kwargs["help_text"] = help_texts[f.name]
if error_messages and f.name in error_messages:
- kwargs['error_messages'] = error_messages[f.name]
+ kwargs["error_messages"] = error_messages[f.name]
if field_classes and f.name in field_classes:
- kwargs['form_class'] = field_classes[f.name]
+ kwargs["form_class"] = field_classes[f.name]
if formfield_callback is None:
formfield = f.formfield(**kwargs)
elif not callable(formfield_callback):
- raise TypeError('formfield_callback must be a function or callable')
+ raise TypeError("formfield_callback must be a function or callable")
else:
formfield = formfield_callback(f, **kwargs)
@@ -192,7 +235,8 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
ignored.append(f.name)
if fields:
field_dict = {
- f: field_dict.get(f) for f in fields
+ f: field_dict.get(f)
+ for f in fields
if (not exclude or f not in exclude) and f not in ignored
}
return field_dict
@@ -200,46 +244,49 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
class ModelFormOptions:
def __init__(self, options=None):
- self.model = getattr(options, 'model', None)
- self.fields = getattr(options, 'fields', None)
- self.exclude = getattr(options, 'exclude', None)
- self.widgets = getattr(options, 'widgets', None)
- self.localized_fields = getattr(options, 'localized_fields', None)
- self.labels = getattr(options, 'labels', None)
- self.help_texts = getattr(options, 'help_texts', None)
- self.error_messages = getattr(options, 'error_messages', None)
- self.field_classes = getattr(options, 'field_classes', None)
+ self.model = getattr(options, "model", None)
+ self.fields = getattr(options, "fields", None)
+ self.exclude = getattr(options, "exclude", None)
+ self.widgets = getattr(options, "widgets", None)
+ self.localized_fields = getattr(options, "localized_fields", None)
+ self.labels = getattr(options, "labels", None)
+ self.help_texts = getattr(options, "help_texts", None)
+ self.error_messages = getattr(options, "error_messages", None)
+ self.field_classes = getattr(options, "field_classes", None)
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
def __new__(mcs, name, bases, attrs):
base_formfield_callback = None
for b in bases:
- if hasattr(b, 'Meta') and hasattr(b.Meta, 'formfield_callback'):
+ if hasattr(b, "Meta") and hasattr(b.Meta, "formfield_callback"):
base_formfield_callback = b.Meta.formfield_callback
break
- formfield_callback = attrs.pop('formfield_callback', base_formfield_callback)
+ formfield_callback = attrs.pop("formfield_callback", base_formfield_callback)
new_class = super().__new__(mcs, name, bases, attrs)
if bases == (BaseModelForm,):
return new_class
- opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
+ opts = new_class._meta = ModelFormOptions(getattr(new_class, "Meta", None))
# We check if a string was passed to `fields` or `exclude`,
# which is likely to be a mistake where the user typed ('foo') instead
# of ('foo',)
- for opt in ['fields', 'exclude', 'localized_fields']:
+ for opt in ["fields", "exclude", "localized_fields"]:
value = getattr(opts, opt)
if isinstance(value, str) and value != ALL_FIELDS:
- msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
- "Did you mean to type: ('%(value)s',)?" % {
- 'model': new_class.__name__,
- 'opt': opt,
- 'value': value,
- })
+ msg = (
+ "%(model)s.Meta.%(opt)s cannot be a string. "
+ "Did you mean to type: ('%(value)s',)?"
+ % {
+ "model": new_class.__name__,
+ "opt": opt,
+ "value": value,
+ }
+ )
raise TypeError(msg)
if opts.model:
@@ -257,9 +304,16 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
opts.fields = None
fields = fields_for_model(
- opts.model, opts.fields, opts.exclude, opts.widgets,
- formfield_callback, opts.localized_fields, opts.labels,
- opts.help_texts, opts.error_messages, opts.field_classes,
+ opts.model,
+ opts.fields,
+ opts.exclude,
+ opts.widgets,
+ formfield_callback,
+ opts.localized_fields,
+ opts.labels,
+ opts.help_texts,
+ opts.error_messages,
+ opts.field_classes,
# limit_choices_to will be applied during ModelForm.__init__().
apply_limit_choices_to=False,
)
@@ -268,9 +322,8 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
none_model_fields = {k for k, v in fields.items() if not v}
missing_fields = none_model_fields.difference(new_class.declared_fields)
if missing_fields:
- message = 'Unknown field(s) (%s) specified for %s'
- message = message % (', '.join(missing_fields),
- opts.model.__name__)
+ message = "Unknown field(s) (%s) specified for %s"
+ message = message % (", ".join(missing_fields), opts.model.__name__)
raise FieldError(message)
# Override default model fields with any custom declared ones
# (plus, include all the other declared fields).
@@ -284,13 +337,23 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
class BaseModelForm(BaseForm):
- def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- initial=None, error_class=ErrorList, label_suffix=None,
- empty_permitted=False, instance=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,
+ instance=None,
+ use_required_attribute=None,
+ renderer=None,
+ ):
opts = self._meta
if opts.model is None:
- raise ValueError('ModelForm has no model class specified.')
+ raise ValueError("ModelForm has no model class specified.")
if instance is None:
# if we didn't get an instance, instantiate a new one
self.instance = opts.model()
@@ -306,8 +369,15 @@ class BaseModelForm(BaseForm):
# super will stop validate_unique from being called.
self._validate_unique = False
super().__init__(
- data, files, auto_id, prefix, object_data, error_class,
- label_suffix, empty_permitted, use_required_attribute=use_required_attribute,
+ data,
+ files,
+ auto_id,
+ prefix,
+ object_data,
+ error_class,
+ label_suffix,
+ empty_permitted,
+ use_required_attribute=use_required_attribute,
renderer=renderer,
)
for formfield in self.fields.values():
@@ -350,7 +420,11 @@ class BaseModelForm(BaseForm):
else:
form_field = self.fields[field]
field_value = self.cleaned_data.get(field)
- if not f.blank and not form_field.required and field_value in form_field.empty_values:
+ if (
+ not f.blank
+ and not form_field.required
+ and field_value in form_field.empty_values
+ ):
exclude.append(f.name)
return exclude
@@ -365,14 +439,17 @@ class BaseModelForm(BaseForm):
# Allow the model generated by construct_instance() to raise
# ValidationError and have them handled in the same way as others.
- if hasattr(errors, 'error_dict'):
+ if hasattr(errors, "error_dict"):
error_dict = errors.error_dict
else:
error_dict = {NON_FIELD_ERRORS: errors}
for field, messages in error_dict.items():
- if (field == NON_FIELD_ERRORS and opts.error_messages and
- NON_FIELD_ERRORS in opts.error_messages):
+ if (
+ field == NON_FIELD_ERRORS
+ and opts.error_messages
+ and NON_FIELD_ERRORS in opts.error_messages
+ ):
error_messages = opts.error_messages[NON_FIELD_ERRORS]
elif field in self.fields:
error_messages = self.fields[field].error_messages
@@ -380,8 +457,10 @@ class BaseModelForm(BaseForm):
continue
for message in messages:
- if (isinstance(message, ValidationError) and
- message.code in error_messages):
+ if (
+ isinstance(message, ValidationError)
+ and message.code in error_messages
+ ):
message.message = error_messages[message.code]
self.add_error(None, errors)
@@ -403,7 +482,9 @@ class BaseModelForm(BaseForm):
exclude.append(name)
try:
- self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
+ self.instance = construct_instance(
+ self, self.instance, opts.fields, opts.exclude
+ )
except ValidationError as e:
self._update_errors(e)
@@ -439,7 +520,7 @@ class BaseModelForm(BaseForm):
# private_fields here. (GenericRelation was previously a fake
# m2m field).
for f in chain(opts.many_to_many, opts.private_fields):
- if not hasattr(f, 'save_form_data'):
+ if not hasattr(f, "save_form_data"):
continue
if fields and f.name not in fields:
continue
@@ -456,9 +537,10 @@ class BaseModelForm(BaseForm):
"""
if self.errors:
raise ValueError(
- "The %s could not be %s because the data didn't validate." % (
+ "The %s could not be %s because the data didn't validate."
+ % (
self.instance._meta.object_name,
- 'created' if self.instance._state.adding else 'changed',
+ "created" if self.instance._state.adding else "changed",
)
)
if commit:
@@ -478,10 +560,19 @@ class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
pass
-def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
- formfield_callback=None, widgets=None, localized_fields=None,
- labels=None, help_texts=None, error_messages=None,
- field_classes=None):
+def modelform_factory(
+ model,
+ form=ModelForm,
+ fields=None,
+ exclude=None,
+ formfield_callback=None,
+ widgets=None,
+ localized_fields=None,
+ labels=None,
+ help_texts=None,
+ error_messages=None,
+ field_classes=None,
+):
"""
Return a ModelForm containing form fields for the given model. You can
optionally pass a `form` argument to use as a starting point for
@@ -517,41 +608,37 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
# inner class.
# Build up a list of attributes that the Meta object will have.
- attrs = {'model': model}
+ attrs = {"model": model}
if fields is not None:
- attrs['fields'] = fields
+ attrs["fields"] = fields
if exclude is not None:
- attrs['exclude'] = exclude
+ attrs["exclude"] = exclude
if widgets is not None:
- attrs['widgets'] = widgets
+ attrs["widgets"] = widgets
if localized_fields is not None:
- attrs['localized_fields'] = localized_fields
+ attrs["localized_fields"] = localized_fields
if labels is not None:
- attrs['labels'] = labels
+ attrs["labels"] = labels
if help_texts is not None:
- attrs['help_texts'] = help_texts
+ attrs["help_texts"] = help_texts
if error_messages is not None:
- attrs['error_messages'] = error_messages
+ attrs["error_messages"] = error_messages
if field_classes is not None:
- attrs['field_classes'] = field_classes
+ attrs["field_classes"] = field_classes
# If parent form class already has an inner Meta, the Meta we're
# creating needs to inherit from the parent's inner meta.
- bases = (form.Meta,) if hasattr(form, 'Meta') else ()
- Meta = type('Meta', bases, attrs)
+ bases = (form.Meta,) if hasattr(form, "Meta") else ()
+ Meta = type("Meta", bases, attrs)
if formfield_callback:
Meta.formfield_callback = staticmethod(formfield_callback)
# Give this new form class a reasonable name.
- class_name = model.__name__ + 'Form'
+ class_name = model.__name__ + "Form"
# Class attributes for the new form class.
- form_class_attrs = {
- 'Meta': Meta,
- 'formfield_callback': formfield_callback
- }
+ form_class_attrs = {"Meta": Meta, "formfield_callback": formfield_callback}
- if (getattr(Meta, 'fields', None) is None and
- getattr(Meta, 'exclude', None) is None):
+ if getattr(Meta, "fields", None) is None and getattr(Meta, "exclude", None) is None:
raise ImproperlyConfigured(
"Calling modelform_factory without defining 'fields' or "
"'exclude' explicitly is prohibited."
@@ -563,20 +650,39 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
# ModelFormSets ##############################################################
+
class BaseModelFormSet(BaseFormSet):
"""
A ``FormSet`` for editing a queryset and/or adding new objects to it.
"""
+
model = None
# Set of fields that must be unique among forms of this set.
unique_fields = set()
- def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- queryset=None, *, initial=None, **kwargs):
+ def __init__(
+ self,
+ data=None,
+ files=None,
+ auto_id="id_%s",
+ prefix=None,
+ queryset=None,
+ *,
+ initial=None,
+ **kwargs,
+ ):
self.queryset = queryset
self.initial_extra = initial
- super().__init__(**{'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix, **kwargs})
+ super().__init__(
+ **{
+ "data": data,
+ "files": files,
+ "auto_id": auto_id,
+ "prefix": prefix,
+ **kwargs,
+ }
+ )
def initial_form_count(self):
"""Return the number of forms that are required in this FormSet."""
@@ -585,7 +691,7 @@ class BaseModelFormSet(BaseFormSet):
return super().initial_form_count()
def _existing_object(self, pk):
- if not hasattr(self, '_object_dict'):
+ if not hasattr(self, "_object_dict"):
self._object_dict = {o.pk: o for o in self.get_queryset()}
return self._object_dict.get(pk)
@@ -602,7 +708,7 @@ class BaseModelFormSet(BaseFormSet):
pk_required = i < self.initial_form_count()
if pk_required:
if self.is_bound:
- pk_key = '%s-%s' % (self.add_prefix(i), self.model._meta.pk.name)
+ pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
try:
pk = self.data[pk_key]
except KeyError:
@@ -618,13 +724,13 @@ class BaseModelFormSet(BaseFormSet):
# user may have tampered with POST data.
pass
else:
- kwargs['instance'] = self._existing_object(pk)
+ kwargs["instance"] = self._existing_object(pk)
else:
- kwargs['instance'] = self.get_queryset()[i]
+ kwargs["instance"] = self.get_queryset()[i]
elif self.initial_extra:
# Set initial values for extra forms
try:
- kwargs['initial'] = self.initial_extra[i - self.initial_form_count()]
+ kwargs["initial"] = self.initial_extra[i - self.initial_form_count()]
except IndexError:
pass
form = super()._construct_form(i, **kwargs)
@@ -633,7 +739,7 @@ class BaseModelFormSet(BaseFormSet):
return form
def get_queryset(self):
- if not hasattr(self, '_queryset'):
+ if not hasattr(self, "_queryset"):
if self.queryset is not None:
qs = self.queryset
else:
@@ -675,6 +781,7 @@ class BaseModelFormSet(BaseFormSet):
def save_m2m():
for form in self.saved_forms:
form.save_m2m()
+
self.save_m2m = save_m2m
if self.edit_only:
return self.save_existing_objects(commit)
@@ -691,10 +798,16 @@ class BaseModelFormSet(BaseFormSet):
all_unique_checks = set()
all_date_checks = set()
forms_to_delete = self.deleted_forms
- valid_forms = [form for form in self.forms if form.is_valid() and form not in forms_to_delete]
+ valid_forms = [
+ form
+ for form in self.forms
+ if form.is_valid() and form not in forms_to_delete
+ ]
for form in valid_forms:
exclude = form._get_validation_exclusions()
- unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
+ unique_checks, date_checks = form.instance._get_unique_checks(
+ exclude=exclude
+ )
all_unique_checks.update(unique_checks)
all_date_checks.update(date_checks)
@@ -706,14 +819,15 @@ class BaseModelFormSet(BaseFormSet):
# Get the data for the set of fields that must be unique among the forms.
row_data = (
field if field in self.unique_fields else form.cleaned_data[field]
- for field in unique_check if field in form.cleaned_data
+ for field in unique_check
+ if field in form.cleaned_data
)
# Reduce Model instances to their primary key values
row_data = tuple(
- d._get_pk_val() if hasattr(d, '_get_pk_val')
+ d._get_pk_val() if hasattr(d, "_get_pk_val")
# Prevent "unhashable type: list" errors later on.
- else tuple(d) if isinstance(d, list)
- else d for d in row_data
+ else tuple(d) if isinstance(d, list) else d
+ for d in row_data
)
if row_data and None not in row_data:
# if we've already seen it then we have a uniqueness failure
@@ -737,10 +851,13 @@ class BaseModelFormSet(BaseFormSet):
uclass, lookup, field, unique_for = date_check
for form in valid_forms:
# see if we have data for both fields
- if (form.cleaned_data and form.cleaned_data[field] is not None and
- form.cleaned_data[unique_for] is not None):
+ if (
+ form.cleaned_data
+ and form.cleaned_data[field] is not None
+ and form.cleaned_data[unique_for] is not None
+ ):
# if it's a date lookup we need to get the data for all the fields
- if lookup == 'date':
+ if lookup == "date":
date = form.cleaned_data[unique_for]
date_data = (date.year, date.month, date.day)
# otherwise it's just the attribute on the date/datetime
@@ -771,7 +888,9 @@ class BaseModelFormSet(BaseFormSet):
"field": unique_check[0],
}
else:
- return gettext("Please correct the duplicate data for %(field)s, which must be unique.") % {
+ return gettext(
+ "Please correct the duplicate data for %(field)s, which must be unique."
+ ) % {
"field": get_text_list(unique_check, _("and")),
}
@@ -780,9 +899,9 @@ class BaseModelFormSet(BaseFormSet):
"Please correct the duplicate data for %(field_name)s "
"which must be unique for the %(lookup)s in %(date_field)s."
) % {
- 'field_name': date_check[2],
- 'date_field': date_check[3],
- 'lookup': str(date_check[1]),
+ "field_name": date_check[2],
+ "date_field": date_check[3],
+ "lookup": str(date_check[1]),
}
def get_form_error(self):
@@ -831,6 +950,7 @@ class BaseModelFormSet(BaseFormSet):
def add_fields(self, form, index):
"""Add a hidden field for the object's primary key."""
from django.db.models import AutoField, ForeignKey, OneToOneField
+
self._pk_field = pk = self.model._meta.pk
# If a pk isn't editable, then it won't be on the form, so we need to
# add it here so we can tell which object is which when we get the
@@ -840,11 +960,15 @@ class BaseModelFormSet(BaseFormSet):
def pk_is_not_editable(pk):
return (
- (not pk.editable) or (pk.auto_created or isinstance(pk, AutoField)) or (
- pk.remote_field and pk.remote_field.parent_link and
- pk_is_not_editable(pk.remote_field.model._meta.pk)
+ (not pk.editable)
+ or (pk.auto_created or isinstance(pk, AutoField))
+ or (
+ pk.remote_field
+ and pk.remote_field.parent_link
+ and pk_is_not_editable(pk.remote_field.model._meta.pk)
)
)
+
if pk_is_not_editable(pk) or pk.name not in form.fields:
if form.is_bound:
# If we're adding the related instance, ignore its primary key
@@ -868,37 +992,75 @@ class BaseModelFormSet(BaseFormSet):
widget = form._meta.widgets.get(self._pk_field.name, HiddenInput)
else:
widget = HiddenInput
- form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=widget)
+ form.fields[self._pk_field.name] = ModelChoiceField(
+ qs, initial=pk_value, required=False, widget=widget
+ )
super().add_fields(form, index)
-def modelformset_factory(model, form=ModelForm, formfield_callback=None,
- formset=BaseModelFormSet, extra=1, can_delete=False,
- can_order=False, max_num=None, fields=None, exclude=None,
- widgets=None, validate_max=False, localized_fields=None,
- labels=None, help_texts=None, error_messages=None,
- min_num=None, validate_min=False, field_classes=None,
- absolute_max=None, can_delete_extra=True, renderer=None,
- edit_only=False):
+def modelformset_factory(
+ model,
+ form=ModelForm,
+ formfield_callback=None,
+ formset=BaseModelFormSet,
+ extra=1,
+ can_delete=False,
+ can_order=False,
+ max_num=None,
+ fields=None,
+ exclude=None,
+ widgets=None,
+ validate_max=False,
+ localized_fields=None,
+ labels=None,
+ help_texts=None,
+ error_messages=None,
+ min_num=None,
+ validate_min=False,
+ field_classes=None,
+ absolute_max=None,
+ can_delete_extra=True,
+ renderer=None,
+ edit_only=False,
+):
"""Return a FormSet class for the given Django model class."""
- meta = getattr(form, 'Meta', None)
- if (getattr(meta, 'fields', fields) is None and
- getattr(meta, 'exclude', exclude) is None):
+ meta = getattr(form, "Meta", None)
+ if (
+ getattr(meta, "fields", fields) is None
+ and getattr(meta, "exclude", exclude) is None
+ ):
raise ImproperlyConfigured(
"Calling modelformset_factory without defining 'fields' or "
"'exclude' explicitly is prohibited."
)
- form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
- formfield_callback=formfield_callback,
- widgets=widgets, localized_fields=localized_fields,
- labels=labels, help_texts=help_texts,
- error_messages=error_messages, field_classes=field_classes)
- FormSet = formset_factory(form, formset, extra=extra, min_num=min_num, max_num=max_num,
- can_order=can_order, can_delete=can_delete,
- validate_min=validate_min, validate_max=validate_max,
- absolute_max=absolute_max, can_delete_extra=can_delete_extra,
- renderer=renderer)
+ form = modelform_factory(
+ model,
+ form=form,
+ fields=fields,
+ exclude=exclude,
+ formfield_callback=formfield_callback,
+ widgets=widgets,
+ localized_fields=localized_fields,
+ labels=labels,
+ help_texts=help_texts,
+ error_messages=error_messages,
+ field_classes=field_classes,
+ )
+ FormSet = formset_factory(
+ form,
+ formset,
+ extra=extra,
+ min_num=min_num,
+ max_num=max_num,
+ can_order=can_order,
+ can_delete=can_delete,
+ validate_min=validate_min,
+ validate_max=validate_max,
+ absolute_max=absolute_max,
+ can_delete_extra=can_delete_extra,
+ renderer=renderer,
+ )
FormSet.model = model
FormSet.edit_only = edit_only
return FormSet
@@ -906,10 +1068,20 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
# InlineFormSets #############################################################
+
class BaseInlineFormSet(BaseModelFormSet):
"""A formset for child objects related to a parent."""
- def __init__(self, data=None, files=None, instance=None,
- save_as_new=False, prefix=None, queryset=None, **kwargs):
+
+ def __init__(
+ self,
+ data=None,
+ files=None,
+ instance=None,
+ save_as_new=False,
+ prefix=None,
+ queryset=None,
+ **kwargs,
+ ):
if instance is None:
self.instance = self.fk.remote_field.model()
else:
@@ -939,7 +1111,7 @@ class BaseInlineFormSet(BaseModelFormSet):
def _construct_form(self, i, **kwargs):
form = super()._construct_form(i, **kwargs)
if self.save_as_new:
- mutable = getattr(form.data, '_mutable', None)
+ mutable = getattr(form.data, "_mutable", None)
# Allow modifying an immutable QueryDict.
if mutable is not None:
form.data._mutable = True
@@ -955,13 +1127,13 @@ class BaseInlineFormSet(BaseModelFormSet):
fk_value = self.instance.pk
if self.fk.remote_field.field_name != self.fk.remote_field.model._meta.pk.name:
fk_value = getattr(self.instance, self.fk.remote_field.field_name)
- fk_value = getattr(fk_value, 'pk', fk_value)
+ fk_value = getattr(fk_value, "pk", fk_value)
setattr(form.instance, self.fk.get_attname(), fk_value)
return form
@classmethod
def get_default_prefix(cls):
- return cls.fk.remote_field.get_accessor_name(model=cls.model).replace('+', '')
+ return cls.fk.remote_field.get_accessor_name(model=cls.model).replace("+", "")
def save_new(self, form, commit=True):
# Ensure the latest copy of the related instance is present on each
@@ -974,26 +1146,28 @@ class BaseInlineFormSet(BaseModelFormSet):
super().add_fields(form, index)
if self._pk_field == self.fk:
name = self._pk_field.name
- kwargs = {'pk_field': True}
+ kwargs = {"pk_field": True}
else:
# The foreign key field might not be on the form, so we poke at the
# Model field to get the label, since we need that for error messages.
name = self.fk.name
kwargs = {
- 'label': getattr(form.fields.get(name), 'label', capfirst(self.fk.verbose_name))
+ "label": getattr(
+ form.fields.get(name), "label", capfirst(self.fk.verbose_name)
+ )
}
# The InlineForeignKeyField assumes that the foreign key relation is
# based on the parent model's pk. If this isn't the case, set to_field
# to correctly resolve the initial form value.
if self.fk.remote_field.field_name != self.fk.remote_field.model._meta.pk.name:
- kwargs['to_field'] = self.fk.remote_field.field_name
+ kwargs["to_field"] = self.fk.remote_field.field_name
# If we're adding a new object, ignore a parent's auto-generated key
# as it will be regenerated on the save request.
if self.instance._state.adding:
- if kwargs.get('to_field') is not None:
- to_field = self.instance._meta.get_field(kwargs['to_field'])
+ if kwargs.get("to_field") is not None:
+ to_field = self.instance._meta.get_field(kwargs["to_field"])
else:
to_field = self.instance._meta.pk
if to_field.has_default():
@@ -1016,24 +1190,30 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
"""
# avoid circular import
from django.db.models import ForeignKey
+
opts = model._meta
if fk_name:
fks_to_parent = [f for f in opts.fields if f.name == fk_name]
if len(fks_to_parent) == 1:
fk = fks_to_parent[0]
parent_list = parent_model._meta.get_parent_list()
- if not isinstance(fk, ForeignKey) or (
- # ForeignKey to proxy models.
- fk.remote_field.model._meta.proxy and
- fk.remote_field.model._meta.proxy_for_model not in parent_list
- ) or (
- # ForeignKey to concrete models.
- not fk.remote_field.model._meta.proxy and
- fk.remote_field.model != parent_model and
- fk.remote_field.model not in parent_list
+ if (
+ not isinstance(fk, ForeignKey)
+ or (
+ # ForeignKey to proxy models.
+ fk.remote_field.model._meta.proxy
+ and fk.remote_field.model._meta.proxy_for_model not in parent_list
+ )
+ or (
+ # ForeignKey to concrete models.
+ not fk.remote_field.model._meta.proxy
+ and fk.remote_field.model != parent_model
+ and fk.remote_field.model not in parent_list
+ )
):
raise ValueError(
- "fk_name '%s' is not a ForeignKey to '%s'." % (fk_name, parent_model._meta.label)
+ "fk_name '%s' is not a ForeignKey to '%s'."
+ % (fk_name, parent_model._meta.label)
)
elif not fks_to_parent:
raise ValueError(
@@ -1043,12 +1223,15 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
# Try to discover what the ForeignKey from model to parent_model is
parent_list = parent_model._meta.get_parent_list()
fks_to_parent = [
- f for f in opts.fields
- if isinstance(f, ForeignKey) and (
- f.remote_field.model == parent_model or
- f.remote_field.model in parent_list or (
- f.remote_field.model._meta.proxy and
- f.remote_field.model._meta.proxy_for_model in parent_list
+ f
+ for f in opts.fields
+ if isinstance(f, ForeignKey)
+ and (
+ f.remote_field.model == parent_model
+ or f.remote_field.model in parent_list
+ or (
+ f.remote_field.model._meta.proxy
+ and f.remote_field.model._meta.proxy_for_model in parent_list
)
)
]
@@ -1058,7 +1241,8 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
if can_fail:
return
raise ValueError(
- "'%s' has no ForeignKey to '%s'." % (
+ "'%s' has no ForeignKey to '%s'."
+ % (
model._meta.label,
parent_model._meta.label,
)
@@ -1066,7 +1250,8 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
else:
raise ValueError(
"'%s' has more than one ForeignKey to '%s'. You must specify "
- "a 'fk_name' attribute." % (
+ "a 'fk_name' attribute."
+ % (
model._meta.label,
parent_model._meta.label,
)
@@ -1074,15 +1259,33 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
return fk
-def inlineformset_factory(parent_model, model, form=ModelForm,
- formset=BaseInlineFormSet, fk_name=None,
- fields=None, exclude=None, extra=3, can_order=False,
- can_delete=True, max_num=None, formfield_callback=None,
- widgets=None, validate_max=False, localized_fields=None,
- labels=None, help_texts=None, error_messages=None,
- min_num=None, validate_min=False, field_classes=None,
- absolute_max=None, can_delete_extra=True, renderer=None,
- edit_only=False):
+def inlineformset_factory(
+ parent_model,
+ model,
+ form=ModelForm,
+ formset=BaseInlineFormSet,
+ fk_name=None,
+ fields=None,
+ exclude=None,
+ extra=3,
+ can_order=False,
+ can_delete=True,
+ max_num=None,
+ formfield_callback=None,
+ widgets=None,
+ validate_max=False,
+ localized_fields=None,
+ labels=None,
+ help_texts=None,
+ error_messages=None,
+ min_num=None,
+ validate_min=False,
+ field_classes=None,
+ absolute_max=None,
+ can_delete_extra=True,
+ renderer=None,
+ edit_only=False,
+):
"""
Return an ``InlineFormSet`` for the given kwargs.
@@ -1094,28 +1297,28 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
if fk.unique:
max_num = 1
kwargs = {
- 'form': form,
- 'formfield_callback': formfield_callback,
- 'formset': formset,
- 'extra': extra,
- 'can_delete': can_delete,
- 'can_order': can_order,
- 'fields': fields,
- 'exclude': exclude,
- 'min_num': min_num,
- 'max_num': max_num,
- 'widgets': widgets,
- 'validate_min': validate_min,
- 'validate_max': validate_max,
- 'localized_fields': localized_fields,
- 'labels': labels,
- 'help_texts': help_texts,
- 'error_messages': error_messages,
- 'field_classes': field_classes,
- 'absolute_max': absolute_max,
- 'can_delete_extra': can_delete_extra,
- 'renderer': renderer,
- 'edit_only': edit_only,
+ "form": form,
+ "formfield_callback": formfield_callback,
+ "formset": formset,
+ "extra": extra,
+ "can_delete": can_delete,
+ "can_order": can_order,
+ "fields": fields,
+ "exclude": exclude,
+ "min_num": min_num,
+ "max_num": max_num,
+ "widgets": widgets,
+ "validate_min": validate_min,
+ "validate_max": validate_max,
+ "localized_fields": localized_fields,
+ "labels": labels,
+ "help_texts": help_texts,
+ "error_messages": error_messages,
+ "field_classes": field_classes,
+ "absolute_max": absolute_max,
+ "can_delete_extra": can_delete_extra,
+ "renderer": renderer,
+ "edit_only": edit_only,
}
FormSet = modelformset_factory(model, **kwargs)
FormSet.fk = fk
@@ -1124,14 +1327,16 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
# Fields #####################################################################
+
class InlineForeignKeyField(Field):
"""
A basic integer field that deals with validating the given value to a
given parent instance in an inline.
"""
+
widget = HiddenInput
default_error_messages = {
- 'invalid_choice': _('The inline value did not match the parent instance.'),
+ "invalid_choice": _("The inline value did not match the parent instance."),
}
def __init__(self, parent_instance, *args, pk_field=False, to_field=None, **kwargs):
@@ -1158,7 +1363,9 @@ class InlineForeignKeyField(Field):
else:
orig = self.parent_instance.pk
if str(value) != str(orig):
- raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
+ raise ValidationError(
+ self.error_messages["invalid_choice"], code="invalid_choice"
+ )
return self.parent_instance
def has_changed(self, initial, data):
@@ -1215,34 +1422,50 @@ class ModelChoiceIterator:
class ModelChoiceField(ChoiceField):
"""A ChoiceField whose choices are a model QuerySet."""
+
# This class is a subclass of ChoiceField for purity, but it doesn't
# actually use any of ChoiceField's implementation.
default_error_messages = {
- 'invalid_choice': _(
- 'Select a valid choice. That choice is not one of the available choices.'
+ "invalid_choice": _(
+ "Select a valid choice. That choice is not one of the available choices."
),
}
iterator = ModelChoiceIterator
- def __init__(self, queryset, *, empty_label="---------",
- required=True, widget=None, label=None, initial=None,
- help_text='', to_field_name=None, limit_choices_to=None,
- blank=False, **kwargs):
+ def __init__(
+ self,
+ queryset,
+ *,
+ empty_label="---------",
+ required=True,
+ widget=None,
+ label=None,
+ initial=None,
+ help_text="",
+ to_field_name=None,
+ limit_choices_to=None,
+ blank=False,
+ **kwargs,
+ ):
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
Field.__init__(
- self, required=required, widget=widget, label=label,
- initial=initial, help_text=help_text, **kwargs
+ self,
+ required=required,
+ widget=widget,
+ label=label,
+ initial=initial,
+ help_text=help_text,
+ **kwargs,
)
- if (
- (required and initial is not None) or
- (isinstance(self.widget, RadioSelect) and not blank)
+ if (required and initial is not None) or (
+ isinstance(self.widget, RadioSelect) and not blank
):
self.empty_label = None
else:
self.empty_label = empty_label
self.queryset = queryset
- self.limit_choices_to = limit_choices_to # limit the queryset later.
+ self.limit_choices_to = limit_choices_to # limit the queryset later.
self.to_field_name = to_field_name
def get_limit_choices_to(self):
@@ -1284,7 +1507,7 @@ class ModelChoiceField(ChoiceField):
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
- if hasattr(self, '_choices'):
+ if hasattr(self, "_choices"):
return self._choices
# Otherwise, execute the QuerySet in self.queryset to determine the
@@ -1299,7 +1522,7 @@ class ModelChoiceField(ChoiceField):
choices = property(_get_choices, ChoiceField._set_choices)
def prepare_value(self, value):
- if hasattr(value, '_meta'):
+ if hasattr(value, "_meta"):
if self.to_field_name:
return value.serializable_value(self.to_field_name)
else:
@@ -1310,15 +1533,15 @@ class ModelChoiceField(ChoiceField):
if value in self.empty_values:
return None
try:
- key = self.to_field_name or 'pk'
+ key = self.to_field_name or "pk"
if isinstance(value, self.queryset.model):
value = getattr(value, key)
value = self.queryset.get(**{key: value})
except (ValueError, TypeError, self.queryset.model.DoesNotExist):
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
@@ -1328,21 +1551,22 @@ class ModelChoiceField(ChoiceField):
def has_changed(self, initial, data):
if self.disabled:
return False
- 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 str(self.prepare_value(initial_value)) != str(data_value)
class ModelMultipleChoiceField(ModelChoiceField):
"""A MultipleChoiceField whose choices are a model QuerySet."""
+
widget = SelectMultiple
hidden_widget = MultipleHiddenInput
default_error_messages = {
- '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."),
+ "invalid_choice": _(
+ "Select a valid choice. %(value)s is not one of the available choices."
),
- 'invalid_pk_value': _('“%(pk)s” is not a valid value.')
+ "invalid_pk_value": _("“%(pk)s” is not a valid value."),
}
def __init__(self, queryset, **kwargs):
@@ -1356,13 +1580,13 @@ class ModelMultipleChoiceField(ModelChoiceField):
def clean(self, value):
value = self.prepare_value(value)
if self.required and not value:
- raise ValidationError(self.error_messages['required'], code='required')
+ raise ValidationError(self.error_messages["required"], code="required")
elif not self.required and not value:
return self.queryset.none()
if not isinstance(value, (list, tuple)):
raise ValidationError(
- self.error_messages['invalid_list'],
- code='invalid_list',
+ self.error_messages["invalid_list"],
+ code="invalid_list",
)
qs = self._check_values(value)
# Since this overrides the inherited ModelChoiceField.clean
@@ -1376,7 +1600,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
corresponding objects. Raise a ValidationError if a given value is
invalid (not a valid PK, not in the queryset, etc.)
"""
- key = self.to_field_name or 'pk'
+ key = self.to_field_name or "pk"
# deduplicate given values to avoid creating many querysets or
# requiring the database backend deduplicate efficiently.
try:
@@ -1384,33 +1608,35 @@ class ModelMultipleChoiceField(ModelChoiceField):
except TypeError:
# list of lists isn't hashable, for example
raise ValidationError(
- self.error_messages['invalid_list'],
- code='invalid_list',
+ self.error_messages["invalid_list"],
+ code="invalid_list",
)
for pk in value:
try:
self.queryset.filter(**{key: pk})
except (ValueError, TypeError):
raise ValidationError(
- self.error_messages['invalid_pk_value'],
- code='invalid_pk_value',
- params={'pk': pk},
+ self.error_messages["invalid_pk_value"],
+ code="invalid_pk_value",
+ params={"pk": pk},
)
- qs = self.queryset.filter(**{'%s__in' % key: value})
+ qs = self.queryset.filter(**{"%s__in" % key: value})
pks = {str(getattr(o, key)) for o in qs}
for val in value:
if str(val) not in pks:
raise ValidationError(
- self.error_messages['invalid_choice'],
- code='invalid_choice',
- params={'value': val},
+ self.error_messages["invalid_choice"],
+ code="invalid_choice",
+ params={"value": val},
)
return qs
def prepare_value(self, value):
- if (hasattr(value, '__iter__') and
- not isinstance(value, str) and
- not hasattr(value, '_meta')):
+ if (
+ hasattr(value, "__iter__")
+ and not isinstance(value, str)
+ and not hasattr(value, "_meta")
+ ):
prepare_value = super().prepare_value
return [prepare_value(v) for v in value]
return super().prepare_value(value)
@@ -1430,7 +1656,6 @@ class ModelMultipleChoiceField(ModelChoiceField):
def modelform_defines_fields(form_class):
- return hasattr(form_class, '_meta') and (
- form_class._meta.fields is not None or
- form_class._meta.exclude is not None
+ return hasattr(form_class, "_meta") and (
+ form_class._meta.fields is not None or form_class._meta.exclude is not None
)
diff --git a/django/forms/renderers.py b/django/forms/renderers.py
index ffb61600c2..88cf504653 100644
--- a/django/forms/renderers.py
+++ b/django/forms/renderers.py
@@ -16,7 +16,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,12 +29,14 @@ 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": [Path(__file__).parent / self.backend.app_dirname],
+ "NAME": "djangoforms",
+ "OPTIONS": {},
+ }
+ )
class DjangoTemplates(EngineMixin, BaseRenderer):
@@ -42,6 +44,7 @@ class DjangoTemplates(EngineMixin, BaseRenderer):
Load Django templates from the built-in widget templates in
django/forms/templates and from apps' 'templates' directory.
"""
+
backend = DjangoTemplates
@@ -50,9 +53,11 @@ 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
@@ -61,5 +66,6 @@ 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)
diff --git a/django/forms/utils.py b/django/forms/utils.py
index 4af57e8586..7d3bb5ad48 100644
--- a/django/forms/utils.py
+++ b/django/forms/utils.py
@@ -13,8 +13,8 @@ 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,23 +37,24 @@ 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.'
+ "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(),
- ))
+ return mark_safe(
+ (renderer or self.renderer).render(
+ template_name or self.template_name,
+ context or self.get_context(),
+ )
+ )
__str__ = render
__html__ = render
@@ -90,9 +91,10 @@ class ErrorDict(dict, RenderableErrorMixin):
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'
+
+ 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)
@@ -106,8 +108,8 @@ class ErrorDict(dict, RenderableErrorMixin):
def get_context(self):
return {
- 'errors': self.items(),
- 'error_class': 'errorlist',
+ "errors": self.items(),
+ "error_class": "errorlist",
}
@@ -115,17 +117,18 @@ class ErrorList(UserList, list, RenderableErrorMixin):
"""
A collection of errors that knows how to display itself in various formats.
"""
- template_name = 'django/forms/errors/list/default.html'
- template_name_text = 'django/forms/errors/list/text.txt'
- template_name_ul = 'django/forms/errors/list/ul.html'
+
+ template_name = "django/forms/errors/list/default.html"
+ template_name_text = "django/forms/errors/list/text.txt"
+ template_name_ul = "django/forms/errors/list/ul.html"
def __init__(self, initlist=None, error_class=None, renderer=None):
super().__init__(initlist)
if error_class is None:
- self.error_class = 'errorlist'
+ self.error_class = "errorlist"
else:
- self.error_class = 'errorlist {}'.format(error_class)
+ self.error_class = "errorlist {}".format(error_class)
self.renderer = renderer or get_default_renderer()
def as_data(self):
@@ -140,16 +143,18 @@ 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,
+ "errors": self,
+ "error_class": self.error_class,
}
def __repr__(self):
@@ -179,6 +184,7 @@ 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
@@ -187,19 +193,20 @@ 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
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 05667f8e44..8c5122ad1d 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -17,24 +17,41 @@ from django.utils.formats import get_format
from django.utils.html import format_html, html_safe
from django.utils.regex_helper import _lazy_re_compile
from django.utils.safestring import mark_safe
-from django.utils.topological_sort import (
- CyclicDependencyError, stable_topological_sort,
-)
+from django.utils.topological_sort import CyclicDependencyError, stable_topological_sort
from django.utils.translation import gettext_lazy as _
from .renderers import get_default_renderer
__all__ = (
- 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'NumberInput',
- 'EmailInput', 'URLInput', 'PasswordInput', 'HiddenInput',
- 'MultipleHiddenInput', 'FileInput', 'ClearableFileInput', 'Textarea',
- 'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
- 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
- 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
- 'SplitHiddenDateTimeWidget', 'SelectDateWidget',
+ "Media",
+ "MediaDefiningClass",
+ "Widget",
+ "TextInput",
+ "NumberInput",
+ "EmailInput",
+ "URLInput",
+ "PasswordInput",
+ "HiddenInput",
+ "MultipleHiddenInput",
+ "FileInput",
+ "ClearableFileInput",
+ "Textarea",
+ "DateInput",
+ "DateTimeInput",
+ "TimeInput",
+ "CheckboxInput",
+ "Select",
+ "NullBooleanSelect",
+ "SelectMultiple",
+ "RadioSelect",
+ "CheckboxSelectMultiple",
+ "MultiWidget",
+ "SplitDateTimeWidget",
+ "SplitHiddenDateTimeWidget",
+ "SelectDateWidget",
)
-MEDIA_TYPES = ('css', 'js')
+MEDIA_TYPES = ("css", "js")
class MediaOrderConflictWarning(RuntimeWarning):
@@ -45,8 +62,8 @@ class MediaOrderConflictWarning(RuntimeWarning):
class Media:
def __init__(self, media=None, css=None, js=None):
if media is not None:
- css = getattr(media, 'css', {})
- js = getattr(media, 'js', [])
+ css = getattr(media, "css", {})
+ js = getattr(media, "js", [])
else:
if css is None:
css = {}
@@ -56,7 +73,7 @@ class Media:
self._js_lists = [js]
def __repr__(self):
- return 'Media(css=%r, js=%r)' % (self._css, self._js)
+ return "Media(css=%r, js=%r)" % (self._css, self._js)
def __str__(self):
return self.render()
@@ -74,26 +91,35 @@ class Media:
return self.merge(*self._js_lists)
def render(self):
- return mark_safe('\n'.join(chain.from_iterable(getattr(self, 'render_' + name)() for name in MEDIA_TYPES)))
+ return mark_safe(
+ "\n".join(
+ chain.from_iterable(
+ getattr(self, "render_" + name)() for name in MEDIA_TYPES
+ )
+ )
+ )
def render_js(self):
return [
- format_html(
- '<script src="{}"></script>',
- self.absolute_path(path)
- ) for path in self._js
+ format_html('<script src="{}"></script>', self.absolute_path(path))
+ for path in self._js
]
def render_css(self):
# To keep rendering order consistent, we can't just iterate over items().
# We need to sort the keys, and iterate over the sorted list.
media = sorted(self._css)
- return chain.from_iterable([
- format_html(
- '<link href="{}" media="{}" rel="stylesheet">',
- self.absolute_path(path), medium
- ) for path in self._css[medium]
- ] for medium in media)
+ return chain.from_iterable(
+ [
+ format_html(
+ '<link href="{}" media="{}" rel="stylesheet">',
+ self.absolute_path(path),
+ medium,
+ )
+ for path in self._css[medium]
+ ]
+ for medium in media
+ )
def absolute_path(self, path):
"""
@@ -101,14 +127,14 @@ class Media:
path. An absolute path will be returned unchanged while a relative path
will be passed to django.templatetags.static.static().
"""
- if path.startswith(('http://', 'https://', '/')):
+ if path.startswith(("http://", "https://", "/")):
return path
return static(path)
def __getitem__(self, name):
"""Return a Media object that only contains media of the given type."""
if name in MEDIA_TYPES:
- return Media(**{str(name): getattr(self, '_' + name)})
+ return Media(**{str(name): getattr(self, "_" + name)})
raise KeyError('Unknown media type "%s"' % name)
@staticmethod
@@ -138,9 +164,10 @@ class Media:
return stable_topological_sort(all_items, dependency_graph)
except CyclicDependencyError:
warnings.warn(
- 'Detected duplicate Media files in an opposite order: {}'.format(
- ', '.join(repr(list_) for list_ in lists)
- ), MediaOrderConflictWarning,
+ "Detected duplicate Media files in an opposite order: {}".format(
+ ", ".join(repr(list_) for list_ in lists)
+ ),
+ MediaOrderConflictWarning,
)
return list(all_items)
@@ -167,9 +194,9 @@ def media_property(cls):
base = Media()
# Get the media definition for this class
- definition = getattr(cls, 'Media', None)
+ definition = getattr(cls, "Media", None)
if definition:
- extend = getattr(definition, 'extend', True)
+ extend = getattr(definition, "extend", True)
if extend:
if extend is True:
m = base
@@ -180,6 +207,7 @@ def media_property(cls):
return m + Media(definition)
return Media(definition)
return base
+
return property(_media)
@@ -187,10 +215,11 @@ class MediaDefiningClass(type):
"""
Metaclass for classes that can have media definitions.
"""
+
def __new__(mcs, name, bases, attrs):
new_class = super().__new__(mcs, name, bases, attrs)
- if 'media' not in attrs:
+ if "media" not in attrs:
new_class.media = media_property(new_class)
return new_class
@@ -213,17 +242,17 @@ class Widget(metaclass=MediaDefiningClass):
@property
def is_hidden(self):
- return self.input_type == 'hidden' if hasattr(self, 'input_type') else False
+ return self.input_type == "hidden" if hasattr(self, "input_type") else False
def subwidgets(self, name, value, attrs=None):
context = self.get_context(name, value, attrs)
- yield context['widget']
+ yield context["widget"]
def format_value(self, value):
"""
Return a value as it should appear when rendered in a template.
"""
- if value == '' or value is None:
+ if value == "" or value is None:
return None
if self.is_localized:
return formats.localize_input(value)
@@ -231,13 +260,13 @@ class Widget(metaclass=MediaDefiningClass):
def get_context(self, name, value, attrs):
return {
- 'widget': {
- 'name': name,
- 'is_hidden': self.is_hidden,
- 'required': self.is_required,
- 'value': self.format_value(value),
- 'attrs': self.build_attrs(self.attrs, attrs),
- 'template_name': self.template_name,
+ "widget": {
+ "name": name,
+ "is_hidden": self.is_hidden,
+ "required": self.is_required,
+ "value": self.format_value(value),
+ "attrs": self.build_attrs(self.attrs, attrs),
+ "template_name": self.template_name,
},
}
@@ -285,44 +314,45 @@ class Input(Widget):
"""
Base class for all <input> widgets.
"""
+
input_type = None # Subclasses must define this.
- template_name = 'django/forms/widgets/input.html'
+ template_name = "django/forms/widgets/input.html"
def __init__(self, attrs=None):
if attrs is not None:
attrs = attrs.copy()
- self.input_type = attrs.pop('type', self.input_type)
+ self.input_type = attrs.pop("type", self.input_type)
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
- context['widget']['type'] = self.input_type
+ context["widget"]["type"] = self.input_type
return context
class TextInput(Input):
- input_type = 'text'
- template_name = 'django/forms/widgets/text.html'
+ input_type = "text"
+ template_name = "django/forms/widgets/text.html"
class NumberInput(Input):
- input_type = 'number'
- template_name = 'django/forms/widgets/number.html'
+ input_type = "number"
+ template_name = "django/forms/widgets/number.html"
class EmailInput(Input):
- input_type = 'email'
- template_name = 'django/forms/widgets/email.html'
+ input_type = "email"
+ template_name = "django/forms/widgets/email.html"
class URLInput(Input):
- input_type = 'url'
- template_name = 'django/forms/widgets/url.html'
+ input_type = "url"
+ template_name = "django/forms/widgets/url.html"
class PasswordInput(Input):
- input_type = 'password'
- template_name = 'django/forms/widgets/password.html'
+ input_type = "password"
+ template_name = "django/forms/widgets/password.html"
def __init__(self, attrs=None, render_value=False):
super().__init__(attrs)
@@ -335,8 +365,8 @@ class PasswordInput(Input):
class HiddenInput(Input):
- input_type = 'hidden'
- template_name = 'django/forms/widgets/hidden.html'
+ input_type = "hidden"
+ template_name = "django/forms/widgets/hidden.html"
class MultipleHiddenInput(HiddenInput):
@@ -344,25 +374,26 @@ class MultipleHiddenInput(HiddenInput):
Handle <input type="hidden"> for fields that have a list
of values.
"""
- template_name = 'django/forms/widgets/multiple_hidden.html'
+
+ template_name = "django/forms/widgets/multiple_hidden.html"
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
- final_attrs = context['widget']['attrs']
- id_ = context['widget']['attrs'].get('id')
+ final_attrs = context["widget"]["attrs"]
+ id_ = context["widget"]["attrs"].get("id")
subwidgets = []
- for index, value_ in enumerate(context['widget']['value']):
+ for index, value_ in enumerate(context["widget"]["value"]):
widget_attrs = final_attrs.copy()
if id_:
# An ID attribute was given. Add a numeric index as a suffix
# so that the inputs don't all have the same ID attribute.
- widget_attrs['id'] = '%s_%s' % (id_, index)
+ widget_attrs["id"] = "%s_%s" % (id_, index)
widget = HiddenInput()
widget.is_required = self.is_required
- subwidgets.append(widget.get_context(name, value_, widget_attrs)['widget'])
+ subwidgets.append(widget.get_context(name, value_, widget_attrs)["widget"])
- context['widget']['subwidgets'] = subwidgets
+ context["widget"]["subwidgets"] = subwidgets
return context
def value_from_datadict(self, data, files, name):
@@ -377,9 +408,9 @@ class MultipleHiddenInput(HiddenInput):
class FileInput(Input):
- input_type = 'file'
+ input_type = "file"
needs_multipart_form = True
- template_name = 'django/forms/widgets/file.html'
+ template_name = "django/forms/widgets/file.html"
def format_value(self, value):
"""File input never renders a value."""
@@ -400,29 +431,29 @@ FILE_INPUT_CONTRADICTION = object()
class ClearableFileInput(FileInput):
- clear_checkbox_label = _('Clear')
- initial_text = _('Currently')
- input_text = _('Change')
- template_name = 'django/forms/widgets/clearable_file_input.html'
+ clear_checkbox_label = _("Clear")
+ initial_text = _("Currently")
+ input_text = _("Change")
+ template_name = "django/forms/widgets/clearable_file_input.html"
def clear_checkbox_name(self, name):
"""
Given the name of the file input, return the name of the clear checkbox
input.
"""
- return name + '-clear'
+ return name + "-clear"
def clear_checkbox_id(self, name):
"""
Given the name of the clear checkbox input, return the HTML id for it.
"""
- return name + '_id'
+ return name + "_id"
def is_initial(self, value):
"""
Return whether value is considered to be initial value.
"""
- return bool(value and getattr(value, 'url', False))
+ return bool(value and getattr(value, "url", False))
def format_value(self, value):
"""
@@ -435,20 +466,23 @@ class ClearableFileInput(FileInput):
context = super().get_context(name, value, attrs)
checkbox_name = self.clear_checkbox_name(name)
checkbox_id = self.clear_checkbox_id(checkbox_name)
- context['widget'].update({
- 'checkbox_name': checkbox_name,
- 'checkbox_id': checkbox_id,
- 'is_initial': self.is_initial(value),
- 'input_text': self.input_text,
- 'initial_text': self.initial_text,
- 'clear_checkbox_label': self.clear_checkbox_label,
- })
+ context["widget"].update(
+ {
+ "checkbox_name": checkbox_name,
+ "checkbox_id": checkbox_id,
+ "is_initial": self.is_initial(value),
+ "input_text": self.input_text,
+ "initial_text": self.initial_text,
+ "clear_checkbox_label": self.clear_checkbox_label,
+ }
+ )
return context
def value_from_datadict(self, data, files, name):
upload = super().value_from_datadict(data, files, name)
if not self.is_required and CheckboxInput().value_from_datadict(
- data, files, self.clear_checkbox_name(name)):
+ data, files, self.clear_checkbox_name(name)
+ ):
if upload:
# If the user contradicts themselves (uploads a new file AND
@@ -461,24 +495,24 @@ class ClearableFileInput(FileInput):
def value_omitted_from_data(self, data, files, name):
return (
- super().value_omitted_from_data(data, files, name) and
- self.clear_checkbox_name(name) not in data
+ super().value_omitted_from_data(data, files, name)
+ and self.clear_checkbox_name(name) not in data
)
class Textarea(Widget):
- template_name = 'django/forms/widgets/textarea.html'
+ template_name = "django/forms/widgets/textarea.html"
def __init__(self, attrs=None):
# Use slightly better defaults than HTML's 20x2 box
- default_attrs = {'cols': '40', 'rows': '10'}
+ default_attrs = {"cols": "40", "rows": "10"}
if attrs:
default_attrs.update(attrs)
super().__init__(default_attrs)
class DateTimeBaseInput(TextInput):
- format_key = ''
+ format_key = ""
supports_microseconds = False
def __init__(self, attrs=None, format=None):
@@ -486,32 +520,34 @@ class DateTimeBaseInput(TextInput):
self.format = format or None
def format_value(self, value):
- return formats.localize_input(value, self.format or formats.get_format(self.format_key)[0])
+ return formats.localize_input(
+ value, self.format or formats.get_format(self.format_key)[0]
+ )
class DateInput(DateTimeBaseInput):
- format_key = 'DATE_INPUT_FORMATS'
- template_name = 'django/forms/widgets/date.html'
+ format_key = "DATE_INPUT_FORMATS"
+ template_name = "django/forms/widgets/date.html"
class DateTimeInput(DateTimeBaseInput):
- format_key = 'DATETIME_INPUT_FORMATS'
- template_name = 'django/forms/widgets/datetime.html'
+ format_key = "DATETIME_INPUT_FORMATS"
+ template_name = "django/forms/widgets/datetime.html"
class TimeInput(DateTimeBaseInput):
- format_key = 'TIME_INPUT_FORMATS'
- template_name = 'django/forms/widgets/time.html'
+ format_key = "TIME_INPUT_FORMATS"
+ template_name = "django/forms/widgets/time.html"
# Defined at module level so that CheckboxInput is picklable (#17976)
def boolean_check(v):
- return not (v is False or v is None or v == '')
+ return not (v is False or v is None or v == "")
class CheckboxInput(Input):
- input_type = 'checkbox'
- template_name = 'django/forms/widgets/checkbox.html'
+ input_type = "checkbox"
+ template_name = "django/forms/widgets/checkbox.html"
def __init__(self, attrs=None, check_test=None):
super().__init__(attrs)
@@ -521,13 +557,13 @@ class CheckboxInput(Input):
def format_value(self, value):
"""Only return the 'value' attribute if value isn't empty."""
- if value is True or value is False or value is None or value == '':
+ if value is True or value is False or value is None or value == "":
return
return str(value)
def get_context(self, name, value, attrs):
if self.check_test(value):
- attrs = {**(attrs or {}), 'checked': True}
+ attrs = {**(attrs or {}), "checked": True}
return super().get_context(name, value, attrs)
def value_from_datadict(self, data, files, name):
@@ -537,7 +573,7 @@ class CheckboxInput(Input):
return False
value = data.get(name)
# Translate true and false strings to boolean values.
- values = {'true': True, 'false': False}
+ values = {"true": True, "false": False}
if isinstance(value, str):
value = values.get(value.lower(), value)
return bool(value)
@@ -554,7 +590,7 @@ class ChoiceWidget(Widget):
template_name = None
option_template_name = None
add_id_index = True
- checked_attribute = {'checked': True}
+ checked_attribute = {"checked": True}
option_inherits_attrs = True
def __init__(self, attrs=None, choices=()):
@@ -591,7 +627,7 @@ class ChoiceWidget(Widget):
for index, (option_value, option_label) in enumerate(self.choices):
if option_value is None:
- option_value = ''
+ option_value = ""
subgroup = []
if isinstance(option_label, (list, tuple)):
@@ -605,50 +641,62 @@ class ChoiceWidget(Widget):
groups.append((group_name, subgroup, index))
for subvalue, sublabel in choices:
- selected = (
- (not has_selected or self.allow_multiple_selected) and
- str(subvalue) in value
- )
+ selected = (not has_selected or self.allow_multiple_selected) and str(
+ subvalue
+ ) in value
has_selected |= selected
- subgroup.append(self.create_option(
- name, subvalue, sublabel, selected, index,
- subindex=subindex, attrs=attrs,
- ))
+ subgroup.append(
+ self.create_option(
+ name,
+ subvalue,
+ sublabel,
+ selected,
+ index,
+ subindex=subindex,
+ attrs=attrs,
+ )
+ )
if subindex is not None:
subindex += 1
return groups
- def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
+ def create_option(
+ self, name, value, label, selected, index, subindex=None, attrs=None
+ ):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
- option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
+ option_attrs = (
+ self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
+ )
if selected:
option_attrs.update(self.checked_attribute)
- if 'id' in option_attrs:
- option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
+ if "id" in option_attrs:
+ option_attrs["id"] = self.id_for_label(option_attrs["id"], index)
return {
- 'name': name,
- 'value': value,
- 'label': label,
- 'selected': selected,
- 'index': index,
- 'attrs': option_attrs,
- 'type': self.input_type,
- 'template_name': self.option_template_name,
- 'wrap_label': True,
+ "name": name,
+ "value": value,
+ "label": label,
+ "selected": selected,
+ "index": index,
+ "attrs": option_attrs,
+ "type": self.input_type,
+ "template_name": self.option_template_name,
+ "wrap_label": True,
}
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
- context['widget']['optgroups'] = self.optgroups(name, context['widget']['value'], attrs)
+ context["widget"]["optgroups"] = self.optgroups(
+ name, context["widget"]["value"], attrs
+ )
return context
- def id_for_label(self, id_, index='0'):
+ def id_for_label(self, id_, index="0"):
"""
Use an incremented id for each option where the main widget
references the zero index.
"""
if id_ and self.add_id_index:
- id_ = '%s_%s' % (id_, index)
+ id_ = "%s_%s" % (id_, index)
return id_
def value_from_datadict(self, data, files, name):
@@ -666,28 +714,28 @@ class ChoiceWidget(Widget):
return []
if not isinstance(value, (tuple, list)):
value = [value]
- return [str(v) if v is not None else '' for v in value]
+ return [str(v) if v is not None else "" for v in value]
class Select(ChoiceWidget):
- input_type = 'select'
- template_name = 'django/forms/widgets/select.html'
- option_template_name = 'django/forms/widgets/select_option.html'
+ input_type = "select"
+ template_name = "django/forms/widgets/select.html"
+ option_template_name = "django/forms/widgets/select_option.html"
add_id_index = False
- checked_attribute = {'selected': True}
+ checked_attribute = {"selected": True}
option_inherits_attrs = False
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
if self.allow_multiple_selected:
- context['widget']['attrs']['multiple'] = True
+ context["widget"]["attrs"]["multiple"] = True
return context
@staticmethod
def _choice_has_empty_value(choice):
"""Return True if the choice's value is empty string or None."""
value, _ = choice
- return value is None or value == ''
+ return value is None or value == ""
def use_required_attribute(self, initial):
"""
@@ -700,44 +748,52 @@ class Select(ChoiceWidget):
return use_required_attribute
first_choice = next(iter(self.choices), None)
- return use_required_attribute and first_choice is not None and self._choice_has_empty_value(first_choice)
+ return (
+ use_required_attribute
+ and first_choice is not None
+ and self._choice_has_empty_value(first_choice)
+ )
class NullBooleanSelect(Select):
"""
A Select Widget intended to be used with NullBooleanField.
"""
+
def __init__(self, attrs=None):
choices = (
- ('unknown', _('Unknown')),
- ('true', _('Yes')),
- ('false', _('No')),
+ ("unknown", _("Unknown")),
+ ("true", _("Yes")),
+ ("false", _("No")),
)
super().__init__(attrs, choices)
def format_value(self, value):
try:
return {
- True: 'true', False: 'false',
- 'true': 'true', 'false': 'false',
+ True: "true",
+ False: "false",
+ "true": "true",
+ "false": "false",
# For backwards compatibility with Django < 2.2.
- '2': 'true', '3': 'false',
+ "2": "true",
+ "3": "false",
}[value]
except KeyError:
- return 'unknown'
+ return "unknown"
def value_from_datadict(self, data, files, name):
value = data.get(name)
return {
True: True,
- 'True': True,
- 'False': False,
+ "True": True,
+ "False": False,
False: False,
- 'true': True,
- 'false': False,
+ "true": True,
+ "false": False,
# For backwards compatibility with Django < 2.2.
- '2': True,
- '3': False,
+ "2": True,
+ "3": False,
}.get(value)
@@ -758,9 +814,9 @@ class SelectMultiple(Select):
class RadioSelect(ChoiceWidget):
- input_type = 'radio'
- template_name = 'django/forms/widgets/radio.html'
- option_template_name = 'django/forms/widgets/radio_option.html'
+ input_type = "radio"
+ template_name = "django/forms/widgets/radio.html"
+ option_template_name = "django/forms/widgets/radio_option.html"
def id_for_label(self, id_, index=None):
"""
@@ -769,15 +825,15 @@ class RadioSelect(ChoiceWidget):
the first input.
"""
if index is None:
- return ''
+ return ""
return super().id_for_label(id_, index)
class CheckboxSelectMultiple(RadioSelect):
allow_multiple_selected = True
- input_type = 'checkbox'
- template_name = 'django/forms/widgets/checkbox_select.html'
- option_template_name = 'django/forms/widgets/checkbox_option.html'
+ input_type = "checkbox"
+ template_name = "django/forms/widgets/checkbox_select.html"
+ option_template_name = "django/forms/widgets/checkbox_option.html"
def use_required_attribute(self, initial):
# Don't use the 'required' attribute because browser validation would
@@ -800,16 +856,15 @@ class MultiWidget(Widget):
You'll probably want to use this class with MultiValueField.
"""
- template_name = 'django/forms/widgets/multiwidget.html'
+
+ template_name = "django/forms/widgets/multiwidget.html"
def __init__(self, widgets, attrs=None):
if isinstance(widgets, dict):
- self.widgets_names = [
- ('_%s' % name) if name else '' for name in widgets
- ]
+ self.widgets_names = [("_%s" % name) if name else "" for name in widgets]
widgets = widgets.values()
else:
- self.widgets_names = ['_%s' % i for i in range(len(widgets))]
+ self.widgets_names = ["_%s" % i for i in range(len(widgets))]
self.widgets = [w() if isinstance(w, type) else w for w in widgets]
super().__init__(attrs)
@@ -827,11 +882,13 @@ class MultiWidget(Widget):
if not isinstance(value, list):
value = self.decompress(value)
- final_attrs = context['widget']['attrs']
- input_type = final_attrs.pop('type', None)
- id_ = final_attrs.get('id')
+ final_attrs = context["widget"]["attrs"]
+ input_type = final_attrs.pop("type", None)
+ id_ = final_attrs.get("id")
subwidgets = []
- for i, (widget_name, widget) in enumerate(zip(self.widgets_names, self.widgets)):
+ for i, (widget_name, widget) in enumerate(
+ zip(self.widgets_names, self.widgets)
+ ):
if input_type is not None:
widget.input_type = input_type
widget_name = name + widget_name
@@ -841,15 +898,17 @@ class MultiWidget(Widget):
widget_value = None
if id_:
widget_attrs = final_attrs.copy()
- widget_attrs['id'] = '%s_%s' % (id_, i)
+ widget_attrs["id"] = "%s_%s" % (id_, i)
else:
widget_attrs = final_attrs
- subwidgets.append(widget.get_context(widget_name, widget_value, widget_attrs)['widget'])
- context['widget']['subwidgets'] = subwidgets
+ subwidgets.append(
+ widget.get_context(widget_name, widget_value, widget_attrs)["widget"]
+ )
+ context["widget"]["subwidgets"] = subwidgets
return context
def id_for_label(self, id_):
- return ''
+ return ""
def value_from_datadict(self, data, files, name):
return [
@@ -869,7 +928,7 @@ class MultiWidget(Widget):
The given value can be assumed to be valid, but not necessarily
non-empty.
"""
- raise NotImplementedError('Subclasses must implement this method.')
+ raise NotImplementedError("Subclasses must implement this method.")
def _get_media(self):
"""
@@ -880,6 +939,7 @@ class MultiWidget(Widget):
for w in self.widgets:
media = media + w.media
return media
+
media = property(_get_media)
def __deepcopy__(self, memo):
@@ -896,10 +956,18 @@ class SplitDateTimeWidget(MultiWidget):
"""
A widget that splits datetime input into two <input type="text"> boxes.
"""
+
supports_microseconds = False
- template_name = 'django/forms/widgets/splitdatetime.html'
+ template_name = "django/forms/widgets/splitdatetime.html"
- def __init__(self, attrs=None, date_format=None, time_format=None, date_attrs=None, time_attrs=None):
+ def __init__(
+ self,
+ attrs=None,
+ date_format=None,
+ time_format=None,
+ date_attrs=None,
+ time_attrs=None,
+ ):
widgets = (
DateInput(
attrs=attrs if date_attrs is None else date_attrs,
@@ -923,12 +991,20 @@ class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
"""
A widget that splits datetime input into two <input type="hidden"> inputs.
"""
- template_name = 'django/forms/widgets/splithiddendatetime.html'
- def __init__(self, attrs=None, date_format=None, time_format=None, date_attrs=None, time_attrs=None):
+ template_name = "django/forms/widgets/splithiddendatetime.html"
+
+ def __init__(
+ self,
+ attrs=None,
+ date_format=None,
+ time_format=None,
+ date_attrs=None,
+ time_attrs=None,
+ ):
super().__init__(attrs, date_format, time_format, date_attrs, time_attrs)
for widget in self.widgets:
- widget.input_type = 'hidden'
+ widget.input_type = "hidden"
class SelectDateWidget(Widget):
@@ -938,14 +1014,15 @@ class SelectDateWidget(Widget):
This also serves as an example of a Widget that has more than one HTML
element and hence implements value_from_datadict.
"""
- none_value = ('', '---')
- month_field = '%s_month'
- day_field = '%s_day'
- year_field = '%s_year'
- template_name = 'django/forms/widgets/select_date.html'
- input_type = 'select'
+
+ none_value = ("", "---")
+ month_field = "%s_month"
+ day_field = "%s_day"
+ year_field = "%s_year"
+ template_name = "django/forms/widgets/select_date.html"
+ input_type = "select"
select_widget = Select
- date_re = _lazy_re_compile(r'(\d{4}|0)-(\d\d?)-(\d\d?)$')
+ date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$")
def __init__(self, attrs=None, years=None, months=None, empty_label=None):
self.attrs = attrs or {}
@@ -966,14 +1043,14 @@ class SelectDateWidget(Widget):
# Optional string, list, or tuple to use as empty_label.
if isinstance(empty_label, (list, tuple)):
if not len(empty_label) == 3:
- raise ValueError('empty_label list/tuple must have 3 elements.')
+ raise ValueError("empty_label list/tuple must have 3 elements.")
- self.year_none_value = ('', empty_label[0])
- self.month_none_value = ('', empty_label[1])
- self.day_none_value = ('', empty_label[2])
+ self.year_none_value = ("", empty_label[0])
+ self.month_none_value = ("", empty_label[1])
+ self.day_none_value = ("", empty_label[2])
else:
if empty_label is not None:
- self.none_value = ('', empty_label)
+ self.none_value = ("", empty_label)
self.year_none_value = self.none_value
self.month_none_value = self.none_value
@@ -986,33 +1063,40 @@ class SelectDateWidget(Widget):
if not self.is_required:
year_choices.insert(0, self.year_none_value)
year_name = self.year_field % name
- date_context['year'] = self.select_widget(attrs, choices=year_choices).get_context(
+ date_context["year"] = self.select_widget(
+ attrs, choices=year_choices
+ ).get_context(
name=year_name,
- value=context['widget']['value']['year'],
- attrs={**context['widget']['attrs'], 'id': 'id_%s' % year_name},
+ value=context["widget"]["value"]["year"],
+ attrs={**context["widget"]["attrs"], "id": "id_%s" % year_name},
)
month_choices = list(self.months.items())
if not self.is_required:
month_choices.insert(0, self.month_none_value)
month_name = self.month_field % name
- date_context['month'] = self.select_widget(attrs, choices=month_choices).get_context(
+ date_context["month"] = self.select_widget(
+ attrs, choices=month_choices
+ ).get_context(
name=month_name,
- value=context['widget']['value']['month'],
- attrs={**context['widget']['attrs'], 'id': 'id_%s' % month_name},
+ value=context["widget"]["value"]["month"],
+ attrs={**context["widget"]["attrs"], "id": "id_%s" % month_name},
)
day_choices = [(i, i) for i in range(1, 32)]
if not self.is_required:
day_choices.insert(0, self.day_none_value)
day_name = self.day_field % name
- date_context['day'] = self.select_widget(attrs, choices=day_choices,).get_context(
+ date_context["day"] = self.select_widget(
+ attrs,
+ choices=day_choices,
+ ).get_context(
name=day_name,
- value=context['widget']['value']['day'],
- attrs={**context['widget']['attrs'], 'id': 'id_%s' % day_name},
+ value=context["widget"]["value"]["day"],
+ attrs={**context["widget"]["attrs"], "id": "id_%s" % day_name},
)
subwidgets = []
for field in self._parse_date_fmt():
- subwidgets.append(date_context[field]['widget'])
- context['widget']['subwidgets'] = subwidgets
+ subwidgets.append(date_context[field]["widget"])
+ context["widget"]["subwidgets"] = subwidgets
return context
def format_value(self, value):
@@ -1029,58 +1113,58 @@ class SelectDateWidget(Widget):
if match:
# Convert any zeros in the date to empty strings to match the
# empty option value.
- year, month, day = [int(val) or '' for val in match.groups()]
+ year, month, day = [int(val) or "" for val in match.groups()]
else:
- input_format = get_format('DATE_INPUT_FORMATS')[0]
+ input_format = get_format("DATE_INPUT_FORMATS")[0]
try:
d = datetime.datetime.strptime(value, input_format)
except ValueError:
pass
else:
year, month, day = d.year, d.month, d.day
- return {'year': year, 'month': month, 'day': day}
+ return {"year": year, "month": month, "day": day}
@staticmethod
def _parse_date_fmt():
- fmt = get_format('DATE_FORMAT')
+ fmt = get_format("DATE_FORMAT")
escaped = False
for char in fmt:
if escaped:
escaped = False
- elif char == '\\':
+ elif char == "\\":
escaped = True
- elif char in 'Yy':
- yield 'year'
- elif char in 'bEFMmNn':
- yield 'month'
- elif char in 'dj':
- yield 'day'
+ elif char in "Yy":
+ yield "year"
+ elif char in "bEFMmNn":
+ yield "month"
+ elif char in "dj":
+ yield "day"
def id_for_label(self, id_):
for first_select in self._parse_date_fmt():
- return '%s_%s' % (id_, first_select)
- return '%s_month' % id_
+ return "%s_%s" % (id_, first_select)
+ return "%s_month" % id_
def value_from_datadict(self, data, files, name):
y = data.get(self.year_field % name)
m = data.get(self.month_field % name)
d = data.get(self.day_field % name)
- if y == m == d == '':
+ if y == m == d == "":
return None
if y is not None and m is not None and d is not None:
- input_format = get_format('DATE_INPUT_FORMATS')[0]
+ input_format = get_format("DATE_INPUT_FORMATS")[0]
input_format = formats.sanitize_strftime_format(input_format)
try:
date_value = datetime.date(int(y), int(m), int(d))
except ValueError:
# Return pseudo-ISO dates with zeros for any unselected values,
# e.g. '2017-0-23'.
- return '%s-%s-%s' % (y or 0, m or 0, d or 0)
+ return "%s-%s-%s" % (y or 0, m or 0, d or 0)
return date_value.strftime(input_format)
return data.get(name)
def value_omitted_from_data(self, data, files, name):
return not any(
- ('{}_{}'.format(name, interval) in data)
- for interval in ('year', 'month', 'day')
+ ("{}_{}".format(name, interval) in data)
+ for interval in ("year", "month", "day")
)