Django Graphene возвращает данные из нескольких моделей под одним гнездом
Я пытаюсь использовать Python Graphene GraphQL для реализации конечной точки поиска, возвращающей все продукты на основе названия. Однако в моей базе данных есть три таблицы продуктов, которые содержат различные типы продуктов - карты, токены, запечатанные продукты.
Я хочу вернуть данные в одном гнезде в Json-ответе. Релейное соединение я использую с https://github.com/saltycrane/graphene-relay-pagination-example/blob/artsy-example/README.md.
Что-то вроде:
Код:
class MagicCards(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
mana_cost_list = graphene.List(graphene.String)
class Meta:
model = magic_sets_cards
interfaces = (relay.Node,)
filter_fields = {'name': ['icontains']}
connection_class = ArtsyConnection
class MagicTokens(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
class Meta:
model = magic_sets_tokens
interfaces = (relay.Node,)
filter_fields = {'name': ['icontains']}
connection_class = ArtsyConnection
class SearchQuery(ObjectType):
magic_cards = ArtsyConnectionField(MagicCards)
magic_tokens = ArtsyConnectionField(MagicTokens)
# pseudo code:
all_products = combine(magic_cards, magic_tokens)
@staticmethod
def resolve_all_products(self, info, **kwargs):
return
@staticmethod
def resolve_magic_cards(self, info, **kwargs):
sql_number_to_int = "CAST((REGEXP_MATCH(number, '\d+'))[1] as INTEGER)"
excluded_sides = ['b', 'c', 'd', 'e']
return magic_sets_cards.objects.exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('-set_id__release_date', 'set_id__name', 'int', 'number').all()
@staticmethod
def resolve_magic_tokens(self, info, **kwargs):
sql_number_to_int = "CAST((REGEXP_MATCH(number, '\d+'))[1] as INTEGER)"
excluded_sides = ['b', 'c', 'd', 'e']
return magic_sets_tokens.objects.exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('-set_id__release_date', 'set_id__name', 'int', 'number').all()
searchSchema = graphene.Schema(query=SearchQuery)
Query:
{
allProducts(name_Icontains: "Spellbook", first: 12, after: "") {
pageCursors {
previous {
cursor
}
first {
cursor
page
}
around {
cursor
isCurrent
page
}
last {
cursor
page
}
next {
cursor
}
}
edges {
node {
... on MagicCards {
name
}
... on MagicTokens {
name
}
}
}
}
}
Теперь я мог бы сделать следующий запрос, однако это означало бы, что каждый тип продукта будет находиться в отдельном гнезде в Json-ответе с собственными курсорами страниц, что мне не нужно.
{
magicCards(name_Icontains: "Spellbook", first: 12, after: "") {
pageCursors {
...
}
edges {
node {
name
}
}
}
magicTokens(name_Icontains: "Spellbook", first: 12, after: "") {
pageCursors {
...
}
edges {
node {
name
}
}
}
}
Вам необходимо использовать тип Union. Попробуйте следующее:
class MagicCards(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
mana_cost_list = graphene.List(graphene.String)
class Meta:
model = magic_sets_cards
interfaces = (relay.Node,)
class MagicTokens(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
class Meta:
model = magic_sets_tokens
interfaces = (relay.Node,)
class SearchType(graphene.Union):
class Meta:
types = (MagicCards, MagicTokens)
class SearchConnection(graphene.Connection):
class Meta:
node = SearchType
class SearchQuery(ObjectType):
all_products = graphene.ConnectionField(SearchConnection, name__icontains=String())
@staticmethod
def resolve_all_products(self, info, **kwargs):
# do filtering with kwargs['name__icontains']
sql_number_to_int = "CAST((REGEXP_MATCH(number, '\d+'))[1] as INTEGER)"
excluded_sides = ['b', 'c', 'd', 'e']
items = list( magic_sets_cards.objects.exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('-set_id__release_date', 'set_id__name', 'int', 'number').all())
items.extend(magic_sets_tokens.objects.exclude(side__in=excluded_sides).extra(select={'int': sql_number_to_int}).order_by('-set_id__release_date', 'set_id__name', 'int', 'number').all())
return items
searchSchema = graphene.Schema(query=SearchQuery)
Вы должны избегать DjangoConnectionField или DjangoFilterConnectionField, поскольку они не принимают типы Union. Логика фильтрации должна быть реализована в логине, что вы можете легко сделать с помощью django-filter. Возвращаемый pageInfo объект будет startCursor, endCursor, hasNextPage, hasPreviousPage по умолчанию. Мне нужно будет увидеть ваш класс ArtsyConnection, чтобы настроить его.