Django и Graphene: как работать с двунаправленными отношениями с полиморфными моделями?

У меня есть модель Django, которая выглядит так (конечно, упрощенно):

from django.db import models
from polymorphic.models import PolymorphicModel

class Tournament(models.Model):
    slug = models.CharField(max_length=100, unique=True)

class Event(PolymorphicModel):
    tournament = models.ForeignKey(Tournament, related_name='events')
    slug = models.CharField(max_length=100)

class PracticeEvent(Event):
    pass

class MatchEvent(Event):
    winner = models.CharField(max_length=100, null=True, blank=True, default=None)

Турниры состоят из двух видов мероприятий: тренировок и матчей. Я хотел бы представить эту модель с помощью GraphQL, используя Graphene. Вот что я придумал:

import graphene
from graphene_django import DjangoObjectType

from . import models

class TournamentType(DjangoObjectType):
    class Meta:
        model = models.Tournament
        exclude_fields = ('id',)

class EventType(graphene.Interface):
    tournament = graphene.Field(TournamentType, required=True)
    slug = graphene.String(required=True)

class PracticeEventType(DjangoObjectType):
    class Meta:
        model = models.PracticeEvent
        interfaces = (EventType,)
        exclude_fields = ('id',)

class MatchEventType(DjangoObjectType):
    class Meta:
        model = models.MatchEvent
        interfaces = (EventType,)
        exclude_fields = ('id',)

extra_types = {PracticeEventType, MatchEventType}

class Query(graphene.ObjectType):
    tournaments = graphene.List(TournamentType)
    events = graphene.List(EventType)
    # ... resolvers ...

schema = graphene.Schema(
    query=Query,
    types=schema_joust.extra_types,)

Все идет нормально; Я могу запросить events { ... } напрямую, и даже tournament доступен. Однако, поскольку DjangoObjectType с model = models.Event нет, я не могу запросить _7 _...

Как я могу это исправить? Я не могу сделать EventType a DjangoObjectTpe, и я не знаю, нужно ли добавлять поле events постфактум.


person Silly Freak    schedule 29.03.2018    source источник


Ответы (2)


Сами по себе EventType.tournament и TournamentType.events не так уж и сложны. Первый показан в вопросе, а второй можно реализовать так:

class EventType(graphene.Interface):
    slug = graphene.String(required=True)

class TournamentType(DjangoObjectType):
    class Meta:
        model = models.Tournament
        exclude_fields = ('id',)

    events = graphene.List(EventType)

    def resolve_events(self, info):
        return self.events.all()

graphene-django не распознает отношения, но объявление и разрешение поля вручную делают трюк. Чтобы также получить обратное поле, которое работало бы, если бы нам не нужно было ссылаться на TournamentType, я покопался в graphene-django и нашел graphene_django.converter.convert_django_field_with_choices. Это позволяет нам определить поле следующим образом:

import graphene
from graphene_django import DjangoObjectType, converter, registry

from . import models

class EventType(graphene.Interface):
    tournament = converter.convert_django_field_with_choices(
        models.Event.tournament.field, registry.get_global_registry())
    slug = graphene.String(required=True)
person Silly Freak    schedule 30.03.2018

Возможно, вам нужен Union тип в сочетании с явным объявлением EventType наследования от интерфейса:

import graphene

# Rename your existing EventType to EventTypeInterface and redefine like 
class EventType(DjangoObjectType):
    class Meta:
        model = Event
        interfaces = [EventTypeInterface]


class EventUnionType(graphene.Union):
    @classmethod
    def resolve_type(cls, instance, info):
        if isinstance(instance, MatchEvent):
            return MatchEventType
        elif isinstance(instance, PracticeEvent):
            return PracticeEventType
        return EventType

    class Meta:
        types = [MatchEventType, PracticeEventType, EventType]
person Mark Chackerian    schedule 30.03.2018
comment
Мне не совсем удалось это реализовать, но я думаю, что это не будет проверять тип: Tournament будет иметь поле типа EventType, а не EventUnionType, верно? Так что он вообще не будет пытаться использовать EventUnionType.resolve_type. - person Silly Freak; 30.03.2018