diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index edd1867ed..49ec15fc1 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -980,21 +980,65 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet): to_field_name='slug', label='L2VPN (slug)', ) - device = MultiValueCharFilter( - method='filter_device', - field_name='name', + region = MultiValueCharFilter( + method='filter_region', + field_name='slug', + label='Region (slug)', + ) + region_id = MultiValueNumberFilter( + method='filter_region', + field_name='pk', + label='Region (ID)', + ) + site = MultiValueCharFilter( + method='filter_site', + field_name='slug', + label='Site (slug)', + ) + site_id = MultiValueNumberFilter( + method='filter_site', + field_name='pk', + label='Site (ID)', + ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='interface__device__name', + queryset=Device.objects.all(), + to_field_name='name', label='Device (name)', ) - device_id = MultiValueNumberFilter( - method='filter_device', - field_name='pk', + device_id = django_filters.ModelMultipleChoiceFilter( + field_name='interface__device', + queryset=Device.objects.all(), label='Device (ID)', ) + virtual_machine = django_filters.ModelMultipleChoiceFilter( + field_name='vminterface__virtual_machine__name', + queryset=VirtualMachine.objects.all(), + to_field_name='name', + label='Virtual machine (name)', + ) + virtual_machine_id = django_filters.ModelMultipleChoiceFilter( + field_name='vminterface__virtual_machine', + queryset=VirtualMachine.objects.all(), + label='Virtual machine (ID)', + ) + interface = django_filters.ModelMultipleChoiceFilter( + field_name='interface__name', + queryset=Interface.objects.all(), + to_field_name='name', + label='Interface (name)', + ) interface_id = django_filters.ModelMultipleChoiceFilter( field_name='interface', queryset=Interface.objects.all(), label='Interface (ID)', ) + vminterface = django_filters.ModelMultipleChoiceFilter( + field_name='vminterface__name', + queryset=VMInterface.objects.all(), + to_field_name='name', + label='VM interface (name)', + ) vminterface_id = django_filters.ModelMultipleChoiceFilter( field_name='vminterface', queryset=VMInterface.objects.all(), @@ -1027,13 +1071,22 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet): qs_filter = Q(l2vpn__name__icontains=value) return queryset.filter(qs_filter) - def filter_device(self, queryset, name, value): - devices = Device.objects.filter(**{'{}__in'.format(name): value}) - if not devices.exists(): - return queryset.none() - interface_ids = [] - for device in devices: - interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) - return queryset.filter( - interface__in=interface_ids + def filter_site(self, queryset, name, value): + qs = queryset.filter( + Q( + Q(**{'vlan__site__{}__in'.format(name): value}) | + Q(**{'interface__device__site__{}__in'.format(name): value}) | + Q(**{'vminterface__virtual_machine__site__{}__in'.format(name): value}) + ) ) + return qs + + def filter_region(self, queryset, name, value): + qs = queryset.filter( + Q( + Q(**{'vlan__site__region__{}__in'.format(name): value}) | + Q(**{'interface__device__site__region__{}__in'.format(name): value}) | + Q(**{'vminterface__virtual_machine__site__region__{}__in'.format(name): value}) + ) + ) + return qs diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 384a4da33..ecf63b49f 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -11,7 +11,7 @@ from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import TenancyFilterForm from utilities.forms import ( add_blank_choice, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, - MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, + MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, APISelectMultiple, ) from virtualization.models import VirtualMachine @@ -508,7 +508,8 @@ class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm): model = L2VPNTermination fieldsets = ( - (None, ('l2vpn_id', 'assigned_object_type_id')), + (None, ('l2vpn_id', )), + ('Assigned Object', ('assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id')), ) l2vpn_id = DynamicModelChoiceField( queryset=L2VPN.objects.all(), @@ -516,7 +517,49 @@ class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm): label='L2VPN' ) assigned_object_type_id = ContentTypeMultipleChoiceField( - queryset=ContentType.objects.all(), + queryset=ContentType.objects.filter(L2VPN_ASSIGNMENT_MODELS), required=False, - label='Object type' + label=_('Assigned Object Type'), + limit_choices_to=L2VPN_ASSIGNMENT_MODELS + ) + region_id = DynamicModelMultipleChoiceField( + queryset=Region.objects.all(), + required=False, + label=_('Region') + ) + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), + required=False, + null_option='None', + query_params={ + 'region_id': '$region_id' + }, + label=_('Site') + ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + null_option='None', + query_params={ + 'site_id': '$site_id' + }, + label=_('Device') + ) + vlan_id = DynamicModelMultipleChoiceField( + queryset=VLAN.objects.all(), + required=False, + null_option='None', + query_params={ + 'site_id': '$site_id' + }, + label=_('VLAN') + ) + virtual_machine_id = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + null_option='None', + query_params={ + 'site_id': '$site_id' + }, + label=_('Virtual Machine') ) diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index 5d85fe915..5adf5e05d 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -113,3 +113,18 @@ class L2VPNTermination(NetBoxModel): f'{l2vpn_type} L2VPNs cannot have more than two terminations; found {terminations_count} already ' f'defined.' ) + + @property + def assigned_object_parent(self): + obj_type = ContentType.objects.get_for_model(self.assigned_object) + if obj_type.model == 'vminterface': + return self.assigned_object.virtual_machine + elif obj_type.model == 'interface': + return self.assigned_object.device + elif obj_type.model == 'vminterface': + return self.assigned_object.virtual_machine + return None + + @property + def assigned_object_site(self): + return self.assigned_object_parent.site diff --git a/netbox/ipam/tables/l2vpn.py b/netbox/ipam/tables/l2vpn.py index 5be525343..e2eae7a32 100644 --- a/netbox/ipam/tables/l2vpn.py +++ b/netbox/ipam/tables/l2vpn.py @@ -53,8 +53,17 @@ class L2VPNTerminationTable(NetBoxTable): linkify=True, orderable=False ) + assigned_object_parent = tables.Column( + linkify=True, + orderable=False + ) + assigned_object_site = tables.Column( + linkify=True, + orderable=False + ) class Meta(NetBoxTable.Meta): model = L2VPNTermination - fields = ('pk', 'l2vpn', 'assigned_object_type', 'assigned_object', 'actions') + fields = ('pk', 'l2vpn', 'assigned_object_type', 'assigned_object', 'assigned_object_parent', + 'assigned_object_site', 'actions') default_columns = ('pk', 'l2vpn', 'assigned_object_type', 'assigned_object', 'actions') diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 9106a4965..081f6e11d 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -1600,3 +1600,24 @@ class L2VPNTerminationTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'vlan': ['VLAN 1', 'VLAN 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_site(self): + site = Site.objects.all().first() + params = {'site_id': [site.pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + params = {'site': ['site-1']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + + def test_device(self): + device = Device.objects.all().first() + params = {'device_id': [device.pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + params = {'device': ['Device 1']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + + def test_virtual_machine(self): + virtual_machine = VirtualMachine.objects.all().first() + params = {'virtual_machine_id': [virtual_machine.pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + params = {'virtual_machine': ['Virtual Machine 1']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)