1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-27 19:05:16 +00:00

use customized constance (#4191)

This commit is contained in:
王健辉 2019-10-25 15:01:41 +08:00 committed by Daniel Pan
parent 749db0bdbf
commit d2555ab137
25 changed files with 1025 additions and 1 deletions

View File

@ -3,7 +3,6 @@ future
captcha
django-compressor
django-statici18n
django-constance
django-post_office
django-webpack_loader
gunicorn

View File

@ -0,0 +1,15 @@
from django.utils.functional import LazyObject
from . import checks
__version__ = '2.4.0'
default_app_config = 'constance.apps.ConstanceConfig'
class LazyConfig(LazyObject):
def _setup(self):
from .base import Config
self._wrapped = Config()
config = LazyConfig()

View File

@ -0,0 +1,330 @@
from collections import OrderedDict
from datetime import datetime, date, time, timedelta
from decimal import Decimal
from operator import itemgetter
import hashlib
from django import forms, VERSION, conf
from django.apps import apps
from django.conf.urls import url
from django.contrib import admin, messages
from django.contrib.admin import widgets
from django.contrib.admin.options import csrf_protect_m
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.core.files.storage import default_storage
from django.forms import fields
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.encoding import smart_bytes
from django.utils.formats import localize
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
import six
from . import LazyConfig, settings
from .checks import get_inconsistent_fieldnames
config = LazyConfig()
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10})
INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET})
STRING_LIKE = (fields.CharField, {
'widget': forms.Textarea(attrs={'rows': 3}),
'required': False,
})
FIELDS = {
bool: (fields.BooleanField, {'required': False}),
int: INTEGER_LIKE,
Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}),
str: STRING_LIKE,
datetime: (
fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}
),
timedelta: (
fields.DurationField, {'widget': widgets.AdminTextInputWidget}
),
date: (fields.DateField, {'widget': widgets.AdminDateWidget}),
time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}),
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}),
}
def parse_additional_fields(fields):
for key in fields:
field = list(fields[key])
if len(field) == 1:
field.append({})
field[0] = import_string(field[0])
if 'widget' in field[1]:
klass = import_string(field[1]['widget'])
field[1]['widget'] = klass(
**(field[1].get('widget_kwargs', {}) or {})
)
if 'widget_kwargs' in field[1]:
del field[1]['widget_kwargs']
fields[key] = field
return fields
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS))
if not six.PY3:
FIELDS.update({
long: INTEGER_LIKE,
unicode: STRING_LIKE,
})
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)))
return initial
class ConstanceForm(forms.Form):
version = forms.CharField(widget=forms.HiddenInput)
def __init__(self, initial, *args, **kwargs):
super(ConstanceForm, self).__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.md5()
for name, options in settings.CONFIG.items():
default = options[0]
if len(options) == 3:
config_type = options[2]
if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type):
raise ImproperlyConfigured(_("Default value type must be "
"equal to declared config "
"parameter type. Please fix "
"the default value of "
"'%(name)s'.")
% {'name': name})
else:
config_type = type(default)
if config_type not in FIELDS:
raise ImproperlyConfigured(_("Constance doesn't support "
"config values of the type "
"%(config_type)s. Please fix "
"the value of '%(name)s'.")
% {'config_type': config_type,
'name': name})
field_class, kwargs = FIELDS[config_type]
self.fields[name] = field_class(label=name, **kwargs)
version_hash.update(smart_bytes(initial.get(name, '')))
self.initial['version'] = version_hash.hexdigest()
def save(self):
for file_field in self.files:
file = self.cleaned_data[file_field]
self.cleaned_data[file_field] = default_storage.save(file.name, file)
for name in settings.CONFIG:
current = getattr(config, name)
new = self.cleaned_data[name]
if conf.settings.USE_TZ and isinstance(current, datetime) and not timezone.is_aware(current):
current = timezone.make_aware(current)
if current != new:
setattr(config, name, new)
def clean_version(self):
value = self.cleaned_data['version']
if settings.IGNORE_ADMIN_VERSION_CHECK:
return value
if value != self.initial['version']:
raise forms.ValidationError(_('The settings have been modified '
'by someone else. Please reload the '
'form and resubmit your changes.'))
return value
def clean(self):
cleaned_data = super(ConstanceForm, self).clean()
if not settings.CONFIG_FIELDSETS:
return cleaned_data
if get_inconsistent_fieldnames():
raise forms.ValidationError(_('CONSTANCE_CONFIG_FIELDSETS is missing '
'field(s) that exists in CONSTANCE_CONFIG.'))
return cleaned_data
class ConstanceAdmin(admin.ModelAdmin):
change_list_template = 'admin/constance/change_list.html'
change_list_form = ConstanceForm
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.module_name
return [
url(r'^$',
self.admin_site.admin_view(self.changelist_view),
name='%s_%s_changelist' % info),
url(r'^$',
self.admin_site.admin_view(self.changelist_view),
name='%s_%s_add' % info),
]
def get_config_value(self, name, options, form, initial):
default, help_text = options[0], options[1]
# First try to load the value from the actual backend
value = initial.get(name)
# Then if the returned value is None, get the default
if value is None:
value = getattr(config, name)
config_value = {
'name': name,
'default': localize(default),
'raw_default': default,
'help_text': _(help_text),
'value': localize(value),
'modified': localize(value) != localize(default),
'form_field': form[name],
'is_date': isinstance(default, date),
'is_datetime': isinstance(default, datetime),
'is_checkbox': isinstance(form[name].field.widget, forms.CheckboxInput),
'is_file': isinstance(form[name].field.widget, forms.FileInput),
}
return config_value
def get_changelist_form(self, request):
"""
Returns a Form class for use in the changelist_view.
"""
# Defaults to self.change_list_form in order to preserve backward
# compatibility
return self.change_list_form
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
if not self.has_change_permission(request, None):
raise PermissionDenied
initial = get_values()
form_cls = self.get_changelist_form(request)
form = form_cls(initial=initial)
if request.method == 'POST':
form = form_cls(
data=request.POST, files=request.FILES, initial=initial
)
if form.is_valid():
form.save()
messages.add_message(
request,
messages.SUCCESS,
_('Live settings updated successfully.'),
)
return HttpResponseRedirect('.')
context = dict(
self.admin_site.each_context(request),
config_values=[],
title=self.model._meta.app_config.verbose_name,
app_label='constance',
opts=self.model._meta,
form=form,
media=self.media + form.media,
icon_type='gif' if VERSION < (1, 9) else 'svg',
)
for name, options in settings.CONFIG.items():
context['config_values'].append(
self.get_config_value(name, options, form, initial)
)
if settings.CONFIG_FIELDSETS:
context['fieldsets'] = []
for fieldset_title, fields_list in settings.CONFIG_FIELDSETS.items():
absent_fields = [field for field in fields_list
if field not in settings.CONFIG]
assert not any(absent_fields), (
"CONSTANCE_CONFIG_FIELDSETS contains field(s) that does "
"not exist: %s" % ', '.join(absent_fields))
config_values = []
for name in fields_list:
options = settings.CONFIG.get(name)
if options:
config_values.append(
self.get_config_value(name, options, form, initial)
)
context['fieldsets'].append({
'title': fieldset_title,
'config_values': config_values
})
if not isinstance(settings.CONFIG_FIELDSETS, OrderedDict):
context['fieldsets'].sort(key=itemgetter('title'))
if not isinstance(settings.CONFIG, OrderedDict):
context['config_values'].sort(key=itemgetter('name'))
request.current_app = self.admin_site.name
return TemplateResponse(request, self.change_list_template, context)
def has_add_permission(self, *args, **kwargs):
return False
def has_delete_permission(self, *args, **kwargs):
return False
def has_change_permission(self, request, obj=None):
if settings.SUPERUSER_ONLY:
return request.user.is_superuser
return super(ConstanceAdmin, self).has_change_permission(request, obj)
class Config(object):
class Meta(object):
app_label = 'constance'
object_name = 'Config'
model_name = module_name = 'config'
verbose_name_plural = _('config')
abstract = False
swapped = False
def get_ordered_objects(self):
return False
def get_change_permission(self):
return 'change_%s' % self.model_name
@property
def app_config(self):
return apps.get_app_config(self.app_label)
@property
def label(self):
return '%s.%s' % (self.app_label, self.object_name)
@property
def label_lower(self):
return '%s.%s' % (self.app_label, self.model_name)
_meta = Meta()
admin.site.register([Config], ConstanceAdmin)

View File

@ -0,0 +1,35 @@
from django.db.models import signals
from django.apps import AppConfig
class ConstanceConfig(AppConfig):
name = 'constance'
verbose_name = 'Constance'
def ready(self):
super(ConstanceConfig, self).ready()
signals.post_migrate.connect(self.create_perm,
dispatch_uid='constance.create_perm')
def create_perm(self, using=None, *args, **kwargs):
"""
Creates a fake content type and permission
to be able to check for permissions
"""
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
constance_dbs = getattr(settings, 'CONSTANCE_DBS', None)
if constance_dbs is not None and using not in constance_dbs:
return
if ContentType._meta.installed and Permission._meta.installed:
content_type, created = ContentType.objects.using(using).get_or_create(
app_label='constance',
model='config',
)
permission, created = Permission.objects.using(using).get_or_create(
content_type=content_type,
codename='change_config',
defaults={'name': 'Can change config'})

View File

@ -0,0 +1,26 @@
"""
Defines the base constance backend
"""
class Backend(object):
def get(self, key):
"""
Get the key from the backend store and return the value.
Return None if not found.
"""
raise NotImplementedError
def mget(self, keys):
"""
Get the keys from the backend store and return a list of the values.
Return an empty list if not found.
"""
raise NotImplementedError
def set(self, key, value):
"""
Add the value to the backend store given the key.
"""
raise NotImplementedError

View File

@ -0,0 +1,111 @@
from django.core.cache import caches
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ImproperlyConfigured
from django.db import OperationalError, ProgrammingError
from django.db.models.signals import post_save
from .. import Backend
from ... import settings, signals, config
class DatabaseBackend(Backend):
def __init__(self):
from .models import Constance
self._model = Constance
self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled'
if not self._model._meta.installed:
raise ImproperlyConfigured(
"The constance.backends.database app isn't installed "
"correctly. Make sure it's in your INSTALLED_APPS setting.")
if settings.DATABASE_CACHE_BACKEND:
self._cache = caches[settings.DATABASE_CACHE_BACKEND]
if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured(
"The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a "
"subclass of Django's local-memory backend (%r). Please "
"set it to a backend that supports cross-process caching."
% settings.DATABASE_CACHE_BACKEND)
else:
self._cache = None
self.autofill()
# Clear simple cache.
post_save.connect(self.clear, sender=self._model)
def add_prefix(self, key):
return "%s%s" % (self._prefix, key)
def autofill(self):
if not self._autofill_timeout or not self._cache:
return
full_cachekey = self.add_prefix(self._autofill_cachekey)
if self._cache.get(full_cachekey):
return
autofill_values = {}
autofill_values[full_cachekey] = 1
for key, value in self.mget(settings.CONFIG):
autofill_values[self.add_prefix(key)] = value
self._cache.set_many(autofill_values, timeout=self._autofill_timeout)
def mget(self, keys):
if not keys:
return
keys = {self.add_prefix(key): key for key in keys}
try:
stored = self._model._default_manager.filter(key__in=keys)
for const in stored:
yield keys[const.key], const.value
except (OperationalError, ProgrammingError):
pass
def get(self, key):
key = self.add_prefix(key)
if self._cache:
value = self._cache.get(key)
if value is None:
self.autofill()
value = self._cache.get(key)
else:
value = None
if value is None:
try:
value = self._model._default_manager.get(key=key).value
except (OperationalError, ProgrammingError, self._model.DoesNotExist):
pass
else:
if self._cache:
self._cache.add(key, value)
return value
def set(self, key, value):
key = self.add_prefix(key)
try:
constance = self._model._default_manager.get(key=key)
except (OperationalError, ProgrammingError):
# database is not created, noop
return
except self._model.DoesNotExist:
old_value = None
constance = self._model._default_manager.create(key=key, value=value)
else:
old_value = constance.value
constance.value = value
constance.save()
if self._cache:
self._cache.set(key, value)
signals.config_updated.send(
sender=config, key=key, old_value=old_value, new_value=value
)
def clear(self, sender, instance, created, **kwargs):
if self._cache and not created:
keys = [self.add_prefix(k) for k in settings.CONFIG]
keys.append(self.add_prefix(self._autofill_cachekey))
self._cache.delete_many(keys)
self.autofill()

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import picklefield.fields
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='Constance',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True,
auto_created=True, serialize=False)),
('key', models.CharField(unique=True, max_length=255, db_column='constance_key')),
('value', picklefield.fields.PickledObjectField(editable=False)),
],
options={
'verbose_name': 'constance',
'verbose_name_plural': 'constances',
'db_table': 'constance_config',
},
bases=(models.Model,),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.1.5 on 2019-01-30 04:04
from django.db import migrations
import picklefield.fields
class Migration(migrations.Migration):
dependencies = [
('database', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='constance',
name='value',
field=picklefield.fields.PickledObjectField(blank=True, editable=False, null=True),
),
]

View File

@ -0,0 +1,24 @@
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
try:
from picklefield import PickledObjectField
except ImportError:
raise ImproperlyConfigured("Couldn't find the the 3rd party app "
"django-picklefield which is required for "
"the constance database backend.")
class Constance(models.Model):
key = models.CharField(max_length=255, unique=True, db_column='constance_key')
value = PickledObjectField(null=True, blank=True)
class Meta:
verbose_name = _('constance')
verbose_name_plural = _('constances')
db_table = 'constance_config'
def __unicode__(self):
return self.key

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from south.db import db
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Constance'
db.create_table('constance_config', (
('id', self.gf('django.db.models.fields.AutoField')(
primary_key=True)),
('key', self.gf('django.db.models.fields.TextField')()),
('value', self.gf('picklefield.fields.PickledObjectField')()),
))
db.send_create_signal('database', ['Constance'])
def backwards(self, orm):
# Deleting model 'Constance'
db.delete_table('constance_config')
models = {
'database.constance': {
'Meta': {'object_name': 'Constance',
'db_table': "'constance_config'"},
'id': ('django.db.models.fields.AutoField', [],
{'primary_key': 'True'}),
'key': ('django.db.models.fields.TextField', [], {}),
'value': ('picklefield.fields.PickledObjectField', [], {})
}
}
complete_apps = ['database']

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from south.db import db
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Constance.key'
db.alter_column('constance_config', 'key',
self.gf('django.db.models.fields.CharField')(
max_length=255))
# Adding unique constraint on 'Constance', fields ['key']
db.create_unique('constance_config', ['key'])
def backwards(self, orm):
# Removing unique constraint on 'Constance', fields ['key']
db.delete_unique('constance_config', ['key'])
# Changing field 'Constance.key'
db.alter_column('constance_config', 'key',
self.gf('django.db.models.fields.TextField')())
models = {
'database.constance': {
'Meta': {'object_name': 'Constance',
'db_table': "'constance_config'"},
'id': ('django.db.models.fields.AutoField', [],
{'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [],
{'unique': 'True', 'max_length': '255'}),
'value': ('picklefield.fields.PickledObjectField', [], {})
}
}
complete_apps = ['database']

View File

@ -0,0 +1,54 @@
from django.core.exceptions import ImproperlyConfigured
import six
from . import Backend
from .. import settings, utils, signals, config
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
class RedisBackend(Backend):
def __init__(self):
super(RedisBackend, self).__init__()
self._prefix = settings.REDIS_PREFIX
connection_cls = settings.REDIS_CONNECTION_CLASS
if connection_cls is not None:
self._rd = utils.import_module_attr(connection_cls)()
else:
try:
import redis
except ImportError:
raise ImproperlyConfigured(
"The Redis backend requires redis-py to be installed.")
if isinstance(settings.REDIS_CONNECTION, six.string_types):
self._rd = redis.from_url(settings.REDIS_CONNECTION)
else:
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
def add_prefix(self, key):
return "%s%s" % (self._prefix, key)
def get(self, key):
value = self._rd.get(self.add_prefix(key))
if value:
return loads(value)
return None
def mget(self, keys):
if not keys:
return
prefixed_keys = [self.add_prefix(key) for key in keys]
for key, value in six.moves.zip(keys, self._rd.mget(prefixed_keys)):
if value:
yield key, loads(value)
def set(self, key, value):
old_value = self.get(key)
self._rd.set(self.add_prefix(key), dumps(value))
signals.config_updated.send(
sender=config, key=key, old_value=old_value, new_value=value
)

View File

@ -0,0 +1,37 @@
from . import settings, utils
class Config(object):
"""
The global config wrapper that handles the backend.
"""
def __init__(self):
super(Config, self).__setattr__('_backend',
utils.import_module_attr(settings.BACKEND)())
def __getattr__(self, key):
try:
if not len(settings.CONFIG[key]) in (2, 3):
raise AttributeError(key)
default = settings.CONFIG[key][0]
except KeyError:
raise AttributeError(key)
if not settings.ENABLED:
return default
result = self._backend.get(key)
if result is None:
result = default
# Do not set default value to db/redis when there is no result.
# setattr(self, key, default)
return result
return result
def __setattr__(self, key, value):
if key not in settings.CONFIG:
raise AttributeError(key)
self._backend.set(key, value)
def __dir__(self):
return settings.CONFIG.keys()

View File

@ -0,0 +1,42 @@
from django.core import checks
from django.utils.translation import ugettext_lazy as _
from . import settings
@checks.register("constance")
def check_fieldsets(*args, **kwargs):
"""
A Django system check to make sure that, if defined, CONFIG_FIELDSETS accounts for
every entry in settings.CONFIG.
"""
if hasattr(settings, "CONFIG_FIELDSETS") and settings.CONFIG_FIELDSETS:
inconsistent_fieldnames = get_inconsistent_fieldnames()
if inconsistent_fieldnames:
return [
checks.Warning(
_(
"CONSTANCE_CONFIG_FIELDSETS is missing "
"field(s) that exists in CONSTANCE_CONFIG."
),
hint=", ".join(sorted(inconsistent_fieldnames)),
obj="settings.CONSTANCE_CONFIG",
id="constance.E001",
)
]
return []
def get_inconsistent_fieldnames():
"""
Returns a set of keys from settings.CONFIG that are not accounted for in
settings.CONFIG_FIELDSETS.
If there are no fieldnames in settings.CONFIG_FIELDSETS, returns an empty set.
"""
field_name_list = []
for fieldset_title, fields_list in settings.CONFIG_FIELDSETS.items():
for field_name in fields_list:
field_name_list.append(field_name)
if not field_name_list:
return {}
return set(set(settings.CONFIG.keys()) - set(field_name_list))

View File

@ -0,0 +1,15 @@
import constance
def config(request):
"""
Simple context processor that puts the config into every
RequestContext. Just make sure you have a setting like this:
TEMPLATE_CONTEXT_PROCESSORS = (
# ...
'constance.context_processors.config',
)
"""
return {"config": constance.config}

View File

@ -0,0 +1,77 @@
# -*- 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 django import VERSION
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')
# API changed in Django>=2.1. cmd argument was removed.
parser_list = self._subparsers_add_parser(subparsers, 'list', cmd=self, help='list all Constance keys and their values')
parser_get = self._subparsers_add_parser(subparsers, '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 = self._subparsers_add_parser(subparsers, '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')
# use nargs='+' so that we pass a list to MultiValueField (eg SplitDateTimeField)
parser_set.add_argument('value', help='value to set', metavar='VALUE', nargs='+')
def _subparsers_add_parser(self, subparsers, name, **kwargs):
# API in Django >= 2.1 changed and removed cmd parameter from add_parser
if VERSION >= (2, 1) and 'cmd' in kwargs:
kwargs.pop('cmd')
return subparsers.add_parser(name, **kwargs)
def handle(self, command, key=None, value=None, *args, **options):
if command == 'get':
try:
self.stdout.write("{}".format(getattr(config, key)), ending="\n")
except AttributeError as e:
raise CommandError(key + " is not defined in settings.CONSTANCE_CONFIG")
elif command == 'set':
try:
if len(value) == 1:
# assume that if a single argument was passed, the field doesn't expect a list
value = value[0]
_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), ending="\n")

View File

@ -0,0 +1,47 @@
from django.conf import settings
BACKEND = getattr(
settings,
'CONSTANCE_BACKEND',
'constance.backends.redisd.RedisBackend'
)
CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {})
CONFIG_FIELDSETS = getattr(settings, 'CONSTANCE_CONFIG_FIELDSETS', {})
ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {})
DATABASE_CACHE_BACKEND = getattr(
settings,
'CONSTANCE_DATABASE_CACHE_BACKEND',
None
)
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(
settings,
'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT',
60 * 60 * 24
)
DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
REDIS_CONNECTION_CLASS = getattr(
settings,
'CONSTANCE_REDIS_CONNECTION_CLASS',
None
)
REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', {})
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)
IGNORE_ADMIN_VERSION_CHECK = getattr(
settings,
'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK',
False
)
ENABLED = getattr(settings, 'CONSTANCE_ENABLED', True)

View File

@ -0,0 +1,5 @@
import django.dispatch
config_updated = django.dispatch.Signal(
providing_args=['key', 'old_value', 'new_value']
)

View File

@ -0,0 +1 @@
from .utils import override_config

View File

@ -0,0 +1,85 @@
from functools import wraps
from django.test import SimpleTestCase
from django.test.utils import override_settings
from .. import config
__all__ = ('override_config',)
class override_config(override_settings):
"""
Decorator to modify constance setting for TestCase.
Based on django.test.utils.override_settings.
"""
def __init__(self, **kwargs):
super(override_config, self).__init__(**kwargs)
self.original_values = {}
def __call__(self, test_func):
"""
Modify the decorated function to override config values.
"""
if isinstance(test_func, type):
if not issubclass(test_func, SimpleTestCase):
raise Exception(
"Only subclasses of Django SimpleTestCase can be "
"decorated with override_config")
return self.modify_test_case(test_func)
else:
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
return inner
def modify_test_case(self, test_case):
"""
Override the config by modifying TestCase methods.
This method follows the Django <= 1.6 method of overriding the
_pre_setup and _post_teardown hooks rather than modifying the TestCase
itself.
"""
original_pre_setup = test_case._pre_setup
original_post_teardown = test_case._post_teardown
def _pre_setup(inner_self):
self.enable()
original_pre_setup(inner_self)
def _post_teardown(inner_self):
original_post_teardown(inner_self)
self.disable()
test_case._pre_setup = _pre_setup
test_case._post_teardown = _post_teardown
return test_case
def enable(self):
"""
Store original config values and set overridden values.
"""
# Store the original values to an instance variable
for config_key in self.options:
self.original_values[config_key] = getattr(config, config_key)
# Update config with the overriden values
self.unpack_values(self.options)
def disable(self):
"""
Set original values to the config.
"""
self.unpack_values(self.original_values)
@staticmethod
def unpack_values(options):
"""
Unpack values from the given dict to config.
"""
for name, value in options.items():
setattr(config, name, value)

View File

@ -0,0 +1,6 @@
from importlib import import_module
def import_module_attr(path):
package, module = path.rsplit('.', 1)
return getattr(import_module(package), module)