Просмотр исходного кода

Refactor generation of additional lookup filters

jeremystretch 4 лет назад
Родитель
Сommit
6377d475fc
1 измененных файлов с 62 добавлено и 51 удалено
  1. 62 51
      netbox/netbox/filtersets.py

+ 62 - 51
netbox/netbox/filtersets.py

@@ -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
@@ -115,6 +116,59 @@ class BaseFilterSet(django_filters.FilterSet):
 
         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
     def get_filters(cls):
         """
@@ -125,59 +179,12 @@ class BaseFilterSet(django_filters.FilterSet):
         """
         filters = super().get_filters()
 
-        new_filters = {}
+        additional_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
+            additional_filters.update(cls.get_additional_lookups(existing_filter_name, existing_filter))
 
-            # 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
+        filters.update(additional_filters)
 
-            # 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
 
 
@@ -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):