mirror of
https://github.com/jazzband/django-constance.git
synced 2026-03-16 22:40:24 +00:00
Merge pull request #180 from jazzband/backport_cli_squashed
Backport command functonality from django-constance-cli (squashed)
This commit is contained in:
commit
7934de8ede
9 changed files with 222 additions and 16 deletions
|
|
@ -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)
|
||||
|
|
|
|||
0
constance/management/__init__.py
Normal file
0
constance/management/__init__.py
Normal file
0
constance/management/commands/__init__.py
Normal file
0
constance/management/commands/__init__.py
Normal file
62
constance/management/commands/constance.py
Normal file
62
constance/management/commands/constance.py
Normal 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")
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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
63
tests/test_cli.py
Normal 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
34
tests/test_utils.py
Normal 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
|
||||
})
|
||||
Loading…
Reference in a new issue