Ejemplo n.º 1
0
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
Ejemplo n.º 3
0
    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
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
 def test_related_field(self):
     result = get_model_field(Business, 'hiredworker__worker')
     self.assertEqual(result, HiredWorker._meta.get_field('worker'))
Ejemplo n.º 14
0
 def test_non_existent_field(self):
     result = get_model_field(User, 'unknown__name')
     self.assertIsNone(result)
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
 def test_related_field(self):
     result = get_model_field(Business, 'hiredworker__worker')
     self.assertEqual(result, HiredWorker._meta.get_field('worker'))
Ejemplo n.º 17
0
 def test_non_existent_field(self):
     result = get_model_field(User, 'unknown__name')
     self.assertIsNone(result)
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
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,{})},
        }