Elijah
Elijah

Reputation: 561

How can I override a DjangoModelFormMutation field type in graphene?

I'm building a simple recipe storage application that uses the Graphene package for GraphQL. I've been able to use Django Forms so far very easily in my mutations, however one of my models fields is really an Enum and I'd like to expose it in Graphene/GraphQL as such.

My enum:

class Unit(Enum):
    # Volume
    TEASPOON = "teaspoon"
    TABLESPOON = "tablespoon"
    FLUID_OUNCE = "fl oz"
    CUP = "cup"
    US_PINT = "us pint"
    IMPERIAL_PINT = "imperial pint"
    US_QUART = "us quart"
    IMPERIAL_QUART = "imperial quart"
    US_GALLON = "us gallon"
    IMPERIAL_GALLON = "imperial gallon"
    MILLILITER = "milliliter"
    LITER = "liter"

    # Mass and Weight
    POUND = "pound"
    OUNCE = "ounce"
    MILLIGRAM = "milligram"
    GRAM = "gram"
    KILOGRAM = "kilogram"

My Model:

class RecipeIngredient(TimeStampedModel):
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredients')
    direction = models.ForeignKey(RecipeDirection, on_delete=models.CASCADE, null=True, related_name='ingredients')

    quantity = models.DecimalField(decimal_places=2, max_digits=10)
    unit = models.TextField(choices=Unit.as_tuple_list())

My form:

class RecipeIngredientForm(forms.ModelForm):
    class Meta:
        model = RecipeIngredient
        fields = (
            'recipe',
            'direction',
            'quantity',
            'unit',
        )

My Mutation:

class CreateRecipeIngredientMutation(DjangoModelFormMutation):
    class Meta:
        form_class = RecipeIngredientForm
        exclude_fields = ('id',)

I've created this graphene enum UnitEnum = Enum.from_enum(Unit) however I haven't been able to get graphene to pick it up. I've tried adding it to the CreateRecipeIngredientMutation as a regular field like unit = UnitEnum() as well as an Input class on that mutation. So far, the closest I've gotten is this Github issue from awhile ago. After playing around with the class in an iPython shell, I think I could just do CreateRecipeIngredientMutation.Input.unit.type.of_type = UnitEnum() but this feels awful.

Upvotes: 3

Views: 1667

Answers (1)

Elijah
Elijah

Reputation: 561

I came up with a solution that works but is not pretty. I used the https://github.com/hzdg/django-enumfields package to help with this.

I created my own form field:

class EnumChoiceField(enumfields.forms.EnumChoiceField):
    def __init__(self, enum, *, coerce=lambda val: val, empty_value='', **kwargs):
        if isinstance(enum, six.string_types):
            self.enum = import_string(enum)
        else:
            self.enum = enum

        super().__init__(coerce=coerce, empty_value=empty_value, **kwargs)

And used it in my Django form. Then in my custom AppConfig I did this:

class CoreAppConfig(AppConfig):
    name = 'myapp.core'

    def ready(self):
        registry = get_global_registry()

        @convert_form_field.register(EnumChoiceField)
        def convert_form_field_to_enum(field: EnumChoiceField):
            converted = registry.get_converted_field(field.enum)
            if converted is None:
                raise ImproperlyConfigured("Enum %r is not registered." % field.enum)
            return converted(description=field.help_text, required=field.required)

And finally in my schema:

UnitEnum = Enum.from_enum(Unit)
get_global_registry().register_converted_field(Unit, UnitEnum)

I really don't like this, but couldn't think of a better way to handle this. I came across this idea when searching down another graphene django issue here https://github.com/graphql-python/graphene-django/issues/481#issuecomment-412227036.

I feel like there has to be a better way to do this.

Upvotes: 1

Related Questions