Refactor generation of additional lookup filters

This commit is contained in:
jeremystretch 2021-10-28 13:56:41 -04:00
parent aa9e68e121
commit 6377d475fc

View File

@ -2,6 +2,7 @@ import django_filters
from copy import deepcopy
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django_filters.exceptions import FieldLookupError
from django_filters.utils import get_model_field, resolve_field
from dcim.forms import MACAddressField
@ -116,29 +117,18 @@ class BaseFilterSet(django_filters.FilterSet):
return None
@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()
def get_additional_lookups(cls, existing_filter_name, existing_filter):
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
# Skip nonstandard lookup expressions
if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
continue
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
continue
return {}
# Get properties of the existing filter for later use
field_name = existing_filter.field_name
@ -146,11 +136,11 @@ class BaseFilterSet(django_filters.FilterSet):
# 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)
new_filter_name = f'{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
# 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
@ -166,7 +156,7 @@ class BaseFilterSet(django_filters.FilterSet):
# 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:
except FieldLookupError:
# The filter could not be created because the lookup expression is not supported on the field
continue
@ -177,7 +167,24 @@ class BaseFilterSet(django_filters.FilterSet):
new_filters[new_filter_name] = new_filter
filters.update(new_filters)
return new_filters
@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()
additional_filters = {}
for existing_filter_name, existing_filter in filters.items():
additional_filters.update(cls.get_additional_lookups(existing_filter_name, existing_filter))
filters.update(additional_filters)
return filters
@ -213,8 +220,12 @@ class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
).exclude(
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
)
custom_field_filters = {}
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):