validators.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. from django.core.exceptions import ValidationError
  2. from django.core import validators
  3. # NOTE: As this module may be imported by configuration.py, we cannot import
  4. # anything from NetBox itself.
  5. class IsEmptyValidator:
  6. """
  7. Employed by CustomValidator to enforce required fields.
  8. """
  9. message = "This field must be empty."
  10. code = 'is_empty'
  11. def __init__(self, enforce=True):
  12. self._enforce = enforce
  13. def __call__(self, value):
  14. if self._enforce and value not in validators.EMPTY_VALUES:
  15. raise ValidationError(self.message, code=self.code)
  16. class IsNotEmptyValidator:
  17. """
  18. Employed by CustomValidator to enforce prohibited fields.
  19. """
  20. message = "This field must not be empty."
  21. code = 'not_empty'
  22. def __init__(self, enforce=True):
  23. self._enforce = enforce
  24. def __call__(self, value):
  25. if self._enforce and value in validators.EMPTY_VALUES:
  26. raise ValidationError(self.message, code=self.code)
  27. class CustomValidator:
  28. """
  29. This class enables the application of user-defined validation rules to NetBox models. It can be instantiated by
  30. passing a dictionary of validation rules in the form {attribute: rules}, where 'rules' is a dictionary mapping
  31. descriptors (e.g. min_length or regex) to values.
  32. A CustomValidator instance is applied by calling it with the instance being validated:
  33. validator = CustomValidator({'name': {'min_length: 10}})
  34. site = Site(name='abcdef')
  35. validator(site) # Raises ValidationError
  36. :param validation_rules: A dictionary mapping object attributes to validation rules
  37. """
  38. VALIDATORS = {
  39. 'min': validators.MinValueValidator,
  40. 'max': validators.MaxValueValidator,
  41. 'min_length': validators.MinLengthValidator,
  42. 'max_length': validators.MaxLengthValidator,
  43. 'regex': validators.RegexValidator,
  44. 'required': IsNotEmptyValidator,
  45. 'prohibited': IsEmptyValidator,
  46. }
  47. def __init__(self, validation_rules=None):
  48. self.validation_rules = validation_rules or {}
  49. assert type(self.validation_rules) is dict, "Validation rules must be passed as a dictionary"
  50. def __call__(self, instance):
  51. # Validate instance attributes per validation rules
  52. for attr_name, rules in self.validation_rules.items():
  53. assert hasattr(instance, attr_name), f"Invalid attribute '{attr_name}' for {instance.__class__.__name__}"
  54. attr = getattr(instance, attr_name)
  55. for descriptor, value in rules.items():
  56. validator = self.get_validator(descriptor, value)
  57. try:
  58. validator(attr)
  59. except ValidationError as exc:
  60. # Re-package the raised ValidationError to associate it with the specific attr
  61. raise ValidationError({attr_name: exc})
  62. # Execute custom validation logic (if any)
  63. self.validate(instance)
  64. def get_validator(self, descriptor, value):
  65. """
  66. Instantiate and return the appropriate validator based on the descriptor given. For
  67. example, 'min' returns MinValueValidator(value).
  68. """
  69. if descriptor not in self.VALIDATORS:
  70. raise NotImplementedError(
  71. f"Unknown validation type for {self.__class__.__name__}: '{descriptor}'"
  72. )
  73. validator_cls = self.VALIDATORS.get(descriptor)
  74. return validator_cls(value)
  75. def validate(self, instance):
  76. """
  77. Custom validation method, to be overridden by the user. Validation failures should
  78. raise a ValidationError exception.
  79. """
  80. return
  81. def fail(self, message, field=None):
  82. """
  83. Raise a ValidationError exception. Associate the provided message with a form/serializer field if specified.
  84. """
  85. if field is not None:
  86. raise ValidationError({field: message})
  87. raise ValidationError(message)