Skip to content

Commit b2728e4

Browse files
committed
Merge pull request #223 from django-json-api/bugfix/many-serializer-method-field
Support many=True on related serializer method fields.
2 parents e4594e0 + 72feb47 commit b2728e4

File tree

5 files changed

+65
-8
lines changed

5 files changed

+65
-8
lines changed

example/serializers.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,33 @@ class EntrySerializer(serializers.ModelSerializer):
2525

2626
def __init__(self, *args, **kwargs):
2727
# to make testing more concise we'll only output the
28-
# `suggested` field when it's requested via `include`
28+
# `featured` field when it's requested via `include`
2929
request = kwargs.get('context', {}).get('request')
30-
if request and 'suggested' not in request.query_params.get('include', []):
31-
self.fields.pop('suggested')
30+
if request and 'featured' not in request.query_params.get('include', []):
31+
self.fields.pop('featured')
3232
super(EntrySerializer, self).__init__(*args, **kwargs)
3333

3434
included_serializers = {
3535
'authors': 'example.serializers.AuthorSerializer',
3636
'comments': 'example.serializers.CommentSerializer',
37-
'suggested': 'example.serializers.EntrySerializer',
37+
'featured': 'example.serializers.EntrySerializer',
3838
}
3939

4040
body_format = serializers.SerializerMethodField()
41+
# many related from model
4142
comments = relations.ResourceRelatedField(
4243
source='comment_set', many=True, read_only=True)
44+
# many related from serializer
4345
suggested = relations.SerializerMethodResourceRelatedField(
44-
source='get_suggested', model=Entry, read_only=True)
46+
source='get_suggested', model=Entry, many=True, read_only=True)
47+
# single related from serializer
48+
featured = relations.SerializerMethodResourceRelatedField(
49+
source='get_featured', model=Entry, read_only=True)
4550

4651
def get_suggested(self, obj):
52+
return Entry.objects.exclude(pk=obj.pk)
53+
54+
def get_featured(self, obj):
4755
return Entry.objects.exclude(pk=obj.pk).first()
4856

4957
def get_body_format(self, obj):
@@ -52,7 +60,7 @@ def get_body_format(self, obj):
5260
class Meta:
5361
model = Entry
5462
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
55-
'authors', 'comments', 'suggested',)
63+
'authors', 'comments', 'featured', 'suggested',)
5664
meta_fields = ('body_format',)
5765

5866

example/tests/integration/test_includes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_included_data_on_detail(single_entry, client):
3131

3232
def test_dynamic_related_data_is_included(single_entry, entry_factory, client):
3333
entry_factory()
34-
response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=suggested')
34+
response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=featured')
3535
included = load_json(response.content).get('included')
3636

3737
assert [x.get('type') for x in included] == ['entries'], 'Dynamic included types are incorrect'

example/tests/integration/test_non_paginated_responses.py

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def test_multiple_entries_no_pagination(multiple_entries, rf):
4141
"comments": {
4242
"meta": {"count": 1},
4343
"data": [{"type": "comments", "id": "1"}]
44+
},
45+
"suggested": {
46+
"data": [{"type": "entries", "id": "2"}]
4447
}
4548
}
4649
},
@@ -69,6 +72,9 @@ def test_multiple_entries_no_pagination(multiple_entries, rf):
6972
"comments": {
7073
"meta": {"count": 1},
7174
"data": [{"type": "comments", "id": "2"}]
75+
},
76+
"suggested": {
77+
"data": [{"type": "entries", "id": "1"}]
7278
}
7379
}
7480
},

example/tests/integration/test_pagination.py

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ def test_pagination_with_single_entry(single_entry, client):
3535
"comments": {
3636
"meta": {"count": 1},
3737
"data": [{"type": "comments", "id": "1"}]
38+
},
39+
"suggested": {
40+
"data": []
3841
}
3942
}
4043
}],

rest_framework_json_api/relations.py

+41-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from rest_framework.fields import MISSING_ERROR_MESSAGE
44
from rest_framework.relations import *
55
from django.utils.translation import ugettext_lazy as _
6+
from django.db.models.query import QuerySet
67

78
from rest_framework_json_api.exceptions import Conflict
89
from rest_framework_json_api.utils import Hyperlink, \
@@ -168,11 +169,50 @@ def choices(self):
168169
])
169170

170171

172+
171173
class SerializerMethodResourceRelatedField(ResourceRelatedField):
174+
"""
175+
Allows us to use serializer method RelatedFields
176+
with return querysets
177+
"""
178+
def __new__(cls, *args, **kwargs):
179+
"""
180+
We override this because getting serializer methods
181+
fails at the base class when many=True
182+
"""
183+
if kwargs.pop('many', False):
184+
return cls.many_init(*args, **kwargs)
185+
return super(ResourceRelatedField, cls).__new__(cls, *args, **kwargs)
186+
187+
def __init__(self, child_relation=None, *args, **kwargs):
188+
# DRF 3.1 doesn't expect the `many` kwarg
189+
kwargs.pop('many', None)
190+
model = kwargs.pop('model', None)
191+
if model:
192+
self.model = model
193+
super(SerializerMethodResourceRelatedField, self).__init__(child_relation, *args, **kwargs)
194+
195+
@classmethod
196+
def many_init(cls, *args, **kwargs):
197+
list_kwargs = {'child_relation': cls(*args, **kwargs)}
198+
for key in kwargs.keys():
199+
if key in ('model',) + MANY_RELATION_KWARGS:
200+
list_kwargs[key] = kwargs[key]
201+
return SerializerMethodResourceRelatedField(**list_kwargs)
202+
172203
def get_attribute(self, instance):
173204
# check for a source fn defined on the serializer instead of the model
174205
if self.source and hasattr(self.parent, self.source):
175206
serializer_method = getattr(self.parent, self.source)
176207
if hasattr(serializer_method, '__call__'):
177208
return serializer_method(instance)
178-
return super(ResourceRelatedField, self).get_attribute(instance)
209+
return super(SerializerMethodResourceRelatedField, self).get_attribute(instance)
210+
211+
def to_representation(self, value):
212+
if isinstance(value, QuerySet):
213+
base = super(SerializerMethodResourceRelatedField, self)
214+
return [base.to_representation(x) for x in value]
215+
return super(SerializerMethodResourceRelatedField, self).to_representation(value)
216+
217+
def get_links(self, obj=None, lookup_field='pk'):
218+
return OrderedDict()

0 commit comments

Comments
 (0)