mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-22 09:37:07 +03:00
Add SerializerMutation base class
This commit is contained in:
parent
2fd3cb032c
commit
14bc1cdb92
126
graphene_django/rest_framework/mutation.py
Normal file
126
graphene_django/rest_framework/mutation.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
|
||||
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(Mutation, metaclass=SerializerMutationMeta):
|
||||
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):
|
||||
return serializer.save()
|
|
@ -8,6 +8,21 @@ from ..utils import import_single_dispatch
|
|||
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 convert_serializer_field(field):
|
||||
raise ImproperlyConfigured(
|
||||
|
|
35
graphene_django/rest_framework/tests/test_mutation.py
Normal file
35
graphene_django/rest_framework/tests/test_mutation.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from py.test import raises
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..mutation import SerializerMutation
|
||||
|
||||
|
||||
class MySerializer(serializers.Serializer):
|
||||
text = serializers.CharField()
|
||||
|
||||
|
||||
def test_needs_serializer_class():
|
||||
with raises(Exception) as exc:
|
||||
class MyMutation(SerializerMutation):
|
||||
pass
|
||||
|
||||
assert exc.value.args[0] == 'Missing serializer_class'
|
||||
|
||||
|
||||
def test_has_fields():
|
||||
class MyMutation(SerializerMutation):
|
||||
class Meta:
|
||||
serializer_class = MySerializer
|
||||
|
||||
assert 'text' in MyMutation._meta.fields
|
||||
assert 'errors' in MyMutation._meta.fields
|
||||
|
||||
|
||||
def test_has_input_fields():
|
||||
class MyMutation(SerializerMutation):
|
||||
class Meta:
|
||||
serializer_class = MySerializer
|
||||
|
||||
assert 'text' in MyMutation.Input._meta.fields
|
||||
|
||||
|
6
graphene_django/rest_framework/types.py
Normal file
6
graphene_django/rest_framework/types.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import graphene
|
||||
|
||||
|
||||
class ErrorType(graphene.ObjectType):
|
||||
field = graphene.String()
|
||||
messages = graphene.List(graphene.String)
|
Loading…
Reference in New Issue
Block a user