mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Refactor generation of additional lookup filters
This commit is contained in:
		@@ -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
 | 
			
		||||
            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
 | 
			
		||||
            # 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
 | 
			
		||||
        filters.update(additional_filters)
 | 
			
		||||
 | 
			
		||||
            # 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user