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 4488068

Browse files
committed
Merge pull request #197 from scottfisk/feature/compound_documents
Use JSONAPIMeta in models to determine the resource name
2 parents 08c6ba8 + fed0bf6 commit 4488068

File tree

9 files changed

+209
-32
lines changed

9 files changed

+209
-32
lines changed

‎docs/usage.md‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ per request via the `PAGINATE_BY_PARAM` query parameter (`page_size` by default)
3636

3737
### Setting the resource_name
3838

39-
You may manually set the `resource_name` property on views or serializers to
40-
specify the `type` key in the json output. It is automatically set for you as the
41-
plural of the view or model name except on resources that do not subclass
39+
You may manually set the `resource_name` property on views, serializers, or
40+
models to specify the `type` key in the json output. In the case of setting the
41+
`resource_name` property for models you must include the property inside a
42+
`JSONAPIMeta` class on the model. It is automatically set for you as the plural
43+
of the view or model name except on resources that do not subclass
4244
`rest_framework.viewsets.ModelViewSet`:
45+
46+
47+
Example - `resource_name` on View:
4348
``` python
4449
class Me(generics.GenericAPIView):
4550
"""
@@ -56,6 +61,23 @@ If you set the `resource_name` property on the object to `False` the data
5661
will be returned without modification.
5762

5863

64+
Example - `resource_name` on Model:
65+
``` python
66+
class Me(models.Model):
67+
"""
68+
A simple model
69+
"""
70+
name = models.CharField(max_length=100)
71+
72+
class JSONAPIMeta:
73+
resource_name = "users"
74+
```
75+
If you set the `resource_name` on a combination of model, serializer, or view
76+
in the same hierarchy, the name will be resolved as following: view >
77+
serializer > model. (Ex: A view `resource_name` will always override a
78+
`resource_name` specified on a serializer or model)
79+
80+
5981
### Inflecting object and relation keys
6082

6183
This package includes the ability (off by default) to automatically convert json

‎example/serializers.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def get_body_format(self, obj):
5151
class Meta:
5252
model = Entry
5353
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
54-
'authors', 'comments', 'suggested',)
54+
'authors', 'comments', 'suggested',)
5555
meta_fields = ('body_format',)
5656

5757

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/views.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from rest_framework_json_api.views import RelationshipView
33
from example.models import Blog, Entry, Author, Comment
44
from example.serializers import (
5-
BlogSerializer, EntrySerializer, AuthorSerializer, CommentSerializer)
5+
BlogSerializer, EntrySerializer, AuthorSerializer, CommentSerializer)
66

77

88
class BlogViewSet(viewsets.ModelViewSet):
@@ -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):
@@ -137,7 +138,18 @@ def to_representation(self, value):
137138
else:
138139
pk = value.pk
139140

140-
return OrderedDict([('type', format_relation_name(get_resource_type_from_instance(value))), ('id', str(pk))])
141+
# check to see if this resource has a different resource_name when
142+
# included and use that name
143+
resource_type = None
144+
root = getattr(self.parent, 'parent', self.parent)
145+
field_name = self.field_name if self.field_name else self.parent.field_name
146+
if getattr(root, 'included_serializers', None) is not None:
147+
includes = get_included_serializers(root)
148+
if field_name in includes.keys():
149+
resource_type = get_resource_type_from_serializer(includes[field_name])
150+
151+
resource_type = resource_type if resource_type else get_resource_type_from_instance(value)
152+
return OrderedDict([('type', resource_type), ('id', str(pk))])
141153

142154
@property
143155
def choices(self):

‎rest_framework_json_api/renderers.py‎

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

282282
if isinstance(field, ListSerializer):
283283
serializer = field.child
284-
model = serializer.Meta.model
285-
relation_type = utils.format_relation_name(model.__name__)
284+
relation_type = utils.get_resource_type_from_serializer(serializer)
286285
relation_queryset = list(relation_instance_or_manager.all())
287286

288287
# Get the serializer fields
@@ -303,15 +302,16 @@ def extract_included(fields, resource, resource_instance, included_resources):
303302
)
304303

305304
if isinstance(field, ModelSerializer):
306-
model=field.Meta.model
307-
relation_type = utils.format_relation_name(model.__name__)
305+
306+
relation_type = utils.get_resource_type_from_serializer(field)
308307

309308
# Get the serializer fields
310309
serializer_fields = utils.get_serializer_fields(field)
311310
if serializer_data:
312311
included_data.append(
313-
JSONRenderer.build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager,
314-
relation_type)
312+
JSONRenderer.build_json_resource_obj(
313+
serializer_fields, serializer_data,
314+
relation_instance_or_manager, relation_type)
315315
)
316316
included_data.extend(
317317
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

@@ -182,7 +182,7 @@ def get_related_resource_type(relation):
182182
relation_model = parent_model_relation.field.related.model
183183
else:
184184
return get_related_resource_type(parent_model_relation)
185-
return format_relation_name(relation_model.__name__)
185+
return get_resource_type_from_model(relation_model)
186186

187187

188188
def get_instance_or_manager_resource_type(resource_instance_or_manager):
@@ -193,25 +193,31 @@ def get_instance_or_manager_resource_type(resource_instance_or_manager):
193193
pass
194194

195195

196+
def get_resource_type_from_model(model):
197+
json_api_meta = getattr(model, 'JSONAPIMeta', None)
198+
return getattr(
199+
json_api_meta,
200+
'resource_name',
201+
format_relation_name(model.__name__))
202+
203+
196204
def get_resource_type_from_queryset(qs):
197-
return format_relation_name(qs.model._meta.model.__name__)
205+
return get_resource_type_from_model(qs.model)
198206

199207

200208
def get_resource_type_from_instance(instance):
201-
return format_relation_name(instance._meta.model.__name__)
209+
return get_resource_type_from_model(instance._meta.model)
202210

203211

204212
def get_resource_type_from_manager(manager):
205-
return format_relation_name(manager.model.__name__)
213+
return get_resource_type_from_model(manager.model)
206214

207215

208216
def get_resource_type_from_serializer(serializer):
209-
try:
210-
# Check the meta class for resource_name
211-
return serializer.Meta.resource_name
212-
except AttributeError:
213-
# Use the serializer model then pluralize and format
214-
return format_relation_name(serializer.Meta.model.__name__)
217+
return getattr(
218+
serializer.Meta,
219+
'resource_name',
220+
get_resource_type_from_model(serializer.Meta.model))
215221

216222

217223
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)

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