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

This commit is contained in:
jeremystretch 2022-12-09 12:45:02 -05:00
parent 2b12138c41
commit 0b100b8fc8
19 changed files with 128 additions and 45 deletions

View File

@ -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.)

View File

@ -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

View File

@ -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', 'asset_tag', 'type', 'width', 'u_height', 'weight', 'max_weight', 'weight_unit', 'desc_units',
'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags', 'custom_fields', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags',
'created', 'last_updated', 'device_count', 'powerfeed_count', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
] ]

View File

@ -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):

View File

@ -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',
) )

View File

@ -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', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight',
'description', 'comments', 'tags', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
) )
def __init__(self, data=None, *args, **kwargs): def __init__(self, data=None, *args, **kwargs):

View File

@ -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),

View File

@ -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"),

View File

@ -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', # Device types
name='_abs_weight',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
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),
),
] ]

View File

@ -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 = ''

View File

@ -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):
""" """

View File

@ -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')
) )

View File

@ -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')
) )

View File

@ -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', 'max_weight', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'description',
'created', 'last_updated', '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',

View File

@ -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 = """

View File

@ -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 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, 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, weight_unit=WeightUnitChoices.UNIT_KILOGRAM), 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)

View File

@ -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,location,name,status,width,u_height,weight,max_weight,weight_unit",
"Site 1,,Rack 4,active,19,42", "Site 1,,Rack 4,active,19,42,100,2000,kg",
"Site 1,Location 1,Rack 5,active,19,42", "Site 1,Location 1,Rack 5,active,19,42,100,2000,kg",
"Site 2,Location 2,Rack 6,active,19,42", "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',
} }

View File

@ -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>

View File

@ -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>