Просмотр исходного кода

feat(dcim): Add changelog message support to bulk component creation (#21769)

Add ChangelogMessageMixin to DeviceBulkAddComponentForm and capture
changelog_message during bulk component creation. Ensure message is
applied to each created component instance. Add test coverage for
changelog message propagation.
Martin Hauser 1 день назад
Родитель
Сommit
c7504628bd

+ 2 - 2
netbox/dcim/forms/bulk_create.py

@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
 
 from dcim.models import *
 from extras.models import Tag
-from netbox.forms.mixins import CustomFieldsMixin
+from netbox.forms.mixins import ChangelogMessageMixin, CustomFieldsMixin
 from utilities.forms import form_from_model
 from utilities.forms.fields import DynamicModelMultipleChoiceField, ExpandableNameField
 from utilities.forms.mixins import BackgroundJobMixin
@@ -28,7 +28,7 @@ __all__ = (
 # Device components
 #
 
-class DeviceBulkAddComponentForm(BackgroundJobMixin, CustomFieldsMixin, ComponentCreateForm):
+class DeviceBulkAddComponentForm(BackgroundJobMixin, ChangelogMessageMixin, CustomFieldsMixin, ComponentCreateForm):
     pk = forms.ModelMultipleChoiceField(
         queryset=Device.objects.all(),
         widget=forms.MultipleHiddenInput()

+ 47 - 1
netbox/dcim/tests/test_views.py

@@ -3,11 +3,13 @@ from decimal import Decimal
 from zoneinfo import ZoneInfo
 
 import yaml
+from django.contrib.contenttypes.models import ContentType
 from django.test import override_settings, tag
 from django.urls import reverse
 from netaddr import EUI
 
-from core.models import ObjectType
+from core.choices import ObjectChangeActionChoices
+from core.models import ObjectChange, ObjectType
 from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
@@ -2741,6 +2743,50 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
             f"{console_ports[2].pk},Console Port 9,New description9",
         )
 
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
+    def test_bulk_add_components_with_changelog_message(self):
+        device1 = Device.objects.get(name='Device 1')
+        device2 = create_test_device('Device 2')
+        changelog_message = 'Bulk-created console ports'
+
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add'],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
+
+        request = {
+            'path': reverse('dcim:device_bulk_add_consoleport'),
+            'data': post_data({
+                'pk': [device1.pk, device2.pk],
+                'name': 'Console Port Bulk',
+                'type': ConsolePortTypeChoices.TYPE_RJ45,
+                'description': 'Bulk-created console port',
+                'changelog_message': changelog_message,
+                '_create': True,
+            }),
+        }
+
+        initial_count = self._get_queryset().count()
+        response = self.client.post(**request)
+        self.assertHttpStatus(response, 302)
+        self.assertEqual(initial_count + 2, self._get_queryset().count())
+
+        created_ports = list(ConsolePort.objects.filter(name='Console Port Bulk').order_by('device_id'))
+        self.assertEqual(len(created_ports), 2)
+        self.assertEqual([port.device_id for port in created_ports], [device1.pk, device2.pk])
+
+        objectchanges = ObjectChange.objects.filter(
+            action=ObjectChangeActionChoices.ACTION_CREATE,
+            changed_object_type=ContentType.objects.get_for_model(ConsolePort),
+            changed_object_id__in=[port.pk for port in created_ports],
+        )
+        self.assertEqual(objectchanges.count(), 2)
+        for objectchange in objectchanges:
+            self.assertEqual(objectchange.message, changelog_message)
+
     @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
     def test_trace(self):
         consoleport = ConsolePort.objects.first()

+ 3 - 0
netbox/netbox/views/generic/bulk_views.py

@@ -1148,6 +1148,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
 
                 new_components = []
                 data = deepcopy(form.cleaned_data)
+                changelog_message = data.pop('changelog_message', '')
                 data.pop('background_job', None)
                 replication_data = {
                     field: data.pop(field) for field in form.replication_fields
@@ -1170,6 +1171,8 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
 
                                 component_form = self.model_form(component_data)
                                 if component_form.is_valid():
+                                    if changelog_message:
+                                        component_form.instance._changelog_message = changelog_message
                                     instance = component_form.save()
                                     logger.debug(f"Created {instance} on {instance.parent_object}")
                                     new_components.append(instance)

+ 10 - 4
netbox/templates/generic/bulk_add_component.html

@@ -64,11 +64,17 @@ Context:
               {% endfor %}
             </div>
           </div>
+
           {# Meta fields #}
-          {% if form.background_job %}
-          <div class="bg-primary-subtle border border-primary rounded-1 pt-3 px-3 mb-3">
-            {% render_field form.background_job %}
-          </div>
+          {% if form.background_job or form.changelog_message %}
+            <div class="bg-primary-subtle border border-primary rounded-1 pt-3 px-3 mb-3">
+              {% if form.changelog_message %}
+                {% render_field form.changelog_message %}
+              {% endif %}
+              {% if form.background_job %}
+                {% render_field form.background_job %}
+              {% endif %}
+            </div>
           {% endif %}
           <div class="form-group text-end">
             <div class="col col-md-12">