From 81f0992acec72b90e3940546744c2afa894de4f9 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 22 Sep 2022 18:02:06 -0700 Subject: [PATCH 01/20] 9654 add weight fields to devices --- netbox/dcim/choices.py | 18 ++++++ ...__abs_weight_devicetype_weight_and_more.py | 58 +++++++++++++++++++ netbox/dcim/models/devices.py | 5 +- netbox/dcim/models/mixins.py | 37 ++++++++++++ netbox/dcim/models/racks.py | 8 ++- netbox/utilities/utils.py | 32 ++++++++++ 6 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py create mode 100644 netbox/dcim/models/mixins.py diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 7d35a40f9c6..38720a6144e 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1314,6 +1314,24 @@ class CableLengthUnitChoices(ChoiceSet): ) +class DeviceWeightUnitChoices(ChoiceSet): + + # Metric + UNIT_KILOGRAM = 'kg' + UNIT_GRAM = 'g' + + # Imperial + UNIT_POUND = 'lb' + UNIT_OUNCE = 'oz' + + CHOICES = ( + (UNIT_KILOGRAM, 'Kilograms'), + (UNIT_GRAM, 'Grams'), + (UNIT_POUND, 'Pounds'), + (UNIT_OUNCE, 'Ounce'), + ) + + # # CableTerminations # diff --git a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py new file mode 100644 index 00000000000..14e2c9475c7 --- /dev/null +++ b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py @@ -0,0 +1,58 @@ +# Generated by Django 4.0.7 on 2022-09-23 01:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0161_cabling_cleanup'), + ] + + operations = [ + migrations.AddField( + model_name='devicetype', + name='_abs_weight', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + ), + migrations.AddField( + model_name='devicetype', + name='weight', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + migrations.AddField( + model_name='devicetype', + name='weight_unit', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='moduletype', + name='_abs_weight', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + ), + migrations.AddField( + model_name='moduletype', + name='weight', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + migrations.AddField( + model_name='moduletype', + name='weight_unit', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='rack', + name='_abs_weight', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + ), + migrations.AddField( + model_name='rack', + name='weight', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + migrations.AddField( + model_name='rack', + name='weight_unit', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index ccf4613bff1..41da36db6fb 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -20,6 +20,7 @@ from utilities.choices import ColorChoices from utilities.fields import ColorField, NaturalOrderingField from .device_components import * +from .mixins import DeviceWeightMixin __all__ = ( @@ -70,7 +71,7 @@ def get_absolute_url(self): return reverse('dcim:manufacturer', args=[self.pk]) -class DeviceType(NetBoxModel): +class DeviceType(NetBoxModel, DeviceWeightMixin): """ A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as well as high-level functional role(s). @@ -308,7 +309,7 @@ def is_child_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD -class ModuleType(NetBoxModel): +class ModuleType(NetBoxModel, DeviceWeightMixin): """ A ModuleType represents a hardware element that can be installed within a device and which houses additional components; for example, a line card within a chassis-based switch such as the Cisco Catalyst 6500. Like a diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py new file mode 100644 index 00000000000..99aa7a73825 --- /dev/null +++ b/netbox/dcim/models/mixins.py @@ -0,0 +1,37 @@ +from django.db import models +from dcim.choices import * +from utilities.utils import to_kilograms + + +class DeviceWeightMixin(models.Model): + weight = models.DecimalField( + max_digits=8, + decimal_places=2, + blank=True, + null=True + ) + weight_unit = models.CharField( + max_length=50, + choices=DeviceWeightUnitChoices, + blank=True, + ) + # Stores the normalized length (in meters) for database ordering + _abs_weight = models.DecimalField( + max_digits=10, + decimal_places=4, + blank=True, + null=True + ) + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + + # Store the given weight (if any) in meters for use in database ordering + if self.weight and self.weight_unit: + self._abs_weight = to_kilograms(self.length, self.length_unit) + else: + self._abs_weight = None + + super().save(*args, **kwargs) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 20027675ab1..74f63a7305e 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -20,6 +20,7 @@ from utilities.utils import array_to_string, drange from .device_components import PowerOutlet, PowerPort from .devices import Device +from .mixins import DeviceWeightMixin from .power import PowerFeed __all__ = ( @@ -63,7 +64,7 @@ def get_absolute_url(self): return reverse('dcim:rackrole', args=[self.pk]) -class Rack(NetBoxModel): +class Rack(NetBoxModel, DeviceWeightMixin): """ Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Each Rack is assigned to a Site and (optionally) a Location. @@ -449,6 +450,11 @@ def get_power_utilization(self): return int(allocated_draw / available_power_total * 100) + def get_total_weight(self): + total_weight = sum(device._abs_weight for device in self.devices.exclude(_abs_weight__isnull=True)) + total_weight += self._abs_weight + return total_weight + class RackReservation(NetBoxModel): """ diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 69ab615fcbb..d7c3c78d08c 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -270,6 +270,38 @@ def to_meters(length, unit): raise ValueError(f"Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.") +def to_kilograms(weight, unit): + """ + Convert the given length to kilograms. + """ + try: + if weight < 0: + raise ValueError("Weight must be a positive number") + except TypeError: + raise TypeError(f"Invalid value '{weight}' for weight (must be a number)") + + valid_units = DeviceWeightUnitChoices.values() + if unit not in valid_units: + raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") + + UNIT_KILOGRAM = 'kg' + UNIT_GRAM = 'g' + + # Imperial + UNIT_POUND = 'lb' + UNIT_OUNCE = 'oz' + + if unit == DeviceWeightUnitChoices.UNIT_KILOGRAM: + return weight + if unit == DeviceWeightUnitChoices.UNIT_GRAM: + return weight * 1000 + if unit == DeviceWeightUnitChoices.UNIT_POUND: + return weight * Decimal(0.453592) + if unit == DeviceWeightUnitChoices.UNIT_OUNCE: + return weight * Decimal(0.0283495) + raise ValueError(f"Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.") + + def render_jinja2(template_code, context): """ Render a Jinja2 template with the provided context. Return the rendered content. From f55cd6388ada7ec07004c4ded0fba3b6e478ac01 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 22 Sep 2022 19:54:23 -0700 Subject: [PATCH 02/20] 9654 add weight fields to devices --- netbox/dcim/choices.py | 2 +- netbox/dcim/forms/bulk_edit.py | 13 ++++++++++++- netbox/dcim/forms/models.py | 14 ++++++++++---- netbox/dcim/models/racks.py | 5 +++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 38720a6144e..d0aab406834 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1328,7 +1328,7 @@ class DeviceWeightUnitChoices(ChoiceSet): (UNIT_KILOGRAM, 'Kilograms'), (UNIT_GRAM, 'Grams'), (UNIT_POUND, 'Pounds'), - (UNIT_OUNCE, 'Ounce'), + (UNIT_OUNCE, 'Ounces'), ) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 396f7e59bd6..534e7665222 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -355,12 +355,23 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): required=False, widget=StaticSelect() ) + weight = forms.DecimalField( + min_value=0, + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False, + initial='', + widget=StaticSelect() + ) model = DeviceType fieldsets = ( (None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')), + ('Attributes', ('weight', 'weight_unit')), ) - nullable_fields = ('part_number', 'airflow') + nullable_fields = ('part_number', 'airflow', 'weight') class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 5728e7f2d18..edcbc29c300 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -363,6 +363,7 @@ class DeviceTypeForm(NetBoxModelForm): ('Chassis', ( 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', )), + ('Attributes', ('weight', 'weight_unit')), ('Images', ('front_image', 'rear_image')), ) @@ -370,7 +371,7 @@ class Meta: model = DeviceType fields = [ 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', - 'front_image', 'rear_image', 'comments', 'tags', + 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags', ] widgets = { 'subdevice_role': StaticSelect(), @@ -379,7 +380,8 @@ class Meta: }), 'rear_image': ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS - }) + }), + 'weight_unit': StaticSelect(), } @@ -391,16 +393,20 @@ class ModuleTypeForm(NetBoxModelForm): fieldsets = ( ('Module Type', ( - 'manufacturer', 'model', 'part_number', 'tags', + 'manufacturer', 'model', 'part_number', 'tags', 'weight', 'weight_unit' )), ) class Meta: model = ModuleType fields = [ - 'manufacturer', 'model', 'part_number', 'comments', 'tags', + 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'weight', 'weight_unit' ] + widgets = { + 'weight_unit': StaticSelect(), + } + class DeviceRoleForm(NetBoxModelForm): slug = SlugField() diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 74f63a7305e..0a34681e0aa 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -19,7 +19,7 @@ from utilities.fields import ColorField, NaturalOrderingField from utilities.utils import array_to_string, drange from .device_components import PowerOutlet, PowerPort -from .devices import Device +from .devices import Device, Module from .mixins import DeviceWeightMixin from .power import PowerFeed @@ -451,7 +451,8 @@ def get_power_utilization(self): return int(allocated_draw / available_power_total * 100) def get_total_weight(self): - total_weight = sum(device._abs_weight for device in self.devices.exclude(_abs_weight__isnull=True)) + total_weight = sum(device.device_type._abs_weight for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type')) + total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) total_weight += self._abs_weight return total_weight From 36ed75d51f38472c20444389c03f26edaf3d3e8c Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 22 Sep 2022 20:11:28 -0700 Subject: [PATCH 03/20] 9654 add weight fields to devices --- netbox/dcim/forms/bulk_edit.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 534e7665222..c6f6485fff6 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -382,12 +382,23 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): part_number = forms.CharField( required=False ) + weight = forms.DecimalField( + min_value=0, + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False, + initial='', + widget=StaticSelect() + ) model = ModuleType fieldsets = ( (None, ('manufacturer', 'part_number')), + ('Attributes', ('weight', 'weight_unit')), ) - nullable_fields = ('part_number',) + nullable_fields = ('part_number', 'weight',) class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm): From 15ad2ab8d22d94c03fab2a0a4de7b42e97050f7e Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 22 Sep 2022 20:26:09 -0700 Subject: [PATCH 04/20] 9654 add weight fields to devices --- netbox/dcim/models/mixins.py | 2 +- netbox/dcim/tables/devicetypes.py | 8 ++++++-- netbox/dcim/tables/modules.py | 7 ++++++- netbox/dcim/tables/template_code.py | 5 +++++ netbox/utilities/utils.py | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index 99aa7a73825..9069f716dae 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -30,7 +30,7 @@ def save(self, *args, **kwargs): # Store the given weight (if any) in meters for use in database ordering if self.weight and self.weight_unit: - self._abs_weight = to_kilograms(self.length, self.length_unit) + self._abs_weight = to_kilograms(self.weight, self.weight_unit) else: self._abs_weight = None diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 3ed4d8c080a..79e79574886 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -5,7 +5,7 @@ InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate, ) from netbox.tables import NetBoxTable, columns -from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS +from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, DEVICE_WEIGHT __all__ = ( 'ConsolePortTemplateTable', @@ -85,12 +85,16 @@ class DeviceTypeTable(NetBoxTable): tags = columns.TagColumn( url_name='dcim:devicetype_list' ) + weight = columns.TemplateColumn( + template_code=DEVICE_WEIGHT, + order_by=('_abs_weight', 'weight_unit') + ) class Meta(NetBoxTable.Meta): model = DeviceType fields = ( 'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', - 'airflow', 'comments', 'instance_count', 'tags', 'created', 'last_updated', + 'airflow', 'comments', 'instance_count', 'tags', 'created', 'last_updated', 'weight' ) default_columns = ( 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count', diff --git a/netbox/dcim/tables/modules.py b/netbox/dcim/tables/modules.py index e40d7bd80a0..43ca53063f5 100644 --- a/netbox/dcim/tables/modules.py +++ b/netbox/dcim/tables/modules.py @@ -2,6 +2,7 @@ from dcim.models import Module, ModuleType from netbox.tables import NetBoxTable, columns +from .template_code import DEVICE_WEIGHT __all__ = ( 'ModuleTable', @@ -26,11 +27,15 @@ class ModuleTypeTable(NetBoxTable): tags = columns.TagColumn( url_name='dcim:moduletype_list' ) + weight = columns.TemplateColumn( + template_code=DEVICE_WEIGHT, + order_by=('_abs_weight', 'weight_unit') + ) class Meta(NetBoxTable.Meta): model = ModuleType fields = ( - 'pk', 'id', 'model', 'manufacturer', 'part_number', 'comments', 'tags', + 'pk', 'id', 'model', 'manufacturer', 'part_number', 'comments', 'tags', 'weight' ) default_columns = ( 'pk', 'model', 'manufacturer', 'part_number', diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index dfc77b85418..9b8fb8fd629 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -15,6 +15,11 @@ {% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %} """ +DEVICE_WEIGHT = """ +{% load helpers %} +{% if record.weight %}{{ record.weight|simplify_decimal }} {{ record.weight_unit }}{% endif %} +""" + DEVICE_LINK = """ {{ record.name|default:'Unnamed device' }} diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index d7c3c78d08c..1d2de47688e 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -12,7 +12,7 @@ from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel -from dcim.choices import CableLengthUnitChoices +from dcim.choices import CableLengthUnitChoices, DeviceWeightUnitChoices from extras.plugins import PluginConfig from extras.utils import is_taggable from netbox.config import get_config From e21f4854b87b25aa24c374afc496b465e74f34e0 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:03:14 -0700 Subject: [PATCH 05/20] 9654 add weight fields to devices --- netbox/dcim/forms/bulk_edit.py | 13 ++++++++++++- netbox/dcim/forms/models.py | 3 ++- netbox/templates/dcim/rack_edit.html | 8 ++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index c6f6485fff6..e3d903de702 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -285,15 +285,26 @@ class RackBulkEditForm(NetBoxModelBulkEditForm): widget=SmallTextarea, label='Comments' ) + weight = forms.DecimalField( + min_value=0, + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False, + initial='', + widget=StaticSelect() + ) model = Rack fieldsets = ( ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')), ('Location', ('region', 'site_group', 'site', 'location')), ('Hardware', ('type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit')), + ('Attributes', ('weight', 'weight_unit')), ) nullable_fields = ( - 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', + 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'weight' ) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index edcbc29c300..2561bbfb014 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -260,7 +260,7 @@ class Meta: fields = [ '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', - 'outer_unit', 'comments', 'tags', + 'outer_unit', 'comments', 'tags', 'weight', 'weight_unit' ] help_texts = { 'site': "The site at which the rack exists", @@ -273,6 +273,7 @@ class Meta: 'type': StaticSelect(), 'width': StaticSelect(), 'outer_unit': StaticSelect(), + 'weight_unit': StaticSelect(), } diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index ca97be34d46..4a340c147fa 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -57,6 +57,14 @@
Dimensions
{% render_field form.desc_units %} +
+
+
Weight
+
+ {% render_field form.weight %} + {% render_field form.weight_unit %} +
+ {% if form.custom_fields %}
From d0bed830d78ed17a366ef098391ecfc8356c32d7 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:12:16 -0700 Subject: [PATCH 06/20] 9654 add weight fields to devices --- netbox/dcim/models/mixins.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index 9069f716dae..296bacb41c0 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ValidationError from django.db import models from dcim.choices import * from utilities.utils import to_kilograms @@ -35,3 +36,12 @@ def save(self, *args, **kwargs): self._abs_weight = None super().save(*args, **kwargs) + + def clean(self): + super().clean() + + # Validate weight and weight_unit + if self.weight is not None and not self.weight_unit: + raise ValidationError("Must specify a unit when setting a weight") + elif self.weight is None: + self.weight_unit = '' From 4f75d1e605fe07f3d6e8d7db82aba4a2f8d4c204 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:14:50 -0700 Subject: [PATCH 07/20] 9654 add weight fields to devices --- netbox/dcim/forms/bulk_edit.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index e3d903de702..28629fb652d 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -307,6 +307,17 @@ class RackBulkEditForm(NetBoxModelBulkEditForm): 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'weight' ) + def clean(self): + super().clean() + + # Validate weight/unit + weight = self.cleaned_data.get('weight') + weight_unit = self.cleaned_data.get('weight_unit') + if weight and not weight_unit: + raise forms.ValidationError({ + 'weight_unit': "Must specify a unit when setting weight" + }) + class RackReservationBulkEditForm(NetBoxModelBulkEditForm): user = forms.ModelChoiceField( @@ -384,6 +395,17 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): ) nullable_fields = ('part_number', 'airflow', 'weight') + def clean(self): + super().clean() + + # Validate weight/unit + weight = self.cleaned_data.get('weight') + weight_unit = self.cleaned_data.get('weight_unit') + if weight and not weight_unit: + raise forms.ValidationError({ + 'weight_unit': "Must specify a unit when setting weight" + }) + class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): manufacturer = DynamicModelChoiceField( @@ -411,6 +433,17 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): ) nullable_fields = ('part_number', 'weight',) + def clean(self): + super().clean() + + # Validate weight/unit + weight = self.cleaned_data.get('weight') + weight_unit = self.cleaned_data.get('weight_unit') + if weight and not weight_unit: + raise forms.ValidationError({ + 'weight_unit': "Must specify a unit when setting weight" + }) + class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm): color = ColorField( From 9675ea616ea00df1cb3063bcabf8c3448b1cb1af Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:17:47 -0700 Subject: [PATCH 08/20] 9654 add weight fields to devices --- netbox/dcim/tables/racks.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index 39553bac066..2cb0fa17a54 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -4,6 +4,7 @@ from dcim.models import Rack, RackReservation, RackRole from netbox.tables import NetBoxTable, columns from tenancy.tables import TenancyColumnsMixin +from .template_code import DEVICE_WEIGHT __all__ = ( 'RackTable', @@ -82,13 +83,17 @@ class RackTable(TenancyColumnsMixin, NetBoxTable): template_code="{{ record.outer_depth }} {{ record.outer_unit }}", verbose_name='Outer Depth' ) + weight = columns.TemplateColumn( + template_code=DEVICE_WEIGHT, + order_by=('_abs_weight', 'weight_unit') + ) class Meta(NetBoxTable.Meta): model = Rack fields = ( 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial', 'asset_tag', 'type', 'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization', - 'get_power_utilization', 'contacts', 'tags', 'created', 'last_updated', + 'get_power_utilization', 'contacts', 'tags', 'created', 'last_updated', 'weight' ) default_columns = ( 'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count', From bb16c989ac62d0bc98e1593f5ebbce0b9c1ed6b3 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:20:47 -0700 Subject: [PATCH 09/20] 9654 add weight fields to devices --- docs/models/dcim/devicetype.md | 4 ++++ docs/models/dcim/moduletype.md | 4 ++++ docs/models/dcim/rack.md | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/docs/models/dcim/devicetype.md b/docs/models/dcim/devicetype.md index 050f9324477..6dc4aa13e9a 100644 --- a/docs/models/dcim/devicetype.md +++ b/docs/models/dcim/devicetype.md @@ -41,6 +41,10 @@ Indicates whether this is a parent type (capable of housing child devices), a ch The default direction in which airflow circulates within the device chassis. This may be configured differently for instantiated devices (e.g. because of different fan modules). +### Weight + +The numeric weight of the device, including a unit designation (e.g. 10 kilograms or 20 pounds). + ### Front & Rear Images Users can upload illustrations of the device's front and rear panels. If present, these will be used to render the device in [rack](./rack.md) elevation diagrams. diff --git a/docs/models/dcim/moduletype.md b/docs/models/dcim/moduletype.md index b8ec0ac6ef7..3122d2e00cd 100644 --- a/docs/models/dcim/moduletype.md +++ b/docs/models/dcim/moduletype.md @@ -35,3 +35,7 @@ The model number assigned to this module type by its manufacturer. Must be uniqu ### Part Number An alternative part number to uniquely identify the module type. + +### Weight + +The numeric weight of the module, including a unit designation (e.g. 3 kilograms or 1 pound). diff --git a/docs/models/dcim/rack.md b/docs/models/dcim/rack.md index 57e7bec982c..e88c36fadf8 100644 --- a/docs/models/dcim/rack.md +++ b/docs/models/dcim/rack.md @@ -65,6 +65,10 @@ The height of the rack, measured in units. 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. +### Weight + +The numeric weight of the rack, including a unit designation (e.g. 10 kilograms or 20 pounds). + ### 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.) From 6b55d887910424e28750bafaa07393587f88a406 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 08:27:19 -0700 Subject: [PATCH 10/20] 9654 add weight fields to devices --- netbox/dcim/api/serializers.py | 8 ++++++-- netbox/dcim/forms/filtersets.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 897ee4ca356..cf92eed2918 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -203,6 +203,7 @@ class RackSerializer(NetBoxModelSerializer): outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False) device_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True) + weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) class Meta: model = Rack @@ -210,6 +211,7 @@ class Meta: 'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count', + 'weight', 'weight_unit', ] @@ -316,13 +318,14 @@ class DeviceTypeSerializer(NetBoxModelSerializer): subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False) device_count = serializers.IntegerField(read_only=True) + weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) class Meta: model = DeviceType fields = [ 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', - 'last_updated', 'device_count', + 'last_updated', 'device_count', 'weight', 'weight_unit' ] @@ -330,12 +333,13 @@ class ModuleTypeSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail') manufacturer = NestedManufacturerSerializer() # module_count = serializers.IntegerField(read_only=True) + weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) class Meta: model = ModuleType fields = [ 'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', + 'created', 'last_updated', 'weight', 'weight_unit' ] diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 96b0d1319a3..15b62b71b1c 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -228,6 +228,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte ('Hardware', ('type', 'width', 'serial', 'asset_tag')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), + ('Attributes', ('weight', 'weight_unit')), ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), @@ -281,6 +282,13 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte required=False ) tag = TagFilterField(model) + weight = forms.IntegerField( + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False + ) class RackElevationFilterForm(RackFilterForm): @@ -370,6 +378,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items', )), + ('Attributes', ('weight', 'weight_unit')), ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -465,6 +474,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): ) ) tag = TagFilterField(model) + weight = forms.IntegerField( + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False + ) class ModuleTypeFilterForm(NetBoxModelFilterSetForm): @@ -476,6 +492,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm): 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports', )), + ('Attributes', ('weight', 'weight_unit')), ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -529,6 +546,13 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm): ) ) tag = TagFilterField(model) + weight = forms.IntegerField( + required=False + ) + weight_unit = forms.ChoiceField( + choices=add_blank_choice(DeviceWeightUnitChoices), + required=False + ) class DeviceRoleFilterForm(NetBoxModelFilterSetForm): From a869720ccadc25bba328b2c7c5925652ed6c07a1 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 10:20:52 -0700 Subject: [PATCH 11/20] 9654 add weight fields to devices --- netbox/dcim/filtersets.py | 6 ++-- netbox/dcim/graphql/types.py | 9 ++++++ netbox/dcim/tests/test_filtersets.py | 42 ++++++++++++++++++++++------ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 0a4439173fa..94f0cf7bab6 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -320,7 +320,7 @@ class Meta: model = Rack fields = [ 'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'desc_units', 'outer_width', 'outer_depth', - 'outer_unit', + 'outer_unit', 'weight', 'weight_unit' ] def search(self, queryset, name, value): @@ -482,7 +482,7 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet): class Meta: model = DeviceType fields = [ - 'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', + 'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit' ] def search(self, queryset, name, value): @@ -576,7 +576,7 @@ class ModuleTypeFilterSet(NetBoxModelFilterSet): class Meta: model = ModuleType - fields = ['id', 'model', 'part_number'] + fields = ['id', 'model', 'part_number', 'weight', 'weight_unit'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index 52a98278a30..78cabbcd19e 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -211,6 +211,9 @@ def resolve_subdevice_role(self, info): def resolve_airflow(self, info): return self.airflow or None + def resolve_weight_unit(self, info): + return self.weight_unit or None + class FrontPortType(ComponentObjectType, CabledObjectMixin): @@ -328,6 +331,9 @@ class Meta: fields = '__all__' filterset_class = filtersets.ModuleTypeFilterSet + def resolve_weight_unit(self, info): + return self.weight_unit or None + class PlatformType(OrganizationalObjectType): @@ -416,6 +422,9 @@ def resolve_type(self, info): def resolve_outer_unit(self, info): return self.outer_unit or None + def resolve_weight_unit(self, info): + return self.weight_unit or None + class RackReservationType(NetBoxObjectType): diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index feef4e90c7e..024a870ffbf 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -409,9 +409,9 @@ def setUpTestData(cls): Tenant.objects.bulk_create(tenants) 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), - 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), - 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), + 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=DeviceWeightUnitChoices.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=DeviceWeightUnitChoices.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=DeviceWeightUnitChoices.UNIT_KILOGRAM), ) Rack.objects.bulk_create(racks) @@ -517,6 +517,14 @@ def test_tenant_group(self): params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_weight(self): + params = {'weight': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_weight_unit(self): + params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RackReservation.objects.all() @@ -688,9 +696,9 @@ def setUpTestData(cls): Manufacturer.objects.bulk_create(manufacturers) device_types = ( - DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png'), - DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR), - DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT), + DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png', weight=10, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), + DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR, weight=20, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), + DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT, weight=30, weight_unit=DeviceWeightUnitChoices.UNIT_KILOGRAM), ) DeviceType.objects.bulk_create(device_types) @@ -839,6 +847,14 @@ def test_inventory_items(self): params = {'inventory_items': 'false'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_weight(self): + params = {'weight': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_weight_unit(self): + params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ModuleType.objects.all() @@ -855,9 +871,9 @@ def setUpTestData(cls): Manufacturer.objects.bulk_create(manufacturers) module_types = ( - ModuleType(manufacturer=manufacturers[0], model='Model 1', part_number='Part Number 1'), - ModuleType(manufacturer=manufacturers[1], model='Model 2', part_number='Part Number 2'), - ModuleType(manufacturer=manufacturers[2], model='Model 3', part_number='Part Number 3'), + ModuleType(manufacturer=manufacturers[0], model='Model 1', part_number='Part Number 1', weight=10, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), + ModuleType(manufacturer=manufacturers[1], model='Model 2', part_number='Part Number 2', weight=20, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), + ModuleType(manufacturer=manufacturers[2], model='Model 3', part_number='Part Number 3', weight=30, weight_unit=DeviceWeightUnitChoices.UNIT_KILOGRAM), ) ModuleType.objects.bulk_create(module_types) @@ -943,6 +959,14 @@ def test_pass_through_ports(self): params = {'pass_through_ports': 'false'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + def test_weight(self): + params = {'weight': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_weight_unit(self): + params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConsolePortTemplate.objects.all() From 844a2085b4a384ba968c3d650cd6aa192c63e428 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 10:35:34 -0700 Subject: [PATCH 12/20] 9654 add weight fields to devices --- netbox/dcim/models/racks.py | 2 +- netbox/templates/dcim/rack.html | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 0a34681e0aa..f69d4f21d4b 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -452,7 +452,7 @@ def get_power_utilization(self): def get_total_weight(self): total_weight = sum(device.device_type._abs_weight for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type')) - total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) + total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device__rack=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) total_weight += self._abs_weight return total_weight diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 51e873ffaf7..d19ca80ae89 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -186,6 +186,27 @@
{% endif %} + + {% if object.get_total_weight %} +
+
+ Weight +
+
+ + + + + + + + + +
Rack Weight{{ object.weight }} kg
Total Weight{{ object.get_total_weight }} kg
+
+
+ {% endif %} + {% include 'inc/panels/image_attachments.html' %}
From 61903dbac44a6a9d5d4df7245f6b7c94aca1fc83 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 10:46:04 -0700 Subject: [PATCH 13/20] 9654 add weight fields to devices --- ...2_devicetype__abs_weight_devicetype_weight_and_more.py | 6 +++--- netbox/dcim/models/mixins.py | 4 ++-- netbox/dcim/models/racks.py | 5 +++-- netbox/templates/dcim/rack.html | 8 +++++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py index 14e2c9475c7..1e46b579087 100644 --- a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py +++ b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='devicetype', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), ), migrations.AddField( model_name='devicetype', @@ -28,7 +28,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='moduletype', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), ), migrations.AddField( model_name='moduletype', @@ -43,7 +43,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='rack', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), ), migrations.AddField( model_name='rack', diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index 296bacb41c0..4655c1ec33b 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -16,10 +16,10 @@ class DeviceWeightMixin(models.Model): choices=DeviceWeightUnitChoices, blank=True, ) - # Stores the normalized length (in meters) for database ordering + # Stores the normalized weight (in kilograms) for database ordering _abs_weight = models.DecimalField( max_digits=10, - decimal_places=4, + decimal_places=2, blank=True, null=True ) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index f69d4f21d4b..bc3a52772f6 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -453,8 +453,9 @@ def get_power_utilization(self): def get_total_weight(self): total_weight = sum(device.device_type._abs_weight for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type')) total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device__rack=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) - total_weight += self._abs_weight - return total_weight + if self._abs_weight: + total_weight += self._abs_weight + return round(total_weight, 2) class RackReservation(NetBoxModel): diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index d19ca80ae89..acd72a5d153 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -196,7 +196,13 @@
- + From 79ec8925b272c6883592effd98b39db7ff224df6 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 23 Sep 2022 10:52:58 -0700 Subject: [PATCH 14/20] 9654 add weight fields to devices --- netbox/templates/dcim/devicetype.html | 10 ++++++++++ netbox/templates/dcim/moduletype.html | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index bb3ec9d2e51..6a37a8d065a 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -35,6 +35,16 @@
+ + + + + + + + From b049678457af419f64517b088b2b4c0da11356ef Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 27 Sep 2022 11:09:44 -0700 Subject: [PATCH 15/20] 9654 changes from code review --- netbox/dcim/api/serializers.py | 6 +++--- netbox/dcim/choices.py | 2 +- netbox/dcim/forms/bulk_edit.py | 10 +++++----- netbox/dcim/forms/filtersets.py | 16 ++++++++-------- netbox/dcim/models/devices.py | 6 +++--- netbox/dcim/models/mixins.py | 6 +++--- netbox/dcim/models/racks.py | 4 ++-- netbox/dcim/tests/test_filtersets.py | 24 ++++++++++++------------ netbox/utilities/utils.py | 21 +++++++-------------- 9 files changed, 44 insertions(+), 51 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index cf92eed2918..2b2d85e3bf5 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -203,7 +203,7 @@ class RackSerializer(NetBoxModelSerializer): outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False) device_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True) - weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) + weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) class Meta: model = Rack @@ -318,7 +318,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer): subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False) device_count = serializers.IntegerField(read_only=True) - weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) + weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) class Meta: model = DeviceType @@ -333,7 +333,7 @@ class ModuleTypeSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail') manufacturer = NestedManufacturerSerializer() # module_count = serializers.IntegerField(read_only=True) - weight_unit = ChoiceField(choices=DeviceWeightUnitChoices, allow_blank=True, required=False) + weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) class Meta: model = ModuleType diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d0aab406834..8466d4861e9 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1314,7 +1314,7 @@ class CableLengthUnitChoices(ChoiceSet): ) -class DeviceWeightUnitChoices(ChoiceSet): +class WeightUnitChoices(ChoiceSet): # Metric UNIT_KILOGRAM = 'kg' diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 28629fb652d..72407b6dd30 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -290,7 +290,7 @@ class RackBulkEditForm(NetBoxModelBulkEditForm): required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False, initial='', widget=StaticSelect() @@ -301,7 +301,7 @@ class RackBulkEditForm(NetBoxModelBulkEditForm): ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')), ('Location', ('region', 'site_group', 'site', 'location')), ('Hardware', ('type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit')), - ('Attributes', ('weight', 'weight_unit')), + ('Weight', ('weight', 'weight_unit')), ) nullable_fields = ( 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'weight' @@ -382,7 +382,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False, initial='', widget=StaticSelect() @@ -391,7 +391,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): model = DeviceType fieldsets = ( (None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')), - ('Attributes', ('weight', 'weight_unit')), + ('Weight', ('weight', 'weight_unit')), ) nullable_fields = ('part_number', 'airflow', 'weight') @@ -420,7 +420,7 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False, initial='', widget=StaticSelect() diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 15b62b71b1c..9647b9c26dd 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -282,11 +282,11 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte required=False ) tag = TagFilterField(model) - weight = forms.IntegerField( + weight = forms.DecimalField( required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False ) @@ -378,7 +378,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items', )), - ('Attributes', ('weight', 'weight_unit')), + ('Weight', ('weight', 'weight_unit')), ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -474,11 +474,11 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm): ) ) tag = TagFilterField(model) - weight = forms.IntegerField( + weight = forms.DecimalField( required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False ) @@ -492,7 +492,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm): 'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports', )), - ('Attributes', ('weight', 'weight_unit')), + ('Weight', ('weight', 'weight_unit')), ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), @@ -546,11 +546,11 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm): ) ) tag = TagFilterField(model) - weight = forms.IntegerField( + weight = forms.DecimalField( required=False ) weight_unit = forms.ChoiceField( - choices=add_blank_choice(DeviceWeightUnitChoices), + choices=add_blank_choice(WeightUnitChoices), required=False ) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index ce80537c631..d72073b84d4 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -20,7 +20,7 @@ from utilities.choices import ColorChoices from utilities.fields import ColorField, NaturalOrderingField from .device_components import * -from .mixins import DeviceWeightMixin +from .mixins import WeightMixin __all__ = ( @@ -71,7 +71,7 @@ def get_absolute_url(self): return reverse('dcim:manufacturer', args=[self.pk]) -class DeviceType(NetBoxModel, DeviceWeightMixin): +class DeviceType(NetBoxModel, WeightMixin): """ A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as well as high-level functional role(s). @@ -309,7 +309,7 @@ def is_child_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD -class ModuleType(NetBoxModel, DeviceWeightMixin): +class ModuleType(NetBoxModel, WeightMixin): """ A ModuleType represents a hardware element that can be installed within a device and which houses additional components; for example, a line card within a chassis-based switch such as the Cisco Catalyst 6500. Like a diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index 4655c1ec33b..98cc4016f01 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -4,7 +4,7 @@ from utilities.utils import to_kilograms -class DeviceWeightMixin(models.Model): +class WeightMixin(models.Model): weight = models.DecimalField( max_digits=8, decimal_places=2, @@ -13,7 +13,7 @@ class DeviceWeightMixin(models.Model): ) weight_unit = models.CharField( max_length=50, - choices=DeviceWeightUnitChoices, + choices=WeightUnitChoices, blank=True, ) # Stores the normalized weight (in kilograms) for database ordering @@ -29,7 +29,7 @@ class Meta: def save(self, *args, **kwargs): - # Store the given weight (if any) in meters for use in database ordering + # Store the given weight (if any) in kilograms for use in database ordering if self.weight and self.weight_unit: self._abs_weight = to_kilograms(self.weight, self.weight_unit) else: diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index bc3a52772f6..90efb3e022d 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -20,7 +20,7 @@ from utilities.utils import array_to_string, drange from .device_components import PowerOutlet, PowerPort from .devices import Device, Module -from .mixins import DeviceWeightMixin +from .mixins import WeightMixin from .power import PowerFeed __all__ = ( @@ -64,7 +64,7 @@ def get_absolute_url(self): return reverse('dcim:rackrole', args=[self.pk]) -class Rack(NetBoxModel, DeviceWeightMixin): +class Rack(NetBoxModel, WeightMixin): """ Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Each Rack is assigned to a Site and (optionally) a Location. diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 024a870ffbf..6536a501fb4 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -409,9 +409,9 @@ def setUpTestData(cls): Tenant.objects.bulk_create(tenants) 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=DeviceWeightUnitChoices.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=DeviceWeightUnitChoices.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=DeviceWeightUnitChoices.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, 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.objects.bulk_create(racks) @@ -522,7 +522,7 @@ def test_weight(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_weight_unit(self): - params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + params = {'weight_unit': WeightUnitChoices.UNIT_POUND} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -696,9 +696,9 @@ def setUpTestData(cls): Manufacturer.objects.bulk_create(manufacturers) device_types = ( - DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png', weight=10, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), - DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR, weight=20, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), - DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT, weight=30, weight_unit=DeviceWeightUnitChoices.UNIT_KILOGRAM), + DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png', weight=10, weight_unit=WeightUnitChoices.UNIT_POUND), + DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR, weight=20, weight_unit=WeightUnitChoices.UNIT_POUND), + DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT, weight=30, weight_unit=WeightUnitChoices.UNIT_KILOGRAM), ) DeviceType.objects.bulk_create(device_types) @@ -852,7 +852,7 @@ def test_weight(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_weight_unit(self): - params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + params = {'weight_unit': WeightUnitChoices.UNIT_POUND} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -871,9 +871,9 @@ def setUpTestData(cls): Manufacturer.objects.bulk_create(manufacturers) module_types = ( - ModuleType(manufacturer=manufacturers[0], model='Model 1', part_number='Part Number 1', weight=10, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), - ModuleType(manufacturer=manufacturers[1], model='Model 2', part_number='Part Number 2', weight=20, weight_unit=DeviceWeightUnitChoices.UNIT_POUND), - ModuleType(manufacturer=manufacturers[2], model='Model 3', part_number='Part Number 3', weight=30, weight_unit=DeviceWeightUnitChoices.UNIT_KILOGRAM), + ModuleType(manufacturer=manufacturers[0], model='Model 1', part_number='Part Number 1', weight=10, weight_unit=WeightUnitChoices.UNIT_POUND), + ModuleType(manufacturer=manufacturers[1], model='Model 2', part_number='Part Number 2', weight=20, weight_unit=WeightUnitChoices.UNIT_POUND), + ModuleType(manufacturer=manufacturers[2], model='Model 3', part_number='Part Number 3', weight=30, weight_unit=WeightUnitChoices.UNIT_KILOGRAM), ) ModuleType.objects.bulk_create(module_types) @@ -964,7 +964,7 @@ def test_weight(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_weight_unit(self): - params = {'weight_unit': DeviceWeightUnitChoices.UNIT_POUND} + params = {'weight_unit': WeightUnitChoices.UNIT_POUND} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 1d2de47688e..263da38a791 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -12,7 +12,7 @@ from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel -from dcim.choices import CableLengthUnitChoices, DeviceWeightUnitChoices +from dcim.choices import CableLengthUnitChoices, WeightUnitChoices from extras.plugins import PluginConfig from extras.utils import is_taggable from netbox.config import get_config @@ -272,7 +272,7 @@ def to_meters(length, unit): def to_kilograms(weight, unit): """ - Convert the given length to kilograms. + Convert the given weight to kilograms. """ try: if weight < 0: @@ -280,24 +280,17 @@ def to_kilograms(weight, unit): except TypeError: raise TypeError(f"Invalid value '{weight}' for weight (must be a number)") - valid_units = DeviceWeightUnitChoices.values() + valid_units = WeightUnitChoices.values() if unit not in valid_units: raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") - UNIT_KILOGRAM = 'kg' - UNIT_GRAM = 'g' - - # Imperial - UNIT_POUND = 'lb' - UNIT_OUNCE = 'oz' - - if unit == DeviceWeightUnitChoices.UNIT_KILOGRAM: + if unit == WeightUnitChoices.UNIT_KILOGRAM: return weight - if unit == DeviceWeightUnitChoices.UNIT_GRAM: + if unit == WeightUnitChoices.UNIT_GRAM: return weight * 1000 - if unit == DeviceWeightUnitChoices.UNIT_POUND: + if unit == WeightUnitChoices.UNIT_POUND: return weight * Decimal(0.453592) - if unit == DeviceWeightUnitChoices.UNIT_OUNCE: + if unit == WeightUnitChoices.UNIT_OUNCE: return weight * Decimal(0.0283495) raise ValueError(f"Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.") From f964c3f35efcb64c15b974ae4a89bc8196d024f0 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 27 Sep 2022 11:41:54 -0700 Subject: [PATCH 16/20] 9654 change _abs_weight to grams --- ...icetype__abs_weight_devicetype_weight_and_more.py | 6 +++--- netbox/dcim/models/mixins.py | 12 +++++------- netbox/dcim/models/racks.py | 2 +- netbox/utilities/utils.py | 10 +++++----- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py index 1e46b579087..3c3421ba5e5 100644 --- a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py +++ b/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='devicetype', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AddField( model_name='devicetype', @@ -28,7 +28,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='moduletype', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AddField( model_name='moduletype', @@ -43,7 +43,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='rack', name='_abs_weight', - field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AddField( model_name='rack', diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index 98cc4016f01..b5449332b5d 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -1,7 +1,7 @@ from django.core.exceptions import ValidationError from django.db import models from dcim.choices import * -from utilities.utils import to_kilograms +from utilities.utils import to_grams class WeightMixin(models.Model): @@ -16,10 +16,8 @@ class WeightMixin(models.Model): choices=WeightUnitChoices, blank=True, ) - # Stores the normalized weight (in kilograms) for database ordering - _abs_weight = models.DecimalField( - max_digits=10, - decimal_places=2, + # Stores the normalized weight (in grams) for database ordering + _abs_weight = models.PositiveBigIntegerField( blank=True, null=True ) @@ -29,9 +27,9 @@ class Meta: def save(self, *args, **kwargs): - # Store the given weight (if any) in kilograms for use in database ordering + # Store the given weight (if any) in grams for use in database ordering if self.weight and self.weight_unit: - self._abs_weight = to_kilograms(self.weight, self.weight_unit) + self._abs_weight = to_grams(self.weight, self.weight_unit) else: self._abs_weight = None diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 90efb3e022d..dcfcebfe2ed 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -455,7 +455,7 @@ def get_total_weight(self): total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device__rack=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) if self._abs_weight: total_weight += self._abs_weight - return round(total_weight, 2) + return round(total_weight / 1000, 2) class RackReservation(NetBoxModel): diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 263da38a791..9f587e88d23 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -270,7 +270,7 @@ def to_meters(length, unit): raise ValueError(f"Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.") -def to_kilograms(weight, unit): +def to_grams(weight, unit): """ Convert the given weight to kilograms. """ @@ -285,13 +285,13 @@ def to_kilograms(weight, unit): raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") if unit == WeightUnitChoices.UNIT_KILOGRAM: - return weight - if unit == WeightUnitChoices.UNIT_GRAM: return weight * 1000 + if unit == WeightUnitChoices.UNIT_GRAM: + return weight if unit == WeightUnitChoices.UNIT_POUND: - return weight * Decimal(0.453592) + return weight * Decimal(453.592) if unit == WeightUnitChoices.UNIT_OUNCE: - return weight * Decimal(0.0283495) + return weight * Decimal(28.3495) raise ValueError(f"Unknown unit {unit}. Must be 'kg', 'g', 'lb', 'oz'.") From c209bebaee9f3324ef372b0dee30f30b21782e29 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Sep 2022 10:48:47 -0400 Subject: [PATCH 17/20] Resolve migrations conflict --- ...t_and_more.py => 0163_rack_devicetype_moduletype_weights.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename netbox/dcim/migrations/{0162_devicetype__abs_weight_devicetype_weight_and_more.py => 0163_rack_devicetype_moduletype_weights.py} (97%) diff --git a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py b/netbox/dcim/migrations/0163_rack_devicetype_moduletype_weights.py similarity index 97% rename from netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py rename to netbox/dcim/migrations/0163_rack_devicetype_moduletype_weights.py index 3c3421ba5e5..09bef573603 100644 --- a/netbox/dcim/migrations/0162_devicetype__abs_weight_devicetype_weight_and_more.py +++ b/netbox/dcim/migrations/0163_rack_devicetype_moduletype_weights.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dcim', '0161_cabling_cleanup'), + ('dcim', '0162_unique_constraints'), ] operations = [ From 74995a06cbec16fab9319cafac112e101792a31f Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 29 Sep 2022 14:33:05 -0700 Subject: [PATCH 18/20] 9654 code-review changes --- netbox/dcim/forms/bulk_edit.py | 50 ++------------------------------- netbox/dcim/forms/filtersets.py | 2 +- netbox/dcim/models/devices.py | 10 ++++++- netbox/dcim/models/racks.py | 2 ++ 4 files changed, 15 insertions(+), 49 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 72407b6dd30..c07db9557d2 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -304,20 +304,9 @@ class RackBulkEditForm(NetBoxModelBulkEditForm): ('Weight', ('weight', 'weight_unit')), ) nullable_fields = ( - 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'weight' + 'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'weight', 'weight_unit' ) - def clean(self): - super().clean() - - # Validate weight/unit - weight = self.cleaned_data.get('weight') - weight_unit = self.cleaned_data.get('weight_unit') - if weight and not weight_unit: - raise forms.ValidationError({ - 'weight_unit': "Must specify a unit when setting weight" - }) - class RackReservationBulkEditForm(NetBoxModelBulkEditForm): user = forms.ModelChoiceField( @@ -393,18 +382,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): (None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')), ('Weight', ('weight', 'weight_unit')), ) - nullable_fields = ('part_number', 'airflow', 'weight') - - def clean(self): - super().clean() - - # Validate weight/unit - weight = self.cleaned_data.get('weight') - weight_unit = self.cleaned_data.get('weight_unit') - if weight and not weight_unit: - raise forms.ValidationError({ - 'weight_unit': "Must specify a unit when setting weight" - }) + nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit') class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): @@ -431,18 +409,7 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): (None, ('manufacturer', 'part_number')), ('Attributes', ('weight', 'weight_unit')), ) - nullable_fields = ('part_number', 'weight',) - - def clean(self): - super().clean() - - # Validate weight/unit - weight = self.cleaned_data.get('weight') - weight_unit = self.cleaned_data.get('weight_unit') - if weight and not weight_unit: - raise forms.ValidationError({ - 'weight_unit': "Must specify a unit when setting weight" - }) + nullable_fields = ('part_number', 'weight', 'weight_unit') class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm): @@ -619,17 +586,6 @@ class CableBulkEditForm(NetBoxModelBulkEditForm): 'type', 'status', 'tenant', 'label', 'color', 'length', ) - def clean(self): - super().clean() - - # Validate length/unit - length = self.cleaned_data.get('length') - length_unit = self.cleaned_data.get('length_unit') - if length and not length_unit: - raise forms.ValidationError({ - 'length_unit': "Must specify a unit when setting length" - }) - class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm): domain = forms.CharField( diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 9647b9c26dd..818da83e1af 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -228,7 +228,7 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte ('Hardware', ('type', 'width', 'serial', 'asset_tag')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), - ('Attributes', ('weight', 'weight_unit')), + ('Weight', ('weight', 'weight_unit')), ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index b15202ac115..20c15f1ca4c 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1,7 +1,8 @@ import decimal - import yaml +from functools import cached_property + from django.apps import apps from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError @@ -946,6 +947,13 @@ def get_children(self): def get_status_color(self): return DeviceStatusChoices.colors.get(self.status) + @cached_property + def get_total_weight(self): + total_weight = sum(module.module_type._abs_weight for module in Module.objects.filter(device=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) + if self.device_type._abs_weight: + total_weight += self.device_type._abs_weight + return round(total_weight / 1000, 2) + class Module(NetBoxModel, ConfigContextModel): """ diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 45c138bf47a..4744784bf63 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -1,4 +1,5 @@ import decimal +from functools import cached_property from django.apps import apps from django.contrib.auth.models import User @@ -455,6 +456,7 @@ def get_power_utilization(self): return int(allocated_draw / available_power_total * 100) + @cached_property def get_total_weight(self): total_weight = sum(device.device_type._abs_weight for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type')) total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device__rack=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) From 44e8c35e0c99984399d2f38ee9a910985c5040b7 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 29 Sep 2022 15:26:57 -0700 Subject: [PATCH 19/20] 9654 total weight on devices --- netbox/templates/dcim/device.html | 25 +++++++++++++++++++++++++ netbox/templates/dcim/rack.html | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 6cc8597495f..217592fb144 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -110,6 +110,31 @@
Rack Weight{{ object.weight }} kg + {% if object.weight %} + {{ object.weight }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Total WeightFull Depth {% checkmark object.is_full_depth %}
Weight + {% if object.weight %} + {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Parent/Child diff --git a/netbox/templates/dcim/moduletype.html b/netbox/templates/dcim/moduletype.html index 2c8e77be39d..8128e64be24 100644 --- a/netbox/templates/dcim/moduletype.html +++ b/netbox/templates/dcim/moduletype.html @@ -22,6 +22,16 @@
Module Type
Part Number {{ object.part_number|placeholder }}
Weight + {% if object.weight %} + {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Instances {{ instance_count }}
+ {% if object.get_total_weight %} +
+
+ Weight +
+
+ + + + + + + + + +
Device Weight + {% if object.device_type.weight %} + {{ object.device_type.weight|floatformat }} {{ object.device_type.get_weight_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Total Weight{{ object.get_total_weight|floatformat }} Kilograms
+
+
+ {% endif %} {% if vc_members %}
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index acd72a5d153..887f7b734c0 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -198,7 +198,7 @@
Rack Weight {% if object.weight %} - {{ object.weight }} + {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} {% else %} {{ ''|placeholder }} {% endif %} @@ -206,7 +206,7 @@
Total Weight - {{ object.get_total_weight }} kg + {{ object.get_total_weight|floatformat }} Kilograms
From d8d439b7fa8b3a6360a4356b3198407866b33bd2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 30 Sep 2022 16:02:14 -0400 Subject: [PATCH 20/20] Misc cleanup --- netbox/dcim/api/serializers.py | 20 +++++++------- netbox/dcim/forms/bulk_edit.py | 6 ++--- netbox/dcim/forms/models.py | 4 +-- netbox/dcim/models/devices.py | 13 ++++++--- netbox/dcim/models/racks.py | 16 ++++++++--- netbox/dcim/tables/devicetypes.py | 2 +- netbox/dcim/tables/modules.py | 2 +- netbox/dcim/tables/racks.py | 6 ++--- netbox/templates/dcim/device.html | 25 ------------------ netbox/templates/dcim/rack.html | 44 +++++++++++-------------------- 10 files changed, 56 insertions(+), 82 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 2b2d85e3bf5..22d56565e57 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -201,17 +201,17 @@ class RackSerializer(NetBoxModelSerializer): default=None) width = ChoiceField(choices=RackWidthChoices, required=False) outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False) + weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) device_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True) - weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) class Meta: model = Rack fields = [ 'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial', - 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', - 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count', - 'weight', 'weight_unit', + 'asset_tag', 'type', 'width', 'u_height', 'weight', 'weight_unit', 'desc_units', 'outer_width', + 'outer_depth', 'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', + 'powerfeed_count', ] @@ -317,29 +317,29 @@ class DeviceTypeSerializer(NetBoxModelSerializer): ) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False) - device_count = serializers.IntegerField(read_only=True) weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) + device_count = serializers.IntegerField(read_only=True) class Meta: model = DeviceType fields = [ 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'airflow', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', - 'last_updated', 'device_count', 'weight', 'weight_unit' + 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', 'device_count', ] class ModuleTypeSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail') manufacturer = NestedManufacturerSerializer() - # module_count = serializers.IntegerField(read_only=True) weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False) + # module_count = serializers.IntegerField(read_only=True) class Meta: model = ModuleType fields = [ - 'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', 'weight', 'weight_unit' + 'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags', + 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index c07db9557d2..d033d3a67f1 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -379,7 +379,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm): model = DeviceType fieldsets = ( - (None, ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')), + ('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')), ('Weight', ('weight', 'weight_unit')), ) nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit') @@ -406,8 +406,8 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm): model = ModuleType fieldsets = ( - (None, ('manufacturer', 'part_number')), - ('Attributes', ('weight', 'weight_unit')), + ('Module Type', ('manufacturer', 'part_number')), + ('Weight', ('weight', 'weight_unit')), ) nullable_fields = ('part_number', 'weight', 'weight_unit') diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 23baa76759a..4faefb623cd 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -260,7 +260,7 @@ class Meta: fields = [ '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', - 'outer_unit', 'comments', 'tags', 'weight', 'weight_unit' + 'outer_unit', 'weight', 'weight_unit', 'comments', 'tags', ] help_texts = { 'site': "The site at which the rack exists", @@ -402,7 +402,7 @@ class ModuleTypeForm(NetBoxModelForm): class Meta: model = ModuleType fields = [ - 'manufacturer', 'model', 'part_number', 'comments', 'tags', 'weight', 'weight_unit' + 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags', ] widgets = { diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 9526d8a7bee..b7c4abd3296 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -141,7 +141,7 @@ class DeviceType(NetBoxModel, WeightMixin): ) clone_fields = ( - 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', + 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', ) class Meta: @@ -346,7 +346,7 @@ class ModuleType(NetBoxModel, WeightMixin): to='extras.ImageAttachment' ) - clone_fields = ('manufacturer',) + clone_fields = ('manufacturer', 'weight', 'weight_unit',) class Meta: ordering = ('manufacturer', 'model') @@ -949,8 +949,13 @@ def get_status_color(self): return DeviceStatusChoices.colors.get(self.status) @cached_property - def get_total_weight(self): - total_weight = sum(module.module_type._abs_weight for module in Module.objects.filter(device=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) + def total_weight(self): + total_weight = sum( + module.module_type._abs_weight + for module in Module.objects.filter(device=self) + .exclude(module_type___abs_weight__isnull=True) + .prefetch_related('module_type') + ) if self.device_type._abs_weight: total_weight += self.device_type._abs_weight return round(total_weight / 1000, 2) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index b196141d76c..6da48b65ca8 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -187,7 +187,7 @@ class Rack(NetBoxModel, WeightMixin): clone_fields = ( 'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width', - 'outer_depth', 'outer_unit', + 'outer_depth', 'outer_unit', 'weight', 'weight_unit', ) class Meta: @@ -457,9 +457,17 @@ def get_power_utilization(self): return int(allocated_draw / available_power_total * 100) @cached_property - def get_total_weight(self): - total_weight = sum(device.device_type._abs_weight for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type')) - total_weight += sum(module.module_type._abs_weight for module in Module.objects.filter(device__rack=self).exclude(module_type___abs_weight__isnull=True).prefetch_related('module_type')) + def total_weight(self): + total_weight = sum( + device.device_type._abs_weight + for device in self.devices.exclude(device_type___abs_weight__isnull=True).prefetch_related('device_type') + ) + total_weight += sum( + module.module_type._abs_weight + for module in Module.objects.filter(device__rack=self) + .exclude(module_type___abs_weight__isnull=True) + .prefetch_related('module_type') + ) if self._abs_weight: total_weight += self._abs_weight return round(total_weight / 1000, 2) diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 79e79574886..8f371ef1a5d 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -94,7 +94,7 @@ class Meta(NetBoxTable.Meta): model = DeviceType fields = ( 'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', - 'airflow', 'comments', 'instance_count', 'tags', 'created', 'last_updated', 'weight' + 'airflow', 'weight', 'comments', 'instance_count', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count', diff --git a/netbox/dcim/tables/modules.py b/netbox/dcim/tables/modules.py index 43ca53063f5..b644e6ba66d 100644 --- a/netbox/dcim/tables/modules.py +++ b/netbox/dcim/tables/modules.py @@ -35,7 +35,7 @@ class ModuleTypeTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = ModuleType fields = ( - 'pk', 'id', 'model', 'manufacturer', 'part_number', 'comments', 'tags', 'weight' + 'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'comments', 'tags', ) default_columns = ( 'pk', 'model', 'manufacturer', 'part_number', diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index 2cb0fa17a54..ffca071456e 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -91,9 +91,9 @@ class RackTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = Rack fields = ( - 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial', 'asset_tag', - 'type', 'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization', - 'get_power_utilization', 'contacts', 'tags', 'created', 'last_updated', 'weight' + 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial', + 'asset_tag', 'type', 'width', 'outer_width', 'outer_depth', 'u_height', 'weight', 'comments', + 'device_count', 'get_utilization', 'get_power_utilization', 'contacts', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count', diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 217592fb144..6cc8597495f 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -110,31 +110,6 @@
- {% if object.get_total_weight %} -
-
- Weight -
-
- - - - - - - - - -
Device Weight - {% if object.device_type.weight %} - {{ object.device_type.weight|floatformat }} {{ object.device_type.get_weight_unit_display }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
Total Weight{{ object.get_total_weight|floatformat }} Kilograms
-
-
- {% endif %} {% if vc_members %}
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 887f7b734c0..e30ce7a6214 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -104,9 +104,7 @@
-
- Dimensions -
+
Dimensions
@@ -147,6 +145,20 @@
{% endif %}
+ + + + + + + +
Rack Weight + {% if object.weight %} + {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Total Weight{{ object.total_weight|floatformat }} Kilograms
@@ -187,32 +199,6 @@
{% endif %} - {% if object.get_total_weight %} -
-
- Weight -
-
- - - - - - - - - -
Rack Weight - {% if object.weight %} - {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
Total Weight{{ object.get_total_weight|floatformat }} Kilograms
-
-
- {% endif %} - {% include 'inc/panels/image_attachments.html' %}