Explorar o código

Closes #15490: CustomValidator support for accessing related object attribute via dotted path

Jeremy Stretch hai 1 ano
pai
achega
817e009e4f
Modificáronse 2 ficheiros con 27 adicións e 6 borrados
  1. 24 3
      netbox/extras/tests/test_customvalidators.py
  2. 3 3
      netbox/extras/validators.py

+ 24 - 3
netbox/extras/tests/test_customvalidation.py → netbox/extras/tests/test_customvalidators.py

@@ -4,7 +4,7 @@ from django.db import transaction
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
 
 
 from dcim.choices import SiteStatusChoices
 from dcim.choices import SiteStatusChoices
-from dcim.models import Site
+from dcim.models import Site, Region
 from extras.validators import CustomValidator
 from extras.validators import CustomValidator
 from ipam.models import ASN, RIR
 from ipam.models import ASN, RIR
 from users.models import User
 from users.models import User
@@ -82,6 +82,13 @@ prohibited_validator = CustomValidator({
 })
 })
 
 
 
 
+region_validator = CustomValidator({
+    'region.name': {
+        'eq': 'Bar',
+    }
+})
+
+
 request_validator = CustomValidator({
 request_validator = CustomValidator({
     'request.user.username': {
     'request.user.username': {
         'eq': 'Bob'
         'eq': 'Bob'
@@ -154,6 +161,20 @@ class CustomValidatorTest(TestCase):
     def test_valid(self):
     def test_valid(self):
         Site(name='abcdef123', slug='abcdef123').clean()
         Site(name='abcdef123', slug='abcdef123').clean()
 
 
+    @override_settings(CUSTOM_VALIDATORS={'dcim.site': [region_validator]})
+    def test_valid(self):
+        region1 = Region(name='Foo', slug='foo')
+        region1.save()
+        region2 = Region(name='Bar', slug='bar')
+        region2.save()
+
+        # Invalid region
+        with self.assertRaises(ValidationError):
+            Site(name='abcdef123', slug='abcdef123', region=region1).clean()
+
+        # Valid region
+        Site(name='abcdef123', slug='abcdef123', region=region2).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):
@@ -207,7 +228,7 @@ class CustomValidatorConfigTest(TestCase):
     @override_settings(
     @override_settings(
         CUSTOM_VALIDATORS={
         CUSTOM_VALIDATORS={
             'dcim.site': (
             'dcim.site': (
-                'extras.tests.test_customvalidation.MyValidator',
+                'extras.tests.test_customvalidators.MyValidator',
             )
             )
         }
         }
     )
     )
@@ -254,7 +275,7 @@ class ProtectionRulesConfigTest(TestCase):
     @override_settings(
     @override_settings(
         PROTECTION_RULES={
         PROTECTION_RULES={
             'dcim.site': (
             'dcim.site': (
-                'extras.tests.test_customvalidation.MyValidator',
+                'extras.tests.test_customvalidators.MyValidator',
             )
             )
         }
         }
     )
     )

+ 3 - 3
netbox/extras/validators.py

@@ -151,14 +151,14 @@ class CustomValidator:
             return []
             return []
 
 
         # Raise a ValidationError for unknown attributes
         # Raise a ValidationError for unknown attributes
-        if not hasattr(instance, name):
+        try:
+            return operator.attrgetter(name)(instance)
+        except AttributeError:
             raise ValidationError(_('Invalid attribute "{name}" for {model}').format(
             raise ValidationError(_('Invalid attribute "{name}" for {model}').format(
                 name=name,
                 name=name,
                 model=instance.__class__.__name__
                 model=instance.__class__.__name__
             ))
             ))
 
 
-        return getattr(instance, name)
-
     def get_validator(self, descriptor, value):
     def get_validator(self, descriptor, value):
         """
         """
         Instantiate and return the appropriate validator based on the descriptor given. For
         Instantiate and return the appropriate validator based on the descriptor given. For