mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Split Filter and FilterSet classes
This commit is contained in:
parent
763b02975c
commit
0de50e0afe
@ -5,9 +5,8 @@ from dcim.filters import CableTerminationFilterSet
|
|||||||
from dcim.models import Region, Site, SiteGroup
|
from dcim.models import Region, Site, SiteGroup
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
)
|
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ from tenancy.filters import TenancyFilterSet
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
|
MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
||||||
NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter,
|
|
||||||
)
|
)
|
||||||
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
@ -6,7 +6,8 @@ from django.forms import DateField, IntegerField, NullBooleanField
|
|||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.filters import BaseFilterSet, ContentTypeFilter
|
from utilities.filtersets import BaseFilterSet
|
||||||
|
from utilities.filters import ContentTypeFilter
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
@ -9,9 +9,10 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup
|
|||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TagFilter,
|
||||||
NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
||||||
|
@ -3,7 +3,8 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter
|
from utilities.filters import TagFilter
|
||||||
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import Secret, SecretRole
|
from .models import Secret, SecretRole
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
from .models import Tenant, TenantGroup
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from django.contrib.auth.models import Group, User
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from users.models import ObjectPermission
|
from users.models import ObjectPermission
|
||||||
from utilities.filters import BaseFilterSet
|
from utilities.filtersets import BaseFilterSet
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupFilterSet',
|
'GroupFilterSet',
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django_filters.constants import EMPTY_VALUES
|
|
||||||
from copy import deepcopy
|
|
||||||
from dcim.forms import MACAddressField
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django_filters.constants import EMPTY_VALUES
|
||||||
from django_filters.utils import get_model_field, resolve_field
|
|
||||||
|
|
||||||
|
from dcim.forms import MACAddressField
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from utilities.constants import (
|
|
||||||
FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
|
|
||||||
FILTER_NUMERIC_BASED_LOOKUP_MAP
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def multivalue_field_factory(field_class):
|
def multivalue_field_factory(field_class):
|
||||||
@ -134,182 +127,3 @@ class ContentTypeFilter(django_filters.CharFilter):
|
|||||||
f'{self.field_name}__model': model
|
f'{self.field_name}__model': model
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# FilterSets
|
|
||||||
#
|
|
||||||
|
|
||||||
class BaseFilterSet(django_filters.FilterSet):
|
|
||||||
"""
|
|
||||||
A base filterset which provides common functionaly to all NetBox filtersets
|
|
||||||
"""
|
|
||||||
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
|
|
||||||
FILTER_DEFAULTS.update({
|
|
||||||
models.AutoField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.CharField: {
|
|
||||||
'filter_class': MultiValueCharFilter
|
|
||||||
},
|
|
||||||
models.DateField: {
|
|
||||||
'filter_class': MultiValueDateFilter
|
|
||||||
},
|
|
||||||
models.DateTimeField: {
|
|
||||||
'filter_class': MultiValueDateTimeFilter
|
|
||||||
},
|
|
||||||
models.DecimalField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.EmailField: {
|
|
||||||
'filter_class': MultiValueCharFilter
|
|
||||||
},
|
|
||||||
models.FloatField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.IntegerField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.PositiveIntegerField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.PositiveSmallIntegerField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.SlugField: {
|
|
||||||
'filter_class': MultiValueCharFilter
|
|
||||||
},
|
|
||||||
models.SmallIntegerField: {
|
|
||||||
'filter_class': MultiValueNumberFilter
|
|
||||||
},
|
|
||||||
models.TimeField: {
|
|
||||||
'filter_class': MultiValueTimeFilter
|
|
||||||
},
|
|
||||||
models.URLField: {
|
|
||||||
'filter_class': MultiValueCharFilter
|
|
||||||
},
|
|
||||||
MACAddressField: {
|
|
||||||
'filter_class': MultiValueMACAddressFilter
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_filter_lookup_dict(existing_filter):
|
|
||||||
# Choose the lookup expression map based on the filter type
|
|
||||||
if isinstance(existing_filter, (
|
|
||||||
MultiValueDateFilter,
|
|
||||||
MultiValueDateTimeFilter,
|
|
||||||
MultiValueNumberFilter,
|
|
||||||
MultiValueTimeFilter
|
|
||||||
)):
|
|
||||||
lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP
|
|
||||||
|
|
||||||
elif isinstance(existing_filter, (
|
|
||||||
TreeNodeMultipleChoiceFilter,
|
|
||||||
)):
|
|
||||||
# TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
|
|
||||||
lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP
|
|
||||||
|
|
||||||
elif isinstance(existing_filter, (
|
|
||||||
django_filters.ModelChoiceFilter,
|
|
||||||
django_filters.ModelMultipleChoiceFilter,
|
|
||||||
TagFilter
|
|
||||||
)) or existing_filter.extra.get('choices'):
|
|
||||||
# These filter types support only negation
|
|
||||||
lookup_map = FILTER_NEGATION_LOOKUP_MAP
|
|
||||||
|
|
||||||
elif isinstance(existing_filter, (
|
|
||||||
django_filters.filters.CharFilter,
|
|
||||||
django_filters.MultipleChoiceFilter,
|
|
||||||
MultiValueCharFilter,
|
|
||||||
MultiValueMACAddressFilter
|
|
||||||
)):
|
|
||||||
lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP
|
|
||||||
|
|
||||||
else:
|
|
||||||
lookup_map = None
|
|
||||||
|
|
||||||
return lookup_map
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_filters(cls):
|
|
||||||
"""
|
|
||||||
Override filter generation to support dynamic lookup expressions for certain filter types.
|
|
||||||
|
|
||||||
For specific filter types, new filters are created based on defined lookup expressions in
|
|
||||||
the form `<field_name>__<lookup_expr>`
|
|
||||||
"""
|
|
||||||
filters = super().get_filters()
|
|
||||||
|
|
||||||
new_filters = {}
|
|
||||||
for existing_filter_name, existing_filter in filters.items():
|
|
||||||
# Loop over existing filters to extract metadata by which to create new filters
|
|
||||||
|
|
||||||
# If the filter makes use of a custom filter method or lookup expression skip it
|
|
||||||
# as we cannot sanely handle these cases in a generic mannor
|
|
||||||
if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Choose the lookup expression map based on the filter type
|
|
||||||
lookup_map = cls._get_filter_lookup_dict(existing_filter)
|
|
||||||
if lookup_map is None:
|
|
||||||
# Do not augment this filter type with more lookup expressions
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Get properties of the existing filter for later use
|
|
||||||
field_name = existing_filter.field_name
|
|
||||||
field = get_model_field(cls._meta.model, field_name)
|
|
||||||
|
|
||||||
# Create new filters for each lookup expression in the map
|
|
||||||
for lookup_name, lookup_expr in lookup_map.items():
|
|
||||||
new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if existing_filter_name in cls.declared_filters:
|
|
||||||
# The filter field has been explicity defined on the filterset class so we must manually
|
|
||||||
# create the new filter with the same type because there is no guarantee the defined type
|
|
||||||
# is the same as the default type for the field
|
|
||||||
resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
|
|
||||||
new_filter = type(existing_filter)(
|
|
||||||
field_name=field_name,
|
|
||||||
lookup_expr=lookup_expr,
|
|
||||||
label=existing_filter.label,
|
|
||||||
exclude=existing_filter.exclude,
|
|
||||||
distinct=existing_filter.distinct,
|
|
||||||
**existing_filter.extra
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# The filter field is listed in Meta.fields so we can safely rely on default behaviour
|
|
||||||
# Will raise FieldLookupError if the lookup is invalid
|
|
||||||
new_filter = cls.filter_for_field(field, field_name, lookup_expr)
|
|
||||||
except django_filters.exceptions.FieldLookupError:
|
|
||||||
# The filter could not be created because the lookup expression is not supported on the field
|
|
||||||
continue
|
|
||||||
|
|
||||||
if lookup_name.startswith('n'):
|
|
||||||
# This is a negation filter which requires a queryset.exclude() clause
|
|
||||||
# Of course setting the negation of the existing filter's exclude attribute handles both cases
|
|
||||||
new_filter.exclude = not existing_filter.exclude
|
|
||||||
|
|
||||||
new_filters[new_filter_name] = new_filter
|
|
||||||
|
|
||||||
filters.update(new_filters)
|
|
||||||
return filters
|
|
||||||
|
|
||||||
|
|
||||||
class NameSlugSearchFilterSet(django_filters.FilterSet):
|
|
||||||
"""
|
|
||||||
A base class for adding the search method to models which only expose the `name` and `slug` fields
|
|
||||||
"""
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
models.Q(name__icontains=value) |
|
|
||||||
models.Q(slug__icontains=value)
|
|
||||||
)
|
|
||||||
|
190
netbox/utilities/filtersets.py
Normal file
190
netbox/utilities/filtersets.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import django_filters
|
||||||
|
from copy import deepcopy
|
||||||
|
from dcim.forms import MACAddressField
|
||||||
|
from django.db import models
|
||||||
|
from django_filters.utils import get_model_field, resolve_field
|
||||||
|
|
||||||
|
from utilities.constants import (
|
||||||
|
FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
|
||||||
|
FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||||
|
)
|
||||||
|
from utilities import filters
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# FilterSets
|
||||||
|
#
|
||||||
|
|
||||||
|
class BaseFilterSet(django_filters.FilterSet):
|
||||||
|
"""
|
||||||
|
A base filterset which provides common functionaly to all NetBox filtersets
|
||||||
|
"""
|
||||||
|
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
|
||||||
|
FILTER_DEFAULTS.update({
|
||||||
|
models.AutoField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.CharField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.DateField: {
|
||||||
|
'filter_class': filters.MultiValueDateFilter
|
||||||
|
},
|
||||||
|
models.DateTimeField: {
|
||||||
|
'filter_class': filters.MultiValueDateTimeFilter
|
||||||
|
},
|
||||||
|
models.DecimalField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.EmailField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.FloatField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.IntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.PositiveIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.PositiveSmallIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.SlugField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.SmallIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.TimeField: {
|
||||||
|
'filter_class': filters.MultiValueTimeFilter
|
||||||
|
},
|
||||||
|
models.URLField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
MACAddressField: {
|
||||||
|
'filter_class': filters.MultiValueMACAddressFilter
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_filter_lookup_dict(existing_filter):
|
||||||
|
# Choose the lookup expression map based on the filter type
|
||||||
|
if isinstance(existing_filter, (
|
||||||
|
filters.MultiValueDateFilter,
|
||||||
|
filters.MultiValueDateTimeFilter,
|
||||||
|
filters.MultiValueNumberFilter,
|
||||||
|
filters.MultiValueTimeFilter
|
||||||
|
)):
|
||||||
|
lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
filters.TreeNodeMultipleChoiceFilter,
|
||||||
|
)):
|
||||||
|
# TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
|
||||||
|
lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
django_filters.ModelChoiceFilter,
|
||||||
|
django_filters.ModelMultipleChoiceFilter,
|
||||||
|
filters.TagFilter
|
||||||
|
)) or existing_filter.extra.get('choices'):
|
||||||
|
# These filter types support only negation
|
||||||
|
lookup_map = FILTER_NEGATION_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
django_filters.filters.CharFilter,
|
||||||
|
django_filters.MultipleChoiceFilter,
|
||||||
|
filters.MultiValueCharFilter,
|
||||||
|
filters.MultiValueMACAddressFilter
|
||||||
|
)):
|
||||||
|
lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP
|
||||||
|
|
||||||
|
else:
|
||||||
|
lookup_map = None
|
||||||
|
|
||||||
|
return lookup_map
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_filters(cls):
|
||||||
|
"""
|
||||||
|
Override filter generation to support dynamic lookup expressions for certain filter types.
|
||||||
|
|
||||||
|
For specific filter types, new filters are created based on defined lookup expressions in
|
||||||
|
the form `<field_name>__<lookup_expr>`
|
||||||
|
"""
|
||||||
|
filters = super().get_filters()
|
||||||
|
|
||||||
|
new_filters = {}
|
||||||
|
for existing_filter_name, existing_filter in filters.items():
|
||||||
|
# Loop over existing filters to extract metadata by which to create new filters
|
||||||
|
|
||||||
|
# If the filter makes use of a custom filter method or lookup expression skip it
|
||||||
|
# as we cannot sanely handle these cases in a generic mannor
|
||||||
|
if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Choose the lookup expression map based on the filter type
|
||||||
|
lookup_map = cls._get_filter_lookup_dict(existing_filter)
|
||||||
|
if lookup_map is None:
|
||||||
|
# Do not augment this filter type with more lookup expressions
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get properties of the existing filter for later use
|
||||||
|
field_name = existing_filter.field_name
|
||||||
|
field = get_model_field(cls._meta.model, field_name)
|
||||||
|
|
||||||
|
# Create new filters for each lookup expression in the map
|
||||||
|
for lookup_name, lookup_expr in lookup_map.items():
|
||||||
|
new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if existing_filter_name in cls.declared_filters:
|
||||||
|
# The filter field has been explicity defined on the filterset class so we must manually
|
||||||
|
# create the new filter with the same type because there is no guarantee the defined type
|
||||||
|
# is the same as the default type for the field
|
||||||
|
resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
|
||||||
|
new_filter = type(existing_filter)(
|
||||||
|
field_name=field_name,
|
||||||
|
lookup_expr=lookup_expr,
|
||||||
|
label=existing_filter.label,
|
||||||
|
exclude=existing_filter.exclude,
|
||||||
|
distinct=existing_filter.distinct,
|
||||||
|
**existing_filter.extra
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The filter field is listed in Meta.fields so we can safely rely on default behaviour
|
||||||
|
# Will raise FieldLookupError if the lookup is invalid
|
||||||
|
new_filter = cls.filter_for_field(field, field_name, lookup_expr)
|
||||||
|
except django_filters.exceptions.FieldLookupError:
|
||||||
|
# The filter could not be created because the lookup expression is not supported on the field
|
||||||
|
continue
|
||||||
|
|
||||||
|
if lookup_name.startswith('n'):
|
||||||
|
# This is a negation filter which requires a queryset.exclude() clause
|
||||||
|
# Of course setting the negation of the existing filter's exclude attribute handles both cases
|
||||||
|
new_filter.exclude = not existing_filter.exclude
|
||||||
|
|
||||||
|
new_filters[new_filter_name] = new_filter
|
||||||
|
|
||||||
|
filters.update(new_filters)
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
|
class NameSlugSearchFilterSet(django_filters.FilterSet):
|
||||||
|
"""
|
||||||
|
A base class for adding the search method to models which only expose the `name` and `slug` fields
|
||||||
|
"""
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
models.Q(name__icontains=value) |
|
||||||
|
models.Q(slug__icontains=value)
|
||||||
|
)
|
@ -13,9 +13,10 @@ from dcim.models import (
|
|||||||
)
|
)
|
||||||
from extras.models import TaggedItem
|
from extras.models import TaggedItem
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter,
|
MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, MultiValueNumberFilter,
|
||||||
MultiValueNumberFilter, MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
|
from utilities.filtersets import BaseFilterSet
|
||||||
|
|
||||||
|
|
||||||
class TreeNodeMultipleChoiceFilterTest(TestCase):
|
class TreeNodeMultipleChoiceFilterTest(TestCase):
|
||||||
|
@ -4,10 +4,8 @@ from django.db.models import Q
|
|||||||
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import MultiValueMACAddressFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
BaseFilterSet, MultiValueMACAddressFilter, NameSlugSearchFilterSet, TagFilter,
|
from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet
|
||||||
TreeNodeMultipleChoiceFilter,
|
|
||||||
)
|
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user