Skip to content

Commit 6ad64dd

Browse files
authored
Merge pull request #186 from patrick91/feature/rest-framework
Support for Django Rest Framework serializers
2 parents 0588f89 + 42e9107 commit 6ad64dd

File tree

11 files changed

+532
-3
lines changed

11 files changed

+532
-3
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Contents:
1111
filtering
1212
authorization
1313
debug
14+
rest-framework
1415
introspection

docs/rest-framework.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Integration with Django Rest Framework
2+
======================================
3+
4+
You can re-use your Django Rest Framework serializer with
5+
graphene django.
6+
7+
8+
Mutation
9+
--------
10+
11+
You can create a Mutation based on a serializer by using the
12+
`SerializerMutation` base class:
13+
14+
.. code:: python
15+
16+
from graphene_django.rest_framework.mutation import SerializerMutation
17+
18+
class MyAwesomeMutation(SerializerMutation):
19+
class Meta:
20+
serializer_class = MySerializer
21+

graphene_django/rest_framework/__init__.py

Whitespace-only changes.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from collections import OrderedDict
2+
from functools import partial
3+
4+
import six
5+
import graphene
6+
from graphene.types import Argument, Field
7+
from graphene.types.mutation import Mutation, MutationMeta
8+
from graphene.types.objecttype import (
9+
ObjectTypeMeta,
10+
merge,
11+
yank_fields_from_attrs
12+
)
13+
from graphene.types.options import Options
14+
from graphene.types.utils import get_field_as
15+
from graphene.utils.is_base_type import is_base_type
16+
17+
from .serializer_converter import (
18+
convert_serializer_to_input_type,
19+
convert_serializer_field
20+
)
21+
from .types import ErrorType
22+
23+
24+
class SerializerMutationOptions(Options):
25+
def __init__(self, *args, **kwargs):
26+
super().__init__(*args, serializer_class=None, **kwargs)
27+
28+
29+
class SerializerMutationMeta(MutationMeta):
30+
def __new__(cls, name, bases, attrs):
31+
if not is_base_type(bases, SerializerMutationMeta):
32+
return type.__new__(cls, name, bases, attrs)
33+
34+
options = Options(
35+
attrs.pop('Meta', None),
36+
name=name,
37+
description=attrs.pop('__doc__', None),
38+
serializer_class=None,
39+
local_fields=None,
40+
only_fields=(),
41+
exclude_fields=(),
42+
interfaces=(),
43+
registry=None
44+
)
45+
46+
if not options.serializer_class:
47+
raise Exception('Missing serializer_class')
48+
49+
cls = ObjectTypeMeta.__new__(
50+
cls, name, bases, dict(attrs, _meta=options)
51+
)
52+
53+
serializer_fields = cls.fields_for_serializer(options)
54+
options.serializer_fields = yank_fields_from_attrs(
55+
serializer_fields,
56+
_as=Field,
57+
)
58+
59+
options.fields = merge(
60+
options.interface_fields, options.serializer_fields,
61+
options.base_fields, options.local_fields,
62+
{'errors': get_field_as(cls.errors, Field)}
63+
)
64+
65+
cls.Input = convert_serializer_to_input_type(options.serializer_class)
66+
67+
cls.Field = partial(
68+
Field,
69+
cls,
70+
resolver=cls.mutate,
71+
input=Argument(cls.Input, required=True)
72+
)
73+
74+
return cls
75+
76+
@staticmethod
77+
def fields_for_serializer(options):
78+
serializer = options.serializer_class()
79+
80+
only_fields = options.only_fields
81+
82+
already_created_fields = {
83+
name
84+
for name, _ in options.local_fields.items()
85+
}
86+
87+
fields = OrderedDict()
88+
for name, field in serializer.fields.items():
89+
is_not_in_only = only_fields and name not in only_fields
90+
is_excluded = (
91+
name in options.exclude_fields or
92+
name in already_created_fields
93+
)
94+
95+
if is_not_in_only or is_excluded:
96+
continue
97+
98+
fields[name] = convert_serializer_field(field, is_input=False)
99+
return fields
100+
101+
102+
class SerializerMutation(six.with_metaclass(SerializerMutationMeta, Mutation)):
103+
errors = graphene.List(
104+
ErrorType,
105+
description='May contain more than one error for '
106+
'same field.'
107+
)
108+
109+
@classmethod
110+
def mutate(cls, instance, args, request, info):
111+
input = args.get('input')
112+
113+
serializer = cls._meta.serializer_class(data=dict(input))
114+
115+
if serializer.is_valid():
116+
return cls.perform_mutate(serializer, info)
117+
else:
118+
errors = [
119+
ErrorType(field=key, messages=value)
120+
for key, value in serializer.errors.items()
121+
]
122+
123+
return cls(errors=errors)
124+
125+
@classmethod
126+
def perform_mutate(cls, serializer, info):
127+
obj = serializer.save()
128+
129+
return cls(errors=[], **obj)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from django.core.exceptions import ImproperlyConfigured
2+
from rest_framework import serializers
3+
4+
import graphene
5+
6+
from ..registry import get_global_registry
7+
from ..utils import import_single_dispatch
8+
from .types import DictType
9+
10+
singledispatch = import_single_dispatch()
11+
12+
13+
def convert_serializer_to_input_type(serializer_class):
14+
serializer = serializer_class()
15+
16+
items = {
17+
name: convert_serializer_field(field)
18+
for name, field in serializer.fields.items()
19+
}
20+
21+
return type(
22+
'{}Input'.format(serializer.__class__.__name__),
23+
(graphene.InputObjectType, ),
24+
items
25+
)
26+
27+
28+
@singledispatch
29+
def get_graphene_type_from_serializer_field(field):
30+
raise ImproperlyConfigured(
31+
"Don't know how to convert the serializer field %s (%s) "
32+
"to Graphene type" % (field, field.__class__)
33+
)
34+
35+
36+
def convert_serializer_field(field, is_input=True):
37+
"""
38+
Converts a django rest frameworks field to a graphql field
39+
and marks the field as required if we are creating an input type
40+
and the field itself is required
41+
"""
42+
43+
graphql_type = get_graphene_type_from_serializer_field(field)
44+
45+
args = []
46+
kwargs = {
47+
'description': field.help_text,
48+
'required': is_input and field.required,
49+
}
50+
51+
# if it is a tuple or a list it means that we are returning
52+
# the graphql type and the child type
53+
if isinstance(graphql_type, (list, tuple)):
54+
kwargs['of_type'] = graphql_type[1]
55+
graphql_type = graphql_type[0]
56+
57+
if isinstance(field, serializers.ModelSerializer):
58+
if is_input:
59+
graphql_type = convert_serializer_to_input_type(field.__class__)
60+
else:
61+
global_registry = get_global_registry()
62+
field_model = field.Meta.model
63+
args = [global_registry.get_type_for_model(field_model)]
64+
65+
return graphql_type(*args, **kwargs)
66+
67+
68+
@get_graphene_type_from_serializer_field.register(serializers.Field)
69+
def convert_serializer_field_to_string(field):
70+
return graphene.String
71+
72+
73+
@get_graphene_type_from_serializer_field.register(serializers.ModelSerializer)
74+
def convert_serializer_to_field(field):
75+
return graphene.Field
76+
77+
78+
@get_graphene_type_from_serializer_field.register(serializers.IntegerField)
79+
def convert_serializer_field_to_int(field):
80+
return graphene.Int
81+
82+
83+
@get_graphene_type_from_serializer_field.register(serializers.BooleanField)
84+
def convert_serializer_field_to_bool(field):
85+
return graphene.Boolean
86+
87+
88+
@get_graphene_type_from_serializer_field.register(serializers.FloatField)
89+
@get_graphene_type_from_serializer_field.register(serializers.DecimalField)
90+
def convert_serializer_field_to_float(field):
91+
return graphene.Float
92+
93+
94+
@get_graphene_type_from_serializer_field.register(serializers.DateTimeField)
95+
@get_graphene_type_from_serializer_field.register(serializers.DateField)
96+
def convert_serializer_field_to_date_time(field):
97+
return graphene.types.datetime.DateTime
98+
99+
100+
@get_graphene_type_from_serializer_field.register(serializers.TimeField)
101+
def convert_serializer_field_to_time(field):
102+
return graphene.types.datetime.Time
103+
104+
105+
@get_graphene_type_from_serializer_field.register(serializers.ListField)
106+
def convert_serializer_field_to_list(field, is_input=True):
107+
child_type = get_graphene_type_from_serializer_field(field.child)
108+
109+
return (graphene.List, child_type)
110+
111+
112+
@get_graphene_type_from_serializer_field.register(serializers.DictField)
113+
def convert_serializer_field_to_dict(field):
114+
return DictType
115+
116+
117+
@get_graphene_type_from_serializer_field.register(serializers.JSONField)
118+
def convert_serializer_field_to_jsonstring(field):
119+
return graphene.types.json.JSONString
120+
121+
122+
@get_graphene_type_from_serializer_field.register(serializers.MultipleChoiceField)
123+
def convert_serializer_field_to_list_of_string(field):
124+
return (graphene.List, graphene.String)

graphene_django/rest_framework/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)