Repository: netbox-community/netbox

Issue:
Unable to filter Sites by multiple Regions
### Environment
* Python version:  3.5.3
* NetBox version:  2.5.3

### Steps to Reproduce
1. Make sure that you have entered Sites for multiple regions like for example Africa, America, Asia, Australia, and Europe.
2. In the Sites view Search/Filet box, select multiple (two or more) Regions and click Apply.

<!-- What did you expect to happen? -->
### Expected Behavior
You would expect NetBox to be able to show all Sites in the selected Regions, in the same way as if you select to filter Sites by e.g. multiple Tenants.

<!-- What happened instead? -->
### Observed Behavior
The resulting list shows only Sites for the alphabetically last one of the Regions selected in the filter box.



Discussion:
I can duplicate this on v2.5.3.  Can confirm it is the last one as well.

Resolving patch:
diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py
--- a/netbox/dcim/filters.py
+++ b/netbox/dcim/filters.py
@@ -8,7 +8,9 @@
 from extras.filters import CustomFieldFilterSet
 from tenancy.models import Tenant
 from utilities.constants import COLOR_CHOICES
-from utilities.filters import NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter
+from utilities.filters import (
+    NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
+)
 from virtualization.models import Cluster
 from .constants import *
 from .models import (
@@ -49,14 +51,15 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
         choices=SITE_STATUS_CHOICES,
         null_value=None
     )
-    region_id = django_filters.NumberFilter(
-        method='filter_region',
-        field_name='pk',
+    region_id = TreeNodeMultipleChoiceFilter(
+        queryset=Region.objects.all(),
+        field_name='region__in',
         label='Region (ID)',
     )
-    region = django_filters.CharFilter(
-        method='filter_region',
-        field_name='slug',
+    region = TreeNodeMultipleChoiceFilter(
+        queryset=Region.objects.all(),
+        field_name='region__in',
+        to_field_name='slug',
         label='Region (slug)',
     )
     tenant_id = django_filters.ModelMultipleChoiceFilter(
@@ -95,16 +98,6 @@ def search(self, queryset, name, value):
             pass
         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(NameSlugSearchFilterSet):
     site_id = django_filters.ModelMultipleChoiceFilter(
@@ -513,14 +506,15 @@ class DeviceFilter(CustomFieldFilterSet):
     )
     name = NullableCharFieldFilter()
     asset_tag = NullableCharFieldFilter()
-    region_id = django_filters.NumberFilter(
-        method='filter_region',
-        field_name='pk',
+    region_id = TreeNodeMultipleChoiceFilter(
+        queryset=Region.objects.all(),
+        field_name='region__in',
         label='Region (ID)',
     )
-    region = django_filters.CharFilter(
-        method='filter_region',
-        field_name='slug',
+    region = TreeNodeMultipleChoiceFilter(
+        queryset=Region.objects.all(),
+        field_name='region__in',
+        to_field_name='slug',
         label='Region (slug)',
     )
     site_id = django_filters.ModelMultipleChoiceFilter(
diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py
--- a/netbox/utilities/filters.py
+++ b/netbox/utilities/filters.py
@@ -4,6 +4,15 @@
 from taggit.models import Tag
 
 
+class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
+    """
+    Filters for a set of Models, including all descendant models within a Tree.  Example: [<Region: R1>,<Region: R2>]
+    """
+    def filter(self, qs, value):
+        value = [node.get_descendants(include_self=True) for node in value]
+        return super().filter(qs, value)
+
+
 class NumericInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
     """
     Filters for a set of numeric values. Example: id__in=100,200,300


Test patch: