diff --git a/example/tests/test_generic_viewset.py b/example/tests/test_generic_viewset.py index 9d32da06..bfde51ea 100644 --- a/example/tests/test_generic_viewset.py +++ b/example/tests/test_generic_viewset.py @@ -1,23 +1,15 @@ -from django.conf import settings +from django.test import override_settings from django.urls import reverse from example.tests import TestBase +@override_settings(JSON_API_FORMAT_KEYS='dasherize') class GenericViewSet(TestBase): """ Test expected responses coming from a Generic ViewSet """ - def setUp(self): - super(GenericViewSet, self).setUp() - - setattr(settings, 'JSON_API_FORMAT_KEYS', 'dasherize') - - def tearDown(self): - - setattr(settings, 'JSON_API_FORMAT_KEYS', 'camelize') - def test_default_rest_framework_behavior(self): """ This is more of an example really, showing default behavior diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index f84c4ae4..21c6d41a 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -1,12 +1,13 @@ import pytest -from django.conf import settings from django.contrib.auth import get_user_model +from django.test import override_settings from django.urls import reverse from django.utils import encoding from example.tests import TestBase +@override_settings(JSON_API_FORMAT_KEYS='dasherize') class ModelViewSetTests(TestBase): """ Test usage with ModelViewSets, also tests pluralization, camelization, @@ -21,12 +22,6 @@ def setUp(self): super(ModelViewSetTests, self).setUp() self.detail_url = reverse('user-detail', kwargs={'pk': self.miles.pk}) - setattr(settings, 'JSON_API_FORMAT_KEYS', 'dasherize') - - def tearDown(self): - - setattr(settings, 'JSON_API_FORMAT_KEYS', 'camelize') - def test_key_in_list_result(self): """ Ensure the result has a 'user' key since that is the name of the model diff --git a/example/tests/unit/test_settings.py b/example/tests/unit/test_settings.py new file mode 100644 index 00000000..516e76ec --- /dev/null +++ b/example/tests/unit/test_settings.py @@ -0,0 +1,17 @@ +import pytest + +from rest_framework_json_api.settings import json_api_settings + + +def test_settings_invalid(): + with pytest.raises(AttributeError): + json_api_settings.INVALID_SETTING + + +def test_settings_default(): + assert json_api_settings.UNIFORM_EXCEPTIONS is False + + +def test_settings_override(settings): + settings.JSON_API_FORMAT_KEYS = 'dasherize' + assert json_api_settings.FORMAT_KEYS == 'dasherize' diff --git a/example/tests/unit/test_utils.py b/example/tests/unit/test_utils.py index 4e690f5b..46315772 100644 --- a/example/tests/unit/test_utils.py +++ b/example/tests/unit/test_utils.py @@ -1,6 +1,6 @@ import pytest -from django.conf import settings from django.contrib.auth import get_user_model +from django.test import override_settings from django.utils import six from rest_framework import serializers from rest_framework.generics import GenericAPIView @@ -29,12 +29,12 @@ class Meta: def test_get_resource_name(): view = APIView() context = {'view': view} - setattr(settings, 'JSON_API_FORMAT_TYPES', None) - assert 'APIViews' == utils.get_resource_name(context), 'not formatted' + with override_settings(JSON_API_FORMAT_TYPES=None): + assert 'APIViews' == utils.get_resource_name(context), 'not formatted' context = {'view': view} - setattr(settings, 'JSON_API_FORMAT_TYPES', 'dasherize') - assert 'api-views' == utils.get_resource_name(context), 'derived from view' + with override_settings(JSON_API_FORMAT_TYPES='dasherize'): + assert 'api-views' == utils.get_resource_name(context), 'derived from view' view.model = get_user_model() assert 'users' == utils.get_resource_name(context), 'derived from view model' diff --git a/rest_framework_json_api/exceptions.py b/rest_framework_json_api/exceptions.py index f6b21ad6..38ff527b 100644 --- a/rest_framework_json_api/exceptions.py +++ b/rest_framework_json_api/exceptions.py @@ -1,9 +1,10 @@ -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from rest_framework import exceptions, status from rest_framework_json_api import utils +from .settings import json_api_settings + def rendered_with_json_api(view): from rest_framework_json_api.renderers import JSONRenderer @@ -29,7 +30,7 @@ def exception_handler(exc, context): # Use regular DRF format if not rendered by DRF JSON API and not uniform is_json_api_view = rendered_with_json_api(context['view']) - is_uniform = getattr(settings, 'JSON_API_UNIFORM_EXCEPTIONS', False) + is_uniform = json_api_settings.UNIFORM_EXCEPTIONS if not is_json_api_view and not is_uniform: return response diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index 86a61e74..98873d2e 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -1,12 +1,12 @@ """ Parsers """ -from django.conf import settings from django.utils import six from rest_framework import parsers from rest_framework.exceptions import ParseError from . import exceptions, renderers, serializers, utils +from .settings import json_api_settings class JSONParser(parsers.JSONParser): @@ -32,7 +32,7 @@ class JSONParser(parsers.JSONParser): @staticmethod def parse_attributes(data): attributes = data.get('attributes') - uses_format_translation = getattr(settings, 'JSON_API_FORMAT_KEYS', False) + uses_format_translation = json_api_settings.FORMAT_KEYS if not attributes: return dict() @@ -44,7 +44,7 @@ def parse_attributes(data): @staticmethod def parse_relationships(data): - uses_format_translation = getattr(settings, 'JSON_API_FORMAT_KEYS', False) + uses_format_translation = json_api_settings.FORMAT_KEYS relationships = data.get('relationships') if not relationships: diff --git a/rest_framework_json_api/settings.py b/rest_framework_json_api/settings.py new file mode 100644 index 00000000..40c5a96b --- /dev/null +++ b/rest_framework_json_api/settings.py @@ -0,0 +1,57 @@ +""" +This module provides the `json_api_settings` object that is used to access +JSON API REST framework settings, checking for user settings first, then falling back to +the defaults. +""" + +from django.conf import settings +from django.core.signals import setting_changed + +JSON_API_SETTINGS_PREFIX = 'JSON_API_' + +DEFAULTS = { + 'FORMAT_KEYS': False, + 'FORMAT_RELATION_KEYS': None, + 'FORMAT_TYPES': False, + 'PLURALIZE_RELATION_TYPE': None, + 'PLURALIZE_TYPES': False, + 'UNIFORM_EXCEPTIONS': False, +} + + +class JSONAPISettings(object): + """ + A settings object that allows json api settings to be access as + properties. + """ + + def __init__(self, user_settings=settings, defaults=DEFAULTS): + self.defaults = defaults + self.user_settings = user_settings + + def __getattr__(self, attr): + if attr not in self.defaults: + raise AttributeError("Invalid JSON API setting: '%s'" % attr) + + value = getattr(self.user_settings, JSON_API_SETTINGS_PREFIX + attr, self.defaults[attr]) + + # Cache the result + setattr(self, attr, value) + return value + + +json_api_settings = JSONAPISettings() + + +def reload_json_api_settings(*args, **kwargs): + django_setting = kwargs['setting'] + setting = django_setting.replace(JSON_API_SETTINGS_PREFIX, '') + value = kwargs['value'] + if setting in DEFAULTS.keys(): + if value is not None: + setattr(json_api_settings, setting, value) + elif hasattr(json_api_settings, setting): + delattr(json_api_settings, setting) + + +setting_changed.connect(reload_json_api_settings) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 2d991050..a7220084 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -18,6 +18,8 @@ from rest_framework.exceptions import APIException from rest_framework.serializers import ManyRelatedField # noqa: F401 +from .settings import json_api_settings + try: from rest_framework_nested.relations import HyperlinkedRouterField except ImportError: @@ -104,7 +106,7 @@ def format_keys(obj, format_type=None): :format_type: Either 'dasherize', 'camelize' or 'underscore' """ if format_type is None: - format_type = getattr(settings, 'JSON_API_FORMAT_KEYS', False) + format_type = json_api_settings.FORMAT_KEYS if format_type in ('dasherize', 'camelize', 'underscore', 'capitalize'): @@ -136,7 +138,7 @@ def format_keys(obj, format_type=None): def format_value(value, format_type=None): if format_type is None: - format_type = getattr(settings, 'JSON_API_FORMAT_KEYS', False) + format_type = json_api_settings.FORMAT_KEYS if format_type == 'dasherize': # inflection can't dasherize camelCase value = inflection.underscore(value) @@ -156,17 +158,17 @@ def format_relation_name(value, format_type=None): "settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES'" ) if format_type is None: - format_type = getattr(settings, 'JSON_API_FORMAT_RELATION_KEYS', None) - pluralize = getattr(settings, 'JSON_API_PLURALIZE_RELATION_TYPE', None) + format_type = json_api_settings.FORMAT_RELATION_KEYS + pluralize = json_api_settings.PLURALIZE_RELATION_TYPE return format_resource_type(value, format_type, pluralize) def format_resource_type(value, format_type=None, pluralize=None): if format_type is None: - format_type = getattr(settings, 'JSON_API_FORMAT_TYPES', False) + format_type = json_api_settings.FORMAT_TYPES if pluralize is None: - pluralize = getattr(settings, 'JSON_API_PLURALIZE_TYPES', False) + pluralize = json_api_settings.PLURALIZE_TYPES if format_type: # format_type will never be None here so we can use format_value