Django Graphene Relay order_by (OrderingFilter)

Posted

技术标签:

【中文标题】Django Graphene Relay order_by (OrderingFilter)【英文标题】: 【发布时间】:2019-12-20 01:16:14 【问题描述】:

我有一个带有中继和过滤器的石墨烯接口。它工作得很好,但我想添加 order_by 选项。我的对象看起来像:

    class FooGQLType(DjangoObjectType):
    class Meta:
        model = Foo
        exclude_fields = ('internal_id',)
        interfaces = (graphene.relay.Node,)
        filter_fields = 
            "id": ["exact"],
            "code": ["exact", "icontains"],
        
        connection_class = ExtendedConnection

class Query(graphene.ObjectType):
    foo = DjangoFilterConnectionField(FooGQLType)

ExtendedConnection 不应该是相关的,但是:

class ExtendedConnection(graphene.Connection):
    class Meta:
        abstract = True

    total_count = graphene.Int()

    def resolve_total_count(root, info, **kwargs):
        return root.length

这让我可以像foo(code_Icontains:"bar") 一样查询。 根据the Graphene documentation,我应该为此在FilterSet 中使用OrderingFilter。我觉得这有点烦人,因为过滤器应该是自动的,但如果我这样做:

    class FooGQLFilter(FilterSet):
    class Meta:
        model = Foo

    order_by = OrderingFilter(
        fields=(
            ('code', 'code'),
            ('lastName', 'last_name'),
            ('otherNames', 'other_names'),
        )
    )

我收到需要提供fieldsexclude 的错误:

AssertionError: Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' has been deprecated since 0.15.0 and is now disallowed. Add an explicit 'Meta.fields' or 'Meta.exclude' to the FooGQLFilter class.

因此,如果我添加 fields = [] 以使其静音,它会编译。 但是,当我使用它时:

foo = DjangoFilterConnectionField(FooGQLType, filterset_class=FooGQLFilter)

我的常规过滤器(例如 code_Icontains)消失了。我可以在那里再次添加它们,但这很愚蠢。快速查看源代码,看起来 Relay 或 django-filters 已经创建了一个 FilterSet 类(有道理),以这种方式覆盖它显然是一个糟糕的主意。

如何在我的 Graphene Relay 过滤对象上添加 orderBy 过滤器?我觉得这应该很简单,但我正在努力解决这个问题。

我还看到了将 DjangoFilterConnectionField 子类化为 connection_resolver 的示例,它以某种方式注入 order_by ,但告诉我没有 orderBy 参数。

【问题讨论】:

如果在Meta 中设置fields = '__all__' 会怎样? fields = '__all__' 实际上使用默认过滤器,因此 code_Icontains 不存在,只有 code。如果我将filter_fields 移动到FilterSet 字段并设置filter_fields = FooGQLFilter._meta.fields,则过滤有效,但order_by 不再有效。在这种情况下,我看不到如何将 order_by 添加到其中... 【参考方案1】:

此解决方案仅适用于 2.6.0 之前的 django-graphene,请参阅 Alok Ramteke 的解决方案以了解更新的版本

我已经改编了a GitHub issue关于这个主题的解决方案:

from graphene_django.filter import DjangoFilterConnectionField
from graphene.utils.str_converters import to_snake_case


class OrderedDjangoFilterConnectionField(DjangoFilterConnectionField):
    """
    Adapted from https://github.com/graphql-python/graphene/issues/251
    Substituting:
    `claims = DjangoFilterConnectionField(ClaimsGraphQLType)`
    with:
    ```
    claims = OrderedDjangoFilterConnectionField(ClaimsGraphQLType,
        orderBy=graphene.List(of_type=graphene.String))
    ```
    """
    @classmethod
    def connection_resolver(cls, resolver, connection, default_manager, max_limit,
                            enforce_first_or_last, filterset_class, filtering_args,
                            root, info, **args):
        filter_kwargs = k: v for k, v in args.items() if k in filtering_args
        qs = filterset_class(
            data=filter_kwargs,
            queryset=default_manager.get_queryset(),
            request=info.context
        ).qs
        order = args.get('orderBy', None)
        if order:
            if type(order) is str:
                snake_order = to_snake_case(order)
            else:
                snake_order = [to_snake_case(o) for o in order]
            qs = qs.order_by(*snake_order)
        return super(DjangoFilterConnectionField, cls).connection_resolver(
            resolver,
            connection,
            qs,
            max_limit,
            enforce_first_or_last,
            root,
            info,
            **args
        )

要使用它,只需修改来自的查询:

claims = DjangoFilterConnectionField(ClaimsGraphQLType)

claims = OrderedDjangoFilterConnectionField(ClaimsGraphQLType,
        orderBy=graphene.List(of_type=graphene.String))

然后你可以查询:

 claims(status: 2, orderBy: "-id")  id  

 claims(status: 2, orderBy: ["creationDate", "lastName"])  id  

【讨论】:

【参考方案2】:

Eric 的解决方案不适用于当前的 graphene-django 版本(2.9.1)或高于 graphene-django 2.6.0 的版本。

DjangoFilterConnectionField 方法在 2.7.0 版本中有所更改。 更多详情,可以查看更新日志here

使用 Eric 的解决方案,会产生错误, connection_resolver() missing 1 required positional argument: 'info’

所以我修改了解决方案,效果很好。

from graphene_django.filter import DjangoFilterConnectionField
from graphene.utils.str_converters import to_snake_case


class OrderedDjangoFilterConnectionField(DjangoFilterConnectionField):

    @classmethod
    def resolve_queryset(
        cls, connection, iterable, info, args, filtering_args, filterset_class
    ):
        qs = super(DjangoFilterConnectionField, cls).resolve_queryset(
            connection, iterable, info, args
        )
        filter_kwargs = k: v for k, v in args.items() if k in filtering_args
        qs = filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs

        order = args.get('orderBy', None)
        if order:
            if type(order) is str:
                snake_order = to_snake_case(order)
            else:
                snake_order = [to_snake_case(o) for o in order]
            qs = qs.order_by(*snake_order)
        return qs

【讨论】:

可能值得注意的是,使用该字段时必须声明并提供自定义filterset_class,或者使用的类型必须在其Meta 类中具有filter_fields 声明。否则会抛出一个错误,指出AssertionError。最初的问题有这个,只是这些答案中缺少它,我想我会帮助遇到同样问题的任何人。【参考方案3】:

嘿,我相信我对处理 orderBy 有一个简单易行的答案,尤其是作为石墨烯中的列表。我回答的原因不是因为我相信我有最聪明的解决方案,我只是想知道如果我可能在不知不觉中造成伤害有什么区别。我实际上不了解 graphene_django 中的连接内容,所以我采取了一条不同的路线,灵感来自观看关于 GraphQL 中排序对象的讨论。

https://www.youtube.com/watch?v=dDxUu-K2qdE

首先使用您的FooType,我已将字段添加为枚举

class FooGQLType(DjangoObjectType):
    class Meta:
        model = Foo

class FooFields(graphene.Enum):
    CODE = "code"
    LAST_NAME = "last_name"
    OTHER_NAMES = "other_names"

class Directions(graphene.Enum):
    ASC = "asc"
    DESC = "desc"

然后在Query 中传递它们的输入

class FooOrderByInput(graphene.InputObjectType):
    order_by = FooFields()
    direction = Directions()

我不确定您要过滤什么,但由于问题主要是关于order_by,所以我暂时假设它是一个随机字符串。我觉得有一种方法可以发出这一行 db 请求,但我不确定如何。

ORDER_BY = 'order_by'
DIRECTION = 'direction'

class Query(graphene.ObjectType):
    foo = graphene.Field(FooGQLType, input=graphene.List(FooOrderByInput), filter_opt=graphene.String())

    def resolve_foo(self, info, input):
        qs = Foo.objects.filter(filter_opt=filter_opt)
        for obj in input:
            order_by = obj.get(ORDER_BY)
            direction = "-" if obj.get(DIRECTION) == Directions.DESC else ""
            qs = qs.order_by(f"directionorder_by")

        return  qs

最好的一点(无论如何对我来说)是它允许查询多个字段,例如

query ($input: [FooOrderByInput]) 
  foo (input: $input) 
    id
    code
    lastName
    otherNames
  


VARIABLES

    "input": [
        
            "orderBy": "LAST_NAME",
            "direction": "DESC"
        ,
        
            "orderBy": "CODE",
            "direction": "ASC"
        
    ]


除了那些拼写正确的关键字之外,它也不允许其他任何东西。它也不必处理camelCase到snake_case。

如果有更好的解决方案或者我的解决方案很糟糕,请告诉我。我很高兴这似乎已经解决了!

【讨论】:

以上是关于Django Graphene Relay order_by (OrderingFilter)的主要内容,如果未能解决你的问题,请参考以下文章

Django Graphene Relay order_by (OrderingFilter)

使用 Django GraphQL JWT 和 Graphene Relay 进行身份验证和授权

如何使用 Graphene-Django Relay 中的外键关系更新模型?

如何使用 GraphQL、Graphene 和 Relay 改变现有数据?

如何删除 Graphene Django 突变查询(中继)中的嵌套输入对象?

如何更改graphene-python(中继)中的连接参数(之后,之前)?