2
0
Эх сурвалжийг харах

Fixes #9653 - Add default_platform to DeviceType

kkthxbye-code 3 жил өмнө
parent
commit
81b8046d1d

+ 2 - 1
netbox/dcim/api/serializers.py

@@ -309,6 +309,7 @@ class ManufacturerSerializer(NetBoxModelSerializer):
 class DeviceTypeSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
     manufacturer = NestedManufacturerSerializer()
+    default_platform = NestedPlatformSerializer(required=False, allow_null=True)
     u_height = serializers.DecimalField(
         max_digits=4,
         decimal_places=1,
@@ -324,7 +325,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
     class Meta:
         model = DeviceType
         fields = [
-            'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
+            'id', 'url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
             'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
             'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
         ]

+ 10 - 0
netbox/dcim/filtersets.py

@@ -436,6 +436,16 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
         to_field_name='slug',
         label=_('Manufacturer (slug)'),
     )
+    default_platform_id = django_filters.ModelMultipleChoiceFilter(
+        queryset=Platform.objects.all(),
+        label=_('Default platform (ID)'),
+    )
+    default_platform = django_filters.ModelMultipleChoiceFilter(
+        field_name='default_platform__slug',
+        queryset=Platform.objects.all(),
+        to_field_name='slug',
+        label=_('Default platform (slug)'),
+    )
     has_front_image = django_filters.BooleanFilter(
         label=_('Has a front image'),
         method='_has_front_image'

+ 5 - 1
netbox/dcim/forms/bulk_edit.py

@@ -374,6 +374,10 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
         queryset=Manufacturer.objects.all(),
         required=False
     )
+    default_platform = DynamicModelChoiceField(
+        queryset=Platform.objects.all(),
+        required=False
+    )
     part_number = forms.CharField(
         required=False
     )
@@ -412,7 +416,7 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
 
     model = DeviceType
     fieldsets = (
-        ('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
+        ('Device Type', ('manufacturer', 'default_platform', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
         ('Weight', ('weight', 'weight_unit')),
     )
     nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')

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

@@ -281,12 +281,17 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
         queryset=Manufacturer.objects.all(),
         to_field_name='name'
     )
+    default_platform = forms.ModelChoiceField(
+        queryset=Platform.objects.all(),
+        to_field_name='name',
+        required=False,
+    )
 
     class Meta:
         model = DeviceType
         fields = [
-            'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
-            'description', 'comments',
+            'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
+            'subdevice_role', 'airflow', 'description', 'comments',
         ]
 
 

+ 6 - 1
netbox/dcim/forms/filtersets.py

@@ -378,7 +378,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
     model = DeviceType
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
-        ('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
+        ('Hardware', ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
         ('Images', ('has_front_image', 'has_rear_image')),
         ('Components', (
             'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
@@ -391,6 +391,11 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
         required=False,
         label=_('Manufacturer')
     )
+    default_platform_id = DynamicModelMultipleChoiceField(
+        queryset=Platform.objects.all(),
+        required=False,
+        label=_('Default platform')
+    )
     part_number = forms.CharField(
         required=False
     )

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

@@ -378,13 +378,17 @@ class DeviceTypeForm(NetBoxModelForm):
     manufacturer = DynamicModelChoiceField(
         queryset=Manufacturer.objects.all()
     )
+    default_platform = DynamicModelChoiceField(
+        queryset=Platform.objects.all(),
+        required=False
+    )
     slug = SlugField(
         slug_source='model'
     )
     comments = CommentField()
 
     fieldsets = (
-        ('Device Type', ('manufacturer', 'model', 'slug', 'description', 'tags')),
+        ('Device Type', ('manufacturer', 'model', 'slug', 'description', 'tags', 'default_platform')),
         ('Chassis', (
             'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
         )),
@@ -395,7 +399,7 @@ class DeviceTypeForm(NetBoxModelForm):
         model = DeviceType
         fields = [
             'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
-            'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags',
+            'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags', 'default_platform'
         ]
         widgets = {
             'airflow': StaticSelect(),

+ 19 - 0
netbox/dcim/migrations/0169_devicetype_default_platform.py

@@ -0,0 +1,19 @@
+# Generated by Django 4.1.6 on 2023-02-10 18:06
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0168_interface_template_enabled'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='devicetype',
+            name='default_platform',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.platform'),
+        ),
+    ]

+ 14 - 1
netbox/dcim/models/devices.py

@@ -82,6 +82,14 @@ class DeviceType(PrimaryModel, WeightMixin):
     slug = models.SlugField(
         max_length=100
     )
+    default_platform = models.ForeignKey(
+        to='dcim.Platform',
+        on_delete=models.SET_NULL,
+        related_name='+',
+        blank=True,
+        null=True,
+        verbose_name='Default platform'
+    )
     part_number = models.CharField(
         max_length=50,
         blank=True,
@@ -121,7 +129,7 @@ class DeviceType(PrimaryModel, WeightMixin):
     )
 
     clone_fields = (
-        'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
+        'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
     )
     prerequisite_models = (
         'dcim.Manufacturer',
@@ -165,6 +173,7 @@ class DeviceType(PrimaryModel, WeightMixin):
             'manufacturer': self.manufacturer.name,
             'model': self.model,
             'slug': self.slug,
+            'default_platform': self.default_platform.name if self.default_platform else None,
             'part_number': self.part_number,
             'u_height': float(self.u_height),
             'is_full_depth': self.is_full_depth,
@@ -801,6 +810,10 @@ class Device(PrimaryModel, ConfigContextModel):
         if is_new and not self.airflow:
             self.airflow = self.device_type.airflow
 
+        # Inherit default_platform from DeviceType if not set
+        if is_new and not self.platform:
+            self.platform = self.device_type.default_platform
+
         super().save(*args, **kwargs)
 
         # If this is a new Device, instantiate all the related components per the DeviceType definition

+ 4 - 1
netbox/dcim/tables/devicetypes.py

@@ -77,6 +77,9 @@ class DeviceTypeTable(NetBoxTable):
     manufacturer = tables.Column(
         linkify=True
     )
+    default_platform = tables.Column(
+        linkify=True
+    )
     is_full_depth = columns.BooleanColumn(
         verbose_name='Full Depth'
     )
@@ -100,7 +103,7 @@ class DeviceTypeTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = models.DeviceType
         fields = (
-            'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
+            'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
             'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
         )
         default_columns = (

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

@@ -699,9 +699,16 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         Manufacturer.objects.bulk_create(manufacturers)
 
+        platforms = (
+            Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0]),
+            Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1]),
+            Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2]),
+        )
+        Platform.objects.bulk_create(platforms)
+
         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=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[0], default_platform=platforms[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], default_platform=platforms[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)
@@ -785,6 +792,13 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_default_platform(self):
+        platforms = Platform.objects.all()[:2]
+        params = {'default_platform_id': [platforms[0].pk, platforms[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        params = {'default_platform': [platforms[0].slug, platforms[1].slug]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_has_front_image(self):
         params = {'has_front_image': True}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

+ 15 - 2
netbox/dcim/tests/test_views.py

@@ -503,6 +503,12 @@ class DeviceTypeTestCase(
         )
         Manufacturer.objects.bulk_create(manufacturers)
 
+        platforms = (
+            Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0]),
+            Platform(name='Platform 2', slug='platform-3', manufacturer=manufacturers[1]),
+        )
+        Platform.objects.bulk_create(platforms)
+
         DeviceType.objects.bulk_create([
             DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturers[0]),
             DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturers[0]),
@@ -513,6 +519,7 @@ class DeviceTypeTestCase(
 
         cls.form_data = {
             'manufacturer': manufacturers[1].pk,
+            'default_platform': platforms[0].pk,
             'model': 'Device Type X',
             'slug': 'device-type-x',
             'part_number': '123ABC',
@@ -525,6 +532,7 @@ class DeviceTypeTestCase(
 
         cls.bulk_edit_data = {
             'manufacturer': manufacturers[1].pk,
+            'default_platform': platforms[1].pk,
             'u_height': 3,
             'is_full_depth': False,
         }
@@ -673,6 +681,7 @@ class DeviceTypeTestCase(
         """
         IMPORT_DATA = """
 manufacturer: Generic
+default_platform: Platform
 model: TEST-1000
 slug: test-1000
 u_height: 2
@@ -755,8 +764,11 @@ inventory-items:
     manufacturer: Generic
 """
 
-        # Create the manufacturer
-        Manufacturer(name='Generic', slug='generic').save()
+        # Create the manufacturer and platform
+        manufacturer = Manufacturer(name='Generic', slug='generic')
+        manufacturer.save()
+        platform = Platform(name='Platform', slug='test-platform', manufacturer=manufacturer)
+        platform.save()
 
         # Add all required permissions to the test user
         self.add_permissions(
@@ -783,6 +795,7 @@ inventory-items:
 
         device_type = DeviceType.objects.get(model='TEST-1000')
         self.assertEqual(device_type.comments, 'Test comment')
+        self.assertEqual(device_type.default_platform.pk, platform.pk)
 
         # Verify all of the components were created
         self.assertEqual(device_type.consoleporttemplates.count(), 3)

+ 4 - 0
netbox/templates/dcim/devicetype.html

@@ -27,6 +27,10 @@
                             <td>Part Number</td>
                             <td>{{ object.part_number|placeholder }}</td>
                         </tr>
+                        <tr>
+                            <td>Default Platform</td>
+                            <td>{{ object.default_platform|linkify }}</td>
+                        </tr>
                         <tr>
                             <td>Description</td>
                             <td>{{ object.description|placeholder }}</td>