mirror of
https://github.com/jazzband/django-constance.git
synced 2026-03-16 22:40:24 +00:00
Add ruff format & lint (isort only) (#560)
This commit is contained in:
parent
554eedc7c2
commit
57083bbed2
47 changed files with 484 additions and 386 deletions
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
|
|
@ -3,6 +3,25 @@ name: Test
|
|||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ruff-format:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
version: 0.5.0
|
||||
args: 'format --check'
|
||||
|
||||
ruff-lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
version: 0.5.0
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from django.utils.functional import LazyObject
|
|||
class LazyConfig(LazyObject):
|
||||
def _setup(self):
|
||||
from .base import Config
|
||||
|
||||
self._wrapped = Config()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from collections import OrderedDict
|
||||
from datetime import date, datetime
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
|
||||
from django import forms, get_version
|
||||
from django import forms
|
||||
from django import get_version
|
||||
from django.apps import apps
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin.options import csrf_protect_m
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponseRedirect
|
||||
|
|
@ -13,7 +16,8 @@ from django.urls import path
|
|||
from django.utils.formats import localize
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import LazyConfig, settings
|
||||
from . import LazyConfig
|
||||
from . import settings
|
||||
from .forms import ConstanceForm
|
||||
from .utils import get_values
|
||||
|
||||
|
|
@ -31,12 +35,8 @@ class ConstanceAdmin(admin.ModelAdmin):
|
|||
def get_urls(self):
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
return [
|
||||
path('',
|
||||
self.admin_site.admin_view(self.changelist_view),
|
||||
name='%s_%s_changelist' % info),
|
||||
path('',
|
||||
self.admin_site.admin_view(self.changelist_view),
|
||||
name='%s_%s_add' % info),
|
||||
path('', self.admin_site.admin_view(self.changelist_view), name='%s_%s_changelist' % info),
|
||||
path('', self.admin_site.admin_view(self.changelist_view), name='%s_%s_add' % info),
|
||||
]
|
||||
|
||||
def get_config_value(self, name, options, form, initial):
|
||||
|
|
@ -88,9 +88,7 @@ class ConstanceAdmin(admin.ModelAdmin):
|
|||
form_cls = self.get_changelist_form(request)
|
||||
form = form_cls(initial=initial, request=request)
|
||||
if request.method == 'POST' and request.user.has_perm('constance.change_config'):
|
||||
form = form_cls(
|
||||
data=request.POST, files=request.FILES, initial=initial, request=request
|
||||
)
|
||||
form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.add_message(
|
||||
|
|
@ -117,9 +115,7 @@ class ConstanceAdmin(admin.ModelAdmin):
|
|||
django_version=get_version(),
|
||||
)
|
||||
for name, options in settings.CONFIG.items():
|
||||
context['config_values'].append(
|
||||
self.get_config_value(name, options, form, initial)
|
||||
)
|
||||
context['config_values'].append(self.get_config_value(name, options, form, initial))
|
||||
|
||||
if settings.CONFIG_FIELDSETS:
|
||||
if isinstance(settings.CONFIG_FIELDSETS, dict):
|
||||
|
|
@ -136,24 +132,18 @@ class ConstanceAdmin(admin.ModelAdmin):
|
|||
fields_list = fieldset_data
|
||||
collapse = False
|
||||
|
||||
absent_fields = [field for field in fields_list
|
||||
if field not in settings.CONFIG]
|
||||
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))
|
||||
'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)
|
||||
)
|
||||
fieldset_context = {
|
||||
'title': fieldset_title,
|
||||
'config_values': config_values
|
||||
}
|
||||
config_values.append(self.get_config_value(name, options, form, initial))
|
||||
fieldset_context = {'title': fieldset_title, 'config_values': config_values}
|
||||
|
||||
if collapse:
|
||||
fieldset_context['collapse'] = True
|
||||
|
|
@ -209,4 +199,4 @@ class Config:
|
|||
_meta = Meta()
|
||||
|
||||
|
||||
admin.site.register([Config], ConstanceAdmin)
|
||||
admin.site.register([Config], ConstanceAdmin)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,3 @@ class ConstanceConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import checks
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ Defines the base constance backend
|
|||
|
||||
|
||||
class Backend:
|
||||
|
||||
def get(self, key):
|
||||
"""
|
||||
Get the key from the backend store and return the value.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
from django.core.cache import caches
|
||||
from django.core.cache.backends.locmem import LocMemCache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import (
|
||||
IntegrityError,
|
||||
OperationalError,
|
||||
ProgrammingError,
|
||||
transaction,
|
||||
)
|
||||
from django.db import IntegrityError
|
||||
from django.db import OperationalError
|
||||
from django.db import ProgrammingError
|
||||
from django.db import transaction
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from constance import config
|
||||
from constance import settings
|
||||
from constance import signals
|
||||
from constance.backends import Backend
|
||||
from constance import settings, signals, config
|
||||
|
||||
|
||||
class DatabaseBackend(Backend):
|
||||
def __init__(self):
|
||||
from constance.models import Constance
|
||||
|
||||
self._model = Constance
|
||||
self._prefix = settings.DATABASE_PREFIX
|
||||
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
|
||||
|
|
@ -24,16 +25,17 @@ class DatabaseBackend(Backend):
|
|||
if self._model._meta.app_config is None:
|
||||
raise ImproperlyConfigured(
|
||||
"The constance.backends.database app isn't installed "
|
||||
"correctly. Make sure it's in your INSTALLED_APPS setting.")
|
||||
"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 "
|
||||
'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)
|
||||
'set it to a backend that supports cross-process caching.' % settings.DATABASE_CACHE_BACKEND
|
||||
)
|
||||
else:
|
||||
self._cache = None
|
||||
self.autofill()
|
||||
|
|
@ -41,7 +43,7 @@ class DatabaseBackend(Backend):
|
|||
post_save.connect(self.clear, sender=self._model)
|
||||
|
||||
def add_prefix(self, key):
|
||||
return f"{self._prefix}{key}"
|
||||
return f'{self._prefix}{key}'
|
||||
|
||||
def autofill(self):
|
||||
if not self._autofill_timeout or not self._cache:
|
||||
|
|
@ -114,9 +116,7 @@ class DatabaseBackend(Backend):
|
|||
if self._cache:
|
||||
self._cache.set(key, value)
|
||||
|
||||
signals.config_updated.send(
|
||||
sender=config, key=key, old_value=old_value, new_value=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:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
from threading import Lock
|
||||
|
||||
from .. import config
|
||||
from .. import signals
|
||||
from . import Backend
|
||||
from .. import signals, config
|
||||
|
||||
|
||||
class MemoryBackend(Backend):
|
||||
"""
|
||||
Simple in-memory backend that should be mostly used for testing purposes
|
||||
"""
|
||||
|
||||
_storage = {}
|
||||
_lock = Lock()
|
||||
|
||||
|
|
@ -33,6 +35,4 @@ class MemoryBackend(Backend):
|
|||
with self._lock:
|
||||
old_value = self._storage.get(key)
|
||||
self._storage[key] = value
|
||||
signals.config_updated.send(
|
||||
sender=config, key=key, old_value=old_value, new_value=value
|
||||
)
|
||||
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
from pickle import loads, dumps
|
||||
from pickle import dumps
|
||||
from pickle import loads
|
||||
from threading import RLock
|
||||
from time import monotonic
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from .. import config
|
||||
from .. import settings
|
||||
from .. import signals
|
||||
from .. import utils
|
||||
from . import Backend
|
||||
from .. import settings, utils, signals, config
|
||||
|
||||
|
||||
class RedisBackend(Backend):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._prefix = settings.REDIS_PREFIX
|
||||
|
|
@ -20,15 +23,14 @@ class RedisBackend(Backend):
|
|||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured(
|
||||
"The Redis backend requires redis-py to be installed.")
|
||||
raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.')
|
||||
if isinstance(settings.REDIS_CONNECTION, str):
|
||||
self._rd = redis.from_url(settings.REDIS_CONNECTION)
|
||||
else:
|
||||
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
|
||||
|
||||
def add_prefix(self, key):
|
||||
return f"{self._prefix}{key}"
|
||||
return f'{self._prefix}{key}'
|
||||
|
||||
def get(self, key):
|
||||
value = self._rd.get(self.add_prefix(key))
|
||||
|
|
@ -47,9 +49,7 @@ class RedisBackend(Backend):
|
|||
def set(self, key, value):
|
||||
old_value = self.get(key)
|
||||
self._rd.set(self.add_prefix(key), dumps(value, protocol=settings.REDIS_PICKLE_VERSION))
|
||||
signals.config_updated.send(
|
||||
sender=config, key=key, old_value=old_value, new_value=value
|
||||
)
|
||||
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
|
||||
|
||||
|
||||
class CachingRedisBackend(RedisBackend):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from . import settings, utils
|
||||
from . import settings
|
||||
from . import utils
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
The global config wrapper that handles the backend.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__setattr__('_backend',
|
||||
utils.import_module_attr(settings.BACKEND)())
|
||||
super().__setattr__('_backend', utils.import_module_attr(settings.BACKEND)())
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from typing import Tuple, Set, List
|
||||
from typing import List
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from django.core import checks
|
||||
from django.core.checks import CheckMessage
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@checks.register("constance")
|
||||
@checks.register('constance')
|
||||
def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
A Django system check to make sure that, if defined,
|
||||
|
|
@ -14,28 +17,22 @@ def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
|
|||
|
||||
errors = []
|
||||
|
||||
if hasattr(settings, "CONFIG_FIELDSETS") and settings.CONFIG_FIELDSETS:
|
||||
if hasattr(settings, 'CONFIG_FIELDSETS') and settings.CONFIG_FIELDSETS:
|
||||
missing_keys, extra_keys = get_inconsistent_fieldnames()
|
||||
if missing_keys:
|
||||
check = checks.Warning(
|
||||
_(
|
||||
"CONSTANCE_CONFIG_FIELDSETS is missing "
|
||||
"field(s) that exists in CONSTANCE_CONFIG."
|
||||
),
|
||||
hint=", ".join(sorted(missing_keys)),
|
||||
obj="settings.CONSTANCE_CONFIG",
|
||||
id="constance.E001",
|
||||
_('CONSTANCE_CONFIG_FIELDSETS is missing ' 'field(s) that exists in CONSTANCE_CONFIG.'),
|
||||
hint=', '.join(sorted(missing_keys)),
|
||||
obj='settings.CONSTANCE_CONFIG',
|
||||
id='constance.E001',
|
||||
)
|
||||
errors.append(check)
|
||||
if extra_keys:
|
||||
check = checks.Warning(
|
||||
_(
|
||||
"CONSTANCE_CONFIG_FIELDSETS contains extra "
|
||||
"field(s) that does not exist in CONFIG."
|
||||
),
|
||||
hint=", ".join(sorted(extra_keys)),
|
||||
obj="settings.CONSTANCE_CONFIG",
|
||||
id="constance.E002",
|
||||
_('CONSTANCE_CONFIG_FIELDSETS contains extra ' 'field(s) that does not exist in CONFIG.'),
|
||||
hint=', '.join(sorted(extra_keys)),
|
||||
obj='settings.CONSTANCE_CONFIG',
|
||||
id='constance.E002',
|
||||
)
|
||||
errors.append(check)
|
||||
return errors
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ def config(request):
|
|||
)
|
||||
|
||||
"""
|
||||
return {"config": constance.config}
|
||||
return {'config': constance.config}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import hashlib
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from os.path import join
|
||||
|
||||
from django import conf, forms
|
||||
from django import conf
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin import widgets
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -15,7 +19,8 @@ from django.utils.module_loading import import_string
|
|||
from django.utils.text import normalize_newlines
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import LazyConfig, settings
|
||||
from . import LazyConfig
|
||||
from . import settings
|
||||
from .checks import get_inconsistent_fieldnames
|
||||
|
||||
config = LazyConfig()
|
||||
|
|
@ -23,27 +28,27 @@ 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,
|
||||
})
|
||||
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}
|
||||
),
|
||||
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])
|
||||
|
|
@ -55,9 +60,7 @@ def parse_additional_fields(fields):
|
|||
|
||||
if 'widget' in field[1]:
|
||||
klass = import_string(field[1]['widget'])
|
||||
field[1]['widget'] = klass(
|
||||
**(field[1].get('widget_kwargs', {}) or {})
|
||||
)
|
||||
field[1]['widget'] = klass(**(field[1].get('widget_kwargs', {}) or {}))
|
||||
|
||||
if 'widget_kwargs' in field[1]:
|
||||
del field[1]['widget_kwargs']
|
||||
|
|
@ -70,7 +73,6 @@ def parse_additional_fields(fields):
|
|||
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS))
|
||||
|
||||
|
||||
|
||||
class ConstanceForm(forms.Form):
|
||||
version = forms.CharField(widget=forms.HiddenInput)
|
||||
|
||||
|
|
@ -90,22 +92,29 @@ class ConstanceForm(forms.Form):
|
|||
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})
|
||||
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})
|
||||
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]
|
||||
if only_view:
|
||||
kwargs['disabled'] = True
|
||||
|
|
@ -139,9 +148,13 @@ class ConstanceForm(forms.Form):
|
|||
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.'))
|
||||
raise forms.ValidationError(
|
||||
_(
|
||||
'The settings have been modified '
|
||||
'by someone else. Please reload the '
|
||||
'form and resubmit your changes.'
|
||||
)
|
||||
)
|
||||
return value
|
||||
|
||||
def clean(self):
|
||||
|
|
@ -152,7 +165,8 @@ class ConstanceForm(forms.Form):
|
|||
|
||||
missing_keys, extra_keys = get_inconsistent_fieldnames()
|
||||
if missing_keys or extra_keys:
|
||||
raise forms.ValidationError(_('CONSTANCE_CONFIG_FIELDSETS is missing '
|
||||
'field(s) that exists in CONSTANCE_CONFIG.'))
|
||||
raise forms.ValidationError(
|
||||
_('CONSTANCE_CONFIG_FIELDSETS is missing ' 'field(s) that exists in CONSTANCE_CONFIG.')
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from django import VERSION
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.management import BaseCommand, CommandError
|
||||
from django.core.management import BaseCommand
|
||||
from django.core.management import CommandError
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ... import config
|
||||
from ...forms import ConstanceForm
|
||||
from ...utils import get_values
|
||||
from ...models import Constance
|
||||
from ...utils import get_values
|
||||
|
||||
|
||||
def _set_constance_value(key, value):
|
||||
|
|
@ -32,7 +33,9 @@ class Command(BaseCommand):
|
|||
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_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')
|
||||
|
|
@ -55,14 +58,12 @@ class Command(BaseCommand):
|
|||
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(str(getattr(config, key)), ending="\n")
|
||||
self.stdout.write(str(getattr(config, key)), ending='\n')
|
||||
except AttributeError as e:
|
||||
raise CommandError(key + " is not defined in settings.CONSTANCE_CONFIG")
|
||||
raise CommandError(key + ' is not defined in settings.CONSTANCE_CONFIG')
|
||||
|
||||
elif command == 'set':
|
||||
try:
|
||||
|
|
@ -72,25 +73,24 @@ class Command(BaseCommand):
|
|||
|
||||
_set_constance_value(key, value)
|
||||
except KeyError as e:
|
||||
raise CommandError(key + " is not defined in settings.CONSTANCE_CONFIG")
|
||||
raise CommandError(key + ' is not defined in settings.CONSTANCE_CONFIG')
|
||||
except ValidationError as e:
|
||||
raise CommandError(", ".join(e))
|
||||
raise CommandError(', '.join(e))
|
||||
|
||||
elif command == 'list':
|
||||
for k, v in get_values().items():
|
||||
self.stdout.write(f"{k}\t{v}", ending="\n")
|
||||
self.stdout.write(f'{k}\t{v}', ending='\n')
|
||||
|
||||
elif command == 'remove_stale_keys':
|
||||
|
||||
actual_keys = settings.CONSTANCE_CONFIG.keys()
|
||||
|
||||
stale_records = Constance.objects.exclude(key__in=actual_keys)
|
||||
if stale_records:
|
||||
self.stdout.write("The following record will be deleted:", ending="\n")
|
||||
self.stdout.write('The following record will be deleted:', ending='\n')
|
||||
else:
|
||||
self.stdout.write("There are no stale records in database.", ending="\n")
|
||||
self.stdout.write('There are no stale records in database.', ending='\n')
|
||||
|
||||
for stale_record in stale_records:
|
||||
self.stdout.write(f"{stale_record.key}\t{stale_record.value}", ending="\n")
|
||||
self.stdout.write(f'{stale_record.key}\t{stale_record.value}', ending='\n')
|
||||
|
||||
stale_records.delete()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from django.db import migrations, models
|
||||
import picklefield.fields
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from logging import getLogger
|
||||
|
||||
from django.core.management.color import no_style
|
||||
from django.db import migrations, DatabaseError
|
||||
|
||||
from django.db import DatabaseError
|
||||
from django.db import migrations
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -16,7 +16,9 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
|
|||
quoted_string = ', '.join([connection.ops.quote_name(item) for item in ['id', 'key', 'value']])
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(f'INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM constance_config', [])
|
||||
cursor.execute(
|
||||
f'INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM constance_config', []
|
||||
)
|
||||
cursor.execute('DROP TABLE constance_config', [])
|
||||
except DatabaseError as exc:
|
||||
logger.exception('copy data from old constance table to a new one')
|
||||
|
|
@ -29,7 +31,6 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('constance', '0001_initial')]
|
||||
|
||||
atomic = False
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_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.")
|
||||
raise ImproperlyConfigured(
|
||||
"Couldn't find the the 3rd party app "
|
||||
'django-picklefield which is required for '
|
||||
'the constance database backend.'
|
||||
)
|
||||
|
||||
|
||||
class Constance(models.Model):
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ import pickle
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
BACKEND = getattr(
|
||||
settings,
|
||||
'CONSTANCE_BACKEND',
|
||||
'constance.backends.redisd.RedisBackend'
|
||||
)
|
||||
BACKEND = getattr(settings, 'CONSTANCE_BACKEND', 'constance.backends.redisd.RedisBackend')
|
||||
|
||||
CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {})
|
||||
|
||||
|
|
@ -16,17 +12,9 @@ ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {})
|
|||
|
||||
FILE_ROOT = getattr(settings, 'CONSTANCE_FILE_ROOT', '')
|
||||
|
||||
DATABASE_CACHE_BACKEND = getattr(
|
||||
settings,
|
||||
'CONSTANCE_DATABASE_CACHE_BACKEND',
|
||||
None
|
||||
)
|
||||
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_CACHE_AUTOFILL_TIMEOUT = getattr(settings, 'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT', 60 * 60 * 24)
|
||||
|
||||
DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
|
||||
|
||||
|
|
@ -34,11 +22,7 @@ REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
|
|||
|
||||
REDIS_CACHE_TIMEOUT = getattr(settings, 'CONSTANCE_REDIS_CACHE_TIMEOUT', 60)
|
||||
|
||||
REDIS_CONNECTION_CLASS = getattr(
|
||||
settings,
|
||||
'CONSTANCE_REDIS_CONNECTION_CLASS',
|
||||
None
|
||||
)
|
||||
REDIS_CONNECTION_CLASS = getattr(settings, 'CONSTANCE_REDIS_CONNECTION_CLASS', None)
|
||||
|
||||
REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', {})
|
||||
|
||||
|
|
@ -46,8 +30,4 @@ REDIS_PICKLE_VERSION = getattr(settings, 'CONSTANCE_REDIS_PICKLE_VERSION', pickl
|
|||
|
||||
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)
|
||||
|
||||
IGNORE_ADMIN_VERSION_CHECK = getattr(
|
||||
settings,
|
||||
'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK',
|
||||
False
|
||||
)
|
||||
IGNORE_ADMIN_VERSION_CHECK = getattr(settings, 'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK', False)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ Pytest constance override config plugin.
|
|||
|
||||
Inspired by https://github.com/pytest-dev/pytest-django/.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from contextlib import ContextDecorator
|
||||
|
||||
import pytest
|
||||
|
||||
from constance import config as constance_config
|
||||
|
||||
|
||||
|
|
@ -13,13 +16,7 @@ def pytest_configure(config): # pragma: no cover
|
|||
"""
|
||||
Register override_config marker.
|
||||
"""
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
(
|
||||
"override_config(**kwargs): "
|
||||
"mark test to override django-constance config"
|
||||
)
|
||||
)
|
||||
config.addinivalue_line('markers', ('override_config(**kwargs): ' 'mark test to override django-constance config'))
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
|
|
@ -27,12 +24,10 @@ def pytest_runtest_call(item): # pragma: no cover
|
|||
"""
|
||||
Validate constance override marker params. Run test with overridden config.
|
||||
"""
|
||||
marker = item.get_closest_marker("override_config")
|
||||
marker = item.get_closest_marker('override_config')
|
||||
if marker is not None:
|
||||
if marker.args:
|
||||
pytest.fail(
|
||||
"Constance override can not not accept positional args"
|
||||
)
|
||||
pytest.fail('Constance override can not not accept positional args')
|
||||
with override_config(**marker.kwargs):
|
||||
yield
|
||||
else:
|
||||
|
|
@ -45,6 +40,7 @@ class override_config(ContextDecorator):
|
|||
|
||||
Act as context manager and decorator.
|
||||
"""
|
||||
|
||||
def enable(self):
|
||||
"""
|
||||
Store original config values and set overridden values.
|
||||
|
|
@ -71,7 +67,7 @@ class override_config(ContextDecorator):
|
|||
self.disable()
|
||||
|
||||
|
||||
@pytest.fixture(name="override_config")
|
||||
@pytest.fixture(name='override_config')
|
||||
def _override_config():
|
||||
"""
|
||||
Make override_config available as a function fixture.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class override_config(override_settings):
|
|||
|
||||
Based on django.test.utils.override_settings.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.original_values = {}
|
||||
|
|
@ -24,15 +25,15 @@ class override_config(override_settings):
|
|||
"""
|
||||
if isinstance(test_func, type):
|
||||
if not issubclass(test_func, SimpleTestCase):
|
||||
raise Exception(
|
||||
"Only subclasses of Django SimpleTestCase can be "
|
||||
"decorated with override_config")
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
from importlib import import_module
|
||||
|
||||
from . import LazyConfig, settings
|
||||
from . import LazyConfig
|
||||
from . import settings
|
||||
|
||||
config = LazyConfig()
|
||||
|
||||
|
||||
def import_module_attr(path):
|
||||
package, module = path.rsplit('.', 1)
|
||||
return getattr(import_module(package), module)
|
||||
|
||||
|
||||
def get_values():
|
||||
"""
|
||||
Get dictionary of values from the backend
|
||||
|
|
@ -15,9 +18,8 @@ def get_values():
|
|||
"""
|
||||
|
||||
# First load a mapping between config name and default value
|
||||
default_initial = ((name, options[0])
|
||||
for name, options in settings.CONFIG.items())
|
||||
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
|
||||
return initial
|
||||
|
|
|
|||
29
docs/conf.py
29
docs/conf.py
|
|
@ -3,9 +3,9 @@
|
|||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ def get_version():
|
|||
return match.group(1)
|
||||
return '0.0.0'
|
||||
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
|
|
@ -35,7 +36,7 @@ project_copyright = datetime.now().year.__str__() + ', Jazzband'
|
|||
# The full version, including alpha/beta/rc tags
|
||||
release = get_version()
|
||||
# The short X.Y version
|
||||
version = ".".join(release.split(".")[:3])
|
||||
version = '.'.join(release.split('.')[:3])
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
|
@ -67,31 +68,31 @@ htmlhelp_basename = 'django-constancedoc'
|
|||
latex_elements = {}
|
||||
|
||||
latex_documents = [
|
||||
('index', 'django-constance.tex', 'django-constance Documentation',
|
||||
'Jazzband', 'manual'),
|
||||
('index', 'django-constance.tex', 'django-constance Documentation', 'Jazzband', 'manual'),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output
|
||||
|
||||
man_pages = [
|
||||
('index', 'django-constance', 'django-constance Documentation',
|
||||
['Jazzband'], 1)
|
||||
]
|
||||
man_pages = [('index', 'django-constance', 'django-constance Documentation', ['Jazzband'], 1)]
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output
|
||||
|
||||
texinfo_documents = [
|
||||
('index', 'django-constance', 'django-constance Documentation',
|
||||
'Jazzband', 'django-constance', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
'index',
|
||||
'django-constance',
|
||||
'django-constance Documentation',
|
||||
'Jazzband',
|
||||
'django-constance',
|
||||
'One line description of project.',
|
||||
'Miscellaneous',
|
||||
),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/3', None),
|
||||
'django': ('https://docs.djangoproject.com/en/dev/',
|
||||
'https://docs.djangoproject.com/en/dev/_objects/'),
|
||||
'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
def setup(app):
|
||||
app.add_crossref_type(
|
||||
directivename="setting",
|
||||
rolename="setting",
|
||||
indextemplate="pair: %s; setting",
|
||||
directivename='setting',
|
||||
rolename='setting',
|
||||
indextemplate='pair: %s; setting',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from cheeseshop.apps.catalog.models import Brand
|
||||
|
||||
admin.site.register(Brand)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Brand(models.Model):
|
||||
name = models.CharField(max_length=75)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from cheeseshop.apps.storage.models import Shelf, Supply
|
||||
|
||||
from cheeseshop.apps.storage.models import Shelf
|
||||
from cheeseshop.apps.storage.models import Supply
|
||||
|
||||
admin.site.register(Shelf)
|
||||
admin.site.register(Supply)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Shelf(models.Model):
|
||||
name = models.CharField(max_length=75)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'shelves'
|
||||
|
||||
|
||||
class Supply(models.Model):
|
||||
name = models.CharField(max_length=75)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'supplies'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
from django.forms import fields, widgets
|
||||
|
||||
from django.forms import fields
|
||||
from django.forms import widgets
|
||||
|
||||
|
||||
class JsonField(fields.CharField):
|
||||
|
|
|
|||
|
|
@ -93,10 +93,7 @@ CONSTANCE_REDIS_CONNECTION = {
|
|||
CONSTANCE_ADDITIONAL_FIELDS = {
|
||||
'yes_no_null_select': [
|
||||
'django.forms.fields.ChoiceField',
|
||||
{
|
||||
'widget': 'django.forms.Select',
|
||||
'choices': ((None, "-----"), ("yes", "Yes"), ("no", "No"))
|
||||
}
|
||||
{'widget': 'django.forms.Select', 'choices': ((None, '-----'), ('yes', 'Yes'), ('no', 'No'))},
|
||||
],
|
||||
'email': ('django.forms.fields.EmailField',),
|
||||
'json_field': ['cheeseshop.fields.JsonField'],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib import admin
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import re_path
|
||||
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ import os
|
|||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cheeseshop.settings")
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cheeseshop.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cheeseshop.settings")
|
||||
if __name__ == '__main__':
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cheeseshop.settings')
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
|
|
|||
|
|
@ -54,3 +54,21 @@ changelog = "https://github.com/jazzband/django-constance/releases/"
|
|||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["constance*"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
indent-width = 4
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "single"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"I", # isort
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from tests.storage import StorageTestsMixin
|
|||
|
||||
|
||||
class TestDatabase(StorageTestsMixin, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.old_backend = settings.BACKEND
|
||||
settings.BACKEND = 'constance.backends.database.DatabaseBackend'
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from constance import settings
|
||||
|
||||
from tests.storage import StorageTestsMixin
|
||||
|
||||
|
||||
class TestMemory(StorageTestsMixin, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.old_backend = settings.BACKEND
|
||||
settings.BACKEND = 'constance.backends.memory.MemoryBackend'
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from constance import settings
|
||||
|
||||
from tests.storage import StorageTestsMixin
|
||||
|
||||
|
||||
class TestRedis(StorageTestsMixin, TestCase):
|
||||
|
||||
_BACKEND = 'constance.backends.redisd.RedisBackend'
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -21,5 +19,4 @@ class TestRedis(StorageTestsMixin, TestCase):
|
|||
|
||||
|
||||
class TestCachingRedis(TestRedis):
|
||||
|
||||
_BACKEND = 'constance.backends.redisd.CachingRedisBackend'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from datetime import datetime, date, time, timedelta
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
SECRET_KEY = 'cheese'
|
||||
|
||||
MIDDLEWARE = (
|
||||
|
|
@ -24,7 +26,7 @@ DATABASES = {
|
|||
'secondary': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
INSTALLED_APPS = (
|
||||
|
|
@ -34,7 +36,6 @@ INSTALLED_APPS = (
|
|||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
|
||||
'constance',
|
||||
'constance.backends.database',
|
||||
)
|
||||
|
|
@ -46,10 +47,7 @@ CONSTANCE_REDIS_CONNECTION_CLASS = 'tests.redis_mockup.Connection'
|
|||
CONSTANCE_ADDITIONAL_FIELDS = {
|
||||
'yes_no_null_select': [
|
||||
'django.forms.fields.ChoiceField',
|
||||
{
|
||||
'widget': 'django.forms.Select',
|
||||
'choices': ((None, "-----"), ("yes", "Yes"), ("no", "No"))
|
||||
}
|
||||
{'widget': 'django.forms.Select', 'choices': ((None, '-----'), ('yes', 'Yes'), ('no', 'No'))},
|
||||
],
|
||||
# note this intentionally uses a tuple so that we can test immutable
|
||||
'email': ('django.forms.fields.EmailField',),
|
||||
|
|
@ -62,8 +60,7 @@ CONSTANCE_CONFIG = {
|
|||
'BOOL_VALUE': (True, 'true or false'),
|
||||
'STRING_VALUE': ('Hello world', 'greetings'),
|
||||
'DECIMAL_VALUE': (Decimal('0.1'), 'the first release version'),
|
||||
'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24),
|
||||
'time of the first commit'),
|
||||
'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24), 'time of the first commit'),
|
||||
'FLOAT_VALUE': (3.1415926536, 'PI'),
|
||||
'DATE_VALUE': (date(2010, 12, 24), 'Merry Chrismas'),
|
||||
'TIME_VALUE': (time(23, 59, 59), 'And happy New Year'),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from datetime import datetime, date, time, timedelta
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from constance import settings
|
||||
|
|
@ -6,7 +9,6 @@ from constance.base import Config
|
|||
|
||||
|
||||
class StorageTestsMixin:
|
||||
|
||||
def setUp(self):
|
||||
self.config = Config()
|
||||
super().setUp()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
from datetime import datetime
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.defaultfilters import linebreaksbr
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.test import RequestFactory
|
||||
from django.test import TestCase
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from constance import settings
|
||||
from constance.admin import Config
|
||||
from constance.utils import get_values
|
||||
from constance.forms import ConstanceForm
|
||||
from unittest import mock
|
||||
from constance.utils import get_values
|
||||
|
||||
|
||||
class TestAdmin(TestCase):
|
||||
|
|
@ -40,9 +41,7 @@ class TestAdmin(TestCase):
|
|||
self.client.login(username='normal', password='nimda')
|
||||
request = self.rf.get('/admin/constance/config/')
|
||||
request.user = self.normaluser
|
||||
self.assertRaises(PermissionDenied,
|
||||
self.options.changelist_view,
|
||||
request, {})
|
||||
self.assertRaises(PermissionDenied, self.options.changelist_view, request, {})
|
||||
self.assertFalse(request.user.has_perm('constance.change_config'))
|
||||
|
||||
# reload user to reset permission cache
|
||||
|
|
@ -63,10 +62,13 @@ class TestAdmin(TestCase):
|
|||
self.assertContains(response, 'LINEBREAK_VALUE')
|
||||
self.assertContains(response, linebreaksbr('eggs\neggs'))
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'Numbers': ('INT_VALUE',),
|
||||
'Text': ('STRING_VALUE',),
|
||||
})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{
|
||||
'Numbers': ('INT_VALUE',),
|
||||
'Text': ('STRING_VALUE',),
|
||||
},
|
||||
)
|
||||
def test_fieldset_headers(self):
|
||||
self.client.login(username='admin', password='nimda')
|
||||
request = self.rf.get('/admin/constance/config/')
|
||||
|
|
@ -75,10 +77,13 @@ class TestAdmin(TestCase):
|
|||
self.assertContains(response, '<h2>Numbers</h2>')
|
||||
self.assertContains(response, '<h2>Text</h2>')
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', (
|
||||
('Numbers', ('INT_VALUE',)),
|
||||
('Text', ('STRING_VALUE',)),
|
||||
))
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
(
|
||||
('Numbers', ('INT_VALUE',)),
|
||||
('Text', ('STRING_VALUE',)),
|
||||
),
|
||||
)
|
||||
def test_fieldset_tuple(self):
|
||||
self.client.login(username='admin', password='nimda')
|
||||
request = self.rf.get('/admin/constance/config/')
|
||||
|
|
@ -87,16 +92,25 @@ class TestAdmin(TestCase):
|
|||
self.assertContains(response, '<h2>Numbers</h2>')
|
||||
self.assertContains(response, '<h2>Text</h2>')
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'Numbers': {
|
||||
'fields': ('INT_VALUE', 'DECIMAL_VALUE',),
|
||||
'collapse': True,
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{
|
||||
'Numbers': {
|
||||
'fields': (
|
||||
'INT_VALUE',
|
||||
'DECIMAL_VALUE',
|
||||
),
|
||||
'collapse': True,
|
||||
},
|
||||
'Text': {
|
||||
'fields': (
|
||||
'STRING_VALUE',
|
||||
'LINEBREAK_VALUE',
|
||||
),
|
||||
'collapse': True,
|
||||
},
|
||||
},
|
||||
'Text': {
|
||||
'fields': ('STRING_VALUE', 'LINEBREAK_VALUE',),
|
||||
'collapse': True,
|
||||
},
|
||||
})
|
||||
)
|
||||
def test_collapsed_fieldsets(self):
|
||||
self.client.login(username='admin', password='nimda')
|
||||
request = self.rf.get('/admin/constance/config/')
|
||||
|
|
@ -104,74 +118,81 @@ class TestAdmin(TestCase):
|
|||
response = self.options.changelist_view(request, {})
|
||||
self.assertContains(response, 'module collapse')
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'FieldSetOne': ('INT_VALUE',)
|
||||
})
|
||||
@mock.patch('constance.settings.CONFIG', {
|
||||
'INT_VALUE': (1, 'some int'),
|
||||
})
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('INT_VALUE',)})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG',
|
||||
{
|
||||
'INT_VALUE': (1, 'some int'),
|
||||
},
|
||||
)
|
||||
@mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True)
|
||||
@mock.patch("constance.forms.ConstanceForm.save", lambda _: None)
|
||||
@mock.patch("constance.forms.ConstanceForm.is_valid", lambda _: True)
|
||||
@mock.patch('constance.forms.ConstanceForm.save', lambda _: None)
|
||||
@mock.patch('constance.forms.ConstanceForm.is_valid', lambda _: True)
|
||||
def test_submit(self):
|
||||
"""
|
||||
Test that submitting the admin page results in an http redirect when
|
||||
everything is in order.
|
||||
"""
|
||||
initial_value = {"INT_VALUE": settings.CONFIG['INT_VALUE'][0]}
|
||||
|
||||
initial_value = {'INT_VALUE': settings.CONFIG['INT_VALUE'][0]}
|
||||
|
||||
self.client.login(username='admin', password='nimda')
|
||||
|
||||
request = self.rf.post('/admin/constance/config/', data={
|
||||
**initial_value,
|
||||
"version": "123",
|
||||
})
|
||||
|
||||
|
||||
request = self.rf.post(
|
||||
'/admin/constance/config/',
|
||||
data={
|
||||
**initial_value,
|
||||
'version': '123',
|
||||
},
|
||||
)
|
||||
|
||||
request.user = self.superuser
|
||||
request._dont_enforce_csrf_checks = True
|
||||
|
||||
with mock.patch("django.contrib.messages.add_message") as mock_message:
|
||||
with mock.patch.object(ConstanceForm, "__init__",
|
||||
**initial_value,
|
||||
return_value=None
|
||||
) as mock_form:
|
||||
with mock.patch('django.contrib.messages.add_message') as mock_message:
|
||||
with mock.patch.object(ConstanceForm, '__init__', **initial_value, return_value=None) as mock_form:
|
||||
response = self.options.changelist_view(request, {})
|
||||
mock_form.assert_called_with(
|
||||
data=request.POST,
|
||||
files=request.FILES,
|
||||
initial=initial_value,
|
||||
request=request
|
||||
data=request.POST, files=request.FILES, initial=initial_value, request=request
|
||||
)
|
||||
|
||||
mock_message.assert_called_with(
|
||||
request, 25, _('Live settings updated successfully.'),
|
||||
request,
|
||||
25,
|
||||
_('Live settings updated successfully.'),
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, HttpResponseRedirect)
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'FieldSetOne': ('MULTILINE',)
|
||||
})
|
||||
@mock.patch('constance.settings.CONFIG', {
|
||||
'MULTILINE': ('Hello\nWorld', 'multiline value'),
|
||||
})
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('MULTILINE',)})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG',
|
||||
{
|
||||
'MULTILINE': ('Hello\nWorld', 'multiline value'),
|
||||
},
|
||||
)
|
||||
@mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True)
|
||||
def test_newlines_normalization(self):
|
||||
self.client.login(username='admin', password='nimda')
|
||||
request = self.rf.post('/admin/constance/config/', data={
|
||||
"MULTILINE": "Hello\r\nWorld",
|
||||
"version": "123",
|
||||
})
|
||||
request = self.rf.post(
|
||||
'/admin/constance/config/',
|
||||
data={
|
||||
'MULTILINE': 'Hello\r\nWorld',
|
||||
'version': '123',
|
||||
},
|
||||
)
|
||||
request.user = self.superuser
|
||||
request._dont_enforce_csrf_checks = True
|
||||
with mock.patch("django.contrib.messages.add_message"):
|
||||
with mock.patch('django.contrib.messages.add_message'):
|
||||
response = self.options.changelist_view(request, {})
|
||||
self.assertIsInstance(response, HttpResponseRedirect)
|
||||
self.assertEqual(get_values()['MULTILINE'], 'Hello\nWorld')
|
||||
|
||||
@mock.patch('constance.settings.CONFIG', {
|
||||
'DATETIME_VALUE': (datetime(2019, 8, 7, 18, 40, 0), 'some naive datetime'),
|
||||
})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG',
|
||||
{
|
||||
'DATETIME_VALUE': (datetime(2019, 8, 7, 18, 40, 0), 'some naive datetime'),
|
||||
},
|
||||
)
|
||||
@mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True)
|
||||
@mock.patch('tests.redis_mockup.Connection.set', mock.MagicMock())
|
||||
def test_submit_aware_datetime(self):
|
||||
|
|
@ -179,21 +200,27 @@ class TestAdmin(TestCase):
|
|||
Test that submitting the admin page results in an http redirect when
|
||||
everything is in order.
|
||||
"""
|
||||
request = self.rf.post('/admin/constance/config/', data={
|
||||
"DATETIME_VALUE_0": "2019-08-07",
|
||||
"DATETIME_VALUE_1": "19:17:01",
|
||||
"version": "123",
|
||||
})
|
||||
request = self.rf.post(
|
||||
'/admin/constance/config/',
|
||||
data={
|
||||
'DATETIME_VALUE_0': '2019-08-07',
|
||||
'DATETIME_VALUE_1': '19:17:01',
|
||||
'version': '123',
|
||||
},
|
||||
)
|
||||
request.user = self.superuser
|
||||
request._dont_enforce_csrf_checks = True
|
||||
with mock.patch("django.contrib.messages.add_message"):
|
||||
with mock.patch('django.contrib.messages.add_message'):
|
||||
response = self.options.changelist_view(request, {})
|
||||
self.assertIsInstance(response, HttpResponseRedirect)
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'Numbers': ('INT_VALUE',),
|
||||
'Text': ('STRING_VALUE',),
|
||||
})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{
|
||||
'Numbers': ('INT_VALUE',),
|
||||
'Text': ('STRING_VALUE',),
|
||||
},
|
||||
)
|
||||
def test_inconsistent_fieldset_submit(self):
|
||||
"""
|
||||
Test that the admin page warns users if the CONFIG_FIELDSETS setting
|
||||
|
|
@ -203,13 +230,19 @@ class TestAdmin(TestCase):
|
|||
request = self.rf.post('/admin/constance/config/', data=None)
|
||||
request.user = self.superuser
|
||||
request._dont_enforce_csrf_checks = True
|
||||
with mock.patch("django.contrib.messages.add_message"):
|
||||
with mock.patch('django.contrib.messages.add_message'):
|
||||
response = self.options.changelist_view(request, {})
|
||||
self.assertContains(response, 'is missing field(s)')
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'Fieldsets': ('STRING_VALUE', 'INT_VALUE',),
|
||||
})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{
|
||||
'Fieldsets': (
|
||||
'STRING_VALUE',
|
||||
'INT_VALUE',
|
||||
),
|
||||
},
|
||||
)
|
||||
def test_fieldset_ordering_1(self):
|
||||
"""Ordering of inner list should be preserved"""
|
||||
self.client.login(username='admin', password='nimda')
|
||||
|
|
@ -218,14 +251,17 @@ class TestAdmin(TestCase):
|
|||
response = self.options.changelist_view(request, {})
|
||||
response.render()
|
||||
content_str = response.content.decode()
|
||||
self.assertGreater(
|
||||
content_str.find('INT_VALUE'),
|
||||
content_str.find('STRING_VALUE')
|
||||
)
|
||||
self.assertGreater(content_str.find('INT_VALUE'), content_str.find('STRING_VALUE'))
|
||||
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {
|
||||
'Fieldsets': ('INT_VALUE', 'STRING_VALUE',),
|
||||
})
|
||||
@mock.patch(
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{
|
||||
'Fieldsets': (
|
||||
'INT_VALUE',
|
||||
'STRING_VALUE',
|
||||
),
|
||||
},
|
||||
)
|
||||
def test_fieldset_ordering_2(self):
|
||||
"""Ordering of inner list should be preserved"""
|
||||
self.client.login(username='admin', password='nimda')
|
||||
|
|
@ -234,10 +270,7 @@ class TestAdmin(TestCase):
|
|||
response = self.options.changelist_view(request, {})
|
||||
response.render()
|
||||
content_str = response.content.decode()
|
||||
self.assertGreater(
|
||||
content_str.find('STRING_VALUE'),
|
||||
content_str.find('INT_VALUE')
|
||||
)
|
||||
self.assertGreater(content_str.find('STRING_VALUE'), content_str.find('INT_VALUE'))
|
||||
|
||||
def test_labels(self):
|
||||
self.assertEqual(type(self.model._meta.label), str)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
from constance import settings
|
||||
from unittest import mock
|
||||
|
||||
from constance.checks import check_fieldsets, get_inconsistent_fieldnames
|
||||
from django.test import TestCase
|
||||
|
||||
from constance import settings
|
||||
from constance.checks import check_fieldsets
|
||||
from constance.checks import get_inconsistent_fieldnames
|
||||
|
||||
|
||||
class ChecksTestCase(TestCase):
|
||||
@mock.patch("constance.settings.CONFIG_FIELDSETS", {"Set1": settings.CONFIG.keys()})
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'Set1': settings.CONFIG.keys()})
|
||||
def test_get_inconsistent_fieldnames_none(self):
|
||||
"""
|
||||
Test that get_inconsistent_fieldnames returns an empty data and no checks fail
|
||||
|
|
@ -17,8 +19,8 @@ class ChecksTestCase(TestCase):
|
|||
self.assertFalse(extra_keys)
|
||||
|
||||
@mock.patch(
|
||||
"constance.settings.CONFIG_FIELDSETS",
|
||||
{"Set1": list(settings.CONFIG.keys())[:-1]},
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{'Set1': list(settings.CONFIG.keys())[:-1]},
|
||||
)
|
||||
def test_get_inconsistent_fieldnames_for_missing_keys(self):
|
||||
"""
|
||||
|
|
@ -31,8 +33,8 @@ class ChecksTestCase(TestCase):
|
|||
self.assertEqual(1, len(check_fieldsets()))
|
||||
|
||||
@mock.patch(
|
||||
"constance.settings.CONFIG_FIELDSETS",
|
||||
{"Set1": list(settings.CONFIG.keys()) + ['FORGOTTEN_KEY']},
|
||||
'constance.settings.CONFIG_FIELDSETS',
|
||||
{'Set1': list(settings.CONFIG.keys()) + ['FORGOTTEN_KEY']},
|
||||
)
|
||||
def test_get_inconsistent_fieldnames_for_extra_keys(self):
|
||||
"""
|
||||
|
|
@ -44,9 +46,7 @@ class ChecksTestCase(TestCase):
|
|||
self.assertTrue(extra_keys)
|
||||
self.assertEqual(1, len(check_fieldsets()))
|
||||
|
||||
@mock.patch(
|
||||
"constance.settings.CONFIG_FIELDSETS", {}
|
||||
)
|
||||
@mock.patch('constance.settings.CONFIG_FIELDSETS', {})
|
||||
def test_check_fieldsets(self):
|
||||
"""
|
||||
check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from textwrap import dedent
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command, CommandError
|
||||
from django.core.management import CommandError
|
||||
from django.core.management import call_command
|
||||
from django.test import TransactionTestCase
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import smart_str
|
||||
from io import StringIO
|
||||
|
||||
from constance import config
|
||||
from constance.models import Constance
|
||||
|
|
@ -25,8 +26,12 @@ class CliTestCase(TransactionTestCase):
|
|||
def test_list(self):
|
||||
call_command('constance', 'list', stdout=self.out)
|
||||
|
||||
self.assertEqual(set(self.out.getvalue().splitlines()), set(dedent(smart_str(
|
||||
""" BOOL_VALUE True
|
||||
self.assertEqual(
|
||||
set(self.out.getvalue().splitlines()),
|
||||
set(
|
||||
dedent(
|
||||
smart_str(
|
||||
""" BOOL_VALUE True
|
||||
EMAIL_VALUE test@example.com
|
||||
INT_VALUE 1
|
||||
LINEBREAK_VALUE Spam spam
|
||||
|
|
@ -38,17 +43,21 @@ class CliTestCase(TransactionTestCase):
|
|||
DECIMAL_VALUE 0.1
|
||||
DATETIME_VALUE 2010-08-23 11:29:24
|
||||
FLOAT_VALUE 3.1415926536
|
||||
""")).splitlines()))
|
||||
"""
|
||||
)
|
||||
).splitlines()
|
||||
),
|
||||
)
|
||||
|
||||
def test_get(self):
|
||||
call_command('constance', *('get EMAIL_VALUE'.split()), stdout=self.out)
|
||||
|
||||
self.assertEqual(self.out.getvalue().strip(), "test@example.com")
|
||||
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")
|
||||
self.assertEqual(config.EMAIL_VALUE, 'blah@example.com')
|
||||
|
||||
call_command('constance', *('set', 'DATETIME_VALUE', '2011-09-24', '12:30:25'), stdout=self.out)
|
||||
|
||||
|
|
@ -58,23 +67,49 @@ class CliTestCase(TransactionTestCase):
|
|||
self.assertEqual(config.DATETIME_VALUE, expected)
|
||||
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
self.assertRaisesMessage(
|
||||
CommandError,
|
||||
'Enter a valid email address.',
|
||||
call_command,
|
||||
'constance',
|
||||
'set',
|
||||
'EMAIL_VALUE',
|
||||
'not a valid email',
|
||||
)
|
||||
|
||||
def test_set_invalid_multi_value(self):
|
||||
self.assertRaisesMessage(CommandError, "Enter a list of values.",
|
||||
call_command, 'constance', 'set', 'DATETIME_VALUE', '2011-09-24 12:30:25')
|
||||
self.assertRaisesMessage(
|
||||
CommandError,
|
||||
'Enter a list of values.',
|
||||
call_command,
|
||||
'constance',
|
||||
'set',
|
||||
'DATETIME_VALUE',
|
||||
'2011-09-24 12:30:25',
|
||||
)
|
||||
|
||||
def test_delete_stale_records(self):
|
||||
|
||||
initial_count = Constance.objects.count()
|
||||
|
||||
Constance.objects.create(key='STALE_KEY', value=None)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from constance.forms import ConstanceForm
|
||||
from django.forms import fields
|
||||
from django.test import TestCase
|
||||
|
||||
from constance.forms import ConstanceForm
|
||||
|
||||
|
||||
class TestForm(TestCase):
|
||||
|
||||
def test_form_field_types(self):
|
||||
|
||||
f = ConstanceForm({})
|
||||
|
||||
self.assertIsInstance(f.fields['INT_VALUE'], fields.IntegerField)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import unittest
|
||||
|
||||
|
||||
try:
|
||||
import pytest
|
||||
|
||||
from constance import config
|
||||
from constance.test.pytest import override_config
|
||||
|
||||
|
||||
class TestPytestOverrideConfigFunctionDecorator:
|
||||
"""Test that the override_config decorator works correctly for Pytest classes.
|
||||
|
||||
|
|
@ -35,7 +33,6 @@ try:
|
|||
"""Ensure `override_config` can be used as test method decorator."""
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
|
||||
@pytest.mark.override_config(BOOL_VALUE=False)
|
||||
class TestPytestOverrideConfigDecorator:
|
||||
"""Test that the override_config decorator works on classes."""
|
||||
|
|
@ -49,7 +46,6 @@ try:
|
|||
"""Ensure that method mark decorator changes already overridden value for class."""
|
||||
assert config.BOOL_VALUE == 'True'
|
||||
|
||||
|
||||
def test_fixture_override_config(override_config):
|
||||
"""
|
||||
Ensure `override_config` fixture is available globally
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class OverrideConfigFunctionDecoratorTestCase(TestCase):
|
|||
|
||||
Test usage of override_config on test method and as context manager.
|
||||
"""
|
||||
|
||||
def test_default_value_is_true(self):
|
||||
"""Assert that the default value of config.BOOL_VALUE is True."""
|
||||
self.assertTrue(config.BOOL_VALUE)
|
||||
|
|
@ -29,6 +30,7 @@ class OverrideConfigFunctionDecoratorTestCase(TestCase):
|
|||
@override_config(BOOL_VALUE=False)
|
||||
class OverrideConfigClassDecoratorTestCase(TestCase):
|
||||
"""Test that the override_config decorator works on classes."""
|
||||
|
||||
def test_override_config_on_class_changes_config_value(self):
|
||||
"""Assert that the class decorator changes config.BOOL_VALUE."""
|
||||
self.assertFalse(config.BOOL_VALUE)
|
||||
|
|
|
|||
|
|
@ -1,33 +1,55 @@
|
|||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from constance.utils import get_values
|
||||
from constance.management.commands.constance import _set_constance_value
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
from constance.management.commands.constance import _set_constance_value
|
||||
from constance.utils import get_values
|
||||
|
||||
|
||||
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')
|
||||
self.assertRaisesMessage(ValidationError, 'Enter a valid date.', _set_constance_value, 'DATETIME_VALUE', ('2000-00-00', '99:99:99',))
|
||||
self.assertRaisesMessage(ValidationError, 'Enter a valid time.', _set_constance_value, 'DATETIME_VALUE', ('2016-01-01', '99:99:99',))
|
||||
self.assertRaisesMessage(
|
||||
ValidationError, 'Enter a valid email address.', _set_constance_value, 'EMAIL_VALUE', 'not a valid email'
|
||||
)
|
||||
self.assertRaisesMessage(
|
||||
ValidationError,
|
||||
'Enter a valid date.',
|
||||
_set_constance_value,
|
||||
'DATETIME_VALUE',
|
||||
(
|
||||
'2000-00-00',
|
||||
'99:99:99',
|
||||
),
|
||||
)
|
||||
self.assertRaisesMessage(
|
||||
ValidationError,
|
||||
'Enter a valid time.',
|
||||
_set_constance_value,
|
||||
'DATETIME_VALUE',
|
||||
(
|
||||
'2016-01-01',
|
||||
'99:99:99',
|
||||
),
|
||||
)
|
||||
|
||||
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),
|
||||
'TIMEDELTA_VALUE': datetime.timedelta(days=1, hours=2, minutes=3),
|
||||
'LINEBREAK_VALUE': 'Spam spam',
|
||||
'DECIMAL_VALUE': Decimal('0.1'),
|
||||
'STRING_VALUE': 'Hello world',
|
||||
'DATETIME_VALUE': datetime.datetime(2010, 8, 23, 11, 29, 24),
|
||||
})
|
||||
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),
|
||||
'TIMEDELTA_VALUE': datetime.timedelta(days=1, hours=2, minutes=3),
|
||||
'LINEBREAK_VALUE': 'Spam spam',
|
||||
'DECIMAL_VALUE': Decimal('0.1'),
|
||||
'STRING_VALUE': 'Hello world',
|
||||
'DATETIME_VALUE': datetime.datetime(2010, 8, 23, 11, 29, 24),
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from django.urls import path
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue