mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Extend CustomValidator to support required, prohibited fields
This commit is contained in:
@ -28,8 +28,13 @@ The `CustomValidator` class supports several validation types:
|
|||||||
* `min_length`: Minimum string length
|
* `min_length`: Minimum string length
|
||||||
* `max_length`: Maximum string length
|
* `max_length`: Maximum string length
|
||||||
* `regex`: Application of a [regular expression](https://en.wikipedia.org/wiki/Regular_expression)
|
* `regex`: Application of a [regular expression](https://en.wikipedia.org/wiki/Regular_expression)
|
||||||
|
* `required`: A value must be specified
|
||||||
|
* `prohibited`: A value must _not_ be specified
|
||||||
|
|
||||||
The `min` and `max` types should be defined for numeric values, whereas `min_length`, `max_length`, and `regex` are suitable for character strings (text values).
|
The `min` and `max` types should be defined for numeric values, whereas `min_length`, `max_length`, and `regex` are suitable for character strings (text values). The `required` and `prohibited` validators may be used for any field, and should be passed a value of `True`.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Bear in mind that these validators merely supplement NetBox's own validation: They will not override it. For example, if a certain model field is required by NetBox, setting a validator for it with `{'prohibited': True}` will not work.
|
||||||
|
|
||||||
### Custom Validation Logic
|
### Custom Validation Logic
|
||||||
|
|
||||||
|
@ -13,15 +13,51 @@ class MyValidator(CustomValidator):
|
|||||||
self.fail("Name must be foo!")
|
self.fail("Name must be foo!")
|
||||||
|
|
||||||
|
|
||||||
stock_validator = CustomValidator({
|
min_validator = CustomValidator({
|
||||||
'name': {
|
|
||||||
'min_length': 5,
|
|
||||||
'max_length': 10,
|
|
||||||
'regex': r'\d{3}$', # Ends with three digits
|
|
||||||
},
|
|
||||||
'asn': {
|
'asn': {
|
||||||
'min': 65000,
|
'min': 65000
|
||||||
'max': 65100,
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
max_validator = CustomValidator({
|
||||||
|
'asn': {
|
||||||
|
'max': 65100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
min_length_validator = CustomValidator({
|
||||||
|
'name': {
|
||||||
|
'min_length': 5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
max_length_validator = CustomValidator({
|
||||||
|
'name': {
|
||||||
|
'max_length': 10
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
regex_validator = CustomValidator({
|
||||||
|
'name': {
|
||||||
|
'regex': r'\d{3}$' # Ends with three digits
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
required_validator = CustomValidator({
|
||||||
|
'description': {
|
||||||
|
'required': True
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
prohibited_validator = CustomValidator({
|
||||||
|
'description': {
|
||||||
|
'prohibited': True
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -30,46 +66,56 @@ custom_validator = MyValidator()
|
|||||||
|
|
||||||
class CustomValidatorTest(TestCase):
|
class CustomValidatorTest(TestCase):
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [min_validator]})
|
||||||
def test_configuration(self):
|
def test_configuration(self):
|
||||||
self.assertIn('dcim.site', settings.CUSTOM_VALIDATORS)
|
self.assertIn('dcim.site', settings.CUSTOM_VALIDATORS)
|
||||||
validator = settings.CUSTOM_VALIDATORS['dcim.site'][0]
|
validator = settings.CUSTOM_VALIDATORS['dcim.site'][0]
|
||||||
self.assertIsInstance(validator, CustomValidator)
|
self.assertIsInstance(validator, CustomValidator)
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [min_validator]})
|
||||||
def test_min(self):
|
def test_min(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abcdef123', slug='abcdefghijk', asn=1).clean()
|
Site(name='abcdef123', slug='abcdefghijk', asn=1).clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [max_validator]})
|
||||||
def test_max(self):
|
def test_max(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abcdef123', slug='abcdefghijk', asn=65535).clean()
|
Site(name='abcdef123', slug='abcdefghijk', asn=65535).clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [min_length_validator]})
|
||||||
def test_min_length(self):
|
def test_min_length(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abc', slug='abc', asn=65000).clean()
|
Site(name='abc', slug='abc', asn=65000).clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [max_length_validator]})
|
||||||
def test_max_length(self):
|
def test_max_length(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abcdefghijk', slug='abcdefghijk', asn=65000).clean()
|
Site(name='abcdefghijk', slug='abcdefghijk').clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [regex_validator]})
|
||||||
def test_regex(self):
|
def test_regex(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abcdefgh', slug='abcdefgh', asn=65000).clean()
|
Site(name='abcdefgh', slug='abcdefgh').clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [stock_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [required_validator]})
|
||||||
|
def test_required(self):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
Site(name='abcdefgh', slug='abcdefgh', description='').clean()
|
||||||
|
|
||||||
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [prohibited_validator]})
|
||||||
|
def test_prohibited(self):
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
Site(name='abcdefgh', slug='abcdefgh', description='ABC').clean()
|
||||||
|
|
||||||
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [min_length_validator]})
|
||||||
def test_valid(self):
|
def test_valid(self):
|
||||||
Site(name='abcdef123', slug='abcdef123', asn=65000).clean()
|
Site(name='abcdef123', slug='abcdef123').clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [custom_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [custom_validator]})
|
||||||
def test_custom_invalid(self):
|
def test_custom_invalid(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
Site(name='abc', slug='abc', asn=65000).clean()
|
Site(name='abc', slug='abc').clean()
|
||||||
|
|
||||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [custom_validator]})
|
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [custom_validator]})
|
||||||
def test_custom_valid(self):
|
def test_custom_valid(self):
|
||||||
Site(name='foo', slug='foo', asn=65000).clean()
|
Site(name='foo', slug='foo').clean()
|
||||||
|
@ -1,6 +1,39 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
|
||||||
|
# NOTE: As this module may be imported by configuration.py, we cannot import
|
||||||
|
# anything from NetBox itself.
|
||||||
|
|
||||||
|
|
||||||
|
class IsEmptyValidator:
|
||||||
|
"""
|
||||||
|
Employed by CustomValidator to enforce required fields.
|
||||||
|
"""
|
||||||
|
message = "This field must be empty."
|
||||||
|
code = 'is_empty'
|
||||||
|
|
||||||
|
def __init__(self, enforce=True):
|
||||||
|
self._enforce = enforce
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
if self._enforce and value not in validators.EMPTY_VALUES:
|
||||||
|
raise ValidationError(self.message, code=self.code)
|
||||||
|
|
||||||
|
|
||||||
|
class IsNotEmptyValidator:
|
||||||
|
"""
|
||||||
|
Employed by CustomValidator to enforce prohibited fields.
|
||||||
|
"""
|
||||||
|
message = "This field must not be empty."
|
||||||
|
code = 'not_empty'
|
||||||
|
|
||||||
|
def __init__(self, enforce=True):
|
||||||
|
self._enforce = enforce
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
if self._enforce and value in validators.EMPTY_VALUES:
|
||||||
|
raise ValidationError(self.message, code=self.code)
|
||||||
|
|
||||||
|
|
||||||
class CustomValidator:
|
class CustomValidator:
|
||||||
"""
|
"""
|
||||||
@ -22,6 +55,8 @@ class CustomValidator:
|
|||||||
'min_length': validators.MinLengthValidator,
|
'min_length': validators.MinLengthValidator,
|
||||||
'max_length': validators.MaxLengthValidator,
|
'max_length': validators.MaxLengthValidator,
|
||||||
'regex': validators.RegexValidator,
|
'regex': validators.RegexValidator,
|
||||||
|
'required': IsNotEmptyValidator,
|
||||||
|
'prohibited': IsEmptyValidator,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, validation_rules=None):
|
def __init__(self, validation_rules=None):
|
||||||
|
Reference in New Issue
Block a user