From ba6481a036fb5a59810ccc5c77eb6dca253b4305 Mon Sep 17 00:00:00 2001 From: Charles-Pierre Astolfi Date: Wed, 16 Mar 2016 23:04:39 +0100 Subject: [PATCH] ResourceRelatedField now accepts serializer methods when many=True --- example/serializers.py | 8 ++++- .../test_non_paginated_responses.py | 10 ++++++ example/tests/integration/test_pagination.py | 11 +++--- rest_framework_json_api/relations.py | 35 +++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/example/serializers.py b/example/serializers.py index 99cee740..21dab570 100644 --- a/example/serializers.py +++ b/example/serializers.py @@ -43,6 +43,12 @@ def __init__(self, *args, **kwargs): suggested = relations.SerializerMethodResourceRelatedField( 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() @@ -52,7 +58,7 @@ def get_body_format(self, obj): class Meta: model = Entry fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date', - 'authors', 'comments', 'suggested',) + 'authors', 'comments', 'suggested', 'all_comments',) meta_fields = ('body_format',) diff --git a/example/tests/integration/test_non_paginated_responses.py b/example/tests/integration/test_non_paginated_responses.py index f68f2b71..5ada5b31 100644 --- a/example/tests/integration/test_non_paginated_responses.py +++ b/example/tests/integration/test_non_paginated_responses.py @@ -31,6 +31,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"} }, @@ -59,6 +64,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 0cc5e15e..a89f803b 100644 --- a/example/tests/integration/test_pagination.py +++ b/example/tests/integration/test_pagination.py @@ -1,7 +1,7 @@ from django.core.urlresolvers import reverse import pytest -from example.tests.utils import dump_json, redump_json +import json pytestmark = pytest.mark.django_db @@ -25,6 +25,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"} }, @@ -56,7 +60,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 b7ccce36..f6f0be8c 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -9,6 +9,26 @@ get_resource_type_from_queryset, get_resource_type_from_instance, \ get_included_serializers, get_resource_type_from_serializer +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 @@ -25,6 +45,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