【问题标题】:How to filter a query by a list of ids in GraphQL using graphene-django?如何使用graphene-django通过GraphQL中的id列表过滤查询?
【发布时间】:2019-06-10 14:59:43
【问题描述】:

我正在尝试使用 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()

【问题讨论】:

  • 首先,您需要使用列表字段扩展SampleType——类似于ids = graphene.List(graphene.ID())
  • @MarkChackerian 你能详细说明一下并提供解决方案吗?我遇到了同样的问题,看不到添加 ids 字段如何改变任何东西。似乎 graphene-django 在输入字段中有一些输入验证问题?

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


【解决方案1】:

GlobalIDMultipleChoiceFilter 来自 django-graphene 有点解决这个问题,如果你在字段名称中输入“in”。您可以创建过滤器,如

from django_filters import FilterSet
from graphene_django.filter import GlobalIDMultipleChoiceFilter

class BookFilter(FilterSet):
    author = GlobalIDMultipleChoiceFilter()

并由

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

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

【讨论】:

    【解决方案2】:

    现有的答案似乎都不适合我,但是通过一些细微的改变,我设法解决了我的问题,如下所示:

    您可以为您的对象类型创建自定义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】:

      我在实现“in”过滤器时也遇到了麻烦——它现在似乎在 graphene-django 中被错误地实现并且不能按预期工作。以下是使其工作的步骤:

      1. 从您的 filter_fields 中删除“in”过滤器
      2. 向您的 DjangoFilterConnectionField 添加一个名为“id__in”的输入值,并使其成为 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 调用过滤器。请注意在签名中使用实际数组(我认为这是一个比发送逗号分隔的值字符串更好的 API)。此解决方案仍允许您将其他过滤器添加到请求中,它们将正确链接在一起。

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

      【讨论】:

        【解决方案4】:

        您可以轻松地使用过滤器,只需将其与您的节点一起使用。

        class ReportFileFilter(FilterSet):
            id = GlobalIDMultipleChoiceFilter()
        

        然后在您的查询中使用 -

        class Query(graphene.ObjectType):
            all_report_files = DjangoFilterConnectionField(ReportFileNode, filterset_class=ReportFileFilter)
        

        这是用于graphql django的中继实现。

        【讨论】:

          【解决方案5】:

          另一种方法是告诉 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},
          })
          

          【讨论】:

            猜你喜欢
            • 2019-02-26
            • 2023-03-11
            • 2023-03-30
            • 2017-03-15
            • 2021-11-09
            • 2021-12-17
            • 2012-03-07
            • 2019-02-06
            • 2017-02-06
            相关资源
            最近更新 更多