Added 'none' options to filters for optional fields

This commit is contained in:
Jeremy Stretch 2016-09-15 17:12:53 -04:00
parent daadf7a49b
commit 9dea5656ad
10 changed files with 142 additions and 79 deletions

View File

@ -5,6 +5,8 @@ from django.db.models import Q
from dcim.models import Site from dcim.models import 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 NullableModelMultipleChoiceFilter
from .models import Provider, Circuit, CircuitType from .models import Provider, Circuit, CircuitType
@ -64,12 +66,12 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Circuit type (slug)', label='Circuit type (slug)',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -187,5 +187,6 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit model = Circuit
type = FilterChoiceField(choices=get_filter_choices(CircuitType, id_field='slug', count_field='circuits')) type = FilterChoiceField(choices=get_filter_choices(CircuitType, id_field='slug', count_field='circuits'))
provider = FilterChoiceField(choices=get_filter_choices(Provider, id_field='slug', count_field='circuits')) provider = FilterChoiceField(choices=get_filter_choices(Provider, id_field='slug', count_field='circuits'))
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='circuits')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='circuits',
null_option='None'))
site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='circuits')) site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='circuits'))

View File

@ -4,6 +4,7 @@ from django.db.models import Q
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter
from .models import ( from .models import (
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer, ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
@ -15,12 +16,12 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
action='search', action='search',
label='Search', label='Search',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -75,34 +76,34 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = django_filters.ModelMultipleChoiceFilter( group = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label='Group',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = django_filters.ModelMultipleChoiceFilter( role = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -177,12 +178,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -210,12 +211,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Device model (slug)', label='Device model (slug)',
) )
platform_id = django_filters.ModelMultipleChoiceFilter( platform_id = NullableModelMultipleChoiceFilter(
name='platform', name='platform',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label='Platform (ID)',
) )
platform = django_filters.ModelMultipleChoiceFilter( platform = NullableModelMultipleChoiceFilter(
name='platform', name='platform',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -120,7 +120,8 @@ class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm): class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Site model = Site
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='sites')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='sites',
null_option='None'))
# #
@ -246,10 +247,13 @@ class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
class RackFilterForm(BootstrapMixin, CustomFieldFilterForm): class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Rack model = Rack
site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='racks')) site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='racks'))
group_id = FilterChoiceField(choices=get_filter_choices(RackGroup, select_related=['site'], count_field='racks'), group_id = FilterChoiceField(choices=get_filter_choices(RackGroup, select_related=['site'], count_field='racks',
null_option='None'),
label='Rack Group') label='Rack Group')
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='racks')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='racks',
role = FilterChoiceField(choices=get_filter_choices(RackRole, id_field='slug', count_field='racks')) null_option='None'))
role = FilterChoiceField(choices=get_filter_choices(RackRole, id_field='slug', count_field='racks',
null_option='None'))
# #
@ -595,11 +599,13 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
count_field='racks__devices'), count_field='racks__devices'),
label='Rack Group') label='Rack Group')
role = FilterChoiceField(choices=get_filter_choices(DeviceRole, id_field='slug', count_field='devices')) role = FilterChoiceField(choices=get_filter_choices(DeviceRole, id_field='slug', count_field='devices'))
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='devices')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='devices',
null_option='None'))
device_type_id = FilterChoiceField(choices=get_filter_choices(DeviceType, select_related=['manufacturer'], device_type_id = FilterChoiceField(choices=get_filter_choices(DeviceType, select_related=['manufacturer'],
count_field='instances'), count_field='instances'),
label='Type') label='Type')
platform = FilterChoiceField(choices=get_filter_choices(Platform, id_field='slug', count_field='devices')) platform = FilterChoiceField(choices=get_filter_choices(Platform, id_field='slug', count_field='devices',
null_option='None'))
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES)) status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))

View File

@ -7,6 +7,7 @@ from django.db.models import Q
from dcim.models import Site, Device, Interface from dcim.models import Site, Device, Interface
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter
from .models import RIR, Aggregate, VRF, Prefix, IPAddress, VLAN, VLANGroup, Role from .models import RIR, Aggregate, VRF, Prefix, IPAddress, VLAN, VLANGroup, Role
@ -21,12 +22,12 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
lookup_type='icontains', lookup_type='icontains',
label='Name', label='Name',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -85,29 +86,34 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
action='search_by_parent', action='search_by_parent',
label='Parent prefix', label='Parent prefix',
) )
vrf = django_filters.MethodFilter( vrf = NullableModelMultipleChoiceFilter(
action='_vrf', name='vrf',
queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
# Duplicate of `vrf` for backward-compatibility # Duplicate of `vrf` for backward-compatibility
vrf_id = django_filters.MethodFilter( vrf_id = NullableModelMultipleChoiceFilter(
action='_vrf', name='vrf_id',
queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
tenant_id = django_filters.MethodFilter( tenant_id = NullableModelMultipleChoiceFilter(
action='_tenant_id', name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.MethodFilter( tenant = NullableModelMultipleChoiceFilter(
action='_tenant', name='tenant',
label='Tenant', queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = NullableModelMultipleChoiceFilter(
name='site', name='site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = django_filters.ModelMultipleChoiceFilter( site = NullableModelMultipleChoiceFilter(
name='site', name='site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -122,12 +128,12 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
name='vlan__vid', name='vlan__vid',
label='VLAN number (1-4095)', label='VLAN number (1-4095)',
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = django_filters.ModelMultipleChoiceFilter( role = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -136,7 +142,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
class Meta: class Meta:
model = Prefix model = Prefix
fields = ['family', 'site_id', 'site', 'vrf', 'vrf_id', 'vlan_id', 'vlan_vid', 'status', 'role_id', 'role'] fields = ['family', 'site_id', 'site', 'vlan_id', 'vlan_vid', 'status', 'role_id', 'role']
def search(self, queryset, value): def search(self, queryset, value):
qs_filter = Q(description__icontains=value) qs_filter = Q(description__icontains=value)
@ -157,17 +163,6 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
except AddrFormatError: except AddrFormatError:
return queryset.none() return queryset.none()
def _vrf(self, queryset, value):
if str(value) == '':
return queryset
try:
vrf_id = int(value)
except ValueError:
return queryset.none()
if vrf_id == 0:
return queryset.filter(vrf__isnull=True)
return queryset.filter(vrf__pk=value)
def _tenant(self, queryset, value): def _tenant(self, queryset, value):
if str(value) == '': if str(value) == '':
return queryset return queryset
@ -196,22 +191,27 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
action='search_by_parent', action='search_by_parent',
label='Parent prefix', label='Parent prefix',
) )
vrf = django_filters.MethodFilter( vrf = NullableModelMultipleChoiceFilter(
action='_vrf', name='vrf',
queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
# Duplicate of `vrf` for backward-compatibility # Duplicate of `vrf` for backward-compatibility
vrf_id = django_filters.MethodFilter( vrf_id = NullableModelMultipleChoiceFilter(
action='_vrf', name='vrf_id',
queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
tenant_id = django_filters.MethodFilter( tenant_id = NullableModelMultipleChoiceFilter(
action='_tenant_id', name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.MethodFilter( tenant = NullableModelMultipleChoiceFilter(
action='_tenant', name='tenant',
label='Tenant', queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
) )
device_id = django_filters.ModelMultipleChoiceFilter( device_id = django_filters.ModelMultipleChoiceFilter(
name='interface__device', name='interface__device',
@ -232,7 +232,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
class Meta: class Meta:
model = IPAddress model = IPAddress
fields = ['q', 'family', 'vrf_id', 'vrf', 'device_id', 'device', 'interface_id'] fields = ['q', 'family', 'device_id', 'device', 'interface_id']
def search(self, queryset, value): def search(self, queryset, value):
qs_filter = Q(description__icontains=value) qs_filter = Q(description__icontains=value)
@ -317,12 +317,12 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = django_filters.ModelMultipleChoiceFilter( group = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -337,23 +337,23 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
name='vid', name='vid',
label='VLAN number (1-4095)', label='VLAN number (1-4095)',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = NullableModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = django_filters.ModelMultipleChoiceFilter( role = NullableModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -74,7 +74,8 @@ class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm): class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = VRF model = VRF
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='vrfs')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='vrfs',
null_option='None'))
# #
@ -272,13 +273,16 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
'placeholder': 'Network', 'placeholder': 'Network',
})) }))
family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family') family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
vrf = FilterChoiceField(choices=get_filter_choices(VRF, count_field='prefixes', null_option=(0, 'Global')), vrf = FilterChoiceField(choices=get_filter_choices(VRF, count_field='prefixes', null_option='Global'),
label='VRF') label='VRF')
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='prefixes'), tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='prefixes',
null_option='None'),
label='Tenant') label='Tenant')
status = FilterChoiceField(choices=prefix_status_choices) status = FilterChoiceField(choices=prefix_status_choices)
site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='prefixes')) site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='prefixes',
role = FilterChoiceField(choices=get_filter_choices(Role, id_field='slug', count_field='prefixes')) null_option='None'))
role = FilterChoiceField(choices=get_filter_choices(Role, id_field='slug', count_field='prefixes',
null_option='None'))
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy') expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
@ -415,8 +419,10 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
'placeholder': 'Prefix', 'placeholder': 'Prefix',
})) }))
family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family') family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
vrf = FilterChoiceField(choices=get_filter_choices(VRF, count_field='ip_addresses'), label='VRF') vrf = FilterChoiceField(choices=get_filter_choices(VRF, count_field='ip_addresses', null_option='None'),
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='prefixes'), label='VRF')
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='ip_addresses',
null_option='None'),
label='Tenant') label='Tenant')
@ -521,8 +527,10 @@ def vlan_status_choices():
class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm): class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = VLAN model = VLAN
site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='vlans')) site = FilterChoiceField(choices=get_filter_choices(Site, id_field='slug', count_field='vlans'))
group_id = FilterChoiceField(choices=get_filter_choices(VLANGroup, select_related=['site'], count_field='vlans'), group_id = FilterChoiceField(choices=get_filter_choices(VLANGroup, select_related=['site'], count_field='vlans',
null_option='None'),
label='VLAN Group') label='VLAN Group')
tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='vlans')) tenant = FilterChoiceField(choices=get_filter_choices(Tenant, id_field='slug', count_field='vlans',
null_option='None'))
status = FilterChoiceField(choices=vlan_status_choices) status = FilterChoiceField(choices=vlan_status_choices)
role = FilterChoiceField(choices=get_filter_choices(Role, id_field='slug', count_field='vlans')) role = FilterChoiceField(choices=get_filter_choices(Role, id_field='slug', count_field='vlans', null_option='None'))

View File

@ -3,6 +3,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from utilities.filters import NullableModelMultipleChoiceFilter
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -11,12 +12,12 @@ class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet):
action='search', action='search',
label='Search', label='Search',
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = django_filters.ModelMultipleChoiceFilter( group = NullableModelMultipleChoiceFilter(
name='group', name='group',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -77,4 +77,5 @@ class TenantBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm): class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Tenant model = Tenant
group = FilterChoiceField(choices=get_filter_choices(TenantGroup, id_field='slug', count_field='tenants')) group = FilterChoiceField(choices=get_filter_choices(TenantGroup, id_field='slug', count_field='tenants',
null_option='None'))

View File

@ -0,0 +1,43 @@
import django_filters
from django.db.models import Q
class NullableModelMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
def __init__(self, *args, **kwargs):
# Convert the queryset to a list of choices prefixed with a "None" option
queryset = kwargs.pop('queryset')
self.to_field_name = kwargs.pop('to_field_name', 'pk')
kwargs['choices'] = [(0, 'None')] + [(getattr(o, self.to_field_name), o) for o in queryset]
super(NullableModelMultipleChoiceFilter, self).__init__(*args, **kwargs)
def filter(self, qs, value):
value = value or () # Make sure we have an iterable
if self.is_noop(qs, value):
return qs
# Even though not a noop, no point filtering if empty
if not value:
return qs
q = Q()
for v in set(value):
# Filtering on NULL
if v == str(0):
arg = {'{}__isnull'.format(self.name): True}
# Filtering on a related field (e.g. slug)
elif self.to_field_name != 'pk':
arg = {'{}__{}'.format(self.name, self.to_field_name): v}
# Filtering on primary key
else:
arg = {self.name: v}
if self.conjoined:
qs = self.get_method(qs)(**arg)
else:
q |= Q(**arg)
if self.distinct:
return self.get_method(qs)(q).distinct()
return self.get_method(qs)(q)

View File

@ -43,7 +43,7 @@ def get_filter_choices(model, id_field='pk', select_related=[], count_field=None
:param id_field: Field to use as the object identifier :param id_field: Field to use as the object identifier
:param select_related: Any related tables to include :param select_related: Any related tables to include
:param count_field: The field to use for a child COUNT() (optional) :param count_field: The field to use for a child COUNT() (optional)
:param null_option: A (value, label) tuple to include at the beginning of the list serving as "null" :param null_option: A choice to include at the beginning of the list serving as "null"
""" """
queryset = model.objects.all() queryset = model.objects.all()
if select_related: if select_related:
@ -54,7 +54,7 @@ def get_filter_choices(model, id_field='pk', select_related=[], count_field=None
else: else:
choices = [(getattr(obj, id_field), u'{}'.format(obj)) for obj in queryset] choices = [(getattr(obj, id_field), u'{}'.format(obj)) for obj in queryset]
if null_option: if null_option:
choices = [null_option] + choices choices = [(0, null_option)] + choices
return choices return choices