Merge pull request #5143 from ryanpetrello/fix-4876

add a field to OPTIONS that tells if the setting came from a .py file
This commit is contained in:
Ryan Petrello
2017-02-02 13:40:27 -05:00
committed by GitHub
5 changed files with 61 additions and 20 deletions

View File

@@ -32,6 +32,7 @@ class Metadata(metadata.SimpleMetadata):
'min_length', 'max_length', 'min_length', 'max_length',
'min_value', 'max_value', 'min_value', 'max_value',
'category', 'category_slug', 'category', 'category_slug',
'defined_in_file'
] ]
for attr in text_attrs: for attr in text_attrs:
@@ -156,6 +157,10 @@ class Metadata(metadata.SimpleMetadata):
# For PUT/POST methods, remove read-only fields. # For PUT/POST methods, remove read-only fields.
if method in ('PUT', 'POST'): if method in ('PUT', 'POST'):
# This value should always be False for PUT/POST, so don't
# show it (file-based read-only settings can't be updated)
meta.pop('defined_in_file', False)
if meta.pop('read_only', False): if meta.pop('read_only', False):
actions[method].pop(field) actions[method].pop(field)

View File

@@ -116,6 +116,7 @@ class SettingsRegistry(object):
placeholder = field_kwargs.pop('placeholder', empty) placeholder = field_kwargs.pop('placeholder', empty)
feature_required = field_kwargs.pop('feature_required', empty) feature_required = field_kwargs.pop('feature_required', empty)
encrypted = bool(field_kwargs.pop('encrypted', False)) encrypted = bool(field_kwargs.pop('encrypted', False))
defined_in_file = bool(field_kwargs.pop('defined_in_file', False))
if getattr(field_kwargs.get('child', None), 'source', None) is not None: if getattr(field_kwargs.get('child', None), 'source', None) is not None:
field_kwargs['child'].source = None field_kwargs['child'].source = None
field_instance = field_class(**field_kwargs) field_instance = field_class(**field_kwargs)
@@ -126,6 +127,13 @@ class SettingsRegistry(object):
field_instance.placeholder = placeholder field_instance.placeholder = placeholder
if feature_required is not empty: if feature_required is not empty:
field_instance.feature_required = feature_required field_instance.feature_required = feature_required
field_instance.defined_in_file = defined_in_file
if field_instance.defined_in_file:
field_instance.help_text = (
str(_('This value has been set manually in a settings file.')) +
'\n\n' +
str(field_instance.help_text)
)
field_instance.encrypted = encrypted field_instance.encrypted = encrypted
original_field_instance = field_instance original_field_instance = field_instance
if field_class != original_field_class: if field_class != original_field_class:

View File

@@ -205,6 +205,7 @@ class SettingsWrapper(UserSettingsHolder):
if file_default != init_default and file_default is not None: if file_default != init_default and file_default is not None:
logger.warning('Setting %s has been marked read-only!', key) logger.warning('Setting %s has been marked read-only!', key)
self.registry._registry[key]['read_only'] = True self.registry._registry[key]['read_only'] = True
self.registry._registry[key]['defined_in_file'] = True
self.__dict__['_awx_conf_init_readonly'] = True self.__dict__['_awx_conf_init_readonly'] = True
# If local preload timer has expired, check to see if another process # If local preload timer has expired, check to see if another process
# has already preloaded the cache and skip preloading if so. # has already preloaded the cache and skip preloading if so.

View File

@@ -25,11 +25,11 @@ def reg(request):
settings = LazySettings() settings = LazySettings()
registry = SettingsRegistry(settings) registry = SettingsRegistry(settings)
# @pytest.mark.readonly can be used to mark specific setting values as # @pytest.mark.defined_in_file can be used to mark specific setting values
# "read-only". This is analogous to manually specifying a setting on the # as "defined in a settings file". This is analogous to manually
# filesystem (e.g., in a local_settings.py in development, or in # specifying a setting on the filesystem (e.g., in a local_settings.py in
# /etc/tower/conf.d/<something>.py) # development, or in /etc/tower/conf.d/<something>.py)
defaults = request.node.get_marker('readonly') defaults = request.node.get_marker('defined_in_file')
if defaults: if defaults:
settings.configure(**defaults.kwargs) settings.configure(**defaults.kwargs)
settings._wrapped = SettingsWrapper(settings._wrapped, settings._wrapped = SettingsWrapper(settings._wrapped,
@@ -280,7 +280,7 @@ def test_field_with_custom_mixin(reg):
assert field.is_great() is True assert field.is_great() is True
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_default_value_from_settings(reg): def test_default_value_from_settings(reg):
reg.register( reg.register(
'AWX_SOME_SETTING', 'AWX_SOME_SETTING',
@@ -293,7 +293,7 @@ def test_default_value_from_settings(reg):
assert field.default == 'DEFAULT' assert field.default == 'DEFAULT'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_default_value_from_settings_with_custom_representation(reg): def test_default_value_from_settings_with_custom_representation(reg):
class LowercaseCharField(fields.CharField): class LowercaseCharField(fields.CharField):

View File

@@ -40,12 +40,12 @@ def settings(request):
settings = LazySettings() settings = LazySettings()
registry = SettingsRegistry(settings) registry = SettingsRegistry(settings)
# @pytest.mark.readonly can be used to mark specific setting values as # @pytest.mark.defined_in_file can be used to mark specific setting values
# "read-only". This is analogous to manually specifying a setting on the # as "defined in a settings file". This is analogous to manually
# filesystem (e.g., in a local_settings.py in development, or in # specifying a setting on the filesystem (e.g., in a local_settings.py in
# /etc/tower/conf.d/<something>.py) # development, or in /etc/tower/conf.d/<something>.py)
readonly_marker = request.node.get_marker('readonly') in_file_marker = request.node.get_marker('defined_in_file')
defaults = readonly_marker.kwargs if readonly_marker else {} defaults = in_file_marker.kwargs if in_file_marker else {}
defaults['DEFAULTS_SNAPSHOT'] = {} defaults['DEFAULTS_SNAPSHOT'] = {}
settings.configure(**defaults) settings.configure(**defaults)
settings._wrapped = SettingsWrapper(settings._wrapped, settings._wrapped = SettingsWrapper(settings._wrapped,
@@ -54,14 +54,14 @@ def settings(request):
return settings return settings
@pytest.mark.readonly(DEBUG=True) @pytest.mark.defined_in_file(DEBUG=True)
def test_unregistered_setting(settings): def test_unregistered_setting(settings):
"native Django settings are not stored in DB, and aren't cached" "native Django settings are not stored in DB, and aren't cached"
assert settings.DEBUG is True assert settings.DEBUG is True
assert settings.cache.get('DEBUG') is None assert settings.cache.get('DEBUG') is None
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting(settings): def test_read_only_setting(settings):
settings.registry.register( settings.registry.register(
'AWX_SOME_SETTING', 'AWX_SOME_SETTING',
@@ -75,7 +75,7 @@ def test_read_only_setting(settings):
assert settings == ['AWX_SOME_SETTING'] assert settings == ['AWX_SOME_SETTING']
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_with_empty_default(settings): def test_read_only_setting_with_empty_default(settings):
settings.registry.register( settings.registry.register(
'AWX_SOME_SETTING', 'AWX_SOME_SETTING',
@@ -90,7 +90,7 @@ def test_read_only_setting_with_empty_default(settings):
assert settings == ['AWX_SOME_SETTING'] assert settings == ['AWX_SOME_SETTING']
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_read_only_defaults_are_cached(settings): def test_read_only_defaults_are_cached(settings):
"read-only settings are stored in the cache" "read-only settings are stored in the cache"
settings.registry.register( settings.registry.register(
@@ -103,7 +103,7 @@ def test_read_only_defaults_are_cached(settings):
assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT' assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_cache_respects_timeout(settings): def test_cache_respects_timeout(settings):
"only preload the cache every SETTING_CACHE_TIMEOUT settings" "only preload the cache every SETTING_CACHE_TIMEOUT settings"
settings.registry.register( settings.registry.register(
@@ -137,6 +137,33 @@ def test_default_setting(settings, mocker):
assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT' assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT'
@pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_setting_is_from_setting_file(settings, mocker):
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system'
)
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert settings.registry.get_setting_field('AWX_SOME_SETTING').defined_in_file is True
def test_setting_is_not_from_setting_file(settings, mocker):
settings.registry.register(
'AWX_SOME_SETTING',
field_class=fields.CharField,
category=_('System'),
category_slug='system',
default='DEFAULT'
)
settings_to_cache = mocker.Mock(**{'order_by.return_value': []})
with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache):
assert settings.AWX_SOME_SETTING == 'DEFAULT'
assert settings.registry.get_setting_field('AWX_SOME_SETTING').defined_in_file is False
def test_empty_setting(settings, mocker): def test_empty_setting(settings, mocker):
"settings with no default and no defined value are not valid" "settings with no default and no defined value are not valid"
settings.registry.register( settings.registry.register(
@@ -180,7 +207,7 @@ def test_setting_from_db(settings, mocker):
assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB' assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB'
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_assignment(settings): def test_read_only_setting_assignment(settings):
"read-only settings cannot be overwritten" "read-only settings cannot be overwritten"
settings.registry.register( settings.registry.register(
@@ -255,7 +282,7 @@ def test_db_setting_deletion(settings, mocker):
assert existing_setting.delete.call_count == 1 assert existing_setting.delete.call_count == 1
@pytest.mark.readonly(AWX_SOME_SETTING='DEFAULT') @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT')
def test_read_only_setting_deletion(settings): def test_read_only_setting_deletion(settings):
"read-only settings cannot be deleted" "read-only settings cannot be deleted"
settings.registry.register( settings.registry.register(