Kaynağa Gözat

Merge pull request #9904 from netbox-community/9888-site_device_filters

Fixes: #9888 - Add filter and columns for device and site
Jeremy Stretch 3 yıl önce
ebeveyn
işleme
44850feaf8

+ 68 - 15
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',
-        label='Device (name)',
+    region = MultiValueCharFilter(
+        method='filter_region',
+        field_name='slug',
+        label='Region (slug)',
     )
-    device_id = MultiValueNumberFilter(
-        method='filter_device',
+    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 = 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

+ 47 - 4
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')
     )

+ 15 - 0
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

+ 10 - 1
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')

+ 21 - 0
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)