From 8a08d3621b087d9d584decdae235931cb022933c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Feb 2023 20:03:41 -0500 Subject: [PATCH] Closes #10374: Require unique tenant names & slugs per group (not globally) --- docs/models/tenancy/tenant.md | 4 +- docs/release-notes/version-3.5.md | 1 + .../0010_tenant_relax_uniqueness.py | 39 +++++++++++++++++++ netbox/tenancy/models/tenants.py | 29 ++++++++++++-- 4 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 netbox/tenancy/migrations/0010_tenant_relax_uniqueness.py diff --git a/docs/models/tenancy/tenant.md b/docs/models/tenancy/tenant.md index 7df6992d1..45a09c787 100644 --- a/docs/models/tenancy/tenant.md +++ b/docs/models/tenancy/tenant.md @@ -6,11 +6,11 @@ A tenant represents a discrete grouping of resources used for administrative pur ### Name -A unique human-friendly name. +A human-friendly name, unique to the assigned group. ### Slug -A unique URL-friendly identifier. (This value can be used for filtering.) +A URL-friendly identifier, unique to the assigned group. (This value can be used for filtering.) ### Group diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 50ce41c78..02045186f 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -28,6 +28,7 @@ A new ASN range model has been introduced to facilitate the provisioning of new * [#9073](https://github.com/netbox-community/netbox/issues/9073) - Enable syncing config context data from remote sources * [#9653](https://github.com/netbox-community/netbox/issues/9653) - Enable setting a default platform for device types +* [#10374](https://github.com/netbox-community/netbox/issues/10374) - Require unique tenant names & slugs per group (not globally) * [#10729](https://github.com/netbox-community/netbox/issues/10729) - Add date & time custom field type * [#11254](https://github.com/netbox-community/netbox/issues/11254) - Introduce the `X-Request-ID` HTTP header to annotate the unique ID of each request for change logging * [#11440](https://github.com/netbox-community/netbox/issues/11440) - Add an `enabled` field for device type interfaces diff --git a/netbox/tenancy/migrations/0010_tenant_relax_uniqueness.py b/netbox/tenancy/migrations/0010_tenant_relax_uniqueness.py new file mode 100644 index 000000000..6082fbfe9 --- /dev/null +++ b/netbox/tenancy/migrations/0010_tenant_relax_uniqueness.py @@ -0,0 +1,39 @@ +# Generated by Django 4.1.7 on 2023-03-01 01:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenancy', '0009_standardize_description_comments'), + ] + + operations = [ + migrations.AlterField( + model_name='tenant', + name='name', + field=models.CharField(max_length=100), + ), + migrations.AlterField( + model_name='tenant', + name='slug', + field=models.SlugField(max_length=100), + ), + migrations.AddConstraint( + model_name='tenant', + constraint=models.UniqueConstraint(fields=('group', 'name'), name='tenancy_tenant_unique_group_name', violation_error_message='Tenant name must be unique per group.'), + ), + migrations.AddConstraint( + model_name='tenant', + constraint=models.UniqueConstraint(condition=models.Q(('group__isnull', True)), fields=('name',), name='tenancy_tenant_unique_name'), + ), + migrations.AddConstraint( + model_name='tenant', + constraint=models.UniqueConstraint(fields=('group', 'slug'), name='tenancy_tenant_unique_group_slug', violation_error_message='Tenant slug must be unique per group.'), + ), + migrations.AddConstraint( + model_name='tenant', + constraint=models.UniqueConstraint(condition=models.Q(('group__isnull', True)), fields=('slug',), name='tenancy_tenant_unique_slug'), + ), + ] diff --git a/netbox/tenancy/models/tenants.py b/netbox/tenancy/models/tenants.py index 4c0c11e2a..a41b8bf99 100644 --- a/netbox/tenancy/models/tenants.py +++ b/netbox/tenancy/models/tenants.py @@ -1,5 +1,6 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models +from django.db.models import Q from django.urls import reverse from netbox.models import NestedGroupModel, PrimaryModel @@ -36,12 +37,10 @@ class Tenant(PrimaryModel): department. """ name = models.CharField( - max_length=100, - unique=True + max_length=100 ) slug = models.SlugField( - max_length=100, - unique=True + max_length=100 ) group = models.ForeignKey( to='tenancy.TenantGroup', @@ -62,6 +61,28 @@ class Tenant(PrimaryModel): class Meta: ordering = ['name'] + constraints = ( + models.UniqueConstraint( + fields=('group', 'name'), + name='%(app_label)s_%(class)s_unique_group_name', + violation_error_message="Tenant name must be unique per group." + ), + models.UniqueConstraint( + fields=('name',), + name='%(app_label)s_%(class)s_unique_name', + condition=Q(group__isnull=True) + ), + models.UniqueConstraint( + fields=('group', 'slug'), + name='%(app_label)s_%(class)s_unique_group_slug', + violation_error_message="Tenant slug must be unique per group." + ), + models.UniqueConstraint( + fields=('slug',), + name='%(app_label)s_%(class)s_unique_slug', + condition=Q(group__isnull=True) + ), + ) def __str__(self): return self.name