-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathserializer_mutation.py
More file actions
181 lines (152 loc) · 6.29 KB
/
Copy pathserializer_mutation.py
File metadata and controls
181 lines (152 loc) · 6.29 KB
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
from collections import OrderedDict
from django.shortcuts import get_object_or_404
from graphene.types import InputField
from graphene.types.mutation import MutationOptions
from graphene.types.objecttype import yank_fields_from_attrs
from graphene.utils.str_converters import to_snake_case
from graphene_django.rest_framework.serializer_converter import convert_serializer_field
from graphene_django.types import ErrorType
from rest_framework import serializers
from rest_framework.exceptions import ErrorDetail
from .mutations import RelayMutation
class SerializerMutationOptions(MutationOptions):
lookup_field = None
model_class = None
model_operations = ["create", "update"]
serializer_class = None
def fields_for_serializer(
input_class,
serializer,
only_fields,
exclude_fields,
is_input=False,
convert_choices_to_enum=True,
lookup_field=None,
):
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
]
)
is_field_in_input_class = input_class and hasattr(input_class, name)
if is_not_in_only or is_excluded or is_field_in_input_class:
continue
fields[name] = convert_serializer_field(
field, is_input=is_input, convert_choices_to_enum=convert_choices_to_enum
)
return fields
class SerializerMutation(RelayMutation):
class Meta:
abstract = True
@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,
**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_class = getattr(cls, "Input", None)
input_fields = fields_for_serializer(
input_class,
serializer,
only_fields,
exclude_fields,
is_input=True,
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
for file_field_name, file_field_values in info.context.FILES.lists():
if len(file_field_values) == 1:
input[to_snake_case(file_field_name)] = file_field_values[0]
else:
input[to_snake_case(file_field_name)] = file_field_values
if model_class:
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 = {}
for field_name, messages in serializer.errors.items():
index = 0
for message in messages:
if isinstance(message, ErrorDetail):
if field_name not in errors:
errors[field_name] = []
errors[field_name].append(message)
else:
for field, msg in message.items():
name = f"{field_name}.{index}.{field}"
if name not in errors:
errors[name] = []
errors[name].append(msg[0].title())
index += 1
errors = ErrorType.from_errors(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)