Attributed all model ValidationErrors to specific fields (where appropriate)

This commit is contained in:
Jeremy Stretch 2016-10-21 15:39:13 -04:00
parent 13243785f1
commit fc2ac8a02b
5 changed files with 117 additions and 74 deletions

View File

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

View File

@ -172,16 +172,6 @@ class PrefixForm(BootstrapMixin, CustomFieldForm):
else: else:
self.fields['vlan'].choices = [] 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): class PrefixFromCSVForm(forms.ModelForm):
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd', vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',

View File

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

View File

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

View File

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