mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-10 19:57:15 +03:00
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:
parent
894b1053a2
commit
612ba5a4ea
|
@ -92,6 +92,71 @@ You can completely overwrite a field, or add new fields, to a ``DjangoObjectType
|
||||||
return 'hello!'
|
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
|
Related models
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -52,13 +52,15 @@ def get_choices(choices):
|
||||||
yield name, value, description
|
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:
|
if registry is not None:
|
||||||
converted = registry.get_converted_field(field)
|
converted = registry.get_converted_field(field)
|
||||||
if converted:
|
if converted:
|
||||||
return converted
|
return converted
|
||||||
choices = getattr(field, "choices", None)
|
choices = getattr(field, "choices", None)
|
||||||
if choices:
|
if choices and convert_choices_to_enum:
|
||||||
meta = field.model._meta
|
meta = field.model._meta
|
||||||
name = to_camel_case("{}_{}".format(meta.object_name, field.name))
|
name = to_camel_case("{}_{}".format(meta.object_name, field.name))
|
||||||
choices = list(get_choices(choices))
|
choices = list(get_choices(choices))
|
||||||
|
|
|
@ -196,6 +196,23 @@ def test_field_with_choices_collision():
|
||||||
convert_django_field_with_choices(field)
|
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():
|
def test_should_float_convert_float():
|
||||||
assert_conversion(models.FloatField, graphene.Float)
|
assert_conversion(models.FloatField, graphene.Float)
|
||||||
|
|
||||||
|
|
|
@ -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 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 graphene.relay import Node
|
||||||
|
|
||||||
from .. import registry
|
from .. import registry
|
||||||
|
@ -224,3 +229,111 @@ def test_django_objecttype_exclude_fields():
|
||||||
|
|
||||||
fields = list(Reporter._meta.fields.keys())
|
fields = list(Reporter._meta.fields.keys())
|
||||||
assert "email" not in fields
|
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
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
|
@ -18,7 +18,9 @@ if six.PY3:
|
||||||
from typing import Type
|
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)
|
_model_fields = get_model_fields(model)
|
||||||
|
|
||||||
fields = OrderedDict()
|
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.
|
# in there. Or when we exclude this field in exclude_fields.
|
||||||
# Or when there is no back reference.
|
# Or when there is no back reference.
|
||||||
continue
|
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
|
fields[name] = converted
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
@ -63,6 +76,7 @@ class DjangoObjectType(ObjectType):
|
||||||
connection_class=None,
|
connection_class=None,
|
||||||
use_connection=None,
|
use_connection=None,
|
||||||
interfaces=(),
|
interfaces=(),
|
||||||
|
convert_choices_to_enum=True,
|
||||||
_meta=None,
|
_meta=None,
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
|
@ -90,7 +104,10 @@ class DjangoObjectType(ObjectType):
|
||||||
)
|
)
|
||||||
|
|
||||||
django_fields = yank_fields_from_attrs(
|
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:
|
if use_connection is None and interfaces:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user