Merge pull request #180 from jazzband/backport_cli_squashed

Backport command functonality from django-constance-cli (squashed)
This commit is contained in:
Camilo Nova 2016-11-30 14:09:30 -05:00 committed by GitHub
commit 7934de8ede
9 changed files with 222 additions and 16 deletions

View file

@ -80,6 +80,22 @@ if not six.PY3:
})
def get_values():
"""
Get dictionary of values from the backend
:return:
"""
# First load a mapping between config name and default value
default_initial = ((name, options[0])
for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
initial = dict(default_initial,
**dict(config._backend.mget(settings.CONFIG.keys())))
return initial
class ConstanceForm(forms.Form):
version = forms.CharField(widget=forms.HiddenInput)
@ -167,17 +183,9 @@ class ConstanceAdmin(admin.ModelAdmin):
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
# First load a mapping between config name and default value
if not self.has_change_permission(request, None):
raise PermissionDenied
default_initial = (
(name, options[0]) for name, options in settings.CONFIG.items()
)
# Then update the mapping with actually values from the backend
initial = dict(
default_initial,
**dict(config._backend.mget(settings.CONFIG.keys()))
)
initial = get_values()
form = self.change_list_form(initial=initial)
if request.method == 'POST':
form = self.change_list_form(data=request.POST, initial=initial)

View file

View file

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.core.management import BaseCommand, CommandError
from django.utils.translation import ugettext as _
from ... import config
from ...admin import ConstanceForm, get_values
def _set_constance_value(key, value):
"""
Parses and sets a Constance value from a string
:param key:
:param value:
:return:
"""
form = ConstanceForm(initial=get_values())
field = form.fields[key]
clean_value = field.clean(field.to_python(value))
setattr(config, key, clean_value)
class Command(BaseCommand):
help = _('Get/Set In-database config settings handled by Constance')
def add_arguments(self, parser):
subparsers = parser.add_subparsers(dest='command')
parser_list = subparsers.add_parser('list', cmd=self, help='list all Constance keys and their values')
parser_get = subparsers.add_parser('get', cmd=self, help='get the value of a Constance key')
parser_get.add_argument('key', help='name of the key to get', metavar='KEY')
parser_set = subparsers.add_parser('set', cmd=self, help='set the value of a Constance key')
parser_set.add_argument('key', help='name of the key to get', metavar='KEY')
parser_set.add_argument('value', help='value to set', metavar='VALUE')
def handle(self, command, key=None, value=None, *args, **options):
if command == 'get':
try:
self.stdout.write("{}".format(getattr(config, key)).encode('utf-8'), ending=b"\n")
except AttributeError as e:
raise CommandError(key + " is not defined in settings.CONSTANCE_CONFIG")
elif command == 'set':
try:
_set_constance_value(key, value)
except KeyError as e:
raise CommandError(key + " is not defined in settings.CONSTANCE_CONFIG")
except ValidationError as e:
raise CommandError(", ".join(e))
elif command == 'list':
for k, v in get_values().items():
self.stdout.write("{}\t{}".format(k, v).encode('utf-8'), ending=b"\n")

View file

@ -143,7 +143,7 @@ Note: Use later evaluated strings instead of direct classes for the field and wi
}
Ordered Fields in Django Admin
-----------------------
------------------------------
In order to Order the fields , you can use OrderedDict collection. Here is an example:
@ -234,6 +234,45 @@ any other variable, e.g.:
to signup for our newletter.
{% endif %}
Command Line
^^^^^^^^^^^^
Constance settings can be get/set on the command line with the manage command `constance`
Available options are:
list - output all values in a tab-separated format::
$ ./manage.py constance list
THE_ANSWER 42
SITE_NAME My Title
get KEY - output a single values::
$ ./manage.py constance get THE_ANSWER
42
set KEY VALUE - set a single value::
$ ./manage.py constance set SITE_NAME "Another Title"
If the value contains spaces it should be wrapped in quotes.
.. note:: Set values are validated as per in admin, an error will be raised if validation fails:
Eg, given this config as per the example app::
CONSTANCE_CONFIG = {
...
'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"),
}
Then setting an invalid date will fail as follow::
$ ./manage.py constance set DATE_ESTABLISHED '1999-12-00'
CommandError: Enter a valid date.
Editing
-------

View file

@ -64,7 +64,7 @@ CONSTANCE_CONFIG = {
'LONG_VALUE': (long_value, 'some looong int'),
'BOOL_VALUE': (True, 'true or false'),
'STRING_VALUE': ('Hello world', 'greetings'),
'UNICODE_VALUE': (six.u('Rivière-Bonjour'), 'greetings'),
'UNICODE_VALUE': (u'Rivière-Bonjour', 'greetings'),
'DECIMAL_VALUE': (Decimal('0.1'), 'the first release version'),
'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24),
'time of the first commit'),

View file

@ -22,7 +22,7 @@ class StorageTestsMixin(object):
self.assertEqual(self.config.LONG_VALUE, long(123456))
self.assertEqual(self.config.BOOL_VALUE, True)
self.assertEqual(self.config.STRING_VALUE, 'Hello world')
self.assertEqual(self.config.UNICODE_VALUE, six.u('Rivière-Bonjour'))
self.assertEqual(self.config.UNICODE_VALUE, u'Rivière-Bonjour')
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('0.1'))
self.assertEqual(self.config.DATETIME_VALUE, datetime(2010, 8, 23, 11, 29, 24))
self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536)
@ -36,7 +36,7 @@ class StorageTestsMixin(object):
self.config.LONG_VALUE = long(654321)
self.config.BOOL_VALUE = False
self.config.STRING_VALUE = 'Beware the weeping angel'
self.config.UNICODE_VALUE = six.u('Québec')
self.config.UNICODE_VALUE = u'Québec'
self.config.DECIMAL_VALUE = Decimal('1.2')
self.config.DATETIME_VALUE = datetime(1977, 10, 2)
self.config.FLOAT_VALUE = 2.718281845905
@ -50,7 +50,7 @@ class StorageTestsMixin(object):
self.assertEqual(self.config.LONG_VALUE, long(654321))
self.assertEqual(self.config.BOOL_VALUE, False)
self.assertEqual(self.config.STRING_VALUE, 'Beware the weeping angel')
self.assertEqual(self.config.UNICODE_VALUE, six.u('Québec'))
self.assertEqual(self.config.UNICODE_VALUE, u'Québec')
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2'))
self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2))
self.assertEqual(self.config.FLOAT_VALUE, 2.718281845905)
@ -74,7 +74,7 @@ class StorageTestsMixin(object):
# set some values and leave out others
self.config.LONG_VALUE = long(654321)
self.config.BOOL_VALUE = False
self.config.UNICODE_VALUE = six.u('Québec')
self.config.UNICODE_VALUE = u'Québec'
self.config.DECIMAL_VALUE = Decimal('1.2')
self.config.DATETIME_VALUE = datetime(1977, 10, 2)
self.config.DATE_VALUE = date(2001, 12, 20)
@ -84,7 +84,7 @@ class StorageTestsMixin(object):
self.assertEqual(self.config.LONG_VALUE, long(654321))
self.assertEqual(self.config.BOOL_VALUE, False)
self.assertEqual(self.config.STRING_VALUE, 'Hello world') # this should be the default value
self.assertEqual(self.config.UNICODE_VALUE, six.u('Québec'))
self.assertEqual(self.config.UNICODE_VALUE, u'Québec')
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2'))
self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2))
self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536) # this should be the default value

63
tests/test_cli.py Normal file
View file

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
from textwrap import dedent
from django.core.management import call_command, CommandError
from django.test import TransactionTestCase
from django.utils.encoding import smart_str
from django.utils.six import StringIO
from constance import config
class CliTestCase(TransactionTestCase):
def setUp(self):
self.out = StringIO()
def test_help(self):
try:
call_command('constance', '--help')
except SystemExit:
pass
def test_list(self):
call_command('constance', 'list', stdout=self.out)
self.assertEqual(set(self.out.getvalue().splitlines()), set(dedent(smart_str(
u""" BOOL_VALUE True
EMAIL_VALUE test@example.com
INT_VALUE 1
LINEBREAK_VALUE Spam spam
DATE_VALUE 2010-12-24
TIME_VALUE 23:59:59
LONG_VALUE 123456
STRING_VALUE Hello world
UNICODE_VALUE Rivière-Bonjour
CHOICE_VALUE yes
DECIMAL_VALUE 0.1
DATETIME_VALUE 2010-08-23 11:29:24
FLOAT_VALUE 3.1415926536
""")).splitlines()))
def test_get(self):
call_command('constance', *('get EMAIL_VALUE'.split()), stdout=self.out)
self.assertEqual(self.out.getvalue().strip(), "test@example.com")
def test_set(self):
call_command('constance', *('set EMAIL_VALUE blah@example.com'.split()), stdout=self.out)
self.assertEqual(config.EMAIL_VALUE, "blah@example.com")
def test_get_invalid_name(self):
self.assertRaisesMessage(CommandError, "NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG",
call_command, 'constance', 'get', 'NOT_A_REAL_CONFIG')
def test_set_invalid_name(self):
self.assertRaisesMessage(CommandError, "NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG",
call_command, 'constance', 'set', 'NOT_A_REAL_CONFIG', 'foo')
def test_set_invalid_value(self):
self.assertRaisesMessage(CommandError, "Enter a valid email address.",
call_command, 'constance', 'set', 'EMAIL_VALUE', 'not a valid email')

34
tests/test_utils.py Normal file
View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
import datetime
from decimal import Decimal
from constance.admin import get_values
from constance.management.commands.constance import _set_constance_value
from django.core.exceptions import ValidationError
from django.test import TestCase
class UtilsTestCase(TestCase):
def test_set_value_validation(self):
self.assertRaisesMessage(ValidationError, 'Enter a whole number.', _set_constance_value, 'INT_VALUE', 'foo')
self.assertRaisesMessage(ValidationError, 'Enter a valid email address.', _set_constance_value, 'EMAIL_VALUE', 'not a valid email')
def test_get_values(self):
self.assertEqual(get_values(), {
'FLOAT_VALUE': 3.1415926536,
'BOOL_VALUE': True,
'EMAIL_VALUE': 'test@example.com',
'INT_VALUE': 1,
'CHOICE_VALUE': 'yes',
'TIME_VALUE': datetime.time(23, 59, 59),
'DATE_VALUE': datetime.date(2010, 12, 24),
'LINEBREAK_VALUE': 'Spam spam',
'DECIMAL_VALUE': Decimal('0.1'),
'STRING_VALUE': 'Hello world',
'UNICODE_VALUE': u'Rivière-Bonjour',
'DATETIME_VALUE': datetime.datetime(2010, 8, 23, 11, 29, 24),
'LONG_VALUE': 123456
})