Repository: netbox-community/netbox

Issue:
Import Device/Module Weight
### NetBox version

v3.4.4

### Feature type

Change to existing functionality

### Proposed functionality

Since we already have weight as an option, we now need to add the ability to import weight data via the CSV/YAML/JSON import. Module Types and Device Types currently do not have weight as an option to import.

NetBox devicetype-library currently has this implemented using [weight value and unit](https://github.com/netbox-community/devicetype-library/blob/7bf2f659b3695a11750e85cd8bc16ae4b5741915/device-types/Inspur/NF5180M6.yaml). We can modify the devicetype-library if needed to support this import functionality.


### Use case

Adding weight to an object is currently supported in the single import option, but not within the bulk import.

### Database changes

_No response_

### External dependencies

_No response_
Import Device/Module Weight
### NetBox version

v3.4.4

### Feature type

Change to existing functionality

### Proposed functionality

Since we already have weight as an option, we now need to add the ability to import weight data via the CSV/YAML/JSON import. Module Types and Device Types currently do not have weight as an option to import.

NetBox devicetype-library currently has this implemented using [weight value and unit](https://github.com/netbox-community/devicetype-library/blob/7bf2f659b3695a11750e85cd8bc16ae4b5741915/device-types/Inspur/NF5180M6.yaml). We can modify the devicetype-library if needed to support this import functionality.


### Use case

Adding weight to an object is currently supported in the single import option, but not within the bulk import.

### Database changes

_No response_

### External dependencies

_No response_


Discussion:
I noticed that this issue is currently marked as "needs owner" and I would like to volunteer to take ownership of it. This is my first contribution to Netbox, but I am eager to learn and contribute to the project.
Thanks @dhenschen!
I noticed that this issue is currently marked as "needs owner" and I would like to volunteer to take ownership of it. This is my first contribution to Netbox, but I am eager to learn and contribute to the project.
Thanks @dhenschen!

Resolving patch:
diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py
--- a/netbox/dcim/forms/bulk_import.py
+++ b/netbox/dcim/forms/bulk_import.py
@@ -292,12 +292,21 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
         required=False,
         help_text=_('The default platform for devices of this type (optional)')
     )
+    weight = forms.DecimalField(
+        required=False,
+        help_text=_('Device weight'),
+    )
+    weight_unit = CSVChoiceField(
+        choices=WeightUnitChoices,
+        required=False,
+        help_text=_('Unit for device weight')
+    )
 
     class Meta:
         model = DeviceType
         fields = [
             'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
-            'subdevice_role', 'airflow', 'description', 'comments',
+            'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments',
         ]
 
 
@@ -306,10 +315,19 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
         queryset=Manufacturer.objects.all(),
         to_field_name='name'
     )
+    weight = forms.DecimalField(
+        required=False,
+        help_text=_('Module weight'),
+    )
+    weight_unit = CSVChoiceField(
+        choices=WeightUnitChoices,
+        required=False,
+        help_text=_('Unit for module weight')
+    )
 
     class Meta:
         model = ModuleType
-        fields = ['manufacturer', 'model', 'part_number', 'description', 'comments']
+        fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments']
 
 
 class DeviceRoleImportForm(NetBoxModelImportForm):
diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py
--- a/netbox/dcim/models/devices.py
+++ b/netbox/dcim/models/devices.py
@@ -184,6 +184,8 @@ def to_yaml(self):
             'subdevice_role': self.subdevice_role,
             'airflow': self.airflow,
             'comments': self.comments,
+            'weight': float(self.weight) if self.weight is not None else None,
+            'weight_unit': self.weight_unit,
         }
 
         # Component templates
@@ -361,6 +363,8 @@ def to_yaml(self):
             'model': self.model,
             'part_number': self.part_number,
             'comments': self.comments,
+            'weight': float(self.weight) if self.weight is not None else None,
+            'weight_unit': self.weight_unit,
         }
 
         # Component templates


Test patch:
diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py
--- a/netbox/dcim/tests/test_views.py
+++ b/netbox/dcim/tests/test_views.py
@@ -681,11 +681,15 @@ def test_import_objects(self):
         """
         IMPORT_DATA = """
 manufacturer: Generic
-default_platform: Platform
 model: TEST-1000
 slug: test-1000
+default_platform: Platform
 u_height: 2
+is_full_depth: false
+airflow: front-to-rear
 subdevice_role: parent
+weight: 10
+weight_unit: kg
 comments: Test comment
 console-ports:
   - name: Console Port 1
@@ -794,8 +798,16 @@ def test_import_objects(self):
         self.assertHttpStatus(response, 200)
 
         device_type = DeviceType.objects.get(model='TEST-1000')
-        self.assertEqual(device_type.comments, 'Test comment')
+        self.assertEqual(device_type.manufacturer.pk, manufacturer.pk)
         self.assertEqual(device_type.default_platform.pk, platform.pk)
+        self.assertEqual(device_type.slug, 'test-1000')
+        self.assertEqual(device_type.u_height, 2)
+        self.assertFalse(device_type.is_full_depth)
+        self.assertEqual(device_type.airflow, DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR)
+        self.assertEqual(device_type.subdevice_role, SubdeviceRoleChoices.ROLE_PARENT)
+        self.assertEqual(device_type.weight, 10)
+        self.assertEqual(device_type.weight_unit, WeightUnitChoices.UNIT_KILOGRAM)
+        self.assertEqual(device_type.comments, 'Test comment')
 
         # Verify all of the components were created
         self.assertEqual(device_type.consoleporttemplates.count(), 3)
@@ -1019,6 +1031,8 @@ def test_import_objects(self):
         IMPORT_DATA = """
 manufacturer: Generic
 model: TEST-1000
+weight: 10
+weight_unit: lb
 comments: Test comment
 console-ports:
   - name: Console Port 1
@@ -1082,7 +1096,8 @@ def test_import_objects(self):
 """
 
         # Create the manufacturer
-        Manufacturer(name='Generic', slug='generic').save()
+        manufacturer = Manufacturer(name='Generic', slug='generic')
+        manufacturer.save()
 
         # Add all required permissions to the test user
         self.add_permissions(
@@ -1105,6 +1120,9 @@ def test_import_objects(self):
         self.assertHttpStatus(response, 200)
 
         module_type = ModuleType.objects.get(model='TEST-1000')
+        self.assertEqual(module_type.manufacturer.pk, manufacturer.pk)
+        self.assertEqual(module_type.weight, 10)
+        self.assertEqual(module_type.weight_unit, WeightUnitChoices.UNIT_POUND)
         self.assertEqual(module_type.comments, 'Test comment')
 
         # Verify all the components were created