Skip to content

Commit b8e598d

Browse files
jkimbosyrusakbary
andauthored
Add options to override how Django Choice fields are converted t… (#860)
* 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 <[email protected]> * Fix tests * Update docs * Import function through import_string function Co-authored-by: Syrus Akbary <[email protected]>
1 parent 1335221 commit b8e598d

File tree

5 files changed

+171
-4
lines changed

5 files changed

+171
-4
lines changed

docs/settings.rst

+30
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,33 @@ Default: ``False``
140140
# 'messages': ['This field is required.'],
141141
# }
142142
# ]
143+
144+
145+
``DJANGO_CHOICE_FIELD_ENUM_V3_NAMING``
146+
--------------------------------------
147+
148+
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``
149+
150+
Default: ``False``
151+
152+
153+
``DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME``
154+
--------------------------------------
155+
156+
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.
157+
158+
If set to a function then the ``DJANGO_CHOICE_FIELD_ENUM_V3_NAMING`` setting is ignored.
159+
160+
Default: ``None``
161+
162+
.. code:: python
163+
164+
# myapp.utils
165+
def enum_naming(field):
166+
if isinstance(field.model, User):
167+
return f"CustomUserEnum{field.name.title()}"
168+
return f"CustomEnum{field.name.title()}"
169+
170+
GRAPHENE = {
171+
'DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME': "myapp.utils.enum_naming"
172+
}

graphene_django/converter.py

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import OrderedDict
22
from django.db import models
33
from django.utils.encoding import force_str
4+
from django.utils.module_loading import import_string
45

56
from graphene import (
67
ID,
@@ -22,6 +23,7 @@
2223
from graphene.utils.str_converters import to_camel_case, to_const
2324
from graphql import assert_valid_name
2425

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

7072

73+
def generate_enum_name(django_model_meta, field):
74+
if graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME:
75+
# Try and import custom function
76+
custom_func = import_string(
77+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME
78+
)
79+
name = custom_func(field)
80+
elif graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING is True:
81+
name = "{app_label}{object_name}{field_name}Choices".format(
82+
app_label=to_camel_case(django_model_meta.app_label.title()),
83+
object_name=django_model_meta.object_name,
84+
field_name=to_camel_case(field.name.title()),
85+
)
86+
else:
87+
name = to_camel_case("{}_{}".format(django_model_meta.object_name, field.name))
88+
return name
89+
90+
91+
def convert_choice_field_to_enum(field, name=None):
92+
if name is None:
93+
name = generate_enum_name(field.model._meta, field)
94+
choices = field.choices
95+
return convert_choices_to_named_enum_with_descriptions(name, choices)
96+
97+
7198
def convert_django_field_with_choices(
7299
field, registry=None, convert_choices_to_enum=True
73100
):
@@ -77,9 +104,7 @@ def convert_django_field_with_choices(
77104
return converted
78105
choices = getattr(field, "choices", None)
79106
if choices and convert_choices_to_enum:
80-
meta = field.model._meta
81-
name = to_camel_case("{}_{}".format(meta.object_name, field.name))
82-
enum = convert_choices_to_named_enum_with_descriptions(name, choices)
107+
enum = convert_choice_field_to_enum(field)
83108
required = not (field.blank or field.null)
84109
converted = enum(description=field.help_text, required=required)
85110
else:

graphene_django/settings.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
# Max items returned in ConnectionFields / FilterConnectionFields
3737
"RELAY_CONNECTION_MAX_LIMIT": 100,
3838
"CAMELCASE_ERRORS": False,
39+
# Set to True to enable v3 naming convention for choice field Enum's
40+
"DJANGO_CHOICE_FIELD_ENUM_V3_NAMING": False,
41+
"DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME": None,
3942
}
4043

4144
if settings.DEBUG:

graphene_django/tests/test_converter.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from collections import namedtuple
23
from django.db import models
34
from django.utils.translation import ugettext_lazy as _
45
from graphene import NonNull
@@ -10,9 +11,14 @@
1011
from graphene.types.json import JSONString
1112

1213
from ..compat import JSONField, ArrayField, HStoreField, RangeField, MissingType
13-
from ..converter import convert_django_field, convert_django_field_with_choices
14+
from ..converter import (
15+
convert_django_field,
16+
convert_django_field_with_choices,
17+
generate_enum_name,
18+
)
1419
from ..registry import Registry
1520
from ..types import DjangoObjectType
21+
from ..settings import graphene_settings
1622
from .models import Article, Film, FilmDetails, Reporter
1723

1824

@@ -325,3 +331,25 @@ def test_should_postgres_range_convert_list():
325331
assert isinstance(field.type, graphene.NonNull)
326332
assert isinstance(field.type.of_type, graphene.List)
327333
assert field.type.of_type.of_type == graphene.Int
334+
335+
336+
def test_generate_enum_name():
337+
MockDjangoModelMeta = namedtuple("DjangoMeta", ["app_label", "object_name"])
338+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
339+
340+
# Simple case
341+
field = graphene.Field(graphene.String, name="type")
342+
model_meta = MockDjangoModelMeta(app_label="users", object_name="User")
343+
assert generate_enum_name(model_meta, field) == "UsersUserTypeChoices"
344+
345+
# More complicated multiple work case
346+
field = graphene.Field(graphene.String, name="fizz_buzz")
347+
model_meta = MockDjangoModelMeta(
348+
app_label="some_long_app_name", object_name="SomeObject"
349+
)
350+
assert (
351+
generate_enum_name(model_meta, field)
352+
== "SomeLongAppNameSomeObjectFizzBuzzChoices"
353+
)
354+
355+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False

graphene_django/tests/test_types.py

+81
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from graphene.relay import Node
1010

1111
from .. import registry
12+
from ..settings import graphene_settings
1213
from ..types import DjangoObjectType, DjangoObjectTypeOptions
14+
from ..converter import convert_choice_field_to_enum
1315
from .models import Article as ArticleModel
1416
from .models import Reporter as ReporterModel
1517

@@ -386,6 +388,10 @@ class Meta:
386388
assert len(record) == 0
387389

388390

391+
def custom_enum_name(field):
392+
return "CustomEnum{}".format(field.name.title())
393+
394+
389395
class TestDjangoObjectType:
390396
@pytest.fixture
391397
def PetModel(self):
@@ -492,3 +498,78 @@ class Query(ObjectType):
492498
}
493499
"""
494500
)
501+
502+
def test_django_objecttype_convert_choices_enum_naming_collisions(self, PetModel):
503+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = True
504+
505+
class PetModelKind(DjangoObjectType):
506+
class Meta:
507+
model = PetModel
508+
fields = ["id", "kind"]
509+
510+
class Query(ObjectType):
511+
pet = Field(PetModelKind)
512+
513+
schema = Schema(query=Query)
514+
515+
assert str(schema) == dedent(
516+
"""\
517+
schema {
518+
query: Query
519+
}
520+
521+
type PetModelKind {
522+
id: ID!
523+
kind: TestsPetModelKindChoices!
524+
}
525+
526+
type Query {
527+
pet: PetModelKind
528+
}
529+
530+
enum TestsPetModelKindChoices {
531+
CAT
532+
DOG
533+
}
534+
"""
535+
)
536+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V3_NAMING = False
537+
538+
def test_django_objecttype_choices_custom_enum_name(self, PetModel):
539+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = (
540+
"graphene_django.tests.test_types.custom_enum_name"
541+
)
542+
543+
class PetModelKind(DjangoObjectType):
544+
class Meta:
545+
model = PetModel
546+
fields = ["id", "kind"]
547+
548+
class Query(ObjectType):
549+
pet = Field(PetModelKind)
550+
551+
schema = Schema(query=Query)
552+
553+
assert str(schema) == dedent(
554+
"""\
555+
schema {
556+
query: Query
557+
}
558+
559+
enum CustomEnumKind {
560+
CAT
561+
DOG
562+
}
563+
564+
type PetModelKind {
565+
id: ID!
566+
kind: CustomEnumKind!
567+
}
568+
569+
type Query {
570+
pet: PetModelKind
571+
}
572+
"""
573+
)
574+
575+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME = None

0 commit comments

Comments
 (0)