graphene-django/graphene_django/rest_framework/mutation.py

182 lines
5.8 KiB
Python
Raw Normal View History

from enum import Enum
2017-05-31 01:30:03 +03:00
from collections import OrderedDict
2017-11-15 01:02:28 +03:00
from django.shortcuts import get_object_or_404
from rest_framework import serializers
2017-11-15 01:02:28 +03:00
2017-05-31 01:30:03 +03:00
import graphene
from graphene.relay.mutation import ClientIDMutation
2017-07-25 09:42:40 +03:00
from graphene.types import Field, InputField
from graphene.types.mutation import MutationOptions
2018-07-20 02:51:33 +03:00
from graphene.types.objecttype import yank_fields_from_attrs
2017-05-31 01:30:03 +03:00
from ..types import ErrorType
from .serializer_converter import convert_serializer_field
2017-05-31 01:30:03 +03:00
2017-07-25 08:27:50 +03:00
class SerializerMutationOptions(MutationOptions):
2017-11-15 01:02:28 +03:00
lookup_field = None
model_class = None
2018-07-20 02:51:33 +03:00
model_operations = ["create", "update"]
2017-07-25 08:27:50 +03:00
serializer_class = None
2017-05-31 01:30:03 +03:00
def fields_for_serializer(
serializer,
only_fields,
exclude_fields,
is_input=False,
convert_choices_to_enum=True,
lookup_field=None,
):
2017-07-25 08:27:50 +03:00
fields = OrderedDict()
for name, field in serializer.fields.items():
is_not_in_only = only_fields and name not in only_fields
is_excluded = any(
[
name in exclude_fields,
field.write_only
and not is_input, # don't show write_only fields in Query
field.read_only
and is_input
and lookup_field != name, # don't show read_only fields in Input
isinstance(
field, serializers.HiddenField
), # don't show hidden fields in Input
]
2017-05-31 01:30:03 +03:00
)
2017-07-25 08:27:50 +03:00
if is_not_in_only or is_excluded:
continue
2017-05-31 01:30:03 +03:00
fields[name] = convert_serializer_field(
field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum
)
2017-07-25 08:27:50 +03:00
return fields
2017-05-31 01:30:03 +03:00
2017-07-25 09:42:40 +03:00
class SerializerMutation(ClientIDMutation):
class Meta:
abstract = True
2017-07-25 08:27:50 +03:00
errors = graphene.List(
2018-07-20 02:51:33 +03:00
ErrorType, description="May contain more than one error for same field."
2017-07-25 08:27:50 +03:00
)
2017-05-31 01:30:03 +03:00
2017-07-25 08:27:50 +03:00
@classmethod
2018-07-20 02:51:33 +03:00
def __init_subclass_with_meta__(
cls,
lookup_field=None,
serializer_class=None,
model_class=None,
model_operations=("create", "update"),
2018-07-20 02:51:33 +03:00
only_fields=(),
exclude_fields=(),
convert_choices_to_enum=True,
_meta=None,
2018-07-20 02:51:33 +03:00
**options
):
2017-07-25 08:27:50 +03:00
if not serializer_class:
2018-07-20 02:51:33 +03:00
raise Exception("serializer_class is required for the SerializerMutation")
2017-05-31 01:30:03 +03:00
2018-07-20 02:51:33 +03:00
if "update" not in model_operations and "create" not in model_operations:
2017-11-15 01:02:28 +03:00
raise Exception('model_operations must contain "create" and/or "update"')
2017-07-25 08:27:50 +03:00
serializer = serializer_class()
2017-11-15 01:02:28 +03:00
if model_class is None:
2018-07-20 02:51:33 +03:00
serializer_meta = getattr(serializer_class, "Meta", None)
2017-11-15 01:02:28 +03:00
if serializer_meta:
2018-07-20 02:51:33 +03:00
model_class = getattr(serializer_meta, "model", None)
2017-11-15 01:02:28 +03:00
if lookup_field is None and model_class:
lookup_field = model_class._meta.pk.name
2018-07-20 02:51:33 +03:00
input_fields = fields_for_serializer(
serializer,
only_fields,
exclude_fields,
is_input=True,
convert_choices_to_enum=convert_choices_to_enum,
lookup_field=lookup_field,
2018-07-20 02:51:33 +03:00
)
output_fields = fields_for_serializer(
serializer,
only_fields,
exclude_fields,
is_input=False,
convert_choices_to_enum=convert_choices_to_enum,
lookup_field=lookup_field,
2018-07-20 02:51:33 +03:00
)
2017-05-31 01:30:03 +03:00
if not _meta:
_meta = SerializerMutationOptions(cls)
2017-11-15 01:02:28 +03:00
_meta.lookup_field = lookup_field
_meta.model_operations = model_operations
_meta.serializer_class = serializer_class
2017-11-15 01:02:28 +03:00
_meta.model_class = model_class
2018-07-20 02:51:33 +03:00
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
2017-05-31 01:30:03 +03:00
2018-07-20 02:51:33 +03:00
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super().__init_subclass_with_meta__(
2018-07-20 02:51:33 +03:00
_meta=_meta, input_fields=input_fields, **options
2017-07-25 08:27:50 +03:00
)
2017-05-31 01:30:03 +03:00
2017-11-15 01:02:28 +03:00
@classmethod
def get_serializer_kwargs(cls, root, info, **input):
lookup_field = cls._meta.lookup_field
model_class = cls._meta.model_class
if model_class:
for input_dict_key, maybe_enum in input.items():
if isinstance(maybe_enum, Enum):
input[input_dict_key] = maybe_enum.value
2018-07-20 02:51:33 +03:00
if "update" in cls._meta.model_operations and lookup_field in input:
instance = get_object_or_404(
model_class, **{lookup_field: input[lookup_field]}
)
partial = True
2018-07-20 02:51:33 +03:00
elif "create" in cls._meta.model_operations:
2017-11-15 01:02:28 +03:00
instance = None
partial = False
2017-11-15 01:02:28 +03:00
else:
raise Exception(
'Invalid update operation. Input parameter "{}" required.'.format(
lookup_field
2018-07-20 02:51:33 +03:00
)
)
2017-11-15 01:02:28 +03:00
return {
2018-07-20 02:51:33 +03:00
"instance": instance,
"data": input,
"context": {"request": info.context},
"partial": partial,
2017-11-15 01:02:28 +03:00
}
2018-07-20 02:51:33 +03:00
return {"data": input, "context": {"request": info.context}}
2017-11-15 01:02:28 +03:00
2017-05-31 01:30:03 +03:00
@classmethod
2017-07-28 19:43:27 +03:00
def mutate_and_get_payload(cls, root, info, **input):
2017-11-15 01:02:28 +03:00
kwargs = cls.get_serializer_kwargs(root, info, **input)
serializer = cls._meta.serializer_class(**kwargs)
2017-05-31 01:30:03 +03:00
if serializer.is_valid():
2017-07-28 19:43:27 +03:00
return cls.perform_mutate(serializer, info)
2017-05-31 01:30:03 +03:00
else:
errors = ErrorType.from_errors(serializer.errors)
2017-05-31 01:30:03 +03:00
return cls(errors=errors)
@classmethod
2017-07-28 19:43:27 +03:00
def perform_mutate(cls, serializer, info):
2017-06-26 20:03:01 +03:00
obj = serializer.save()
kwargs = {}
for f, field in serializer.fields.items():
if not field.write_only:
if isinstance(field, serializers.SerializerMethodField):
kwargs[f] = field.to_representation(obj)
else:
kwargs[f] = field.get_attribute(obj)
return cls(errors=None, **kwargs)