Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 146cff6

Browse files
committed
Refactored some of the utility functions to be part of the renderer class.
1 parent 55e771a commit 146cff6

File tree

2 files changed

+305
-308
lines changed

2 files changed

+305
-308
lines changed

‎rest_framework_json_api/renderers.py‎

Lines changed: 304 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
"""
22
Renderers
33
"""
4+
import copy
45
from collections import OrderedDict
56

7+
from django.utils import six, encoding
8+
from rest_framework import relations
69
from rest_framework import renderers
10+
from rest_framework.serializers import BaseSerializer, ListSerializer, ModelSerializer
11+
from rest_framework.settings import api_settings
712

813
from . import utils
914

@@ -31,6 +36,301 @@ class JSONRenderer(renderers.JSONRenderer):
3136
media_type = 'application/vnd.api+json'
3237
format = 'vnd.api+json'
3338

39+
def extract_attributes(self, fields, resource):
40+
data = OrderedDict()
41+
for field_name, field in six.iteritems(fields):
42+
# ID is always provided in the root of JSON API so remove it from attributes
43+
if field_name == 'id':
44+
continue
45+
# Skip fields with relations
46+
if isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)):
47+
continue
48+
49+
# Skip read_only attribute fields when `resource` is an empty
50+
# serializer. Prevents the "Raw Data" form of the browsable API
51+
# from rendering `"foo": null` for read only fields
52+
try:
53+
resource[field_name]
54+
except KeyError:
55+
if fields[field_name].read_only:
56+
continue
57+
58+
data.update({
59+
field_name: resource.get(field_name)
60+
})
61+
62+
return utils.format_keys(data)
63+
64+
def extract_relationships(self, fields, resource, resource_instance):
65+
# Avoid circular deps
66+
from rest_framework_json_api.relations import ResourceRelatedField
67+
68+
data = OrderedDict()
69+
70+
# Don't try to extract relationships from a non-existent resource
71+
if resource_instance is None:
72+
return
73+
74+
for field_name, field in six.iteritems(fields):
75+
# Skip URL field
76+
if field_name == api_settings.URL_FIELD_NAME:
77+
continue
78+
79+
# Skip fields without relations
80+
if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)):
81+
continue
82+
83+
source = field.source
84+
try:
85+
relation_instance_or_manager = getattr(resource_instance, source)
86+
except AttributeError:
87+
# if the field is not defined on the model then we check the serializer
88+
# and if no value is there we skip over the field completely
89+
serializer_method = getattr(field.parent, source, None)
90+
if serializer_method and hasattr(serializer_method, '__call__'):
91+
relation_instance_or_manager = serializer_method(resource_instance)
92+
else:
93+
continue
94+
95+
relation_type = utils.get_related_resource_type(field)
96+
97+
if isinstance(field, relations.HyperlinkedIdentityField):
98+
# special case for HyperlinkedIdentityField
99+
relation_data = list()
100+
101+
# Don't try to query an empty relation
102+
relation_queryset = relation_instance_or_manager.all() \
103+
if relation_instance_or_manager is not None else list()
104+
105+
for related_object in relation_queryset:
106+
relation_data.append(
107+
OrderedDict([('type', relation_type), ('id', encoding.force_text(related_object.pk))])
108+
)
109+
110+
data.update({field_name: {
111+
'links': {
112+
"related": resource.get(field_name)},
113+
'data': relation_data,
114+
'meta': {
115+
'count': len(relation_data)
116+
}
117+
}})
118+
continue
119+
120+
if isinstance(field, ResourceRelatedField):
121+
# special case for ResourceRelatedField
122+
relation_data = {
123+
'data': resource.get(field_name)
124+
}
125+
126+
field_links = field.get_links(resource_instance)
127+
relation_data.update(
128+
{'links': field_links}
129+
if field_links else dict()
130+
)
131+
data.update({field_name: relation_data})
132+
continue
133+
134+
if isinstance(field, (relations.PrimaryKeyRelatedField, relations.HyperlinkedRelatedField)):
135+
relation_id = relation_instance_or_manager.pk if resource.get(field_name) else None
136+
137+
relation_data = {
138+
'data': (
139+
OrderedDict([('type', relation_type), ('id', encoding.force_text(relation_id))])
140+
if relation_id is not None else None)
141+
}
142+
143+
relation_data.update(
144+
{'links': {'related': resource.get(field_name)}}
145+
if isinstance(field, relations.HyperlinkedRelatedField) and resource.get(field_name) else dict()
146+
)
147+
data.update({field_name: relation_data})
148+
continue
149+
150+
if isinstance(field, relations.ManyRelatedField):
151+
152+
if isinstance(field.child_relation, ResourceRelatedField):
153+
# special case for ResourceRelatedField
154+
relation_data = {
155+
'data': resource.get(field_name)
156+
}
157+
158+
field_links = field.child_relation.get_links(resource_instance)
159+
relation_data.update(
160+
{'links': field_links}
161+
if field_links else dict()
162+
)
163+
relation_data.update(
164+
{
165+
'meta': {
166+
'count': len(resource.get(field_name))
167+
}
168+
}
169+
)
170+
data.update({field_name: relation_data})
171+
continue
172+
173+
relation_data = list()
174+
for related_object in relation_instance_or_manager.all():
175+
related_object_type = utils.get_instance_or_manager_resource_type(related_object)
176+
relation_data.append(OrderedDict([
177+
('type', related_object_type),
178+
('id', encoding.force_text(related_object.pk))
179+
]))
180+
data.update({
181+
field_name: {
182+
'data': relation_data,
183+
'meta': {
184+
'count': len(relation_data)
185+
}
186+
}
187+
})
188+
continue
189+
190+
if isinstance(field, ListSerializer):
191+
relation_data = list()
192+
193+
serializer_data = resource.get(field_name)
194+
resource_instance_queryset = list(relation_instance_or_manager.all())
195+
if isinstance(serializer_data, list):
196+
for position in range(len(serializer_data)):
197+
nested_resource_instance = resource_instance_queryset[position]
198+
nested_resource_instance_type = utils.get_resource_type_from_instance(nested_resource_instance)
199+
relation_data.append(OrderedDict([
200+
('type', nested_resource_instance_type),
201+
('id', encoding.force_text(nested_resource_instance.pk))
202+
]))
203+
204+
data.update({field_name: {'data': relation_data}})
205+
continue
206+
207+
if isinstance(field, ModelSerializer):
208+
relation_model = field.Meta.model
209+
relation_type = utils.format_relation_name(relation_model.__name__)
210+
211+
data.update({
212+
field_name: {
213+
'data': (
214+
OrderedDict([
215+
('type', relation_type),
216+
('id', encoding.force_text(relation_instance_or_manager.pk))
217+
]) if resource.get(field_name) else None)
218+
}
219+
})
220+
continue
221+
222+
return utils.format_keys(data)
223+
224+
def extract_included(self, fields, resource, resource_instance, included_resources):
225+
# this function may be called with an empty record (example: Browsable Interface)
226+
if not resource_instance:
227+
return
228+
229+
included_data = list()
230+
current_serializer = fields.serializer
231+
context = current_serializer.context
232+
included_serializers = utils.get_included_serializers(current_serializer)
233+
included_resources = copy.copy(included_resources)
234+
235+
for field_name, field in six.iteritems(fields):
236+
# Skip URL field
237+
if field_name == api_settings.URL_FIELD_NAME:
238+
continue
239+
240+
# Skip fields without relations or serialized data
241+
if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)):
242+
continue
243+
244+
try:
245+
included_resources.remove(field_name)
246+
except ValueError:
247+
# Skip fields not in requested included resources
248+
continue
249+
250+
try:
251+
relation_instance_or_manager = getattr(resource_instance, field_name)
252+
except AttributeError:
253+
try:
254+
# For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source`
255+
relation_instance_or_manager = getattr(resource_instance, field.child_relation.source)
256+
except AttributeError:
257+
serializer_method = getattr(current_serializer, field.source)
258+
relation_instance_or_manager = serializer_method(resource_instance)
259+
260+
new_included_resources = [key.replace('%s.' % field_name, '', 1)
261+
for key in included_resources
262+
if field_name == key.split('.')[0]]
263+
serializer_data = resource.get(field_name)
264+
265+
if isinstance(field, relations.ManyRelatedField):
266+
serializer_class = included_serializers.get(field_name)
267+
field = serializer_class(relation_instance_or_manager.all(), many=True, context=context)
268+
serializer_data = field.data
269+
270+
if isinstance(field, relations.RelatedField):
271+
serializer_class = included_serializers.get(field_name)
272+
if relation_instance_or_manager is None:
273+
continue
274+
field = serializer_class(relation_instance_or_manager, context=context)
275+
serializer_data = field.data
276+
277+
if isinstance(field, ListSerializer):
278+
serializer = field.child
279+
model = serializer.Meta.model
280+
relation_type = utils.format_relation_name(model.__name__)
281+
relation_queryset = list(relation_instance_or_manager.all())
282+
283+
# Get the serializer fields
284+
serializer_fields = utils.get_serializer_fields(serializer)
285+
if serializer_data:
286+
for position in range(len(serializer_data)):
287+
serializer_resource = serializer_data[position]
288+
nested_resource_instance = relation_queryset[position]
289+
included_data.append(
290+
self.build_json_resource_obj(
291+
serializer_fields, serializer_resource, nested_resource_instance, relation_type
292+
)
293+
)
294+
included_data.extend(
295+
self.extract_included(
296+
serializer_fields, serializer_resource, nested_resource_instance, new_included_resources
297+
)
298+
)
299+
300+
if isinstance(field, ModelSerializer):
301+
model = field.Meta.model
302+
relation_type = utils.format_relation_name(model.__name__)
303+
304+
# Get the serializer fields
305+
serializer_fields = utils.get_serializer_fields(field)
306+
if serializer_data:
307+
included_data.append(
308+
self.build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager,
309+
relation_type)
310+
)
311+
included_data.extend(
312+
self.extract_included(
313+
serializer_fields, serializer_data, relation_instance_or_manager, new_included_resources
314+
)
315+
)
316+
317+
return utils.format_keys(included_data)
318+
319+
def build_json_resource_obj(self, fields, resource, resource_instance, resource_name):
320+
resource_data = [
321+
('type', resource_name),
322+
('id', encoding.force_text(resource_instance.pk) if resource_instance else None),
323+
('attributes', self.extract_attributes(fields, resource)),
324+
]
325+
relationships = self.extract_relationships(fields, resource, resource_instance)
326+
if relationships:
327+
resource_data.append(('relationships', relationships))
328+
# Add 'self' link if field is present and valid
329+
if api_settings.URL_FIELD_NAME in resource and \
330+
isinstance(fields[api_settings.URL_FIELD_NAME], relations.RelatedField):
331+
resource_data.append(('links', {'self': resource[api_settings.URL_FIELD_NAME]}))
332+
return OrderedDict(resource_data)
333+
34334
def render_relationship_view(self, data, accepted_media_type=None, renderer_context=None):
35335
# Special case for RelationshipView
36336
view = renderer_context.get("view", None)
@@ -106,17 +406,17 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
106406
resource = serializer_data[position] # Get current resource
107407
resource_instance = resource_serializer.instance[position] # Get current instance
108408
json_api_data.append(
109-
utils.build_json_resource_obj(fields, resource, resource_instance, resource_name))
110-
included = utils.extract_included(fields, resource, resource_instance, included_resources)
409+
self.build_json_resource_obj(fields, resource, resource_instance, resource_name))
410+
included = self.extract_included(fields, resource, resource_instance, included_resources)
111411
if included:
112412
json_api_included.extend(included)
113413
else:
114414
# Check if data contains a serializer
115415
if hasattr(data, 'serializer'):
116416
fields = utils.get_serializer_fields(data.serializer)
117417
resource_instance = data.serializer.instance
118-
json_api_data = utils.build_json_resource_obj(fields, data, resource_instance, resource_name)
119-
included = utils.extract_included(fields, data, resource_instance, included_resources)
418+
json_api_data = self.build_json_resource_obj(fields, data, resource_instance, resource_name)
419+
included = self.extract_included(fields, data, resource_instance, included_resources)
120420
if included:
121421
json_api_included.extend(included)
122422
else:

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /