Search code examples
djangographene-pythongraphene-django

Question about graphene-django mutation documentation


While I've been able to make my application work, I have some concerns about doing things the right way. Therefore there is something I "do not understand" :

In the documentation, here, in the QuestionMutation class, there is a question attribute. What does that actually mean ? It says that it defines the response and I don't understand what this means.

Code from the documentation :

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)

    @classmethod
    def mutate(cls, root, 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)

And in my code I've been able to do this :

My type :

class UserType(DjangoObjectType):

    class Meta:
        model = User
        fields = (
            'id',
            'username',
            'password',
            'email',
            'first_name',
            'last_name',
            'is_active',
            'group_ids',
        )

    full_name = graphene.String()           # Python property
    full_identification = graphene.String() # Python property

My mutation :

class UpdateUser(graphene.Mutation):
    # --------> I could comment these and no problem. Why ? <--------
    # id = graphene.ID()
    # username = graphene.String()
    # email = graphene.String()
    # first_name = graphene.String()
    # last_name = graphene.String()
    # is_active = graphene.Boolean()

    class Arguments:
        id = graphene.ID()
        username = graphene.String()
        email = graphene.String()
        first_name = graphene.String()
        last_name = graphene.String()
        is_active = graphene.Boolean()

    class Meta:
        output = UserType

    @login_required
    def mutate(self, info, **kwargs):
        user = get_object_or_404(User, id=kwargs['id'])
        for attr, value in kwargs.items():
            setattr(user, attr, value)
        user.save()
        return user
        # I'm not returning explicit UserType, is it a problem ?
        # I actually do it with the Meta class. I guess it is the same for this ?

and it works whereas I didn't specify anything for the response attributes. And I don't even return the same kind of thing. Can someone explain if I'm doing it wrong ?

You can see here that if I call this mutation :

mutation {
  updateUser (
    id: 42
    username: "updated_user"
    firstName: "updated"
    lastName: "user"
    email: "updated.user@test.com"
  ) {
    id
    username
    firstName
    lastName
    email
  }
}

I'm able to get this answer even without return values :

{
  "data": {
    "updateUser": {
      "id": "42",
      "username": "updated_user",
      "firstName": "updated",
      "lastName": "user",
      "email": "updated.user@test.com"
    }
  }
}

Solution

  • I also asked the question HERE and it appears that there is more information about these configuration THERE.

    Finally, since I've set the ouput = UserType and model = User, my UserType and User classes are duck typed and it works like a charm even without defining return fields (the ones commented in the question).

    In other words, this sample of code appears to be a correct way to do this, and the official documentation doesn't do it like this but it is intented to work like this too (as you can read in the code docs).

    class UserType(DjangoObjectType):
    
        class Meta:
            model = User
            fields = (
                'id',
                'username',
                'password',
                'email',
                'first_name',
                'last_name',
                'is_active',
                'group_ids',
            )
    
        full_name = graphene.String()           # Python property
        full_identification = graphene.String() # Python property
    
    class UpdateUser(graphene.Mutation):
            # We can omit return fields since UserType is defined as output ('output = UserType')
            # and UserType has already defined fields in its class.
    
        class Arguments:
            id = graphene.ID()
            username = graphene.String()
            email = graphene.String()
            first_name = graphene.String()
            last_name = graphene.String()
            is_active = graphene.Boolean()
    
        class Meta:
            output = UserType
    
        @login_required
        def mutate(self, info, **kwargs):
            user = get_object_or_404(User, id=kwargs['id'])
            for attr, value in kwargs.items():
                setattr(user, attr, value)
            user.save()
            return user
            # Returning a User can be done since it is linked to the UserType and we
            # said we return a UserType with 'output = UserType'