如何使用graphene-django通过GraphQL中的ID列表过滤查询?

时间:2019-01-16 13:49:31

标签: django-filter graphene-python graphql-python

我正在尝试使用Django和Graphene执行GraphQL查询。要使用ID查询单个对象,我做了以下操作:

{
  samples(id:"U2FtcGxlU2V0VHlwZToxMjYw") {
    edges {
      nodes {
        name
      }
    }
  }
}

它运行正常。当我尝试使用多个ID进行查询时出现问题,如下所示:

{
  samples(id_In:"U2FtcGxlU2V0VHlwZToxMjYw, U2FtcGxlU2V0VHlwZToxMjYx") {
    edges {
      nodes {
        name
      }
    }
  }
} 

在后一种情况下,出现以下错误:

argument should be a bytes-like object or ASCII string, not 'list'

这是在django-graphene

中如何定义类型和查询的示意图
class SampleType(DjangoObjectType):
  class Meta:
    model = Sample
    filter_fields = {
      'id': ['exact', 'in'],
     }
     interfaces = (graphene.relay.Node,)

class Query(object):
  samples = DjangoFilterConnectionField(SampleType)

  def resolve_sample_sets(self, info, **kwargs):
    return Sample.objects.all()

5 个答案:

答案 0 :(得分:4)

如果在字段名称中输入“ in”,则django-graphene的

GlobalIDMultipleChoiceFilter可以解决此问题。您可以创建类似

的过滤器
from django_filters import FilterSet
from graphene_django.filter import GlobalIDMultipleChoiceFilter

class BookFilter(FilterSet):
    author = GlobalIDMultipleChoiceFilter()

并通过

使用它
{
  books(author: ["<GlobalID1>", "<GlobalID2>"]) {
    edges {
      nodes {
        name
      }
    }
  }
}

仍然不够完美,但是对自定义代码的需求已降至最低。

答案 1 :(得分:1)

您可以轻松地使用过滤器,只需将其放入节点即可。

TIME_ZONE = 'America/New_York'  

然后在查询中只使用-

class ReportFileFilter(FilterSet):
    id = GlobalIDMultipleChoiceFilter()

这是对graphql django的中继实现。

答案 2 :(得分:1)

在介绍这些答案时,似乎没有一个对我有用,但是通过一些小的更改,我设法解决了以下问题:

您可以为您的对象类型创建自定义FilterSet类,并使用GlobalIDMultipleChoiceFilter过滤字段。例如:

from django_filters import FilterSet
from graphene_django.filter import GlobalIDFilter, GlobalIDMultipleChoiceFilter

class SampleFilter(FilterSet):
    id = GlobalIDFilter()
    id__in = GlobalIDMultipleChoiceFilter(field_name="id")

    class Meta:
        model = Sample
        fields = (
            "id_in",
            "id",
        )

我碰到的一件事情是,您无法使用此方法定义filter_fields 。相反,您只需要专门依赖自定义FilterSet类,就可以使对象类型有效地如下所示:

from graphene import relay
from graphene_django import DjangoObjectType

class SampleType(DjangoObjectType):
    class Meta:
        model = Sample
        filterset_class = SampleFilter
        interfaces = (relay.Node,)

答案 3 :(得分:0)

我也难以实现'in'过滤器-它现在似乎在graphene-django中实现不当,并且无法按预期工作。以下是使其工作的步骤:

  1. 从您的filter_fields中删除“输入”过滤器
  2. 将名为“ id__in”的输入值添加到您的DjangoFilterConnectionField中,并使其成为ID列表
  3. 重命名解析器以匹配“样本”字段。
  4. 在字段的解析程序中通过“ id__in”处理过滤。对于您来说,它将如下所示:
from base64 import b64decode

def get_pk_from_node_id(node_id: str):
    """Gets pk from node_id"""
    model_with_pk = b64decode(node_id).decode('utf-8')
    model_name, pk = model_with_pk.split(":")
    return pk


class SampleType(DjangoObjectType):
    class Meta:
        model = Sample
        filter_fields = {
            'id': ['exact'],
         }
        interfaces = (graphene.relay.Node,)


class Query(object):

    samples = DjangoFilterConnectionField(SampleType, id__in=graphene.List(graphene.ID))

    def resolve_samples(self, info, **kwargs):
        # filter_field for 'in' seems to not work, this hack works
        id__in = kwargs.get('id__in')
        if id__in:
            node_ids = kwargs.pop('id__in')
            pk_list = [get_pk_from_node_id(node_id) for node_id in node_ids]
            return Sample._default_manager.filter(id__in=pk_list)
        return Sample._default_manager.all()

这将允许您使用以下api调用过滤器。请注意在签名中使用实际数组(我认为这比发送逗号分隔的值字符串更好)。该解决方案仍然允许您向​​请求中添加其他过滤器,它们将正确链接在一起。

{
  samples(id_In: ["U2FtcGxlU2V0VHlwZToxMjYw", "U2FtcGxlU2V0VHlwZToxMjYx"]) {
    edges {
      nodes {
        name
      }
    }
  }
} 

答案 4 :(得分:0)

另一种方法是告诉graphene_django的Relay过滤器也处理一个列表。此过滤器已在graphene_django的mixin中注册,并应用于您定义的任何过滤器。

这是我的解决方案:

from graphene_django.filter.filterset import (
    GlobalIDFilter,
    GrapheneFilterSetMixin,
)
from graphql_relay import from_global_id


class CustomGlobalIDFilter(GlobalIDFilter):
    """Allow __in lookup for IDs"""
    def filter(self, qs, value):
        if isinstance(value, list):
            value_lst = [from_global_id(v)[1] for v in value]
            return super(GlobalIDFilter, self).filter(qs, value_lst)
        else:
            return super().filter(qs, value)

# Fix the mixin defaults
GrapheneFilterSetMixin.FILTER_DEFAULTS.update({
    AutoField: {"filter_class": CustomGlobalIDFilter},
    OneToOneField: {"filter_class": CustomGlobalIDFilter},
    ForeignKey: {"filter_class": CustomGlobalIDFilter},
})