mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-28 20:43:43 +03:00
36cf100e8b
* Use ruff format to replace black * Adjust ruff config to be compatible with ruff-format https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules * Format * Replace black with ruff format in Makefile
189 lines
6.1 KiB
Python
189 lines
6.1 KiB
Python
from collections import OrderedDict
|
|
from enum import Enum
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
from rest_framework import serializers
|
|
|
|
import graphene
|
|
from graphene.relay.mutation import ClientIDMutation
|
|
from graphene.types import Field, InputField
|
|
from graphene.types.mutation import MutationOptions
|
|
from graphene.types.objecttype import yank_fields_from_attrs
|
|
|
|
from ..types import ErrorType
|
|
from .serializer_converter import convert_serializer_field
|
|
|
|
|
|
class SerializerMutationOptions(MutationOptions):
|
|
lookup_field = None
|
|
model_class = None
|
|
model_operations = ["create", "update"]
|
|
serializer_class = None
|
|
optional_fields = ()
|
|
|
|
|
|
def fields_for_serializer(
|
|
serializer,
|
|
only_fields,
|
|
exclude_fields,
|
|
is_input=False,
|
|
convert_choices_to_enum=True,
|
|
lookup_field=None,
|
|
optional_fields=(),
|
|
):
|
|
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
|
|
]
|
|
)
|
|
|
|
if is_not_in_only or is_excluded:
|
|
continue
|
|
is_optional = name in optional_fields or "__all__" in optional_fields
|
|
|
|
fields[name] = convert_serializer_field(
|
|
field,
|
|
is_input=is_input,
|
|
convert_choices_to_enum=convert_choices_to_enum,
|
|
force_optional=is_optional,
|
|
)
|
|
return fields
|
|
|
|
|
|
class SerializerMutation(ClientIDMutation):
|
|
class Meta:
|
|
abstract = True
|
|
|
|
errors = graphene.List(
|
|
ErrorType, description="May contain more than one error for same field."
|
|
)
|
|
|
|
@classmethod
|
|
def __init_subclass_with_meta__(
|
|
cls,
|
|
lookup_field=None,
|
|
serializer_class=None,
|
|
model_class=None,
|
|
model_operations=("create", "update"),
|
|
only_fields=(),
|
|
exclude_fields=(),
|
|
convert_choices_to_enum=True,
|
|
_meta=None,
|
|
optional_fields=(),
|
|
**options,
|
|
):
|
|
if not serializer_class:
|
|
raise Exception("serializer_class is required for the SerializerMutation")
|
|
|
|
if "update" not in model_operations and "create" not in model_operations:
|
|
raise Exception('model_operations must contain "create" and/or "update"')
|
|
|
|
serializer = serializer_class()
|
|
if model_class is None:
|
|
serializer_meta = getattr(serializer_class, "Meta", None)
|
|
if serializer_meta:
|
|
model_class = getattr(serializer_meta, "model", None)
|
|
|
|
if lookup_field is None and model_class:
|
|
lookup_field = model_class._meta.pk.name
|
|
|
|
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,
|
|
optional_fields=optional_fields,
|
|
)
|
|
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,
|
|
)
|
|
|
|
if not _meta:
|
|
_meta = SerializerMutationOptions(cls)
|
|
_meta.lookup_field = lookup_field
|
|
_meta.model_operations = model_operations
|
|
_meta.serializer_class = serializer_class
|
|
_meta.model_class = model_class
|
|
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
|
|
|
|
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
|
|
super().__init_subclass_with_meta__(
|
|
_meta=_meta, input_fields=input_fields, **options
|
|
)
|
|
|
|
@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
|
|
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
|
|
elif "create" in cls._meta.model_operations:
|
|
instance = None
|
|
partial = False
|
|
else:
|
|
raise Exception(
|
|
'Invalid update operation. Input parameter "{}" required.'.format(
|
|
lookup_field
|
|
)
|
|
)
|
|
|
|
return {
|
|
"instance": instance,
|
|
"data": input,
|
|
"context": {"request": info.context},
|
|
"partial": partial,
|
|
}
|
|
|
|
return {"data": input, "context": {"request": info.context}}
|
|
|
|
@classmethod
|
|
def mutate_and_get_payload(cls, root, info, **input):
|
|
kwargs = cls.get_serializer_kwargs(root, info, **input)
|
|
serializer = cls._meta.serializer_class(**kwargs)
|
|
|
|
if serializer.is_valid():
|
|
return cls.perform_mutate(serializer, info)
|
|
else:
|
|
errors = ErrorType.from_errors(serializer.errors)
|
|
|
|
return cls(errors=errors)
|
|
|
|
@classmethod
|
|
def perform_mutate(cls, serializer, info):
|
|
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)
|