Skip to content

Commit 36a60a3

Browse files
authoredApr 23, 2021
Preserve field names when no formatting is configured (#909)
This way it is possible to have field names which do not follow the python underscore convention.
1 parent a25e387 commit 36a60a3

File tree

11 files changed

+179
-57
lines changed

11 files changed

+179
-57
lines changed
 

‎CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ any parts of the framework not mentioned in the documentation should generally b
1818
### Fixed
1919

2020
* Allow `get_serializer_class` to be overwritten when using related urls without defining `serializer_class` fallback
21+
* Preserve field names when no formatting is configured.
22+
23+
### Deprecated
24+
25+
* Deprecated default `format_type` argument of `rest_framework_json_api.utils.format_value`. Use `rest_framework_json_api.utils.format_field_name` or specify specifc `format_type` instead.
26+
* Deprecated `format_type` argument of `rest_framework_json_api.utils.format_link_segment`. Use `format_value` instead.
2127

2228
## [4.1.0] - 2021-03-08
2329

‎rest_framework_json_api/django_filters/backends.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rest_framework.exceptions import ValidationError
55
from rest_framework.settings import api_settings
66

7-
from rest_framework_json_api.utils import format_value
7+
from rest_framework_json_api.utils import undo_format_field_name
88

99

1010
class DjangoFilterBackend(DjangoFilterBackend):
@@ -119,8 +119,7 @@ def get_filterset_kwargs(self, request, queryset, view):
119119
)
120120
# convert jsonapi relationship path to Django ORM's __ notation
121121
key = m.groupdict()["assoc"].replace(".", "__")
122-
# undo JSON_API_FORMAT_FIELD_NAMES conversion:
123-
key = format_value(key, "underscore")
122+
key = undo_format_field_name(key)
124123
data.setlist(key, val)
125124
filter_keys.append(key)
126125
del data[qp]

‎rest_framework_json_api/filters.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from rest_framework.exceptions import ValidationError
44
from rest_framework.filters import BaseFilterBackend, OrderingFilter
55

6-
from rest_framework_json_api.utils import format_value
6+
from rest_framework_json_api.utils import undo_format_field_name
77

88

99
class OrderingFilter(OrderingFilter):
@@ -15,7 +15,7 @@ class OrderingFilter(OrderingFilter):
1515
:py:class:`rest_framework.filters.OrderingFilter` with
1616
:py:attr:`~rest_framework.filters.OrderingFilter.ordering_param` = "sort"
1717
18-
Also applies DJA format_value() to convert (e.g. camelcase) to underscore.
18+
Also supports undo of field name formatting
1919
(See JSON_API_FORMAT_FIELD_NAMES in docs/usage.md)
2020
"""
2121

@@ -38,7 +38,7 @@ def remove_invalid_fields(self, queryset, fields, view, request):
3838
bad_terms = [
3939
term
4040
for term in fields
41-
if format_value(term.replace(".", "__").lstrip("-"), "underscore")
41+
if undo_format_field_name(term.replace(".", "__").lstrip("-"))
4242
not in valid_fields
4343
]
4444
if bad_terms:
@@ -56,10 +56,10 @@ def remove_invalid_fields(self, queryset, fields, view, request):
5656
item_rewritten = item.replace(".", "__")
5757
if item_rewritten.startswith("-"):
5858
underscore_fields.append(
59-
"-" + format_value(item_rewritten.lstrip("-"), "underscore")
59+
"-" + undo_format_field_name(item_rewritten.lstrip("-"))
6060
)
6161
else:
62-
underscore_fields.append(format_value(item_rewritten, "underscore"))
62+
underscore_fields.append(undo_format_field_name(item_rewritten))
6363

6464
return super(OrderingFilter, self).remove_invalid_fields(
6565
queryset, underscore_fields, view, request

‎rest_framework_json_api/metadata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from rest_framework.settings import api_settings
88
from rest_framework.utils.field_mapping import ClassLookupDict
99

10-
from rest_framework_json_api.utils import format_value, get_related_resource_type
10+
from rest_framework_json_api.utils import format_field_name, get_related_resource_type
1111

1212

1313
class JSONAPIMetadata(SimpleMetadata):
@@ -93,7 +93,7 @@ def get_serializer_info(self, serializer):
9393

9494
return OrderedDict(
9595
[
96-
(format_value(field_name), self.get_field_info(field))
96+
(format_field_name(field_name), self.get_field_info(field))
9797
for field_name, field in serializer.fields.items()
9898
]
9999
)

‎rest_framework_json_api/parsers.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from rest_framework import parsers
55
from rest_framework.exceptions import ParseError
66

7-
from . import exceptions, renderers, serializers, utils
8-
from .settings import json_api_settings
7+
from rest_framework_json_api import exceptions, renderers, serializers
8+
from rest_framework_json_api.utils import get_resource_name, undo_format_field_names
99

1010

1111
class JSONParser(parsers.JSONParser):
@@ -37,27 +37,13 @@ class JSONParser(parsers.JSONParser):
3737

3838
@staticmethod
3939
def parse_attributes(data):
40-
attributes = data.get("attributes")
41-
uses_format_translation = json_api_settings.FORMAT_FIELD_NAMES
42-
43-
if not attributes:
44-
return dict()
45-
elif uses_format_translation:
46-
# convert back to python/rest_framework's preferred underscore format
47-
return utils.format_field_names(attributes, "underscore")
48-
else:
49-
return attributes
40+
attributes = data.get("attributes") or dict()
41+
return undo_format_field_names(attributes)
5042

5143
@staticmethod
5244
def parse_relationships(data):
53-
uses_format_translation = json_api_settings.FORMAT_FIELD_NAMES
54-
relationships = data.get("relationships")
55-
56-
if not relationships:
57-
relationships = dict()
58-
elif uses_format_translation:
59-
# convert back to python/rest_framework's preferred underscore format
60-
relationships = utils.format_field_names(relationships, "underscore")
45+
relationships = data.get("relationships") or dict()
46+
relationships = undo_format_field_names(relationships)
6147

6248
# Parse the relationships
6349
parsed_relationships = dict()
@@ -130,7 +116,7 @@ def parse(self, stream, media_type=None, parser_context=None):
130116

131117
# Check for inconsistencies
132118
if request.method in ("PUT", "POST", "PATCH"):
133-
resource_name = utils.get_resource_name(
119+
resource_name = get_resource_name(
134120
parser_context, expand_polymorphic_types=True
135121
)
136122
if isinstance(resource_name, str):

‎rest_framework_json_api/utils.py

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
import inspect
33
import operator
4+
import warnings
45
from collections import OrderedDict
56

67
import inflection
@@ -118,8 +119,76 @@ def format_field_names(obj, format_type=None):
118119
return obj
119120

120121

122+
def undo_format_field_names(obj):
123+
"""
124+
Takes a dict and undo format field names to underscore which is the Python convention
125+
but only in case `JSON_API_FORMAT_FIELD_NAMES` is actually configured.
126+
"""
127+
if json_api_settings.FORMAT_FIELD_NAMES:
128+
return format_field_names(obj, "underscore")
129+
130+
return obj
131+
132+
133+
def format_field_name(field_name):
134+
"""
135+
Takes a field name and returns it with formatted keys as set in
136+
`JSON_API_FORMAT_FIELD_NAMES`
137+
"""
138+
return format_value(field_name, json_api_settings.FORMAT_FIELD_NAMES)
139+
140+
141+
def undo_format_field_name(field_name):
142+
"""
143+
Takes a string and undos format field name to underscore which is the Python convention
144+
but only in case `JSON_API_FORMAT_FIELD_NAMES` is actually configured.
145+
"""
146+
if json_api_settings.FORMAT_FIELD_NAMES:
147+
return format_value(field_name, "underscore")
148+
149+
return field_name
150+
151+
152+
def format_link_segment(value, format_type=None):
153+
"""
154+
Takes a string value and returns it with formatted keys as set in `format_type`
155+
or `JSON_API_FORMAT_RELATED_LINKS`.
156+
157+
:format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore'
158+
"""
159+
if format_type is None:
160+
format_type = json_api_settings.FORMAT_RELATED_LINKS
161+
else:
162+
warnings.warn(
163+
DeprecationWarning(
164+
"Using `format_type` argument is deprecated."
165+
"Use `format_value` instead."
166+
)
167+
)
168+
169+
return format_value(value, format_type)
170+
171+
172+
def undo_format_link_segment(value):
173+
"""
174+
Takes a link segment and undos format link segment to underscore which is the Python convention
175+
but only in case `JSON_API_FORMAT_RELATED_LINKS` is actually configured.
176+
"""
177+
178+
if json_api_settings.FORMAT_RELATED_LINKS:
179+
return format_value(value, "underscore")
180+
181+
return value
182+
183+
121184
def format_value(value, format_type=None):
122185
if format_type is None:
186+
warnings.warn(
187+
DeprecationWarning(
188+
"Using `format_value` without passing on `format_type` argument is deprecated."
189+
"Use `format_field_name` instead."
190+
)
191+
)
123192
format_type = json_api_settings.FORMAT_FIELD_NAMES
124193
if format_type == "dasherize":
125194
# inflection can't dasherize camelCase
@@ -142,25 +211,11 @@ def format_resource_type(value, format_type=None, pluralize=None):
142211
pluralize = json_api_settings.PLURALIZE_TYPES
143212

144213
if format_type:
145-
# format_type will never be None here so we can use format_value
146214
value = format_value(value, format_type)
147215

148216
return inflection.pluralize(value) if pluralize else value
149217

150218

151-
def format_link_segment(value, format_type=None):
152-
"""
153-
Takes a string value and returns it with formatted keys as set in `format_type`
154-
or `JSON_API_FORMAT_RELATED_LINKS`.
155-
156-
:format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore'
157-
"""
158-
if format_type is None:
159-
format_type = json_api_settings.FORMAT_RELATED_LINKS
160-
161-
return format_value(value, format_type)
162-
163-
164219
def get_related_resource_type(relation):
165220
from rest_framework_json_api.serializers import PolymorphicModelSerializer
166221

@@ -348,7 +403,7 @@ def format_drf_errors(response, context, exc):
348403
# handle all errors thrown from serializers
349404
else:
350405
for field, error in response.data.items():
351-
field = format_value(field)
406+
field = format_field_name(field)
352407
pointer = "/data/attributes/{}".format(field)
353408
if isinstance(exc, Http404) and isinstance(error, str):
354409
# 404 errors don't have a pointer

‎rest_framework_json_api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
from rest_framework_json_api.utils import (
2626
Hyperlink,
2727
OrderedDict,
28-
format_value,
2928
get_included_resources,
3029
get_resource_type_from_instance,
30+
undo_format_link_segment,
3131
)
3232

3333

@@ -187,7 +187,7 @@ def get_related_serializer_class(self):
187187

188188
def get_related_field_name(self):
189189
field_name = self.kwargs["related_field"]
190-
return format_value(field_name, "underscore")
190+
return undo_format_link_segment(field_name)
191191

192192
def get_related_instance(self):
193193
parent_obj = self.get_object()

‎tests/test_parsers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def parser_context(self, rf):
2929
@pytest.mark.parametrize(
3030
"format_field_names",
3131
[
32-
None,
32+
False,
3333
"dasherize",
3434
"camelize",
3535
"capitalize",

‎tests/test_relations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def test_to_representation(self, model, field):
233233
@pytest.mark.parametrize(
234234
"format_related_links",
235235
[
236-
None,
236+
False,
237237
"dasherize",
238238
"camelize",
239239
"capitalize",

‎tests/test_utils.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
from rest_framework_json_api import serializers
99
from rest_framework_json_api.utils import (
10+
format_field_name,
1011
format_field_names,
1112
format_link_segment,
1213
format_resource_type,
1314
format_value,
1415
get_included_serializers,
1516
get_related_resource_type,
1617
get_resource_name,
18+
undo_format_field_name,
19+
undo_format_field_names,
20+
undo_format_link_segment,
1721
)
1822
from tests.models import (
1923
BasicModel,
@@ -176,6 +180,7 @@ def test_get_resource_name_with_errors(status_code):
176180
@pytest.mark.parametrize(
177181
"format_type,output",
178182
[
183+
(False, {"full_name": {"last-name": "a", "first-name": "b"}}),
179184
("camelize", {"fullName": {"last-name": "a", "first-name": "b"}}),
180185
("capitalize", {"FullName": {"last-name": "a", "first-name": "b"}}),
181186
("dasherize", {"full-name": {"last-name": "a", "first-name": "b"}}),
@@ -192,22 +197,86 @@ def test_format_field_names(settings, format_type, output):
192197
@pytest.mark.parametrize(
193198
"format_type,output",
194199
[
195-
(None, "first_Name"),
200+
(False, {"fullName": "Test Name"}),
201+
("camelize", {"full_name": "Test Name"}),
202+
],
203+
)
204+
def test_undo_format_field_names(settings, format_type, output):
205+
settings.JSON_API_FORMAT_FIELD_NAMES = format_type
206+
207+
value = {"fullName": "Test Name"}
208+
assert undo_format_field_names(value) == output
209+
210+
211+
@pytest.mark.parametrize(
212+
"format_type,output",
213+
[
214+
(False, "full_name"),
215+
("camelize", "fullName"),
216+
("capitalize", "FullName"),
217+
("dasherize", "full-name"),
218+
("underscore", "full_name"),
219+
],
220+
)
221+
def test_format_field_name(settings, format_type, output):
222+
settings.JSON_API_FORMAT_FIELD_NAMES = format_type
223+
224+
field_name = "full_name"
225+
assert format_field_name(field_name) == output
226+
227+
228+
@pytest.mark.parametrize(
229+
"format_type,output",
230+
[
231+
(False, "fullName"),
232+
("camelize", "full_name"),
233+
],
234+
)
235+
def test_undo_format_field_name(settings, format_type, output):
236+
settings.JSON_API_FORMAT_FIELD_NAMES = format_type
237+
238+
field_name = "fullName"
239+
assert undo_format_field_name(field_name) == output
240+
241+
242+
@pytest.mark.parametrize(
243+
"format_type,output",
244+
[
245+
(False, "first_Name"),
196246
("camelize", "firstName"),
197247
("capitalize", "FirstName"),
198248
("dasherize", "first-name"),
199249
("underscore", "first_name"),
200250
],
201251
)
202-
def test_format_field_segment(settings, format_type, output):
252+
def test_format_link_segment(settings, format_type, output):
203253
settings.JSON_API_FORMAT_RELATED_LINKS = format_type
204254
assert format_link_segment("first_Name") == output
205255

206256

257+
def test_format_link_segment_deprecates_format_type_argument():
258+
with pytest.deprecated_call():
259+
assert "first-name" == format_link_segment("first_name", "dasherize")
260+
261+
262+
@pytest.mark.parametrize(
263+
"format_links,output",
264+
[
265+
(False, "fullName"),
266+
("camelize", "full_name"),
267+
],
268+
)
269+
def test_undo_format_link_segment(settings, format_links, output):
270+
settings.JSON_API_FORMAT_RELATED_LINKS = format_links
271+
272+
link_segment = "fullName"
273+
assert undo_format_link_segment(link_segment) == output
274+
275+
207276
@pytest.mark.parametrize(
208277
"format_type,output",
209278
[
210-
(None, "first_name"),
279+
(False, "first_name"),
211280
("camelize", "firstName"),
212281
("capitalize", "FirstName"),
213282
("dasherize", "first-name"),
@@ -218,6 +287,11 @@ def test_format_value(settings, format_type, output):
218287
assert format_value("first_name", format_type) == output
219288

220289

290+
def test_format_value_deprecates_default_format_type_argument():
291+
with pytest.deprecated_call():
292+
assert "first_name" == format_value("first_name")
293+
294+
221295
@pytest.mark.parametrize(
222296
"resource_type,pluralize,output",
223297
[

‎tests/test_views.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rest_framework_json_api.parsers import JSONParser
99
from rest_framework_json_api.relations import ResourceRelatedField
1010
from rest_framework_json_api.renderers import JSONRenderer
11-
from rest_framework_json_api.utils import format_value
11+
from rest_framework_json_api.utils import format_link_segment
1212
from rest_framework_json_api.views import ModelViewSet
1313
from tests.models import BasicModel
1414

@@ -17,16 +17,18 @@ class TestModelViewSet:
1717
@pytest.mark.parametrize(
1818
"format_links",
1919
[
20-
None,
20+
False,
2121
"dasherize",
2222
"camelize",
2323
"capitalize",
2424
"underscore",
2525
],
2626
)
2727
def test_get_related_field_name_handles_formatted_link_segments(
28-
self, format_links, rf
28+
self, settings, format_links, rf
2929
):
30+
settings.JSON_API_FORMAT_RELATED_LINKS = format_links
31+
3032
# use field name which actually gets formatted
3133
related_model_field_name = "related_field_model"
3234

@@ -43,7 +45,7 @@ class Meta:
4345
class RelatedFieldNameView(ModelViewSet):
4446
serializer_class = RelatedFieldNameSerializer
4547

46-
url_segment = format_value(related_model_field_name, format_links)
48+
url_segment = format_link_segment(related_model_field_name)
4749

4850
request = rf.get(f"/basic_models/1/{url_segment}")
4951

0 commit comments

Comments
 (0)
Please sign in to comment.