Fixes #23150: Pre-select the parent object on 'create & add another' for non-cloning models

ObjectEditView and ComponentCreateView build the add-another redirect
from prepare_cloned_fields(), which returns an empty QueryDict for models
that don't support cloning - the component templates (InterfaceTemplate,
etc.). When adding components to a device type via 'Create & Add
Another', the parent device type was dropped and the form came back
blank.

For non-cloning models, fall back to pre-selecting any parent object the
instance links to (device type/module type/device/module). Models that
support cloning (device components etc.) are unaffected and continue to
use clone() as before.

Fixes #23150
This commit is contained in:
breken-ai 2026-09-09 00:35:18 -07:00
parent d2191e0fb3
commit c81429649c
2 changed files with 37 additions and 1 deletions

View File

@ -2425,6 +2425,22 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
model = InterfaceTemplate
validation_excluded_fields = ('name', 'label')
def test_create_object_add_another_preserves_device_type(self):
# Regression test for #23150: "create & add another" must retain the
# parent device type, since component templates don't support cloning
self.add_permissions('dcim.add_interfacetemplate')
device_type = DeviceType.objects.first()
data = dict(self.form_data)
data.update({
'_addanother': True,
'name': 'Interface Template Y',
})
response = self.client.post(self._get_url('add'), data=post_data(data))
self.assertHttpStatus(response, 302)
self.assertIn(f'device_type={device_type.pk}', response.headers['Location'])
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -3901,6 +3917,20 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
model = Interface
validation_excluded_fields = ('name', 'label')
def test_bulk_create_add_another_preserves_device(self):
# Regression test for #23150: "add components" with "create & add another"
# must retain the parent device, since components don't support cloning
self.add_permissions('dcim.add_interface')
device = Device.objects.first()
response = self.client.post(
self._get_url('add'),
data=post_data(dict(self.bulk_create_data, _addanother=True)),
)
self.assertHttpStatus(response, 302)
self.assertIn(f'device={device.pk}', response.headers['Location'])
@classmethod
def setUpTestData(cls):
device = create_test_device('Device 1')

View File

@ -49,7 +49,13 @@ def prepare_cloned_fields(instance):
"""
# Generate the clone attributes from the instance
if not issubclass(type(instance), CloningMixin):
return QueryDict(mutable=True)
# The model doesn't support cloning (e.g. device components & templates), but we
# can still pre-select its parent object when the user clicks "create & add another"
params = []
for field_name in ('device_type', 'module_type', 'device', 'module'):
if value := getattr(instance, f'{field_name}_id', None):
params.append((field_name, value))
return QueryDict(urlencode(params), mutable=True)
attrs = instance.clone()
# Prepare QueryDict parameters