Skip to content

Commit 6131764

Browse files
author
Oliver Sauder
committed
Preserve values from being formatted
Introduce `JSON_API_FORMAT_FIELD_NAMES`. When set preserves keys of values. `JSON_API_FORMAT_KEYS` still exists and will still format all keys recursively.
1 parent e501b04 commit 6131764

File tree

5 files changed

+94
-17
lines changed

5 files changed

+94
-17
lines changed

example/tests/test_parsers.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from io import BytesIO
33

4-
from django.test import TestCase
4+
from django.test import TestCase, override_settings
55
from rest_framework.exceptions import ParseError
66

77
from rest_framework_json_api.parsers import JSONParser
@@ -22,7 +22,10 @@ def __init__(self):
2222
data = {
2323
'data': {
2424
'id': 123,
25-
'type': 'Blog'
25+
'type': 'Blog',
26+
'attributes': {
27+
'json-value': {'JsonKey': 'JsonValue'}
28+
},
2629
},
2730
'meta': {
2831
'random_key': 'random_value'
@@ -31,13 +34,24 @@ def __init__(self):
3134

3235
self.string = json.dumps(data)
3336

34-
def test_parse_include_metadata(self):
37+
def test_parse_include_metadata_format_keys(self):
3538
parser = JSONParser()
3639

3740
stream = BytesIO(self.string.encode('utf-8'))
3841
data = parser.parse(stream, None, self.parser_context)
3942

4043
self.assertEqual(data['_meta'], {'random_key': 'random_value'})
44+
self.assertEqual(data['json_value'], {'json_key': 'JsonValue'})
45+
46+
@override_settings(JSON_API_FORMAT_KEYS=False, JSON_API_FORMAT_FIELD_NAMES='dasherize')
47+
def test_parse_include_metadata_format_field_names(self):
48+
parser = JSONParser()
49+
50+
stream = BytesIO(self.string.encode('utf-8'))
51+
data = parser.parse(stream, None, self.parser_context)
52+
53+
self.assertEqual(data['_meta'], {'random_key': 'random_value'})
54+
self.assertEqual(data['json_value'], {'JsonKey': 'JsonValue'})
4155

4256
def test_parse_invalid_data(self):
4357
parser = JSONParser()

example/tests/unit/test_renderers.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
from rest_framework_json_api import serializers, views
24
from rest_framework_json_api.renderers import JSONRenderer
35

@@ -19,9 +21,14 @@ class DummyTestSerializer(serializers.ModelSerializer):
1921
related_models = RelatedModelSerializer(
2022
source='comments', many=True, read_only=True)
2123

24+
json_field = serializers.SerializerMethodField()
25+
26+
def get_json_field(self, entry):
27+
return {'JsonKey': 'JsonValue'}
28+
2229
class Meta:
2330
model = Entry
24-
fields = ('related_models',)
31+
fields = ('related_models', 'json_field')
2532

2633
class JSONAPIMeta:
2734
included_resources = ('related_models',)
@@ -44,3 +51,18 @@ def test_simple_reverse_relation_included_renderer():
4451
renderer_context={'view': DummyTestViewSet()})
4552

4653
assert rendered
54+
55+
56+
def test_render_format_field_names_only(settings):
57+
"""Test that json field is kept untouched."""
58+
delattr(settings, 'JSON_API_FORMAT_KEYS')
59+
settings.JSON_API_FORMAT_FIELD_NAMES = 'dasherize'
60+
61+
serializer = DummyTestSerializer(instance=Entry())
62+
renderer = JSONRenderer()
63+
rendered = renderer.render(
64+
serializer.data,
65+
renderer_context={'view': DummyTestViewSet()})
66+
67+
result = json.loads(rendered)
68+
assert result['data']['attributes']['json-field'] == {'JsonKey': 'JsonValue'}

example/tests/unit/test_utils.py

+13
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ def test_format_keys():
8484
assert utils.format_keys([underscored], 'dasherize') == output
8585

8686

87+
@pytest.mark.parametrize("format_type,output", [
88+
('camelize', {'fullName': {'last-name': 'a', 'first-name': 'b'}}),
89+
('capitalize', {'FullName': {'last-name': 'a', 'first-name': 'b'}}),
90+
('dasherize', {'full-name': {'last-name': 'a', 'first-name': 'b'}}),
91+
('underscore', {'full_name': {'last-name': 'a', 'first-name': 'b'}}),
92+
])
93+
def test_format_field_names(settings, format_type, output):
94+
settings.JSON_API_FORMAT_FIELD_NAMES = format_type
95+
96+
value = {'full_name': {'last-name': 'a', 'first-name': 'b'}}
97+
assert utils.format_keys(value, format_type, False) == output
98+
99+
87100
def test_format_value():
88101
assert utils.format_value('first_name', 'camelize') == 'firstName'
89102
assert utils.format_value('first_name', 'capitalize') == 'FirstName'

rest_framework_json_api/parsers.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@ class JSONParser(parsers.JSONParser):
2929
media_type = 'application/vnd.api+json'
3030
renderer_class = renderers.JSONRenderer
3131

32+
@staticmethod
33+
def _uses_format_translation():
34+
return getattr(
35+
settings,
36+
'JSON_API_FORMAT_FIELD_NAMES',
37+
getattr(settings, 'JSON_API_FORMAT_KEYS', False)
38+
)
39+
3240
@staticmethod
3341
def parse_attributes(data):
3442
attributes = data.get('attributes')
35-
uses_format_translation = getattr(settings, 'JSON_API_FORMAT_KEYS', False)
43+
uses_format_translation = JSONParser._uses_format_translation()
3644

3745
if not attributes:
3846
return dict()
@@ -44,7 +52,7 @@ def parse_attributes(data):
4452

4553
@staticmethod
4654
def parse_relationships(data):
47-
uses_format_translation = getattr(settings, 'JSON_API_FORMAT_KEYS', False)
55+
uses_format_translation = JSONParser._uses_format_translation()
4856
relationships = data.get('relationships')
4957

5058
if not relationships:

rest_framework_json_api/utils.py

+31-11
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,31 @@ def get_serializer_fields(serializer):
9696
return fields
9797

9898

99-
def format_keys(obj, format_type=None):
99+
def format_keys(obj, format_type=None, recursive=None):
100100
"""
101-
Takes either a dict or list and returns it with camelized keys only if
102-
JSON_API_FORMAT_KEYS is set.
101+
Takes either a dict or list and formats keys as configured in
102+
`JSON_API_FORMAT_KEYS` if set. `JSON_API_FIELD_NAMES` takes precedence
103+
over `JSON_API_FORMAT_KEYS` but only formats field names preserving
104+
values.
103105
104-
:format_type: Either 'dasherize', 'camelize' or 'underscore'
106+
:format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore'
107+
:recursive: If set all keys will be formatted; otherwise only keys of given object
105108
"""
106109
if format_type is None:
107-
format_type = getattr(settings, 'JSON_API_FORMAT_KEYS', False)
110+
format_type_field_names = getattr(settings, 'JSON_API_FORMAT_FIELD_NAMES', False)
111+
format_type_format_keys = getattr(settings, 'JSON_API_FORMAT_KEYS', False)
112+
113+
if format_type_field_names and format_type_format_keys:
114+
raise RuntimeError(
115+
'Setting `JSON_API_FORMAT_FIELD_NAMES` and `JSON_API_FORMAT_KEYS` '
116+
'may not both be set.'
117+
)
118+
119+
format_type = format_type_field_names or format_type_format_keys
120+
121+
# only format keys of values when `FORMAT_KEYS` is set
122+
if recursive is None:
123+
recursive = bool(getattr(settings, 'JSON_API_FORMAT_KEYS', False))
108124

109125
if format_type in ('dasherize', 'camelize', 'underscore', 'capitalize'):
110126

@@ -115,18 +131,18 @@ def format_keys(obj, format_type=None):
115131
# inflection can't dasherize camelCase
116132
key = inflection.underscore(key)
117133
formatted[inflection.dasherize(key)] \
118-
= format_keys(value, format_type)
134+
= recursive and format_keys(value, format_type) or value
119135
elif format_type == 'camelize':
120136
formatted[inflection.camelize(key, False)] \
121-
= format_keys(value, format_type)
137+
= recursive and format_keys(value, format_type) or value
122138
elif format_type == 'capitalize':
123139
formatted[inflection.camelize(key)] \
124-
= format_keys(value, format_type)
140+
= recursive and format_keys(value, format_type) or value
125141
elif format_type == 'underscore':
126142
formatted[inflection.underscore(key)] \
127-
= format_keys(value, format_type)
143+
= recursive and format_keys(value, format_type) or value
128144
return formatted
129-
if isinstance(obj, list):
145+
if isinstance(obj, list) and recursive:
130146
return [format_keys(item, format_type) for item in obj]
131147
else:
132148
return obj
@@ -136,7 +152,11 @@ def format_keys(obj, format_type=None):
136152

137153
def format_value(value, format_type=None):
138154
if format_type is None:
139-
format_type = getattr(settings, 'JSON_API_FORMAT_KEYS', False)
155+
format_type = getattr(
156+
settings,
157+
'JSON_API_FORMAT_FIELD_NAMES',
158+
getattr(settings, 'JSON_API_FORMAT_KEYS', False)
159+
)
140160
if format_type == 'dasherize':
141161
# inflection can't dasherize camelCase
142162
value = inflection.underscore(value)

0 commit comments

Comments
 (0)