Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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‎

Lines changed: 14 additions & 6 deletions
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‎

Lines changed: 1 addition & 1 deletion
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‎

Lines changed: 6 additions & 0 deletions
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‎

Lines changed: 3 additions & 0 deletions
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‎

Lines changed: 41 additions & 1 deletion
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)

AltStyle によって変換されたページ (->オリジナル) /