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
2 changes: 1 addition & 1 deletion example/factories/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- encoding: utf-8 -*-
from __future__ import unicode_literals

import factory
from faker import Factory as FakerFactory
Expand All @@ -22,6 +21,7 @@ class Meta:
name = factory.LazyAttribute(lambda x: faker.name())
email = factory.LazyAttribute(lambda x: faker.email())

bio = factory.RelatedFactory('example.factories.AuthorBioFactory', 'author')

class AuthorBioFactory(factory.django.DjangoModelFactory):
class Meta:
Expand Down
5 changes: 5 additions & 0 deletions example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, *args, **kwargs):
super(EntrySerializer, self).__init__(*args, **kwargs)

included_serializers = {
'authors': 'example.serializers.AuthorSerializer',
'comments': 'example.serializers.CommentSerializer',
'suggested': 'example.serializers.EntrySerializer',
}
Expand Down Expand Up @@ -73,6 +74,10 @@ class Meta:


class CommentSerializer(serializers.ModelSerializer):
included_serializers = {
'entry': EntrySerializer,
'author': AuthorSerializer
}

class Meta:
model = Comment
Expand Down
69 changes: 65 additions & 4 deletions example/tests/integration/test_includes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def test_included_data_on_detail(single_entry, client):
expected_comment_count = single_entry.comment_set.count()
assert comment_count == expected_comment_count, 'Detail comment count is incorrect'


def test_dynamic_related_data_is_included(single_entry, entry_factory, client):
entry_factory()
response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=suggested')
Expand All @@ -39,14 +40,74 @@ def test_dynamic_related_data_is_included(single_entry, entry_factory, client):

def test_missing_field_not_included(author_bio_factory, author_factory, client):
# First author does not have a bio
author = author_factory()
author = author_factory(bio=None)
response = client.get(reverse('author-detail', args=[author.pk])+'?include=bio')
data = load_json(response.content)
assert 'included' not in data
# Second author does
bio = author_bio_factory()
response = client.get(reverse('author-detail', args=[bio.author.pk])+'?include=bio')
author = author_factory()
response = client.get(reverse('author-detail', args=[author.pk])+'?include=bio')
data = load_json(response.content)
assert 'included' in data
assert len(data['included']) == 1
assert data['included'][0]['attributes']['body'] == bio.body
assert data['included'][0]['attributes']['body'] == author.bio.body


def test_deep_included_data_on_list(multiple_entries, client):
response = client.get(reverse("entry-list") + '?include=comments,comments.author,'
'comments.author.bio&page_size=5')
included = load_json(response.content).get('included')

assert len(load_json(response.content)['data']) == len(multiple_entries), 'Incorrect entry count'
assert [x.get('type') for x in included] == [
'authorBios', 'authorBios', 'authors', 'authors', 'comments', 'comments'
], 'List included types are incorrect'

comment_count = len([resource for resource in included if resource["type"] == "comments"])
expected_comment_count = sum([entry.comment_set.count() for entry in multiple_entries])
assert comment_count == expected_comment_count, 'List comment count is incorrect'

author_count = len([resource for resource in included if resource["type"] == "authors"])
expected_author_count = sum(
[entry.comment_set.filter(author__isnull=False).count() for entry in multiple_entries])
assert author_count == expected_author_count, 'List author count is incorrect'

author_bio_count = len([resource for resource in included if resource["type"] == "authorBios"])
expected_author_bio_count = sum([entry.comment_set.filter(
author__bio__isnull=False).count() for entry in multiple_entries])
assert author_bio_count == expected_author_bio_count, 'List author bio count is incorrect'

# Also include entry authors
response = client.get(reverse("entry-list") + '?include=authors,comments,comments.author,'
'comments.author.bio&page_size=5')
included = load_json(response.content).get('included')

assert len(load_json(response.content)['data']) == len(multiple_entries), 'Incorrect entry count'
assert [x.get('type') for x in included] == [
'authorBios', 'authorBios', 'authors', 'authors', 'authors', 'authors',
'comments', 'comments'], 'List included types are incorrect'

author_count = len([resource for resource in included if resource["type"] == "authors"])
expected_author_count = sum(
[entry.authors.count() for entry in multiple_entries] +
[entry.comment_set.filter(author__isnull=False).count() for entry in multiple_entries])
assert author_count == expected_author_count, 'List author count is incorrect'


def test_deep_included_data_on_detail(single_entry, client):
# Same test as in list but also ensures that intermediate resources (here comments' authors)
# are returned along with the leaf nodes
response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) +
'?include=comments,comments.author.bio')
included = load_json(response.content).get('included')

assert [x.get('type') for x in included] == ['authorBios', 'authors', 'comments'], \
'Detail included types are incorrect'

comment_count = len([resource for resource in included if resource["type"] == "comments"])
expected_comment_count = single_entry.comment_set.count()
assert comment_count == expected_comment_count, 'Detail comment count is incorrect'

author_bio_count = len([resource for resource in included if resource["type"] == "authorBios"])
expected_author_bio_count = single_entry.comment_set.filter(author__bio__isnull=False).count()
assert author_bio_count == expected_author_bio_count, 'Detail author bio count is incorrect'
4 changes: 3 additions & 1 deletion rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ def extract_included(fields, resource, resource_instance, included_resources):
included_resources.remove(field_name)
except ValueError:
# Skip fields not in requested included resources
continue
# If no child field, directly continue with the next field
if field_name not in [node.split('.')[0] for node in included_resources]:
continue

try:
relation_instance_or_manager = getattr(resource_instance, field_name)
Expand Down
2 changes: 1 addition & 1 deletion rest_framework_json_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def validate_path(serializer_class, field_path, path):
)
)
if len(field_path) > 1:
new_included_field_path = field_path[-1:]
new_included_field_path = field_path[1:]
# We go down one level in the path
validate_path(this_included_serializer, new_included_field_path, path)

Expand Down
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ deps =
drf33: djangorestframework>=3.3,<3.4
-r{toxinidir}/requirements-development.txt

setenv= DJANGO_SETTINGS_MODULE=example.settings.test
setenv =
PYTHONPATH = {toxinidir}
DJANGO_SETTINGS_MODULE=example.settings.test

commands =
py.test --basetemp={envtmpdir}
Expand Down