Codementor Events

Mutation and Query In GraphQL Using Python/Django PART 2.

Published Nov 25, 2017Last updated May 24, 2018
Mutation and Query In GraphQL Using Python/Django PART 2.

Previously, we looked at the code implementation of GraphQL’s query method from the server side and also how to use the Interactive Graphql Console to query(get) the data from the database via the client. In this part we shall be considering GraphQL Mutation!

Source Code/Source Repo

GraphQL Mutation

Mutation in graphql is the equivalent of the PUT, DELETE, UPDATE, POST and PATCH Http verbs/methods for RESTFUL services. we can also say mutation is what graphql uses to modify server-side data as well.

Django-Server-Implementation(GraphQL)

As with the book model from the previous part where we created a query, let’s also create a mutation for it as well…

Back to the schema.py file let’s introduce a Create and an Update Mutation for the book model on our server

# schema.py
...
import graphene
from graphene import InputObjectType

class BookCreateInput(InputObjectType):

    """
    Class defined to accept input data 
    from the interactive graphql console.
    """

    title = graphene.String(required=False)    
    isbn = graphene.String(required=False)    
    category = graphene.String(required=False)
    

Data sent in from the client would usually pass through this class definition as input data, here we define the fields that we are expecting from the client for any book mutation, remember the book model has this same fields.

With the input class defined,the next thing to do is to define the Mutation for the book creation and update process.

schema.py

class CreateBook(relay.ClientIDMutation):
  
    class Input:
        # BookCreateInput class used as argument here.
        book = graphene.Argument(BookCreateInput)

    new_book = graphene.Field(BookNode)

    @classmethod
    def mutate_and_get_payload(cls, args, context, info):

        book_data = args.get('book') # get the book input from the args
        book = Book() # get an instance of the book model here
        new_book = update_create_instance(book, book_data) # use custom function to create book

        return cls(new_book=new_book) # newly created book instance returned.


class UpdateBook(relay.ClientIDMutation):

    class Input:
        book = graphene.Argument(BookCreateInput) # get the book input from the args
        id = graphene.String(required=True) # get the book id

    errors = graphene.List(graphene.String)
    updated_book = graphene.Field(BookNode)

    @classmethod
    def mutate_and_get_payload(cls, args, context, info):

        try:
            book_instance = get_object(Book, args['id']) # get book by id
            if book_instance:
                # modify and update book model instance
                book_data = args.get('book')
                updated_book = update_create_instance(book_instance, book_data)
                return cls(updated_book=updated_book)
        except ValidationError as e:
            # return an error if something wrong happens
            return cls(updated_book=None, errors=get_errors(e))

# helper function

"""Script defined to create helper functions for graphql schema."""

from graphql_relay.node.node import from_global_id

def get_object(object_name, relayId, otherwise=None):
    try:
        return object_name.objects.get(pk=from_global_id(relayId)[1])
    except:
        return otherwise

def update_create_instance(instance, args, exception=['id']):
    if instance:
        [setattr(instance, key, value) for key, value in args.items() if key not in exception]you

    
    # caution if you literally cloned this project, then be sure to have
    # elasticsearch running as every saved instance must go through 
    # elasticsearch according to the way this project is configured.
    instance.save()

    return instance

def get_errors(e):
    # transform django errors to redux errors
    # django: {"key1": [value1], {"key2": [value2]}}
    # redux: ["key1", "value1", "key2", "value2"]
    fields = e.message_dict.keys()
    messages = ['; '.join(m) for m in e.message_dict.values()]
    errors = [i for pair in zip(fields, messages) for i in pair]
    return errors

With the Mutation classes defined, the final step would be to add the Mutation class to the schema file.

class Mutation(ObjectType):
    create_book = CreateBook.Field()     
    update_book = UpdateBook.Field()   

schema = graphene.Schema(    
    query=Query,    
    mutation=Mutation,
)

TEST YOUR NEW FEATURE…

Time for some fun!!

Well, we have pretty much come to the end of the server side implementation now it’s time to test out our new mutation functionality…

Run the server as usual and navigate to localhost:8000/graphql and let’s make a mutation to create a new book on the server


Create New Book

The Mutation

mutation CreateNewBook($input: CreateBookInput!){
  createBook(input: $input) {
    newBook {
      id
      title
      isbn
    }
    clientMutationId 
  }
}

and Query Variable

{"input": { "book": 
  { 
    "title": "The Famous Five", 
    "isbn": "34vr34jsad", 
    "category": "Fantasy"
  }
}}

Tells the server to create a new book with the data passed via the query variable which is graphql’s way of dynamically setting data.

Naming a mutation “mutation CreateNewBook” is a way of identifying which mutation process is been called and the definition “($input: CreateBookInput!)” pair is used to tell the mutation that we are going to pass in an “input” that is of type CreateBookInput! (NOT NULLABLE).

The next line createBook is used to create the book where we pass in the variable “$input” via the “query variable” defined.

The newBook object tells graphql what we want to be returned after the book instance is created.

To update an existing book on the server

The Mutation

mutation UpdateBookById($input: UpdateBookInput!) {
  updateBook(input: $input) {
    updatedBook {
      title
    }
  }
}

and Query Variable

{"input": { "id": "Qm9va05vZGU6Tm9uZQ==", "book": {
  "title": "The Famous 5",
  "isbn": "599223"
}}}

Allows us to modify an already existing data.

Conclusion

This is where we put a hard stop to this tutorial, we have looked at query and mutation with graphql as well as the server side implementation of graphql using Python/Django. We certainly did not look at how to delete data from the server side and I suppose you can experiment with that to enforce the idea.

Thanks for reading and feel free to like and share this post.

Discover and read more posts from Samuel Afuavare James
get started
post commentsBe the first to share your opinion
Show more replies