Skip to content

Support for Django Rest Framework serializers #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jul 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fb2af70
Initial implementation of serializer field converter
patrick91 May 28, 2017
dc86e4e
Add optional requires for rest framework
patrick91 May 28, 2017
2fd3cb0
Fix import on python 2
patrick91 May 28, 2017
14bc1cd
Add SerializerMutation base class
patrick91 May 30, 2017
60b6ba8
Initial docs
patrick91 Jun 23, 2017
c389924
Use six.with_metaclass to support python 2.7
patrick91 Jun 23, 2017
d10895d
Refactor converter
patrick91 Jun 26, 2017
6de3bbc
Small test refactor
patrick91 Jun 26, 2017
f747102
Add support for rest framework List Field
patrick91 Jun 26, 2017
68f6281
Add support for dict field
patrick91 Jun 26, 2017
a7c3337
Add test for duration field
patrick91 Jun 26, 2017
772e2d1
Add test for file field
patrick91 Jun 26, 2017
47c5dfc
Add test for FilePathField
patrick91 Jun 26, 2017
b500ffb
Add test for IPAddressField
patrick91 Jun 26, 2017
66d1875
Fix missing path
patrick91 Jun 26, 2017
0e2c736
Add test for image field
patrick91 Jun 26, 2017
510ee93
Add support for JSONField
patrick91 Jun 26, 2017
1a04d60
Add support for MultipleChoiceField
patrick91 Jun 26, 2017
7888fa7
Restore django filter check
patrick91 Jun 26, 2017
000ef6c
Fix result from mutation
patrick91 Jun 26, 2017
5c3306e
Return empty errors when successful
patrick91 Jun 26, 2017
93bbc19
Add missing new line
patrick91 Jun 29, 2017
302ea0d
Account for nested ModelSerializers
Jul 11, 2017
ee23638
Add converters for datetime fields
Jul 11, 2017
81a6dff
Update field converter tests
Jul 18, 2017
afbe6c9
Add nested model mutation tests
Jul 18, 2017
42e9107
Merge pull request #1 from spockNinja/feature/rest-framework
patrick91 Jul 21, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ Contents:
filtering
authorization
debug
rest-framework
introspection
21 changes: 21 additions & 0 deletions docs/rest-framework.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Integration with Django Rest Framework
======================================

You can re-use your Django Rest Framework serializer with
graphene django.


Mutation
--------

You can create a Mutation based on a serializer by using the
`SerializerMutation` base class:

.. code:: python

from graphene_django.rest_framework.mutation import SerializerMutation

class MyAwesomeMutation(SerializerMutation):
class Meta:
serializer_class = MySerializer

Empty file.
129 changes: 129 additions & 0 deletions graphene_django/rest_framework/mutation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from collections import OrderedDict
from functools import partial

import six
import graphene
from graphene.types import Argument, Field
from graphene.types.mutation import Mutation, MutationMeta
from graphene.types.objecttype import (
ObjectTypeMeta,
merge,
yank_fields_from_attrs
)
from graphene.types.options import Options
from graphene.types.utils import get_field_as
from graphene.utils.is_base_type import is_base_type

from .serializer_converter import (
convert_serializer_to_input_type,
convert_serializer_field
)
from .types import ErrorType


class SerializerMutationOptions(Options):
def __init__(self, *args, **kwargs):
super().__init__(*args, serializer_class=None, **kwargs)


class SerializerMutationMeta(MutationMeta):
def __new__(cls, name, bases, attrs):
if not is_base_type(bases, SerializerMutationMeta):
return type.__new__(cls, name, bases, attrs)

options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.pop('__doc__', None),
serializer_class=None,
local_fields=None,
only_fields=(),
exclude_fields=(),
interfaces=(),
registry=None
)

if not options.serializer_class:
raise Exception('Missing serializer_class')

cls = ObjectTypeMeta.__new__(
cls, name, bases, dict(attrs, _meta=options)
)

serializer_fields = cls.fields_for_serializer(options)
options.serializer_fields = yank_fields_from_attrs(
serializer_fields,
_as=Field,
)

options.fields = merge(
options.interface_fields, options.serializer_fields,
options.base_fields, options.local_fields,
{'errors': get_field_as(cls.errors, Field)}
)

cls.Input = convert_serializer_to_input_type(options.serializer_class)

cls.Field = partial(
Field,
cls,
resolver=cls.mutate,
input=Argument(cls.Input, required=True)
)

return cls

@staticmethod
def fields_for_serializer(options):
serializer = options.serializer_class()

only_fields = options.only_fields

already_created_fields = {
name
for name, _ in options.local_fields.items()
}

fields = OrderedDict()
for name, field in serializer.fields.items():
is_not_in_only = only_fields and name not in only_fields
is_excluded = (
name in options.exclude_fields or
name in already_created_fields
)

if is_not_in_only or is_excluded:
continue

fields[name] = convert_serializer_field(field, is_input=False)
return fields


class SerializerMutation(six.with_metaclass(SerializerMutationMeta, Mutation)):
errors = graphene.List(
ErrorType,
description='May contain more than one error for '
'same field.'
)

@classmethod
def mutate(cls, instance, args, request, info):
input = args.get('input')

serializer = cls._meta.serializer_class(data=dict(input))

if serializer.is_valid():
return cls.perform_mutate(serializer, info)
else:
errors = [
ErrorType(field=key, messages=value)
for key, value in serializer.errors.items()
]

return cls(errors=errors)

@classmethod
def perform_mutate(cls, serializer, info):
obj = serializer.save()

return cls(errors=[], **obj)
124 changes: 124 additions & 0 deletions graphene_django/rest_framework/serializer_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers

import graphene

from ..registry import get_global_registry
from ..utils import import_single_dispatch
from .types import DictType

singledispatch = import_single_dispatch()


def convert_serializer_to_input_type(serializer_class):
serializer = serializer_class()

items = {
name: convert_serializer_field(field)
for name, field in serializer.fields.items()
}

return type(
'{}Input'.format(serializer.__class__.__name__),
(graphene.InputObjectType, ),
items
)


@singledispatch
def get_graphene_type_from_serializer_field(field):
raise ImproperlyConfigured(
"Don't know how to convert the serializer field %s (%s) "
"to Graphene type" % (field, field.__class__)
)


def convert_serializer_field(field, is_input=True):
"""
Converts a django rest frameworks field to a graphql field
and marks the field as required if we are creating an input type
and the field itself is required
"""

graphql_type = get_graphene_type_from_serializer_field(field)

args = []
kwargs = {
'description': field.help_text,
'required': is_input and field.required,
}

# if it is a tuple or a list it means that we are returning
# the graphql type and the child type
if isinstance(graphql_type, (list, tuple)):
kwargs['of_type'] = graphql_type[1]
graphql_type = graphql_type[0]

if isinstance(field, serializers.ModelSerializer):
if is_input:
graphql_type = convert_serializer_to_input_type(field.__class__)
else:
global_registry = get_global_registry()
field_model = field.Meta.model
args = [global_registry.get_type_for_model(field_model)]

return graphql_type(*args, **kwargs)


@get_graphene_type_from_serializer_field.register(serializers.Field)
def convert_serializer_field_to_string(field):
return graphene.String


@get_graphene_type_from_serializer_field.register(serializers.ModelSerializer)
def convert_serializer_to_field(field):
return graphene.Field


@get_graphene_type_from_serializer_field.register(serializers.IntegerField)
def convert_serializer_field_to_int(field):
return graphene.Int


@get_graphene_type_from_serializer_field.register(serializers.BooleanField)
def convert_serializer_field_to_bool(field):
return graphene.Boolean


@get_graphene_type_from_serializer_field.register(serializers.FloatField)
@get_graphene_type_from_serializer_field.register(serializers.DecimalField)
def convert_serializer_field_to_float(field):
return graphene.Float


@get_graphene_type_from_serializer_field.register(serializers.DateTimeField)
@get_graphene_type_from_serializer_field.register(serializers.DateField)
def convert_serializer_field_to_date_time(field):
return graphene.types.datetime.DateTime


@get_graphene_type_from_serializer_field.register(serializers.TimeField)
def convert_serializer_field_to_time(field):
return graphene.types.datetime.Time


@get_graphene_type_from_serializer_field.register(serializers.ListField)
def convert_serializer_field_to_list(field, is_input=True):
child_type = get_graphene_type_from_serializer_field(field.child)

return (graphene.List, child_type)


@get_graphene_type_from_serializer_field.register(serializers.DictField)
def convert_serializer_field_to_dict(field):
return DictType


@get_graphene_type_from_serializer_field.register(serializers.JSONField)
def convert_serializer_field_to_jsonstring(field):
return graphene.types.json.JSONString


@get_graphene_type_from_serializer_field.register(serializers.MultipleChoiceField)
def convert_serializer_field_to_list_of_string(field):
return (graphene.List, graphene.String)
Empty file.
Loading