mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-15 19:52:52 -06:00
Merge pull request #10368 from netbox-community/10361-unique-constraints
Closes #10361: Migrate from unique_together to UniqueConstraints
This commit is contained in:
commit
c349e06346
39
netbox/circuits/migrations/0039_unique_constraints.py
Normal file
39
netbox/circuits/migrations/0039_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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}'
|
||||
|
@ -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
|
||||
|
331
netbox/dcim/migrations/0162_unique_constraints.py
Normal file
331
netbox/dcim/migrations/0162_unique_constraints.py
Normal 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.'),
|
||||
),
|
||||
]
|
@ -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'
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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})
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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, ]
|
||||
|
@ -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):
|
||||
|
||||
|
27
netbox/extras/migrations/0078_unique_constraints.py
Normal file
27
netbox/extras/migrations/0078_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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}"
|
||||
|
43
netbox/ipam/migrations/0062_unique_constraints.py
Normal file
43
netbox/ipam/migrations/0062_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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):
|
||||
|
@ -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'
|
||||
|
||||
|
35
netbox/tenancy/migrations/0008_unique_constraints.py
Normal file
35
netbox/tenancy/migrations/0008_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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:
|
||||
|
43
netbox/virtualization/migrations/0033_unique_constraints.py
Normal file
43
netbox/virtualization/migrations/0033_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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
|
||||
|
27
netbox/wireless/migrations/0006_unique_constraints.py
Normal file
27
netbox/wireless/migrations/0006_unique_constraints.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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}'
|
||||
|
Loading…
Reference in New Issue
Block a user