Skip to content

Commit

Permalink
Add options to override how Django Choice fields are converted t… (#860)
Browse files Browse the repository at this point in the history
* Add new setting to create unique enum names

* Add specific tests for name generation

* Add schema test

* Rename settings field

* Rename setting

* Add custom function setting

* Add documentation

* Use format instead of f strings

* Update graphene_django/converter.py

Co-Authored-By: Syrus Akbary <me@syrusakbary.com>

* Fix tests

* Update docs

* Import function through import_string function

Co-authored-by: Syrus Akbary <me@syrusakbary.com>
  • Loading branch information
jkimbo and syrusakbary committed Mar 13, 2020
1 parent 1335221 commit b8e598d
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 4 deletions.
30 changes: 30 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,33 @@ Default: ``False``
# 'messages': ['This field is required.'],
# }
# ]
``DJANGO_CHOICE_FIELD_ENUM_V3_NAMING``
--------------------------------------

Set to ``True`` to use the new naming format for the auto generated Enum types from Django choice fields. The new format looks like this: ``{app_label}{object_name}{field_name}Choices``

Default: ``False``


``DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME``
--------------------------------------

Define the path of a function that takes the Django choice field and returns a string to completely customise the naming for the Enum type.

If set to a function then the ``DJANGO_CHOICE_FIELD_ENUM_V3_NAMING`` setting is ignored.

Default: ``None``

.. code:: python
# myapp.utils
def enum_naming(field):
if isinstance(field.model, User):
return f"CustomUserEnum{field.name.title()}"
return f"CustomEnum{field.name.title()}"
GRAPHENE = {
'DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME': "myapp.utils.enum_naming"
}
31 changes: 28 additions & 3 deletions graphene_django/converter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import OrderedDict
from django.db import models
from django.utils.encoding import force_str
from django.utils.module_loading import import_string

from graphene import (
ID,
Expand All @@ -22,6 +23,7 @@
from graphene.utils.str_converters import to_camel_case, to_const
from graphql import assert_valid_name

from .settings import graphene_settings
from .compat import ArrayField, HStoreField, JSONField, RangeField
from .fields import DjangoListField, DjangoConnectionField
from .utils import import_single_dispatch
Expand Down Expand Up @@ -68,6 +70,31 @@ def description(self):
return Enum(name, list(named_choices), type=EnumWithDescriptionsType)


def generate_enum_name(django_model_meta, field):
if graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME:
# Try and import custom function
custom_func = import_string(
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME
)
name = custom_func(field)
elif graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING is True:
name = "{app_label}{object_name}{field_name}Choices".format(
app_label=to_camel_case(django_model_meta.app_label.title()),
object_name=django_model_meta.object_name,
field_name=to_camel_case(field.name.title()),
)
else:
name = to_camel_case("{}_{}".format(django_model_meta.object_name, field.name))
return name


def convert_choice_field_to_enum(field, name=None):
if name is None:
name = generate_enum_name(field.model._meta, field)
choices = field.choices
return convert_choices_to_named_enum_with_descriptions(name, choices)


def convert_django_field_with_choices(
field, registry=None, convert_choices_to_enum=True
):
Expand All @@ -77,9 +104,7 @@ def convert_django_field_with_choices(
return converted
choices = getattr(field, "choices", None)
if choices and convert_choices_to_enum:
meta = field.model._meta
name = to_camel_case("{}_{}".format(meta.object_name, field.name))
enum = convert_choices_to_named_enum_with_descriptions(name, choices)
enum = convert_choice_field_to_enum(field)
required = not (field.blank or field.null)
converted = enum(description=field.help_text, required=required)
else:
Expand Down
3 changes: 3 additions & 0 deletions graphene_django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
# Max items returned in ConnectionFields / FilterConnectionFields
"RELAY_CONNECTION_MAX_LIMIT": 100,
"CAMELCASE_ERRORS": False,
# Set to True to enable v3 naming convention for choice field Enum's
"DJANGO_CHOICE_FIELD_ENUM_V3_NAMING": False,
"DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME": None,
}

if settings.DEBUG:
Expand Down
30 changes: 29 additions & 1 deletion graphene_django/tests/test_converter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from collections import namedtuple
from django.db import models
from django.utils.translation import ugettext_lazy as _
from graphene import NonNull
Expand All @@ -10,9 +11,14 @@
from graphene.types.json import JSONString

from ..compat import JSONField, ArrayField, HStoreField, RangeField, MissingType
from ..converter import convert_django_field, convert_django_field_with_choices
from ..converter import (
convert_django_field,
convert_django_field_with_choices,
generate_enum_name,
)
from ..registry import Registry
from ..types import DjangoObjectType
from ..settings import graphene_settings
from .models import Article, Film, FilmDetails, Reporter


Expand Down Expand Up @@ -325,3 +331,25 @@ def test_should_postgres_range_convert_list():
assert isinstance(field.type, graphene.NonNull)
assert isinstance(field.type.of_type, graphene.List)
assert field.type.of_type.of_type == graphene.Int


def test_generate_enum_name():
MockDjangoModelMeta = namedtuple("DjangoMeta", ["app_label", "object_name"])
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True

# Simple case
field = graphene.Field(graphene.String, name="type")
model_meta = MockDjangoModelMeta(app_label="users", object_name="User")
assert generate_enum_name(model_meta, field) == "UsersUserTypeChoices"

# More complicated multiple work case
field = graphene.Field(graphene.String, name="fizz_buzz")
model_meta = MockDjangoModelMeta(
app_label="some_long_app_name", object_name="SomeObject"
)
assert (
generate_enum_name(model_meta, field)
== "SomeLongAppNameSomeObjectFizzBuzzChoices"
)

graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False
81 changes: 81 additions & 0 deletions graphene_django/tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from graphene.relay import Node

from .. import registry
from ..settings import graphene_settings
from ..types import DjangoObjectType, DjangoObjectTypeOptions
from ..converter import convert_choice_field_to_enum
from .models import Article as ArticleModel
from .models import Reporter as ReporterModel

Expand Down Expand Up @@ -386,6 +388,10 @@ class Meta:
assert len(record) == 0


def custom_enum_name(field):
return "CustomEnum{}".format(field.name.title())


class TestDjangoObjectType:
@pytest.fixture
def PetModel(self):
Expand Down Expand Up @@ -492,3 +498,78 @@ class Query(ObjectType):
}
"""
)

def test_django_objecttype_convert_choices_enum_naming_collisions(self, PetModel):
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True

class PetModelKind(DjangoObjectType):
class Meta:
model = PetModel
fields = ["id", "kind"]

class Query(ObjectType):
pet = Field(PetModelKind)

schema = Schema(query=Query)

assert str(schema) == dedent(
"""\
schema {
query: Query
}
type PetModelKind {
id: ID!
kind: TestsPetModelKindChoices!
}
type Query {
pet: PetModelKind
}
enum TestsPetModelKindChoices {
CAT
DOG
}
"""
)
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False

def test_django_objecttype_choices_custom_enum_name(self, PetModel):
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = (
"graphene_django.tests.test_types.custom_enum_name"
)

class PetModelKind(DjangoObjectType):
class Meta:
model = PetModel
fields = ["id", "kind"]

class Query(ObjectType):
pet = Field(PetModelKind)

schema = Schema(query=Query)

assert str(schema) == dedent(
"""\
schema {
query: Query
}
enum CustomEnumKind {
CAT
DOG
}
type PetModelKind {
id: ID!
kind: CustomEnumKind!
}
type Query {
pet: PetModelKind
}
"""
)

graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = None

0 comments on commit b8e598d

Please sign in to comment.