Krzysztof Żuraw Blog

Django, GraphQL & React - part two

July 30, 2017

In this post, I will guide you through setting up GraphQL with Django. Let’s get started!

I will use a library called graphene-django. It will help a lot and allow me to get the job done instead of writing boilerplate code.

I decided I will have 3 django applications: actors, films and movies database. The first two should be self-explanatory, the last one is simple integration with third-party api - The Movie Data Base.

Let’s start from actors application - it will have actor model:

class Actor(models.Model):
    RATING_CHOICES = (
        (1, 1),
        (2, 2),
        (3, 3),
        (4, 4),
        (5, 5)
    )
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    age = models.PositiveSmallIntegerField()
    rating = models.PositiveSmallIntegerField(choices=RATING_CHOICES)

    def __str__(self):
        return f'Actor: {self.first_name} {self.last_name}'

Here is the most important part of the design - schema:

import graphene
from graphene_django.types import DjangoObjectType

from .models import Actor


class ActorType(DjangoObjectType):
    class Meta:
        model = Actor


class Query(graphene.AbstractType):
    all_actors = graphene.List(ActorType)
    actor = graphene.Field(
        ActorType,
        id=graphene.Int(),
    )

    def resolve_all_actors(self, args, context, info):
        return Actor.objects.all()

    def resolve_actor(self, args, context, info):
        id = args.get('id')
        return Actor.objects.get(id=id)

It is for GraphQL to know how data in django look like. I have Query which is a way of saying to GraphQL that we want to ask for either all actors or for specific one. I also add a handy shortcut from graphene_django called DjangoObjectType - all I need is to provide a model and it will know which field particular model has.

I got also resolve_actor & resolve_all_actors so I can either query for all of them in GraphQL. I can go to http://127.0.0.1:8000/graphql and execute:

query{
    allActors{
        lastName
    }
}

to get response:

{
    "data": {
        "allActors": [
        {
            "lastName": "Travolta"
        },
        {
            "lastName": "Jackson"
        },
        {
            "lastName": "Thurman"
        },
        {
            "lastName": "Foxx"
        },
        {
            "lastName": "Waltz"
        },
        {
            "lastName": "DiCaprio"
        },
        {
            "lastName": "Pitt"
        },
        {
            "lastName": "Laurent"
        },
        {
            "lastName": "Russell"
        },
        {
            "lastName": "Leigh"
        }
        ]
    }
}

or just for one actor:

{
    actor(id: 1) {
        firstName
        lastName
    }
}

and get:

{
    "data": {
        "actor": {
        "firstName": "John",
        "lastName": "Travolta"
        }
    }
}

Exact the same thing I did for films application - schema looks like this:

class FilmType(DjangoObjectType):
    actors = graphene.List(ActorType)

    class Meta:
        model = Film

    @graphene.resolve_only_args
    def resolve_actors(self):
        return self.actors.all()


class Query(graphene.AbstractType):
    all_films = graphene.List(FilmType)
    film = graphene.Field(
        FilmType,
        id=graphene.Int(),
    )

    def resolve_all_films(self, args, context, info):
        return Film.objects.all()

    def resolve_film(self, args, context, info):
        id = args.get('id')
        return Film.objects.get(id=id)

Everything here is almost the same but I got many to many relation in a database between Film & Actor model. In order for GraphQL to understand it I need to use decorator resolve_only_args. As the name suggests function wrapped inside decorator will be resolved using arguments passed - in this case, I will be Film instance so I can get all actors that played in this movie:

{
    film(id: 1) {
        title
        actors {
            firstName
            lastName
        }
    }
}
{
    "data": {
        "film": {
            "title": "Pulp Fiction",
            "actors": [
                {
                "firstName": "John",
                "lastName": "Travolta"
                },
                {
                "firstName": "Samuel L.",
                "lastName": "Jackson"
                },
                {
                "firstName": "Uma",
                "lastName": "Thurman"
                }
            ]
        }
    }
}

The last bit missing is external api which I implemented in a way to cache as much as I can:

URL = "https://api.themoviedb.org/3/search/movie"


class ExternalMovie(object):

    session = requests.Session()

    def __init__(self, title):
        self._payload = {'api_key': settings.TMDB_API_KEY, 'query': title}

    @cached_property
    def description(self):
        response = self.session.get(URL, data=self._payload)
        response.raise_for_status()
        return response.json()['results'][0]['overview']

I use cached_property so next calls via GraphQL will be cached. Schema to this is very simple:

class Query(graphene.AbstractType):
    description = graphene.String(title=graphene.String())

    def resolve_description(self, args, context, info):
        title = args.get('title')
        external_movie = ExternalMovie(title=title)
        return external_movie.description

and allows me to query for description:

{
    description(title: "Pulp Fiction")
}
{
    "data": {
        "description": "..."
    }
}

That’s all for today! Feel free to comment - was this blog post helpful? Was something missing?

Repo with code can be found on github.

Tagged with django python graphql

I turned off Disqus comments. If you want to give me feedback please write to krzysztof.zuraw(at)fastmail.com or use Keybase.


Krzysztof ŻurawDelivered by Krzysztof Żuraw. Opinions are my own. You can follow updates via RSS feed.