Procházet zdrojové kódy

Closes #2388: Enable filtering of devices/VMs by region

Jeremy Stretch před 7 roky
rodič
revize
b4998f4b01

+ 1 - 0
CHANGELOG.md

@@ -2,6 +2,7 @@ v2.4.7 (FUTURE)
 
 
 ## Enhancements
 ## Enhancements
 
 
+* [#2388](https://github.com/digitalocean/netbox/issues/2388) - Enable filtering of devices/VMs by region
 * [#2427](https://github.com/digitalocean/netbox/issues/2427) - Allow filtering of interfaces by assigned VLAN or VLAN ID
 * [#2427](https://github.com/digitalocean/netbox/issues/2427) - Allow filtering of interfaces by assigned VLAN or VLAN ID
 * [#2512](https://github.com/digitalocean/netbox/issues/2512) - Add device field to inventory item filter form
 * [#2512](https://github.com/digitalocean/netbox/issues/2512) - Add device field to inventory item filter form
 
 

+ 21 - 0
netbox/dcim/filters.py

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 
 
 import django_filters
 import django_filters
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
+from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
 from netaddr import EUI
 from netaddr import EUI
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
@@ -456,6 +457,16 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
     )
     )
     name = NullableCharFieldFilter()
     name = NullableCharFieldFilter()
     asset_tag = NullableCharFieldFilter()
     asset_tag = NullableCharFieldFilter()
+    region_id = django_filters.NumberFilter(
+        method='filter_region',
+        name='pk',
+        label='Region (ID)',
+    )
+    region = django_filters.CharFilter(
+        method='filter_region',
+        name='slug',
+        label='Region (slug)',
+    )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         label='Site (ID)',
         label='Site (ID)',
@@ -538,6 +549,16 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
             Q(comments__icontains=value)
             Q(comments__icontains=value)
         ).distinct()
         ).distinct()
 
 
+    def filter_region(self, queryset, name, value):
+        try:
+            region = Region.objects.get(**{name: value})
+        except ObjectDoesNotExist:
+            return queryset.none()
+        return queryset.filter(
+            Q(site__region=region) |
+            Q(site__region__in=region.get_descendants())
+        )
+
     def _mac_address(self, queryset, name, value):
     def _mac_address(self, queryset, name, value):
         value = value.strip()
         value = value.strip()
         if not value:
         if not value:

+ 5 - 0
netbox/dcim/forms.py

@@ -1108,6 +1108,11 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
 class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
 class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Device
     model = Device
     q = forms.CharField(required=False, label='Search')
     q = forms.CharField(required=False, label='Search')
+    region = FilterTreeNodeMultipleChoiceField(
+        queryset=Region.objects.all(),
+        to_field_name='slug',
+        required=False,
+    )
     site = FilterChoiceField(
     site = FilterChoiceField(
         queryset=Site.objects.annotate(filter_count=Count('devices')),
         queryset=Site.objects.annotate(filter_count=Count('devices')),
         to_field_name='slug',
         to_field_name='slug',

+ 22 - 1
netbox/virtualization/filters.py

@@ -1,11 +1,12 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 import django_filters
 import django_filters
+from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
 from netaddr import EUI
 from netaddr import EUI
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
 
 
-from dcim.models import DeviceRole, Interface, Platform, Site
+from dcim.models import DeviceRole, Interface, Platform, Region, Site
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.filters import NumericInFilter
 from utilities.filters import NumericInFilter
@@ -116,6 +117,16 @@ class VirtualMachineFilter(CustomFieldFilterSet):
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         label='Cluster (ID)',
         label='Cluster (ID)',
     )
     )
+    region_id = django_filters.NumberFilter(
+        method='filter_region',
+        name='pk',
+        label='Region (ID)',
+    )
+    region = django_filters.CharFilter(
+        method='filter_region',
+        name='slug',
+        label='Region (slug)',
+    )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         name='cluster__site',
         name='cluster__site',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -173,6 +184,16 @@ class VirtualMachineFilter(CustomFieldFilterSet):
             Q(comments__icontains=value)
             Q(comments__icontains=value)
         )
         )
 
 
+    def filter_region(self, queryset, name, value):
+        try:
+            region = Region.objects.get(**{name: value})
+        except ObjectDoesNotExist:
+            return queryset.none()
+        return queryset.filter(
+            Q(cluster__site__region=region) |
+            Q(cluster__site__region__in=region.get_descendants())
+        )
+
 
 
 class InterfaceFilter(django_filters.FilterSet):
 class InterfaceFilter(django_filters.FilterSet):
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
     virtual_machine_id = django_filters.ModelMultipleChoiceFilter(

+ 7 - 2
netbox/virtualization/forms.py

@@ -16,8 +16,8 @@ from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
     AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
-    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea,
-    add_blank_choice
+    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField,
+    JSONField, SlugField, SmallTextarea, add_blank_choice,
 )
 )
 from .constants import VM_STATUS_CHOICES
 from .constants import VM_STATUS_CHOICES
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@@ -386,6 +386,11 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
         queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
         label='Cluster'
         label='Cluster'
     )
     )
+    region = FilterTreeNodeMultipleChoiceField(
+        queryset=Region.objects.all(),
+        to_field_name='slug',
+        required=False,
+    )
     site = FilterChoiceField(
     site = FilterChoiceField(
         queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
         queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
         to_field_name='slug',
         to_field_name='slug',