Skip to content

Commit 5488b18

Browse files
authored
Render meta_fields in included resources (#883)
For consistency reasons the meta fields should also be rendered in included resources.
1 parent 952e26b commit 5488b18

File tree

8 files changed

+60
-25
lines changed

8 files changed

+60
-25
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Matt Layman <https://www.mattlayman.com>
2323
Michael Haselton <[email protected]>
2424
Mohammed Ali Zubair <[email protected]>
2525
Nathanael Gordon <[email protected]>
26+
Nick Kozhenin <[email protected]>
2627
Ola Tarkowska <[email protected]>
2728
Oliver Sauder <[email protected]>
2829
Raphael Cohen <[email protected]>

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ any parts of the framework not mentioned in the documentation should generally b
1919

2020
* Allow users to overwrite a view's `get_serializer_class()` method when using [related urls](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#related-urls)
2121
* Correctly resolve the resource type of `ResourceRelatedField(many=True)` fields on plain serializers
22+
* Render `meta_fields` in included resources
2223

2324

2425
## [4.0.0] - 2020-10-31

example/serializers.py

+11
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ class AuthorSerializer(serializers.ModelSerializer):
251251
write_only=True,
252252
help_text="help for defaults",
253253
)
254+
initials = serializers.SerializerMethodField()
254255
included_serializers = {"bio": AuthorBioSerializer, "type": AuthorTypeSerializer}
255256
related_serializers = {
256257
"bio": "example.serializers.AuthorBioSerializer",
@@ -272,11 +273,16 @@ class Meta:
272273
"type",
273274
"secrets",
274275
"defaults",
276+
"initials",
275277
)
278+
meta_fields = ("initials",)
276279

277280
def get_first_entry(self, obj):
278281
return obj.entries.first()
279282

283+
def get_initials(self, obj):
284+
return "".join([word[0] for word in obj.name.split(" ")])
285+
280286

281287
class AuthorListSerializer(AuthorSerializer):
282288
pass
@@ -298,6 +304,7 @@ class Meta:
298304
class CommentSerializer(serializers.ModelSerializer):
299305
# testing remapping of related name
300306
writer = relations.ResourceRelatedField(source="author", read_only=True)
307+
modified_days_ago = serializers.SerializerMethodField()
301308

302309
included_serializers = {
303310
"entry": EntrySerializer,
@@ -312,6 +319,10 @@ class Meta:
312319
"modified_at",
313320
)
314321
# fields = ('entry', 'body', 'author',)
322+
meta_fields = ("modified_days_ago",)
323+
324+
def get_modified_days_ago(self, obj):
325+
return (datetime.now() - obj.modified_at).days
315326

316327

317328
class ProjectTypeSerializer(serializers.ModelSerializer):

example/tests/integration/test_includes.py

+14
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,17 @@ def test_data_resource_not_included_again(single_comment, client):
261261
# The comment in the data attribute must not be included again.
262262
expected_comment_count -= 1
263263
assert comment_count == expected_comment_count, "Comment count incorrect"
264+
265+
266+
def test_meta_object_added_to_included_resources(single_entry, client):
267+
response = client.get(
268+
reverse("entry-detail", kwargs={"pk": single_entry.pk}) + "?include=comments"
269+
)
270+
assert response.json()["included"][0].get("meta")
271+
272+
response = client.get(
273+
reverse("entry-detail", kwargs={"pk": single_entry.pk})
274+
+ "?include=comments.author"
275+
)
276+
assert response.json()["included"][0].get("meta")
277+
assert response.json()["included"][1].get("meta")

example/tests/test_format_keys.py

+1
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ def test_options_format_field_names(db, client):
6363
"comments",
6464
"secrets",
6565
"defaults",
66+
"initials",
6667
}
6768
assert expected_keys == data["actions"]["POST"].keys()

example/tests/test_serializers.py

+3
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def test_model_serializer_with_implicit_fields(self, comment, client):
195195
"data": {"type": "writers", "id": str(comment.author.pk)}
196196
},
197197
},
198+
"meta": {
199+
"modifiedDaysAgo": (datetime.now() - comment.modified_at).days
200+
},
198201
}
199202
}
200203

example/tests/unit/test_renderer_class_methods.py

+15-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from rest_framework_json_api import serializers
55
from rest_framework_json_api.renderers import JSONRenderer
6+
from rest_framework_json_api.utils import get_serializer_fields
67

78
pytestmark = pytest.mark.django_db
89

@@ -20,10 +21,7 @@ class Meta:
2021

2122

2223
def test_build_json_resource_obj():
23-
resource = {
24-
"pk": 1,
25-
"username": "Alice",
26-
}
24+
resource = {"username": "Alice", "version": "1.0.0"}
2725

2826
serializer = ResourceSerializer(data={"username": "Alice"})
2927
serializer.is_valid()
@@ -33,11 +31,16 @@ def test_build_json_resource_obj():
3331
"type": "user",
3432
"id": "1",
3533
"attributes": {"username": "Alice"},
34+
"meta": {"version": "1.0.0"},
3635
}
3736

3837
assert (
3938
JSONRenderer.build_json_resource_obj(
40-
serializer.fields, resource, resource_instance, "user"
39+
get_serializer_fields(serializer),
40+
resource,
41+
resource_instance,
42+
"user",
43+
serializer,
4144
)
4245
== output
4346
)
@@ -47,11 +50,8 @@ def test_can_override_methods():
4750
"""
4851
Make sure extract_attributes and extract_relationships can be overriden.
4952
"""
50-
resource = {
51-
"pk": 1,
52-
"username": "Alice",
53-
}
5453

54+
resource = {"username": "Alice", "version": "1.0.0"}
5555
serializer = ResourceSerializer(data={"username": "Alice"})
5656
serializer.is_valid()
5757
resource_instance = serializer.save()
@@ -60,6 +60,7 @@ def test_can_override_methods():
6060
"type": "user",
6161
"id": "1",
6262
"attributes": {"username": "Alice"},
63+
"meta": {"version": "1.0.0"},
6364
}
6465

6566
class CustomRenderer(JSONRenderer):
@@ -80,7 +81,11 @@ def extract_relationships(cls, fields, resource, resource_instance):
8081

8182
assert (
8283
CustomRenderer.build_json_resource_obj(
83-
serializer.fields, resource, resource_instance, "user"
84+
get_serializer_fields(serializer),
85+
resource,
86+
resource_instance,
87+
"user",
88+
serializer,
8489
)
8590
== output
8691
)

rest_framework_json_api/renderers.py

+14-15
Original file line numberDiff line numberDiff line change
@@ -373,11 +373,11 @@ def extract_included(
373373
serializer_resource,
374374
nested_resource_instance,
375375
resource_type,
376+
serializer,
376377
getattr(serializer, "_poly_force_type_resolution", False),
377378
)
378-
included_cache[new_item["type"]][
379-
new_item["id"]
380-
] = utils.format_field_names(new_item)
379+
included_cache[new_item["type"]][new_item["id"]] = new_item
380+
381381
cls.extract_included(
382382
serializer_fields,
383383
serializer_resource,
@@ -397,11 +397,11 @@ def extract_included(
397397
serializer_data,
398398
relation_instance,
399399
relation_type,
400+
field,
400401
getattr(field, "_poly_force_type_resolution", False),
401402
)
402-
included_cache[new_item["type"]][
403-
new_item["id"]
404-
] = utils.format_field_names(new_item)
403+
included_cache[new_item["type"]][new_item["id"]] = new_item
404+
405405
cls.extract_included(
406406
serializer_fields,
407407
serializer_data,
@@ -450,6 +450,7 @@ def build_json_resource_obj(
450450
resource,
451451
resource_instance,
452452
resource_name,
453+
serializer,
453454
force_type_resolution=False,
454455
):
455456
"""
@@ -476,6 +477,11 @@ def build_json_resource_obj(
476477
resource_data.append(
477478
("links", {"self": resource[api_settings.URL_FIELD_NAME]})
478479
)
480+
481+
meta = cls.extract_meta(serializer, resource)
482+
if meta:
483+
resource_data.append(("meta", utils.format_field_names(meta)))
484+
479485
return OrderedDict(resource_data)
480486

481487
def render_relationship_view(
@@ -582,13 +588,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
582588
resource,
583589
resource_instance,
584590
resource_name,
591+
serializer,
585592
force_type_resolution,
586593
)
587-
meta = self.extract_meta(serializer, resource)
588-
if meta:
589-
json_resource_obj.update(
590-
{"meta": utils.format_field_names(meta)}
591-
)
592594
json_api_data.append(json_resource_obj)
593595

594596
self.extract_included(
@@ -610,13 +612,10 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
610612
serializer_data,
611613
resource_instance,
612614
resource_name,
615+
serializer,
613616
force_type_resolution,
614617
)
615618

616-
meta = self.extract_meta(serializer, serializer_data)
617-
if meta:
618-
json_api_data.update({"meta": utils.format_field_names(meta)})
619-
620619
self.extract_included(
621620
fields,
622621
serializer_data,

0 commit comments

Comments
 (0)