Skip to content

Commit 325f3b9

Browse files
committed
Merge pull request #152 from scottfisk/feature/compound_documents
Added the ability to specify a resource_name on Models
2 parents 42e0554 + 836391f commit 325f3b9

File tree

9 files changed

+185
-29
lines changed

9 files changed

+185
-29
lines changed

example/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def get_suggested(self, obj):
3535
class Meta:
3636
model = Entry
3737
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
38-
'authors', 'comments', 'suggested',)
38+
'authors', 'comments', 'suggested',)
3939

4040

4141
class AuthorSerializer(serializers.ModelSerializer):
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import pytest
2+
from django.core.urlresolvers import reverse
3+
4+
from example.tests.utils import load_json
5+
6+
from example import models, serializers, views
7+
pytestmark = pytest.mark.django_db
8+
9+
10+
class _PatchedModel:
11+
class JSONAPIMeta:
12+
resource_name = "resource_name_from_JSONAPIMeta"
13+
14+
15+
def _check_resource_and_relationship_comment_type_match(django_client):
16+
entry_response = django_client.get(reverse("entry-list"))
17+
comment_response = django_client.get(reverse("comment-list"))
18+
19+
comment_resource_type = load_json(comment_response.content).get('data')[0].get('type')
20+
comment_relationship_type = load_json(entry_response.content).get(
21+
'data')[0].get('relationships').get('comments').get('data')[0].get('type')
22+
23+
assert comment_resource_type == comment_relationship_type, "The resource type seen in the relationships and head resource do not match"
24+
25+
26+
def _check_relationship_and_included_comment_type_are_the_same(django_client, url):
27+
response = django_client.get(url + "?include=comments")
28+
data = load_json(response.content).get('data')[0]
29+
comment = load_json(response.content).get('included')[0]
30+
31+
comment_relationship_type = data.get('relationships').get('comments').get('data')[0].get('type')
32+
comment_included_type = comment.get('type')
33+
34+
assert comment_relationship_type == comment_included_type, "The resource type seen in the relationships and included do not match"
35+
36+
37+
@pytest.mark.usefixtures("single_entry")
38+
class TestModelResourceName:
39+
40+
def test_model_resource_name_on_list(self, client):
41+
models.Comment.__bases__ += (_PatchedModel,)
42+
response = client.get(reverse("comment-list"))
43+
data = load_json(response.content)['data'][0]
44+
# name should be super-author instead of model name RenamedAuthor
45+
assert (data.get('type') == 'resource_name_from_JSONAPIMeta'), (
46+
'resource_name from model incorrect on list')
47+
48+
# Precedence tests
49+
def test_resource_name_precendence(self, client):
50+
# default
51+
response = client.get(reverse("comment-list"))
52+
data = load_json(response.content)['data'][0]
53+
assert (data.get('type') == 'comments'), (
54+
'resource_name from model incorrect on list')
55+
56+
# model > default
57+
models.Comment.__bases__ += (_PatchedModel,)
58+
response = client.get(reverse("comment-list"))
59+
data = load_json(response.content)['data'][0]
60+
assert (data.get('type') == 'resource_name_from_JSONAPIMeta'), (
61+
'resource_name from model incorrect on list')
62+
63+
# serializer > model
64+
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
65+
response = client.get(reverse("comment-list"))
66+
data = load_json(response.content)['data'][0]
67+
assert (data.get('type') == 'resource_name_from_serializer'), (
68+
'resource_name from serializer incorrect on list')
69+
70+
# view > serializer > model
71+
views.CommentViewSet.resource_name = 'resource_name_from_view'
72+
response = client.get(reverse("comment-list"))
73+
data = load_json(response.content)['data'][0]
74+
assert (data.get('type') == 'resource_name_from_view'), (
75+
'resource_name from view incorrect on list')
76+
77+
def teardown_method(self, method):
78+
models.Comment.__bases__ = (models.Comment.__bases__[0],)
79+
try:
80+
delattr(serializers.CommentSerializer.Meta, "resource_name")
81+
except AttributeError:
82+
pass
83+
try:
84+
delattr(views.CommentViewSet, "resource_name")
85+
except AttributeError:
86+
pass
87+
88+
89+
@pytest.mark.usefixtures("single_entry")
90+
class TestResourceNameConsistency:
91+
92+
# Included rename tests
93+
def test_type_match_on_included_and_inline_base(self, client):
94+
_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))
95+
96+
def test_type_match_on_included_and_inline_with_JSONAPIMeta(self, client):
97+
models.Comment.__bases__ += (_PatchedModel,)
98+
99+
_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))
100+
101+
def test_type_match_on_included_and_inline_with_serializer_resource_name(self, client):
102+
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
103+
104+
_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))
105+
106+
def test_type_match_on_included_and_inline_with_serializer_resource_name_and_JSONAPIMeta(self, client):
107+
models.Comment.__bases__ += (_PatchedModel,)
108+
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
109+
110+
_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))
111+
112+
# Relation rename tests
113+
def test_resource_and_relationship_type_match(self, client):
114+
_check_resource_and_relationship_comment_type_match(client)
115+
116+
def test_resource_and_relationship_type_match_with_serializer_resource_name(self, client):
117+
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
118+
119+
_check_resource_and_relationship_comment_type_match(client)
120+
121+
def test_resource_and_relationship_type_match_with_JSONAPIMeta(self, client):
122+
models.Comment.__bases__ += (_PatchedModel,)
123+
124+
_check_resource_and_relationship_comment_type_match(client)
125+
126+
def test_resource_and_relationship_type_match_with_serializer_resource_name_and_JSONAPIMeta(self, client):
127+
models.Comment.__bases__ += (_PatchedModel,)
128+
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
129+
130+
_check_resource_and_relationship_comment_type_match(client)
131+
132+
def teardown_method(self, method):
133+
models.Comment.__bases__ = (models.Comment.__bases__[0],)
134+
try:
135+
delattr(serializers.CommentSerializer.Meta, "resource_name")
136+
except AttributeError:
137+
pass

example/urls_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
from rest_framework import routers
33

44
from example.views import BlogViewSet, EntryViewSet, AuthorViewSet, EntryRelationshipView, BlogRelationshipView, \
5-
CommentRelationshipView, AuthorRelationshipView
5+
CommentRelationshipView, AuthorRelationshipView, CommentViewSet
66
from .api.resources.identity import Identity, GenericIdentity
77

88
router = routers.DefaultRouter(trailing_slash=False)
99

1010
router.register(r'blogs', BlogViewSet)
1111
router.register(r'entries', EntryViewSet)
1212
router.register(r'authors', AuthorViewSet)
13+
router.register(r'comments', CommentViewSet)
1314

1415
# for the old tests
1516
router.register(r'identities', Identity)

example/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,3 @@ class CommentRelationshipView(RelationshipView):
4141
class AuthorRelationshipView(RelationshipView):
4242
queryset = Author.objects.all()
4343
self_link_view_name = 'author-relationships'
44-

rest_framework_json_api/relations.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from django.utils.translation import ugettext_lazy as _
66

77
from rest_framework_json_api.exceptions import Conflict
8-
from rest_framework_json_api.utils import format_relation_name, Hyperlink, \
9-
get_resource_type_from_queryset, get_resource_type_from_instance
8+
from rest_framework_json_api.utils import Hyperlink, \
9+
get_resource_type_from_queryset, get_resource_type_from_instance, \
10+
get_included_serializers, get_resource_type_from_serializer
1011

1112

1213
class ResourceRelatedField(PrimaryKeyRelatedField):
@@ -127,7 +128,18 @@ def to_representation(self, value):
127128
else:
128129
pk = value.pk
129130

130-
return OrderedDict([('type', format_relation_name(get_resource_type_from_instance(value))), ('id', str(pk))])
131+
# check to see if this resource has a different resource_name when
132+
# included and use that name
133+
resource_type = None
134+
root = getattr(self.parent, 'parent', self.parent)
135+
field_name = self.field_name if self.field_name else self.parent.field_name
136+
if getattr(root, 'included_serializers', None) is not None:
137+
includes = get_included_serializers(root)
138+
if field_name in includes.keys():
139+
resource_type = get_resource_type_from_serializer(includes[field_name])
140+
141+
resource_type = resource_type if resource_type else get_resource_type_from_instance(value)
142+
return OrderedDict([('type', resource_type), ('id', str(pk))])
131143

132144
@property
133145
def choices(self):

rest_framework_json_api/renderers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,7 @@ def extract_included(fields, resource, resource_instance, included_resources):
276276

277277
if isinstance(field, ListSerializer):
278278
serializer = field.child
279-
model = serializer.Meta.model
280-
relation_type = utils.format_relation_name(model.__name__)
279+
relation_type = utils.get_resource_type_from_serializer(serializer)
281280
relation_queryset = list(relation_instance_or_manager.all())
282281

283282
# Get the serializer fields
@@ -298,15 +297,16 @@ def extract_included(fields, resource, resource_instance, included_resources):
298297
)
299298

300299
if isinstance(field, ModelSerializer):
301-
model = field.Meta.model
302-
relation_type = utils.format_relation_name(model.__name__)
300+
301+
relation_type = utils.get_resource_type_from_serializer(field)
303302

304303
# Get the serializer fields
305304
serializer_fields = utils.get_serializer_fields(field)
306305
if serializer_data:
307306
included_data.append(
308-
JSONRenderer.build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager,
309-
relation_type)
307+
JSONRenderer.build_json_resource_obj(
308+
serializer_fields, serializer_data,
309+
relation_instance_or_manager, relation_type)
310310
)
311311
included_data.extend(
312312
JSONRenderer.extract_included(

rest_framework_json_api/serializers.py

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

55
from rest_framework_json_api.relations import ResourceRelatedField
6-
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, \
7-
get_resource_type_from_serializer, get_included_serializers
6+
from rest_framework_json_api.utils import (
7+
get_resource_type_from_model, get_resource_type_from_instance,
8+
get_resource_type_from_serializer, get_included_serializers)
89

910

1011
class ResourceIdentifierObjectSerializer(BaseSerializer):
@@ -24,12 +25,12 @@ def __init__(self, *args, **kwargs):
2425

2526
def to_representation(self, instance):
2627
return {
27-
'type': format_relation_name(get_resource_type_from_instance(instance)),
28+
'type': get_resource_type_from_instance(instance),
2829
'id': str(instance.pk)
2930
}
3031

3132
def to_internal_value(self, data):
32-
if data['type'] != format_relation_name(self.model_class.__name__):
33+
if data['type'] != get_resource_type_from_model(self.model_class):
3334
self.fail('incorrect_model_type', model_type=self.model_class, received_type=data['type'])
3435
pk = data['id']
3536
try:

rest_framework_json_api/utils.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def get_resource_name(context):
5050
return get_resource_type_from_serializer(serializer)
5151
except AttributeError:
5252
try:
53-
resource_name = view.model.__name__
53+
resource_name = get_resource_type_from_model(view.model)
5454
except AttributeError:
5555
resource_name = view.__class__.__name__
5656

@@ -171,7 +171,7 @@ def get_related_resource_type(relation):
171171
relation_model = parent_model_relation.field.related.model
172172
else:
173173
return get_related_resource_type(parent_model_relation)
174-
return format_relation_name(relation_model.__name__)
174+
return get_resource_type_from_model(relation_model)
175175

176176

177177
def get_instance_or_manager_resource_type(resource_instance_or_manager):
@@ -182,25 +182,31 @@ def get_instance_or_manager_resource_type(resource_instance_or_manager):
182182
pass
183183

184184

185+
def get_resource_type_from_model(model):
186+
json_api_meta = getattr(model, 'JSONAPIMeta', None)
187+
return getattr(
188+
json_api_meta,
189+
'resource_name',
190+
format_relation_name(model.__name__))
191+
192+
185193
def get_resource_type_from_queryset(qs):
186-
return format_relation_name(qs.model._meta.model.__name__)
194+
return get_resource_type_from_model(qs.model)
187195

188196

189197
def get_resource_type_from_instance(instance):
190-
return format_relation_name(instance._meta.model.__name__)
198+
return get_resource_type_from_model(instance._meta.model)
191199

192200

193201
def get_resource_type_from_manager(manager):
194-
return format_relation_name(manager.model.__name__)
202+
return get_resource_type_from_model(manager.model)
195203

196204

197205
def get_resource_type_from_serializer(serializer):
198-
try:
199-
# Check the meta class for resource_name
200-
return serializer.Meta.resource_name
201-
except AttributeError:
202-
# Use the serializer model then pluralize and format
203-
return format_relation_name(serializer.Meta.model.__name__)
206+
return getattr(
207+
serializer.Meta,
208+
'resource_name',
209+
get_resource_type_from_model(serializer.Meta.model))
204210

205211

206212
def get_included_serializers(serializer):

rest_framework_json_api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from rest_framework_json_api.exceptions import Conflict
1414
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer
15-
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, OrderedDict, Hyperlink
15+
from rest_framework_json_api.utils import get_resource_type_from_instance, OrderedDict, Hyperlink
1616

1717

1818
class RelationshipView(generics.GenericAPIView):
@@ -154,7 +154,7 @@ def _instantiate_serializer(self, instance):
154154
def get_resource_name(self):
155155
if not hasattr(self, '_resource_name'):
156156
instance = getattr(self.get_object(), self.kwargs['related_field'])
157-
self._resource_name = format_relation_name(get_resource_type_from_instance(instance))
157+
self._resource_name = get_resource_type_from_instance(instance)
158158
return self._resource_name
159159

160160
def set_resource_name(self, value):

0 commit comments

Comments
 (0)