Closes #1871: Enable filtering sites by parent region

This commit is contained in:
Jeremy Stretch 2019-01-03 16:59:49 -05:00
parent 209a9f0ffc
commit 0a820d9c98
6 changed files with 32 additions and 11 deletions

View File

@ -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

View File

@ -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( region_id = django_filters.NumberFilter(
queryset=Region.objects.all(), method='filter_region',
field_name='pk',
label='Region (ID)', label='Region (ID)',
) )
region = django_filters.ModelMultipleChoiceFilter( region = django_filters.CharFilter(
field_name='region__slug', method='filter_region',
queryset=Region.objects.all(), field_name='slug',
to_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(

View File

@ -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')),

View File

@ -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

View File

@ -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

View File

@ -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'): obj_count = getattr(obj, self.count_attr, None)
return '{} ({})'.format(label, obj.filter_count) if obj_count is not None:
return '{} ({})'.format(label, obj_count)
return label return label