validators.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import decimal
  2. import re
  3. from django.core.exceptions import ValidationError
  4. from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile
  5. from django.utils.translation import gettext_lazy as _
  6. from netbox.config import get_config
  7. __all__ = (
  8. 'ColorValidator',
  9. 'EnhancedURLValidator',
  10. 'ExclusionValidator',
  11. 'MultipleOfValidator',
  12. 'validate_regex',
  13. )
  14. ColorValidator = RegexValidator(
  15. regex='^[0-9a-f]{6}$',
  16. message='Enter a valid hexadecimal RGB color code.',
  17. code='invalid'
  18. )
  19. class EnhancedURLValidator(URLValidator):
  20. """
  21. Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension and enforce allowed
  22. schemes specified in the configuration.
  23. """
  24. fqdn_re = URLValidator.hostname_re + URLValidator.domain_re + URLValidator.tld_re
  25. host_res = [URLValidator.ipv4_re, URLValidator.ipv6_re, fqdn_re, URLValidator.hostname_re]
  26. regex = _lazy_re_compile(
  27. r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (enforced separately)
  28. r'(?:\S+(?::\S*)?@)?' # HTTP basic authentication
  29. r'(?:' + '|'.join(host_res) + ')' # IPv4, IPv6, FQDN, or hostname
  30. r'(?::\d{2,5})?' # Port number
  31. r'(?:[/?#][^\s]*)?' # Path
  32. r'\Z', re.IGNORECASE)
  33. schemes = None
  34. def __call__(self, value):
  35. if self.schemes is None:
  36. # We can't load the allowed schemes until the configuration has been initialized
  37. self.schemes = get_config().ALLOWED_URL_SCHEMES
  38. return super().__call__(value)
  39. class ExclusionValidator(BaseValidator):
  40. """
  41. Ensure that a field's value is not equal to any of the specified values.
  42. """
  43. message = 'This value may not be %(show_value)s.'
  44. def compare(self, a, b):
  45. return a in b
  46. class MultipleOfValidator(BaseValidator):
  47. """
  48. Checks that a field's value is a numeric multiple of the given value. Both values are
  49. cast as Decimals for comparison.
  50. """
  51. def __init__(self, multiple):
  52. self.multiple = decimal.Decimal(str(multiple))
  53. super().__init__(limit_value=None)
  54. def __call__(self, value):
  55. if decimal.Decimal(str(value)) % self.multiple != 0:
  56. raise ValidationError(
  57. _("{value} must be a multiple of {multiple}.").format(value=value, multiple=self.multiple)
  58. )
  59. def validate_regex(value):
  60. """
  61. Checks that the value is a valid regular expression. (Don't confuse this with RegexValidator, which *uses* a regex
  62. to validate a value.)
  63. """
  64. try:
  65. re.compile(value)
  66. except re.error:
  67. raise ValidationError(_("{value} is not a valid regular expression.").format(value=value))