Răsfoiți Sursa

18417 Add outer_height to racks (#18940)

* 18417 add rack outer height

* 18417 add rack outer height

* 18417 fix tests

* 18417 fix validation message

* Update netbox/dcim/filtersets.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/filtersets.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/dcim/models/racks.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* 16224 review changes

* 16224 review changes

* 16224 update table display

* 18417 use TemplateColumn

* 18417 review changes

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson 10 luni în urmă
părinte
comite
7a71c7b8f8

+ 1 - 1
docs/models/dcim/racktype.md

@@ -40,7 +40,7 @@ The number of the numerically lowest unit in the rack. This value defaults to on
 
 ### Outer Dimensions
 
-The external width and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
+The external width, height and depth of the rack can be tracked to aid in floorplan calculations. These measurements must be designated in either millimeters or inches.
 
 ### Mounting Depth
 

+ 5 - 5
netbox/dcim/api/serializers_/racks.py

@@ -70,8 +70,8 @@ class RackTypeSerializer(RackBaseSerializer):
         model = RackType
         fields = [
             'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
-            'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
-            'max_weight', 'weight_unit', 'mounting_depth', 'description', 'comments', 'tags',
+            'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
+            'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'comments', 'tags',
             'custom_fields', 'created', 'last_updated',
         ]
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description')
@@ -129,9 +129,9 @@ class RackSerializer(RackBaseSerializer):
         fields = [
             'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status',
             'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'weight',
-            'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
-            'airflow', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
-            'powerfeed_count',
+            'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
+            'mounting_depth', 'airflow', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated', 'device_count', 'powerfeed_count',
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
 

+ 4 - 4
netbox/dcim/filtersets.py

@@ -313,8 +313,8 @@ class RackTypeFilterSet(NetBoxModelFilterSet):
     class Meta:
         model = RackType
         fields = (
-            'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
+            'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
         )
 
     def search(self, queryset, name, value):
@@ -426,8 +426,8 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
         model = Rack
         fields = (
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
-            'description',
+            'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
+            'weight_unit', 'description',
         )
 
     def search(self, queryset, name, value):

+ 16 - 8
netbox/dcim/forms/bulk_edit.py

@@ -260,6 +260,11 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         min_value=1
     )
+    outer_height = forms.IntegerField(
+        label=_('Outer height'),
+        required=False,
+        min_value=1
+    )
     outer_depth = forms.IntegerField(
         label=_('Outer depth'),
         required=False,
@@ -302,7 +307,7 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
     fieldsets = (
         FieldSet('manufacturer', 'description', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
         FieldSet(
-            InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+            InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             'mounting_depth',
             name=_('Dimensions')
@@ -310,7 +315,7 @@ class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
     )
     nullable_fields = (
-        'outer_width', 'outer_depth', 'outer_unit', 'weight',
+        'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
         'max_weight', 'weight_unit', 'description', 'comments',
     )
 
@@ -404,6 +409,11 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         min_value=1
     )
+    outer_height = forms.IntegerField(
+        label=_('Outer height'),
+        required=False,
+        min_value=1
+    )
     outer_depth = forms.IntegerField(
         label=_('Outer depth'),
         required=False,
@@ -451,15 +461,13 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
     fieldsets = (
         FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')),
         FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
-        FieldSet(
-            'form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'outer_width', 'outer_depth', 'outer_unit',
-            'mounting_depth', name=_('Hardware')
-        ),
+        FieldSet('outer_width', 'outer_height', 'outer_depth', 'outer_unit', name=_('Outer Dimensions')),
+        FieldSet('form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'mounting_depth', name=_('Hardware')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
     )
     nullable_fields = (
-        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
-        'max_weight', 'weight_unit', 'description', 'comments',
+        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_height', 'outer_depth',
+        'outer_unit', 'weight', 'max_weight', 'weight_unit', 'description', 'comments',
     )
 
 

+ 2 - 2
netbox/dcim/forms/bulk_import.py

@@ -222,7 +222,7 @@ class RackTypeImportForm(NetBoxModelImportForm):
         model = RackType
         fields = (
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
-            'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+            'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
             'weight_unit', 'description', 'comments', 'tags',
         )
 
@@ -307,7 +307,7 @@ class RackImportForm(NetBoxModelImportForm):
         model = Rack
         fields = (
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'rack_type', 'form_factor', 'serial',
-            'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
+            'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
             'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
         )
 

+ 7 - 6
netbox/dcim/forms/model_forms.py

@@ -226,7 +226,7 @@ class RackTypeForm(NetBoxModelForm):
         FieldSet('manufacturer', 'model', 'slug', 'description', 'form_factor', 'tags', name=_('Rack Type')),
         FieldSet(
             'width', 'u_height',
-            InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+            InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
             InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
             'mounting_depth', name=_('Dimensions')
         ),
@@ -237,8 +237,8 @@ class RackTypeForm(NetBoxModelForm):
         model = RackType
         fields = [
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
-            'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
-            'description', 'comments', 'tags',
+            'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+            'weight_unit', 'description', 'comments', 'tags',
         ]
 
 
@@ -283,8 +283,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
         fields = [
             'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
             'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit',
-            'description', 'comments', 'tags',
+            'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
+            'weight_unit', 'description', 'comments', 'tags',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -306,7 +306,8 @@ class RackForm(TenancyForm, NetBoxModelForm):
                 *self.fieldsets,
                 FieldSet(
                     'form_factor', 'width', 'starting_unit', 'u_height',
-                    InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
+                    InlineFields('outer_width', 'outer_height', 'outer_depth', 'outer_unit',
+                                 label=_('Outer Dimensions')),
                     InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
                     'mounting_depth', 'desc_units', name=_('Dimensions')
                 ),

+ 23 - 0
netbox/dcim/migrations/0203_add_rack_outer_height.py

@@ -0,0 +1,23 @@
+# Generated by Django 5.2b1 on 2025-03-18 15:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0202_location_comments_region_comments_sitegroup_comments'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='rack',
+            name='outer_height',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='racktype',
+            name='outer_height',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+    ]

+ 17 - 10
netbox/dcim/models/racks.py

@@ -73,6 +73,12 @@ class RackBase(WeightMixin, PrimaryModel):
         null=True,
         help_text=_('Outer dimension of rack (width)')
     )
+    outer_height = models.PositiveSmallIntegerField(
+        verbose_name=_('outer height'),
+        blank=True,
+        null=True,
+        help_text=_('Outer dimension of rack (height)')
+    )
     outer_depth = models.PositiveSmallIntegerField(
         verbose_name=_('outer depth'),
         blank=True,
@@ -140,7 +146,7 @@ class RackType(RackBase):
     )
 
     clone_fields = (
-        'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
+        'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
         'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
     )
     prerequisite_models = (
@@ -173,8 +179,8 @@ class RackType(RackBase):
         super().clean()
 
         # Validate outer dimensions and unit
-        if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
-            raise ValidationError(_("Must specify a unit when setting an outer width/depth"))
+        if any([self.outer_width, self.outer_depth, self.outer_height]) and not self.outer_unit:
+            raise ValidationError(_("Must specify a unit when setting an outer dimension"))
 
         # Validate max_weight and weight_unit
         if self.max_weight and not self.weight_unit:
@@ -188,7 +194,7 @@ class RackType(RackBase):
             self._abs_max_weight = None
 
         # Clear unit if outer width & depth are not set
-        if self.outer_width is None and self.outer_depth is None:
+        if not any([self.outer_width, self.outer_depth, self.outer_height]):
             self.outer_unit = None
 
         super().save(*args, **kwargs)
@@ -235,8 +241,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
     """
     # Fields which cannot be set locally if a RackType is assigned
     RACKTYPE_FIELDS = (
-        'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
-        'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
+        'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
+        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
     )
 
     form_factor = models.CharField(
@@ -329,7 +335,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
 
     clone_fields = (
         'site', 'location', 'tenant', 'status', 'role', 'form_factor', 'width', 'airflow', 'u_height', 'desc_units',
-        'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
+        'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
+        'weight_unit',
     )
     prerequisite_models = (
         'dcim.Site',
@@ -364,8 +371,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
             raise ValidationError(_("Assigned location must belong to parent site ({site}).").format(site=self.site))
 
         # Validate outer dimensions and unit
-        if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
-            raise ValidationError(_("Must specify a unit when setting an outer width/depth"))
+        if any([self.outer_width, self.outer_depth, self.outer_height]) and not self.outer_unit:
+            raise ValidationError(_("Must specify a unit when setting an outer dimension"))
 
         # Validate max_weight and weight_unit
         if self.max_weight and not self.weight_unit:
@@ -414,7 +421,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
             self._abs_max_weight = None
 
         # Clear unit if outer width & depth are not set
-        if self.outer_width is None and self.outer_depth is None:
+        if not any([self.outer_width, self.outer_depth, self.outer_height]):
             self.outer_unit = None
 
         super().save(*args, **kwargs)

+ 22 - 13
netbox/dcim/tables/racks.py

@@ -5,7 +5,7 @@ from django_tables2.utils import Accessor
 from dcim.models import Rack, RackReservation, RackRole, RackType
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
-from .template_code import WEIGHT
+from .template_code import OUTER_UNIT, WEIGHT
 
 __all__ = (
     'RackTable',
@@ -62,12 +62,16 @@ class RackTypeTable(NetBoxTable):
         template_code="{{ value }}U",
         verbose_name=_('Height')
     )
-    outer_width = tables.TemplateColumn(
-        template_code="{{ record.outer_width }} {{ record.outer_unit }}",
+    outer_width = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Width')
     )
-    outer_depth = tables.TemplateColumn(
-        template_code="{{ record.outer_depth }} {{ record.outer_unit }}",
+    outer_height = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
+        verbose_name=_('Outer Height')
+    )
+    outer_depth = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Depth')
     )
     weight = columns.TemplateColumn(
@@ -96,8 +100,8 @@ class RackTypeTable(NetBoxTable):
         model = RackType
         fields = (
             'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description', 'comments',
-            'instance_count', 'tags', 'created', 'last_updated',
+            'outer_height', 'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'description',
+            'comments', 'instance_count', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'instance_count',
@@ -159,12 +163,16 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
     tags = columns.TagColumn(
         url_name='dcim:rack_list'
     )
-    outer_width = tables.TemplateColumn(
-        template_code="{{ record.outer_width }} {{ record.outer_unit }}",
+    outer_width = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Width')
     )
-    outer_depth = tables.TemplateColumn(
-        template_code="{{ record.outer_depth }} {{ record.outer_unit }}",
+    outer_height = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
+        verbose_name=_('Outer Height')
+    )
+    outer_depth = columns.TemplateColumn(
+        template_code=OUTER_UNIT,
         verbose_name=_('Outer Depth')
     )
     weight = columns.TemplateColumn(
@@ -183,8 +191,9 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
         fields = (
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role',
             'rack_type', 'serial', 'asset_tag', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'comments', 'device_count',
-            'get_utilization', 'get_power_utilization', 'description', 'contacts', 'tags', 'created', 'last_updated',
+            'outer_height', 'outer_depth', 'mounting_depth', 'airflow', 'weight', 'max_weight', 'comments',
+            'device_count', 'get_utilization', 'get_power_utilization', 'description', 'contacts',
+            'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'rack_type', 'u_height',

+ 5 - 0
netbox/dcim/tables/template_code.py

@@ -109,6 +109,11 @@ LOCATION_BUTTONS = """
 </a>
 """
 
+OUTER_UNIT = """
+{% load helpers %}
+{% if value %}{{ value }} {{ record.outer_unit }}{% endif %}
+"""
+
 #
 # Device component templatebuttons
 #

+ 16 - 0
netbox/dcim/tests/test_filtersets.py

@@ -585,6 +585,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=1,
                 desc_units=False,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=100,
@@ -603,6 +604,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=2,
                 desc_units=False,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=200,
@@ -621,6 +623,7 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=3,
                 desc_units=True,
                 outer_width=300,
+                outer_height=300,
                 outer_depth=300,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 mounting_depth=300,
@@ -681,6 +684,10 @@ class RackTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'outer_width': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_outer_height(self):
+        params = {'outer_height': [100, 200]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_outer_depth(self):
         params = {'outer_depth': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@@ -764,6 +771,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=1,
                 desc_units=False,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=100,
@@ -782,6 +790,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 starting_unit=2,
                 desc_units=False,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 mounting_depth=200,
@@ -831,6 +840,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=42,
                 desc_units=False,
                 outer_width=100,
+                outer_height=100,
                 outer_depth=100,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 weight=10,
@@ -854,6 +864,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=43,
                 desc_units=False,
                 outer_width=200,
+                outer_height=200,
                 outer_depth=200,
                 outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
                 weight=20,
@@ -877,6 +888,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
                 u_height=44,
                 desc_units=True,
                 outer_width=300,
+                outer_height=300,
                 outer_depth=300,
                 outer_unit=RackDimensionUnitChoices.UNIT_INCH,
                 weight=30,
@@ -957,6 +969,10 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'outer_width': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_outer_height(self):
+        params = {'outer_height': [100, 200]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_outer_depth(self):
         params = {'outer_depth': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 10 - 0
netbox/templates/dcim/inc/panels/racktype_dimensions.html

@@ -24,6 +24,16 @@
         {% endif %}
       </td>
     </tr>
+    <tr>
+      <th scope="row">{% trans "Outer Height" %}</th>
+      <td>
+        {% if object.outer_height %}
+          {{ object.outer_height }} {{ object.get_outer_unit_display }}
+        {% else %}
+          {{ ''|placeholder }}
+        {% endif %}
+      </td>
+    </tr>
     <tr>
       <th scope="row">{% trans "Outer Depth" %}</th>
       <td>