#19731: Support module_bay_types in device/module type YAML import and export
Follow-up QA for the ModuleBayType feature added in #22648. ModuleBayTemplate.to_yaml() omitted module_bay_types, and ModuleBayTemplateImportForm (used by the DeviceType/ModuleType YAML "Import Components" flow) didn't expose the field either, so bay-type constraints could never be defined as part of a device type's YAML definition -- only assigned by hand, one bay at a time, after import. Add module_bay_types (by name) to the import form, scoped to the parent device/module type's manufacturer (or global types) via clean_device_type/ clean_module_type, mirroring the existing scoping pattern used elsewhere in this form for power_port/cooling_intake. Add it to to_yaml()'s output symmetrically.
This commit is contained in:
parent
f89d3b1f20
commit
157a30ecd7
|
|
@ -1,4 +1,5 @@
|
|||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
|
||||
|
|
@ -213,13 +214,37 @@ class PortTemplateMappingImportForm(forms.ModelForm):
|
|||
|
||||
|
||||
class ModuleBayTemplateImportForm(forms.ModelForm):
|
||||
module_bay_types = forms.ModelMultipleChoiceField(
|
||||
label=_('Module bay types'),
|
||||
queryset=ModuleBayType.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleBayTemplate
|
||||
fields = [
|
||||
'device_type', 'module_type', 'name', 'label', 'position', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'position', 'description', 'module_bay_types',
|
||||
]
|
||||
|
||||
def clean_device_type(self):
|
||||
if device_type := self.cleaned_data['device_type']:
|
||||
module_bay_types = self.fields['module_bay_types']
|
||||
module_bay_types.queryset = module_bay_types.queryset.filter(
|
||||
Q(manufacturer__isnull=True) | Q(manufacturer=device_type.manufacturer)
|
||||
)
|
||||
|
||||
return device_type
|
||||
|
||||
def clean_module_type(self):
|
||||
if module_type := self.cleaned_data['module_type']:
|
||||
module_bay_types = self.fields['module_bay_types']
|
||||
module_bay_types.queryset = module_bay_types.queryset.filter(
|
||||
Q(manufacturer__isnull=True) | Q(manufacturer=module_type.manufacturer)
|
||||
)
|
||||
|
||||
return module_type
|
||||
|
||||
|
||||
class DeviceBayTemplateImportForm(forms.ModelForm):
|
||||
|
||||
|
|
|
|||
|
|
@ -988,6 +988,7 @@ class ModuleBayTemplate(ModularComponentTemplateModel):
|
|||
'position': self.position,
|
||||
'enabled': self.enabled,
|
||||
'description': self.description,
|
||||
'module_bay_types': [t.name for t in self.module_bay_types.all()],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,19 @@ class ModuleTypeTestCase(TestCase):
|
|||
module_type.refresh_from_db()
|
||||
self.assertEqual(module_type.interface_template_count, 1)
|
||||
|
||||
def test_module_bay_template_to_yaml_includes_module_bay_types(self):
|
||||
"""
|
||||
ModuleBayTemplate.to_yaml() should export its assigned module bay types by name.
|
||||
"""
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
|
||||
bay_type = ModuleBayType.objects.create(name='SFP28', slug='sfp28')
|
||||
module_bay_template = ModuleBayTemplate.objects.create(module_type=module_type, name='Module Bay 1')
|
||||
module_bay_template.module_bay_types.set([bay_type])
|
||||
|
||||
data = module_bay_template.to_yaml()
|
||||
self.assertEqual(data['module_bay_types'], ['SFP28'])
|
||||
|
||||
def test_attributes(self):
|
||||
"""
|
||||
ModuleType.attributes should normalize iterable values into strings for presentation.
|
||||
|
|
|
|||
|
|
@ -998,6 +998,8 @@ port-mappings:
|
|||
rear_port: Rear Port 3
|
||||
module-bays:
|
||||
- name: Module Bay 1
|
||||
module_bay_types:
|
||||
- SFP28
|
||||
- name: Module Bay 2
|
||||
- name: Module Bay 3
|
||||
device-bays:
|
||||
|
|
@ -1018,6 +1020,7 @@ inventory-items:
|
|||
manufacturer.save()
|
||||
platform = Platform(name='Platform', slug='test-platform', manufacturer=manufacturer)
|
||||
platform.save()
|
||||
ModuleBayType.objects.create(name='SFP28', slug='sfp28')
|
||||
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
|
|
@ -1126,6 +1129,7 @@ inventory-items:
|
|||
self.assertEqual(device_type.modulebaytemplates.count(), 3)
|
||||
mb1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(mb1.name, 'Module Bay 1')
|
||||
self.assertEqual(list(mb1.module_bay_types.values_list('name', flat=True)), ['SFP28'])
|
||||
|
||||
self.assertEqual(device_type.devicebaytemplates.count(), 3)
|
||||
db1 = DeviceBayTemplate.objects.first()
|
||||
|
|
@ -1648,6 +1652,8 @@ port-mappings:
|
|||
module-bays:
|
||||
- name: Module Bay 1
|
||||
position: 1
|
||||
module_bay_types:
|
||||
- SFP28
|
||||
- name: Module Bay 2
|
||||
position: 2
|
||||
- name: Module Bay 3
|
||||
|
|
@ -1657,6 +1663,7 @@ module-bays:
|
|||
# Create the manufacturer
|
||||
manufacturer = Manufacturer(name='Generic', slug='generic')
|
||||
manufacturer.save()
|
||||
ModuleBayType.objects.create(name='SFP28', slug='sfp28')
|
||||
|
||||
# Add all required permissions to the test user
|
||||
self.add_permissions(
|
||||
|
|
@ -1752,6 +1759,7 @@ module-bays:
|
|||
mb1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(mb1.name, 'Module Bay 1')
|
||||
self.assertEqual(mb1.position, '1')
|
||||
self.assertEqual(list(mb1.module_bay_types.values_list('name', flat=True)), ['SFP28'])
|
||||
|
||||
@override_settings(STREAMING_EXPORTS=True)
|
||||
def test_export_objects(self):
|
||||
|
|
|
|||
Loading…
Reference in New Issue