Skip to content
Advertisement

How to use a django abstract class with graphene-django?

I’m trying to have a unique interface for two concrete classes that are similar and inherit from a common abstract class.

My django model classes:

class Metadata(models.Model):
    name = models.CharField(max_length=255)
    sequence = models.PositiveSmallIntegerField()
    is_choices = False

    class Meta:
        abstract = True


class MetadataScalar(Metadata):
    string_format = models.CharField(max_length=255, blank=True, null=True)


class MetadataChoices(Metadata):
    is_choices = True
    choices = models.CharField(max_length=255, blank=True, null=True)

My graphene-django api:

class MetadataNode(DjangoObjectType):
    class Meta:
        interfaces = (Node,)
        connection_class = Connection
        model = Metadata
        fields = '__all__'


class MetadataScalarNode(MetadataNode):
    class Meta:
        interfaces = (Node,)
        connection_class = Connection
        model = MetadataScalar
        fields = '__all__'


class MetadataChoicesNode(MetadataNode):
    class Meta:
        interfaces = (Node,)
        connection_class = Connection
        model = MetadataChoices
        fields = '__all__'


class CreateMetadata(ClientIDMutation):
    metadata = Field(MetadataNode)

    class Input:
        name = String(max_length=255, required=True)
        sequence = Int(required=True)
        string_format = String()
        choices = List(String)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **input):
        if 'string_format' in input:
            metadata = MetadataScalar.objects.create(
                name=input.get('name'),
                sequence=input.get('sequence'),
                string_format=input.get('string_format')
            )
        elif 'choices' in input:
            metadata = MetadataChoices.objects.create(
                name=input.get('name'),
                sequence=input.get('sequence'),
                choices=','.join(input.get('choices'))
            )
        return CreateMetadata(metadata=metadata)

When querying the graphql mutation corresponding to CreateMetadata, the metadata concrete class is successfully created. ;-)

The problem is that when the query asks for the created concrete Metadata in the result (here either MetadataScalar or MetadataChoices), graphql cannot find a node for the concrete class and outputs the following error message:

Expected value of type "MetadataNode" but got: MetadataScalar.

For your information, here is one example query:

mutation {
  createMetadata (input: {
    stringFormat: "foo"
    sequence: 12
    name: "bar"
  }) {
    metadata {
      name
      sequence
    }
  }
}

How to make it work nicely, without having to specify two different result types (metadataScalar and metadataChoices variables) in the second part of the query?

Advertisement

Answer

You can use Union in order to be able to specify multiple different result classes.

In your case, that woud be:

class MetadataScalarNode(DjangoObjectType):
    class Meta:
        interfaces = (Node,)
        connection_class = Connection
        model = MetadataScalar
        fields = '__all__'


class MetadataChoicesNode(DjangoObjectType):
    class Meta:
        interfaces = (Node,)
        connection_class = Connection
        model = MetadataChoices
        fields = '__all__'


class MetadataNode(Union):
    class Meta:
        types = (MetadataScalarNode, MetadataChoicesNode)

The graphql query wil look like:

mutation {
  createMetadata (input: {
    stringFormat: "foo"
    sequence: 12
    name: "bar"
  }) {
    metadata {
      __typename
      ... on MetadataScalarNode {
        name
        sequence
        stringFormat
      }
      ... on MetadataChoicesNode {
        name
        sequence
        choices
      }
    }
  }
}

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement