Skip to content
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 @@ -15,6 +15,7 @@ David Guillot, for Contexte <dguillot@contexte.com>
David Vogt <david.vogt@adfinis-sygroup.ch>
Felix Viernickel <felix@gedankenspieler.org>
Greg Aker <greg@gregaker.net>
Harshal Kalewar <kalewarh@gmail.com>
Humayun Ahmad <humayunahbh@gmail.com>
Jamie Bliss <astronouth7303@gmail.com>
Jason Housley <housleyjk@gmail.com>
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Note that in line with [Django REST framework policy](https://www.django-rest-framework.org/topics/release-notes/),
any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.

## [Unreleased]

### Fixed

* Ensured that an empty `included` array is returned in responses when the `include` query parameter is present but no related resources exist.

## [8.0.0] - 2025-07-24

### Added
Expand Down
48 changes: 47 additions & 1 deletion example/tests/integration/test_includes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def test_missing_field_not_included(author_bio_factory, author_factory, client):
# First author does not have a bio
author = author_factory(bio=None)
response = client.get(reverse("author-detail", args=[author.pk]) + "?include=bio")
assert "included" not in response.json()
content = response.json()
assert "included" in content
assert content["included"] == []
# Second author does
author = author_factory()
response = client.get(reverse("author-detail", args=[author.pk]) + "?include=bio")
Expand Down Expand Up @@ -204,3 +206,47 @@ def test_meta_object_added_to_included_resources(single_entry, client):
)
assert response.json()["included"][0].get("meta")
assert response.json()["included"][1].get("meta")


def test_included_array_empty_when_requested_but_no_data(blog_factory, client):
blog = blog_factory()
response = client.get(
reverse("blog-detail", kwargs={"pk": blog.pk}) + "?include=tags"
)
content = response.json()

assert "included" in content
assert content["included"] == []


def test_included_array_populated_when_related_data_exists(
blog_factory, tagged_item_factory, client
):
blog = blog_factory()
tag = tagged_item_factory(tag="django")
blog.tags.add(tag)

response = client.get(
reverse("blog-detail", kwargs={"pk": blog.pk}) + "?include=tags"
)
included = response.json()["included"]

assert included, "Expected included array to be populated"
assert [x.get("type") for x in included] == [
"taggedItems"
], "Included types incorrect"
assert included[0]["attributes"]["tag"] == "django"


def test_included_array_present_via_jsonapimeta_defaults(
single_entry, comment_factory, author_factory, client
):
author = author_factory()
comment_factory(entry=single_entry, author=author)

response = client.get(reverse("entry-detail", kwargs={"pk": single_entry.pk}))

included = response.json()["included"]

assert included, "Expected included array due to JSONAPIMeta defaults"
assert any(resource["type"] == "comments" for resource in included)
3 changes: 2 additions & 1 deletion example/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,8 @@ def test_search_keywords(self):
},
"meta": {"bodyFormat": "text"},
}
]
],
"included": [],
}
assert response.json() == expected_result

Expand Down
2 changes: 1 addition & 1 deletion rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
if not included_cache[obj_type]:
del included_cache[obj_type]

if included_cache:
if included_resources:
render_data["included"] = list()
for included_type in sorted(included_cache.keys()):
for included_id in sorted(included_cache[included_type].keys()):
Expand Down