Django & Graphene: How to handle bidirectional relationship with polmorphic models?
Asked Answered
R

2

5

I have a Django model that looks like this (simplified of course):

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)

Tournaments consist of two kinds of events: practice events, and matches. I'd like to expose this model using GraphQL, using Graphene. This is what I have come up with:

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,)

So far, so good; I can query events { ... } directly, and even the tournament is available. However, as there is no DjangoObjectType with model = models.Event, I can't query tournaments { events {...} }...

How can I fix this? I can't make EventType a DjangoObjectTpe, and I don't know to add the events field after the fact.

Rogue answered 29/3, 2018 at 18:6 Comment(0)
R
7

On their own, EventType.tournament and TournamentType.events aren't so hard. The first one is shown in the question, and the second one can be implemented like this:

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 doesn't recognize the relationship, but declaring and resolving the field manually does the trick. To also get the reverse-field, which would work if we didn't need to reference TournamentType, I digged into graphene-django and found graphene_django.converter.convert_django_field_with_choices. This lets us define the field like this:

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)
Rogue answered 30/3, 2018 at 22:8 Comment(0)
C
1

Perhaps a Union type is what you want, combined with declaring EventType explicitly to inherit from an interface:

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]
Celestina answered 30/3, 2018 at 13:39 Comment(1)
I didn't quite manage to implement that, but I think it wouldn't typecheck: Tournament would have a field of type EventType, not of EventUnionType, right? So it wouldn't try to use EventUnionType.resolve_type at all.Rogue

© 2022 - 2024 — McMap. All rights reserved.