mirror of
https://github.com/ansible/awx.git
synced 2026-03-02 17:28:51 -03:30
Update settings serializer to allow empty/blank values.
This commit is contained in:
@@ -7,7 +7,7 @@ from django.utils.encoding import force_text
|
|||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
__all__ = ['BooleanNullField', 'CharNullField', 'ChoiceNullField', 'EncryptedPasswordField']
|
__all__ = ['BooleanNullField', 'CharNullField', 'ChoiceNullField', 'EncryptedPasswordField', 'VerbatimField']
|
||||||
|
|
||||||
|
|
||||||
class NullFieldMixin(object):
|
class NullFieldMixin(object):
|
||||||
@@ -76,5 +76,14 @@ class EncryptedPasswordField(CharNullField):
|
|||||||
return '$encrypted$'
|
return '$encrypted$'
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class VerbatimField(serializers.Field):
|
||||||
|
'''
|
||||||
|
Custom field that passes the value through without changes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
return value
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ from awx.main.redact import REPLACE_STR
|
|||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
|
|
||||||
from awx.api.license import feature_enabled
|
from awx.api.license import feature_enabled
|
||||||
from awx.api.fields import BooleanNullField, CharNullField, ChoiceNullField, EncryptedPasswordField
|
from awx.api.fields import BooleanNullField, CharNullField, ChoiceNullField, EncryptedPasswordField, VerbatimField
|
||||||
|
|
||||||
from awx.fact.models import * # noqa
|
from awx.fact.models import * # noqa
|
||||||
|
|
||||||
@@ -2216,6 +2216,8 @@ class ActivityStreamSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class TowerSettingsSerializer(BaseSerializer):
|
class TowerSettingsSerializer(BaseSerializer):
|
||||||
|
|
||||||
|
value = VerbatimField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TowerSettings
|
model = TowerSettings
|
||||||
fields = ('key', 'description', 'category', 'value', 'value_type', 'user')
|
fields = ('key', 'description', 'category', 'value', 'value_type', 'user')
|
||||||
@@ -2249,14 +2251,20 @@ class TowerSettingsSerializer(BaseSerializer):
|
|||||||
manifest = settings.TOWER_SETTINGS_MANIFEST
|
manifest = settings.TOWER_SETTINGS_MANIFEST
|
||||||
if attrs['key'] not in manifest:
|
if attrs['key'] not in manifest:
|
||||||
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key".format(attrs['key'])]))
|
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key".format(attrs['key'])]))
|
||||||
# TODO: Type checking/coercion, contextual validation
|
|
||||||
return super(TowerSettingsSerializer, self).validate(attrs)
|
|
||||||
|
|
||||||
def _create(self, validated_data):
|
if attrs['value_type'] == 'json':
|
||||||
current_val = TowerSettings.objects.filter(key=validated_data['key'])
|
attrs['value'] = json.dumps(attrs['value'])
|
||||||
if current_val.exists():
|
elif attrs['value_type'] == 'list':
|
||||||
return self.update(current_val[0], validated_data)
|
try:
|
||||||
return super(TowerSettingsSerializer, self).create(validated_data)
|
attrs['value'] = ','.join(map(force_text, attrs['value']))
|
||||||
|
except TypeError:
|
||||||
|
attrs['value'] = force_text(attrs['value'])
|
||||||
|
elif attrs['value_type'] == 'bool':
|
||||||
|
attrs['value'] = force_text(bool(attrs['value']))
|
||||||
|
else:
|
||||||
|
attrs['value'] = force_text(attrs['value'])
|
||||||
|
|
||||||
|
return super(TowerSettingsSerializer, self).validate(attrs)
|
||||||
|
|
||||||
|
|
||||||
class AuthTokenSerializer(serializers.Serializer):
|
class AuthTokenSerializer(serializers.Serializer):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import json
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# Tower
|
# Tower
|
||||||
@@ -34,7 +34,9 @@ class TowerSettings(CreatedModifiedModel):
|
|||||||
)
|
)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
category = models.CharField(max_length=128)
|
category = models.CharField(max_length=128)
|
||||||
value = models.TextField()
|
value = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
value_type = models.CharField(
|
value_type = models.CharField(
|
||||||
max_length=12,
|
max_length=12,
|
||||||
choices=SETTINGS_TYPE_CHOICES
|
choices=SETTINGS_TYPE_CHOICES
|
||||||
@@ -54,9 +56,12 @@ class TowerSettings(CreatedModifiedModel):
|
|||||||
elif self.value_type == 'password':
|
elif self.value_type == 'password':
|
||||||
converted_type = self.value
|
converted_type = self.value
|
||||||
elif self.value_type == 'list':
|
elif self.value_type == 'list':
|
||||||
converted_type = [x.strip() for x in self.value.split(',')]
|
if self.value:
|
||||||
|
converted_type = [x.strip() for x in self.value.split(',')]
|
||||||
|
else:
|
||||||
|
converted_type = []
|
||||||
elif self.value_type == 'bool':
|
elif self.value_type == 'bool':
|
||||||
converted_type = smart_text(self.value).lower() in ('true', 'yes', '1')
|
converted_type = force_text(self.value).lower() in ('true', 'yes', '1')
|
||||||
elif self.value_type == 'string':
|
elif self.value_type == 'string':
|
||||||
converted_type = self.value
|
converted_type = self.value
|
||||||
else:
|
else:
|
||||||
@@ -69,8 +74,11 @@ class TowerSettings(CreatedModifiedModel):
|
|||||||
if self.value_type == 'json':
|
if self.value_type == 'json':
|
||||||
self.value = json.dumps(value)
|
self.value = json.dumps(value)
|
||||||
elif self.value_type == 'list':
|
elif self.value_type == 'list':
|
||||||
self.value = ','.join(value)
|
try:
|
||||||
|
self.value = ','.join(map(force_text, value))
|
||||||
|
except TypeError:
|
||||||
|
self.value = force_text(value)
|
||||||
elif self.value_type == 'bool':
|
elif self.value_type == 'bool':
|
||||||
self.value = smart_text(bool(value))
|
self.value = force_text(bool(value))
|
||||||
else:
|
else:
|
||||||
self.value = smart_text(value)
|
self.value = force_text(value)
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ TEST_TOWER_SETTINGS_MANIFEST = {
|
|||||||
"default": ["A", "Simple", "List"],
|
"default": ["A", "Simple", "List"],
|
||||||
"type": "list",
|
"type": "list",
|
||||||
"category": "test"
|
"category": "test"
|
||||||
|
},
|
||||||
|
"TEST_SETTING_JSON": {
|
||||||
|
"name": "A JSON Field",
|
||||||
|
"description": "A JSON Field",
|
||||||
|
"default": {"key": "value", "otherkey": ["list", "of", "things"]},
|
||||||
|
"type": "json",
|
||||||
|
"category": "test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +53,7 @@ class SettingsTest(BaseTest):
|
|||||||
self.setup_instances()
|
self.setup_instances()
|
||||||
self.setup_users()
|
self.setup_users()
|
||||||
|
|
||||||
def get_settings(self, expected_count=4):
|
def get_settings(self, expected_count=5):
|
||||||
result = self.get(reverse('api:settings_list'), expect=200)
|
result = self.get(reverse('api:settings_list'), expect=200)
|
||||||
self.assertEqual(result['count'], expected_count)
|
self.assertEqual(result['count'], expected_count)
|
||||||
return result['results']
|
return result['results']
|
||||||
@@ -74,21 +81,29 @@ class SettingsTest(BaseTest):
|
|||||||
with self.current_user(self.super_django_user):
|
with self.current_user(self.super_django_user):
|
||||||
self.get_settings(expected_count=len(TEST_TOWER_SETTINGS_MANIFEST))
|
self.get_settings(expected_count=len(TEST_TOWER_SETTINGS_MANIFEST))
|
||||||
|
|
||||||
def test_set_and_reset_settings(self):
|
def set_and_reset_setting(self, key, values, expected_values=()):
|
||||||
settings_reset = reverse('api:settings_reset')
|
settings_reset = reverse('api:settings_reset')
|
||||||
|
setting = self.get_individual_setting(key)
|
||||||
|
self.assertEqual(setting['value'], TEST_TOWER_SETTINGS_MANIFEST[key]['default'])
|
||||||
|
for n, value in enumerate(values):
|
||||||
|
self.set_setting(key, value)
|
||||||
|
setting = self.get_individual_setting(key)
|
||||||
|
if len(expected_values) > n:
|
||||||
|
self.assertEqual(setting['value'], expected_values[n])
|
||||||
|
else:
|
||||||
|
self.assertEqual(setting['value'], value)
|
||||||
|
self.post(settings_reset, data={"key": key}, expect=204)
|
||||||
|
setting = self.get_individual_setting(key)
|
||||||
|
self.assertEqual(setting['value'], TEST_TOWER_SETTINGS_MANIFEST[key]['default'])
|
||||||
|
|
||||||
|
def test_set_and_reset_settings(self):
|
||||||
with self.current_user(self.super_django_user):
|
with self.current_user(self.super_django_user):
|
||||||
# Set and reset a single setting
|
self.set_and_reset_setting('TEST_SETTING_INT', (2, 0))
|
||||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
self.set_and_reset_setting('TEST_SETTING_STRING', ('blah', '', u'\u2620'))
|
||||||
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
|
self.set_and_reset_setting('TEST_SETTING_BOOL', (True, False))
|
||||||
self.set_setting('TEST_SETTING_INT', 2)
|
# List values are always saved as strings.
|
||||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
self.set_and_reset_setting('TEST_SETTING_LIST', ([4, 5, 6], [], [2]), (['4', '5', '6'], [], ['2']))
|
||||||
self.assertEqual(setting_int['value'], 2)
|
self.set_and_reset_setting('TEST_SETTING_JSON', ({"k": "v"}, {}, [], [7, 8], 'str'))
|
||||||
self.set_setting('TEST_SETTING_INT', 3)
|
|
||||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
|
||||||
self.assertEqual(setting_int['value'], 3)
|
|
||||||
self.post(settings_reset, data={"key": 'TEST_SETTING_INT'}, expect=204)
|
|
||||||
setting_int = self.get_individual_setting('TEST_SETTING_INT')
|
|
||||||
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
|
|
||||||
|
|
||||||
def test_clear_all_settings(self):
|
def test_clear_all_settings(self):
|
||||||
settings_list = reverse('api:settings_list')
|
settings_list = reverse('api:settings_list')
|
||||||
@@ -97,6 +112,7 @@ class SettingsTest(BaseTest):
|
|||||||
self.set_setting('TEST_SETTING_STRING', "foo")
|
self.set_setting('TEST_SETTING_STRING', "foo")
|
||||||
self.set_setting('TEST_SETTING_BOOL', False)
|
self.set_setting('TEST_SETTING_BOOL', False)
|
||||||
self.set_setting('TEST_SETTING_LIST', [1,2,3])
|
self.set_setting('TEST_SETTING_LIST', [1,2,3])
|
||||||
|
self.set_setting('TEST_SETTING_JSON', '{"key": "new value"}')
|
||||||
all_settings = self.get_settings()
|
all_settings = self.get_settings()
|
||||||
for setting_entry in all_settings:
|
for setting_entry in all_settings:
|
||||||
self.assertNotEqual(setting_entry['value'],
|
self.assertNotEqual(setting_entry['value'],
|
||||||
|
|||||||
Reference in New Issue
Block a user