فهرست منبع

Closes #10675: Add max_weight field to track maximum load capacity for racks

jeremystretch 3 سال پیش
والد
کامیت
0b100b8fc8

+ 5 - 1
docs/models/dcim/rack.md

@@ -73,6 +73,10 @@ The maximum depth of a mounted device that the rack can accommodate, in millimet
 
 
 The numeric weight of the rack, including a unit designation (e.g. 10 kilograms or 20 pounds).
 The numeric weight of the rack, including a unit designation (e.g. 10 kilograms or 20 pounds).
 
 
+### Maximum Weight
+
+The maximum total weight capacity for all installed devices, inclusive of the rack itself.
+
 ### Descending Units
 ### Descending Units
 
 
-If selected, the rack's elevation will display unit 1 at the top of the rack. (Most racks use asceneding numbering, with unit 1 assigned to the bottommost position.)
+If selected, the rack's elevation will display unit 1 at the top of the rack. (Most racks use ascending numbering, with unit 1 assigned to the bottommost position.)

+ 2 - 1
docs/release-notes/version-3.4.md

@@ -6,6 +6,7 @@
 
 
 * [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
 * [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
 * [#10371](https://github.com/netbox-community/netbox/issues/10371) - Add operational status field for modules
 * [#10371](https://github.com/netbox-community/netbox/issues/10371) - Add operational status field for modules
+* [#10675](https://github.com/netbox-community/netbox/issues/10675) - Add `max_weight` field to track maximum load capacity for racks
 * [#10945](https://github.com/netbox-community/netbox/issues/10945) - Enabled recurring execution of scheduled reports & scripts
 * [#10945](https://github.com/netbox-community/netbox/issues/10945) - Enabled recurring execution of scheduled reports & scripts
 * [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
 * [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
 * [#11022](https://github.com/netbox-community/netbox/issues/11022) - Introduce `QUEUE_MAPPINGS` configuration parameter to allow customization of background task prioritization
 * [#11022](https://github.com/netbox-community/netbox/issues/11022) - Introduce `QUEUE_MAPPINGS` configuration parameter to allow customization of background task prioritization
@@ -146,7 +147,7 @@ This release introduces a new programmatic API that enables plugins and custom s
     * Added `description` and `comments` fields
     * Added `description` and `comments` fields
 * dcim.Rack
 * dcim.Rack
     * Added a `description` field
     * Added a `description` field
-    * Added optional `weight` and `weight_unit` fields
+    * Added optional `weight`, `max_weight`, and `weight_unit` fields
 * dcim.RackReservation
 * dcim.RackReservation
     * Added a `comments` field
     * Added a `comments` field
 * dcim.VirtualChassis
 * dcim.VirtualChassis

+ 3 - 3
netbox/dcim/api/serializers.py

@@ -210,9 +210,9 @@ class RackSerializer(NetBoxModelSerializer):
         model = Rack
         model = Rack
         fields = [
         fields = [
             'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
             'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
-            'asset_tag', 'type', 'width', 'u_height', 'weight', 'weight_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags', 'custom_fields',
-            'created', 'last_updated', 'device_count', 'powerfeed_count',
+            'asset_tag', 'type', 'width', 'u_height', 'weight', 'max_weight', 'weight_unit', 'desc_units',
+            'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags',
+            'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
         ]
         ]
 
 
 
 

+ 1 - 1
netbox/dcim/filtersets.py

@@ -322,7 +322,7 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
         model = Rack
         model = Rack
         fields = [
         fields = [
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
             'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'weight', 'weight_unit'
+            'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit'
         ]
         ]
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):

+ 6 - 2
netbox/dcim/forms/bulk_edit.py

@@ -294,6 +294,10 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         min_value=0,
         min_value=0,
         required=False
         required=False
     )
     )
+    max_weight = forms.IntegerField(
+        min_value=0,
+        required=False
+    )
     weight_unit = forms.ChoiceField(
     weight_unit = forms.ChoiceField(
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),
         required=False,
         required=False,
@@ -316,11 +320,11 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         ('Hardware', (
         ('Hardware', (
             'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
             'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
         )),
         )),
-        ('Weight', ('weight', 'weight_unit')),
+        ('Weight', ('weight', 'max_weight', 'weight_unit')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
         'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
-        'weight_unit', 'description', 'comments',
+        'max_weight', 'weight_unit', 'description', 'comments',
     )
     )
 
 
 
 

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

@@ -195,13 +195,18 @@ class RackImportForm(NetBoxModelImportForm):
         required=False,
         required=False,
         help_text=_('Unit for outer dimensions')
         help_text=_('Unit for outer dimensions')
     )
     )
+    weight_unit = CSVChoiceField(
+        choices=WeightUnitChoices,
+        required=False,
+        help_text=_('Unit for rack weights')
+    )
 
 
     class Meta:
     class Meta:
         model = Rack
         model = Rack
         fields = (
         fields = (
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
-            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
-            'description', 'comments', 'tags',
+            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight',
+            'max_weight', 'weight_unit', 'description', 'comments', 'tags',
         )
         )
 
 
     def __init__(self, data=None, *args, **kwargs):
     def __init__(self, data=None, *args, **kwargs):

+ 7 - 2
netbox/dcim/forms/filtersets.py

@@ -229,7 +229,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
         ('Hardware', ('type', 'width', 'serial', 'asset_tag')),
         ('Hardware', ('type', 'width', 'serial', 'asset_tag')),
         ('Tenant', ('tenant_group_id', 'tenant_id')),
         ('Tenant', ('tenant_group_id', 'tenant_id')),
         ('Contacts', ('contact', 'contact_role', 'contact_group')),
         ('Contacts', ('contact', 'contact_role', 'contact_group')),
-        ('Weight', ('weight', 'weight_unit')),
+        ('Weight', ('weight', 'max_weight', 'weight_unit')),
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -284,7 +284,12 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
     weight = forms.DecimalField(
     weight = forms.DecimalField(
-        required=False
+        required=False,
+        min_value=1
+    )
+    max_weight = forms.IntegerField(
+        required=False,
+        min_value=1
     )
     )
     weight_unit = forms.ChoiceField(
     weight_unit = forms.ChoiceField(
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),

+ 1 - 1
netbox/dcim/forms/model_forms.py

@@ -279,7 +279,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
         fields = [
         fields = [
             'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
             'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
             'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
             'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'description', 'comments', 'tags',
+            'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
             'site': _("The site at which the rack exists"),
             'site': _("The site at which the rack exists"),

+ 23 - 9
netbox/dcim/migrations/0163_rack_devicetype_moduletype_weights.py

@@ -1,5 +1,3 @@
-# Generated by Django 4.0.7 on 2022-09-23 01:01
-
 from django.db import migrations, models
 from django.db import migrations, models
 
 
 
 
@@ -10,11 +8,8 @@ class Migration(migrations.Migration):
     ]
     ]
 
 
     operations = [
     operations = [
-        migrations.AddField(
-            model_name='devicetype',
-            name='_abs_weight',
-            field=models.PositiveBigIntegerField(blank=True, null=True),
-        ),
+
+        # Device types
         migrations.AddField(
         migrations.AddField(
             model_name='devicetype',
             model_name='devicetype',
             name='weight',
             name='weight',
@@ -26,10 +21,12 @@ class Migration(migrations.Migration):
             field=models.CharField(blank=True, max_length=50),
             field=models.CharField(blank=True, max_length=50),
         ),
         ),
         migrations.AddField(
         migrations.AddField(
-            model_name='moduletype',
+            model_name='devicetype',
             name='_abs_weight',
             name='_abs_weight',
             field=models.PositiveBigIntegerField(blank=True, null=True),
             field=models.PositiveBigIntegerField(blank=True, null=True),
         ),
         ),
+
+        # Module types
         migrations.AddField(
         migrations.AddField(
             model_name='moduletype',
             model_name='moduletype',
             name='weight',
             name='weight',
@@ -41,18 +38,35 @@ class Migration(migrations.Migration):
             field=models.CharField(blank=True, max_length=50),
             field=models.CharField(blank=True, max_length=50),
         ),
         ),
         migrations.AddField(
         migrations.AddField(
-            model_name='rack',
+            model_name='moduletype',
             name='_abs_weight',
             name='_abs_weight',
             field=models.PositiveBigIntegerField(blank=True, null=True),
             field=models.PositiveBigIntegerField(blank=True, null=True),
         ),
         ),
+
+        # Racks
         migrations.AddField(
         migrations.AddField(
             model_name='rack',
             model_name='rack',
             name='weight',
             name='weight',
             field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True),
             field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True),
         ),
         ),
+        migrations.AddField(
+            model_name='rack',
+            name='max_weight',
+            field=models.PositiveIntegerField(blank=True, null=True),
+        ),
         migrations.AddField(
         migrations.AddField(
             model_name='rack',
             model_name='rack',
             name='weight_unit',
             name='weight_unit',
             field=models.CharField(blank=True, max_length=50),
             field=models.CharField(blank=True, max_length=50),
         ),
         ),
+        migrations.AddField(
+            model_name='rack',
+            name='_abs_weight',
+            field=models.PositiveBigIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='_abs_max_weight',
+            field=models.PositiveBigIntegerField(blank=True, null=True),
+        ),
     ]
     ]

+ 1 - 3
netbox/dcim/models/mixins.py

@@ -39,7 +39,5 @@ class WeightMixin(models.Model):
         super().clean()
         super().clean()
 
 
         # Validate weight and weight_unit
         # Validate weight and weight_unit
-        if self.weight is not None and not self.weight_unit:
+        if self.weight and not self.weight_unit:
             raise ValidationError("Must specify a unit when setting a weight")
             raise ValidationError("Must specify a unit when setting a weight")
-        elif self.weight is None:
-            self.weight_unit = ''

+ 26 - 2
netbox/dcim/models/racks.py

@@ -17,7 +17,7 @@ from dcim.svg import RackElevationSVG
 from netbox.models import OrganizationalModel, PrimaryModel
 from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.choices import ColorChoices
 from utilities.choices import ColorChoices
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.fields import ColorField, NaturalOrderingField
-from utilities.utils import array_to_string, drange
+from utilities.utils import array_to_string, drange, to_grams
 from .device_components import PowerPort
 from .device_components import PowerPort
 from .devices import Device, Module
 from .devices import Device, Module
 from .mixins import WeightMixin
 from .mixins import WeightMixin
@@ -149,6 +149,16 @@ class Rack(PrimaryModel, WeightMixin):
         choices=RackDimensionUnitChoices,
         choices=RackDimensionUnitChoices,
         blank=True,
         blank=True,
     )
     )
+    max_weight = models.PositiveIntegerField(
+        blank=True,
+        null=True,
+        help_text=_('Maximum load capacity for the rack')
+    )
+    # Stores the normalized max weight (in grams) for database ordering
+    _abs_max_weight = models.PositiveBigIntegerField(
+        blank=True,
+        null=True
+    )
     mounting_depth = models.PositiveSmallIntegerField(
     mounting_depth = models.PositiveSmallIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
@@ -174,7 +184,7 @@ class Rack(PrimaryModel, WeightMixin):
 
 
     clone_fields = (
     clone_fields = (
         'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
         'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
-        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit',
+        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
     )
     )
     prerequisite_models = (
     prerequisite_models = (
         'dcim.Site',
         'dcim.Site',
@@ -215,6 +225,10 @@ class Rack(PrimaryModel, WeightMixin):
         elif self.outer_width is None and self.outer_depth is None:
         elif self.outer_width is None and self.outer_depth is None:
             self.outer_unit = ''
             self.outer_unit = ''
 
 
+        # Validate max_weight and weight_unit
+        if self.max_weight and not self.weight_unit:
+            raise ValidationError("Must specify a unit when setting a maximum weight")
+
         if self.pk:
         if self.pk:
             # Validate that Rack is tall enough to house the installed Devices
             # Validate that Rack is tall enough to house the installed Devices
             top_device = Device.objects.filter(
             top_device = Device.objects.filter(
@@ -237,6 +251,16 @@ class Rack(PrimaryModel, WeightMixin):
                         'location': f"Location must be from the same site, {self.site}."
                         'location': f"Location must be from the same site, {self.site}."
                     })
                     })
 
 
+    def save(self, *args, **kwargs):
+
+        # Store the given max weight (if any) in grams for use in database ordering
+        if self.max_weight and self.weight_unit:
+            self._abs_max_weight = to_grams(self.max_weight, self.weight_unit)
+        else:
+            self._abs_max_weight = None
+
+        super().save(*args, **kwargs)
+
     @property
     @property
     def units(self):
     def units(self):
         """
         """

+ 2 - 2
netbox/dcim/tables/devicetypes.py

@@ -3,7 +3,7 @@ import django_tables2 as tables
 from dcim import models
 from dcim import models
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import ContactsColumnMixin
 from tenancy.tables import ContactsColumnMixin
-from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, DEVICE_WEIGHT
+from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, WEIGHT
 
 
 __all__ = (
 __all__ = (
     'ConsolePortTemplateTable',
     'ConsolePortTemplateTable',
@@ -84,7 +84,7 @@ class DeviceTypeTable(NetBoxTable):
         template_code='{{ value|floatformat }}'
         template_code='{{ value|floatformat }}'
     )
     )
     weight = columns.TemplateColumn(
     weight = columns.TemplateColumn(
-        template_code=DEVICE_WEIGHT,
+        template_code=WEIGHT,
         order_by=('_abs_weight', 'weight_unit')
         order_by=('_abs_weight', 'weight_unit')
     )
     )
 
 

+ 2 - 2
netbox/dcim/tables/modules.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 
 
 from dcim.models import Module, ModuleType
 from dcim.models import Module, ModuleType
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
-from .template_code import DEVICE_WEIGHT
+from .template_code import WEIGHT
 
 
 __all__ = (
 __all__ = (
     'ModuleTable',
     'ModuleTable',
@@ -28,7 +28,7 @@ class ModuleTypeTable(NetBoxTable):
         url_name='dcim:moduletype_list'
         url_name='dcim:moduletype_list'
     )
     )
     weight = columns.TemplateColumn(
     weight = columns.TemplateColumn(
-        template_code=DEVICE_WEIGHT,
+        template_code=WEIGHT,
         order_by=('_abs_weight', 'weight_unit')
         order_by=('_abs_weight', 'weight_unit')
     )
     )
 
 

+ 8 - 4
netbox/dcim/tables/racks.py

@@ -4,7 +4,7 @@ from django_tables2.utils import Accessor
 from dcim.models import Rack, RackReservation, RackRole
 from dcim.models import Rack, RackReservation, RackRole
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
-from .template_code import DEVICE_WEIGHT
+from .template_code import WEIGHT
 
 
 __all__ = (
 __all__ = (
     'RackTable',
     'RackTable',
@@ -81,17 +81,21 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
         verbose_name='Outer Depth'
         verbose_name='Outer Depth'
     )
     )
     weight = columns.TemplateColumn(
     weight = columns.TemplateColumn(
-        template_code=DEVICE_WEIGHT,
+        template_code=WEIGHT,
         order_by=('_abs_weight', 'weight_unit')
         order_by=('_abs_weight', 'weight_unit')
     )
     )
+    max_weight = columns.TemplateColumn(
+        template_code=WEIGHT,
+        order_by=('_abs_max_weight', 'weight_unit')
+    )
 
 
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = Rack
         model = Rack
         fields = (
         fields = (
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial',
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial',
             'asset_tag', 'type', 'u_height', 'width', 'outer_width', 'outer_depth', 'mounting_depth', 'weight',
             'asset_tag', 'type', 'u_height', 'width', 'outer_width', 'outer_depth', 'mounting_depth', 'weight',
-            'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'description', 'contacts', 'tags',
-            'created', 'last_updated',
+            'max_weight', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'description',
+            'contacts', 'tags', 'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',

+ 2 - 2
netbox/dcim/tables/template_code.py

@@ -15,9 +15,9 @@ CABLE_LENGTH = """
 {% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
 {% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
 """
 """
 
 
-DEVICE_WEIGHT = """
+WEIGHT = """
 {% load helpers %}
 {% load helpers %}
-{% if record.weight %}{{ record.weight|simplify_decimal }} {{ record.weight_unit }}{% endif %}
+{% if value %}{{ value|simplify_decimal }} {{ record.weight_unit }}{% endif %}
 """
 """
 
 
 DEVICE_LINK = """
 DEVICE_LINK = """

+ 7 - 3
netbox/dcim/tests/test_filtersets.py

@@ -409,9 +409,9 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
         Tenant.objects.bulk_create(tenants)
         Tenant.objects.bulk_create(tenants)
 
 
         racks = (
         racks = (
-            Rack(name='Rack 1', facility_id='rack-1', site=sites[0], location=locations[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER, weight=10, weight_unit=WeightUnitChoices.UNIT_POUND),
-            Rack(name='Rack 2', facility_id='rack-2', site=sites[1], location=locations[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER, weight=20, weight_unit=WeightUnitChoices.UNIT_POUND),
-            Rack(name='Rack 3', facility_id='rack-3', site=sites[2], location=locations[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH, weight=30, weight_unit=WeightUnitChoices.UNIT_KILOGRAM),
+            Rack(name='Rack 1', facility_id='rack-1', site=sites[0], location=locations[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER, weight=10, max_weight=1000, weight_unit=WeightUnitChoices.UNIT_POUND),
+            Rack(name='Rack 2', facility_id='rack-2', site=sites[1], location=locations[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER, weight=20, max_weight=2000, weight_unit=WeightUnitChoices.UNIT_POUND),
+            Rack(name='Rack 3', facility_id='rack-3', site=sites[2], location=locations[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH, weight=30, max_weight=3000, weight_unit=WeightUnitChoices.UNIT_KILOGRAM),
         )
         )
         Rack.objects.bulk_create(racks)
         Rack.objects.bulk_create(racks)
 
 
@@ -521,6 +521,10 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'weight': [10, 20]}
         params = {'weight': [10, 20]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_max_weight(self):
+        params = {'max_weight': [1000, 2000]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_weight_unit(self):
     def test_weight_unit(self):
         params = {'weight_unit': WeightUnitChoices.UNIT_POUND}
         params = {'weight_unit': WeightUnitChoices.UNIT_POUND}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 10 - 4
netbox/dcim/tests/test_views.py

@@ -388,15 +388,18 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'outer_width': 500,
             'outer_width': 500,
             'outer_depth': 500,
             'outer_depth': 500,
             'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER,
             'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER,
+            'weight': 100,
+            'max_weight': 2000,
+            'weight_unit': WeightUnitChoices.UNIT_POUND,
             'comments': 'Some comments',
             'comments': 'Some comments',
             'tags': [t.pk for t in tags],
             'tags': [t.pk for t in tags],
         }
         }
 
 
         cls.csv_data = (
         cls.csv_data = (
-            "site,location,name,status,width,u_height",
-            "Site 1,,Rack 4,active,19,42",
-            "Site 1,Location 1,Rack 5,active,19,42",
-            "Site 2,Location 2,Rack 6,active,19,42",
+            "site,location,name,status,width,u_height,weight,max_weight,weight_unit",
+            "Site 1,,Rack 4,active,19,42,100,2000,kg",
+            "Site 1,Location 1,Rack 5,active,19,42,100,2000,kg",
+            "Site 2,Location 2,Rack 6,active,19,42,100,2000,kg",
         )
         )
 
 
         cls.csv_update_data = (
         cls.csv_update_data = (
@@ -420,6 +423,9 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'outer_width': 30,
             'outer_width': 30,
             'outer_depth': 30,
             'outer_depth': 30,
             'outer_unit': RackDimensionUnitChoices.UNIT_INCH,
             'outer_unit': RackDimensionUnitChoices.UNIT_INCH,
+            'weight': 200,
+            'max_weight': 4000,
+            'weight_unit': WeightUnitChoices.UNIT_POUND,
             'comments': 'New comments',
             'comments': 'New comments',
         }
         }
 
 

+ 10 - 0
netbox/templates/dcim/rack.html

@@ -169,6 +169,16 @@
                             {% endif %}
                             {% endif %}
                         </td>
                         </td>
                     </tr>
                     </tr>
+                    <tr>
+                        <th scope="row">Maximum Weight</th>
+                        <td>
+                            {% if object.max_weight %}
+                                {{ object.max_weight }} {{ object.get_weight_unit_display }}
+                            {% else %}
+                                {{ ''|placeholder }}
+                            {% endif %}
+                        </td>
+                    </tr>
                     <tr>
                     <tr>
                         <th scope="row">Total Weight</th>
                         <th scope="row">Total Weight</th>
                         <td>
                         <td>

+ 5 - 1
netbox/templates/dcim/rack_edit.html

@@ -58,10 +58,14 @@
         </div>
         </div>
         <div class="row mb-3">
         <div class="row mb-3">
             <label class="col col-md-3 col-form-label text-lg-end">Weight</label>
             <label class="col col-md-3 col-form-label text-lg-end">Weight</label>
-            <div class="col col-md-6 mb-1">
+            <div class="col col-md-3 mb-1">
                 {{ form.weight }}
                 {{ form.weight }}
                 <div class="form-text">Weight</div>
                 <div class="form-text">Weight</div>
             </div>
             </div>
+            <div class="col col-md-3 mb-1">
+                {{ form.max_weight }}
+                <div class="form-text">Maximum Weight</div>
+            </div>
             <div class="col col-md-3 mb-1">
             <div class="col col-md-3 mb-1">
                 {{ form.weight_unit }}
                 {{ form.weight_unit }}
                 <div class="form-text">Unit</div>
                 <div class="form-text">Unit</div>