mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#2269: Allow non-unique Device names
This commit is contained in:
23
netbox/dcim/migrations/0086_device_name_nonunique.py
Normal file
23
netbox/dcim/migrations/0086_device_name_nonunique.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 2.2.6 on 2019-12-09 15:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0006_custom_tag_models'),
|
||||||
|
('dcim', '0085_3569_poweroutlet_fields'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='device',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='device',
|
||||||
|
unique_together={('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ('site', 'tenant', 'name')},
|
||||||
|
),
|
||||||
|
]
|
@ -1523,8 +1523,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64,
|
max_length=64,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True
|
||||||
unique=True
|
|
||||||
)
|
)
|
||||||
serial = models.CharField(
|
serial = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -1645,6 +1644,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = [
|
unique_together = [
|
||||||
|
['site', 'tenant', 'name'], # See validate_unique below
|
||||||
['rack', 'position', 'face'],
|
['rack', 'position', 'face'],
|
||||||
['virtual_chassis', 'vc_position'],
|
['virtual_chassis', 'vc_position'],
|
||||||
]
|
]
|
||||||
@ -1659,6 +1659,18 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:device', args=[self.pk])
|
return reverse('dcim:device', args=[self.pk])
|
||||||
|
|
||||||
|
def validate_unique(self, exclude=None):
|
||||||
|
|
||||||
|
# Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary
|
||||||
|
# because Django does not consider two NULL fields to be equal, and thus will not trigger a violation
|
||||||
|
# of the uniqueness constraint without manual intervention.
|
||||||
|
if self.tenant is None and Device.objects.exclude(pk=self.pk).filter(name=self.name, tenant__isnull=True):
|
||||||
|
raise ValidationError({
|
||||||
|
'name': 'A device with this name already exists.'
|
||||||
|
})
|
||||||
|
|
||||||
|
super().validate_unique(exclude)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
super().clean()
|
super().clean()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
from tenancy.models import Tenant
|
||||||
|
|
||||||
|
|
||||||
class RackTestCase(TestCase):
|
class RackTestCase(TestCase):
|
||||||
@ -281,6 +282,42 @@ class DeviceTestCase(TestCase):
|
|||||||
name='Device Bay 1'
|
name='Device Bay 1'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_device_duplicate_name_per_site(self):
|
||||||
|
|
||||||
|
device1 = Device(
|
||||||
|
site=self.site,
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.device_role,
|
||||||
|
name='Test Device 1'
|
||||||
|
)
|
||||||
|
device1.save()
|
||||||
|
|
||||||
|
device2 = Device(
|
||||||
|
site=device1.site,
|
||||||
|
device_type=device1.device_type,
|
||||||
|
device_role=device1.device_role,
|
||||||
|
name=device1.name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two devices assigned to the same Site and no Tenant should fail validation
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
device2.full_clean()
|
||||||
|
|
||||||
|
tenant = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
|
||||||
|
device1.tenant = tenant
|
||||||
|
device1.save()
|
||||||
|
device2.tenant = tenant
|
||||||
|
|
||||||
|
# Two devices assigned to the same Site and the same Tenant should fail validation
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
device2.full_clean()
|
||||||
|
|
||||||
|
device2.tenant = None
|
||||||
|
|
||||||
|
# Two devices assigned to the same Site and different Tenants should pass validation
|
||||||
|
device2.full_clean()
|
||||||
|
device2.save()
|
||||||
|
|
||||||
|
|
||||||
class CableTestCase(TestCase):
|
class CableTestCase(TestCase):
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user