Как отфильтровать запрос по списку идентификаторов в 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 ответов
GlobalIDMultipleChoiceFilter
from 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 прямо сейчас и не работает должным образом. Вот шаги, чтобы заставить его работать:
- Удалите фильтр 'in' из вашего filter_fields
- Добавьте входное значение в свой DjangoFilterConnectionField под названием id__in и сделайте его списком идентификаторов.
- Переименуйте свой преобразователь в соответствии с полем "образцы".
- Обработайте фильтрацию по 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},
})