Skip to content

Commit 576a9c6

Browse files
committed
Merge pull request #15 from erichonkanen/master
Rest Framework <-> Ember Data key format conversion
2 parents 3895a0d + cb79765 commit 576a9c6

9 files changed

+263
-81
lines changed

README.rst

+55-2
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ override ``settings.REST_FRAMEWORK``::
116116
),
117117
}
118118

119-
120-
121119
If ``PAGINATE_BY`` is set the renderer will return a ``meta`` object with
122120
record count and the next and previous links. Django Rest Framework looks
123121
for the ``page`` GET parameter by default allowing you to make requests for
@@ -141,6 +139,61 @@ the ``resource_name`` property is required on the class::
141139
permission_classes = (permissions.IsAuthenticated, )
142140

143141

142+
Ember Data <-> Rest Framework Format Conversion
143+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
144+
*(camelization/underscore/pluralize)*
145+
146+
This package includes the optional ability to automatically convert json requests
147+
and responses from the Ember Data camelCase to python/rest_framework's preferred
148+
underscore. Additionally resource names can be pluralized if more than one object
149+
is included in a serialized response as Ember Data expects. To hook this up,
150+
include the following in your project settings::
151+
152+
REST_EMBER_FORMAT_KEYS = True
153+
REST_EMBER_PLURALIZE_KEYS = True
154+
155+
156+
Example - Without format conversion::
157+
158+
{
159+
"identity": [
160+
{
161+
"id": 1,
162+
"username": "john",
163+
"first_name": "John",
164+
"last_name": "Coltrane"
165+
},
166+
{
167+
"id": 2,
168+
"username": "frodo",
169+
"first_name": "Bilbo",
170+
"last_name": "Baggins"
171+
},
172+
],
173+
...
174+
}
175+
176+
Example - With format conversion::
177+
178+
{
179+
"identities": [
180+
{
181+
"id": 1,
182+
"username": "john",
183+
"firstName": "John",
184+
"lastName": "Coltrane"
185+
},
186+
{
187+
"id": 2,
188+
"username": "frodo",
189+
"firstName": "Bilbo",
190+
"lastName": "Baggins"
191+
},
192+
],
193+
...
194+
}
195+
196+
144197
Managing the trailing slash
145198
^^^^^^^^^^^^^^^^^^^^^^^^^^^
146199

example/tests/test_format_keys.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import json
2+
3+
from example.tests import TestBase
4+
5+
from django.contrib.auth import get_user_model
6+
from django.core.urlresolvers import reverse, reverse_lazy
7+
from django.conf import settings
8+
9+
10+
class FormatKeysSetTests(TestBase):
11+
"""
12+
Test that camelization and underscoring of key names works if they are activated.
13+
"""
14+
list_url = reverse_lazy('user-list')
15+
16+
def setUp(self):
17+
super(FormatKeysSetTests, self).setUp()
18+
self.detail_url = reverse('user-detail', kwargs={'pk': self.miles.pk})
19+
20+
# Set the format keys settings.
21+
setattr(settings, 'REST_EMBER_FORMAT_KEYS', True)
22+
setattr(settings, 'REST_EMBER_PLURALIZE_KEYS', True)
23+
24+
def tearDown(self):
25+
# Remove the format keys settings.
26+
delattr(settings, 'REST_EMBER_FORMAT_KEYS')
27+
delattr(settings, 'REST_EMBER_PLURALIZE_KEYS')
28+
29+
30+
def test_camelization(self):
31+
"""
32+
Test that camelization works.
33+
"""
34+
response = self.client.get(self.list_url)
35+
self.assertEqual(response.status_code, 200)
36+
37+
user = get_user_model().objects.all()[0]
38+
expected = {
39+
u'user': [{
40+
u'id': user.pk,
41+
u'firstName': user.first_name,
42+
u'lastName': user.last_name,
43+
u'email': user.email
44+
}]
45+
}
46+
47+
json_content = json.loads(response.content)
48+
meta = json_content.get('meta')
49+
50+
self.assertEquals(expected.get('user'), json_content.get('user'))
51+
self.assertEqual('http://testserver/user-viewset/?page=2', meta.get('nextLink'))
52+
53+
def test_pluralization(self):
54+
"""
55+
Test that the key name is pluralized.
56+
"""
57+
response = self.client.get(self.list_url, {'page_size': 2})
58+
self.assertEqual(response.status_code, 200)
59+
60+
users = get_user_model().objects.all()
61+
expected = {
62+
u'users': [{
63+
u'id': users[0].pk,
64+
u'firstName': users[0].first_name,
65+
u'lastName': users[0].last_name,
66+
u'email': users[0].email
67+
},{
68+
u'id': users[1].pk,
69+
u'firstName': users[1].first_name,
70+
u'lastName': users[1].last_name,
71+
u'email': users[1].email
72+
}]
73+
}
74+
75+
json_content = json.loads(response.content)
76+
self.assertEquals(expected.get('users'), json_content.get('users'))

example/tests/test_generic_viewset.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import json
32
from example.tests import TestBase
43
from django.core.urlresolvers import reverse
@@ -49,3 +48,4 @@ def test_ember_expected_renderer(self):
4948
}
5049
)
5150

51+

example/tests/test_model_viewsets.py

+62-56
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
2-
31
import json
2+
43
from example.tests import TestBase
4+
55
from django.contrib.auth import get_user_model
66
from django.core.urlresolvers import reverse, reverse_lazy
77
from django.conf import settings
88

99

1010
class ModelViewSetTests(TestBase):
1111
"""
12-
Test usage with ModelViewSets
12+
Test usage with ModelViewSets, also tests pluralization, camelization,
13+
and underscore.
1314
14-
[<RegexURLPattern user-list ^user-viewsets/$>, <RegexURLPattern user-detail ^user-viewsets/(?P<pk>[^/]+)/$>]
15+
[<RegexURLPattern user-list ^user-viewsets/$>,
16+
<RegexURLPattern user-detail ^user-viewsets/(?P<pk>[^/]+)/$>]
1517
"""
1618
list_url = reverse_lazy('user-list')
1719

@@ -21,29 +23,31 @@ def setUp(self):
2123

2224
def test_key_in_list_result(self):
2325
"""
24-
Ensure the result has a "user" key since that is the name of the model
26+
Ensure the result has a 'user' key since that is the name of the model
2527
"""
2628
response = self.client.get(self.list_url)
2729
self.assertEqual(response.status_code, 200)
2830

2931
user = get_user_model().objects.all()[0]
30-
expected = {"user": [{
31-
'id': user.pk,
32-
'first_name': user.first_name,
33-
'last_name': user.last_name,
34-
'email': user.email
35-
}]}
32+
expected = {
33+
u'user': [{
34+
u'id': user.pk,
35+
u'first_name': user.first_name,
36+
u'last_name': user.last_name,
37+
u'email': user.email
38+
}]
39+
}
3640

3741
json_content = json.loads(response.content)
38-
meta = json_content.get("meta")
42+
meta = json_content.get('meta')
3943

4044
self.assertEquals(expected.get('user'), json_content.get('user'))
4145
self.assertEquals(meta.get('count', 0),
4246
get_user_model().objects.count())
43-
self.assertEquals(meta.get("next"), 2)
47+
self.assertEquals(meta.get('next'), 2)
4448
self.assertEqual('http://testserver/user-viewset/?page=2',
45-
meta.get("next_link"))
46-
self.assertEqual(meta.get("page"), 1)
49+
meta.get('next_link'))
50+
self.assertEqual(meta.get('page'), 1)
4751

4852
def test_page_two_in_list_result(self):
4953
"""
@@ -53,72 +57,75 @@ def test_page_two_in_list_result(self):
5357
self.assertEqual(response.status_code, 200)
5458

5559
user = get_user_model().objects.all()[1]
56-
expected = {"user": [{
57-
'id': user.pk,
58-
'first_name': user.first_name,
59-
'last_name': user.last_name,
60-
'email': user.email
61-
}]}
60+
expected = {
61+
u'user': [{
62+
u'id': user.pk,
63+
u'first_name': user.first_name,
64+
u'last_name': user.last_name,
65+
u'email': user.email
66+
}]
67+
}
6268

6369
json_content = json.loads(response.content)
64-
meta = json_content.get("meta")
70+
meta = json_content.get('meta')
6571

6672
self.assertEquals(expected.get('user'), json_content.get('user'))
6773
self.assertEquals(meta.get('count', 0),
6874
get_user_model().objects.count())
69-
self.assertIsNone(meta.get("next"))
70-
self.assertIsNone(meta.get("next_link"))
71-
self.assertEqual(meta.get("previous"), 1)
75+
self.assertIsNone(meta.get('next'))
76+
self.assertIsNone(meta.get('next_link'))
77+
self.assertEqual(meta.get('previous'), 1)
7278
self.assertEqual('http://testserver/user-viewset/?page=1',
73-
meta.get("previous_link"))
74-
self.assertEqual(meta.get("page"), 2)
79+
meta.get('previous_link'))
80+
self.assertEqual(meta.get('page'), 2)
7581

7682
def test_page_range_in_list_result(self):
7783
"""
78-
Ensure that the range of a page can be changed from the client.
84+
Ensure that the range of a page can be changed from the client,
85+
tests pluralization as two objects means it converts ``user`` to
86+
``users``.
7987
"""
8088
response = self.client.get(self.list_url, {'page_size': 2})
8189
self.assertEqual(response.status_code, 200)
8290

8391
users = get_user_model().objects.all()
84-
expected = {"user": [
85-
{
86-
'id': users[0].pk,
87-
'first_name': users[0].first_name,
88-
'last_name': users[0].last_name,
89-
'email': users[0].email
90-
},
91-
{
92-
'id': users[1].pk,
93-
'first_name': users[1].first_name,
94-
'last_name': users[1].last_name,
95-
'email': users[1].email
96-
}]}
92+
expected = {
93+
u'users': [{
94+
u'id': users[0].pk,
95+
u'first_name': users[0].first_name,
96+
u'last_name': users[0].last_name,
97+
u'email': users[0].email
98+
},{
99+
u'id': users[1].pk,
100+
u'first_name': users[1].first_name,
101+
u'last_name': users[1].last_name,
102+
u'email': users[1].email
103+
}]
104+
}
97105

98106
json_content = json.loads(response.content)
99-
meta = json_content.get("meta")
100-
self.assertEquals(expected.get('user'), json_content.get('user'))
107+
meta = json_content.get('meta')
108+
self.assertEquals(expected.get('users'), json_content.get('user'))
101109
self.assertEquals(meta.get('count', 0),
102110
get_user_model().objects.count())
103111

104112

105113
def test_key_in_detail_result(self):
106114
"""
107-
Ensure the result has a "user" key.
115+
Ensure the result has a 'user' key.
108116
"""
109117
response = self.client.get(self.detail_url)
110118
self.assertEqual(response.status_code, 200)
111119

112120
result = json.loads(response.content)
113121
expected = {
114-
'user': {
115-
'id': self.miles.pk,
116-
'first_name': self.miles.first_name,
117-
'last_name': self.miles.last_name,
118-
'email': self.miles.email
122+
u'user': {
123+
u'id': self.miles.pk,
124+
u'first_name': self.miles.first_name,
125+
u'last_name': self.miles.last_name,
126+
u'email': self.miles.email
119127
}
120128
}
121-
122129
self.assertEqual(result, expected)
123130

124131
def test_key_in_post(self):
@@ -127,11 +134,11 @@ def test_key_in_post(self):
127134
"""
128135
self.client.login(username='miles', password='pw')
129136
data = {
130-
'user': {
131-
'id': self.miles.pk,
132-
'first_name': self.miles.first_name,
133-
'last_name': self.miles.last_name,
134-
'email': '[email protected]'
137+
u'user': {
138+
u'id': self.miles.pk,
139+
u'first_name': self.miles.first_name,
140+
u'last_name': self.miles.last_name,
141+
u'email': '[email protected]'
135142
}
136143
}
137144
response = self.client.put(self.detail_url, data=data, format='json')
@@ -145,4 +152,3 @@ def test_key_in_post(self):
145152
self.assertEqual(
146153
get_user_model().objects.get(pk=self.miles.pk).email,
147154
148-

example/tests/test_multiple_id_mixin.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
import json
42
from example.tests import TestBase
53
from django.contrib.auth import get_user_model
@@ -71,3 +69,4 @@ def test_multiple_ids_in_query_params(self):
7169
meta.get("next_link"))
7270
self.assertEqual(meta.get("page"), 1)
7371

72+

rest_framework_ember/parsers.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
Parsers
33
"""
44
from rest_framework.parsers import JSONParser
5-
65
from rest_framework_ember.utils import get_resource_name
76

7+
from .utils import format_keys
8+
89

910
class EmberJSONParser(JSONParser):
1011
"""
@@ -21,14 +22,11 @@ class EmberJSONParser(JSONParser):
2122
So we can work with the grain on both Ember and RestFramework,
2223
Do some tweaks to the payload so DRF gets what it expects.
2324
"""
24-
2525
def parse(self, stream, media_type=None, parser_context=None):
2626
"""
2727
Parses the incoming bytestream as JSON and returns the resulting data
2828
"""
29-
result = super(EmberJSONParser, self).parse(
30-
stream, media_type=None, parser_context=None)
31-
32-
resource_name = get_resource_name(parser_context.get('view', None))
33-
return result.get(resource_name)
34-
29+
result = super(EmberJSONParser, self).parse(stream, media_type=None,
30+
parser_context=None)
31+
resource = result.get(get_resource_name(parser_context.get('view', None)))
32+
return format_keys(resource, 'underscore')

0 commit comments

Comments
 (0)