diff --git a/CHANGELOG.md b/CHANGELOG.md index 04069795..3dd15afa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/usage.md b/docs/usage.md index 990638f9..c8727f1b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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: diff --git a/example/api/resources/identity.py b/example/api/resources/identity.py index 470bd79c..a00def74 100644 --- a/example/api/resources/identity.py +++ b/example/api/resources/identity.py @@ -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): diff --git a/example/settings/dev.py b/example/settings/dev.py index 61dfa443..01e2fef1 100644 --- a/example/settings/dev.py +++ b/example/settings/dev.py @@ -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, diff --git a/example/settings/test.py b/example/settings/test.py index 1f0e959d..bbf6e400 100644 --- a/example/settings/test.py +++ b/example/settings/test.py @@ -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({ diff --git a/example/tests/test_generic_viewset.py b/example/tests/test_generic_viewset.py index bfde51ea..15d9bd1e 100644 --- a/example/tests/test_generic_viewset.py +++ b/example/tests/test_generic_viewset.py @@ -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 diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index 21c6d41a..ee3e4ba0 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -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, diff --git a/example/tests/test_parsers.py b/example/tests/test_parsers.py index 3c7a102c..aec80a12 100644 --- a/example/tests/test_parsers.py +++ b/example/tests/test_parsers.py @@ -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 @@ -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' @@ -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() diff --git a/example/tests/unit/test_renderers.py b/example/tests/unit/test_renderers.py index de40afac..42a45a94 100644 --- a/example/tests/unit/test_renderers.py +++ b/example/tests/unit/test_renderers.py @@ -1,3 +1,5 @@ +import json + from rest_framework_json_api import serializers, views from rest_framework_json_api.renderers import JSONRenderer @@ -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',) @@ -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'} diff --git a/example/tests/unit/test_settings.py b/example/tests/unit/test_settings.py index 516e76ec..e6b82a24 100644 --- a/example/tests/unit/test_settings.py +++ b/example/tests/unit/test_settings.py @@ -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' diff --git a/example/tests/unit/test_utils.py b/example/tests/unit/test_utils.py index 46315772..a1414050 100644 --- a/example/tests/unit/test_utils.py +++ b/example/tests/unit/test_utils.py @@ -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 @@ -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' diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 98873d2e..8459f3ba 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -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() diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 6059d2a2..ba6424ee 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -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): @@ -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): @@ -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, @@ -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, @@ -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( @@ -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 @@ -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 diff --git a/rest_framework_json_api/settings.py b/rest_framework_json_api/settings.py index 40c5a96b..6c7eeffe 100644 --- a/rest_framework_json_api/settings.py +++ b/rest_framework_json_api/settings.py @@ -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, } @@ -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() diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index a7220084..39000216 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -98,13 +98,50 @@ def get_serializer_fields(serializer): return fields +def format_field_names(obj, format_type=None): + """ + Takes a dict and returns it with formatted keys as set in `format_type` + or `JSON_API_FORMAT_FIELD_NAMES` + + :format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore' + """ + if format_type is None: + format_type = json_api_settings.FORMAT_FIELD_NAMES + + if isinstance(obj, dict): + formatted = OrderedDict() + for key, value in obj.items(): + key = format_value(key, format_type) + formatted[key] = value + return formatted + + return obj + + +def _format_object(obj, format_type=None): + """Depending on settings calls either `format_keys` or `format_field_names`""" + + if json_api_settings.FORMAT_KEYS is not None: + return format_keys(obj, format_type) + + return format_field_names(obj, format_type) + + def format_keys(obj, format_type=None): """ Takes either a dict or list and returns it with camelized keys only if JSON_API_FORMAT_KEYS is set. - :format_type: Either 'dasherize', 'camelize' or 'underscore' + :format_type: Either 'dasherize', 'camelize', 'capitalize' or 'underscore' """ + warnings.warn( + "`format_keys` function and `JSON_API_FORMAT_KEYS` setting are deprecated and will be " + "removed in the future. " + "Use `format_field_names` and `JSON_API_FIELD_NAMES` instead. Be aware that " + "`format_field_names` only formats keys and preserves value.", + DeprecationWarning + ) + if format_type is None: format_type = json_api_settings.FORMAT_KEYS @@ -138,7 +175,7 @@ def format_keys(obj, format_type=None): def format_value(value, format_type=None): if format_type is None: - format_type = json_api_settings.FORMAT_KEYS + format_type = json_api_settings.format_type if format_type == 'dasherize': # inflection can't dasherize camelCase value = inflection.underscore(value) @@ -155,7 +192,9 @@ def format_value(value, format_type=None): def format_relation_name(value, format_type=None): warnings.warn( "The 'format_relation_name' function has been renamed 'format_resource_type' and the " - "settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES'" + "settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES' instead of " + "'JSON_API_FORMAT_RELATION_KEYS' and 'JSON_API_PLURALIZE_RELATION_TYPE'", + DeprecationWarning ) if format_type is None: format_type = json_api_settings.FORMAT_RELATION_KEYS pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy