From 89a90a8ef6df92e974d7b1352b1d4be53157670b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 10 Jul 2025 12:30:36 -0400 Subject: [PATCH] Fixes #19827: Enforce uniqueness for device role names & slugs --- .../migrations/0208_devicerole_uniqueness.py | 44 +++++++++++++++++++ netbox/dcim/models/devices.py | 22 ++++++++++ 2 files changed, 66 insertions(+) create mode 100644 netbox/dcim/migrations/0208_devicerole_uniqueness.py diff --git a/netbox/dcim/migrations/0208_devicerole_uniqueness.py b/netbox/dcim/migrations/0208_devicerole_uniqueness.py new file mode 100644 index 000000000..fbb9c7e64 --- /dev/null +++ b/netbox/dcim/migrations/0208_devicerole_uniqueness.py @@ -0,0 +1,44 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0207_remove_redundant_indexes'), + ('extras', '0129_fix_script_paths'), + ] + + operations = [ + migrations.AddConstraint( + model_name='devicerole', + constraint=models.UniqueConstraint( + fields=('parent', 'name'), + name='dcim_devicerole_parent_name' + ), + ), + migrations.AddConstraint( + model_name='devicerole', + constraint=models.UniqueConstraint( + condition=models.Q(('parent__isnull', True)), + fields=('name',), + name='dcim_devicerole_name', + violation_error_message='A top-level device role with this name already exists.' + ), + ), + migrations.AddConstraint( + model_name='devicerole', + constraint=models.UniqueConstraint( + fields=('parent', 'slug'), + name='dcim_devicerole_parent_slug' + ), + ), + migrations.AddConstraint( + model_name='devicerole', + constraint=models.UniqueConstraint( + condition=models.Q(('parent__isnull', True)), + fields=('slug',), + name='dcim_devicerole_slug', + violation_error_message='A top-level device role with this slug already exists.' + ), + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 5988f8241..f0c116b0e 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -398,6 +398,28 @@ class DeviceRole(NestedGroupModel): class Meta: ordering = ('name',) + constraints = ( + models.UniqueConstraint( + fields=('parent', 'name'), + name='%(app_label)s_%(class)s_parent_name' + ), + models.UniqueConstraint( + fields=('name',), + name='%(app_label)s_%(class)s_name', + condition=Q(parent__isnull=True), + violation_error_message=_("A top-level device role with this name already exists.") + ), + models.UniqueConstraint( + fields=('parent', 'slug'), + name='%(app_label)s_%(class)s_parent_slug' + ), + models.UniqueConstraint( + fields=('slug',), + name='%(app_label)s_%(class)s_slug', + condition=Q(parent__isnull=True), + violation_error_message=_("A top-level device role with this slug already exists.") + ), + ) verbose_name = _('device role') verbose_name_plural = _('device roles')