def get_filtering_args_from_filterset(filterset_class, type): """ Inspect a FilterSet and produce the arguments to pass to a Graphene Field. These arguments will be available to filter against in the GraphQL """ from ..forms.converter import convert_form_field args = {} model = filterset_class._meta.model for name, filter_field in six.iteritems(filterset_class.base_filters): form_field = None if name in filterset_class.declared_filters: form_field = filter_field.field else: model_field = get_model_field(model, filter_field.field_name) filter_type = filter_field.lookup_expr if filter_type != "isnull" and hasattr(model_field, "formfield"): form_field = model_field.formfield( required=filter_field.extra.get("required", False)) # Fallback to field defined on filter if we can't get it from the # model field if not form_field: form_field = filter_field.field field_type = convert_form_field(form_field).Argument() field_type.description = filter_field.label args[name] = field_type return args
def get_fields(cls): fields = super(FilterSet, cls).get_fields() for name, lookups in fields.items(): if lookups == filters.ALL_LOOKUPS: field = get_model_field(cls._meta.model, name) fields[name] = utils.lookups_for_field(field) return fields
def _generate_lookup_expression_filters(cls, filter_name, filter_field): """ For specific filter types, new filters are created based on defined lookup expressions in the form `<field_name>__<lookup_expr>` """ magic_filters = {} if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in"]: return magic_filters # Choose the lookup expression map based on the filter type lookup_map = cls._get_filter_lookup_dict(filter_field) if lookup_map is None: # Do not augment this filter type with more lookup expressions return magic_filters # Get properties of the existing filter for later use field_name = filter_field.field_name field = get_model_field(cls._meta.model, field_name) # If there isn't a model field, return. if field is None: return magic_filters # Create new filters for each lookup expression in the map for lookup_name, lookup_expr in lookup_map.items(): new_filter_name = "{}__{}".format(filter_name, lookup_name) try: if 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(filter_field)( field_name=field_name, lookup_expr=lookup_expr, label=filter_field.label, exclude=filter_field.exclude, distinct=filter_field.distinct, **filter_field.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 filter_field.exclude magic_filters[new_filter_name] = new_filter return magic_filters
def get_fields(cls): fields = super(FilterSet, cls).get_fields() for name, lookups in six.iteritems(fields): if lookups == filters.ALL_LOOKUPS: field = get_model_field(cls._meta.model, name) fields[name] = utils.lookups_for_field(field) return fields
def get_fields(cls): # Extend the 'Meta.fields' dict syntax to allow '__all__' field lookups. fields = super(FilterSet, cls).get_fields() for name, lookups in fields.items(): if lookups == filters.ALL_LOOKUPS: field = get_model_field(cls._meta.model, name) fields[name] = utils.lookups_for_field(field) return fields
def _construct_filter_backend( model: Type[Model], resource_name: str, filters: Dict[str, Filter], computed_filters: Dict[str, ComputedFilter]) -> Tuple[Type, Type]: constructed_filters_transform_callbacks = {} constructed_filters = {} for key, filter in filters.items(): for lookup_expr in filter.lookups: field = get_model_field(model, filter.field) filter_name = BaseFilterSet.get_filter_name(key, lookup_expr) # If the filter is explicitly declared on the class, skip generation if field is not None: constructed_filters[ filter_name] = BaseFilterSet.filter_for_field( field, filter.field, lookup_expr) if filter.transform_value: constructed_filters_transform_callbacks[ key] = filter.transform_value filter_set = type( f'{resource_name}FilterSet', (FilterSet, ), { **constructed_filters, **{ field: computed_filter.filter_type(method=f'filter_{field}') for field, computed_filter in computed_filters.items() }, **{ f'filter_{field}': lambda self, queryset, field_name, value: computed_filter.filter_func( queryset, value) for field, computed_filter in computed_filters.items() }, 'Meta': type('Meta', (), { 'model': model, 'fields': [] }) }) def _get_filterset_kwargs(self, request, queryset, view): result = super(self.__class__, self).get_filterset_kwargs(request, queryset, view) queryset = result['queryset'] for field, value in result['data'].items(): if field in constructed_filters_transform_callbacks: new_value, new_queryset = constructed_filters_transform_callbacks[ field](value, queryset) queryset = new_queryset result['data'][field] = new_value result['queryset'] = queryset return result filter_backend = type(f'{resource_name}FilterBackend', (DjangoFilterBackend, ), {'get_filterset_kwargs': _get_filterset_kwargs}) return filter_set, filter_backend
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 ) elif hasattr(existing_filter, 'custom_field'): # Filter is for a custom field custom_field = existing_filter.custom_field new_filter = custom_field.to_filter(lookup_expr=lookup_expr) 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
def get_fields(cls): # Extend the 'Meta.fields' dict syntax to allow '__all__' field lookups. fields = super(FilterSet, cls).get_fields() for name, lookups in fields.items(): if lookups == filters.ALL_LOOKUPS: field = get_model_field(cls._meta.model, name) if field is None: raise FieldDoesNotExist("Unknown field %s in %s" % (name, cls._meta.model.__name__)) fields[name] = utils.lookups_for_field(field) return fields
def get_fields(cls): # Extend the 'Meta.fields' dict syntax to allow '__all__' field lookups. fields = super(FilterSet, cls).get_fields() for name, lookups in fields.items(): if lookups == filters.ALL_LOOKUPS: field = get_model_field(cls._meta.model, name) if field is not None: fields[name] = utils.lookups_for_field(field) else: # FilterSet will handle invalid name fields[name] = [] return fields
def filters_for_model(cls, model, opts): fields = opts.fields if not isinstance(fields, dict): return super(FilterSet, cls).filters_for_model(model, opts) # replace all '__all__' values by the resolved list of all lookups fields = fields.copy() for name, lookups in six.iteritems(fields): if lookups == filters.ALL_LOOKUPS: field = get_model_field(model, name) fields[name] = utils.lookups_for_field(field) return filterset.filters_for_model(model, fields, opts.exclude, cls.filter_for_field, cls.filter_for_reverse_field)
def get_filtering_args_from_filterset(filterset_class, type, obvious_filters=[]): """ Original: - https://github.com/graphql-python/graphene-django/blob/master/graphene_django/filter/utils.py#L7 - filters not in 'declared_filters' are defined by Graphene-Django for model fields `formfield`. obivius_filters: - Force to use field class defined in filters. """ args = {} model = filterset_class._meta.model for name, filter_field in six.iteritems(filterset_class.base_filters): form_field = None if name in filterset_class.declared_filters: form_field = filter_field.field elif filter_field.__class__ in obvious_filters: form_field = filter_field.field else: model_field = get_model_field(model, filter_field.field_name) filter_type = filter_field.lookup_expr if filter_type != "isnull" and hasattr(model_field, "formfield"): form_field = model_field.formfield( required=filter_field.extra.get("required", False)) # Fallback to field defined on filter if we can't get it from the # model field if not form_field: form_field = filter_field.field field_type = convert_form_field(form_field).Argument() field_type.description = filter_field.label # For RangeFilter, duplicate filter args for suffixes if isinstance(filter_field, RangeFilter) and hasattr( filter_field.field, "widget"): suffixes = getattr(filter_field.field.widget, "suffixes", []) for s in suffixes: if s: args[f"{name}_{s}"] = field_type else: args[name] = field_type return args
def get_filtering_args_from_filterset(filterset_class, type): """ Inspect a FilterSet and produce the arguments to pass to a Graphene Field. These arguments will be available to filter against in the GraphQL """ from ..forms.converter import convert_form_field args = {} model = filterset_class._meta.model for name, filter_field in filterset_class.base_filters.items(): form_field = None filter_type = filter_field.lookup_expr if name in filterset_class.declared_filters: # Get the filter field from the explicitly declared filter form_field = filter_field.field field = convert_form_field(form_field) else: # Get the filter field with no explicit type declaration model_field = get_model_field(model, filter_field.field_name) if filter_type != "isnull" and hasattr(model_field, "formfield"): form_field = model_field.formfield( required=filter_field.extra.get("required", False) ) # Fallback to field defined on filter if we can't get it from the # model field if not form_field: form_field = filter_field.field field = convert_form_field(form_field) if filter_type in {"in", "range", "contains", "overlap"}: # Replace CSV filters (`in`, `range`, `contains`, `overlap`) argument type to be a list of # the same type as the field. See comments in # `replace_csv_filters` method for more details. field = graphene.List(field.get_type()) field_type = field.Argument() field_type.description = str(filter_field.label) if filter_field.label else None args[name] = field_type return args
def test_related_field(self): result = get_model_field(Business, 'hiredworker__worker') self.assertEqual(result, HiredWorker._meta.get_field('worker'))
def test_non_existent_field(self): result = get_model_field(User, 'unknown__name') self.assertIsNone(result)
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
def get_filtering_args_from_filterset(filterset_class, type): """ Inspect a FilterSet and produce the arguments to pass to a Graphene Field. These arguments will be available to filter against in the GraphQL API. """ from ..forms.converter import convert_form_field args = {} model = filterset_class._meta.model registry = type._meta.registry for name, filter_field in filterset_class.base_filters.items(): filter_type = filter_field.lookup_expr required = filter_field.extra.get("required", False) field_type = None form_field = None if ( name not in filterset_class.declared_filters or isinstance(filter_field, ListFilter) or isinstance(filter_field, RangeFilter) or isinstance(filter_field, ArrayFilter) ): # Get the filter field for filters that are no explicitly declared. if filter_type == "isnull": field = graphene.Boolean(required=required) else: model_field = get_model_field(model, filter_field.field_name) # Get the form field either from: # 1. the formfield corresponding to the model field # 2. the field defined on filter if hasattr(model_field, "formfield"): form_field = model_field.formfield(required=required) if not form_field: form_field = filter_field.field # First try to get the matching field type from the GraphQL DjangoObjectType if model_field: if ( isinstance(form_field, forms.ModelChoiceField) or isinstance(form_field, forms.ModelMultipleChoiceField) or isinstance(form_field, GlobalIDMultipleChoiceField) or isinstance(form_field, GlobalIDFormField) ): # Foreign key have dynamic types and filtering on a foreign key actually means filtering on its ID. field_type = get_field_type( registry, model_field.related_model, "id" ) else: field_type = get_field_type( registry, model_field.model, model_field.name ) if not field_type: # Fallback on converting the form field either because: # - it's an explicitly declared filters # - we did not manage to get the type from the model type form_field = form_field or filter_field.field field_type = convert_form_field(form_field) if isinstance(filter_field, ListFilter) or isinstance( filter_field, RangeFilter ): # Replace InFilter/RangeFilter filters (`in`, `range`) argument type to be a list of # the same type as the field. See comments in `replace_csv_filters` method for more details. field_type = graphene.List(field_type.get_type()) args[name] = graphene.Argument( field_type.get_type(), description=filter_field.label, required=required, ) return args
def get_filterset_class(self, view, queryset=None): """ Return the `FilterSet` class used to filter the queryset. """ filterset_class = getattr(view, 'filterset_class', None) filterset_fields_overwrite = getattr(view, 'filterset_fields_overwrite', {}) # full text search if filterset_class: filterset_model = filterset_class._meta.model # noqa # FilterSets do not need to specify a Meta class if filterset_model and queryset is not None: assert issubclass( queryset.model, filterset_model ), 'FilterSet model %s does not match queryset model %s' % (filterset_model, queryset.model) return filterset_class serializer = view.get_serializer() if not isinstance(serializer, serializers.ModelSerializer): return None filterset_model = serializer.Meta.model # noqa filterset_fields = {} overwrite_fields = {k: v for k, v in filterset_fields_overwrite.items() if isinstance(v, Filter)} overwrite_kwargs = {k: v for k, v in filterset_fields_overwrite.items() if isinstance(v, dict)} for filter_name, field in serializer.fields.items(): if ( getattr(field, "write_only", False) or field.source == "*" or isinstance(field, serializers.BaseSerializer) ): continue field_name = field.source.replace(".", "__") or filter_name if get_model_field(filterset_model, field_name) is None and ( queryset is not None and filter_name not in queryset.query.annotations ): continue try: filter_spec = FILTER_FOR_SERIALIZER_FIELD_DEFAULTS[field] except KeyError: warnings.warn(f"{filter_name} 字段未找到过滤器, 跳过自动成filter!") continue extra = filter_spec.get("extra") kwargs = {"field_name": field_name, "label": field.label, "help_text": field.help_text} if callable(extra): kwargs.update(extra(field)) if "queryset" in kwargs and kwargs["queryset"] is None: warnings.warn(f"{filter_name} 字段未提供queryset, 跳过自动成filter!") continue overwrite_value = overwrite_kwargs.get(filter_name) if overwrite_value: kwargs.update(overwrite_value) filterset_field = filter_spec["filter_class"](**kwargs) filterset_fields[filter_name] = filterset_field filterset_fields.update(overwrite_fields) AutoFilterSet = type("AutoFilterSet", (self.filterset_base,), filterset_fields) # noqa return AutoFilterSet
from django.db import models from Credit.models import Credit from django_filters import utils, filters from django_filters.rest_framework import FilterSet CHECKIN_FILTER_EXTRAS = { utils.get_model_field(Credit, 'appId'): {'help_text': '{integer} which represents an app.'}, utils.get_model_field(Credit, 'userId'): {'help_text': '{sting} which represents the userid or username.'} } class CreditFilterSet(FilterSet): class Meta: filter_overrides = { models.PositiveIntegerField : {'filter_class': filters.NumberFilter, 'extra': lambda field : CHECKIN_FILTER_EXTRAS.get(field,{})}, models.CharField : {'filter_class': filters.CharFilter, 'extra': lambda field : CHECKIN_FILTER_EXTRAS.get(field,{})}, }