graphql-python / graphene-django

Build powerful, efficient, and flexible GraphQL APIs with seamless Django integration.
http://docs.graphene-python.org/projects/django/en/latest/
MIT License
4.3k stars 769 forks source link

Mutation can not represent TextChoice value #1476

Open FranciscoNMora opened 1 year ago

FranciscoNMora commented 1 year ago

Note: for support questions, please use stackoverflow. This repository's issues are reserved for feature requests and bug reports.

When executing a mutation that returns a Type of a Model that has a CharField with a TextChoices enum, it returns the following error: "Enum 'Status' cannot represent value: <AppointmentStatus.CANCELLED: 'C'>"

Define these classes

class AppointmentStatus(models.TextChoices):
    BOOKED = "B", "Booked"
    CANCELLED = "C", "Cancelled"

class Appointment(models.Model):
    id = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True)
    status = models.CharField(max_length=1, choices=AppointmentStatus)

class AppointmentType(DjangoObjectType):
    class Meta:
        model = Appointment

class CancelAppointment(Mutation):
    appointment = graphene.Field(AppointmentType, required=False)

    class Arguments:
        appointment_id = graphene.UUID(required=True)

    def mutate(self, info, appointment_id):
        appointment = Appointment.objects.get(id=appointment_id)
        return CancelAppointment(appointment=appointment)

class Mutation(object):
    cancel_appointment = CancelAppointment.Field()

When using this mutation and querying the result status

mutation cancelAppointment($appointmentId: UUID!) {
  cancelAppointment(
    appointmentId: $appointmentId
  ) {
    appointment {
      id
      status
    }
  }
}

and querying the result status I receive the following error:

"errors": [
    {
      "message": "Enum 'Status' cannot represent value: <AppointmentStatus.CANCELLED: 'C'>'",
      "locations": [
        {
          "line": 10,
          "column": 7
        }
      ],
      "path": [
        "cancelAppointment",
        "appointment",
        "status"
      ]
    }

I'm trying to migrate from graphene-django 2.15.0 to 3.1.5.

These are the whole traces of the problem:

File "/usr/local/lib/python3.9/site-packages/graphql/execution/execute.py", line 540, in execute_field       
  completed = self.complete_value(                                                                           
File "/usr/local/lib/python3.9/site-packages/graphql/execution/execute.py", line 639, in complete_value      
  return self.complete_leaf_value(cast(GraphQLLeafType, return_type), result)                                
File "/usr/local/lib/python3.9/site-packages/graphql/execution/execute.py", line 774, in complete_leaf_value 
  serialized_result = return_type.serialize(result)                                                          
File "/usr/local/lib/python3.9/site-packages/graphene/types/definitions.py", line 58, in serialize           
  return super(GrapheneEnumType, self).serialize(value)                                                      
File "/usr/local/lib/python3.9/site-packages/graphql/type/definition.py", line 1233, in serialize            
  raise GraphQLError(           
vintersnow commented 10 months ago

I'm facing the same issue. Is there any solution?

vintersnow commented 10 months ago

It seems working if I return the value (not Enum).

class AppointmentType(DjangoObjectType):
    class Meta:
        model = Appointment

    def resolve_status(self):
        return self.status.value
savin-ivelum commented 8 months ago

I believe the problem is in this check:

class GrapheneEnumType(GrapheneGraphQLType, GraphQLEnumType):
    def serialize(self, value):
        if not isinstance(value, PyEnum):  # <-------
            ...
        return super(GrapheneEnumType, self).serialize(value)

Graphene check that field is enum, but expect that this is graphene enum, and in case it's get django enum exception is raised. I think it can be fixed with:

if not isinstance(value, self.graphene_type._meta.enum):
elyas-hedayat commented 8 months ago

Have you ever tried convert_choices_to_enum = False on objecttypes? from django-graphene 3.2 you can set it in graphene config too.

savin-ivelum commented 8 months ago

But it will change scheme type to string right? But I'd like to have enum type

savin-ivelum commented 8 months ago

I've found a way to handle django class choices in graphene-django. I'm not sure that this is the right place to do it, but the main idea is to check if the return value has a choice type and return its value.

I assume that it's better to handle this in resolver function, but I don't see where to patch or pass it as default for fields with choices

# graphene_django/converter.py
@@ -54,6 +56,8 @@ class BlankValueField(Field):
                 return_value = func(*args, **kwargs)
                 if return_value == "":
                     return None
+                if isinstance(return_value, models.Choices):
+                    return return_value.value
                 return return_value

             return wrapped_resolver
savin-ivelum commented 8 months ago

And here is a monkey patch to use it right now:

from graphene.types.resolver import dict_or_attr_resolver, set_default_resolver

def custom_resolver(*args, **kwargs):
    resolved = dict_or_attr_resolver(*args, **kwargs)

    if isinstance(resolved, models.Choices):
        resolved = resolved.value

    return resolved

set_default_resolver(custom_resolver)

schema = graphene.Schema(...)
serengetisunset commented 1 month ago

I'd love to have this working in Graphene too! Are there any updates on whether this will be addressed in a future release?