1

**FOLDER STRUCTURE **

models.py

import graphene
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType

from general.models import Character, Director, Episode


class CharacterType(DjangoObjectType):
    pk = graphene.Int(source="pk", required=True)

    class Meta:
        model = Character
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "character_species": ["exact", "icontains"],
            "location": ["exact"],
            "status": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)


class EpisodeType(DjangoObjectType):
    pk = graphene.Int(source="pk")

    class Meta:
        model = Episode
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "directed_by__name": ["exact", "icontains"],
            "aired_date": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)


class DirectorType(DjangoObjectType):
    pk = graphene.Int(source="pk", required=True)

    class Meta:
        model = Director
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "first_directed_episode__name": ["exact", "icontains"],
            "last_directed_episode__name": ["exact", "icontains"],
            "age": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)

types.py

import graphene
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType

from general.models import Character, Director, Episode


class CharacterType(DjangoObjectType):
    pk = graphene.Int(source="pk", required=True)

    class Meta:
        model = Character
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "character_species": ["exact", "icontains"],
            "location": ["exact"],
            "status": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)


class EpisodeType(DjangoObjectType):
    pk = graphene.Int(source="pk")

    class Meta:
        model = Episode
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "directed_by__name": ["exact", "icontains"],
            "aired_date": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)


class DirectorType(DjangoObjectType):
    pk = graphene.Int(source="pk", required=True)

    class Meta:
        model = Director
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "first_directed_episode__name": ["exact", "icontains"],
            "last_directed_episode__name": ["exact", "icontains"],
            "age": ["exact"],
        }
        fields = "__all__"
        interfaces = (relay.Node,)

loaders.py

from collections import defaultdict
from promise import Promise
from promise.dataloader import DataLoader
from general.models import Episode


class EpisodesByDirectorIdLoader(DataLoader):
    def batch_load_fn(self, director_ids):
        episodes_by_director_ids = defaultdict(list)
        all_episodes_of_directors = Episode.objects.filter(
            directed_by__pk__in=director_ids
        )
        for episode in all_episodes_of_directors.iterator():
            episodes_by_director_ids[episode.directed_by.pk].append(episode)

        return Promise.resolve(
            [
                episodes_by_director_ids.get(director_id, [])
                for director_id in director_ids
            ]
        )

queries.py

import graphene
from graphene_django.filter import DjangoFilterConnectionField

from general.models import Character, Director, Episode

from .types import EpisodeType


class Query(graphene.ObjectType):
    all_episodes = DjangoFilterConnectionField(EpisodeType)

    @staticmethod
    def resolve_all_episodes(root, info):
        # return Episode.objects.all() # RETURNS QUERYSET
        return info.context.episodes_by_director_id_loader.load(1) # RETURNS PROMISE OBJECT

QUERY

query{
  allEpisodes{
    edges{
      node{
        name
      }
    }
  }
}

OUTPUT

{
  "errors": [
    {
      "message": "Cannot return null for non-nullable field EpisodeTypeConnection.edges.",
      "locations": [
        {
          "line": 26,
          "column": 5
        }
      ],
      "path": [
        "allEpisodes",
        "edges"
      ]
    }
  ],
  "data": {
    "allEpisodes": null
  }
}

Created models named Episode, Character and Director. Wrote types, queries and mutations which worked perfectly before using dataloader, still works perfectly when returning queryset in resolver but shows some kind of error.

The expected result was to use dataloader to improve the performance, the resole method returns Promise object and the query after execution shows the above output

HarilalAV
  • 41
  • 1
  • 7
  • Seems to me like the issue is the combo with `DjangoFilterConnection` as outlined here: https://github.com/graphql-python/graphene-django/issues/159 – rymanso Nov 15 '22 at 13:01

1 Answers1

0

FIXED BY EDITING RESOLVER AND LOADER AND CHANGING THE GRAPHENE AND DJANGO VERSION

loaders.py

from collections import defaultdict
from promise import Promise
from promise.dataloader import DataLoader
from general.models import Episode, Director


class EpisodesByDirectorIdLoader(DataLoader):
    def batch_load_fn(self, director_ids):
        episodes_directed_by_director_ids = defaultdict(list)

        episodes = Episode.objects.filter(directed_by_id__in=director_ids)
        for episode in episodes.iterator():episodes_directed_by_director_ids[episode.directed_by_id].append(episode)
        
        return Promise.resolve(
            [episodes_directed_by_director_ids.get(director_id, []) for director_id in director_ids]
        )

Resolver

class DirectorType(DjangoObjectType):
pk = graphene.Int(source="pk", required=True)
episodes_directed = graphene.List(EpisodeType)

class Meta:
    model = Director
    filter_fields = {
        "name": ["exact", "icontains", "istartswith"],
        "first_directed_episode__name": ["exact", "icontains"],
        "last_directed_episode__name": ["exact", "icontains"],
        "age": ["exact"],
    }
    only_fields = (
        "id",
        "name",
        "age",
        "first_directed_episode",
        "last_directed_episode",
    )
    interfaces = (relay.Node,)
    use_connection = True

@staticmethod
def resolve_episodes_directed(root, info, **kwargs):
    # return Episode.objects.filter(directed_by_id=root.id)
    return info.context.episodes_by_director_id_loader.load(root.id)

requirements.txt

aiodataloader==0.2.1
aniso8601==7.0.0
asgiref==3.5.2
backports.zoneinfo==0.2.1
black==22.10.0
click==8.1.3
Cython==0.29.32
Django==3.2.16
django-debug-toolbar==3.7.0
django-filter==22.1
django-graphiql-debug-toolbar==0.2.0
django-utils-six==2.0
factory-boy==3.2.1
Faker==15.3.2
flake8==5.0.4
graphene==2.1.9
graphene-django==2.2.0
graphql-core==2.3.2
graphql-relay==2.0.1
isort==5.10.1
mccabe==0.7.0
mypy-extensions==0.4.3
pathspec==0.10.1
platformdirs==2.5.2
promise==2.3
psycopg2-binary==2.9.5
pycodestyle==2.9.1
pyflakes==2.5.0
python-dateutil==2.8.2
pytz==2022.6
Rx==1.6.1
singledispatch==3.7.0
six==1.16.0
sqlparse==0.4.3
text-unidecode==1.3
tomli==2.0.1
typing-extensions==4.4.0 
HarilalAV
  • 41
  • 1
  • 7