diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 7b9a9e4b2..eac616a2c 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -17,6 +17,7 @@ * [#9306](https://github.com/netbox-community/netbox/issues/9306) - Include VC master interfaces when selecting a LAG/bridge for a VC member interface * [#9311](https://github.com/netbox-community/netbox/issues/9311) - Permit creating contact assignment without a priority via the REST API * [#9313](https://github.com/netbox-community/netbox/issues/9313) - Remove HTML code from CSV output of many-to-many relationships +* [#9330](https://github.com/netbox-community/netbox/issues/9330) - Add missing `module_type` field to REST API serializers for modular device component templates --- diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 813c946a3..7fcab6ba3 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -315,7 +315,16 @@ class ModuleTypeSerializer(NetBoxModelSerializer): class ConsolePortTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleporttemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField( choices=ConsolePortTypeChoices, allow_blank=True, @@ -325,13 +334,23 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer): class Meta: model = ConsolePortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'description', 'created', + 'last_updated', ] class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverporttemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField( choices=ConsolePortTypeChoices, allow_blank=True, @@ -341,13 +360,23 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = ConsoleServerPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'description', 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'description', 'created', + 'last_updated', ] class PowerPortTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerporttemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField( choices=PowerPortTypeChoices, allow_blank=True, @@ -357,14 +386,23 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = PowerPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', - 'description', 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', + 'allocated_draw', 'description', 'created', 'last_updated', ] class PowerOutletTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlettemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField( choices=PowerOutletTypeChoices, allow_blank=True, @@ -383,48 +421,75 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer): class Meta: model = PowerOutletTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', - 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', + 'description', 'created', 'last_updated', ] class InterfaceTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfacetemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField(choices=InterfaceTypeChoices) class Meta: model = InterfaceTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'created', - 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', + 'created', 'last_updated', ] class RearPortTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearporttemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField(choices=PortTypeChoices) class Meta: model = RearPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'color', 'positions', 'description', - 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', + 'description', 'created', 'last_updated', ] class FrontPortTemplateSerializer(ValidatedModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontporttemplate-detail') - device_type = NestedDeviceTypeSerializer() + device_type = NestedDeviceTypeSerializer( + required=False, + allow_null=True, + default=None + ) + module_type = NestedModuleTypeSerializer( + required=False, + allow_null=True, + default=None + ) type = ChoiceField(choices=PortTypeChoices) rear_port = NestedRearPortTemplateSerializer() class Meta: model = FrontPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', - 'description', 'created', 'last_updated', + 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', + 'rear_port_position', 'description', 'created', 'last_updated', ] diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 5c7d22955..22537abe0 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -523,6 +523,9 @@ class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) console_port_templates = ( ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'), @@ -541,9 +544,13 @@ class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase): 'name': 'Console Port Template 5', }, { - 'device_type': devicetype.pk, + 'module_type': moduletype.pk, 'name': 'Console Port Template 6', }, + { + 'module_type': moduletype.pk, + 'name': 'Console Port Template 7', + }, ] @@ -560,6 +567,9 @@ class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) console_server_port_templates = ( ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'), @@ -578,9 +588,13 @@ class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase): 'name': 'Console Server Port Template 5', }, { - 'device_type': devicetype.pk, + 'module_type': moduletype.pk, 'name': 'Console Server Port Template 6', }, + { + 'module_type': moduletype.pk, + 'name': 'Console Server Port Template 7', + }, ] @@ -597,6 +611,9 @@ class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) power_port_templates = ( PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'), @@ -615,9 +632,13 @@ class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase): 'name': 'Power Port Template 5', }, { - 'device_type': devicetype.pk, + 'module_type': moduletype.pk, 'name': 'Power Port Template 6', }, + { + 'module_type': moduletype.pk, + 'name': 'Power Port Template 7', + }, ] @@ -634,6 +655,9 @@ class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) power_port_templates = ( PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'), @@ -664,6 +688,14 @@ class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase): 'name': 'Power Outlet Template 6', 'power_port': None, }, + { + 'module_type': moduletype.pk, + 'name': 'Power Outlet Template 7', + }, + { + 'module_type': moduletype.pk, + 'name': 'Power Outlet Template 8', + }, ] @@ -680,6 +712,9 @@ class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) interface_templates = ( InterfaceTemplate(device_type=devicetype, name='Interface Template 1', type='1000base-t'), @@ -700,10 +735,15 @@ class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase): 'type': '1000base-t', }, { - 'device_type': devicetype.pk, + 'module_type': moduletype.pk, 'name': 'Interface Template 6', 'type': '1000base-t', }, + { + 'module_type': moduletype.pk, + 'name': 'Interface Template 7', + 'type': '1000base-t', + }, ] @@ -720,14 +760,19 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) rear_port_templates = ( RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C), RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C), RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C), RearPortTemplate(device_type=devicetype, name='Rear Port Template 4', type=PortTypeChoices.TYPE_8P8C), - RearPortTemplate(device_type=devicetype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C), - RearPortTemplate(device_type=devicetype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(module_type=moduletype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(module_type=moduletype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(module_type=moduletype, name='Rear Port Template 7', type=PortTypeChoices.TYPE_8P8C), + RearPortTemplate(module_type=moduletype, name='Rear Port Template 8', type=PortTypeChoices.TYPE_8P8C), ) RearPortTemplate.objects.bulk_create(rear_port_templates) @@ -745,15 +790,28 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase): rear_port=rear_port_templates[1] ), FrontPortTemplate( - device_type=devicetype, - name='Front Port Template 3', + module_type=moduletype, + name='Front Port Template 5', type=PortTypeChoices.TYPE_8P8C, - rear_port=rear_port_templates[2] + rear_port=rear_port_templates[4] + ), + FrontPortTemplate( + module_type=moduletype, + name='Front Port Template 6', + type=PortTypeChoices.TYPE_8P8C, + rear_port=rear_port_templates[5] ), ) FrontPortTemplate.objects.bulk_create(front_port_templates) cls.create_data = [ + { + 'device_type': devicetype.pk, + 'name': 'Front Port Template 3', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_port_templates[2].pk, + 'rear_port_position': 1, + }, { 'device_type': devicetype.pk, 'name': 'Front Port Template 4', @@ -762,17 +820,17 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase): 'rear_port_position': 1, }, { - 'device_type': devicetype.pk, - 'name': 'Front Port Template 5', + 'module_type': moduletype.pk, + 'name': 'Front Port Template 7', 'type': PortTypeChoices.TYPE_8P8C, - 'rear_port': rear_port_templates[4].pk, + 'rear_port': rear_port_templates[6].pk, 'rear_port_position': 1, }, { - 'device_type': devicetype.pk, - 'name': 'Front Port Template 6', + 'module_type': moduletype.pk, + 'name': 'Front Port Template 8', 'type': PortTypeChoices.TYPE_8P8C, - 'rear_port': rear_port_templates[5].pk, + 'rear_port': rear_port_templates[7].pk, 'rear_port_position': 1, }, ] @@ -791,6 +849,9 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase): devicetype = DeviceType.objects.create( manufacturer=manufacturer, model='Device Type 1', slug='device-type-1' ) + moduletype = ModuleType.objects.create( + manufacturer=manufacturer, model='Module Type 1' + ) rear_port_templates = ( RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C), @@ -811,10 +872,15 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase): 'type': PortTypeChoices.TYPE_8P8C, }, { - 'device_type': devicetype.pk, + 'module_type': moduletype.pk, 'name': 'Rear Port Template 6', 'type': PortTypeChoices.TYPE_8P8C, }, + { + 'module_type': moduletype.pk, + 'name': 'Rear Port Template 7', + 'type': PortTypeChoices.TYPE_8P8C, + }, ]