From 56fd6c365532194d6c111cc06faeaaaf46b4243b Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Thu, 24 Aug 2023 20:29:28 +0300 Subject: [PATCH 1/7] adding optional_fields to enforce fields to be optional --- graphene_django/rest_framework/mutation.py | 40 +++++++++++-------- .../rest_framework/serializer_converter.py | 5 ++- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index 9423d4f60..eb9396168 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -19,15 +19,17 @@ class SerializerMutationOptions(MutationOptions): model_class = None model_operations = ["create", "update"] serializer_class = None + optional_fields = () def fields_for_serializer( - serializer, - only_fields, - exclude_fields, - is_input=False, - convert_choices_to_enum=True, - lookup_field=None, + serializer, + only_fields, + exclude_fields, + is_input=False, + convert_choices_to_enum=True, + lookup_field=None, + optional_fields=(), ): fields = OrderedDict() for name, field in serializer.fields.items(): @@ -48,9 +50,11 @@ def fields_for_serializer( if is_not_in_only or is_excluded: continue + is_optional = name in optional_fields fields[name] = convert_serializer_field( - field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum + field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum, + force_optional=is_optional ) return fields @@ -65,16 +69,17 @@ class Meta: @classmethod def __init_subclass_with_meta__( - cls, - lookup_field=None, - serializer_class=None, - model_class=None, - model_operations=("create", "update"), - only_fields=(), - exclude_fields=(), - convert_choices_to_enum=True, - _meta=None, - **options + cls, + lookup_field=None, + serializer_class=None, + model_class=None, + model_operations=("create", "update"), + only_fields=(), + exclude_fields=(), + convert_choices_to_enum=True, + _meta=None, + optional_fields=() + **options ): if not serializer_class: raise Exception("serializer_class is required for the SerializerMutation") @@ -98,6 +103,7 @@ def __init_subclass_with_meta__( is_input=True, convert_choices_to_enum=convert_choices_to_enum, lookup_field=lookup_field, + optional_fields=optional_fields ) output_fields = fields_for_serializer( serializer, diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py index 328c46fd2..deab7d201 100644 --- a/graphene_django/rest_framework/serializer_converter.py +++ b/graphene_django/rest_framework/serializer_converter.py @@ -18,7 +18,8 @@ def get_graphene_type_from_serializer_field(field): ) -def convert_serializer_field(field, is_input=True, convert_choices_to_enum=True): +def convert_serializer_field(field, is_input=True, convert_choices_to_enum=True, + force_optional=False): """ Converts a django rest frameworks field to a graphql field and marks the field as required if we are creating an input type @@ -31,7 +32,7 @@ def convert_serializer_field(field, is_input=True, convert_choices_to_enum=True) graphql_type = get_graphene_type_from_serializer_field(field) args = [] - kwargs = {"description": field.help_text, "required": is_input and field.required} + kwargs = {"description": field.help_text, "required": is_input and field.required and not force_optional} # if it is a tuple or a list it means that we are returning # the graphql type and the child type From 44f550825ecc4725903086164fe1205f99959bbc Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Thu, 24 Aug 2023 20:30:05 +0300 Subject: [PATCH 2/7] adding support for all --- graphene_django/rest_framework/mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index eb9396168..061c61f07 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -50,7 +50,7 @@ def fields_for_serializer( if is_not_in_only or is_excluded: continue - is_optional = name in optional_fields + is_optional = name in optional_fields or '__all__' in optional_fields fields[name] = convert_serializer_field( field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum, From cde0e2385447b4f61ada0008edba03f81da28b86 Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Fri, 25 Aug 2023 16:56:52 +0300 Subject: [PATCH 3/7] adding unit tests --- graphene_django/rest_framework/tests/test_mutation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index bfe53cc9a..9159d32a7 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -104,6 +104,15 @@ class Meta: assert "cool_name" in MyMutation.Input._meta.fields assert "created" not in MyMutation.Input._meta.fields +def test_model_serializer_optional_fields(): + class MyMutation(SerializerMutation): + class Meta: + serializer_class = MyModelSerializer + optional_fields = ("cool_name",) + + assert "cool_name" in MyMutation.Input._meta.fields + assert MyMutation.Input._meta.fields["cool_name"].type == String + def test_write_only_field(): class WriteOnlyFieldModelSerializer(serializers.ModelSerializer): From 6240bdd85dbd2ec7610244f784b2c5e355ccae4f Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 <10292198+mahmoudmostafa0@users.noreply.github.com> Date: Sat, 26 Aug 2023 11:20:54 +0300 Subject: [PATCH 4/7] Update graphene_django/rest_framework/mutation.py Co-authored-by: Kien Dang --- graphene_django/rest_framework/mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index 061c61f07..16c824cf5 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -78,7 +78,7 @@ def __init_subclass_with_meta__( exclude_fields=(), convert_choices_to_enum=True, _meta=None, - optional_fields=() + optional_fields=(), **options ): if not serializer_class: From 5cd43c562ffacc25414f0e755b31c8dec3d0137f Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Sat, 26 Aug 2023 14:38:01 +0300 Subject: [PATCH 5/7] linting --- graphene_django/rest_framework/mutation.py | 45 ++++++++++--------- .../rest_framework/serializer_converter.py | 10 +++-- .../rest_framework/tests/test_mutation.py | 1 + 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index 061c61f07..d9ec2c87a 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -23,13 +23,13 @@ class SerializerMutationOptions(MutationOptions): def fields_for_serializer( - serializer, - only_fields, - exclude_fields, - is_input=False, - convert_choices_to_enum=True, - lookup_field=None, - optional_fields=(), + serializer, + only_fields, + exclude_fields, + is_input=False, + convert_choices_to_enum=True, + lookup_field=None, + optional_fields=(), ): fields = OrderedDict() for name, field in serializer.fields.items(): @@ -50,11 +50,13 @@ def fields_for_serializer( if is_not_in_only or is_excluded: continue - is_optional = name in optional_fields or '__all__' in optional_fields + is_optional = name in optional_fields or "__all__" in optional_fields fields[name] = convert_serializer_field( - field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum, - force_optional=is_optional + field, + is_input=is_input, + convert_choices_to_enum=convert_choices_to_enum, + force_optional=is_optional, ) return fields @@ -69,17 +71,16 @@ class Meta: @classmethod def __init_subclass_with_meta__( - cls, - lookup_field=None, - serializer_class=None, - model_class=None, - model_operations=("create", "update"), - only_fields=(), - exclude_fields=(), - convert_choices_to_enum=True, - _meta=None, - optional_fields=() - **options + cls, + lookup_field=None, + serializer_class=None, + model_class=None, + model_operations=("create", "update"), + only_fields=(), + exclude_fields=(), + convert_choices_to_enum=True, + _meta=None, + optional_fields=() ** options, ): if not serializer_class: raise Exception("serializer_class is required for the SerializerMutation") @@ -103,7 +104,7 @@ def __init_subclass_with_meta__( is_input=True, convert_choices_to_enum=convert_choices_to_enum, lookup_field=lookup_field, - optional_fields=optional_fields + optional_fields=optional_fields, ) output_fields = fields_for_serializer( serializer, diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py index deab7d201..51695c5d0 100644 --- a/graphene_django/rest_framework/serializer_converter.py +++ b/graphene_django/rest_framework/serializer_converter.py @@ -18,8 +18,9 @@ def get_graphene_type_from_serializer_field(field): ) -def convert_serializer_field(field, is_input=True, convert_choices_to_enum=True, - force_optional=False): +def convert_serializer_field( + field, is_input=True, convert_choices_to_enum=True, force_optional=False +): """ Converts a django rest frameworks field to a graphql field and marks the field as required if we are creating an input type @@ -32,7 +33,10 @@ def convert_serializer_field(field, is_input=True, convert_choices_to_enum=True, graphql_type = get_graphene_type_from_serializer_field(field) args = [] - kwargs = {"description": field.help_text, "required": is_input and field.required and not force_optional} + kwargs = { + "description": field.help_text, + "required": is_input and field.required and not force_optional, + } # if it is a tuple or a list it means that we are returning # the graphql type and the child type diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index 9159d32a7..bfb2f170e 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -104,6 +104,7 @@ class Meta: assert "cool_name" in MyMutation.Input._meta.fields assert "created" not in MyMutation.Input._meta.fields + def test_model_serializer_optional_fields(): class MyMutation(SerializerMutation): class Meta: From 137ac326e27ae2af4bc5244ac181fe7185dbaaa3 Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Sat, 26 Aug 2023 14:49:54 +0300 Subject: [PATCH 6/7] linting --- graphene_django/rest_framework/mutation.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py index 00851ddb2..47e71861b 100644 --- a/graphene_django/rest_framework/mutation.py +++ b/graphene_django/rest_framework/mutation.py @@ -71,17 +71,17 @@ class Meta: @classmethod def __init_subclass_with_meta__( - cls, - lookup_field=None, - serializer_class=None, - model_class=None, - model_operations=("create", "update"), - only_fields=(), - exclude_fields=(), - convert_choices_to_enum=True, - _meta=None, - optional_fields=(), - **options + cls, + lookup_field=None, + serializer_class=None, + model_class=None, + model_operations=("create", "update"), + only_fields=(), + exclude_fields=(), + convert_choices_to_enum=True, + _meta=None, + optional_fields=(), + **options ): if not serializer_class: raise Exception("serializer_class is required for the SerializerMutation") From 41ea685abcef142c4c475d5ee4ca28dbd9ad23f9 Mon Sep 17 00:00:00 2001 From: mahmoudmostafa0 Date: Sat, 26 Aug 2023 15:13:43 +0300 Subject: [PATCH 7/7] add missing import --- graphene_django/rest_framework/tests/test_mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py index bfb2f170e..58bc4ce6c 100644 --- a/graphene_django/rest_framework/tests/test_mutation.py +++ b/graphene_django/rest_framework/tests/test_mutation.py @@ -3,7 +3,7 @@ from pytest import raises from rest_framework import serializers -from graphene import Field, ResolveInfo +from graphene import Field, ResolveInfo, String from graphene.types.inputobjecttype import InputObjectType from ...types import DjangoObjectType