Skip to content

Commit bc6a08e

Browse files
committed
Add DjangoFormInputObjectType to forms/types
InputObjectType derived class which gets fields from django form. Type of fields with choices (converted to enum) is set to custom scalar type (using Meta.object_type) to dynamically convert enum values back.
1 parent f6ec068 commit bc6a08e

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

graphene_django/forms/types.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,120 @@
1+
import graphene
2+
3+
from graphene import ID
4+
from graphene.types.inputobjecttype import InputObjectType
5+
from graphene.utils.str_converters import to_camel_case
6+
7+
from .mutation import fields_for_form
18
from ..types import ErrorType # noqa Import ErrorType for backwards compatability
9+
from ..converter import BlankValueField
10+
11+
12+
class DjangoFormInputObjectType(InputObjectType):
13+
@classmethod
14+
def __init_subclass_with_meta__(
15+
cls,
16+
container=None,
17+
_meta=None,
18+
only_fields=(),
19+
exclude_fields=(),
20+
form_class=None,
21+
object_type=None,
22+
add_id_field_name=None,
23+
add_id_field_type=None,
24+
**options,
25+
):
26+
"""Retrieve fields from django form (Meta.form_class). Received
27+
fields are set to cls (they will be converted to input fields
28+
by InputObjectType). Type of fields with choices (converted
29+
to enum) is set to custom scalar type (using Meta.object_type)
30+
to dynamically convert enum values back.
31+
32+
class MyDjangoFormInput(DjangoFormInputObjectType):
33+
# any other fields can be placed here and other inputobjectforms as well
34+
35+
class Meta:
36+
form_class = MyDjangoModelForm
37+
object_type = MyModelType
38+
39+
class SomeMutation(graphene.Mutation):
40+
class Arguments:
41+
data = MyDjangoFormInput(required=True)
42+
43+
@staticmethod
44+
def mutate(_root, _info, data):
45+
form_inst = MyDjangoModelForm(data=data)
46+
if form_inst.is_valid():
47+
django_model_instance = form_inst.save(commit=False)
48+
# ... etc ...
49+
"""
50+
51+
if not form_class:
52+
raise Exception("form_class is required for DjangoFormInputObjectType")
53+
54+
form = form_class()
55+
form_fields = fields_for_form(form, only_fields, exclude_fields)
56+
57+
for name, field in form_fields.items():
58+
if (
59+
object_type
60+
and name in object_type._meta.fields
61+
and isinstance(object_type._meta.fields[name], BlankValueField)
62+
):
63+
# Field type BlankValueField here means that field
64+
# with choises have been converted to enum
65+
# (BlankValueField is using only for that task ?)
66+
setattr(cls, name, cls.get_enum_cnv_cls_instance(name, object_type))
67+
elif (
68+
object_type
69+
and name in object_type._meta.fields
70+
and object_type._meta.convert_choices_to_enum is False
71+
and form.fields[name].__class__.__name__ == "TypedChoiceField"
72+
):
73+
# FIXME
74+
# in case if convert_choices_to_enum is False
75+
# form field class is converted to String but original
76+
# model field type is needed here... (.converter.py bug?)
77+
# This is temp workaround to get field type from ObjectType field
78+
# TEST: test_enum_not_converted_and_field_type_as_in_model
79+
setattr(cls, name, object_type._meta.fields[name].type())
80+
else:
81+
# set input field according to django form field
82+
setattr(cls, name, field)
83+
84+
# explicitly adding id field (absent in django form fields)
85+
# with name and type from Meta or 'id' with graphene.ID by default
86+
if add_id_field_name:
87+
setattr(cls, add_id_field_name, add_id_field_type or ID(required=False))
88+
elif "id" not in exclude_fields:
89+
cls.id = ID(required=False)
90+
91+
super(DjangoFormInputObjectType, cls).__init_subclass_with_meta__(
92+
container=container, _meta=_meta, **options
93+
)
94+
95+
@staticmethod
96+
def get_enum_cnv_cls_instance(field_name, object_type):
97+
"""Saves args in context to convert enum values in
98+
Dynamically created Scalar derived class
99+
"""
100+
101+
@staticmethod
102+
def parse_value(value):
103+
# field_name & object_type have been saved in context (closure)
104+
field = object_type._meta.fields[field_name]
105+
if isinstance(field.type, graphene.NonNull):
106+
val_before_convert = field.type._of_type[value].value
107+
else:
108+
val_before_convert = field.type[value].value
109+
return graphene.String.parse_value(val_before_convert)
110+
111+
cls_doc = "String scalar to convert choice value back from enum to original"
112+
scalar_type = type(
113+
(
114+
f"{field_name[0].upper()}{to_camel_case(field_name[1:])}"
115+
"EnumBackConvString"
116+
),
117+
(graphene.String,),
118+
{"parse_value": parse_value, "__doc__": cls_doc},
119+
)
120+
return scalar_type()

graphene_django/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def __init_subclass_with_meta__(
254254
_meta.filterset_class = filterset_class
255255
_meta.fields = django_fields
256256
_meta.connection = connection
257+
_meta.convert_choices_to_enum = convert_choices_to_enum
257258

258259
super(DjangoObjectType, cls).__init_subclass_with_meta__(
259260
_meta=_meta, interfaces=interfaces, **options

0 commit comments

Comments
 (0)