Skip to content

Preserve values from being formatted #420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
v2.5.0 - [unreleased]
* Add new pagination classes based on JSON:API query parameter *recommendations*:
* JsonApiPageNumberPagination and JsonApiLimitOffsetPagination. See [usage docs](docs/usage.md#pagination).
* Deprecates PageNumberPagination and LimitOffsetPagination.
* Add ReadOnlyModelViewSet extension with prefetch mixins.
* `JsonApiPageNumberPagination` and `JsonApiLimitOffsetPagination`. See [usage docs](docs/usage.md#pagination).
* Deprecates `PageNumberPagination` and `LimitOffsetPagination`.
* Add `ReadOnlyModelViewSet` extension with prefetch mixins.
* Add support for Django REST Framework 3.8.x
* Introduce `JSON_API_FORMAT_FIELD_NAMES` option replacing `JSON_API_FORMAT_KEYS` but in comparision preserving
values from being formatted as attributes can contain any [json value](http://jsonapi.org/format/#document-resource-object-attributes).
* `JSON_API_FORMAT_KEYS` still works as before (formating all json value keys also nested) but is marked as deprecated.

v2.4.0 - Released January 25, 2018

Expand Down
6 changes: 3 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ multiple endpoints. Setting the `resource_name` on views may result in a differe

### Inflecting object and relation keys

This package includes the ability (off by default) to automatically convert json
requests and responses from the python/rest_framework's preferred underscore to
This package includes the ability (off by default) to automatically convert [json
api field names](http://jsonapi.org/format/#document-resource-object-fields) of requests and responses from the python/rest_framework's preferred underscore to
a format of your choice. To hook this up include the following setting in your
project settings:

``` python
JSON_API_FORMAT_KEYS = 'dasherize'
JSON_API_FORMAT_FIELD_NAMES = 'dasherize'
```

Possible values:
Expand Down
2 changes: 1 addition & 1 deletion example/api/resources/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def posts(self, request):
encoding.force_text('identities'): IdentitySerializer(identities, many=True).data,
encoding.force_text('posts'): PostSerializer(posts, many=True).data,
}
return Response(utils.format_keys(data, format_type='camelize'))
return Response(utils.format_field_names(data, format_type='camelize'))

@detail_route()
def manual_resource_name(self, request, *args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion example/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

INTERNAL_IPS = ('127.0.0.1', )

JSON_API_FORMAT_KEYS = 'camelize'
JSON_API_FORMAT_FIELD_NAMES = 'camelize'
JSON_API_FORMAT_TYPES = 'camelize'
REST_FRAMEWORK = {
'PAGE_SIZE': 5,
Expand Down
2 changes: 1 addition & 1 deletion example/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

ROOT_URLCONF = 'example.urls_test'

JSON_API_FORMAT_KEYS = 'camelize'
JSON_API_FIELD_NAMES = 'camelize'
JSON_API_FORMAT_TYPES = 'camelize'
JSON_API_PLURALIZE_TYPES = True
REST_FRAMEWORK.update({
Expand Down
2 changes: 1 addition & 1 deletion example/tests/test_generic_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from example.tests import TestBase


@override_settings(JSON_API_FORMAT_KEYS='dasherize')
@override_settings(JSON_API_FORMAT_FIELD_NAMES='dasherize')
class GenericViewSet(TestBase):
"""
Test expected responses coming from a Generic ViewSet
Expand Down
2 changes: 1 addition & 1 deletion example/tests/test_model_viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from example.tests import TestBase


@override_settings(JSON_API_FORMAT_KEYS='dasherize')
@override_settings(JSON_API_FORMAT_FIELD_NAMES='dasherize')
class ModelViewSetTests(TestBase):
"""
Test usage with ModelViewSets, also tests pluralization, camelization,
Expand Down
21 changes: 18 additions & 3 deletions example/tests/test_parsers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from io import BytesIO

from django.test import TestCase
from django.test import TestCase, override_settings
from rest_framework.exceptions import ParseError

from rest_framework_json_api.parsers import JSONParser
Expand All @@ -22,7 +22,10 @@ def __init__(self):
data = {
'data': {
'id': 123,
'type': 'Blog'
'type': 'Blog',
'attributes': {
'json-value': {'JsonKey': 'JsonValue'}
},
},
'meta': {
'random_key': 'random_value'
Expand All @@ -31,13 +34,25 @@ def __init__(self):

self.string = json.dumps(data)

def test_parse_include_metadata(self):
@override_settings(JSON_API_FORMAT_KEYS='camelize')
def test_parse_include_metadata_format_keys(self):
parser = JSONParser()

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

self.assertEqual(data['_meta'], {'random_key': 'random_value'})
self.assertEqual(data['json_value'], {'json_key': 'JsonValue'})

@override_settings(JSON_API_FORMAT_FIELD_NAMES='dasherize')
def test_parse_include_metadata_format_field_names(self):
parser = JSONParser()

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

self.assertEqual(data['_meta'], {'random_key': 'random_value'})
self.assertEqual(data['json_value'], {'JsonKey': 'JsonValue'})

def test_parse_invalid_data(self):
parser = JSONParser()
Expand Down
28 changes: 27 additions & 1 deletion example/tests/unit/test_renderers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from rest_framework_json_api import serializers, views
from rest_framework_json_api.renderers import JSONRenderer

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

json_field = serializers.SerializerMethodField()

def get_json_field(self, entry):
return {'JsonKey': 'JsonValue'}

class Meta:
model = Entry
fields = ('related_models',)
fields = ('related_models', 'json_field')

class JSONAPIMeta:
included_resources = ('related_models',)
Expand Down Expand Up @@ -61,3 +68,22 @@ def test_simple_reverse_relation_included_read_only_viewset():
ReadOnlyDummyTestViewSet)

assert rendered


def test_render_format_field_names(settings):
"""Test that json field is kept untouched."""
settings.JSON_API_FORMAT_FIELD_NAMES = 'dasherize'
rendered = render_dummy_test_serialized_view(DummyTestViewSet)

result = json.loads(rendered.decode())
assert result['data']['attributes']['json-field'] == {'JsonKey': 'JsonValue'}


def test_render_format_keys(settings):
"""Test that json field value keys are formated."""
delattr(settings, 'JSON_API_FORMAT_FILED_NAMES')
settings.JSON_API_FORMAT_KEYS = 'dasherize'
rendered = render_dummy_test_serialized_view(DummyTestViewSet)

result = json.loads(rendered.decode())
assert result['data']['attributes']['json-field'] == {'json-key': 'JsonValue'}
4 changes: 2 additions & 2 deletions example/tests/unit/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ def test_settings_default():


def test_settings_override(settings):
settings.JSON_API_FORMAT_KEYS = 'dasherize'
assert json_api_settings.FORMAT_KEYS == 'dasherize'
settings.JSON_API_FORMAT_FIELD_NAMES = 'dasherize'
assert json_api_settings.FORMAT_FIELD_NAMES == 'dasherize'
16 changes: 15 additions & 1 deletion example/tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def test_format_keys():
}

output = {'firstName': 'a', 'lastName': 'b'}
assert utils.format_keys(underscored, 'camelize') == output
result = pytest.deprecated_call(utils.format_keys, underscored, 'camelize')
assert result == output

output = {'FirstName': 'a', 'LastName': 'b'}
assert utils.format_keys(underscored, 'capitalize') == output
Expand All @@ -84,6 +85,19 @@ def test_format_keys():
assert utils.format_keys([underscored], 'dasherize') == output


@pytest.mark.parametrize("format_type,output", [
('camelize', {'fullName': {'last-name': 'a', 'first-name': 'b'}}),
('capitalize', {'FullName': {'last-name': 'a', 'first-name': 'b'}}),
('dasherize', {'full-name': {'last-name': 'a', 'first-name': 'b'}}),
('underscore', {'full_name': {'last-name': 'a', 'first-name': 'b'}}),
])
def test_format_field_names(settings, format_type, output):
settings.JSON_API_FORMAT_FIELD_NAMES = format_type

value = {'full_name': {'last-name': 'a', 'first-name': 'b'}}
assert utils.format_field_names(value, format_type) == output


def test_format_value():
assert utils.format_value('first_name', 'camelize') == 'firstName'
assert utils.format_value('first_name', 'capitalize') == 'FirstName'
Expand Down
8 changes: 4 additions & 4 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,26 @@ class JSONParser(parsers.JSONParser):
@staticmethod
def parse_attributes(data):
attributes = data.get('attributes')
uses_format_translation = json_api_settings.FORMAT_KEYS
uses_format_translation = json_api_settings.format_type

if not attributes:
return dict()
elif uses_format_translation:
# convert back to python/rest_framework's preferred underscore format
return utils.format_keys(attributes, 'underscore')
return utils._format_object(attributes, 'underscore')
else:
return attributes

@staticmethod
def parse_relationships(data):
uses_format_translation = json_api_settings.FORMAT_KEYS
uses_format_translation = json_api_settings.format_type
relationships = data.get('relationships')

if not relationships:
relationships = dict()
elif uses_format_translation:
# convert back to python/rest_framework's preferred underscore format
relationships = utils.format_keys(relationships, 'underscore')
relationships = utils._format_object(relationships, 'underscore')

# Parse the relationships
parsed_relationships = dict()
Expand Down
16 changes: 9 additions & 7 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def extract_attributes(cls, fields, resource):
field_name: resource.get(field_name)
})

return utils.format_keys(data)
return utils._format_object(data)

@classmethod
def extract_relationships(cls, fields, resource, resource_instance):
Expand Down Expand Up @@ -281,7 +281,7 @@ def extract_relationships(cls, fields, resource, resource_instance):
})
continue

return utils.format_keys(data)
return utils._format_object(data)

@classmethod
def extract_relation_instance(cls, field_name, field, resource_instance, serializer):
Expand Down Expand Up @@ -405,7 +405,7 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
getattr(serializer, '_poly_force_type_resolution', False)
)
included_cache[new_item['type']][new_item['id']] = \
utils.format_keys(new_item)
utils._format_object(new_item)
cls.extract_included(
serializer_fields,
serializer_resource,
Expand All @@ -427,7 +427,9 @@ def extract_included(cls, fields, resource, resource_instance, included_resource
relation_type,
getattr(field, '_poly_force_type_resolution', False)
)
included_cache[new_item['type']][new_item['id']] = utils.format_keys(new_item)
included_cache[new_item['type']][new_item['id']] = utils._format_object(
new_item
)
cls.extract_included(
serializer_fields,
serializer_data,
Expand Down Expand Up @@ -577,7 +579,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
)
meta = self.extract_meta(serializer, resource)
if meta:
json_resource_obj.update({'meta': utils.format_keys(meta)})
json_resource_obj.update({'meta': utils._format_object(meta)})
json_api_data.append(json_resource_obj)

self.extract_included(
Expand All @@ -594,7 +596,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):

meta = self.extract_meta(serializer, serializer_data)
if meta:
json_api_data.update({'meta': utils.format_keys(meta)})
json_api_data.update({'meta': utils._format_object(meta)})

self.extract_included(
fields, serializer_data, resource_instance, included_resources, included_cache
Expand All @@ -620,7 +622,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
render_data['included'].append(included_cache[included_type][included_id])

if json_api_meta:
render_data['meta'] = utils.format_keys(json_api_meta)
render_data['meta'] = utils._format_object(json_api_meta)

return super(JSONRenderer, self).render(
render_data, accepted_media_type, renderer_context
Expand Down
16 changes: 13 additions & 3 deletions rest_framework_json_api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
JSON_API_SETTINGS_PREFIX = 'JSON_API_'

DEFAULTS = {
'FORMAT_KEYS': False,
'FORMAT_RELATION_KEYS': None,
'FORMAT_FIELD_NAMES': False,
'FORMAT_TYPES': False,
'PLURALIZE_RELATION_TYPE': None,
'PLURALIZE_TYPES': False,
'UNIFORM_EXCEPTIONS': False,

# deprecated settings to be removed in the future
'FORMAT_KEYS': None,
'FORMAT_RELATION_KEYS': None,
'PLURALIZE_RELATION_TYPE': None,
}


Expand All @@ -39,6 +42,13 @@ def __getattr__(self, attr):
setattr(self, attr, value)
return value

@property
def format_type(self):
if self.FORMAT_KEYS is not None:
return self.FORMAT_KEYS

return self.FORMAT_FIELD_NAMES


json_api_settings = JSONAPISettings()

Expand Down
Loading