From cc58b91e43c433838afa07259bb0bf5f3929f9eb Mon Sep 17 00:00:00 2001 From: Andy Clayton Date: Thu, 31 Aug 2017 11:07:05 -0500 Subject: [PATCH 1/2] fix SerializerMutation regression for 2.x 72529b7 seems to break SerializerMutation by commenting out support for input fields. As a result input only ever had a clientMutationId field. --- graphene_django/rest_framework/mutation.py | 1 + .../rest_framework/serializer_converter.py | 18 ++++++++++++++++-- .../rest_framework/tests/test_mutation.py | 7 +++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index beaaa49..6eef870 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -55,6 +55,7 @@ class SerializerMutation(ClientIDMutation): output_fields = fields_for_serializer(serializer, only_fields, exclude_fields, is_input=False) _meta = SerializerMutationOptions(cls) + _meta.serializer_class = serializer_class _meta.fields = yank_fields_from_attrs( output_fields, _as=Field, diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py index e115e82..c472cee 100644 --- a/graphene_django/rest_framework/serializer_converter.py +++ b/graphene_django/rest_framework/serializer_converter.py @@ -42,8 +42,7 @@ def convert_serializer_field(field, is_input=True): if isinstance(field, serializers.ModelSerializer): if is_input: - return Dynamic(lambda: None) - # graphql_type = convert_serializer_to_input_type(field.__class__) + graphql_type = convert_serializer_to_input_type(field.__class__) else: global_registry = get_global_registry() field_model = field.Meta.model @@ -52,6 +51,21 @@ def convert_serializer_field(field, is_input=True): return graphql_type(*args, **kwargs) +def convert_serializer_to_input_type(serializer_class): + serializer = serializer_class() + + items = { + name: convert_serializer_field(field) + for name, field in serializer.fields.items() + } + + return type( + '{}Input'.format(serializer.__class__.__name__), + (graphene.InputObjectType,), + items + ) + + @get_graphene_type_from_serializer_field.register(serializers.Field) def convert_serializer_field_to_string(field): return graphene.String diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index 836f3fe..5374a66 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -65,7 +65,6 @@ def test_nested_model(): assert model_field.type == MyFakeModelGrapheneType model_input = MyMutation.Input._meta.fields['model'] - model_input_type = model_input.get_type() - assert not model_input_type - # assert issubclass(model_input_type, InputObjectType) - # assert 'cool_name' in model_input_type._meta.fields + model_input_type = model_input._type.of_type + assert issubclass(model_input_type, InputObjectType) + assert 'cool_name' in model_input_type._meta.fields From c130490b4f226384becfb72c1407bcefb8094b09 Mon Sep 17 00:00:00 2001 From: Andy Clayton Date: Thu, 31 Aug 2017 12:15:18 -0500 Subject: [PATCH 2/2] ensure SerializerMutation.errors is None on success in 2.x Upon success the result was correct but also included: "errors": [ { "message": "User Error: expected iterable, but did not find one for field Payload.errors." } ] This seemed to be due to Payload.errors defaulting to graphene.List rather than unset or None. Unsure what exactly changed with 2.x to break this, so I welcome a better fix, but explicitly setting errors to None also seems easy enough. --- graphene_django/rest_framework/mutation.py | 2 +- .../rest_framework/tests/test_mutation.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index 6eef870..94d1e4b 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -84,4 +84,4 @@ class SerializerMutation(ClientIDMutation): @classmethod def perform_mutate(cls, serializer, info): obj = serializer.save() - return cls(**obj) + return cls(errors=None, **obj) diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index 5374a66..852265d 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -22,6 +22,9 @@ class MySerializer(serializers.Serializer): text = serializers.CharField() model = MyModelSerializer() + def create(self, validated_data): + return validated_data + def test_needs_serializer_class(): with raises(Exception) as exc: @@ -68,3 +71,29 @@ def test_nested_model(): model_input_type = model_input._type.of_type assert issubclass(model_input_type, InputObjectType) assert 'cool_name' in model_input_type._meta.fields + + +def test_mutate_and_get_payload_success(): + + class MyMutation(SerializerMutation): + class Meta: + serializer_class = MySerializer + + result = MyMutation.mutate_and_get_payload(None, None, **{ + 'text': 'value', + 'model': { + 'cool_name': 'other_value' + } + }) + assert result.errors is None + + +def test_mutate_and_get_payload_error(): + + class MyMutation(SerializerMutation): + class Meta: + serializer_class = MySerializer + + # missing required fields + result = MyMutation.mutate_and_get_payload(None, None, **{}) + assert len(result.errors) > 0 \ No newline at end of file