Add ruff format & lint (isort only) (#560)

This commit is contained in:
Alexandr Artemyev 2024-07-03 19:21:33 +05:00 committed by GitHub
parent 554eedc7c2
commit 57083bbed2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 484 additions and 386 deletions

View file

@ -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:

View file

@ -4,6 +4,7 @@ from django.utils.functional import LazyObject
class LazyConfig(LazyObject):
def _setup(self):
from .base import Config
self._wrapped = Config()

View file

@ -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)

View file

@ -9,4 +9,3 @@ class ConstanceConfig(AppConfig):
def ready(self):
from . import checks

View file

@ -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.

View file

@ -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:

View file

@ -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)

View file

@ -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):

View file

@ -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:

View file

@ -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

View file

@ -12,4 +12,4 @@ def config(request):
)
"""
return {"config": constance.config}
return {'config': constance.config}

View file

@ -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

View file

@ -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()

View file

@ -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 = []

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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.

View file

@ -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):

View file

@ -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

View file

@ -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/'),
}

View file

@ -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',
)

View file

@ -1,4 +1,5 @@
from django.contrib import admin
from cheeseshop.apps.catalog.models import Brand
admin.site.register(Brand)

View file

@ -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(

View file

@ -1,5 +1,5 @@
from django.db import models
class Brand(models.Model):
name = models.CharField(max_length=75)

View file

@ -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)

View file

@ -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(

View file

@ -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'

View file

@ -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):

View file

@ -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'],

View file

@ -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 = [

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'),

View file

@ -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()

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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),
},
)

View file

@ -1,8 +1,6 @@
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]