Как отфильтровать запрос по списку идентификаторов в GraphQL с помощью graphene-django?

Я пытаюсь выполнить запрос GraphQL, используя Django и Graphene. Для запроса одного объекта с использованием идентификатора я сделал следующее:

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

И это просто отлично работает. Проблема возникает, когда я пытаюсь выполнить запрос с несколькими идентификаторами, например:

{
  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()

0 ответов

GlobalIDMultipleChoiceFilterfrom 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
      }
    }
  }
}

Все еще не идеально, но необходимость в индивидуальном коде сведена к минимуму.

Вы можете легко использовать фильтр, просто поместив его в свои узлы.

class ReportFileFilter(FilterSet):
    id = GlobalIDMultipleChoiceFilter()

Затем в вашем запросе просто используйте -

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

Это для релейной реализации graphql django.

Ни один из существующих ответов, похоже, не помог мне, поскольку они были представлены, однако с некоторыми небольшими изменениями мне удалось решить мою проблему следующим образом:

Вы можете создать собственный 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,)

У меня также возникли проблемы с реализацией фильтра "in" - он, похоже, неправильно реализован в graphene-django прямо сейчас и не работает должным образом. Вот шаги, чтобы заставить его работать:

  1. Удалите фильтр 'in' из вашего filter_fields
  2. Добавьте входное значение в свой DjangoFilterConnectionField под названием id__in и сделайте его списком идентификаторов.
  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
      }
    }
  }
} 

Другой способ - указать фильтру Relay в graphene_django, что он также работает со списком. Этот фильтр регистрируется в миксине в graphene_django и применяется к любому определенному вами фильтру.

Итак, вот мое решение:

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},
})
Другие вопросы по тегам