Sfoglia il codice sorgente

Closes #1871: Enable filtering sites by parent region

Jeremy Stretch 7 anni fa
parent
commit
0a820d9c98

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@ v2.5.3 (FUTURE)
 ## Enhancements
 ## Enhancements
 
 
 * [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length
 * [#1630](https://github.com/digitalocean/netbox/issues/1630) - Enable bulk editing of prefix/IP mask length
+* [#1871](https://github.com/digitalocean/netbox/issues/1871) - Enable filtering sites by parent region
 * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors
 * [#2693](https://github.com/digitalocean/netbox/issues/2693) - Additional cable colors
 * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search
 * [#2726](https://github.com/digitalocean/netbox/issues/2726) - Include cables in global search
 
 

+ 16 - 6
netbox/dcim/filters.py

@@ -62,14 +62,14 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
         choices=SITE_STATUS_CHOICES,
         choices=SITE_STATUS_CHOICES,
         null_value=None
         null_value=None
     )
     )
-    region_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Region.objects.all(),
+    region_id = django_filters.NumberFilter(
+        method='filter_region',
+        field_name='pk',
         label='Region (ID)',
         label='Region (ID)',
     )
     )
-    region = django_filters.ModelMultipleChoiceFilter(
-        field_name='region__slug',
-        queryset=Region.objects.all(),
-        to_field_name='slug',
+    region = django_filters.CharFilter(
+        method='filter_region',
+        field_name='slug',
         label='Region (slug)',
         label='Region (slug)',
     )
     )
     tenant_id = django_filters.ModelMultipleChoiceFilter(
     tenant_id = django_filters.ModelMultipleChoiceFilter(
@@ -108,6 +108,16 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
             pass
             pass
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
+    def filter_region(self, queryset, name, value):
+        try:
+            region = Region.objects.get(**{name: value})
+        except ObjectDoesNotExist:
+            return queryset.none()
+        return queryset.filter(
+            Q(region=region) |
+            Q(region__in=region.get_descendants())
+        )
+
 
 
 class RackGroupFilter(django_filters.FilterSet):
 class RackGroupFilter(django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(

+ 2 - 1
netbox/dcim/forms.py

@@ -236,9 +236,10 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False
         required=False
     )
     )
     region = FilterTreeNodeMultipleChoiceField(
     region = FilterTreeNodeMultipleChoiceField(
-        queryset=Region.objects.annotate(filter_count=Count('sites')),
+        queryset=Region.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
         required=False,
         required=False,
+        count_attr='site_count'
     )
     )
     tenant = FilterChoiceField(
     tenant = FilterChoiceField(
         queryset=Tenant.objects.annotate(filter_count=Count('sites')),
         queryset=Tenant.objects.annotate(filter_count=Count('sites')),

+ 7 - 0
netbox/dcim/models.py

@@ -201,6 +201,13 @@ class Region(MPTTModel, ChangeLoggedModel):
             self.parent.name if self.parent else None,
             self.parent.name if self.parent else None,
         )
         )
 
 
+    @property
+    def site_count(self):
+        return Site.objects.filter(
+            Q(region=self) |
+            Q(region__in=self.get_descendants())
+        ).count()
+
 
 
 #
 #
 # Sites
 # Sites

+ 1 - 1
netbox/dcim/views.py

@@ -124,7 +124,7 @@ class BulkDisconnectView(GetReturnURLMixin, View):
 #
 #
 
 
 class RegionListView(ObjectListView):
 class RegionListView(ObjectListView):
-    queryset = Region.objects.annotate(site_count=Count('sites'))
+    queryset = Region.objects.all()
     filter = filters.RegionFilter
     filter = filters.RegionFilter
     filter_form = forms.RegionFilterForm
     filter_form = forms.RegionFilterForm
     table = tables.RegionTable
     table = tables.RegionTable

+ 5 - 3
netbox/utilities/forms.py

@@ -505,8 +505,9 @@ class FilterChoiceIterator(forms.models.ModelChoiceIterator):
 class FilterChoiceFieldMixin(object):
 class FilterChoiceFieldMixin(object):
     iterator = FilterChoiceIterator
     iterator = FilterChoiceIterator
 
 
-    def __init__(self, null_label=None, *args, **kwargs):
+    def __init__(self, null_label=None, count_attr='filter_count', *args, **kwargs):
         self.null_label = null_label
         self.null_label = null_label
+        self.count_attr = count_attr
         if 'required' not in kwargs:
         if 'required' not in kwargs:
             kwargs['required'] = False
             kwargs['required'] = False
         if 'widget' not in kwargs:
         if 'widget' not in kwargs:
@@ -515,8 +516,9 @@ class FilterChoiceFieldMixin(object):
 
 
     def label_from_instance(self, obj):
     def label_from_instance(self, obj):
         label = super().label_from_instance(obj)
         label = super().label_from_instance(obj)
-        if hasattr(obj, 'filter_count'):
-            return '{} ({})'.format(label, obj.filter_count)
+        obj_count = getattr(obj, self.count_attr, None)
+        if obj_count is not None:
+            return '{} ({})'.format(label, obj_count)
         return label
         return label