Files
netbox/netbox/utilities/validators.py
Jeremy Stretch 8d7889e2c0 Closes #19002: Module type profiles (#19014)
* Move Module & ModuleType models to a separate file

* Add ModuleTypeProfile & related fields

* Initial work on JSON schema validation

* Add attributes property on ModuleType

* Introduce MultipleOfValidator

* Introduce JSONSchemaProperty

* Enable dynamic form field rendering

* Misc cleanup

* Fix migration conflict

* Ensure deterministic ordering of attriubte fields

* Support choices & default values

* Include module type attributes on module view

* Enable modifying individual attributes via REST API

* Enable filtering by attribute values

* Add documentation & tests

* Schema should be optional

* Include attributes column for profiles

* Profile is nullable

* Include some initial profiles to be installed via migration

* Fix migrations conflict

* Fix filterset test

* Misc cleanup

* Fixes #19023: get_field_value() should respect null values in bound forms (#19024)

* Skip filters which do not specify a JSON-serializable value

* Fix handling of array item types

* Fix initial data in schema field during bulk edit

* Implement sanity checking for JSON schema definitions

* Fall back to filtering by string value
2025-04-01 12:05:06 -05:00

84 lines
2.7 KiB
Python

import decimal
import re
from django.core.exceptions import ValidationError
from django.core.validators import BaseValidator, RegexValidator, URLValidator, _lazy_re_compile
from django.utils.translation import gettext_lazy as _
from netbox.config import get_config
__all__ = (
'ColorValidator',
'EnhancedURLValidator',
'ExclusionValidator',
'MultipleOfValidator',
'validate_regex',
)
ColorValidator = RegexValidator(
regex='^[0-9a-f]{6}$',
message='Enter a valid hexadecimal RGB color code.',
code='invalid'
)
class EnhancedURLValidator(URLValidator):
"""
Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension and enforce allowed
schemes specified in the configuration.
"""
fqdn_re = URLValidator.hostname_re + URLValidator.domain_re + URLValidator.tld_re
host_res = [URLValidator.ipv4_re, URLValidator.ipv6_re, fqdn_re, URLValidator.hostname_re]
regex = _lazy_re_compile(
r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (enforced separately)
r'(?:\S+(?::\S*)?@)?' # HTTP basic authentication
r'(?:' + '|'.join(host_res) + ')' # IPv4, IPv6, FQDN, or hostname
r'(?::\d{2,5})?' # Port number
r'(?:[/?#][^\s]*)?' # Path
r'\Z', re.IGNORECASE)
schemes = None
def __call__(self, value):
if self.schemes is None:
# We can't load the allowed schemes until the configuration has been initialized
self.schemes = get_config().ALLOWED_URL_SCHEMES
return super().__call__(value)
class ExclusionValidator(BaseValidator):
"""
Ensure that a field's value is not equal to any of the specified values.
"""
message = 'This value may not be %(show_value)s.'
def compare(self, a, b):
return a in b
class MultipleOfValidator(BaseValidator):
"""
Checks that a field's value is a numeric multiple of the given value. Both values are
cast as Decimals for comparison.
"""
def __init__(self, multiple):
self.multiple = decimal.Decimal(str(multiple))
super().__init__(limit_value=None)
def __call__(self, value):
if decimal.Decimal(str(value)) % self.multiple != 0:
raise ValidationError(
_("{value} must be a multiple of {multiple}.").format(value=value, multiple=self.multiple)
)
def validate_regex(value):
"""
Checks that the value is a valid regular expression. (Don't confuse this with RegexValidator, which *uses* a regex
to validate a value.)
"""
try:
re.compile(value)
except re.error:
raise ValidationError(_("{value} is not a valid regular expression.").format(value=value))