diff options
Diffstat (limited to 'django/core/validators.py')
| -rw-r--r-- | django/core/validators.py | 374 |
1 files changed, 210 insertions, 164 deletions
diff --git a/django/core/validators.py b/django/core/validators.py index 9ad90f006f..5272258a77 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -8,21 +8,24 @@ from django.utils.deconstruct import deconstructible from django.utils.encoding import punycode from django.utils.ipv6 import is_valid_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 # These values, if given to validate(), will trigger the self.required check. -EMPTY_VALUES = (None, '', [], (), {}) +EMPTY_VALUES = (None, "", [], (), {}) @deconstructible class RegexValidator: - regex = '' - message = _('Enter a valid value.') - code = 'invalid' + regex = "" + message = _("Enter a valid value.") + code = "invalid" inverse_match = False flags = 0 - def __init__(self, regex=None, message=None, code=None, inverse_match=None, flags=None): + def __init__( + self, regex=None, message=None, code=None, inverse_match=None, flags=None + ): if regex is not None: self.regex = regex if message is not None: @@ -34,7 +37,9 @@ class RegexValidator: if flags is not None: self.flags = flags if self.flags and not isinstance(self.regex, str): - raise TypeError("If the flags are set, regex must be a regular expression string.") + raise TypeError( + "If the flags are set, regex must be a regular expression string." + ) self.regex = _lazy_re_compile(self.regex, self.flags) @@ -46,54 +51,58 @@ class RegexValidator: regex_matches = self.regex.search(str(value)) invalid_input = regex_matches if self.inverse_match else not regex_matches if invalid_input: - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) def __eq__(self, other): return ( - isinstance(other, RegexValidator) and - self.regex.pattern == other.regex.pattern and - self.regex.flags == other.regex.flags and - (self.message == other.message) and - (self.code == other.code) and - (self.inverse_match == other.inverse_match) + isinstance(other, RegexValidator) + and self.regex.pattern == other.regex.pattern + and self.regex.flags == other.regex.flags + and (self.message == other.message) + and (self.code == other.code) + and (self.inverse_match == other.inverse_match) ) @deconstructible class URLValidator(RegexValidator): - ul = '\u00a1-\uffff' # Unicode letters range (must not be a raw string). + ul = "\u00a1-\uffff" # Unicode letters range (must not be a raw string). # IP patterns ipv4_re = ( - r'(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)' - r'(?:\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}' + r"(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)" + r"(?:\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}" ) - ipv6_re = r'\[[0-9a-f:.]+\]' # (simple regex, validated later) + ipv6_re = r"\[[0-9a-f:.]+\]" # (simple regex, validated later) # Host patterns - hostname_re = r'[a-z' + ul + r'0-9](?:[a-z' + ul + r'0-9-]{0,61}[a-z' + ul + r'0-9])?' + hostname_re = ( + r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?" + ) # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1 - domain_re = r'(?:\.(?!-)[a-z' + ul + r'0-9-]{1,63}(?<!-))*' + domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*" tld_re = ( - r'\.' # dot - r'(?!-)' # can't start with a dash - r'(?:[a-z' + ul + '-]{2,63}' # domain label - r'|xn--[a-z0-9]{1,59})' # or punycode label - r'(?<!-)' # can't end with a dash - r'\.?' # may have a trailing dot + r"\." # dot + r"(?!-)" # can't start with a dash + r"(?:[a-z" + ul + "-]{2,63}" # domain label + r"|xn--[a-z0-9]{1,59})" # or punycode label + r"(?<!-)" # can't end with a dash + r"\.?" # may have a trailing dot ) - host_re = '(' + hostname_re + domain_re + tld_re + '|localhost)' + host_re = "(" + hostname_re + domain_re + tld_re + "|localhost)" regex = _lazy_re_compile( - r'^(?:[a-z0-9.+-]*)://' # scheme is validated separately - r'(?:[^\s:@/]+(?::[^\s:@/]*)?@)?' # user:pass authentication - r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')' - r'(?::[0-9]{1,5})?' # port - r'(?:[/?#][^\s]*)?' # resource path - r'\Z', re.IGNORECASE) - message = _('Enter a valid URL.') - schemes = ['http', 'https', 'ftp', 'ftps'] - unsafe_chars = frozenset('\t\r\n') + r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately + r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication + r"(?:" + ipv4_re + "|" + ipv6_re + "|" + host_re + ")" + r"(?::[0-9]{1,5})?" # port + r"(?:[/?#][^\s]*)?" # resource path + r"\Z", + re.IGNORECASE, + ) + message = _("Enter a valid URL.") + schemes = ["http", "https", "ftp", "ftps"] + unsafe_chars = frozenset("\t\r\n") def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs) @@ -102,19 +111,19 @@ class URLValidator(RegexValidator): def __call__(self, value): if not isinstance(value, str): - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) if self.unsafe_chars.intersection(value): - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) # Check if the scheme is valid. - scheme = value.split('://')[0].lower() + scheme = value.split("://")[0].lower() if scheme not in self.schemes: - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) # Then check full URL try: splitted_url = urlsplit(value) except ValueError: - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) try: super().__call__(value) except ValidationError as e: @@ -131,26 +140,28 @@ class URLValidator(RegexValidator): raise else: # Now verify IPv6 in the netloc part - host_match = re.search(r'^\[(.+)\](?::[0-9]{1,5})?$', splitted_url.netloc) + host_match = re.search(r"^\[(.+)\](?::[0-9]{1,5})?$", splitted_url.netloc) if host_match: potential_ip = host_match[1] try: validate_ipv6_address(potential_ip) except ValidationError: - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError( + self.message, code=self.code, params={"value": value} + ) # The maximum length of a full host name is 253 characters per RFC 1034 # section 3.1. It's defined to be 255 bytes or less, but this includes # one byte for the length of the name and one byte for the trailing dot # that's used to indicate absolute names in DNS. if splitted_url.hostname is None or len(splitted_url.hostname) > 253: - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) integer_validator = RegexValidator( - _lazy_re_compile(r'^-?\d+\Z'), - message=_('Enter a valid integer.'), - code='invalid', + _lazy_re_compile(r"^-?\d+\Z"), + message=_("Enter a valid integer."), + code="invalid", ) @@ -160,21 +171,24 @@ def validate_integer(value): @deconstructible class EmailValidator: - message = _('Enter a valid email address.') - code = 'invalid' + message = _("Enter a valid email address.") + code = "invalid" user_regex = _lazy_re_compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string - re.IGNORECASE) + re.IGNORECASE, + ) domain_regex = _lazy_re_compile( # max length for domain name labels is 63 characters per RFC 1034 - r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z', - re.IGNORECASE) + r"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z", + re.IGNORECASE, + ) literal_regex = _lazy_re_compile( # literal form, ipv4 or ipv6 address (SMTP 4.1.3) - r'\[([A-F0-9:.]+)\]\Z', - re.IGNORECASE) - domain_allowlist = ['localhost'] + r"\[([A-F0-9:.]+)\]\Z", + re.IGNORECASE, + ) + domain_allowlist = ["localhost"] def __init__(self, message=None, code=None, allowlist=None): if message is not None: @@ -185,16 +199,17 @@ class EmailValidator: self.domain_allowlist = allowlist def __call__(self, value): - if not value or '@' not in value: - raise ValidationError(self.message, code=self.code, params={'value': value}) + if not value or "@" not in value: + raise ValidationError(self.message, code=self.code, params={"value": value}) - user_part, domain_part = value.rsplit('@', 1) + user_part, domain_part = value.rsplit("@", 1) if not self.user_regex.match(user_part): - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) - if (domain_part not in self.domain_allowlist and - not self.validate_domain_part(domain_part)): + if domain_part not in self.domain_allowlist and not self.validate_domain_part( + domain_part + ): # Try for possible IDN domain-part try: domain_part = punycode(domain_part) @@ -203,7 +218,7 @@ class EmailValidator: else: if self.validate_domain_part(domain_part): return - raise ValidationError(self.message, code=self.code, params={'value': value}) + raise ValidationError(self.message, code=self.code, params={"value": value}) def validate_domain_part(self, domain_part): if self.domain_regex.match(domain_part): @@ -221,28 +236,30 @@ class EmailValidator: def __eq__(self, other): return ( - isinstance(other, EmailValidator) and - (self.domain_allowlist == other.domain_allowlist) and - (self.message == other.message) and - (self.code == other.code) + isinstance(other, EmailValidator) + and (self.domain_allowlist == other.domain_allowlist) + and (self.message == other.message) + and (self.code == other.code) ) validate_email = EmailValidator() -slug_re = _lazy_re_compile(r'^[-a-zA-Z0-9_]+\Z') +slug_re = _lazy_re_compile(r"^[-a-zA-Z0-9_]+\Z") validate_slug = RegexValidator( slug_re, # Translators: "letters" means latin letters: a-z and A-Z. - _('Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.'), - 'invalid' + _("Enter a valid “slug” consisting of letters, numbers, underscores or hyphens."), + "invalid", ) -slug_unicode_re = _lazy_re_compile(r'^[-\w]+\Z') +slug_unicode_re = _lazy_re_compile(r"^[-\w]+\Z") validate_unicode_slug = RegexValidator( slug_unicode_re, - _('Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.'), - 'invalid' + _( + "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens." + ), + "invalid", ) @@ -250,25 +267,26 @@ def validate_ipv4_address(value): try: ipaddress.IPv4Address(value) except ValueError: - raise ValidationError(_('Enter a valid IPv4 address.'), code='invalid', params={'value': value}) + raise ValidationError( + _("Enter a valid IPv4 address."), code="invalid", params={"value": value} + ) else: # Leading zeros are forbidden to avoid ambiguity with the octal # notation. This restriction is included in Python 3.9.5+. # TODO: Remove when dropping support for PY39. - if any( - octet != '0' and octet[0] == '0' - for octet in value.split('.') - ): + if any(octet != "0" and octet[0] == "0" for octet in value.split(".")): raise ValidationError( - _('Enter a valid IPv4 address.'), - code='invalid', - params={'value': value}, + _("Enter a valid IPv4 address."), + code="invalid", + params={"value": value}, ) def validate_ipv6_address(value): if not is_valid_ipv6_address(value): - raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid', params={'value': value}) + raise ValidationError( + _("Enter a valid IPv6 address."), code="invalid", params={"value": value} + ) def validate_ipv46_address(value): @@ -278,13 +296,17 @@ def validate_ipv46_address(value): try: validate_ipv6_address(value) except ValidationError: - raise ValidationError(_('Enter a valid IPv4 or IPv6 address.'), code='invalid', params={'value': value}) + raise ValidationError( + _("Enter a valid IPv4 or IPv6 address."), + code="invalid", + params={"value": value}, + ) ip_address_validator_map = { - 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')), - 'ipv4': ([validate_ipv4_address], _('Enter a valid IPv4 address.')), - 'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')), + "both": ([validate_ipv46_address], _("Enter a valid IPv4 or IPv6 address.")), + "ipv4": ([validate_ipv4_address], _("Enter a valid IPv4 address.")), + "ipv6": ([validate_ipv6_address], _("Enter a valid IPv6 address.")), } @@ -293,33 +315,39 @@ def ip_address_validators(protocol, unpack_ipv4): Depending on the given parameters, return the appropriate validators for the GenericIPAddressField. """ - if protocol != 'both' and unpack_ipv4: + if protocol != "both" and unpack_ipv4: raise ValueError( - "You can only use `unpack_ipv4` if `protocol` is set to 'both'") + "You can only use `unpack_ipv4` if `protocol` is set to 'both'" + ) try: return ip_address_validator_map[protocol.lower()] except KeyError: - raise ValueError("The protocol '%s' is unknown. Supported: %s" - % (protocol, list(ip_address_validator_map))) + raise ValueError( + "The protocol '%s' is unknown. Supported: %s" + % (protocol, list(ip_address_validator_map)) + ) -def int_list_validator(sep=',', message=None, code='invalid', allow_negative=False): - regexp = _lazy_re_compile(r'^%(neg)s\d+(?:%(sep)s%(neg)s\d+)*\Z' % { - 'neg': '(-)?' if allow_negative else '', - 'sep': re.escape(sep), - }) +def int_list_validator(sep=",", message=None, code="invalid", allow_negative=False): + regexp = _lazy_re_compile( + r"^%(neg)s\d+(?:%(sep)s%(neg)s\d+)*\Z" + % { + "neg": "(-)?" if allow_negative else "", + "sep": re.escape(sep), + } + ) return RegexValidator(regexp, message=message, code=code) validate_comma_separated_integer_list = int_list_validator( - message=_('Enter only digits separated by commas.'), + message=_("Enter only digits separated by commas."), ) @deconstructible class BaseValidator: - message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).') - code = 'limit_value' + message = _("Ensure this value is %(limit_value)s (it is %(show_value)s).") + code = "limit_value" def __init__(self, limit_value, message=None): self.limit_value = limit_value @@ -328,8 +356,10 @@ class BaseValidator: def __call__(self, value): cleaned = self.clean(value) - limit_value = self.limit_value() if callable(self.limit_value) else self.limit_value - params = {'limit_value': limit_value, 'show_value': cleaned, 'value': value} + limit_value = ( + self.limit_value() if callable(self.limit_value) else self.limit_value + ) + params = {"limit_value": limit_value, "show_value": cleaned, "value": value} if self.compare(cleaned, limit_value): raise ValidationError(self.message, code=self.code, params=params) @@ -337,9 +367,9 @@ class BaseValidator: if not isinstance(other, self.__class__): return NotImplemented return ( - self.limit_value == other.limit_value and - self.message == other.message and - self.code == other.code + self.limit_value == other.limit_value + and self.message == other.message + and self.code == other.code ) def compare(self, a, b): @@ -351,8 +381,8 @@ class BaseValidator: @deconstructible class MaxValueValidator(BaseValidator): - message = _('Ensure this value is less than or equal to %(limit_value)s.') - code = 'max_value' + message = _("Ensure this value is less than or equal to %(limit_value)s.") + code = "max_value" def compare(self, a, b): return a > b @@ -360,8 +390,8 @@ class MaxValueValidator(BaseValidator): @deconstructible class MinValueValidator(BaseValidator): - message = _('Ensure this value is greater than or equal to %(limit_value)s.') - code = 'min_value' + message = _("Ensure this value is greater than or equal to %(limit_value)s.") + code = "min_value" def compare(self, a, b): return a < b @@ -370,10 +400,11 @@ class MinValueValidator(BaseValidator): @deconstructible class MinLengthValidator(BaseValidator): message = ngettext_lazy( - 'Ensure this value has at least %(limit_value)d character (it has %(show_value)d).', - 'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).', - 'limit_value') - code = 'min_length' + "Ensure this value has at least %(limit_value)d character (it has %(show_value)d).", + "Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).", + "limit_value", + ) + code = "min_length" def compare(self, a, b): return a < b @@ -385,10 +416,11 @@ class MinLengthValidator(BaseValidator): @deconstructible class MaxLengthValidator(BaseValidator): message = ngettext_lazy( - 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).', - 'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).', - 'limit_value') - code = 'max_length' + "Ensure this value has at most %(limit_value)d character (it has %(show_value)d).", + "Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).", + "limit_value", + ) + code = "max_length" def compare(self, a, b): return a > b @@ -403,22 +435,23 @@ class DecimalValidator: Validate that the input does not exceed the maximum number of digits expected, otherwise raise ValidationError. """ + messages = { - 'invalid': _('Enter a number.'), - 'max_digits': ngettext_lazy( - 'Ensure that there are no more than %(max)s digit in total.', - 'Ensure that there are no more than %(max)s digits in total.', - 'max' + "invalid": _("Enter a number."), + "max_digits": ngettext_lazy( + "Ensure that there are no more than %(max)s digit in total.", + "Ensure that there are no more than %(max)s digits in total.", + "max", ), - 'max_decimal_places': ngettext_lazy( - 'Ensure that there are no more than %(max)s decimal place.', - 'Ensure that there are no more than %(max)s decimal places.', - 'max' + "max_decimal_places": ngettext_lazy( + "Ensure that there are no more than %(max)s decimal place.", + "Ensure that there are no more than %(max)s decimal places.", + "max", ), - 'max_whole_digits': ngettext_lazy( - 'Ensure that there are no more than %(max)s digit before the decimal point.', - 'Ensure that there are no more than %(max)s digits before the decimal point.', - 'max' + "max_whole_digits": ngettext_lazy( + "Ensure that there are no more than %(max)s digit before the decimal point.", + "Ensure that there are no more than %(max)s digits before the decimal point.", + "max", ), } @@ -428,8 +461,10 @@ class DecimalValidator: def __call__(self, value): digit_tuple, exponent = value.as_tuple()[1:] - if exponent in {'F', 'n', 'N'}: - raise ValidationError(self.messages['invalid'], code='invalid', params={'value': value}) + if exponent in {"F", "n", "N"}: + raise ValidationError( + self.messages["invalid"], code="invalid", params={"value": value} + ) if exponent >= 0: # A positive exponent adds that many trailing zeros. digits = len(digit_tuple) + exponent @@ -449,43 +484,48 @@ class DecimalValidator: if self.max_digits is not None and digits > self.max_digits: raise ValidationError( - self.messages['max_digits'], - code='max_digits', - params={'max': self.max_digits, 'value': value}, + self.messages["max_digits"], + code="max_digits", + params={"max": self.max_digits, "value": value}, ) if self.decimal_places is not None and decimals > self.decimal_places: raise ValidationError( - self.messages['max_decimal_places'], - code='max_decimal_places', - params={'max': self.decimal_places, 'value': value}, + self.messages["max_decimal_places"], + code="max_decimal_places", + params={"max": self.decimal_places, "value": value}, ) - if (self.max_digits is not None and self.decimal_places is not None and - whole_digits > (self.max_digits - self.decimal_places)): + if ( + self.max_digits is not None + and self.decimal_places is not None + and whole_digits > (self.max_digits - self.decimal_places) + ): raise ValidationError( - self.messages['max_whole_digits'], - code='max_whole_digits', - params={'max': (self.max_digits - self.decimal_places), 'value': value}, + self.messages["max_whole_digits"], + code="max_whole_digits", + params={"max": (self.max_digits - self.decimal_places), "value": value}, ) def __eq__(self, other): return ( - isinstance(other, self.__class__) and - self.max_digits == other.max_digits and - self.decimal_places == other.decimal_places + isinstance(other, self.__class__) + and self.max_digits == other.max_digits + and self.decimal_places == other.decimal_places ) @deconstructible class FileExtensionValidator: message = _( - 'File extension “%(extension)s” is not allowed. ' - 'Allowed extensions are: %(allowed_extensions)s.' + "File extension “%(extension)s” is not allowed. " + "Allowed extensions are: %(allowed_extensions)s." ) - code = 'invalid_extension' + code = "invalid_extension" def __init__(self, allowed_extensions=None, message=None, code=None): if allowed_extensions is not None: - allowed_extensions = [allowed_extension.lower() for allowed_extension in allowed_extensions] + allowed_extensions = [ + allowed_extension.lower() for allowed_extension in allowed_extensions + ] self.allowed_extensions = allowed_extensions if message is not None: self.message = message @@ -494,23 +534,26 @@ class FileExtensionValidator: def __call__(self, value): extension = Path(value.name).suffix[1:].lower() - if self.allowed_extensions is not None and extension not in self.allowed_extensions: + if ( + self.allowed_extensions is not None + and extension not in self.allowed_extensions + ): raise ValidationError( self.message, code=self.code, params={ - 'extension': extension, - 'allowed_extensions': ', '.join(self.allowed_extensions), - 'value': value, - } + "extension": extension, + "allowed_extensions": ", ".join(self.allowed_extensions), + "value": value, + }, ) def __eq__(self, other): return ( - isinstance(other, self.__class__) and - self.allowed_extensions == other.allowed_extensions and - self.message == other.message and - self.code == other.code + isinstance(other, self.__class__) + and self.allowed_extensions == other.allowed_extensions + and self.message == other.message + and self.code == other.code ) @@ -525,14 +568,17 @@ def get_available_image_extensions(): def validate_image_file_extension(value): - return FileExtensionValidator(allowed_extensions=get_available_image_extensions())(value) + return FileExtensionValidator(allowed_extensions=get_available_image_extensions())( + value + ) @deconstructible class ProhibitNullCharactersValidator: """Validate that the string doesn't contain the null character.""" - message = _('Null characters are not allowed.') - code = 'null_characters_not_allowed' + + message = _("Null characters are not allowed.") + code = "null_characters_not_allowed" def __init__(self, message=None, code=None): if message is not None: @@ -541,12 +587,12 @@ class ProhibitNullCharactersValidator: self.code = code def __call__(self, value): - if '\x00' in str(value): - raise ValidationError(self.message, code=self.code, params={'value': value}) + if "\x00" in str(value): + raise ValidationError(self.message, code=self.code, params={"value": value}) def __eq__(self, other): return ( - isinstance(other, self.__class__) and - self.message == other.message and - self.code == other.code + isinstance(other, self.__class__) + and self.message == other.message + and self.code == other.code ) |
