From 7c56b21095689667cc25125f5888526803449471 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 19 Oct 2021 13:46:35 -0400 Subject: [PATCH] Closes #7354: Relax uniqueness constraints on region, site group, and location names --- docs/models/dcim/location.md | 3 +- docs/models/dcim/rack.md | 2 +- docs/models/dcim/region.md | 2 + docs/models/dcim/sitegroup.md | 2 + docs/release-notes/version-3.1.md | 1 + netbox/dcim/api/serializers.py | 4 +- .../0137_relax_uniqueness_constraints.py | 45 +++++++++++++++++++ netbox/dcim/models/sites.py | 32 ++++++++----- 8 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 netbox/dcim/migrations/0137_relax_uniqueness_constraints.py diff --git a/docs/models/dcim/location.md b/docs/models/dcim/location.md index 16df208ac..901a68acf 100644 --- a/docs/models/dcim/location.md +++ b/docs/models/dcim/location.md @@ -2,4 +2,5 @@ Racks and devices can be grouped by location within a site. A location may represent a floor, room, cage, or similar organizational unit. Locations can be nested to form a hierarchy. For example, you may have floors within a site, and rooms within a floor. -The name and facility ID of each rack within a location must be unique. (Racks not assigned to the same location may have identical names and/or facility IDs.) +Each location must have a name that is unique within its parent site and location, if any. + diff --git a/docs/models/dcim/rack.md b/docs/models/dcim/rack.md index 90c9cfe6e..9465a828c 100644 --- a/docs/models/dcim/rack.md +++ b/docs/models/dcim/rack.md @@ -1,6 +1,6 @@ # Racks -The rack model represents a physical two- or four-post equipment rack in which devices can be installed. Each rack must be assigned to a site, and may optionally be assigned to a location and/or tenant. Racks can also be organized by user-defined functional roles. +The rack model represents a physical two- or four-post equipment rack in which devices can be installed. Each rack must be assigned to a site, and may optionally be assigned to a location and/or tenant. Racks can also be organized by user-defined functional roles. The name and facility ID of each rack within a location must be unique. Rack height is measured in *rack units* (U); racks are commonly between 42U and 48U tall, but NetBox allows you to define racks of arbitrary height. A toggle is provided to indicate whether rack units are in ascending (from the ground up) or descending order. diff --git a/docs/models/dcim/region.md b/docs/models/dcim/region.md index 734467500..bac186264 100644 --- a/docs/models/dcim/region.md +++ b/docs/models/dcim/region.md @@ -1,3 +1,5 @@ # Regions Sites can be arranged geographically using regions. A region might represent a continent, country, city, campus, or other area depending on your use case. Regions can be nested recursively to construct a hierarchy. For example, you might define several country regions, and within each of those several state or city regions to which sites are assigned. + +Each region must have a name that is unique within its parent region, if any. diff --git a/docs/models/dcim/sitegroup.md b/docs/models/dcim/sitegroup.md index 3c1ed11bd..04ebcc1a5 100644 --- a/docs/models/dcim/sitegroup.md +++ b/docs/models/dcim/sitegroup.md @@ -1,3 +1,5 @@ # Site Groups Like regions, site groups can be used to organize sites. Whereas regions are intended to provide geographic organization, site groups can be used to classify sites by role or function. Also like regions, site groups can be nested to form a hierarchy. Sites which belong to a child group are also considered to be members of any of its parent groups. + +Each site group must have a name that is unique within its parent group, if any. diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index 172fd3ed8..a94f749c7 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -15,6 +15,7 @@ * [#6711](https://github.com/netbox-community/netbox/issues/6711) - Add `longtext` custom field type with Markdown support * [#6715](https://github.com/netbox-community/netbox/issues/6715) - Add tenant assignment for cables * [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations +* [#7354](https://github.com/netbox-community/netbox/issues/7354) - Relax uniqueness constraints on region, site group, and location names ### Other Changes diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 22ea903bb..9b0e7f5b3 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -81,7 +81,7 @@ class ConnectedEndpointSerializer(serializers.ModelSerializer): class RegionSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') - parent = NestedRegionSerializer(required=False, allow_null=True) + parent = NestedRegionSerializer(required=False, allow_null=True, default=None) site_count = serializers.IntegerField(read_only=True) class Meta: @@ -94,7 +94,7 @@ class RegionSerializer(NestedGroupModelSerializer): class SiteGroupSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail') - parent = NestedSiteGroupSerializer(required=False, allow_null=True) + parent = NestedSiteGroupSerializer(required=False, allow_null=True, default=None) site_count = serializers.IntegerField(read_only=True) class Meta: diff --git a/netbox/dcim/migrations/0137_relax_uniqueness_constraints.py b/netbox/dcim/migrations/0137_relax_uniqueness_constraints.py new file mode 100644 index 000000000..8f7d40026 --- /dev/null +++ b/netbox/dcim/migrations/0137_relax_uniqueness_constraints.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.8 on 2021-10-19 17:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0136_device_airflow'), + ] + + operations = [ + migrations.AlterField( + model_name='region', + name='name', + field=models.CharField(max_length=100), + ), + migrations.AlterField( + model_name='region', + name='slug', + field=models.SlugField(max_length=100), + ), + migrations.AlterField( + model_name='sitegroup', + name='name', + field=models.CharField(max_length=100), + ), + migrations.AlterField( + model_name='sitegroup', + name='slug', + field=models.SlugField(max_length=100), + ), + migrations.AlterUniqueTogether( + name='location', + unique_together={('site', 'parent', 'name'), ('site', 'parent', 'slug')}, + ), + migrations.AlterUniqueTogether( + name='region', + unique_together={('parent', 'slug'), ('parent', 'name')}, + ), + migrations.AlterUniqueTogether( + name='sitegroup', + unique_together={('parent', 'slug'), ('parent', 'name')}, + ), + ] diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 0d9816b0b..ab9d8e82d 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -41,12 +41,10 @@ class Region(NestedGroupModel): db_index=True ) name = models.CharField( - max_length=100, - unique=True + max_length=100 ) slug = models.SlugField( - max_length=100, - unique=True + max_length=100 ) description = models.CharField( max_length=200, @@ -64,6 +62,12 @@ class Region(NestedGroupModel): to='tenancy.ContactAssignment' ) + class Meta: + unique_together = ( + ('parent', 'name'), + ('parent', 'slug'), + ) + def get_absolute_url(self): return reverse('dcim:region', args=[self.pk]) @@ -94,12 +98,10 @@ class SiteGroup(NestedGroupModel): db_index=True ) name = models.CharField( - max_length=100, - unique=True + max_length=100 ) slug = models.SlugField( - max_length=100, - unique=True + max_length=100 ) description = models.CharField( max_length=200, @@ -117,6 +119,12 @@ class SiteGroup(NestedGroupModel): to='tenancy.ContactAssignment' ) + class Meta: + unique_together = ( + ('parent', 'name'), + ('parent', 'slug'), + ) + def get_absolute_url(self): return reverse('dcim:sitegroup', args=[self.pk]) @@ -325,10 +333,10 @@ class Location(NestedGroupModel): class Meta: ordering = ['site', 'name'] - unique_together = [ - ['site', 'name'], - ['site', 'slug'], - ] + unique_together = ([ + ('site', 'parent', 'name'), + ('site', 'parent', 'slug'), + ]) def get_absolute_url(self): return reverse('dcim:location', args=[self.pk])