strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
415 stars 120 forks source link

Enum values in mutation input causes ValidationError #491

Closed cngai closed 8 months ago

cngai commented 8 months ago

When calling a mutation with an input argument that accepts an enum value, instead of saving the enum value to the field, the object attempts to save the enum itself, thus throwing a Django ValidationError "Value 'ExampleEnum.VALUE' is not a valid choice.".

Describe the Bug

Example

# models.py
class Fruit(models.Model):
    status = models.CharField(max_length=16, choices=[("FRESH", "FRESH"), ("ROTTEN", "ROTTEN"), ("UNDERRIPE, "UNDERRIPE")], ...)

# settings.py
STRAWBERRY_DJANGO = {
    "GENERATE_ENUMS_FROM_CHOICES": True,
}

# schema.py
@strawberry_django.type(Fruit, fields="__all__")
class FruitType:
    pass

@strawberry_django.input(Fruit, partial=True)
class FruitInputPartial:
    id: auto
    status: strawberry.auto

@strawberry.type
class Mutation:
    patch_fruit: FruitType = strawberry_django.mutations.update(FruitInputPartial)

# GraphQL Mutation
mutation PatchFruit {
    patchFruit(data: {"id": 1, "status": FRESH}) {
        status
    }
}

In the example above, Strawberry would auto-generate a FruitStatusEnum for me since the Fruit.status model field has defined choices. The input type for my FruitInputPartial.status field is auto-resolved as FruitStatusEnum. However, when I call the mutation defined above, it tries saving the status field as FruitStatusEnum.FRESH as opposed to FruitStatusEnum.FRESH.value, which is why I get the django.core.exceptions.ValidationError: {'status': ["Value 'FruitStatusEnum.FRESH' is not a valid choice."]} error.

Reproduction Steps

  1. For tests/test_input_mutations.py/test_input_update_mutation, override the test settings to set GENERATE_ENUMS_FROM_CHOICES to be True by adding the@override_settings decorator to the test (shown below).
    
    from django.test import override_settings
    from strawberry_django.settings import strawberry_django_settings

Add this decorator to the test function

@override_settings( STRAWBERRY_DJANGO={ **strawberry_django_settings(), "GENERATE_ENUMS_FROM_CHOICES": True, }, ) @pytest.mark.django_db(transaction=True) def test_input_update_mutation(db, gql_client: GraphQLTestClient): query = """ mutation CreateIssue ($input: IssueInputPartial!) { updateIssue (input: $input) { ...

2. Run the test and print out the response: `print(res.data)`.

**Expected:** The test should pass and `Issue.kind` should have a value of `"f"` due to the update mutation.
**Actual:** The test will fail and you will observe the following response:

{'updateIssue': {'__typename': 'OperationInfo', 'messages': [{'kind': 'VALIDATION', 'field': 'kind', 'message': "Value 'ProjectsIssueKindEnum.f' is not a valid choice."}]}}


### Potential Fix
If we pass in an enum alue, we can correctly parse the input type by adding an if-statement in the `mutations/resolvers.py/parse_input` function as follows:
```python
if isinstance(data, enum.Enum):
    return data.value

Additional Context

The current workaround is to change the strawberry.auto type for my input field to be str | None, but then I lose the strong typing for my client-side app.

Let me know if you need any more information or if you'd like me to submit a PR. Thanks!

System Information

Upvote & Fund

Fund with Polar

bellini666 commented 8 months ago

Hey @cngai ,

As I said on discord, I usually use django-choices-field which automatically integrates with strawberry: https://strawberry-graphql.github.io/strawberry-django/integrations/choices-field/, so I was not really aware of this =P

I think your "Potential Fix" is the way to go here! Do you want to try to open a PR with that solution? Let me know if I can help you with that :)

cngai commented 8 months ago

Thanks for taking a look @bellini666 ! I'll put up a PR in the next few days. Appreciate the help!