Skip to content

Commit 63d7882

Browse files
committed
Merge pull request #216 from adaptivdesign/develop
fixed get_root_meta for lists
2 parents d08e9f3 + ccef34f commit 63d7882

File tree

7 files changed

+114
-70
lines changed

7 files changed

+114
-70
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ pip-delete-this-directory.txt
3030

3131
# Tox
3232
.tox/
33+
34+
# VirtualEnv
35+
.venv/

docs/api.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Gathers the data from serializer fields specified in `meta_fields` and adds it t
3838

3939
#### extract_root_meta
4040

41-
`extract_root_meta(serializer, resource, meta)`
41+
`extract_root_meta(serializer, resource)`
4242

4343
Calls a `get_root_meta` function on a serializer, if it exists.
4444

@@ -47,4 +47,3 @@ Calls a `get_root_meta` function on a serializer, if it exists.
4747
`build_json_resource_obj(fields, resource, resource_instance, resource_name)`
4848

4949
Builds the resource object (type, id, attributes) and extracts relationships.
50-

docs/usage.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,17 @@ added to the `meta` object within the same `data` as the serializer.
260260
To add metadata to the top level `meta` object add:
261261

262262
``` python
263-
def get_root_meta(self, obj):
264-
return {
265-
'size': len(obj)
266-
}
263+
def get_root_meta(self, resource, many):
264+
if many:
265+
# Dealing with a list request
266+
return {
267+
'size': len(resource)
268+
}
269+
else:
270+
# Dealing with a detail request
271+
return {
272+
'foo': 'bar'
273+
}
267274
```
268275
to the serializer. It must return a dict and will be merged with the existing top level `meta`.
269276

example/serializers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ class BlogSerializer(serializers.ModelSerializer):
77

88
copyright = serializers.SerializerMethodField()
99

10-
def get_copyright(self, obj):
10+
def get_copyright(self, resource):
1111
return datetime.now().year
1212

13-
def get_root_meta(self, obj):
13+
def get_root_meta(self, resource, many):
1414
return {
1515
'api_docs': '/docs/api/blogs'
1616
}

example/tests/integration/test_meta.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,39 @@
77
pytestmark = pytest.mark.django_db
88

99

10-
def test_top_level_meta(blog, client):
10+
def test_top_level_meta_for_list_view(blog, client):
11+
12+
expected = {
13+
"data": [{
14+
"type": "blogs",
15+
"id": "1",
16+
"attributes": {
17+
"name": blog.name
18+
},
19+
"meta": {
20+
"copyright": datetime.now().year
21+
},
22+
}],
23+
'links': {
24+
'first': 'http://testserver/blogs?page=1',
25+
'last': 'http://testserver/blogs?page=1',
26+
'next': None,
27+
'prev': None
28+
},
29+
'meta': {
30+
'pagination': {'count': 1, 'page': 1, 'pages': 1},
31+
'apiDocs': '/docs/api/blogs'
32+
}
33+
}
34+
35+
response = client.get(reverse("blog-list"))
36+
content_dump = redump_json(response.content)
37+
expected_dump = dump_json(expected)
38+
39+
assert content_dump == expected_dump
40+
41+
42+
def test_top_level_meta_for_detail_view(blog, client):
1143

1244
expected = {
1345
"data": {

example/tests/unit/test_renderer_class_methods.py

+26-22
Original file line numberDiff line numberDiff line change
@@ -61,38 +61,42 @@ def test_extract_meta():
6161
}
6262
assert JSONRenderer.extract_meta(serializer, serializer.data) == expected
6363

64-
def test_extract_root_meta():
65-
def get_root_meta(obj):
66-
return {
67-
'foo': 'meta-value'
68-
}
6964

70-
serializer = ResourceSerializer()
71-
serializer.get_root_meta = get_root_meta
65+
class ExtractRootMetaResourceSerializer(ResourceSerializer):
66+
def get_root_meta(self, resource, many):
67+
if many:
68+
return {
69+
'foo': 'meta-many-value'
70+
}
71+
else:
72+
return {
73+
'foo': 'meta-value'
74+
}
75+
76+
77+
class InvalidExtractRootMetaResourceSerializer(ResourceSerializer):
78+
def get_root_meta(self, resource, many):
79+
return 'not a dict'
80+
81+
82+
def test_extract_root_meta():
83+
serializer = ExtractRootMetaResourceSerializer()
7284
expected = {
7385
'foo': 'meta-value',
7486
}
75-
assert JSONRenderer.extract_root_meta(serializer, {}, {}) == expected
87+
assert JSONRenderer.extract_root_meta(serializer, {}) == expected
7688

7789
def test_extract_root_meta_many():
78-
def get_root_meta(obj):
79-
return {
80-
'foo': 'meta-value'
81-
}
82-
83-
serializer = ResourceSerializer(many=True)
84-
serializer.get_root_meta = get_root_meta
90+
serializer = ExtractRootMetaResourceSerializer(many=True)
8591
expected = {
86-
'foo': 'meta-value'
92+
'foo': 'meta-many-value'
8793
}
88-
assert JSONRenderer.extract_root_meta(serializer, {}, {}) == expected
94+
assert JSONRenderer.extract_root_meta(serializer, {}) == expected
8995

9096
def test_extract_root_meta_invalid_meta():
91-
def get_root_meta(obj):
97+
def get_root_meta(resource, many):
9298
return 'not a dict'
9399

94-
serializer = ResourceSerializer()
95-
serializer.get_root_meta = get_root_meta
100+
serializer = InvalidExtractRootMetaResourceSerializer()
96101
with pytest.raises(AssertionError) as e_info:
97-
JSONRenderer.extract_root_meta(serializer, {}, {})
98-
102+
JSONRenderer.extract_root_meta(serializer, {})

rest_framework_json_api/renderers.py

+38-39
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,18 @@ def extract_meta(serializer, resource):
338338
return data
339339

340340
@staticmethod
341-
def extract_root_meta(serializer, resource, meta):
341+
def extract_root_meta(serializer, resource):
342+
many = False
343+
if hasattr(serializer, 'child'):
344+
many = True
345+
serializer = serializer.child
346+
347+
data = {}
342348
if getattr(serializer, 'get_root_meta', None):
343-
root_meta = serializer.get_root_meta(resource)
344-
if root_meta:
345-
assert isinstance(root_meta, dict), 'get_root_meta must return a dict'
346-
meta.update(root_meta)
347-
return meta
349+
json_api_meta = serializer.get_root_meta(resource, many)
350+
assert isinstance(json_api_meta, dict), 'get_root_meta must return a dict'
351+
data.update(json_api_meta)
352+
return data
348353

349354
@staticmethod
350355
def build_json_resource_obj(fields, resource, resource_instance, resource_name):
@@ -412,6 +417,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
412417
else:
413418
included_resources = list()
414419

420+
json_api_data = data
415421
json_api_included = list()
416422
# initialize json_api_meta with pagination meta or an empty dict
417423
json_api_meta = data.get('meta', {}) if isinstance(data, dict) else {}
@@ -421,51 +427,44 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
421427
else:
422428
serializer_data = data
423429

424-
if hasattr(serializer_data, 'serializer') and getattr(serializer_data.serializer, 'many', False):
425-
# The below is not true for non-paginated responses
426-
# and isinstance(data, dict):
427-
428-
# If detail view then json api spec expects dict, otherwise a list
429-
# - http://jsonapi.org/format/#document-top-level
430-
# The `results` key may be missing if unpaginated or an OPTIONS request
430+
serializer = getattr(serializer_data, 'serializer', None)
431431

432-
resource_serializer = serializer_data.serializer
432+
if serializer is not None:
433433

434434
# Get the serializer fields
435-
fields = utils.get_serializer_fields(resource_serializer)
435+
fields = utils.get_serializer_fields(serializer)
436436

437-
json_api_data = list()
438-
for position in range(len(serializer_data)):
439-
resource = serializer_data[position] # Get current resource
440-
resource_instance = resource_serializer.instance[position] # Get current instance
437+
# Extract root meta for any type of serializer
438+
json_api_meta.update(self.extract_root_meta(serializer, serializer_data))
441439

442-
json_resource_obj = self.build_json_resource_obj(fields, resource, resource_instance, resource_name)
443-
meta = self.extract_meta(resource_serializer, resource)
444-
if meta:
445-
json_resource_obj.update({'meta': utils.format_keys(meta)})
446-
json_api_meta = self.extract_root_meta(resource_serializer, resource, json_api_meta)
447-
json_api_data.append(json_resource_obj)
440+
if getattr(serializer, 'many', False):
441+
json_api_data = list()
448442

449-
included = self.extract_included(fields, resource, resource_instance, included_resources)
450-
if included:
451-
json_api_included.extend(included)
452-
else:
453-
# Check if data contains a serializer
454-
if hasattr(data, 'serializer'):
455-
fields = utils.get_serializer_fields(data.serializer)
456-
resource_instance = data.serializer.instance
457-
json_api_data = self.build_json_resource_obj(fields, data, resource_instance, resource_name)
443+
for position in range(len(serializer_data)):
444+
resource = serializer_data[position] # Get current resource
445+
resource_instance = serializer.instance[position] # Get current instance
446+
447+
json_resource_obj = self.build_json_resource_obj(fields, resource, resource_instance, resource_name)
448+
meta = self.extract_meta(serializer, resource)
449+
if meta:
450+
json_resource_obj.update({'meta': utils.format_keys(meta)})
451+
json_api_data.append(json_resource_obj)
452+
453+
included = self.extract_included(fields, resource, resource_instance, included_resources)
454+
if included:
455+
json_api_included.extend(included)
456+
else:
457+
resource_instance = serializer.instance
458+
json_api_data = self.build_json_resource_obj(fields, serializer_data, resource_instance, resource_name)
458459

459-
meta = self.extract_meta(data.serializer, data)
460+
meta = self.extract_meta(serializer, serializer_data)
460461
if meta:
461462
json_api_data.update({'meta': utils.format_keys(meta)})
462-
json_api_meta = self.extract_root_meta(data.serializer, data, json_api_meta)
463463

464-
included = self.extract_included(fields, data, resource_instance, included_resources)
464+
included = self.extract_included(fields, serializer_data, resource_instance, included_resources)
465465
if included:
466466
json_api_included.extend(included)
467-
else:
468-
json_api_data = data
467+
469468

470469
# Make sure we render data in a specific order
471470
render_data = OrderedDict()

0 commit comments

Comments
 (0)