5

I've defined Django models with fields containing text choices corresponding to enums. The GraphQL API provides mutations (which are not derived from models directly) with arguments of type enum which shall accept the same values as the models only. How can I get rid of my dublication?

models.py:

class SomeModel(models.Model):

    class SomeEnum(models.TextChoices):

        A = "A", _("Option A")
        B = "B", _("Option B")

    enum_field = models.CharField(
        max_length=1,
        choices=SomeEnum.choices,
        default=SomeEnum.A,
    )

schema.py:


class SomeEnumSchema(graphene.Enum):

    A = "A"
    B = "B"


class SomeMutation(graphene.Mutation):

    class Arguments:
        some_enum = SomeEnumSchema(required=True)

thinwybk
  • 4,193
  • 2
  • 40
  • 76
  • I don't know python ... extract values to some base class, common to both - multiple inheritance ? – xadm Apr 03 '20 at 14:53

3 Answers3

11

You can use graphene.Enum.from_enum().

This function can convert normal Enum type to graphene.Enum.

Do notice that models.TextChoices is only available for Dajango version above 3.0

models.py (for Django version >= 3.0)

from django.db import models

class SomeModel(models.Model):

    class SomeEnum(models.TextChoices):

        A = "A", _("Option A")
        B = "B", _("Option B")

    enum_field = models.CharField(
        max_length=1,
        choices=SomeEnum.choices,
        default=SomeEnum.A,
    )

models.py (for Django version < 3.0)

from enum import Enum
class SomeEnum(Enum):
    A = "A"
    B = "B"

schema.py:

SomeEnumSchema = graphene.Enum.from_enum(SomeEnum)
class SomeMutation(graphene.Mutation):
    class Arguments:
        some_enum = SomeEnumSchema(required=True)
Kant Chen
  • 153
  • 1
  • 7
  • An important note for the Django >= 3.0 version is that the class variable name for each enum option has to match the first element of the tuple. This means that to use a lowercase key of the _Option A_ enum, it has to be written as `a = "a", _("Option A")` – Moritz Nov 24 '20 at 14:13
1

If you'd like to use Enums in custom mutations then, you could use graphine.Enum like this.

class PermisionEnum(graphene.Enum):
    READ = "read"
    WRITE = "write"


class SimpleModelMutation(graphene.Mutation):

    class Arguments:
        # reusable
        permission = PermisionEnum(required=True)
        # not reusable
        scope = graphene.Enum('scope', [
            ("PERSONAL", "personal"),
            ("PROJECT", "project")
        ])(required=True)

I recommend to use PermisionEnum(required=True) approach because with it PermisionEnum class can be reuse across different mutations. This is not possible when you declare enums as I do it with scope filed!

Lukasz Dynowski
  • 11,169
  • 9
  • 81
  • 124
0

extend your graphene.Enum instances with the class below

from inspect import getmembers, isroutine

class EnumChoices:
    @classmethod
    def choices(self):
        attributes = getmembers(self, lambda a: not (isroutine(a)))
        values = [(a[0], a[1]._value_) for a in attributes if hasattr(a[1], "_value_")]
        return values

Your Example Enum will look like the one below

# import the EnumChoices class above 
from graphene import Enum

class YourExampleChoices(EnumChoices, Enum):
    BAD = "Bad"
    MEH = "Meh"
    NORMAL = "Normal"
    LIKE = "Like"
    LOVE = "Love"

Your example graphene.types.InputObjectType will look like the one below

# import the YourExampleChoices class above
from graphene.types import InputObjectType

class YourExampleChoicesMutationInput(InputObjectType):
    choice = YourExampleChoices(required=True)

Your example graphene.Mutation will look like the one below

# import the YourExampleChoicesMutationInput class above 
import graphene

from graphene.types.mutation import Mutation

class YourExampleMutation(Mutation):
    # make sure to define your mutation results fields

    class Arguments:
        input = YourExampleChoicesMutationInput(required=True)

    def mutate(root, info, input):
        # what do you want this mutation to do?
        pass

Your Django model will look like the one below

# import the YourExampleChoices class above
# notice that `YourExampleChoices.choices()` is callable, this is slightly different from the `Textchoices.choices` which isn't callable

class YourExampleModel(TimestampBase):
    choices = models.CharField(
        max_length=6, 
        choices=YourExampleChoices.choices(), 
        default=YourExampleChoices.NORMAL._value_
    )
Enogwe Victor
  • 70
  • 1
  • 5