From ec153363403809786c9a4ffad511fe6483ac609e Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 31 May 2023 09:05:50 -0700 Subject: [PATCH 1/8] 12175 add rack starting unit --- netbox/dcim/constants.py | 2 ++ .../dcim/migrations/0174_rack_starting_unit.py | 17 +++++++++++++++++ netbox/dcim/models/racks.py | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 netbox/dcim/migrations/0174_rack_starting_unit.py diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 80d7558c9b..dad3c3a0de 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -16,6 +16,8 @@ RACK_ELEVATION_DEFAULT_LEGEND_WIDTH = 30 RACK_ELEVATION_DEFAULT_MARGIN_WIDTH = 15 +RACK_STARTING_UNIT_DEFAULT = 1 + # # RearPorts diff --git a/netbox/dcim/migrations/0174_rack_starting_unit.py b/netbox/dcim/migrations/0174_rack_starting_unit.py new file mode 100644 index 0000000000..22d9536383 --- /dev/null +++ b/netbox/dcim/migrations/0174_rack_starting_unit.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.9 on 2023-05-31 15:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0173_remove_napalm_fields'), + ] + + operations = [ + migrations.AddField( + model_name='rack', + name='starting_unit', + field=models.PositiveSmallIntegerField(default=1), + ), + ] diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index e5412a3ab9..3fae616d7a 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -129,6 +129,11 @@ class Rack(PrimaryModel, WeightMixin): validators=[MinValueValidator(1), MaxValueValidator(100)], help_text=_('Height in rack units') ) + starting_unit = models.PositiveSmallIntegerField( + default=RACK_STARTING_UNIT_DEFAULT, + verbose_name='Starting unit', + help_text=_('Starting unit for rack') + ) desc_units = models.BooleanField( default=False, verbose_name='Descending units', From ba9ef11522c539ba4c7652a062458d3f356ab3e2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 31 May 2023 11:40:47 -0700 Subject: [PATCH 2/8] 12175 rack starting unit to svg --- netbox/dcim/forms/model_forms.py | 4 ++-- netbox/dcim/models/racks.py | 4 ++-- netbox/dcim/svg/racks.py | 6 ++++-- netbox/templates/dcim/rack.html | 6 ++++++ netbox/templates/dcim/rack_edit.html | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 8379fd0857..d4a2641c77 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -221,8 +221,8 @@ class Meta: model = Rack fields = [ 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', - 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', - 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags', + 'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', + 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags', ] diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 3fae616d7a..f7d8c0d65f 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -274,8 +274,8 @@ def units(self): Return a list of unit numbers, top to bottom. """ if self.desc_units: - return drange(decimal.Decimal(1.0), self.u_height + 1, 0.5) - return drange(self.u_height + decimal.Decimal(0.5), 0.5, -0.5) + return drange(decimal.Decimal(self.starting_unit), self.u_height + 1, 0.5) + return drange(self.u_height + decimal.Decimal(0.5) + self.starting_unit - 1, 0.5 + self.starting_unit - 1, -0.5) def get_status_color(self): return RackStatusChoices.colors.get(self.status) diff --git a/netbox/dcim/svg/racks.py b/netbox/dcim/svg/racks.py index 9c317ea160..6333abcf18 100644 --- a/netbox/dcim/svg/racks.py +++ b/netbox/dcim/svg/racks.py @@ -150,9 +150,9 @@ def _get_device_coords(self, position, height): x = self.legend_width + RACK_ELEVATION_BORDER_WIDTH y = RACK_ELEVATION_BORDER_WIDTH if self.rack.desc_units: - y += int((position - 1) * self.unit_height) + y += int((position - self.rack.starting_unit) * self.unit_height) else: - y += int((self.rack.u_height - position + 1) * self.unit_height) - int(height * self.unit_height) + y += int((self.rack.u_height - position + self.rack.starting_unit) * self.unit_height) - int(height * self.unit_height) return x, y @@ -237,6 +237,7 @@ def draw_legend(self): start_y = ru * self.unit_height + RACK_ELEVATION_BORDER_WIDTH position_coordinates = (self.legend_width / 2, start_y + self.unit_height / 2 + RACK_ELEVATION_BORDER_WIDTH) unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru + unit = unit + self.rack.starting_unit - 1 self.drawing.add( Text(str(unit), position_coordinates, class_='unit') ) @@ -278,6 +279,7 @@ def draw_background(self, face): for ru in range(0, self.rack.u_height): unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru + unit = unit + self.rack.starting_unit - 1 y_offset = RACK_ELEVATION_BORDER_WIDTH + ru * self.unit_height text_coords = ( x_offset + self.unit_width / 2, diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 52b5d4bfec..01aeacff17 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -101,6 +101,12 @@
Dimensions
Height {{ object.u_height }}U ({% if object.desc_units %}descending{% else %}ascending{% endif %}) + + Starting Unit + + {{ object.starting_unit }} + + Outer Width diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index 4bbd72405f..a1ebb7531e 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -71,6 +71,7 @@
Dimensions
{% render_field form.mounting_depth %} {% render_field form.desc_units %} + {% render_field form.starting_unit %} {% if form.custom_fields %} From 78e26ea5914fe0aa78a4fbc67c628daf1b1178ce Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 15 Jun 2023 12:46:18 -0700 Subject: [PATCH 3/8] verify devices can still fit if change rack starting_unit --- netbox/dcim/models/racks.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index f7d8c0d65f..2c5976d3dd 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -241,12 +241,25 @@ def clean(self): ).order_by('-position').first() if top_device: min_height = top_device.position + top_device.device_type.u_height - 1 - if self.u_height < min_height: + if self.u_height + self.start_unit < min_height: raise ValidationError({ 'u_height': "Rack must be at least {}U tall to house currently installed devices.".format( min_height ) }) + # Validate the Rack starting_unit > position of any installed devices + first_device = Device.objects.filter( + rack=self + ).exclude( + position__isnull=True + ).order_by('position').first() + if first_device: + if self.starting_unit > first_device.position: + raise ValidationError({ + 'starting_unit': "Rack must have a staring unit at most {} to house currently installed devices.".format( + first_device.position + ) + }) # Validate that Rack was assigned a Location of its same site, if applicable if self.location: if self.location.site != self.site: From 080daba8403813082eaddddd830eac5e2be67b31 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 15 Jun 2023 12:54:45 -0700 Subject: [PATCH 4/8] 12175 fix migration --- netbox/dcim/migrations/0174_rack_starting_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/migrations/0174_rack_starting_unit.py b/netbox/dcim/migrations/0174_rack_starting_unit.py index 22d9536383..e32738660a 100644 --- a/netbox/dcim/migrations/0174_rack_starting_unit.py +++ b/netbox/dcim/migrations/0174_rack_starting_unit.py @@ -5,7 +5,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dcim', '0173_remove_napalm_fields'), + ('dcim', '0174_device_latitude_device_longitude'), ] operations = [ From affc927eb66ed72fba2364a5cbacc856ce99266c Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 15 Jun 2023 13:07:04 -0700 Subject: [PATCH 5/8] 12175 fix typo and test --- netbox/dcim/models/racks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index cfab3ce0af..17a35a5279 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -241,7 +241,7 @@ def clean(self): ).order_by('-position').first() if top_device: min_height = top_device.position + top_device.device_type.u_height - 1 - if self.u_height + self.start_unit < min_height: + if self.u_height + self.starting_unit - 1 < min_height: raise ValidationError({ 'u_height': "Rack must be at least {}U tall to house currently installed devices.".format( min_height From 1ce0484f911f58bd682bcb7db63f153fe39b5bf4 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 15 Jun 2023 13:48:45 -0700 Subject: [PATCH 6/8] 12175 fix test --- netbox/dcim/tests/test_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index a327d6400f..3908975466 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -389,6 +389,7 @@ def setUpTestData(cls): 'outer_width': 500, 'outer_depth': 500, 'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER, + 'starting_unit': 1, 'weight': 100, 'max_weight': 2000, 'weight_unit': WeightUnitChoices.UNIT_POUND, From eb115108dd365ab945bafbc22b6fe46df9d2e49f Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 15 Jun 2023 13:58:08 -0700 Subject: [PATCH 7/8] 12175 fix max height calc display --- netbox/dcim/models/racks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 17a35a5279..079b75dabc 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -240,8 +240,8 @@ def clean(self): position__isnull=True ).order_by('-position').first() if top_device: - min_height = top_device.position + top_device.device_type.u_height - 1 - if self.u_height + self.starting_unit - 1 < min_height: + min_height = top_device.position + top_device.device_type.u_height - self.starting_unit + if self.u_height < min_height: raise ValidationError({ 'u_height': "Rack must be at least {}U tall to house currently installed devices.".format( min_height From 40327cd82168f1b39c2c43d1d59bc0d86d859298 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 22 Jun 2023 08:55:06 -0400 Subject: [PATCH 8/8] Misc cleanup & fixes --- netbox/dcim/models/racks.py | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 079b75dabc..8e86a17024 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -233,33 +233,24 @@ def clean(self): raise ValidationError("Must specify a unit when setting a maximum weight") if self.pk: - # Validate that Rack is tall enough to house the installed Devices - top_device = Device.objects.filter( - rack=self - ).exclude( - position__isnull=True - ).order_by('-position').first() - if top_device: + mounted_devices = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('position') + + # Validate that Rack is tall enough to house the highest mounted Device + if top_device := mounted_devices.last(): min_height = top_device.position + top_device.device_type.u_height - self.starting_unit if self.u_height < min_height: raise ValidationError({ - 'u_height': "Rack must be at least {}U tall to house currently installed devices.".format( - min_height - ) + 'u_height': f"Rack must be at least {min_height}U tall to house currently installed devices." }) - # Validate the Rack starting_unit > position of any installed devices - first_device = Device.objects.filter( - rack=self - ).exclude( - position__isnull=True - ).order_by('position').first() - if first_device: - if self.starting_unit > first_device.position: + + # Validate that the Rack's starting unit is less than or equal to the position of the lowest mounted Device + if last_device := mounted_devices.first(): + if self.starting_unit > last_device.position: raise ValidationError({ - 'starting_unit': "Rack must have a staring unit at most {} to house currently installed devices.".format( - first_device.position - ) + 'starting_unit': f"Rack unit numbering must begin at {last_device.position} or less to house " + f"currently installed devices." }) + # Validate that Rack was assigned a Location of its same site, if applicable if self.location: if self.location.site != self.site: @@ -287,7 +278,7 @@ def units(self): Return a list of unit numbers, top to bottom. """ if self.desc_units: - return drange(decimal.Decimal(self.starting_unit), self.u_height + 1, 0.5) + return drange(decimal.Decimal(self.starting_unit), self.u_height + self.starting_unit, 0.5) return drange(self.u_height + decimal.Decimal(0.5) + self.starting_unit - 1, 0.5 + self.starting_unit - 1, -0.5) def get_status_color(self):