1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Check for extraneous custom field data on clean()

This commit is contained in:
Jeremy Stretch
2020-11-12 12:18:31 -05:00
parent aed25fea3a
commit 4a8a1ce45c
9 changed files with 69 additions and 6 deletions

View File

@ -155,6 +155,8 @@ class Cable(ChangeLoggedModel, CustomFieldModel):
def clean(self):
from circuits.models import CircuitTermination
super().clean()
# Validate that termination A exists
if not hasattr(self, 'termination_a_type'):
raise ValidationError('Termination A type has not been specified')

View File

@ -254,6 +254,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
return yaml.dump(dict(data), sort_keys=False)
def clean(self):
super().clean()
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
# room to expand within their racks. This validation will impose a very high performance penalty when there are
@ -634,7 +635,6 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
super().validate_unique(exclude)
def clean(self):
super().clean()
# Validate site/rack combination
@ -917,6 +917,7 @@ class VirtualChassis(ChangeLoggedModel, CustomFieldModel):
return reverse('dcim:virtualchassis', kwargs={'pk': self.pk})
def clean(self):
super().clean()
# Verify that the selected master device has been assigned to this VirtualChassis. (Skip when creating a new
# VirtualChassis.)

View File

@ -64,6 +64,7 @@ class PowerPanel(ChangeLoggedModel, CustomFieldModel):
)
def clean(self):
super().clean()
# RackGroup must belong to assigned Site
if self.rack_group and self.rack_group.site != self.site:
@ -172,6 +173,7 @@ class PowerFeed(ChangeLoggedModel, PathEndpoint, CableTermination, CustomFieldMo
)
def clean(self):
super().clean()
# Rack must belong to same Site as PowerPanel
if self.rack and self.rack.site != self.power_panel.site:

View File

@ -296,6 +296,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return reverse('dcim:rack', args=[self.pk])
def clean(self):
super().clean()
# Validate outer dimensions and unit
if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
@ -602,6 +603,7 @@ class RackReservation(ChangeLoggedModel, CustomFieldModel):
return reverse('dcim:rackreservation', args=[self.pk])
def clean(self):
super().clean()
if hasattr(self, 'rack') and self.units:

View File

@ -43,6 +43,16 @@ class CustomFieldModel(models.Model):
(field, self.custom_field_data.get(field.name)) for field in fields
])
def clean(self):
# Validate custom field data
custom_field_names = CustomField.objects.get_for_model(self).values_list('name', flat=True)
for field_name in self.custom_field_data:
if field_name not in custom_field_names:
raise ValidationError({
'custom_field_data': f'Unknown custom field: {field_name}'
})
class CustomFieldManager(models.Manager):
use_in_migrations = True

View File

@ -1,9 +1,10 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.urls import reverse
from rest_framework import status
from dcim.forms import SiteCSVForm
from dcim.models import Site
from dcim.models import Site, Rack
from extras.choices import *
from extras.models import CustomField
from utilities.testing import APITestCase, TestCase
@ -534,3 +535,44 @@ class CustomFieldImportTest(TestCase):
form = SiteCSVForm(data=form_data)
self.assertFalse(form.is_valid())
self.assertIn('cf_select', form.errors)
class CustomFieldModelTest(TestCase):
@classmethod
def setUpTestData(cls):
cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo')
cf1.save()
cf1.content_types.set([ContentType.objects.get_for_model(Site)])
cf2 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='bar')
cf2.save()
cf2.content_types.set([ContentType.objects.get_for_model(Rack)])
def test_cf_data(self):
site = Site(name='Test Site', slug='test-site')
# Check custom field data on new instance
site.cf['foo'] = 'abc'
self.assertEqual(site.cf['foo'], 'abc')
# Check custom field data from database
site.save()
site = Site.objects.get(name='Test Site')
self.assertEqual(site.cf['foo'], 'abc')
def test_invalid_data(self):
"""
Setting custom field data for a non-applicable (or non-existent) CustomField should raise a ValidationError.
"""
site = Site(name='Test Site', slug='test-site')
# Set custom field data
site.cf['foo'] = 'abc'
site.cf['bar'] = 'def'
with self.assertRaises(ValidationError):
site.clean()
del(site.cf['bar'])
site.clean()

View File

@ -256,6 +256,7 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
return reverse('ipam:aggregate', args=[self.pk])
def clean(self):
super().clean()
if self.prefix:
@ -442,6 +443,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
return reverse('ipam:prefix', args=[self.pk])
def clean(self):
super().clean()
if self.prefix:
@ -721,6 +723,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
).exclude(pk=self.pk)
def clean(self):
super().clean()
if self.address:
@ -970,6 +973,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
return reverse('ipam:vlan', args=[self.pk])
def clean(self):
super().clean()
# Validate VLAN group
if self.group and self.group.site != self.site:
@ -1078,6 +1082,7 @@ class Service(ChangeLoggedModel, CustomFieldModel):
return self.device or self.virtual_machine
def clean(self):
super().clean()
# A Service must belong to a Device *or* to a VirtualMachine
if self.device and self.virtual_machine:

View File

@ -74,7 +74,8 @@ class UserKey(models.Model):
def __str__(self):
return self.user.username
def clean(self, *args, **kwargs):
def clean(self):
super().clean()
if self.public_key:
@ -105,8 +106,6 @@ class UserKey(models.Model):
)
})
super().clean()
def save(self, *args, **kwargs):
# Check whether public_key has been modified. If so, nullify the initial master_key_cipher.

View File

@ -172,6 +172,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
return reverse('virtualization:cluster', args=[self.pk])
def clean(self):
super().clean()
# If the Cluster is assigned to a Site, verify that all host Devices belong to that Site.
if self.pk and self.site:
@ -317,7 +318,6 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
super().validate_unique(exclude)
def clean(self):
super().clean()
# Validate primary IP addresses