mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Refactor generation of additional lookup filters
This commit is contained in:
parent
aa9e68e121
commit
6377d475fc
@ -2,6 +2,7 @@ import django_filters
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django_filters.exceptions import FieldLookupError
|
||||||
from django_filters.utils import get_model_field, resolve_field
|
from django_filters.utils import get_model_field, resolve_field
|
||||||
|
|
||||||
from dcim.forms import MACAddressField
|
from dcim.forms import MACAddressField
|
||||||
@ -115,6 +116,59 @@ class BaseFilterSet(django_filters.FilterSet):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_additional_lookups(cls, existing_filter_name, existing_filter):
|
||||||
|
new_filters = {}
|
||||||
|
|
||||||
|
# Skip nonstandard lookup expressions
|
||||||
|
if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 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 = f'{existing_filter_name}__{lookup_name}'
|
||||||
|
|
||||||
|
try:
|
||||||
|
if existing_filter_name in cls.declared_filters:
|
||||||
|
# The filter field has been explicitly 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 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
|
||||||
|
|
||||||
|
return new_filters
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_filters(cls):
|
def get_filters(cls):
|
||||||
"""
|
"""
|
||||||
@ -125,59 +179,12 @@ class BaseFilterSet(django_filters.FilterSet):
|
|||||||
"""
|
"""
|
||||||
filters = super().get_filters()
|
filters = super().get_filters()
|
||||||
|
|
||||||
new_filters = {}
|
additional_filters = {}
|
||||||
for existing_filter_name, existing_filter in filters.items():
|
for existing_filter_name, existing_filter in filters.items():
|
||||||
# Loop over existing filters to extract metadata by which to create new filters
|
additional_filters.update(cls.get_additional_lookups(existing_filter_name, existing_filter))
|
||||||
|
|
||||||
# If the filter makes use of a custom filter method or lookup expression skip it
|
filters.update(additional_filters)
|
||||||
# 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
|
return filters
|
||||||
|
|
||||||
|
|
||||||
@ -213,8 +220,12 @@ class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
).exclude(
|
).exclude(
|
||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
custom_field_filters = {}
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
custom_field_filters[f'cf_{cf.name}'] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||||
|
|
||||||
|
self.filters.update(custom_field_filters)
|
||||||
|
|
||||||
|
|
||||||
class OrganizationalModelFilterSet(PrimaryModelFilterSet):
|
class OrganizationalModelFilterSet(PrimaryModelFilterSet):
|
||||||
|
Loading…
Reference in New Issue
Block a user