Mutations

Introduction

Graphene-Django makes it easy to perform mutations.

With Graphene-Django we can take advantage of pre-existing Django features to quickly build CRUD functionality, while still using the core graphene mutation features to add custom mutations to a Django project.

Simple example

import graphene

from graphene_django import DjangoObjectType

from .models import Question


class QuestionType(DjangoObjectType):
    class Meta:
        model = Question


class QuestionMutation(graphene.Mutation):
    class Arguments:
        # The input arguments for this mutation
        text = graphene.String(required=True)
        id = graphene.ID()

    # The class attributes define the response of the mutation
    question = graphene.Field(QuestionType)

    def mutate(self, info, text, id):
        question = Question.objects.get(pk=id)
        question.text = text
        question.save()
        # Notice we return an instance of this mutation
        return QuestionMutation(question=question)


class Mutation:
    update_question = QuestionMutation.Field()

Django Forms

Graphene-Django comes with mutation classes that will convert the fields on Django forms into inputs on a mutation.

DjangoFormMutation

from graphene_django.forms.mutation import DjangoFormMutation

class MyForm(forms.Form):
    name = forms.CharField()

class MyMutation(DjangoFormMutation):
    class Meta:
        form_class = MyForm

MyMutation will automatically receive an input argument. This argument should be a dict where the key is name and the value is a string.

DjangoModelFormMutation

DjangoModelFormMutation will pull the fields from a ModelForm.

from graphene_django.forms.mutation import DjangoModelFormMutation

class Pet(models.Model):
    name = models.CharField()

class PetForm(forms.ModelForm):
    class Meta:
        model = Pet
        fields = ('name',)

# This will get returned when the mutation completes successfully
class PetType(DjangoObjectType):
    class Meta:
        model = Pet

class PetMutation(DjangoModelFormMutation):
    pet = Field(PetType)

    class Meta:
        form_class = PetForm

PetMutation will grab the fields from PetForm and turn them into inputs. If the form is valid then the mutation will lookup the DjangoObjectType for the Pet model and return that under the key pet. Otherwise it will return a list of errors.

You can change the input name (default is input) and the return field name (default is the model name lowercase).

class PetMutation(DjangoModelFormMutation):
    class Meta:
        form_class = PetForm
        input_field_name = 'data'
        return_field_name = 'my_pet'

Form validation

Form mutations will call is_valid() on your forms.

If the form is valid then the class method perform_mutate(form, info) is called on the mutation. Override this method to change how the form is saved or to return a different Graphene object type.

If the form is not valid then a list of errors will be returned. These errors have two fields: field, a string containing the name of the invalid form field, and messages, a list of strings with the validation messages.

Django REST Framework

You can re-use your Django Rest Framework serializer with Graphene Django mutations.

You can create a Mutation based on a serializer by using the SerializerMutation base class:

from graphene_django.rest_framework.mutation import SerializerMutation

class MyAwesomeMutation(SerializerMutation):
    class Meta:
        serializer_class = MySerializer

Create/Update Operations

By default ModelSerializers accept create and update operations. To customize this use the model_operations attribute on the SerializerMutation class.

The update operation looks up models by the primary key by default. You can customize the look up with the lookup_field attribute on the SerializerMutation class.

from graphene_django.rest_framework.mutation import SerializerMutation
from .serializers imoprt MyModelSerializer


class AwesomeModelMutation(SerializerMutation):
    class Meta:
        serializer_class = MyModelSerializer
        model_operations = ['create', 'update']
        lookup_field = 'id'

Overriding Update Queries

Use the method get_serializer_kwargs to override how updates are applied.

from graphene_django.rest_framework.mutation import SerializerMutation
from .serializers imoprt MyModelSerializer


class AwesomeModelMutation(SerializerMutation):
    class Meta:
        serializer_class = MyModelSerializer

    @classmethod
    def get_serializer_kwargs(cls, root, info, **input):
        if 'id' in input:
            instance = Post.objects.filter(
                id=input['id'], owner=info.context.user
            ).first()
            if instance:
                return {'instance': instance, 'data': input, 'partial': True}

            else:
                raise http.Http404

        return {'data': input, 'partial': True}

Relay

You can use relay with mutations. A Relay mutation must inherit from ClientIDMutation and implement the mutate_and_get_payload method:

import graphene import relay, DjangoObjectType
from graphql_relay import from_global_id

from .queries import QuestionType


class QuestionMutation(relay.ClientIDMutation):
    class Input:
        text = graphene.String(required=True)
        id = graphene.ID()

    question = graphene.Field(QuestionType)

    @classmethod
    def mutate_and_get_payload(cls, root, info, text, id):
        question = Question.objects.get(pk=from_global_id(id))
        question.text = text
        question.save()
        return QuestionMutation(question=question)

Notice that the class Arguments is renamed to class Input with relay. This is due to a deprecation of class Arguments in graphene 2.0.

Relay ClientIDMutation accept a clientIDMutation argument. This argument is also sent back to the client with the mutation result (you do not have to do anything). For services that manage a pool of many GraphQL requests in bulk, the clientIDMutation allows you to match up a specific mutation with the response.