diff --git a/example/serializers.py b/example/serializers.py index cddbca37..f079cb8a 100644 --- a/example/serializers.py +++ b/example/serializers.py @@ -29,13 +29,19 @@ def __init__(self, *args, **kwargs): suggested = relations.ResourceRelatedField( source='get_suggested', model=Entry, read_only=True) + all_comments = relations.ResourceRelatedField(source='get_all_comments', + many=True, model=Comment, read_only=True) + + def get_all_comments(self, obj): + return Comment.objects.all() + def get_suggested(self, obj): return Entry.objects.exclude(pk=obj.pk).first() class Meta: model = Entry fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date', - 'authors', 'comments', 'suggested',) + 'authors', 'comments', 'suggested', 'all_comments',) class AuthorSerializer(serializers.ModelSerializer): diff --git a/example/tests/integration/test_non_paginated_responses.py b/example/tests/integration/test_non_paginated_responses.py index 4a2684f4..86ad6a26 100644 --- a/example/tests/integration/test_non_paginated_responses.py +++ b/example/tests/integration/test_non_paginated_responses.py @@ -28,6 +28,11 @@ def test_multiple_entries_no_pagination(multiple_entries, rf): }, "relationships": { + 'allComments': { + 'meta': {'count': 2}, + 'data': [{'id': '1','type': 'comments'}, + {'id': '2','type': 'comments'}] + }, "blog": { "data": {"type": "blogs", "id": "1"} }, @@ -53,6 +58,11 @@ def test_multiple_entries_no_pagination(multiple_entries, rf): }, "relationships": { + 'allComments': { + 'meta': {'count': 2}, + 'data': [{'id': '1','type': 'comments'}, + {'id': '2','type': 'comments'}] + }, "blog": { "data": {"type": "blogs", "id": "2"} }, diff --git a/example/tests/integration/test_pagination.py b/example/tests/integration/test_pagination.py index 793205a5..784e63cd 100644 --- a/example/tests/integration/test_pagination.py +++ b/example/tests/integration/test_pagination.py @@ -1,3 +1,4 @@ +import json from django.core.urlresolvers import reverse import pytest @@ -22,6 +23,10 @@ def test_pagination_with_single_entry(single_entry, client): }, "relationships": { + 'allComments': { + 'meta': {'count': 1}, + 'data': [{'id': '1','type': 'comments'}] + }, "blog": { "data": {"type": "blogs", "id": "1"} }, @@ -53,7 +58,6 @@ def test_pagination_with_single_entry(single_entry, client): } response = client.get(reverse("entry-list")) - content_dump = redump_json(response.content) - expected_dump = dump_json(expected) + content = json.loads(response.content.decode('utf-8')) - assert content_dump == expected_dump + assert content == expected diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 8d1f4d48..83ab44a8 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -8,6 +8,28 @@ from rest_framework_json_api.utils import format_relation_name, Hyperlink, \ get_resource_type_from_queryset, get_resource_type_from_instance +import pdb + +JSONAPI_MANY_RELATION_KWARGS = ('model', ) + MANY_RELATION_KWARGS + +class ManyResourceRelatedField(ManyRelatedField): + """ + Allows us to use serializer method RelatedFields + with return querysets + """ + def __init__(self, child_relation=None, *args, **kwargs): + model = kwargs.pop('model', None) + if model: + self.model = model + super(ManyResourceRelatedField, self).__init__(child_relation, *args, **kwargs) + + def get_attribute(self, instance): + if self.source and hasattr(self.parent, self.source): + serializer_method = getattr(self.parent, self.source) + if hasattr(serializer_method, '__call__'): + return serializer_method(instance) + return super(ManyResourceRelatedField, self).get_attribute(instance) + class ResourceRelatedField(PrimaryKeyRelatedField): self_link_view_name = None @@ -22,6 +44,21 @@ class ResourceRelatedField(PrimaryKeyRelatedField): 'no_match': _('Invalid hyperlink - No URL match.'), } + def __new__(cls, *args, **kwargs): + # We override this because getting + # serializer methods fails when many is true + if kwargs.pop('many', False): + return cls.many_init(*args, **kwargs) + return super(ResourceRelatedField, cls).__new__(cls, *args, **kwargs) + + @classmethod + def many_init(cls, *args, **kwargs): + list_kwargs = {'child_relation': cls(*args, **kwargs)} + for key in kwargs.keys(): + if key in JSONAPI_MANY_RELATION_KWARGS: + list_kwargs[key] = kwargs[key] + return ManyResourceRelatedField(**list_kwargs) + def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwargs): if self_link_view_name is not None: self.self_link_view_name = self_link_view_name diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 5dfa8c69..a27f3a9e 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -381,7 +381,7 @@ def extract_relationships(fields, resource, resource_instance): relation_data = list() serializer_data = resource.get(field_name) - resource_instance_queryset = list(relation_instance_or_manager.all()) + resource_instance_queryset = relation_instance_or_manager.all() if isinstance(serializer_data, list): for position in range(len(serializer_data)): nested_resource_instance = resource_instance_queryset[position]