Merge pull request #10368 from netbox-community/10361-unique-constraints

Closes #10361: Migrate from unique_together to UniqueConstraints
This commit is contained in:
Jeremy Stretch 2022-09-27 15:55:44 -04:00 committed by GitHub
commit c349e06346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 800 additions and 240 deletions

View File

@ -0,0 +1,39 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0038_cabling_cleanup'),
]
operations = [
migrations.RemoveConstraint(
model_name='providernetwork',
name='circuits_providernetwork_provider_name',
),
migrations.AlterUniqueTogether(
name='circuit',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='circuittermination',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='providernetwork',
unique_together=set(),
),
migrations.AddConstraint(
model_name='circuit',
constraint=models.UniqueConstraint(fields=('provider', 'cid'), name='circuits_circuit_unique_provider_cid'),
),
migrations.AddConstraint(
model_name='circuittermination',
constraint=models.UniqueConstraint(fields=('circuit', 'term_side'), name='circuits_circuittermination_unique_circuit_term_side'),
),
migrations.AddConstraint(
model_name='providernetwork',
constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_unique_provider_name'),
),
]

View File

@ -132,7 +132,12 @@ class Circuit(NetBoxModel):
class Meta:
ordering = ['provider', 'cid']
unique_together = ['provider', 'cid']
constraints = (
models.UniqueConstraint(
fields=('provider', 'cid'),
name='%(app_label)s_%(class)s_unique_provider_cid'
),
)
def __str__(self):
return self.cid
@ -208,7 +213,12 @@ class CircuitTermination(
class Meta:
ordering = ['circuit', 'term_side']
unique_together = ['circuit', 'term_side']
constraints = (
models.UniqueConstraint(
fields=('circuit', 'term_side'),
name='%(app_label)s_%(class)s_unique_circuit_term_side'
),
)
def __str__(self):
return f'Termination {self.term_side}: {self.site or self.provider_network}'

View File

@ -106,10 +106,9 @@ class ProviderNetwork(NetBoxModel):
constraints = (
models.UniqueConstraint(
fields=('provider', 'name'),
name='circuits_providernetwork_provider_name'
name='%(app_label)s_%(class)s_unique_provider_name'
),
)
unique_together = ('provider', 'name')
def __str__(self):
return self.name

View File

@ -0,0 +1,331 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0161_cabling_cleanup'),
]
operations = [
migrations.RemoveConstraint(
model_name='cabletermination',
name='dcim_cable_termination_unique_termination',
),
migrations.RemoveConstraint(
model_name='location',
name='dcim_location_name',
),
migrations.RemoveConstraint(
model_name='location',
name='dcim_location_slug',
),
migrations.RemoveConstraint(
model_name='region',
name='dcim_region_name',
),
migrations.RemoveConstraint(
model_name='region',
name='dcim_region_slug',
),
migrations.RemoveConstraint(
model_name='sitegroup',
name='dcim_sitegroup_name',
),
migrations.RemoveConstraint(
model_name='sitegroup',
name='dcim_sitegroup_slug',
),
migrations.AlterUniqueTogether(
name='consoleport',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='consoleporttemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='consoleserverport',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='consoleserverporttemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='device',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='devicebay',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='devicebaytemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='devicetype',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='frontport',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='frontporttemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='interface',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='interfacetemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='inventoryitem',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='inventoryitemtemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='modulebay',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='modulebaytemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='moduletype',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='powerfeed',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='poweroutlet',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='poweroutlettemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='powerpanel',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='powerport',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='powerporttemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='rack',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='rearport',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='rearporttemplate',
unique_together=set(),
),
migrations.AddConstraint(
model_name='cabletermination',
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='dcim_cabletermination_unique_termination'),
),
migrations.AddConstraint(
model_name='consoleport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_consoleport_unique_device_name'),
),
migrations.AddConstraint(
model_name='consoleporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_consoleporttemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='consoleporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_consoleporttemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='consoleserverport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_consoleserverport_unique_device_name'),
),
migrations.AddConstraint(
model_name='consoleserverporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_consoleserverporttemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='consoleserverporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_consoleserverporttemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(fields=('name', 'site', 'tenant'), name='dcim_device_unique_name_site_tenant'),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(condition=models.Q(('tenant__isnull', True)), fields=('name', 'site'), name='dcim_device_unique_name_site', violation_error_message='Device name must be unique per site.'),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(fields=('rack', 'position', 'face'), name='dcim_device_unique_rack_position_face'),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(fields=('virtual_chassis', 'vc_position'), name='dcim_device_unique_virtual_chassis_vc_position'),
),
migrations.AddConstraint(
model_name='devicebay',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_devicebay_unique_device_name'),
),
migrations.AddConstraint(
model_name='devicebaytemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_devicebaytemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='devicetype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'model'), name='dcim_devicetype_unique_manufacturer_model'),
),
migrations.AddConstraint(
model_name='devicetype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'slug'), name='dcim_devicetype_unique_manufacturer_slug'),
),
migrations.AddConstraint(
model_name='frontport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_frontport_unique_device_name'),
),
migrations.AddConstraint(
model_name='frontport',
constraint=models.UniqueConstraint(fields=('rear_port', 'rear_port_position'), name='dcim_frontport_unique_rear_port_position'),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_frontporttemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_frontporttemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('rear_port', 'rear_port_position'), name='dcim_frontporttemplate_unique_rear_port_position'),
),
migrations.AddConstraint(
model_name='interface',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_interface_unique_device_name'),
),
migrations.AddConstraint(
model_name='interfacetemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_interfacetemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='interfacetemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_interfacetemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='inventoryitem',
constraint=models.UniqueConstraint(fields=('device', 'parent', 'name'), name='dcim_inventoryitem_unique_device_parent_name'),
),
migrations.AddConstraint(
model_name='inventoryitemtemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'parent', 'name'), name='dcim_inventoryitemtemplate_unique_device_type_parent_name'),
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('site', 'name'), name='dcim_location_name', violation_error_message='A location with this name already exists within the specified site.'),
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('site', 'slug'), name='dcim_location_slug', violation_error_message='A location with this slug already exists within the specified site.'),
),
migrations.AddConstraint(
model_name='modulebay',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_modulebay_unique_device_name'),
),
migrations.AddConstraint(
model_name='modulebaytemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_modulebaytemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='moduletype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'model'), name='dcim_moduletype_unique_manufacturer_model'),
),
migrations.AddConstraint(
model_name='powerfeed',
constraint=models.UniqueConstraint(fields=('power_panel', 'name'), name='dcim_powerfeed_unique_power_panel_name'),
),
migrations.AddConstraint(
model_name='poweroutlet',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_poweroutlet_unique_device_name'),
),
migrations.AddConstraint(
model_name='poweroutlettemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_poweroutlettemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='poweroutlettemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_poweroutlettemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='powerpanel',
constraint=models.UniqueConstraint(fields=('site', 'name'), name='dcim_powerpanel_unique_site_name'),
),
migrations.AddConstraint(
model_name='powerport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_powerport_unique_device_name'),
),
migrations.AddConstraint(
model_name='powerporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_powerporttemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='powerporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_powerporttemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='rack',
constraint=models.UniqueConstraint(fields=('location', 'name'), name='dcim_rack_unique_location_name'),
),
migrations.AddConstraint(
model_name='rack',
constraint=models.UniqueConstraint(fields=('location', 'facility_id'), name='dcim_rack_unique_location_facility_id'),
),
migrations.AddConstraint(
model_name='rearport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_rearport_unique_device_name'),
),
migrations.AddConstraint(
model_name='rearporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_rearporttemplate_unique_device_type_name'),
),
migrations.AddConstraint(
model_name='rearporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_rearporttemplate_unique_module_type_name'),
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('name',), name='dcim_region_name', violation_error_message='A top-level region with this name already exists.'),
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('slug',), name='dcim_region_slug', violation_error_message='A top-level region with this slug already exists.'),
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('name',), name='dcim_sitegroup_name', violation_error_message='A top-level site group with this name already exists.'),
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('slug',), name='dcim_sitegroup_slug', violation_error_message='A top-level site group with this slug already exists.'),
),
]

View File

@ -269,7 +269,7 @@ class CableTermination(models.Model):
constraints = (
models.UniqueConstraint(
fields=('termination_type', 'termination_id'),
name='dcim_cable_termination_unique_termination'
name='%(app_label)s_%(class)s_unique_termination'
),
)

View File

@ -61,6 +61,13 @@ class ComponentTemplateModel(WebhooksMixin, ChangeLoggedModel):
class Meta:
abstract = True
ordering = ('device_type', '_name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
name='%(app_label)s_%(class)s_unique_device_type_name'
),
)
def __str__(self):
if self.label:
@ -100,6 +107,17 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
class Meta:
abstract = True
ordering = ('device_type', 'module_type', '_name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
name='%(app_label)s_%(class)s_unique_device_type_name'
),
models.UniqueConstraint(
fields=('module_type', 'name'),
name='%(app_label)s_%(class)s_unique_module_type_name'
),
)
def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
@ -145,13 +163,6 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
component_model = ConsolePort
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@ -181,13 +192,6 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
component_model = ConsoleServerPort
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@ -229,13 +233,6 @@ class PowerPortTemplate(ModularComponentTemplateModel):
component_model = PowerPort
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@ -291,13 +288,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
component_model = PowerOutlet
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def clean(self):
super().clean()
@ -372,13 +362,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
component_model = Interface
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@ -428,12 +411,20 @@ class FrontPortTemplate(ModularComponentTemplateModel):
component_model = FrontPort
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
('rear_port', 'rear_port_position'),
class Meta(ModularComponentTemplateModel.Meta):
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
name='%(app_label)s_%(class)s_unique_device_type_name'
),
models.UniqueConstraint(
fields=('module_type', 'name'),
name='%(app_label)s_%(class)s_unique_module_type_name'
),
models.UniqueConstraint(
fields=('rear_port', 'rear_port_position'),
name='%(app_label)s_%(class)s_unique_rear_port_position'
),
)
def clean(self):
@ -507,13 +498,6 @@ class RearPortTemplate(ModularComponentTemplateModel):
component_model = RearPort
class Meta:
ordering = ('device_type', 'module_type', '_name')
unique_together = (
('device_type', 'name'),
('module_type', 'name'),
)
def instantiate(self, **kwargs):
return self.component_model(
name=self.resolve_name(kwargs.get('module')),
@ -547,10 +531,6 @@ class ModuleBayTemplate(ComponentTemplateModel):
component_model = ModuleBay
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return self.component_model(
device=device,
@ -574,10 +554,6 @@ class DeviceBayTemplate(ComponentTemplateModel):
"""
component_model = DeviceBay
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def instantiate(self, device):
return self.component_model(
device=device,
@ -653,7 +629,12 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
class Meta:
ordering = ('device_type__id', 'parent__id', '_name')
unique_together = ('device_type', 'parent', 'name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'parent', 'name'),
name='%(app_label)s_%(class)s_unique_device_type_parent_name'
),
)
def instantiate(self, **kwargs):
parent = InventoryItem.objects.get(name=self.parent.name, **kwargs) if self.parent else None

View File

@ -69,6 +69,13 @@ class ComponentModel(NetBoxModel):
class Meta:
abstract = True
ordering = ('device', '_name')
constraints = (
models.UniqueConstraint(
fields=('device', 'name'),
name='%(app_label)s_%(class)s_unique_device_name'
),
)
def __str__(self):
if self.label:
@ -99,7 +106,7 @@ class ModularComponentModel(ComponentModel):
object_id_field='component_id'
)
class Meta:
class Meta(ComponentModel.Meta):
abstract = True
@ -265,10 +272,6 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint):
clone_fields = ('device', 'module', 'type', 'speed')
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
@ -292,10 +295,6 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
clone_fields = ('device', 'module', 'type', 'speed')
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:consoleserverport', kwargs={'pk': self.pk})
@ -329,10 +328,6 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint):
clone_fields = ('device', 'module', 'maximum_draw', 'allocated_draw')
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:powerport', kwargs={'pk': self.pk})
@ -443,10 +438,6 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint):
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:poweroutlet', kwargs={'pk': self.pk})
@ -677,9 +668,8 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'poe_mode', 'poe_type', 'vrf',
)
class Meta:
class Meta(ModularComponentModel.Meta):
ordering = ('device', CollateAsChar('_name'))
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:interface', kwargs={'pk': self.pk})
@ -895,11 +885,16 @@ class FrontPort(ModularComponentModel, CabledObjectModel):
clone_fields = ('device', 'type', 'color')
class Meta:
ordering = ('device', '_name')
unique_together = (
('device', 'name'),
('rear_port', 'rear_port_position'),
class Meta(ModularComponentModel.Meta):
constraints = (
models.UniqueConstraint(
fields=('device', 'name'),
name='%(app_label)s_%(class)s_unique_device_name'
),
models.UniqueConstraint(
fields=('rear_port', 'rear_port_position'),
name='%(app_label)s_%(class)s_unique_rear_port_position'
),
)
def get_absolute_url(self):
@ -944,10 +939,6 @@ class RearPort(ModularComponentModel, CabledObjectModel):
)
clone_fields = ('device', 'type', 'color', 'positions')
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:rearport', kwargs={'pk': self.pk})
@ -980,10 +971,6 @@ class ModuleBay(ComponentModel):
clone_fields = ('device',)
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
@ -1002,10 +989,6 @@ class DeviceBay(ComponentModel):
clone_fields = ('device',)
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def get_absolute_url(self):
return reverse('dcim:devicebay', kwargs={'pk': self.pk})
@ -1141,7 +1124,12 @@ class InventoryItem(MPTTModel, ComponentModel):
class Meta:
ordering = ('device__id', 'parent__id', '_name')
unique_together = ('device', 'parent', 'name')
constraints = (
models.UniqueConstraint(
fields=('device', 'parent', 'name'),
name='%(app_label)s_%(class)s_unique_device_parent_name'
),
)
def get_absolute_url(self):
return reverse('dcim:inventoryitem', kwargs={'pk': self.pk})

View File

@ -143,10 +143,16 @@ class DeviceType(NetBoxModel):
class Meta:
ordering = ['manufacturer', 'model']
unique_together = [
['manufacturer', 'model'],
['manufacturer', 'slug'],
]
constraints = (
models.UniqueConstraint(
fields=('manufacturer', 'model'),
name='%(app_label)s_%(class)s_unique_manufacturer_model'
),
models.UniqueConstraint(
fields=('manufacturer', 'slug'),
name='%(app_label)s_%(class)s_unique_manufacturer_slug'
),
)
def __str__(self):
return self.model
@ -341,8 +347,11 @@ class ModuleType(NetBoxModel):
class Meta:
ordering = ('manufacturer', 'model')
unique_together = (
('manufacturer', 'model'),
constraints = (
models.UniqueConstraint(
fields=('manufacturer', 'model'),
name='%(app_label)s_%(class)s_unique_manufacturer_model'
),
)
def __str__(self):
@ -651,10 +660,25 @@ class Device(NetBoxModel, ConfigContextModel):
class Meta:
ordering = ('_name', 'pk') # Name may be null
unique_together = (
('site', 'tenant', 'name'), # See validate_unique below
('rack', 'position', 'face'),
('virtual_chassis', 'vc_position'),
constraints = (
models.UniqueConstraint(
fields=('name', 'site', 'tenant'),
name='%(app_label)s_%(class)s_unique_name_site_tenant'
),
models.UniqueConstraint(
fields=('name', 'site'),
name='%(app_label)s_%(class)s_unique_name_site',
condition=Q(tenant__isnull=True),
violation_error_message="Device name must be unique per site."
),
models.UniqueConstraint(
fields=('rack', 'position', 'face'),
name='%(app_label)s_%(class)s_unique_rack_position_face'
),
models.UniqueConstraint(
fields=('virtual_chassis', 'vc_position'),
name='%(app_label)s_%(class)s_unique_virtual_chassis_vc_position'
),
)
def __str__(self):
@ -679,23 +703,6 @@ class Device(NetBoxModel, ConfigContextModel):
def get_absolute_url(self):
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.name and hasattr(self, 'site') and self.tenant is None:
if Device.objects.exclude(pk=self.pk).filter(
name=self.name,
site=self.site,
tenant__isnull=True
):
raise ValidationError({
'name': 'A device with this name already exists.'
})
super().validate_unique(exclude)
def clean(self):
super().clean()

View File

@ -50,7 +50,12 @@ class PowerPanel(NetBoxModel):
class Meta:
ordering = ['site', 'name']
unique_together = ['site', 'name']
constraints = (
models.UniqueConstraint(
fields=('site', 'name'),
name='%(app_label)s_%(class)s_unique_site_name'
),
)
def __str__(self):
return self.name
@ -138,7 +143,12 @@ class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
class Meta:
ordering = ['power_panel', 'name']
unique_together = ['power_panel', 'name']
constraints = (
models.UniqueConstraint(
fields=('power_panel', 'name'),
name='%(app_label)s_%(class)s_unique_power_panel_name'
),
)
def __str__(self):
return self.name

View File

@ -3,12 +3,11 @@ import decimal
from django.apps import apps
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Count, Sum
from django.db.models import Count
from django.urls import reverse
from dcim.choices import *
@ -18,7 +17,7 @@ from netbox.models import OrganizationalModel, NetBoxModel
from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField
from utilities.utils import array_to_string, drange
from .device_components import PowerOutlet, PowerPort
from .device_components import PowerPort
from .devices import Device
from .power import PowerFeed
@ -191,10 +190,16 @@ class Rack(NetBoxModel):
class Meta:
ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
unique_together = (
constraints = (
# Name and facility_id must be unique *only* within a Location
('location', 'name'),
('location', 'facility_id'),
models.UniqueConstraint(
fields=('location', 'name'),
name='%(app_label)s_%(class)s_unique_location_name'
),
models.UniqueConstraint(
fields=('location', 'facility_id'),
name='%(app_label)s_%(class)s_unique_location_facility_id'
),
)
def __str__(self):

View File

@ -62,38 +62,26 @@ class Region(NestedGroupModel):
constraints = (
models.UniqueConstraint(
fields=('parent', 'name'),
name='dcim_region_parent_name'
name='%(app_label)s_%(class)s_parent_name'
),
models.UniqueConstraint(
fields=('name',),
name='dcim_region_name',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_name',
condition=Q(parent__isnull=True),
violation_error_message="A top-level region with this name already exists."
),
models.UniqueConstraint(
fields=('parent', 'slug'),
name='dcim_region_parent_slug'
name='%(app_label)s_%(class)s_parent_slug'
),
models.UniqueConstraint(
fields=('slug',),
name='dcim_region_slug',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_slug',
condition=Q(parent__isnull=True),
violation_error_message="A top-level region with this slug already exists."
),
)
def validate_unique(self, exclude=None):
if self.parent is None:
regions = Region.objects.exclude(pk=self.pk)
if regions.filter(name=self.name, parent__isnull=True).exists():
raise ValidationError({
'name': 'A region with this name already exists.'
})
if regions.filter(slug=self.slug, parent__isnull=True).exists():
raise ValidationError({
'name': 'A region with this slug already exists.'
})
super().validate_unique(exclude=exclude)
def get_absolute_url(self):
return reverse('dcim:region', args=[self.pk])
@ -148,38 +136,26 @@ class SiteGroup(NestedGroupModel):
constraints = (
models.UniqueConstraint(
fields=('parent', 'name'),
name='dcim_sitegroup_parent_name'
name='%(app_label)s_%(class)s_parent_name'
),
models.UniqueConstraint(
fields=('name',),
name='dcim_sitegroup_name',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_name',
condition=Q(parent__isnull=True),
violation_error_message="A top-level site group with this name already exists."
),
models.UniqueConstraint(
fields=('parent', 'slug'),
name='dcim_sitegroup_parent_slug'
name='%(app_label)s_%(class)s_parent_slug'
),
models.UniqueConstraint(
fields=('slug',),
name='dcim_sitegroup_slug',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_slug',
condition=Q(parent__isnull=True),
violation_error_message="A top-level site group with this slug already exists."
),
)
def validate_unique(self, exclude=None):
if self.parent is None:
site_groups = SiteGroup.objects.exclude(pk=self.pk)
if site_groups.filter(name=self.name, parent__isnull=True).exists():
raise ValidationError({
'name': 'A site group with this name already exists.'
})
if site_groups.filter(slug=self.slug, parent__isnull=True).exists():
raise ValidationError({
'name': 'A site group with this slug already exists.'
})
super().validate_unique(exclude=exclude)
def get_absolute_url(self):
return reverse('dcim:sitegroup', args=[self.pk])
@ -379,38 +355,26 @@ class Location(NestedGroupModel):
constraints = (
models.UniqueConstraint(
fields=('site', 'parent', 'name'),
name='dcim_location_parent_name'
name='%(app_label)s_%(class)s_parent_name'
),
models.UniqueConstraint(
fields=('site', 'name'),
name='dcim_location_name',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_name',
condition=Q(parent__isnull=True),
violation_error_message="A location with this name already exists within the specified site."
),
models.UniqueConstraint(
fields=('site', 'parent', 'slug'),
name='dcim_location_parent_slug'
name='%(app_label)s_%(class)s_parent_slug'
),
models.UniqueConstraint(
fields=('site', 'slug'),
name='dcim_location_slug',
condition=Q(parent=None)
name='%(app_label)s_%(class)s_slug',
condition=Q(parent__isnull=True),
violation_error_message="A location with this slug already exists within the specified site."
),
)
def validate_unique(self, exclude=None):
if self.parent is None:
locations = Location.objects.exclude(pk=self.pk)
if locations.filter(name=self.name, site=self.site, parent__isnull=True).exists():
raise ValidationError({
"name": f"A location with this name in site {self.site} already exists."
})
if locations.filter(slug=self.slug, site=self.site, parent__isnull=True).exists():
raise ValidationError({
"name": f"A location with this slug in site {self.site} already exists."
})
super().validate_unique(exclude=exclude)
@classmethod
def get_prerequisite_models(cls):
return [Site, ]

View File

@ -384,7 +384,7 @@ class DeviceTestCase(TestCase):
site=self.site,
device_type=self.device_type,
device_role=self.device_role,
name=''
name=None
)
device1.save()
@ -392,12 +392,12 @@ class DeviceTestCase(TestCase):
site=device1.site,
device_type=device1.device_type,
device_role=device1.device_role,
name=''
name=None
)
device2.full_clean()
device2.save()
self.assertEqual(Device.objects.filter(name='').count(), 2)
self.assertEqual(Device.objects.filter(name__isnull=True).count(), 2)
def test_device_duplicate_names(self):

View File

@ -0,0 +1,27 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('extras', '0077_customlink_extend_text_and_url'),
]
operations = [
migrations.AlterUniqueTogether(
name='exporttemplate',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='webhook',
unique_together=set(),
),
migrations.AddConstraint(
model_name='exporttemplate',
constraint=models.UniqueConstraint(fields=('content_type', 'name'), name='extras_exporttemplate_unique_content_type_name'),
),
migrations.AddConstraint(
model_name='webhook',
constraint=models.UniqueConstraint(fields=('payload_url', 'type_create', 'type_update', 'type_delete'), name='extras_webhook_unique_payload_url_types'),
),
]

View File

@ -131,7 +131,12 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
class Meta:
ordering = ('name',)
unique_together = ('payload_url', 'type_create', 'type_update', 'type_delete',)
constraints = (
models.UniqueConstraint(
fields=('payload_url', 'type_create', 'type_update', 'type_delete'),
name='%(app_label)s_%(class)s_unique_payload_url_types'
),
)
def __str__(self):
return self.name
@ -297,9 +302,12 @@ class ExportTemplate(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
class Meta:
ordering = ['content_type', 'name']
unique_together = [
['content_type', 'name']
]
constraints = (
models.UniqueConstraint(
fields=('content_type', 'name'),
name='%(app_label)s_%(class)s_unique_content_type_name'
),
)
def __str__(self):
return f"{self.content_type}: {self.name}"

View File

@ -0,0 +1,43 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ipam', '0061_fhrpgroup_name'),
]
operations = [
migrations.AlterUniqueTogether(
name='fhrpgroupassignment',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='vlan',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='vlangroup',
unique_together=set(),
),
migrations.AddConstraint(
model_name='fhrpgroupassignment',
constraint=models.UniqueConstraint(fields=('interface_type', 'interface_id', 'group'), name='ipam_fhrpgroupassignment_unique_interface_group'),
),
migrations.AddConstraint(
model_name='vlan',
constraint=models.UniqueConstraint(fields=('group', 'vid'), name='ipam_vlan_unique_group_vid'),
),
migrations.AddConstraint(
model_name='vlan',
constraint=models.UniqueConstraint(fields=('group', 'name'), name='ipam_vlan_unique_group_name'),
),
migrations.AddConstraint(
model_name='vlangroup',
constraint=models.UniqueConstraint(fields=('scope_type', 'scope_id', 'name'), name='ipam_vlangroup_unique_scope_name'),
),
migrations.AddConstraint(
model_name='vlangroup',
constraint=models.UniqueConstraint(fields=('scope_type', 'scope_id', 'slug'), name='ipam_vlangroup_unique_scope_slug'),
),
]

View File

@ -102,7 +102,12 @@ class FHRPGroupAssignment(WebhooksMixin, ChangeLoggedModel):
class Meta:
ordering = ('-priority', 'pk')
unique_together = ('interface_type', 'interface_id', 'group')
constraints = (
models.UniqueConstraint(
fields=('interface_type', 'interface_id', 'group'),
name='%(app_label)s_%(class)s_unique_interface_group'
),
)
verbose_name = 'FHRP group assignment'
def __str__(self):

View File

@ -70,10 +70,16 @@ class VLANGroup(OrganizationalModel):
class Meta:
ordering = ('name', 'pk') # Name may be non-unique
unique_together = [
['scope_type', 'scope_id', 'name'],
['scope_type', 'scope_id', 'slug'],
]
constraints = (
models.UniqueConstraint(
fields=('scope_type', 'scope_id', 'name'),
name='%(app_label)s_%(class)s_unique_scope_name'
),
models.UniqueConstraint(
fields=('scope_type', 'scope_id', 'slug'),
name='%(app_label)s_%(class)s_unique_scope_slug'
),
)
verbose_name = 'VLAN group'
verbose_name_plural = 'VLAN groups'
@ -189,10 +195,16 @@ class VLAN(NetBoxModel):
class Meta:
ordering = ('site', 'group', 'vid', 'pk') # (site, group, vid) may be non-unique
unique_together = [
['group', 'vid'],
['group', 'name'],
]
constraints = (
models.UniqueConstraint(
fields=('group', 'vid'),
name='%(app_label)s_%(class)s_unique_group_vid'
),
models.UniqueConstraint(
fields=('group', 'name'),
name='%(app_label)s_%(class)s_unique_group_name'
),
)
verbose_name = 'VLAN'
verbose_name_plural = 'VLANs'

View File

@ -0,0 +1,35 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0007_contact_link'),
]
operations = [
migrations.AlterUniqueTogether(
name='contact',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='contactassignment',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='contactgroup',
unique_together=set(),
),
migrations.AddConstraint(
model_name='contact',
constraint=models.UniqueConstraint(fields=('group', 'name'), name='tenancy_contact_unique_group_name'),
),
migrations.AddConstraint(
model_name='contactassignment',
constraint=models.UniqueConstraint(fields=('content_type', 'object_id', 'contact', 'role'), name='tenancy_contactassignment_unique_object_contact_role'),
),
migrations.AddConstraint(
model_name='contactgroup',
constraint=models.UniqueConstraint(fields=('parent', 'name'), name='tenancy_contactgroup_unique_parent_name'),
),
]

View File

@ -41,8 +41,11 @@ class ContactGroup(NestedGroupModel):
class Meta:
ordering = ['name']
unique_together = (
('parent', 'name')
constraints = (
models.UniqueConstraint(
fields=('parent', 'name'),
name='%(app_label)s_%(class)s_unique_parent_name'
),
)
def get_absolute_url(self):
@ -118,8 +121,11 @@ class Contact(NetBoxModel):
class Meta:
ordering = ['name']
unique_together = (
('group', 'name')
constraints = (
models.UniqueConstraint(
fields=('group', 'name'),
name='%(app_label)s_%(class)s_unique_group_name'
),
)
def __str__(self):
@ -159,7 +165,12 @@ class ContactAssignment(WebhooksMixin, ChangeLoggedModel):
class Meta:
ordering = ('priority', 'contact')
unique_together = ('content_type', 'object_id', 'contact', 'role', 'priority')
constraints = (
models.UniqueConstraint(
fields=('content_type', 'object_id', 'contact', 'role'),
name='%(app_label)s_%(class)s_unique_object_contact_role'
),
)
def __str__(self):
if self.priority:

View File

@ -0,0 +1,43 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('virtualization', '0032_virtualmachine_update_sites'),
]
operations = [
migrations.AlterUniqueTogether(
name='cluster',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='virtualmachine',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='vminterface',
unique_together=set(),
),
migrations.AddConstraint(
model_name='cluster',
constraint=models.UniqueConstraint(fields=('group', 'name'), name='virtualization_cluster_unique_group_name'),
),
migrations.AddConstraint(
model_name='cluster',
constraint=models.UniqueConstraint(fields=('site', 'name'), name='virtualization_cluster_unique_site_name'),
),
migrations.AddConstraint(
model_name='virtualmachine',
constraint=models.UniqueConstraint(fields=('name', 'cluster', 'tenant'), name='virtualization_virtualmachine_unique_name_cluster_tenant'),
),
migrations.AddConstraint(
model_name='virtualmachine',
constraint=models.UniqueConstraint(condition=models.Q(('tenant__isnull', True)), fields=('name', 'cluster'), name='virtualization_virtualmachine_unique_name_cluster', violation_error_message='Virtual machine name must be unique per site.'),
),
migrations.AddConstraint(
model_name='vminterface',
constraint=models.UniqueConstraint(fields=('virtual_machine', 'name'), name='virtualization_vminterface_unique_virtual_machine_name'),
),
]

View File

@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Q
from django.urls import reverse
from dcim.models import BaseInterface, Device
@ -159,9 +160,15 @@ class Cluster(NetBoxModel):
class Meta:
ordering = ['name']
unique_together = (
('group', 'name'),
('site', 'name'),
constraints = (
models.UniqueConstraint(
fields=('group', 'name'),
name='%(app_label)s_%(class)s_unique_group_name'
),
models.UniqueConstraint(
fields=('site', 'name'),
name='%(app_label)s_%(class)s_unique_site_name'
),
)
def __str__(self):
@ -309,9 +316,18 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
class Meta:
ordering = ('_name', 'pk') # Name may be non-unique
unique_together = [
['cluster', 'tenant', 'name']
]
constraints = (
models.UniqueConstraint(
fields=('name', 'cluster', 'tenant'),
name='%(app_label)s_%(class)s_unique_name_cluster_tenant'
),
models.UniqueConstraint(
fields=('name', 'cluster'),
name='%(app_label)s_%(class)s_unique_name_cluster',
condition=Q(tenant__isnull=True),
violation_error_message="Virtual machine name must be unique per site."
),
)
def __str__(self):
return self.name
@ -323,20 +339,6 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
def get_absolute_url(self):
return reverse('virtualization:virtualmachine', args=[self.pk])
def validate_unique(self, exclude=None):
# Check for a duplicate name on a VM assigned to the same Cluster 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 VirtualMachine.objects.exclude(pk=self.pk).filter(
name=self.name, cluster=self.cluster, tenant__isnull=True
):
raise ValidationError({
'name': 'A virtual machine with this name already exists in the assigned cluster.'
})
super().validate_unique(exclude)
def clean(self):
super().clean()
@ -465,9 +467,14 @@ class VMInterface(NetBoxModel, BaseInterface):
)
class Meta:
verbose_name = 'interface'
ordering = ('virtual_machine', CollateAsChar('_name'))
unique_together = ('virtual_machine', 'name')
constraints = (
models.UniqueConstraint(
fields=('virtual_machine', 'name'),
name='%(app_label)s_%(class)s_unique_virtual_machine_name'
),
)
verbose_name = 'interface'
def __str__(self):
return self.name

View File

@ -0,0 +1,27 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wireless', '0005_wirelesslink_interface_types'),
]
operations = [
migrations.AlterUniqueTogether(
name='wirelesslangroup',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='wirelesslink',
unique_together=set(),
),
migrations.AddConstraint(
model_name='wirelesslangroup',
constraint=models.UniqueConstraint(fields=('parent', 'name'), name='wireless_wirelesslangroup_unique_parent_name'),
),
migrations.AddConstraint(
model_name='wirelesslink',
constraint=models.UniqueConstraint(fields=('interface_a', 'interface_b'), name='wireless_wirelesslink_unique_interfaces'),
),
]

View File

@ -69,8 +69,11 @@ class WirelessLANGroup(NestedGroupModel):
class Meta:
ordering = ('name', 'pk')
unique_together = (
('parent', 'name')
constraints = (
models.UniqueConstraint(
fields=('parent', 'name'),
name='%(app_label)s_%(class)s_unique_parent_name'
),
)
verbose_name = 'Wireless LAN Group'
@ -195,7 +198,12 @@ class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
class Meta:
ordering = ['pk']
unique_together = ('interface_a', 'interface_b')
constraints = (
models.UniqueConstraint(
fields=('interface_a', 'interface_b'),
name='%(app_label)s_%(class)s_unique_interfaces'
),
)
def __str__(self):
return f'#{self.pk}'