diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 96036f4da..6a94a691b 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1404,18 +1404,25 @@ class InterfaceBulkEditForm( parent = DynamicModelChoiceField( label=_('Parent'), queryset=Interface.objects.all(), - required=False + required=False, + query_params={ + 'virtual_chassis_member_id': '$device', + } ) bridge = DynamicModelChoiceField( label=_('Bridge'), queryset=Interface.objects.all(), - required=False + required=False, + query_params={ + 'virtual_chassis_member_id': '$device', + } ) lag = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, query_params={ 'type': 'lag', + 'virtual_chassis_member_id': '$device', }, label=_('LAG') ) @@ -1472,6 +1479,7 @@ class InterfaceBulkEditForm( required=False, query_params={ 'group_id': '$vlan_group', + 'available_on_device': '$device', }, label=_('Untagged VLAN') ) @@ -1480,9 +1488,28 @@ class InterfaceBulkEditForm( required=False, query_params={ 'group_id': '$vlan_group', + 'available_on_device': '$device', }, label=_('Tagged VLANs') ) + add_tagged_vlans = DynamicModelMultipleChoiceField( + label=_('Add tagged VLANs'), + queryset=VLAN.objects.all(), + required=False, + query_params={ + 'group_id': '$vlan_group', + 'available_on_device': '$device', + }, + ) + remove_tagged_vlans = DynamicModelMultipleChoiceField( + label=_('Remove tagged VLANs'), + queryset=VLAN.objects.all(), + required=False, + query_params={ + 'group_id': '$vlan_group', + 'available_on_device': '$device', + } + ) vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -1509,7 +1536,7 @@ 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', name=_('802.1Q Switching')), + FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'add_tagged_vlans', 'remove_tagged_vlans', name=_('802.1Q Switching')), FieldSet( 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans', name=_('Wireless') @@ -1523,19 +1550,7 @@ class InterfaceBulkEditForm( def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.device_id: - device = Device.objects.filter(pk=self.device_id).first() - - # Restrict parent/bridge/LAG interface assignment by device - self.fields['parent'].widget.add_query_param('virtual_chassis_member_id', device.pk) - self.fields['bridge'].widget.add_query_param('virtual_chassis_member_id', device.pk) - self.fields['lag'].widget.add_query_param('virtual_chassis_member_id', device.pk) - - # Limit VLAN choices by device - self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk) - self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk) - - else: + if not self.device_id: # See #4523 if 'pk' in self.initial: site = None @@ -1559,6 +1574,13 @@ class InterfaceBulkEditForm( 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] ) + self.fields['add_tagged_vlans'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) + self.fields['remove_tagged_vlans'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) + self.fields['parent'].choices = () self.fields['parent'].widget.attrs['disabled'] = True self.fields['bridge'].choices = () diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index d7d28b95f..c3478e316 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -615,6 +615,12 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): if form.cleaned_data.get('remove_tags', None): obj.tags.remove(*form.cleaned_data['remove_tags']) + # Add/remove tagged VLANs + if form.cleaned_data.get('add_tagged_vlans', None): + obj.tagged_vlans.add(*form.cleaned_data['add_tagged_vlans']) + if form.cleaned_data.get('remove_tagged_vlans', None): + obj.tagged_vlans.remove(*form.cleaned_data['remove_tagged_vlans']) + # Rebuild the tree for MPTT models if issubclass(self.queryset.model, MPTTModel): self.queryset.model.objects.rebuild()