Repository: graphql-python/graphene-django

Issue:
DjangoObjectType interprets string choices fields as required in schema even with blank=True attribute
  To reproduce this problem, create a Django model with a choices field with `blank=True`:

    class MyModel(models.Model):
        PERIODIC_INTERVAL_CHOICES = (('Weekly', 'Weekly'),
             ('Bi-Weekly', 'Bi-Weekly'),
              ('Monthly', 'Monthly'),
              ('Quarterly', 'Quarterly'),
              ('Semi-Annually', 'Semi-Annually'),
              'Annually', 'Annually'))
        
        payment_frequency = models.CharField(
            blank=True,
            choices=PERIODIC_INTERVAL_CHOICES,
            max_length=13)

The schema generated by DjangoObjectType will show incorrectly show that this field `payment_frequency` is required.  This is incorrect -- the expected behavior is that it NOT required.

I have been able to fix this issue by patching how the value of required is determined in `graphene.converter.convert_django_field_with_choices` (shown for graphene-django 2.0.0)  :

    def convert_django_field_with_choices(field, registry=None):
        # Modified from graphene_django.converter import convert_django_field_with_choices
        # to adjust "required"
        choices = getattr(field, 'choices', None)
        if choices:
            meta = field.model._meta
            name = to_camel_case('{}_{}'.format(meta.object_name, field.name))
            choices = list(get_choices(choices))
            named_choices = [(c[0], c[1]) for c in choices]
            named_choices_descriptions = {c[0]: c[2] for c in choices}
    
            class EnumWithDescriptionsType(object):
    
                @property
                def description(self):
                    return named_choices_descriptions[self.name]
    
            enum = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
            required = not (field.blank or field.null or field.default)  # MODIFIED FROM ORIGINAL
            return enum(description=field.help_text, required=required)
        return convert_django_field(field, registry)



Discussion:
Hi @picturedots, I have tried your solution but still the error reported
The code above is for iillustrative purposes of where in the code the issue lies.  To actually use my code you need to replace every single one of your string enum fields, using a method like

    def create_enum_field(django_model, field_name):
        """ 
        Creates a graphene Enum field from a Django choices field that is
        suitable for inputs and outputs.
        """
        django_field = django_model._meta.get_field(field_name)
        graphene_field = convert_django_field_with_choices(django_field)
        return graphene_field

that is called in a `DjangoObjectType` like 

    payment_frequency = create_enum_field(MyModel, 'payment_frequency')
Hi @picturedots. Do you have a temporary solution for this while waiting for the error to be fixed?

What I did for now is to add a field in `DjangoObjectType` with the same name and use the field as source.

```
class MyModel(DjangoObjectType):
    payment_frequency = graphene.String(source='payment_frequency')
```

The only problem with this is that the `payment_frequency` is identified as a `String` instead of `MyModelPaymentFrequency `
Yes, @picturedots I tried your suggestion as well (even after replacing the fields in my DjangoObjecttype) and experienced the same errors. I wonder what the difference is between our codebases. Not sure how to solve it.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Hey, bot -- this issue is not fix as far as I know.  Please don't close.
Just want to add a bump to this thread after spending some time dealing with this issue. Django specifies that empty CharFields should be represented as "" rather than None so that there is only one representation of an empty field ("" rather than "" and None). This means setting blank=True and NOT setting null=True on the CharField model definition (https://docs.djangoproject.com/en/2.2/ref/models/fields/#null). Graphene-django v2.4.0 does not handle this case correctly. CharFields with blank=True show as required and will raise an error: "Expected a value of type \"{field_from_choices_array}\" but received: ", Graphene-django is saying the field is required to be defined and that the "" value is not allowed; however, the "" character is indeed an allowed character for this field in django--in fact it's the idiomatic character for an empty CharField.

While [this](https://github.com/graphql-python/graphene-django/pull/598) thread suggests that explicitly defining the blank field is more idiomatic, django already has a default value for blank CharFields, "", and using this seems like a sensible default for Graphene-django rather than requiring the "" field to be explicitly defined a la [this thread](https://stackoverflow.com/questions/14541074/empty-label-choicefield-django) on every CHOICES array that is set on a CharField(blank=True).

Additionally, if I define a "" choice for my CharField(blank=True) as per the suggestions in the links above, graphene-django returns an "A_" value for the blank field--less than ideal.

I'd suggest that and CharField(blank=True) with a choices set should be correctly identified by graphene in the schema as NOT required and to correctly handle a "" value from django by returning a "" value to the GQL client.

Resolving patch:
diff --git a/graphene_django/converter.py b/graphene_django/converter.py
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -73,7 +73,8 @@ def description(self):
                 return named_choices_descriptions[self.name]
 
         enum = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
-        converted = enum(description=field.help_text, required=not field.null)
+        required = not (field.blank or field.null)
+        converted = enum(description=field.help_text, required=required)
     else:
         converted = convert_django_field(field, registry)
     if registry is not None:


Test patch:
diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py
--- a/graphene_django/tests/models.py
+++ b/graphene_django/tests/models.py
@@ -38,7 +38,7 @@ class Reporter(models.Model):
     last_name = models.CharField(max_length=30)
     email = models.EmailField()
     pets = models.ManyToManyField("self")
-    a_choice = models.CharField(max_length=30, choices=CHOICES)
+    a_choice = models.CharField(max_length=30, choices=CHOICES, blank=True)
     objects = models.Manager()
     doe_objects = DoeReporterManager()
 
diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py
--- a/graphene_django/tests/test_types.py
+++ b/graphene_django/tests/test_types.py
@@ -171,7 +171,7 @@ def test_schema_representation():
   lastName: String!
   email: String!
   pets: [Reporter!]!
-  aChoice: ReporterAChoice!
+  aChoice: ReporterAChoice
   reporterType: ReporterReporterType
   articles(before: String, after: String, first: Int, last: Int): ArticleConnection
 }