From b4998f4b01a32b56b59bc3245e985f5f5527a3a4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Nov 2018 10:31:56 -0500 Subject: [PATCH] Closes #2388: Enable filtering of devices/VMs by region --- CHANGELOG.md | 1 + netbox/dcim/filters.py | 21 +++++++++++++++++++++ netbox/dcim/forms.py | 5 +++++ netbox/virtualization/filters.py | 23 ++++++++++++++++++++++- netbox/virtualization/forms.py | 9 +++++++-- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 877a45a1e..7647a4152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ v2.4.7 (FUTURE) ## 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 * [#2512](https://github.com/digitalocean/netbox/issues/2512) - Add device field to inventory item filter form diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 1870df762..689e88a5d 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import django_filters from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from netaddr import EUI from netaddr.core import AddrFormatError @@ -456,6 +457,16 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): ) name = 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( queryset=Site.objects.all(), label='Site (ID)', @@ -538,6 +549,16 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet): Q(comments__icontains=value) ).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): value = value.strip() if not value: diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index b24c979c5..46e039211 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1108,6 +1108,11 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Device q = forms.CharField(required=False, label='Search') + region = FilterTreeNodeMultipleChoiceField( + queryset=Region.objects.all(), + to_field_name='slug', + required=False, + ) site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('devices')), to_field_name='slug', diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 6af4e4a22..99df19aee 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -1,11 +1,12 @@ from __future__ import unicode_literals import django_filters +from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from netaddr import EUI 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 tenancy.models import Tenant from utilities.filters import NumericInFilter @@ -116,6 +117,16 @@ class VirtualMachineFilter(CustomFieldFilterSet): queryset=Cluster.objects.all(), 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( name='cluster__site', queryset=Site.objects.all(), @@ -173,6 +184,16 @@ class VirtualMachineFilter(CustomFieldFilterSet): 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): virtual_machine_id = django_filters.ModelMultipleChoiceFilter( diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 8f973955c..6d11ed78a 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -16,8 +16,8 @@ from tenancy.models import Tenant from utilities.forms import ( AnnotatedMultipleChoiceField, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, 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 .models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -386,6 +386,11 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm): queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')), label='Cluster' ) + region = FilterTreeNodeMultipleChoiceField( + queryset=Region.objects.all(), + to_field_name='slug', + required=False, + ) site = FilterChoiceField( queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')), to_field_name='slug',