Add convert_choices_to_enum option on DjangoObjectType Meta class (#674)

* Add convert_choices_to_enum meta option

* Add tests

* Run black

* Update documentation

* Add link to Django choices documentation

* Add test and documentation note

That setting to an empty list is the same as setting the value as False

* Fix Django warning in tests

* rst is not markdown
This commit is contained in:
Jonathan Kim 2019-06-17 18:48:29 +01:00 committed by GitHub
parent 894b1053a2
commit 612ba5a4ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 6 deletions

View File

@ -92,6 +92,71 @@ You can completely overwrite a field, or add new fields, to a ``DjangoObjectType
return 'hello!'
Choices to Enum conversion
~~~~~~~~~~~~~~~~~~~~~~~~~~
By default Graphene-Django will convert any Django fields that have `choices`_
defined into a GraphQL enum type.
.. _choices: https://docs.djangoproject.com/en/2.2/ref/models/fields/#choices
For example the following ``Model`` and ``DjangoObjectType``:
.. code:: python
class PetModel(models.Model):
kind = models.CharField(max_length=100, choices=(('cat', 'Cat'), ('dog', 'Dog')))
class Pet(DjangoObjectType):
class Meta:
model = PetModel
Results in the following GraphQL schema definition:
.. code::
type Pet {
id: ID!
kind: PetModelKind!
}
enum PetModelKind {
CAT
DOG
}
You can disable this automatic conversion by setting
``convert_choices_to_enum`` attribute to ``False`` on the ``DjangoObjectType``
``Meta`` class.
.. code:: python
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = False
.. code::
type Pet {
id: ID!
kind: String!
}
You can also set ``convert_choices_to_enum`` to a list of fields that should be
automatically converted into enums:
.. code:: python
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = ['kind']
**Note:** Setting ``convert_choices_to_enum = []`` is the same as setting it to
``False``.
Related models
--------------

View File

@ -52,13 +52,15 @@ def get_choices(choices):
yield name, value, description
def convert_django_field_with_choices(field, registry=None):
def convert_django_field_with_choices(
field, registry=None, convert_choices_to_enum=True
):
if registry is not None:
converted = registry.get_converted_field(field)
if converted:
return converted
choices = getattr(field, "choices", None)
if choices:
if choices and convert_choices_to_enum:
meta = field.model._meta
name = to_camel_case("{}_{}".format(meta.object_name, field.name))
choices = list(get_choices(choices))

View File

@ -196,6 +196,23 @@ def test_field_with_choices_collision():
convert_django_field_with_choices(field)
def test_field_with_choices_convert_enum_false():
field = models.CharField(
help_text="Language", choices=(("es", "Spanish"), ("en", "English"))
)
class TranslatedModel(models.Model):
language = field
class Meta:
app_label = "test"
graphene_type = convert_django_field_with_choices(
field, convert_choices_to_enum=False
)
assert isinstance(graphene_type, graphene.String)
def test_should_float_convert_float():
assert_conversion(models.FloatField, graphene.Float)

View File

@ -1,6 +1,11 @@
from collections import OrderedDict, defaultdict
from textwrap import dedent
import pytest
from django.db import models
from mock import patch
from graphene import Interface, ObjectType, Schema, Connection, String
from graphene import Connection, Field, Interface, ObjectType, Schema, String
from graphene.relay import Node
from .. import registry
@ -224,3 +229,111 @@ def test_django_objecttype_exclude_fields():
fields = list(Reporter._meta.fields.keys())
assert "email" not in fields
class TestDjangoObjectType:
@pytest.fixture
def PetModel(self):
class PetModel(models.Model):
kind = models.CharField(choices=(("cat", "Cat"), ("dog", "Dog")))
cuteness = models.IntegerField(
choices=((1, "Kind of cute"), (2, "Pretty cute"), (3, "OMG SO CUTE!!!"))
)
yield PetModel
# Clear Django model cache so we don't get warnings when creating the
# model multiple times
PetModel._meta.apps.all_models = defaultdict(OrderedDict)
def test_django_objecttype_convert_choices_enum_false(self, PetModel):
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = False
class Query(ObjectType):
pet = Field(Pet)
schema = Schema(query=Query)
assert str(schema) == dedent(
"""\
schema {
query: Query
}
type Pet {
id: ID!
kind: String!
cuteness: Int!
}
type Query {
pet: Pet
}
"""
)
def test_django_objecttype_convert_choices_enum_list(self, PetModel):
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = ["kind"]
class Query(ObjectType):
pet = Field(Pet)
schema = Schema(query=Query)
assert str(schema) == dedent(
"""\
schema {
query: Query
}
type Pet {
id: ID!
kind: PetModelKind!
cuteness: Int!
}
enum PetModelKind {
CAT
DOG
}
type Query {
pet: Pet
}
"""
)
def test_django_objecttype_convert_choices_enum_empty_list(self, PetModel):
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = []
class Query(ObjectType):
pet = Field(Pet)
schema = Schema(query=Query)
assert str(schema) == dedent(
"""\
schema {
query: Query
}
type Pet {
id: ID!
kind: String!
cuteness: Int!
}
type Query {
pet: Pet
}
"""
)

View File

@ -18,7 +18,9 @@ if six.PY3:
from typing import Type
def construct_fields(model, registry, only_fields, exclude_fields):
def construct_fields(
model, registry, only_fields, exclude_fields, convert_choices_to_enum
):
_model_fields = get_model_fields(model)
fields = OrderedDict()
@ -33,7 +35,18 @@ def construct_fields(model, registry, only_fields, exclude_fields):
# in there. Or when we exclude this field in exclude_fields.
# Or when there is no back reference.
continue
converted = convert_django_field_with_choices(field, registry)
_convert_choices_to_enum = convert_choices_to_enum
if not isinstance(_convert_choices_to_enum, bool):
# then `convert_choices_to_enum` is a list of field names to convert
if name in _convert_choices_to_enum:
_convert_choices_to_enum = True
else:
_convert_choices_to_enum = False
converted = convert_django_field_with_choices(
field, registry, convert_choices_to_enum=_convert_choices_to_enum
)
fields[name] = converted
return fields
@ -63,6 +76,7 @@ class DjangoObjectType(ObjectType):
connection_class=None,
use_connection=None,
interfaces=(),
convert_choices_to_enum=True,
_meta=None,
**options
):
@ -90,7 +104,10 @@ class DjangoObjectType(ObjectType):
)
django_fields = yank_fields_from_attrs(
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
construct_fields(
model, registry, only_fields, exclude_fields, convert_choices_to_enum
),
_as=Field,
)
if use_connection is None and interfaces: