Skip to content

Render meta fields of included resource #883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Matt Layman <https://www.mattlayman.com>
Michael Haselton <[email protected]>
Mohammed Ali Zubair <[email protected]>
Nathanael Gordon <[email protected]>
Nick Kozhenin <[email protected]>
Ola Tarkowska <[email protected]>
Oliver Sauder <[email protected]>
Raphael Cohen <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ any parts of the framework not mentioned in the documentation should generally b

* 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)
* Correctly resolve the resource type of `ResourceRelatedField(many=True)` fields on plain serializers
* Render `meta_fields` in included resources


## [4.0.0] - 2020-10-31
Expand Down
11 changes: 11 additions & 0 deletions example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class AuthorSerializer(serializers.ModelSerializer):
write_only=True,
help_text="help for defaults",
)
initials = serializers.SerializerMethodField()
included_serializers = {"bio": AuthorBioSerializer, "type": AuthorTypeSerializer}
related_serializers = {
"bio": "example.serializers.AuthorBioSerializer",
Expand All @@ -272,11 +273,16 @@ class Meta:
"type",
"secrets",
"defaults",
"initials",
)
meta_fields = ("initials",)

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

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


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

included_serializers = {
"entry": EntrySerializer,
Expand All @@ -312,6 +319,10 @@ class Meta:
"modified_at",
)
# fields = ('entry', 'body', 'author',)
meta_fields = ("modified_days_ago",)

def get_modified_days_ago(self, obj):
return (datetime.now() - obj.modified_at).days


class ProjectTypeSerializer(serializers.ModelSerializer):
Expand Down
14 changes: 14 additions & 0 deletions example/tests/integration/test_includes.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,17 @@ def test_data_resource_not_included_again(single_comment, client):
# The comment in the data attribute must not be included again.
expected_comment_count -= 1
assert comment_count == expected_comment_count, "Comment count incorrect"


def test_meta_object_added_to_included_resources(single_entry, client):
response = client.get(
reverse("entry-detail", kwargs={"pk": single_entry.pk}) + "?include=comments"
)
assert response.json()["included"][0].get("meta")

response = client.get(
reverse("entry-detail", kwargs={"pk": single_entry.pk})
+ "?include=comments.author"
)
assert response.json()["included"][0].get("meta")
assert response.json()["included"][1].get("meta")
1 change: 1 addition & 0 deletions example/tests/test_format_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ def test_options_format_field_names(db, client):
"comments",
"secrets",
"defaults",
"initials",
}
assert expected_keys == data["actions"]["POST"].keys()
3 changes: 3 additions & 0 deletions example/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ def test_model_serializer_with_implicit_fields(self, comment, client):
"data": {"type": "writers", "id": str(comment.author.pk)}
},
},
"meta": {
"modifiedDaysAgo": (datetime.now() - comment.modified_at).days
},
}
}

Expand Down
25 changes: 15 additions & 10 deletions example/tests/unit/test_renderer_class_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from rest_framework_json_api import serializers
from rest_framework_json_api.renderers import JSONRenderer
from rest_framework_json_api.utils import get_serializer_fields

pytestmark = pytest.mark.django_db

Expand All @@ -20,10 +21,7 @@ class Meta:


def test_build_json_resource_obj():
resource = {
"pk": 1,
"username": "Alice",
}
resource = {"username": "Alice", "version": "1.0.0"}

serializer = ResourceSerializer(data={"username": "Alice"})
serializer.is_valid()
Expand All @@ -33,11 +31,16 @@ def test_build_json_resource_obj():
"type": "user",
"id": "1",
"attributes": {"username": "Alice"},
"meta": {"version": "1.0.0"},
}

assert (
JSONRenderer.build_json_resource_obj(
serializer.fields, resource, resource_instance, "user"
get_serializer_fields(serializer),
resource,
resource_instance,
"user",
serializer,
)
== output
)
Expand All @@ -47,11 +50,8 @@ def test_can_override_methods():
"""
Make sure extract_attributes and extract_relationships can be overriden.
"""
resource = {
"pk": 1,
"username": "Alice",
}

resource = {"username": "Alice", "version": "1.0.0"}
serializer = ResourceSerializer(data={"username": "Alice"})
serializer.is_valid()
resource_instance = serializer.save()
Expand All @@ -60,6 +60,7 @@ def test_can_override_methods():
"type": "user",
"id": "1",
"attributes": {"username": "Alice"},
"meta": {"version": "1.0.0"},
}

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

assert (
CustomRenderer.build_json_resource_obj(
serializer.fields, resource, resource_instance, "user"
get_serializer_fields(serializer),
resource,
resource_instance,
"user",
serializer,
)
== output
)
Expand Down
29 changes: 14 additions & 15 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,11 @@ def extract_included(
serializer_resource,
nested_resource_instance,
resource_type,
serializer,
getattr(serializer, "_poly_force_type_resolution", False),
)
included_cache[new_item["type"]][
new_item["id"]
] = utils.format_field_names(new_item)
included_cache[new_item["type"]][new_item["id"]] = new_item

cls.extract_included(
serializer_fields,
serializer_resource,
Expand All @@ -397,11 +397,11 @@ def extract_included(
serializer_data,
relation_instance,
relation_type,
field,
getattr(field, "_poly_force_type_resolution", False),
)
included_cache[new_item["type"]][
new_item["id"]
] = utils.format_field_names(new_item)
included_cache[new_item["type"]][new_item["id"]] = new_item

cls.extract_included(
serializer_fields,
serializer_data,
Expand Down Expand Up @@ -450,6 +450,7 @@ def build_json_resource_obj(
resource,
resource_instance,
resource_name,
serializer,
force_type_resolution=False,
):
"""
Expand All @@ -476,6 +477,11 @@ def build_json_resource_obj(
resource_data.append(
("links", {"self": resource[api_settings.URL_FIELD_NAME]})
)

meta = cls.extract_meta(serializer, resource)
if meta:
resource_data.append(("meta", utils.format_field_names(meta)))

return OrderedDict(resource_data)

def render_relationship_view(
Expand Down Expand Up @@ -582,13 +588,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
resource,
resource_instance,
resource_name,
serializer,
force_type_resolution,
)
meta = self.extract_meta(serializer, resource)
if meta:
json_resource_obj.update(
{"meta": utils.format_field_names(meta)}
)
json_api_data.append(json_resource_obj)

self.extract_included(
Expand All @@ -610,13 +612,10 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
serializer_data,
resource_instance,
resource_name,
serializer,
force_type_resolution,
)

meta = self.extract_meta(serializer, serializer_data)
if meta:
json_api_data.update({"meta": utils.format_field_names(meta)})

self.extract_included(
fields,
serializer_data,
Expand Down