Buckets:
| """ | |
| Field classes. | |
| """ | |
| import copy | |
| import datetime | |
| import json | |
| import math | |
| import operator | |
| import os | |
| import re | |
| import uuid | |
| from decimal import Decimal, DecimalException | |
| from io import BytesIO | |
| from urllib.parse import urlsplit, urlunsplit | |
| from django.core import validators | |
| 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, | |
| ) | |
| from django.utils import formats | |
| from django.utils.choices import normalize_choices | |
| from django.utils.dateparse import parse_datetime, parse_duration | |
| from django.utils.duration import duration_string | |
| from django.utils.ipv6 import MAX_IPV6_ADDRESS_LENGTH, clean_ipv6_address | |
| from django.utils.regex_helper import _lazy_re_compile | |
| from django.utils.translation import gettext_lazy as _ | |
| from django.utils.translation import ngettext_lazy | |
| __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", | |
| ) | |
| 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". | |
| ) | |
| 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."), | |
| } | |
| empty_values = list(validators.EMPTY_VALUES) | |
| bound_field_class = 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, | |
| template_name=None, | |
| bound_field_class=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 | |
| # be used for this Field when displaying it. Each Field has a | |
| # default Widget that it'll use if you don't specify this. In | |
| # most cases, the default widget is TextInput. | |
| # label -- A verbose name for this field, for use in displaying this | |
| # field in a form. By default, Django will use a "pretty" | |
| # version of the form field name, if the Field is part of a | |
| # Form. | |
| # initial -- A value to use in this Field's initial display. This value | |
| # is *not* used as a fallback if data isn't given. | |
| # help_text -- An optional string to use as "help text" for this Field. | |
| # error_messages -- An optional dictionary to override the default | |
| # messages that the field will raise. | |
| # show_hidden_initial -- Boolean that specifies if it is needed to | |
| # render a hidden widget with initial value | |
| # after widget. | |
| # validators -- List of additional validators to use | |
| # localize -- Boolean that specifies if the field should be localized. | |
| # disabled -- Boolean that specifies whether the field is disabled, | |
| # that is its widget is shown in the form but not editable. | |
| # label_suffix -- Suffix to be added to the label. Overrides | |
| # form's label_suffix. | |
| # bound_field_class -- BoundField class to use in | |
| # Field.get_bound_field. | |
| self.required, self.label, self.initial = required, label, initial | |
| self.show_hidden_initial = show_hidden_initial | |
| self.help_text = help_text | |
| self.disabled = disabled | |
| self.label_suffix = label_suffix | |
| self.bound_field_class = bound_field_class or self.bound_field_class | |
| widget = widget or self.widget | |
| if isinstance(widget, type): | |
| widget = widget() | |
| else: | |
| widget = copy.deepcopy(widget) | |
| # Trigger the localization machinery if needed. | |
| self.localize = localize | |
| if self.localize: | |
| widget.is_localized = True | |
| # Let the widget know whether it should display as required. | |
| widget.is_required = self.required | |
| # Hook into self.widget_attrs() for any Field-specific HTML attributes. | |
| extra_attrs = self.widget_attrs(widget) | |
| if extra_attrs: | |
| widget.attrs.update(extra_attrs) | |
| self.widget = widget | |
| messages = {} | |
| for c in reversed(self.__class__.__mro__): | |
| messages.update(getattr(c, "default_error_messages", {})) | |
| messages.update(error_messages or {}) | |
| self.error_messages = messages | |
| self.validators = [*self.default_validators, *validators] | |
| self.template_name = template_name | |
| super().__init__() | |
| def prepare_value(self, value): | |
| return value | |
| def to_python(self, value): | |
| return value | |
| def validate(self, value): | |
| if value in self.empty_values and self.required: | |
| raise ValidationError(self.error_messages["required"], code="required") | |
| def run_validators(self, value): | |
| if value in self.empty_values: | |
| return | |
| errors = [] | |
| for v in self.validators: | |
| try: | |
| v(value) | |
| except ValidationError as e: | |
| 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: | |
| raise ValidationError(errors) | |
| def clean(self, value): | |
| """ | |
| Validate the given value and return its "cleaned" value as an | |
| appropriate Python object. Raise ValidationError for any errors. | |
| """ | |
| value = self.to_python(value) | |
| self.validate(value) | |
| self.run_validators(value) | |
| return value | |
| def bound_data(self, data, initial): | |
| """ | |
| Return the value that should be shown for this field on render of a | |
| bound form, given the submitted POST data for the field and the initial | |
| data, if any. | |
| For most fields, this will simply be data; FileFields need to handle it | |
| a bit differently. | |
| """ | |
| if self.disabled: | |
| return initial | |
| return data | |
| def widget_attrs(self, widget): | |
| """ | |
| Given a Widget instance (*not* a Widget class), return a dictionary of | |
| any HTML attributes that should be added to the Widget, based on this | |
| Field. | |
| """ | |
| return {} | |
| def has_changed(self, initial, data): | |
| """Return True if data differs from initial.""" | |
| # Always return False if the field is disabled since self.bound_data | |
| # always uses the initial value in this case. | |
| if self.disabled: | |
| return False | |
| try: | |
| data = self.to_python(data) | |
| 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 "" | |
| return initial_value != data_value | |
| def get_bound_field(self, form, field_name): | |
| """ | |
| Return a BoundField instance that will be used when accessing the form | |
| field in a template. | |
| """ | |
| bound_field_class = ( | |
| self.bound_field_class or form.bound_field_class or BoundField | |
| ) | |
| return bound_field_class(form, self, field_name) | |
| def __deepcopy__(self, memo): | |
| result = copy.copy(self) | |
| memo[id(self)] = result | |
| result.widget = copy.deepcopy(self.widget, memo) | |
| result.error_messages = self.error_messages.copy() | |
| result.validators = self.validators[:] | |
| return result | |
| def _clean_bound_field(self, bf): | |
| value = bf.initial if self.disabled else bf.data | |
| return self.clean(value) | |
| class CharField(Field): | |
| 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 | |
| self.empty_value = empty_value | |
| super().__init__(**kwargs) | |
| if min_length is not None: | |
| self.validators.append(validators.MinLengthValidator(int(min_length))) | |
| if max_length is not None: | |
| self.validators.append(validators.MaxLengthValidator(int(max_length))) | |
| self.validators.append(validators.ProhibitNullCharactersValidator()) | |
| def to_python(self, value): | |
| """Return a string.""" | |
| if value not in self.empty_values: | |
| value = str(value) | |
| if self.strip: | |
| value = value.strip() | |
| if value in self.empty_values: | |
| return self.empty_value | |
| return value | |
| def widget_attrs(self, widget): | |
| 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) | |
| 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) | |
| return attrs | |
| class IntegerField(Field): | |
| widget = NumberInput | |
| default_error_messages = { | |
| "invalid": _("Enter a whole number."), | |
| } | |
| re_decimal = _lazy_re_compile(r"\.0*\s*$") | |
| def __init__(self, *, max_value=None, min_value=None, step_size=None, **kwargs): | |
| self.max_value, self.min_value, self.step_size = max_value, min_value, step_size | |
| if kwargs.get("localize") and self.widget == NumberInput: | |
| # Localized number input is not well supported on most browsers | |
| kwargs.setdefault("widget", super().widget) | |
| super().__init__(**kwargs) | |
| if max_value is not None: | |
| self.validators.append(validators.MaxValueValidator(max_value)) | |
| if min_value is not None: | |
| self.validators.append(validators.MinValueValidator(min_value)) | |
| if step_size is not None: | |
| self.validators.append( | |
| validators.StepValueValidator(step_size, offset=min_value) | |
| ) | |
| def to_python(self, value): | |
| """ | |
| Validate that int() can be called on the input. Return the result | |
| of int() or None for empty values. | |
| """ | |
| value = super().to_python(value) | |
| if value in self.empty_values: | |
| return None | |
| if self.localize: | |
| value = formats.sanitize_separators(value) | |
| # Strip trailing decimal and zeros. | |
| try: | |
| value = int(self.re_decimal.sub("", str(value))) | |
| except (ValueError, TypeError): | |
| 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 | |
| if self.max_value is not None: | |
| attrs["max"] = self.max_value | |
| if self.step_size is not None: | |
| attrs["step"] = self.step_size | |
| return attrs | |
| class FloatField(IntegerField): | |
| default_error_messages = { | |
| "invalid": _("Enter a number."), | |
| } | |
| def to_python(self, value): | |
| """ | |
| Validate that float() can be called on the input. Return the result | |
| of float() or None for empty values. | |
| """ | |
| value = super(IntegerField, self).to_python(value) | |
| if value in self.empty_values: | |
| return None | |
| if self.localize: | |
| value = formats.sanitize_separators(value) | |
| try: | |
| value = float(value) | |
| except (ValueError, TypeError): | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| return value | |
| def validate(self, value): | |
| super().validate(value) | |
| if value in self.empty_values: | |
| return | |
| if not math.isfinite(value): | |
| 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: | |
| if self.step_size is not None: | |
| step = str(self.step_size) | |
| else: | |
| step = "any" | |
| attrs.setdefault("step", step) | |
| return attrs | |
| class DecimalField(IntegerField): | |
| default_error_messages = { | |
| "invalid": _("Enter a number."), | |
| } | |
| 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)) | |
| def to_python(self, value): | |
| """ | |
| Validate that the input is a decimal number. Return a Decimal | |
| instance or None for empty values. Ensure that there are no more | |
| than max_digits in the number and no more than decimal_places digits | |
| after the decimal point. | |
| """ | |
| if value in self.empty_values: | |
| return None | |
| if self.localize: | |
| value = formats.sanitize_separators(value) | |
| try: | |
| value = Decimal(str(value)) | |
| except DecimalException: | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| return value | |
| def validate(self, value): | |
| super().validate(value) | |
| if value in self.empty_values: | |
| return | |
| if not value.is_finite(): | |
| raise ValidationError( | |
| self.error_messages["invalid"], | |
| code="invalid", | |
| params={"value": value}, | |
| ) | |
| def widget_attrs(self, widget): | |
| attrs = super().widget_attrs(widget) | |
| if isinstance(widget, NumberInput) and "step" not in widget.attrs: | |
| if 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) | |
| return attrs | |
| class BaseTemporalField(Field): | |
| def __init__(self, *, input_formats=None, **kwargs): | |
| super().__init__(**kwargs) | |
| if input_formats is not None: | |
| self.input_formats = input_formats | |
| def to_python(self, value): | |
| value = value.strip() | |
| # Try to strptime against each input format. | |
| for format in self.input_formats: | |
| try: | |
| return self.strptime(value, format) | |
| except (ValueError, TypeError): | |
| continue | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| def strptime(self, value, format): | |
| raise NotImplementedError("Subclasses must define this method.") | |
| class DateField(BaseTemporalField): | |
| widget = DateInput | |
| input_formats = formats.get_format_lazy("DATE_INPUT_FORMATS") | |
| default_error_messages = { | |
| "invalid": _("Enter a valid date."), | |
| } | |
| def to_python(self, value): | |
| """ | |
| Validate that the input can be converted to a date. Return a Python | |
| datetime.date object. | |
| """ | |
| if value in self.empty_values: | |
| return None | |
| if isinstance(value, datetime.datetime): | |
| return value.date() | |
| if isinstance(value, datetime.date): | |
| return value | |
| return super().to_python(value) | |
| def strptime(self, value, format): | |
| return datetime.datetime.strptime(value, format).date() | |
| class TimeField(BaseTemporalField): | |
| widget = TimeInput | |
| input_formats = formats.get_format_lazy("TIME_INPUT_FORMATS") | |
| default_error_messages = {"invalid": _("Enter a valid time.")} | |
| def to_python(self, value): | |
| """ | |
| Validate that the input can be converted to a time. Return a Python | |
| datetime.time object. | |
| """ | |
| if value in self.empty_values: | |
| return None | |
| if isinstance(value, datetime.time): | |
| return value | |
| return super().to_python(value) | |
| def strptime(self, value, format): | |
| return datetime.datetime.strptime(value, format).time() | |
| class DateTimeFormatsIterator: | |
| def __iter__(self): | |
| 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."), | |
| } | |
| def prepare_value(self, value): | |
| if isinstance(value, datetime.datetime): | |
| value = to_current_timezone(value) | |
| return value | |
| def to_python(self, value): | |
| """ | |
| Validate that the input can be converted to a datetime. Return a | |
| Python datetime.datetime object. | |
| """ | |
| if value in self.empty_values: | |
| return None | |
| if isinstance(value, datetime.datetime): | |
| return from_current_timezone(value) | |
| if isinstance(value, datetime.date): | |
| result = datetime.datetime(value.year, value.month, value.day) | |
| return from_current_timezone(result) | |
| try: | |
| result = parse_datetime(value.strip()) | |
| except ValueError: | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| if not result: | |
| result = super().to_python(value) | |
| return from_current_timezone(result) | |
| def strptime(self, value, format): | |
| return datetime.datetime.strptime(value, format) | |
| class DurationField(Field): | |
| default_error_messages = { | |
| "invalid": _("Enter a valid duration."), | |
| "overflow": _("The number of days must be between {min_days} and {max_days}."), | |
| } | |
| def prepare_value(self, value): | |
| if isinstance(value, datetime.timedelta): | |
| return duration_string(value) | |
| return value | |
| def to_python(self, value): | |
| if value in self.empty_values: | |
| return None | |
| if isinstance(value, datetime.timedelta): | |
| return value | |
| 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", | |
| ) | |
| if value is None: | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| return value | |
| class RegexField(CharField): | |
| def __init__(self, regex, **kwargs): | |
| """ | |
| regex can be either a string or a compiled regular expression object. | |
| """ | |
| kwargs.setdefault("strip", False) | |
| super().__init__(**kwargs) | |
| self._set_regex(regex) | |
| def _get_regex(self): | |
| return self._regex | |
| def _set_regex(self, regex): | |
| if isinstance(regex, str): | |
| regex = re.compile(regex) | |
| self._regex = regex | |
| 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) | |
| regex = property(_get_regex, _set_regex) | |
| class EmailField(CharField): | |
| widget = EmailInput | |
| default_validators = [validators.validate_email] | |
| def __init__(self, **kwargs): | |
| # The default maximum length of an email is 320 characters per RFC 3696 | |
| # section 3. | |
| kwargs.setdefault("max_length", 320) | |
| super().__init__(strip=True, **kwargs) | |
| 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." | |
| ), | |
| } | |
| def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs): | |
| self.max_length = max_length | |
| self.allow_empty_file = allow_empty_file | |
| super().__init__(**kwargs) | |
| def to_python(self, data): | |
| if data in self.empty_values: | |
| return None | |
| # UploadedFile objects should have name and size attributes. | |
| try: | |
| file_name = data.name | |
| file_size = data.size | |
| except AttributeError: | |
| 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 | |
| ) | |
| if not file_name: | |
| 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") | |
| 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" | |
| ) | |
| # False means the field value should be cleared; further validation is | |
| # not needed. | |
| if data is False: | |
| if not self.required: | |
| return False | |
| # If the field is required, clearing is not possible (the widget | |
| # shouldn't return False data in that case anyway). False is not | |
| # in self.empty_value; if a False value makes it this far | |
| # it should be validated from here on out as None (so it will be | |
| # caught by the required check). | |
| data = None | |
| if not data and initial: | |
| return initial | |
| return super().clean(data) | |
| def bound_data(self, _, initial): | |
| return initial | |
| def has_changed(self, initial, data): | |
| return not self.disabled and data is not None | |
| def _clean_bound_field(self, bf): | |
| value = bf.initial if self.disabled else bf.data | |
| return self.clean(value, bf.initial) | |
| class ImageField(FileField): | |
| default_validators = [validators.validate_image_file_extension] | |
| default_error_messages = { | |
| "invalid_image": _( | |
| "Upload a valid image. The file you uploaded was either not an " | |
| "image or a corrupted image." | |
| ), | |
| } | |
| def to_python(self, data): | |
| """ | |
| Check that the file-upload field data contains a valid image (GIF, JPG, | |
| PNG, etc. -- whatever Pillow supports). | |
| """ | |
| f = super().to_python(data) | |
| if f is None: | |
| return None | |
| from PIL import Image | |
| # 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"): | |
| file = data.temporary_file_path() | |
| else: | |
| if hasattr(data, "read"): | |
| file = BytesIO(data.read()) | |
| else: | |
| file = BytesIO(data["content"]) | |
| try: | |
| # load() could spot a truncated JPEG, but it loads the entire | |
| # image in memory, which is a DoS vector. See #3848 and #18520. | |
| image = Image.open(file) | |
| # verify() must be called immediately after the constructor. | |
| image.verify() | |
| # Annotating so subclasses can reuse it for their own validation | |
| f.image = image | |
| # Pillow doesn't detect the MIME type of all formats. In those | |
| # cases, content_type will be None. | |
| f.content_type = Image.MIME.get(image.format) | |
| except Exception as exc: | |
| # Pillow doesn't recognize it as an image. | |
| raise ValidationError( | |
| self.error_messages["invalid_image"], | |
| code="invalid_image", | |
| ) from exc | |
| 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/*") | |
| return attrs | |
| class URLField(CharField): | |
| widget = URLInput | |
| default_error_messages = { | |
| "invalid": _("Enter a valid URL."), | |
| } | |
| default_validators = [validators.URLValidator()] | |
| def __init__(self, *, assume_scheme=None, **kwargs): | |
| self.assume_scheme = assume_scheme or "https" | |
| super().__init__(strip=True, **kwargs) | |
| def to_python(self, value): | |
| def split_url(url): | |
| """ | |
| Return a list of url parts via urlsplit(), or raise | |
| ValidationError for some malformed URLs. | |
| """ | |
| try: | |
| return list(urlsplit(url)) | |
| except ValueError: | |
| # urlsplit can raise a ValueError with some | |
| # misformatted URLs. | |
| 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, add a scheme. | |
| url_fields[0] = self.assume_scheme | |
| 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] = "" | |
| # Rebuild the url_fields list, since the domain segment may now | |
| # contain the path too. | |
| url_fields = split_url(urlunsplit(url_fields)) | |
| value = urlunsplit(url_fields) | |
| return value | |
| class BooleanField(Field): | |
| widget = CheckboxInput | |
| def to_python(self, value): | |
| """Return a Python boolean object.""" | |
| # Explicitly check for the string 'False', which is what a hidden 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"): | |
| value = False | |
| else: | |
| value = bool(value) | |
| return super().to_python(value) | |
| def validate(self, value): | |
| if not value and self.required: | |
| raise ValidationError(self.error_messages["required"], code="required") | |
| def has_changed(self, initial, data): | |
| if self.disabled: | |
| return False | |
| # Sometimes data or initial may be a string equivalent of a boolean | |
| # so we should run it through to_python first to get a boolean value | |
| return self.to_python(initial) != self.to_python(data) | |
| 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): | |
| """ | |
| Explicitly check for the string 'True' and 'False', which is what a | |
| hidden field will submit for True and False, for 'true' and 'false', | |
| which are likely to be returned by JavaScript serializations of forms, | |
| and for '1' and '0', which is what a RadioField will submit. Unlike | |
| the Booleanfield, this field must check for True because it doesn't | |
| use the bool() function. | |
| """ | |
| if value in (True, "True", "true", "1"): | |
| return True | |
| elif value in (False, "False", "false", "0"): | |
| return False | |
| else: | |
| return None | |
| def validate(self, value): | |
| pass | |
| class ChoiceField(Field): | |
| widget = Select | |
| default_error_messages = { | |
| "invalid_choice": _( | |
| "Select a valid choice. %(value)s is not one of the available choices." | |
| ), | |
| } | |
| def __init__(self, *, choices=(), **kwargs): | |
| super().__init__(**kwargs) | |
| self.choices = choices | |
| def __deepcopy__(self, memo): | |
| result = super().__deepcopy__(memo) | |
| result._choices = copy.deepcopy(self._choices, memo) | |
| return result | |
| def choices(self): | |
| return self._choices | |
| def choices(self, value): | |
| # Setting choices on the field also sets the choices on the widget. | |
| # Note that the property setter for the widget will re-normalize. | |
| self._choices = self.widget.choices = normalize_choices(value) | |
| def to_python(self, value): | |
| """Return a string.""" | |
| if value in self.empty_values: | |
| return "" | |
| return str(value) | |
| def validate(self, value): | |
| """Validate that the input is in self.choices.""" | |
| super().validate(value) | |
| if value and not self.valid_value(value): | |
| raise ValidationError( | |
| self.error_messages["invalid_choice"], | |
| code="invalid_choice", | |
| params={"value": value}, | |
| ) | |
| def valid_value(self, value): | |
| """Check to see if the provided value is a valid choice.""" | |
| text_value = str(value) | |
| for k, v in self.choices: | |
| if isinstance(v, (list, tuple)): | |
| # This is an optgroup, so look inside the group for options | |
| for k2, v2 in v: | |
| if value == k2 or text_value == str(k2): | |
| return True | |
| else: | |
| if value == k or text_value == str(k): | |
| return True | |
| return False | |
| class TypedChoiceField(ChoiceField): | |
| def __init__(self, *, coerce=lambda val: val, empty_value="", **kwargs): | |
| self.coerce = coerce | |
| self.empty_value = empty_value | |
| super().__init__(**kwargs) | |
| def _coerce(self, value): | |
| """ | |
| Validate that the value can be coerced to the right type (if not | |
| empty). | |
| """ | |
| if value == self.empty_value or value in self.empty_values: | |
| return self.empty_value | |
| try: | |
| value = self.coerce(value) | |
| except (ValueError, TypeError, ValidationError): | |
| raise ValidationError( | |
| self.error_messages["invalid_choice"], | |
| code="invalid_choice", | |
| params={"value": value}, | |
| ) | |
| return value | |
| def clean(self, value): | |
| value = super().clean(value) | |
| return self._coerce(value) | |
| 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."), | |
| } | |
| 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" | |
| ) | |
| 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") | |
| # 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}, | |
| ) | |
| def has_changed(self, initial, data): | |
| if self.disabled: | |
| return False | |
| if initial is None: | |
| initial = [] | |
| if data is None: | |
| data = [] | |
| if len(initial) != len(data): | |
| return True | |
| initial_set = {str(value) for value in initial} | |
| data_set = {str(value) for value in data} | |
| return data_set != initial_set | |
| class TypedMultipleChoiceField(MultipleChoiceField): | |
| def __init__(self, *, coerce=lambda val: val, **kwargs): | |
| self.coerce = coerce | |
| self.empty_value = kwargs.pop("empty_value", []) | |
| super().__init__(**kwargs) | |
| def _coerce(self, value): | |
| """ | |
| Validate that the values are in self.choices and can be coerced to the | |
| right type. | |
| """ | |
| if value == self.empty_value or value in self.empty_values: | |
| return self.empty_value | |
| new_value = [] | |
| for choice in value: | |
| try: | |
| new_value.append(self.coerce(choice)) | |
| except (ValueError, TypeError, ValidationError): | |
| raise ValidationError( | |
| self.error_messages["invalid_choice"], | |
| code="invalid_choice", | |
| params={"value": choice}, | |
| ) | |
| return new_value | |
| def clean(self, value): | |
| value = super().clean(value) | |
| return self._coerce(value) | |
| def validate(self, value): | |
| if value != self.empty_value: | |
| super().validate(value) | |
| elif self.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 | |
| # required validation will be handled by ComboField, not by those | |
| # individual fields. | |
| for f in fields: | |
| f.required = False | |
| self.fields = fields | |
| def clean(self, value): | |
| """ | |
| Validate the given value against all of self.fields, which is a | |
| list of Field instances. | |
| """ | |
| super().clean(value) | |
| for field in self.fields: | |
| value = field.clean(value) | |
| return value | |
| class MultiValueField(Field): | |
| """ | |
| Aggregate the logic of multiple Fields. | |
| Its clean() method takes a "decompressed" list of values, which are then | |
| cleaned into a single value according to self.fields. Each value in | |
| this list is cleaned by the corresponding field -- the first value is | |
| cleaned by the first field, the second value is cleaned by the second | |
| field, etc. Once all fields are cleaned, the list of clean values is | |
| "compressed" into a single value. | |
| Subclasses should not have to implement clean(). Instead, they must | |
| implement compress(), which takes a list of valid values and returns a | |
| "compressed" version of those values -- a single value. | |
| You'll probably want to use this with MultiWidget. | |
| """ | |
| default_error_messages = { | |
| "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"]) | |
| if self.disabled: | |
| f.disabled = True | |
| if self.require_all_fields: | |
| # Set 'required' to False on the individual fields, because the | |
| # required validation will be handled by MultiValueField, not | |
| # by those individual fields. | |
| f.required = False | |
| self.fields = fields | |
| def __deepcopy__(self, memo): | |
| result = super().__deepcopy__(memo) | |
| result.fields = tuple(x.__deepcopy__(memo) for x in self.fields) | |
| return result | |
| def validate(self, value): | |
| pass | |
| def clean(self, value): | |
| """ | |
| Validate every value in the given list. A value is validated against | |
| the corresponding Field in self.fields. | |
| For example, if this MultiValueField was instantiated with | |
| fields=(DateField(), TimeField()), clean() would call | |
| DateField.clean(value[0]) and TimeField.clean(value[1]). | |
| """ | |
| clean_data = [] | |
| errors = [] | |
| if self.disabled and not isinstance(value, list): | |
| value = self.widget.decompress(value) | |
| 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" | |
| ) | |
| else: | |
| return self.compress([]) | |
| else: | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| for i, field in enumerate(self.fields): | |
| try: | |
| field_value = value[i] | |
| except IndexError: | |
| field_value = None | |
| if field_value in self.empty_values: | |
| if self.require_all_fields: | |
| # 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" | |
| ) | |
| 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"]) | |
| continue | |
| try: | |
| clean_data.append(field.clean(field_value)) | |
| except ValidationError as e: | |
| # Collect all validation errors in a single list, which we'll | |
| # raise at the end of clean(), rather than raising a single | |
| # exception for the first error we encounter. Skip duplicates. | |
| errors.extend(m for m in e.error_list if m not in errors) | |
| if errors: | |
| raise ValidationError(errors) | |
| out = self.compress(clean_data) | |
| self.validate(out) | |
| self.run_validators(out) | |
| return out | |
| def compress(self, data_list): | |
| """ | |
| Return a single value for the given list of values. The values can be | |
| assumed to be valid. | |
| For example, if this MultiValueField was instantiated with | |
| 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.") | |
| def has_changed(self, initial, data): | |
| if self.disabled: | |
| return False | |
| if initial is None: | |
| initial = ["" for x in range(0, len(data))] | |
| else: | |
| if not isinstance(initial, list): | |
| initial = self.widget.decompress(initial) | |
| for field, initial, data in zip(self.fields, initial, data): | |
| try: | |
| initial = field.to_python(initial) | |
| except ValidationError: | |
| return True | |
| if field.has_changed(initial, data): | |
| return True | |
| return False | |
| class FilePathField(ChoiceField): | |
| 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) | |
| if self.required: | |
| self.choices = [] | |
| else: | |
| self.choices = [("", "---------")] | |
| if self.match is not None: | |
| self.match_re = re.compile(self.match) | |
| if recursive: | |
| for root, dirs, files in sorted(os.walk(self.path)): | |
| if self.allow_files: | |
| for f in sorted(files): | |
| if self.match is None or self.match_re.search(f): | |
| f = os.path.join(root, f) | |
| self.choices.append((f, f.replace(path, "", 1))) | |
| if self.allow_folders: | |
| for f in sorted(dirs): | |
| if f == "__pycache__": | |
| continue | |
| if self.match is None or self.match_re.search(f): | |
| f = os.path.join(root, f) | |
| self.choices.append((f, f.replace(path, "", 1))) | |
| else: | |
| choices = [] | |
| with os.scandir(self.path) as entries: | |
| for f in entries: | |
| if f.name == "__pycache__": | |
| continue | |
| if ( | |
| (self.allow_files and f.is_file()) | |
| or (self.allow_folders and f.is_dir()) | |
| ) and (self.match is None or self.match_re.search(f.name)): | |
| choices.append((f.path, f.name)) | |
| choices.sort(key=operator.itemgetter(1)) | |
| self.choices.extend(choices) | |
| self.widget.choices = self.choices | |
| class SplitDateTimeField(MultiValueField): | |
| widget = SplitDateTimeWidget | |
| hidden_widget = SplitHiddenDateTimeWidget | |
| default_error_messages = { | |
| "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) | |
| 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, | |
| ), | |
| ) | |
| super().__init__(fields, **kwargs) | |
| def compress(self, data_list): | |
| if data_list: | |
| # 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" | |
| ) | |
| if data_list[1] in self.empty_values: | |
| 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): | |
| self.unpack_ipv4 = unpack_ipv4 | |
| self.default_validators = validators.ip_address_validators( | |
| protocol, unpack_ipv4 | |
| ) | |
| kwargs.setdefault("max_length", MAX_IPV6_ADDRESS_LENGTH) | |
| super().__init__(**kwargs) | |
| def to_python(self, value): | |
| if value in self.empty_values: | |
| return "" | |
| value = value.strip() | |
| if value and ":" in value: | |
| return clean_ipv6_address( | |
| value, self.unpack_ipv4, max_length=self.max_length | |
| ) | |
| return value | |
| class SlugField(CharField): | |
| default_validators = [validators.validate_slug] | |
| def __init__(self, *, allow_unicode=False, **kwargs): | |
| self.allow_unicode = allow_unicode | |
| if self.allow_unicode: | |
| self.default_validators = [validators.validate_unicode_slug] | |
| super().__init__(**kwargs) | |
| class UUIDField(CharField): | |
| default_error_messages = { | |
| "invalid": _("Enter a valid UUID."), | |
| } | |
| def prepare_value(self, value): | |
| if isinstance(value, uuid.UUID): | |
| return str(value) | |
| return value | |
| def to_python(self, value): | |
| value = super().to_python(value) | |
| if value in self.empty_values: | |
| return None | |
| if not isinstance(value, uuid.UUID): | |
| try: | |
| value = uuid.UUID(value) | |
| except ValueError: | |
| raise ValidationError(self.error_messages["invalid"], code="invalid") | |
| return value | |
| class InvalidJSONInput(str): | |
| pass | |
| class JSONString(str): | |
| pass | |
| class JSONField(CharField): | |
| default_error_messages = { | |
| "invalid": _("Enter a valid JSON."), | |
| } | |
| widget = Textarea | |
| def __init__(self, encoder=None, decoder=None, **kwargs): | |
| self.encoder = encoder | |
| self.decoder = decoder | |
| super().__init__(**kwargs) | |
| def to_python(self, value): | |
| if self.disabled: | |
| return value | |
| if value in self.empty_values: | |
| return None | |
| elif isinstance(value, (list, dict, int, float, JSONString)): | |
| return value | |
| try: | |
| converted = json.loads(value, cls=self.decoder) | |
| except json.JSONDecodeError: | |
| raise ValidationError( | |
| self.error_messages["invalid"], | |
| code="invalid", | |
| params={"value": value}, | |
| ) | |
| if isinstance(converted, str): | |
| return JSONString(converted) | |
| else: | |
| return converted | |
| def bound_data(self, data, initial): | |
| if self.disabled: | |
| return initial | |
| if data is None: | |
| return None | |
| try: | |
| return json.loads(data, cls=self.decoder) | |
| except json.JSONDecodeError: | |
| return InvalidJSONInput(data) | |
| def prepare_value(self, value): | |
| if isinstance(value, InvalidJSONInput): | |
| return value | |
| return json.dumps(value, ensure_ascii=False, cls=self.encoder) | |
| def has_changed(self, initial, data): | |
| if super().has_changed(initial, data): | |
| 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 | |
| ) | |
Xet Storage Details
- Size:
- 49.1 kB
- Xet hash:
- aa097811d08c00cdbc26f6236ec6126992490cb6451a8d9ed4407c377819608c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.