Is there a way to elegantly use a django model as an input object type?

Viewed 673

Say I have,

class PersonNode(DjangoObjectType):
    class Meta:
        model = Person
        fields = ('first_name', 'last_name', 'age',
                  'sex', 'alive', 'unique_identifier',)
        filter_fields = ('first_name', 'last_name', 'age',
                         'sex', 'alive', 'unique_identifier',)
        interfaces = (graphene.relay.Node,)

class PersonNodeInput(graphene.InputObjectType):
    first_name = graphene.String()
    last_name = graphene.String()
    # rest of person model fields

class Valid(graphene.ObjectType):
    ok = graphene.Boolean()

class Query(graphene.ObjectType):
    validate_person = graphene.Field(Valid, args={
                                     "data": PersonNodeInput()})
    person = graphene.relay.Node.Field(PersonNode)
    all_people = DjangoFilterConnectionField(PersonNode)

    def resolve_validate_person(root, info, data):
        print("resolve validate person")
        return Valid(ok=True)

Is it possible to avoid writing out PersonNodeInput? It would be nice if you could subclass something like "DjangoInputObjectType" and specify the model and fields you want in a Meta attribute.

3 Answers

We've been using this Graphene Django CUD library that tries to remove some of the boilerplate involved in defining mutations.

Adapting your case to their example would look something like

from graphene_django_cud.mutations import DjangoCreateMutation

class CreatePersonMutation(DjangoCreateMutation):
    class Meta:
        model = Person

class PersonMutations(graphene.ObjectType):
    create_person = CreateUserMutation.Field()

schema = Schema(mutation=PersonMutations)

I don't know how well it works with Relay nodes, because we're not using Relay.

you might want to check out the dicussion here. charlesverdad's answer did the trick for me:

from rest_framework import serializers
from graphene_django.rest_framework.serializer_converter import convert_serializer_to_input_type

class PersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        exclude = ('profile',)

class PersonArgsInput(convert_serializer_to_input_type(PersonSerializer)):
    create = graphene.Boolean(required=True)

class PersonsMutation(graphene.Mutation):
    person_set = graphene.List(PersonType)
    class Arguments:
        person_set = graphene.List(PersonArgsInput)
    def mutate():
        ...

I like this way because I use the Django serializer anyway. All credits to Charles Verdad.

There is a PR in graphene-django https://github.com/graphql-python/graphene-django/pull/1325 (Adds DjangoFormInputObjectType (InputObjectType derived class) which can retrieve fields from django form):

class MyDjangoFormInput(DjangoFormInputObjectType):

    # any other fields can be placed here as well as
    # other djangoforminputobjects and intputobjects

    class Meta:
        form_class = MyDjangoModelForm


class SomeMutation(graphene.Mutation):
    class Arguments:
        data = MyDjangoFormInput(required=True)

    @staticmethod
    def mutate(_root, _info, data):
        form_inst = MyDjangoModelForm(data=data)
        if form_inst.is_valid():
            model_inst = form_inst.save(commit=False)

        # ...
Related