mirror of
				https://github.com/graphql-python/graphene-django.git
				synced 2025-11-04 18:08:01 +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!'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
        }
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user