From bcc7f85dadf47f1c150c6da1f589a443638c1f8e Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Mon, 11 Jan 2021 16:34:50 -0800 Subject: [PATCH] Add BlankField and mount enums using it v3 (#1096) * Add BlankField and mount enums using it * fix lint error from duplicate import Co-authored-by: Jonathan Kim --- graphene_django/converter.py | 32 +++++++++++++++--- graphene_django/tests/test_converter.py | 43 +++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/graphene_django/converter.py b/graphene_django/converter.py index 11df638..1da68d5 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -1,10 +1,11 @@ from collections import OrderedDict -from functools import singledispatch +from functools import singledispatch, partial, wraps from django.db import models from django.utils.encoding import force_str from django.utils.functional import Promise from django.utils.module_loading import import_string + from graphene import ( ID, UUID, @@ -22,6 +23,7 @@ from graphene import ( Time, Decimal, ) +from graphene.types.resolver import get_default_resolver from graphene.types.json import JSONString from graphene.utils.str_converters import to_camel_case from graphql import GraphQLError, assert_valid_name @@ -33,6 +35,24 @@ from .settings import graphene_settings from .utils.str_converters import to_const +class BlankValueField(Field): + def get_resolver(self, parent_resolver): + resolver = self.resolver or parent_resolver + + # create custom resolver + def blank_field_wrapper(func): + @wraps(func) + def wrapped_resolver(*args, **kwargs): + return_value = func(*args, **kwargs) + if return_value == "": + return None + return return_value + + return wrapped_resolver + + return blank_field_wrapper(resolver) + + def convert_choice_name(name): name = to_const(force_str(name)) try: @@ -71,7 +91,8 @@ def convert_choices_to_named_enum_with_descriptions(name, choices): def description(self): return str(named_choices_descriptions[self.name]) - return Enum(name, list(named_choices), type=EnumWithDescriptionsType) + return_type = Enum(name, list(named_choices), type=EnumWithDescriptionsType) + return return_type def generate_enum_name(django_model_meta, field): @@ -108,11 +129,12 @@ def convert_django_field_with_choices( return converted choices = getattr(field, "choices", None) if choices and convert_choices_to_enum: - enum = convert_choice_field_to_enum(field) + EnumCls = convert_choice_field_to_enum(field) required = not (field.blank or field.null) - converted = enum( + + converted = EnumCls( description=get_django_field_description(field), required=required - ) + ).mount_as(BlankValueField) else: converted = convert_django_field(field, registry) if registry is not None: diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py index fd94a24..fe84e73 100644 --- a/graphene_django/tests/test_converter.py +++ b/graphene_django/tests/test_converter.py @@ -164,8 +164,7 @@ def test_field_with_choices_convert_enum(): class Meta: app_label = "test" - graphene_type = convert_django_field_with_choices(field) - assert isinstance(graphene_type, graphene.Enum) + graphene_type = convert_django_field_with_choices(field).type.of_type assert graphene_type._meta.name == "TestTranslatedModelLanguageChoices" assert graphene_type._meta.enum.__members__["ES"].value == "es" assert graphene_type._meta.enum.__members__["ES"].description == "Spanish" @@ -418,3 +417,43 @@ def test_generate_v2_enum_name(graphene_settings): app_label="some_long_app_name", object_name="SomeObject" ) assert generate_enum_name(model_meta, field) == "SomeObjectFizzBuzz" + + +def test_choice_enum_blank_value(): + """Test that choice fields with blank values work""" + + class ReporterType(DjangoObjectType): + class Meta: + model = Reporter + fields = ( + "first_name", + "a_choice", + ) + + class Query(graphene.ObjectType): + reporter = graphene.Field(ReporterType) + + def resolve_reporter(root, info): + return Reporter.objects.first() + + schema = graphene.Schema(query=Query) + + # Create model with empty choice option + Reporter.objects.create( + first_name="Bridget", last_name="Jones", email="bridget@example.com" + ) + + result = schema.execute( + """ + query { + reporter { + firstName + aChoice + } + } + """ + ) + assert not result.errors + assert result.data == { + "reporter": {"firstName": "Bridget", "aChoice": None}, + }