Fixes #1649: Correct fitlering on null values (e.g. ?tenant_id=0) for django-filters v1.1.0+

This commit is contained in:
Jeremy Stretch 2017-10-30 17:20:22 -04:00
parent a5b7c057eb
commit 4668149943
8 changed files with 70 additions and 115 deletions

View File

@ -7,7 +7,7 @@ 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, NumericInFilter from utilities.filters import NumericInFilter
from .models import Provider, Circuit, CircuitTermination, CircuitType from .models import Provider, Circuit, CircuitTermination, CircuitType
@ -78,11 +78,11 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Circuit type (slug)', label='Circuit type (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -9,7 +9,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 NullableCharFieldFilter, NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NullableCharFieldFilter, NumericInFilter
from virtualization.models import Cluster from virtualization.models import Cluster
from .models import ( from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@ -21,11 +21,11 @@ from .models import (
class RegionFilter(django_filters.FilterSet): class RegionFilter(django_filters.FilterSet):
parent_id = NullableModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Parent region (ID)', label='Parent region (ID)',
) )
parent = NullableModelMultipleChoiceFilter( parent = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent region (slug)', label='Parent region (slug)',
@ -42,20 +42,20 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
region_id = NullableModelMultipleChoiceFilter( region_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Region (ID)', label='Region (ID)',
) )
region = NullableModelMultipleChoiceFilter( region = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
@ -126,31 +126,31 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
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 = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
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 = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role',
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -193,12 +193,12 @@ class RackReservationFilter(django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group', name='rack__group',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='rack__group', name='rack__group',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -368,21 +368,21 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
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)',
) )
platform_id = NullableModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label='Platform (ID)',
) )
platform = NullableModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
name='platform', name='platform',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -405,12 +405,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Rack group (ID)', label='Rack group (ID)',
) )
rack_id = NullableModelMultipleChoiceFilter( rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack', name='rack',
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
label='Rack (ID)', label='Rack (ID)',
) )
cluster_id = NullableModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='VM cluster (ID)', label='VM cluster (ID)',
) )
@ -595,7 +595,7 @@ class DeviceBayFilter(DeviceComponentFilterSet):
class InventoryItemFilter(DeviceComponentFilterSet): class InventoryItemFilter(DeviceComponentFilterSet):
parent_id = NullableModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(), queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)', label='Parent inventory item (ID)',
) )

View File

@ -9,7 +9,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, NumericInFilter from utilities.filters import NumericInFilter
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .models import ( from .models import (
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
@ -23,11 +23,11 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -110,37 +110,37 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label='Mask length',
) )
vrf_id = NullableModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
vrf = NullableModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
name='vrf', name='vrf',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label='VRF (RD)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
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)',
) )
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
vlan_id = NullableModelMultipleChoiceFilter( vlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
label='VLAN (ID)', label='VLAN (ID)',
) )
@ -148,11 +148,11 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
name='vlan__vid', name='vlan__vid',
label='VLAN number (1-4095)', label='VLAN number (1-4095)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -207,21 +207,21 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label='Mask length',
) )
vrf_id = NullableModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
vrf = NullableModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
name='vrf', name='vrf',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label='VRF (RD)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -290,11 +290,11 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
class VLANGroupFilter(django_filters.FilterSet): class VLANGroupFilter(django_filters.FilterSet):
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -312,41 +312,41 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group', name='group',
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label='Group',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
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 = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -206,6 +206,10 @@ LOGIN_URL = '/{}login/'.format(BASE_PATH)
# Secrets # Secrets
SECRETS_MIN_PUBKEY_SIZE = 2048 SECRETS_MIN_PUBKEY_SIZE = 2048
# Django filters
FILTERS_NULL_CHOICE_LABEL = 'None'
FILTERS_NULL_CHOICE_VALUE = '0' # Must be a string
# Django REST framework (API) # Django REST framework (API)
REST_FRAMEWORK_VERSION = VERSION[0:3] # Use major.minor as API version REST_FRAMEWORK_VERSION = VERSION[0:3] # Use major.minor as API version
REST_FRAMEWORK = { REST_FRAMEWORK = {

View File

@ -5,7 +5,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, NumericInFilter from utilities.filters import NumericInFilter
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -22,11 +22,11 @@ class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group', name='group',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='slug', to_field_name='slug',

View File

@ -4,7 +4,6 @@ import django_filters
import itertools import itertools
from django import forms from django import forms
from django.db.models import Q
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -66,51 +65,3 @@ class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField):
stripped_value = value stripped_value = value
super(NullableModelMultipleChoiceField, self).clean(stripped_value) super(NullableModelMultipleChoiceField, self).clean(stripped_value)
return value return value
class NullableModelMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
"""
This class extends ModelMultipleChoiceFilter to accept an additional value which implies "is null". The default
queryset filter argument is:
.filter(fieldname=value)
When filtering by the value representing "is null" ('0' by default) the argument is modified to:
.filter(fieldname__isnull=True)
"""
field_class = NullableModelMultipleChoiceField
def __init__(self, *args, **kwargs):
self.null_value = kwargs.get('null_value', 0)
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 by "is null"
if v == force_text(self.null_value):
arg = {'{}__isnull'.format(self.name): True}
# Filtering by a related field (e.g. slug)
elif self.field.to_field_name is not None:
arg = {'{}__{}'.format(self.name, self.field.to_field_name): v}
# Filtering by primary key (default)
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

@ -9,7 +9,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Interface, Platform, Site from dcim.models import DeviceRole, Interface, Platform, 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, NumericInFilter from utilities.filters import NumericInFilter
from .constants import STATUS_CHOICES from .constants import STATUS_CHOICES
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -20,11 +20,11 @@ class ClusterFilter(CustomFieldFilterSet):
method='search', method='search',
label='Search', label='Search',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Parent group (ID)', label='Parent group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent group (slug)', label='Parent group (slug)',
@ -72,12 +72,12 @@ class VirtualMachineFilter(CustomFieldFilterSet):
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=STATUS_CHOICES choices=STATUS_CHOICES
) )
cluster_group_id = NullableModelMultipleChoiceFilter( cluster_group_id = django_filters.ModelMultipleChoiceFilter(
name='cluster__group', name='cluster__group',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Cluster group (ID)', label='Cluster group (ID)',
) )
cluster_group = NullableModelMultipleChoiceFilter( cluster_group = django_filters.ModelMultipleChoiceFilter(
name='cluster__group', name='cluster__group',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -87,29 +87,29 @@ class VirtualMachineFilter(CustomFieldFilterSet):
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='Cluster (ID)', label='Cluster (ID)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
platform_id = NullableModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label='Platform (ID)',
) )
platform = NullableModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label='Platform (slug)',

View File

@ -1,7 +1,7 @@
Django>=1.11,<2.0 Django>=1.11,<2.0
django-cors-headers>=2.1 django-cors-headers>=2.1
django-debug-toolbar>=1.8 django-debug-toolbar>=1.8
django-filter>=1.0.4 django-filter>=1.1.0
django-mptt==0.8.7 django-mptt==0.8.7
django-rest-swagger>=2.1.0 django-rest-swagger>=2.1.0
django-tables2>=1.10.0 django-tables2>=1.10.0