From 026add9c334e70e5a26adf27242c9d60406f9975 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Thu, 18 Dec 2025 11:20:39 +0100 Subject: [PATCH] feat(object_actions): Include parent PK in BulkEdit context Enhances `BulkEdit.get_context()` to include the parent object's primary key in the URL parameters for child objects. This allows proper association between parent-child objects during bulk edits. Fixes #20320 --- netbox/netbox/object_actions.py | 16 ++++++++++ netbox/netbox/tests/test_object_actions.py | 37 +++++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/netbox/netbox/object_actions.py b/netbox/netbox/object_actions.py index 38e48464aa4..688082f134a 100644 --- a/netbox/netbox/object_actions.py +++ b/netbox/netbox/object_actions.py @@ -1,3 +1,4 @@ +from django.db.models import ForeignKey from django.template import loader from django.urls.exceptions import NoReverseMatch from django.utils.translation import gettext_lazy as _ @@ -175,6 +176,21 @@ class BulkEdit(ObjectAction): permissions_required = {'change'} template_name = 'buttons/bulk_edit.html' + @classmethod + def get_context(cls, context, model): + url_params = super().get_url_params(context) + + # If this is a child object, pass the parent's PK as a URL parameter + if parent := context.get('object'): + for field in model._meta.get_fields(): + if isinstance(field, ForeignKey) and field.remote_field.model == parent.__class__: + url_params[field.name] = parent.pk + break + + return { + 'url_params': url_params, + } + class BulkRename(ObjectAction): """ diff --git a/netbox/netbox/tests/test_object_actions.py b/netbox/netbox/tests/test_object_actions.py index 7e3b16bf108..fc195d4e5eb 100644 --- a/netbox/netbox/tests/test_object_actions.py +++ b/netbox/netbox/tests/test_object_actions.py @@ -1,11 +1,10 @@ from unittest import skipIf from django.conf import settings -from django.test import TestCase +from django.test import RequestFactory, TestCase -from dcim.models import Device -from netbox.object_actions import AddObject, BulkImport -from netbox.tests.dummy_plugin.models import DummyNetBoxModel +from dcim.models import Device, DeviceType, Manufacturer +from netbox.object_actions import AddObject, BulkEdit, BulkImport class ObjectActionTest(TestCase): @@ -20,9 +19,11 @@ def test_get_url_core_model(self): url = BulkImport.get_url(obj) self.assertEqual(url, '/dcim/devices/import/') - @skipIf('netbox.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS") + @skipIf('netbox.tests.dummy_plugin' not in settings.PLUGINS, 'dummy_plugin not in settings.PLUGINS') def test_get_url_plugin_model(self): """Test URL generation for plugin models includes plugins: namespace""" + from netbox.tests.dummy_plugin.models import DummyNetBoxModel + obj = DummyNetBoxModel() url = AddObject.get_url(obj) @@ -30,3 +31,29 @@ def test_get_url_plugin_model(self): url = BulkImport.get_url(obj) self.assertEqual(url, '/plugins/dummy-plugin/netboxmodel/import/') + + def test_bulk_edit_get_context_child_object(self): + """ + Test that the parent object's PK is included in the context for child objects. + + Ensure that BulkEdit.get_context() correctly identifies and + includes the parent object's PK when rendering a child object's + action button. + """ + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + + # Mock context containing the parent object (DeviceType) + request = RequestFactory().get('/') + context = { + 'request': request, + 'object': device_type, + } + + # Get context for the child model (Device) + action_context = BulkEdit.get_context(context, Device) + + # Verify that 'device_type' (the FK field name) is present in + # url_params with the parent's PK + self.assertIn('url_params', action_context) + self.assertEqual(action_context['url_params'].get('device_type'), device_type.pk)