Address review feedback

This commit is contained in:
Brian Tiemann
2026-03-06 14:11:37 -05:00
parent 07af32edb3
commit 229a154211
2 changed files with 37 additions and 4 deletions

View File

@@ -203,6 +203,7 @@ class ModuleSerializer(PrimaryModelSerializer):
module_type = data.get('module_type')
module_bay = data.get('module_bay')
# Required-field validation fires separately; skip here if any are missing.
if not all([device, module_type, module_bay]):
return data
@@ -238,8 +239,7 @@ class ModuleSerializer(PrimaryModelSerializer):
if template.name.count(MODULE_TOKEN) != len(module_bays):
raise serializers.ValidationError(
_(
"Cannot install module with placeholder values in a module bay tree {level} in tree "
"but {tokens} placeholders given."
"Cannot install module with {tokens} placeholder(s) in a module bay at depth {level}."
).format(
level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
)
@@ -274,8 +274,8 @@ class ModuleSerializer(PrimaryModelSerializer):
# Tags are handled after save; pop them here to pass to _save_tags()
tags = validated_data.pop('tags', None)
# Build the instance without saving so we can set private attributes
# that control component replication behaviour in Module.save()
# _adopt_components and _disable_replication must be set on the instance before
# save() is called, so we cannot delegate to super().create() here.
instance = self.Meta.model(**validated_data)
if adopt_components:
instance._adopt_components = True

View File

@@ -1846,9 +1846,42 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
}
response = self.client.patch(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(response.data['serial'], 'PATCHED')
# No interfaces should have been created by the PATCH
self.assertFalse(device.interfaces.exists())
def test_adopt_and_replicate_components(self):
"""
Installing a module with both adopt_components=True and replicate_components=True
should adopt existing unowned components and create new components for templates
that have no matching existing component.
"""
self.add_permissions('dcim.add_module')
manufacturer = Manufacturer.objects.get(name='Generic')
device = create_test_device('Device for Adopt+Replicate Test')
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Adopt+Replicate Test Module Type')
InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
InterfaceTemplate.objects.create(module_type=module_type, name='eth1', type='1000base-t')
module_bay = ModuleBay.objects.create(device=device, name='Adopt+Replicate Bay')
# eth0 already exists (unowned); eth1 does not
existing_iface = Interface.objects.create(device=device, name='eth0', type='1000base-t')
url = reverse('dcim-api:module-list')
data = {
'device': device.pk,
'module_bay': module_bay.pk,
'module_type': module_type.pk,
'adopt_components': True,
'replicate_components': True,
}
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
# eth0 should have been adopted (now owned by the new module)
existing_iface.refresh_from_db()
self.assertIsNotNone(existing_iface.module)
# eth1 should have been created
self.assertTrue(device.interfaces.filter(name='eth1').exists())
class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
model = ConsolePort