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 2adfb5b

Browse files
serialize nested serializers as attribute json value (#776)
1 parent 1318610 commit 2adfb5b

File tree

7 files changed

+153
-10
lines changed

7 files changed

+153
-10
lines changed

‎CHANGELOG.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ any parts of the framework not mentioned in the documentation should generally b
1010

1111
## [Unreleased]
1212

13+
### Added
14+
15+
* Added support for serializing nested serializers as attribute json value introducing setting `JSON_API_SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE`
16+
1317
### Fixed
1418

15-
* Avoid `AttributeError` for PUT and PATCH methods when using `APIView`
19+
* Avoid `AttributeError` for PUT and PATCH methods when using `APIView`
1620

1721
### Changed
1822

@@ -22,6 +26,7 @@ any parts of the framework not mentioned in the documentation should generally b
2226
### Deprecated
2327

2428
* Deprecate `source` argument of `SerializerMethodResourceRelatedField`, use `method_name` instead
29+
* Rendering nested serializers as relationships is deprecated. Use `ResourceRelatedField` instead
2530

2631

2732
## [3.1.0] - 2020年02月08日

‎example/tests/unit/test_renderers.py‎

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import json
22

33
import pytest
4+
from django.test import override_settings
5+
from django.utils import timezone
46

57
from rest_framework_json_api import serializers, views
68
from rest_framework_json_api.renderers import JSONRenderer
79

8-
from example.models import Author, Comment, Entry
10+
from example.models import Author, Comment, Entry, Blog
911

1012

1113
# serializers
@@ -38,6 +40,31 @@ class JSONAPIMeta:
3840
included_resources = ('related_models',)
3941

4042

43+
class EntryDRFSerializers(serializers.ModelSerializer):
44+
45+
class Meta:
46+
model = Entry
47+
fields = ('headline', 'body_text')
48+
read_only_fields = ('tags',)
49+
50+
51+
class CommentWithNestedFieldsSerializer(serializers.ModelSerializer):
52+
entry = EntryDRFSerializers()
53+
54+
class Meta:
55+
model = Comment
56+
exclude = ('created_at', 'modified_at', 'author')
57+
# fields = ('entry', 'body', 'author',)
58+
59+
60+
class AuthorWithNestedFieldsSerializer(serializers.ModelSerializer):
61+
comments = CommentWithNestedFieldsSerializer(many=True)
62+
63+
class Meta:
64+
model = Author
65+
fields = ('name', 'email', 'comments')
66+
67+
4168
# views
4269
class DummyTestViewSet(views.ModelViewSet):
4370
queryset = Entry.objects.all()
@@ -49,6 +76,12 @@ class ReadOnlyDummyTestViewSet(views.ReadOnlyModelViewSet):
4976
serializer_class = DummyTestSerializer
5077

5178

79+
class AuthorWithNestedFieldsViewSet(views.ModelViewSet):
80+
queryset = Author.objects.all()
81+
serializer_class = AuthorWithNestedFieldsSerializer
82+
resource_name = 'authors'
83+
84+
5285
def render_dummy_test_serialized_view(view_class, instance):
5386
serializer = view_class.serializer_class(instance=instance)
5487
renderer = JSONRenderer()
@@ -138,3 +171,54 @@ def test_extract_relation_instance(comment):
138171
field=serializer.fields['blog'], resource_instance=comment
139172
)
140173
assert got == comment.entry.blog
174+
175+
176+
def test_attribute_rendering_strategy(db):
177+
# setting up
178+
blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
179+
entry = Entry.objects.create(
180+
blog=blog,
181+
headline='headline',
182+
body_text='body_text',
183+
pub_date=timezone.now(),
184+
mod_date=timezone.now(),
185+
n_comments=0,
186+
n_pingbacks=0,
187+
rating=3
188+
)
189+
190+
author = Author.objects.create(name='some_author', email='some_author@example.org')
191+
entry.authors.add(author)
192+
193+
Comment.objects.create(
194+
entry=entry,
195+
body='testing one two three',
196+
author=Author.objects.first()
197+
)
198+
199+
with override_settings(
200+
JSON_API_SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE=True):
201+
rendered = render_dummy_test_serialized_view(AuthorWithNestedFieldsViewSet, author)
202+
result = json.loads(rendered.decode())
203+
204+
expected = {
205+
"data": {
206+
"type": "authors",
207+
"id": "1",
208+
"attributes": {
209+
"name": "some_author",
210+
"email": "some_author@example.org",
211+
"comments": [
212+
{
213+
"id": 1,
214+
"entry": {
215+
'headline': 'headline',
216+
'body_text': 'body_text',
217+
},
218+
"body": "testing one two three"
219+
}
220+
]
221+
}
222+
}
223+
}
224+
assert expected == result

‎example/views.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
EntryDRFSerializers,
2424
EntrySerializer,
2525
ProjectSerializer,
26-
ProjectTypeSerializer
27-
)
26+
ProjectTypeSerializer)
2827

2928
HTTP_422_UNPROCESSABLE_ENTITY = 422
3029

‎pytest.ini‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ DJANGO_SETTINGS_MODULE=example.settings.test
33
filterwarnings =
44
error::DeprecationWarning
55
error::PendingDeprecationWarning
6+
ignore::DeprecationWarning:rest_framework_json_api.serializers

‎rest_framework_json_api/renderers.py‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from rest_framework.relations import PKOnlyObject
1414
from rest_framework.serializers import BaseSerializer, ListSerializer, Serializer
1515
from rest_framework.settings import api_settings
16+
from .settings import json_api_settings
1617

1718
import rest_framework_json_api
1819
from rest_framework_json_api import utils
@@ -52,6 +53,7 @@ def extract_attributes(cls, fields, resource):
5253
Builds the `attributes` object of the JSON API resource object.
5354
"""
5455
data = OrderedDict()
56+
render_nested_as_attribute = json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE
5557
for field_name, field in iter(fields.items()):
5658
# ID is always provided in the root of JSON API so remove it from attributes
5759
if field_name == 'id':
@@ -61,10 +63,13 @@ def extract_attributes(cls, fields, resource):
6163
continue
6264
# Skip fields with relations
6365
if isinstance(
64-
field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)
66+
field, (relations.RelatedField, relations.ManyRelatedField)
6567
):
6668
continue
6769

70+
if isinstance(field, BaseSerializer) and not render_nested_as_attribute:
71+
continue
72+
6873
# Skip read_only attribute fields when `resource` is an empty
6974
# serializer. Prevents the "Raw Data" form of the browsable API
7075
# from rendering `"foo": null` for read only fields
@@ -89,6 +94,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
8994
from rest_framework_json_api.relations import ResourceRelatedField
9095

9196
data = OrderedDict()
97+
render_nested_as_attribute = json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE
9298

9399
# Don't try to extract relationships from a non-existent resource
94100
if resource_instance is None:
@@ -109,6 +115,9 @@ def extract_relationships(cls, fields, resource, resource_instance):
109115
):
110116
continue
111117

118+
if isinstance(field, BaseSerializer) and render_nested_as_attribute:
119+
continue
120+
112121
source = field.source
113122
relation_type = utils.get_related_resource_type(field)
114123

@@ -327,18 +336,22 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
327336
included_serializers = utils.get_included_serializers(current_serializer)
328337
included_resources = copy.copy(included_resources)
329338
included_resources = [inflection.underscore(value) for value in included_resources]
339+
render_nested_as_attribute = json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE
330340

331341
for field_name, field in iter(fields.items()):
332342
# Skip URL field
333343
if field_name == api_settings.URL_FIELD_NAME:
334344
continue
335345

336-
# Skip fields without relations or serialized data
346+
# Skip fields without relations
337347
if not isinstance(
338-
field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)
348+
field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)
339349
):
340350
continue
341351

352+
if isinstance(field, BaseSerializer) and render_nested_as_attribute:
353+
continue
354+
342355
try:
343356
included_resources.remove(field_name)
344357
except ValueError:

‎rest_framework_json_api/serializers.py‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
import inflection
24
from django.core.exceptions import ObjectDoesNotExist
35
from django.db.models.query import QuerySet
@@ -15,6 +17,8 @@
1517
get_resource_type_from_serializer
1618
)
1719

20+
from rest_framework_json_api.settings import json_api_settings
21+
1822

1923
class ResourceIdentifierObjectSerializer(BaseSerializer):
2024
default_error_messages = {
@@ -115,8 +119,41 @@ def validate_path(serializer_class, field_path, path):
115119
super(IncludedResourcesValidationMixin, self).__init__(*args, **kwargs)
116120

117121

122+
class SerializerMetaclass(SerializerMetaclass):
123+
124+
@classmethod
125+
def _get_declared_fields(cls, bases, attrs):
126+
fields = super()._get_declared_fields(bases, attrs)
127+
for field_name, field in fields.items():
128+
if isinstance(field, BaseSerializer) and \
129+
not json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE:
130+
clazz = '{}.{}'.format(attrs['__module__'], attrs['__qualname__'])
131+
if isinstance(field, ListSerializer):
132+
nested_class = type(field.child).__name__
133+
else:
134+
nested_class = type(field).__name__
135+
136+
warnings.warn(DeprecationWarning(
137+
"Rendering nested serializer as relationship is deprecated. "
138+
"Use `ResourceRelatedField` instead if {} in serializer {} should remain "
139+
"a relationship. Otherwise set "
140+
"JSON_API_SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE to True to render nested "
141+
"serializer as nested json attribute".format(nested_class, clazz)))
142+
return fields
143+
144+
145+
# If user imports serializer from here we can catch class definition and check
146+
# nested serializers for depricated use.
147+
class Serializer(
148+
IncludedResourcesValidationMixin, SparseFieldsetsMixin, Serializer,
149+
metaclass=SerializerMetaclass
150+
):
151+
pass
152+
153+
118154
class HyperlinkedModelSerializer(
119-
IncludedResourcesValidationMixin, SparseFieldsetsMixin, HyperlinkedModelSerializer
155+
IncludedResourcesValidationMixin, SparseFieldsetsMixin, HyperlinkedModelSerializer,
156+
metaclass=SerializerMetaclass
120157
):
121158
"""
122159
A type of `ModelSerializer` that uses hyperlinked relationships instead
@@ -132,7 +169,8 @@ class HyperlinkedModelSerializer(
132169
"""
133170

134171

135-
class ModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, ModelSerializer):
172+
class ModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, ModelSerializer,
173+
metaclass=SerializerMetaclass):
136174
"""
137175
A `ModelSerializer` is just a regular `Serializer`, except that:
138176
@@ -193,9 +231,11 @@ def to_representation(self, instance):
193231
def _get_field_representation(self, field, instance):
194232
request = self.context.get('request')
195233
is_included = field.source in get_included_resources(request)
234+
render_nested_as_attribute = json_api_settings.SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE
196235
if not is_included and \
197236
isinstance(field, ModelSerializer) and \
198-
hasattr(instance, field.source + '_id'):
237+
hasattr(instance, field.source + '_id') and \
238+
not render_nested_as_attribute:
199239
attribute = getattr(instance, field.source + '_id')
200240

201241
if attribute is None:

‎rest_framework_json_api/settings.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
'FORMAT_TYPES': False,
1515
'PLURALIZE_TYPES': False,
1616
'UNIFORM_EXCEPTIONS': False,
17+
'SERIALIZE_NESTED_SERIALIZERS_AS_ATTRIBUTE': False
1718
}
1819

1920

0 commit comments

Comments
(0)

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