-
Notifications
You must be signed in to change notification settings - Fork 764
/
Copy pathmutation.py
201 lines (170 loc) · 6.54 KB
/
mutation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
from collections import OrderedDict
from enum import Enum
from asgiref.sync import sync_to_async
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 ..utils import is_running_async
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 is_running_async():
async def perform_mutate_async():
if await sync_to_async(serializer.is_valid)():
return await sync_to_async(cls.perform_mutate)(serializer, info)
else:
errors = ErrorType.from_errors(serializer.errors)
return cls(errors=errors)
return perform_mutate_async()
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)