Browse Source

Attributed all model ValidationErrors to specific fields (where appropriate)

Jeremy Stretch 8 years ago
parent
commit
fc2ac8a02b
5 changed files with 118 additions and 75 deletions
  1. 69 40
      netbox/dcim/models.py
  2. 0 10
      netbox/ipam/forms.py
  3. 26 12
      netbox/ipam/models.py
  4. 5 5
      netbox/secrets/forms.py
  5. 18 8
      netbox/secrets/models.py

+ 69 - 40
netbox/dcim/models.py

@@ -401,8 +401,11 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
             if top_device:
                 min_height = top_device.position + top_device.device_type.u_height - 1
                 if self.u_height < min_height:
-                    raise ValidationError("Rack must be at least {}U tall with currently installed devices."
-                                          .format(min_height))
+                    raise ValidationError({
+                        'u_height': "Rack must be at least {}U tall to house currently installed devices.".format(
+                            min_height
+                        )
+                    })
 
     def to_csv(self):
         return ','.join([
@@ -596,27 +599,39 @@ class DeviceType(models.Model):
                 u_available = d.rack.get_available_units(u_height=self.u_height, rack_face=face_required,
                                                          exclude=[d.pk])
                 if d.position not in u_available:
-                    raise ValidationError("Device {} in rack {} does not have sufficient space to accommodate a height "
-                                          "of {}U".format(d, d.rack, self.u_height))
+                    raise ValidationError({
+                        'u_height': "Device {} in rack {} does not have sufficient space to accommodate a height of "
+                                    "{}U".format(d, d.rack, self.u_height)
+                    })
 
         if not self.is_console_server and self.cs_port_templates.count():
-            raise ValidationError("Must delete all console server port templates associated with this device before "
-                                  "declassifying it as a console server.")
+            raise ValidationError({
+                'is_console_server': "Must delete all console server port templates associated with this device before "
+                                     "declassifying it as a console server."
+            })
 
         if not self.is_pdu and self.power_outlet_templates.count():
-            raise ValidationError("Must delete all power outlet templates associated with this device before "
-                                  "declassifying it as a PDU.")
+            raise ValidationError({
+                'is_pdu': "Must delete all power outlet templates associated with this device before declassifying it "
+                          "as a PDU."
+            })
 
         if not self.is_network_device and self.interface_templates.filter(mgmt_only=False).count():
-            raise ValidationError("Must delete all non-management-only interface templates associated with this device "
-                                  "before declassifying it as a network device.")
+            raise ValidationError({
+                'is_network_device': "Must delete all non-management-only interface templates associated with this "
+                                     "device before declassifying it as a network device."
+            })
 
         if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count():
-            raise ValidationError("Must delete all device bay templates associated with this device before "
-                                  "declassifying it as a parent device.")
+            raise ValidationError({
+                'subdevice_role': "Must delete all device bay templates associated with this device before "
+                                  "declassifying it as a parent device."
+            })
 
         if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD:
-            raise ValidationError("Child device types must be 0U.")
+            raise ValidationError({
+                'u_height': "Child device types must be 0U."
+            })
 
     @property
     def is_parent_device(self):
@@ -824,29 +839,39 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
 
     def clean(self):
 
-        # Validate device type assignment
-        if not hasattr(self, 'device_type'):
-            raise ValidationError("Must specify device type.")
-
-        # Child devices cannot be assigned to a rack face/unit
-        if self.device_type.is_child_device and (self.face is not None or self.position):
-            raise ValidationError("Child device types cannot be assigned a rack face or position.")
-
         # Validate position/face combination
         if self.position and self.face is None:
-            raise ValidationError("Must specify rack face with rack position.")
-
-        # Validate rack space
-        rack_face = self.face if not self.device_type.is_full_depth else None
-        exclude_list = [self.pk] if self.pk else []
-        try:
-            available_units = self.rack.get_available_units(u_height=self.device_type.u_height, rack_face=rack_face,
-                                                            exclude=exclude_list)
-            if self.position and self.position not in available_units:
-                raise ValidationError("U{} is already occupied or does not have sufficient space to accommodate a(n) "
-                                      "{} ({}U).".format(self.position, self.device_type, self.device_type.u_height))
-        except Rack.DoesNotExist:
-            pass
+            raise ValidationError({
+                'face': "Must specify rack face when defining rack position."
+            })
+
+        if self.device_type:
+
+            # Child devices cannot be assigned to a rack face/unit
+            if self.device_type.is_child_device and self.face is not None:
+                raise ValidationError({
+                    'face': "Child device types cannot be assigned to a rack face. This is an attribute of the parent "
+                            "device."
+                })
+            if self.device_type.is_child_device and self.position:
+                raise ValidationError({
+                    'position': "Child device types cannot be assigned to a rack position. This is an attribute of the "
+                                "parent device."
+                })
+
+            # Validate rack space
+            rack_face = self.face if not self.device_type.is_full_depth else None
+            exclude_list = [self.pk] if self.pk else []
+            try:
+                available_units = self.rack.get_available_units(u_height=self.device_type.u_height, rack_face=rack_face,
+                                                                exclude=exclude_list)
+                if self.position and self.position not in available_units:
+                    raise ValidationError({
+                        'position': "U{} is already occupied or does not have sufficient space to accommodate a(n) {} "
+                                    "({}U).".format(self.position, self.device_type, self.device_type.u_height)
+                    })
+            except Rack.DoesNotExist:
+                pass
 
     def save(self, *args, **kwargs):
 
@@ -1094,9 +1119,10 @@ class Interface(models.Model):
     def clean(self):
 
         if self.form_factor == IFACE_FF_VIRTUAL and self.is_connected:
-            raise ValidationError({'form_factor': "Virtual interfaces cannot be connected to another interface or "
-                                                  "circuit. Disconnect the interface or choose a physical form "
-                                                  "factor."})
+            raise ValidationError({
+                'form_factor': "Virtual interfaces cannot be connected to another interface or circuit. Disconnect the "
+                               "interface or choose a physical form factor."
+            })
 
     @property
     def is_physical(self):
@@ -1147,7 +1173,9 @@ class InterfaceConnection(models.Model):
 
     def clean(self):
         if self.interface_a == self.interface_b:
-            raise ValidationError("Cannot connect an interface to itself")
+            raise ValidationError({
+                'interface_b': "Cannot connect an interface to itself."
+            })
 
     # Used for connections export
     def to_csv(self):
@@ -1180,8 +1208,9 @@ class DeviceBay(models.Model):
 
         # Validate that the parent Device can have DeviceBays
         if not self.device.device_type.is_parent_device:
-            raise ValidationError("This type of device ({}) does not support device bays."
-                                  .format(self.device.device_type))
+            raise ValidationError("This type of device ({}) does not support device bays.".format(
+                self.device.device_type
+            ))
 
         # Cannot install a device into itself, obviously
         if self.device == self.installed_device:

+ 0 - 10
netbox/ipam/forms.py

@@ -172,16 +172,6 @@ class PrefixForm(BootstrapMixin, CustomFieldForm):
         else:
             self.fields['vlan'].choices = []
 
-    def clean_prefix(self):
-        prefix = self.cleaned_data['prefix']
-        if prefix.version == 4 and prefix.prefixlen == 32:
-            raise forms.ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 "
-                                        "addresses instead.")
-        elif prefix.version == 6 and prefix.prefixlen == 128:
-            raise forms.ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 "
-                                        "addresses instead.")
-        return prefix
-
 
 class PrefixFromCSVForm(forms.ModelForm):
     vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',

+ 26 - 12
netbox/ipam/models.py

@@ -139,16 +139,22 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
             if self.pk:
                 covering_aggregates = covering_aggregates.exclude(pk=self.pk)
             if covering_aggregates:
-                raise ValidationError("{} is already covered by an existing aggregate ({})"
-                                      .format(self.prefix, covering_aggregates[0]))
+                raise ValidationError({
+                    'prefix': "Aggregates cannot overlap. {} is already covered by an existing aggregate ({}).".format(
+                        self.prefix, covering_aggregates[0]
+                    )
+                })
 
             # Ensure that the aggregate being added does not cover an existing aggregate
             covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
             if self.pk:
                 covered_aggregates = covered_aggregates.exclude(pk=self.pk)
             if covered_aggregates:
-                raise ValidationError("{} overlaps with an existing aggregate ({})"
-                                      .format(self.prefix, covered_aggregates[0]))
+                raise ValidationError({
+                    'prefix': "Aggregates cannot overlap. {} covers an existing aggregate ({}).".format(
+                        self.prefix, covered_aggregates[0]
+                    )
+                })
 
     def save(self, *args, **kwargs):
         if self.prefix:
@@ -268,14 +274,17 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
         return reverse('ipam:prefix', args=[self.pk])
 
     def clean(self):
+
         # Disallow host masks
         if self.prefix:
             if self.prefix.version == 4 and self.prefix.prefixlen == 32:
-                raise ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 addresses "
-                                      "instead.")
+                raise ValidationError({
+                    'prefix': "Cannot create host addresses (/32) as prefixes. Create an IPv4 address instead."
+                })
             elif self.prefix.version == 6 and self.prefix.prefixlen == 128:
-                raise ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 addresses "
-                                      "instead.")
+                raise ValidationError({
+                    'prefix': "Cannot create host addresses (/128) as prefixes. Create an IPv6 address instead."
+                })
 
     def save(self, *args, **kwargs):
         if self.prefix:
@@ -369,13 +378,16 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
             duplicate_ips = IPAddress.objects.filter(vrf=self.vrf, address__net_host=str(self.address.ip))\
                 .exclude(pk=self.pk)
             if duplicate_ips:
-                raise ValidationError("Duplicate IP address found in VRF {}: {}".format(self.vrf,
-                                                                                        duplicate_ips.first()))
+                raise ValidationError({
+                    'address': "Duplicate IP address found in VRF {}: {}".format(self.vrf, duplicate_ips.first())
+                })
         elif not self.vrf and settings.ENFORCE_GLOBAL_UNIQUE:
             duplicate_ips = IPAddress.objects.filter(vrf=None, address__net_host=str(self.address.ip))\
                 .exclude(pk=self.pk)
             if duplicate_ips:
-                raise ValidationError("Duplicate IP address found in global table: {}".format(duplicate_ips.first()))
+                raise ValidationError({
+                    'address': "Duplicate IP address found in global table: {}".format(duplicate_ips.first())
+                })
 
     def save(self, *args, **kwargs):
         if self.address:
@@ -478,7 +490,9 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
 
         # Validate VLAN group
         if self.group and self.group.site != self.site:
-            raise ValidationError("VLAN group must belong to the assigned site ({}).".format(self.site))
+            raise ValidationError({
+                'group': "VLAN group must belong to the assigned site ({}).".format(self.site)
+            })
 
     def to_csv(self):
         return ','.join([

+ 5 - 5
netbox/secrets/forms.py

@@ -57,14 +57,14 @@ class SecretForm(forms.ModelForm, BootstrapMixin):
         fields = ['role', 'name', 'plaintext', 'plaintext2']
 
     def clean(self):
+
         if self.cleaned_data['plaintext']:
             validate_rsa_key(self.cleaned_data['private_key'])
 
-    def clean_plaintext2(self):
-        plaintext = self.cleaned_data['plaintext']
-        plaintext2 = self.cleaned_data['plaintext2']
-        if plaintext != plaintext2:
-            raise forms.ValidationError("The two given plaintext values do not match. Please check your input.")
+        if self.cleaned_data['plaintext'] != self.cleaned_data['plaintext2']:
+            raise forms.ValidationError({
+                'plaintext2': "The two given plaintext values do not match. Please check your input."
+            })
 
 
 class SecretFromCSVForm(forms.ModelForm):

+ 18 - 8
netbox/secrets/models.py

@@ -81,24 +81,34 @@ class UserKey(CreatedUpdatedModel):
 
     def clean(self, *args, **kwargs):
 
-        # Validate the public key format and length.
         if self.public_key:
+
+            # Validate the public key format
             try:
                 pubkey = RSA.importKey(self.public_key)
             except ValueError:
-                raise ValidationError("Invalid RSA key format.")
+                raise ValidationError({
+                    'public_key': "Invalid RSA key format."
+                })
             except:
                 raise ValidationError("Something went wrong while trying to save your key. Please ensure that you're "
                                       "uploading a valid RSA public key in PEM format (no SSH/PGP).")
-            # key.size() returns 1 less than the key modulus
-            pubkey_length = pubkey.size() + 1
+
+            # Validate the public key length
+            pubkey_length = pubkey.size() + 1  # key.size() returns 1 less than the key modulus
             if pubkey_length < settings.SECRETS_MIN_PUBKEY_SIZE:
-                raise ValidationError("Insufficient key length. Keys must be at least {} bits long."
-                                      .format(settings.SECRETS_MIN_PUBKEY_SIZE))
+                raise ValidationError({
+                    'public_key': "Insufficient key length. Keys must be at least {} bits long.".format(
+                        settings.SECRETS_MIN_PUBKEY_SIZE
+                    )
+                })
             # We can't use keys bigger than our master_key_cipher field can hold
             if pubkey_length > 4096:
-                raise ValidationError("Public key size ({}) is too large. Maximum key size is 4096 bits."
-                                      .format(pubkey_length))
+                raise ValidationError({
+                    'public_key': "Public key size ({}) is too large. Maximum key size is 4096 bits.".format(
+                        pubkey_length
+                    )
+                })
 
         super(UserKey, self).clean()