diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 6a94a691b..54cc4dd87 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -13,7 +13,7 @@ from tenancy.models import Tenant from users.models import User from utilities.forms import BulkEditForm, add_blank_choice, form_from_model from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField -from utilities.forms.rendering import FieldSet, InlineFields +from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions from wireless.models import WirelessLAN, WirelessLANGroup from wireless.choices import WirelessRoleChoices @@ -1536,7 +1536,13 @@ class InterfaceBulkEditForm( FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')), FieldSet('poe_mode', 'poe_type', name=_('PoE')), FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')), - FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'add_tagged_vlans', 'remove_tagged_vlans', name=_('802.1Q Switching')), + FieldSet('mode', 'vlan_group', 'untagged_vlan', name=_('802.1Q Switching')), + FieldSet( + TabbedGroups( + FieldSet('tagged_vlans', name=_('Assignment')), + FieldSet('add_tagged_vlans', 'remove_tagged_vlans', name=_('Add/Remove')), + ), + ), FieldSet( 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans', name=_('Wireless') diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 66308677d..f390be89b 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2616,7 +2616,9 @@ class InterfaceBulkEditView(generic.BulkEditView): table = tables.InterfaceTable form = forms.InterfaceBulkEditForm - def extra_object_field_operations(self, form, obj): + def post_save_operations(self, form, obj): + super().post_save_operations(form, obj) + # Add/remove tagged VLANs if obj.mode == InterfaceModeChoices.MODE_TAGGED: if form.cleaned_data.get('add_tagged_vlans', None): diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 33c8afe42..4b2f1ae28 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -541,12 +541,16 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'change') - def extra_object_field_operations(self, form, obj): + def post_save_operations(self, form, obj): """ This method is called for each object in _update_objects. Override to perform additional object-level operations that are specific to a particular ModelForm. """ - pass + # Add/remove tags + if form.cleaned_data.get('add_tags', None): + obj.tags.add(*form.cleaned_data['add_tags']) + if form.cleaned_data.get('remove_tags', None): + obj.tags.remove(*form.cleaned_data['remove_tags']) def _update_objects(self, form, request): custom_fields = getattr(form, 'custom_fields', {}) @@ -619,13 +623,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): elif form.cleaned_data[name]: getattr(obj, name).set(form.cleaned_data[name]) - # Add/remove tags - if form.cleaned_data.get('add_tags', None): - obj.tags.add(*form.cleaned_data['add_tags']) - if form.cleaned_data.get('remove_tags', None): - obj.tags.remove(*form.cleaned_data['remove_tags']) - - self.extra_object_field_operations(form, obj) + self.post_save_operations(form, obj) # Rebuild the tree for MPTT models if issubclass(self.queryset.model, MPTTModel):