Skip to content

Commit a276011

Browse files
author
Leifur Halldor Asgeirsson
committed
included_serializers may be specified with strings
in the included_serializers class attribute, classes may be specified as a string containing the absolute python path for the desired class. Thus, the values in included_serializers may be a class, a string with the python path of the desired class, or 'self' to specify the class of the current serializer. for example: ```python from rest_framework_json_api import serializers from rest_framework_json_api.relations import ResourceRelatedField from myapp.models import Thing from anotherapp.serializers import CategorySerializer class ThingSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Thing serializer_related_field = ResourceRelatedField included_serializers = { 'category': CategorySerializer, 'accessories': 'myapp.serializers.AccessorySerializer', 'related_things': 'self' # ThingSerializer } ```
1 parent 2faeca2 commit a276011

File tree

4 files changed

+73
-15
lines changed

4 files changed

+73
-15
lines changed

example/serializers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from rest_framework import serializers
2-
from example.models import Blog, Entry, Author
2+
from example.models import Blog, Entry, Author, Comment
33

44

55
class BlogSerializer(serializers.ModelSerializer):
@@ -21,3 +21,10 @@ class AuthorSerializer(serializers.ModelSerializer):
2121
class Meta:
2222
model = Author
2323
fields = ('name', 'email',)
24+
25+
26+
class CommentSerializer(serializers.ModelSerializer):
27+
28+
class Meta:
29+
model = Comment
30+
fields = ('entry', 'body', 'author',)

example/tests/test_utils.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from django.utils import six
23
from django.conf import settings
34
from django.contrib.auth import get_user_model
45
from rest_framework import serializers
@@ -7,6 +8,10 @@
78

89
from rest_framework_json_api import utils
910

11+
from example.serializers import (EntrySerializer, BlogSerializer,
12+
AuthorSerializer, CommentSerializer)
13+
from rest_framework_json_api.utils import get_included_serializers
14+
1015
pytestmark = pytest.mark.django_db
1116

1217
class ResourceView(APIView):
@@ -100,3 +105,43 @@ def test_build_json_resource_obj():
100105
assert utils.build_json_resource_obj(
101106
serializer.fields, resource, resource_instance, 'user') == output
102107

108+
109+
class SerializerWithIncludedSerializers(EntrySerializer):
110+
included_serializers = {
111+
'blog': BlogSerializer,
112+
'authors': 'example.serializers.AuthorSerializer',
113+
'comments': 'example.serializers.CommentSerializer',
114+
'self': 'self' # this wouldn't make sense in practice (and would be prohibited by
115+
# IncludedResourcesValidationMixin) but it's useful for the test
116+
}
117+
118+
119+
def test_get_included_serializers_against_class():
120+
klass = SerializerWithIncludedSerializers
121+
included_serializers = get_included_serializers(klass)
122+
expected_included_serializers = {
123+
'blog': BlogSerializer,
124+
'authors': AuthorSerializer,
125+
'comments': CommentSerializer,
126+
'self': klass
127+
}
128+
assert (six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers),
129+
'the keys must be preserved')
130+
131+
assert included_serializers == expected_included_serializers
132+
133+
134+
def test_get_included_serializers_against_instance():
135+
klass = SerializerWithIncludedSerializers
136+
instance = klass()
137+
included_serializers = get_included_serializers(instance)
138+
expected_included_serializers = {
139+
'blog': BlogSerializer,
140+
'authors': AuthorSerializer,
141+
'comments': CommentSerializer,
142+
'self': klass
143+
}
144+
assert (six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers),
145+
'the keys must be preserved')
146+
147+
assert included_serializers == expected_included_serializers

rest_framework_json_api/serializers.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from rest_framework.serializers import *
44

55
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, \
6-
get_resource_type_from_serializer
6+
get_resource_type_from_serializer, get_included_serializers
77

88

99
class ResourceIdentifierObjectSerializer(BaseSerializer):
@@ -67,11 +67,8 @@ def __init__(self, *args, **kwargs):
6767
request = context.get('request') if context else None
6868
view = context.get('view') if context else None
6969

70-
def validate_path(serializer_class, field_path, serializers, path):
71-
serializers = {
72-
key: serializer_class if serializer == 'self' else serializer
73-
for key, serializer in serializers.items()
74-
} if serializers else dict()
70+
def validate_path(serializer_class, field_path, path):
71+
serializers = get_included_serializers(serializer_class)
7572
if serializers is None:
7673
raise ParseError('This endpoint does not support the include parameter')
7774
this_field_name = field_path[0]
@@ -94,9 +91,8 @@ def validate_path(serializer_class, field_path, serializers, path):
9491
for included_field_name in included_resources:
9592
included_field_path = included_field_name.split('.')
9693
this_serializer_class = view.serializer_class
97-
included_serializers = getattr(this_serializer_class, 'included_serializers', None)
9894
# lets validate the current path
99-
validate_path(this_serializer_class, included_field_path, included_serializers, included_field_name)
95+
validate_path(this_serializer_class, included_field_path, included_field_name)
10096

10197
super(IncludedResourcesValidationMixin, self).__init__(*args, **kwargs)
10298

rest_framework_json_api/utils.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""
22
Utils.
33
"""
4+
import copy
45
import inflection
56
from django.conf import settings
67
from django.utils import six, encoding
78
from django.utils.translation import ugettext_lazy as _
9+
from django.utils.module_loading import import_string
810
from rest_framework.compat import OrderedDict
911
from rest_framework.serializers import BaseSerializer, ListSerializer, ModelSerializer
1012
from rest_framework.relations import RelatedField, HyperlinkedRelatedField, PrimaryKeyRelatedField, \
@@ -395,12 +397,7 @@ def extract_included(fields, resource, resource_instance, included_resources):
395397

396398
current_serializer = fields.serializer
397399
context = current_serializer.context
398-
included_serializers = getattr(fields.serializer, 'included_serializers', None)
399-
400-
included_serializers = {
401-
key: current_serializer.__class__ if serializer == 'self' else serializer
402-
for key, serializer in included_serializers.items()
403-
} if included_serializers else dict()
400+
included_serializers = get_included_serializers(current_serializer)
404401

405402
for field_name, field in six.iteritems(fields):
406403
# Skip URL field
@@ -473,6 +470,19 @@ def extract_included(fields, resource, resource_instance, included_resources):
473470
return format_keys(included_data)
474471

475472

473+
def get_included_serializers(serializer):
474+
included_serializers = copy.copy(getattr(serializer, 'included_serializers', dict()))
475+
476+
for name, value in six.iteritems(included_serializers):
477+
if not isinstance(value, type):
478+
if value == 'self':
479+
included_serializers[name] = serializer if isinstance(serializer, type) else serializer.__class__
480+
else:
481+
included_serializers[name] = import_string(value)
482+
483+
return included_serializers
484+
485+
476486
class Hyperlink(six.text_type):
477487
"""
478488
A string like object that additionally has an associated name.

0 commit comments

Comments
 (0)