From a0ad2eabc6027dee80e008d1307253e76de7f76f Mon Sep 17 00:00:00 2001 From: Alexandr Artemyev Date: Wed, 3 Jul 2024 16:00:39 +0500 Subject: [PATCH] Add ruff format & lint (isort only) --- .github/workflows/test.yml | 19 ++ constance/__init__.py | 1 + constance/admin.py | 46 ++-- constance/apps.py | 1 - constance/backends/__init__.py | 1 - constance/backends/database.py | 30 +-- constance/backends/memory.py | 8 +- constance/backends/redisd.py | 18 +- constance/base.py | 7 +- constance/checks.py | 31 ++- constance/context_processors.py | 2 +- constance/forms.py | 82 ++++--- constance/management/commands/constance.py | 28 +-- constance/migrations/0001_initial.py | 4 +- .../migrations/0002_migrate_from_old_table.py | 9 +- constance/models.py | 11 +- constance/settings.py | 30 +-- constance/test/pytest.py | 22 +- constance/test/unittest.py | 7 +- constance/utils.py | 10 +- docs/conf.py | 29 +-- docs/extensions/settings.py | 6 +- example/cheeseshop/apps/catalog/admin.py | 1 + .../apps/catalog/migrations/0001_initial.py | 7 +- example/cheeseshop/apps/catalog/models.py | 2 +- example/cheeseshop/apps/storage/admin.py | 4 +- .../apps/storage/migrations/0001_initial.py | 7 +- example/cheeseshop/apps/storage/models.py | 3 +- example/cheeseshop/fields.py | 4 +- example/cheeseshop/settings.py | 5 +- example/cheeseshop/urls.py | 5 +- example/cheeseshop/wsgi.py | 2 +- example/manage.py | 4 +- pyproject.toml | 18 ++ tests/backends/test_database.py | 1 - tests/backends/test_memory.py | 2 - tests/backends/test_redis.py | 3 - tests/settings.py | 17 +- tests/storage.py | 6 +- tests/test_admin.py | 213 ++++++++++-------- tests/test_checks.py | 20 +- tests/test_cli.py | 67 ++++-- tests/test_form.py | 5 +- tests/test_pytest_overrides.py | 4 - tests/test_test_overrides.py | 2 + tests/test_utils.py | 64 ++++-- tests/urls.py | 2 - 47 files changed, 484 insertions(+), 386 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b94d5c6c..93401da6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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: diff --git a/constance/__init__.py b/constance/__init__.py index 1db3d096..2549e71e 100644 --- a/constance/__init__.py +++ b/constance/__init__.py @@ -4,6 +4,7 @@ class LazyConfig(LazyObject): def _setup(self): from .base import Config + self._wrapped = Config() diff --git a/constance/admin.py b/constance/admin.py index 8052c317..4c66dadc 100644 --- a/constance/admin.py +++ b/constance/admin.py @@ -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.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 @@ def __init__(self, model, admin_site): 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 @@ def changelist_view(self, request, extra_context=None): 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 @@ def changelist_view(self, request, extra_context=None): 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 @@ def changelist_view(self, request, extra_context=None): 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 @@ def label_lower(self): _meta = Meta() -admin.site.register([Config], ConstanceAdmin) \ No newline at end of file +admin.site.register([Config], ConstanceAdmin) diff --git a/constance/apps.py b/constance/apps.py index 569d1f9d..ab74e11e 100644 --- a/constance/apps.py +++ b/constance/apps.py @@ -9,4 +9,3 @@ class ConstanceConfig(AppConfig): def ready(self): from . import checks - diff --git a/constance/backends/__init__.py b/constance/backends/__init__.py index 09ed5b77..481735d3 100644 --- a/constance/backends/__init__.py +++ b/constance/backends/__init__.py @@ -4,7 +4,6 @@ class Backend: - def get(self, key): """ Get the key from the backend store and return the value. diff --git a/constance/backends/database.py b/constance/backends/database.py index 9d7b4941..dea1b508 100644 --- a/constance/backends/database.py +++ b/constance/backends/database.py @@ -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 @@ def __init__(self): 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 @@ def __init__(self): 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 @@ def set(self, key, value): 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: diff --git a/constance/backends/memory.py b/constance/backends/memory.py index 960766ab..6cde32f6 100644 --- a/constance/backends/memory.py +++ b/constance/backends/memory.py @@ -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 @@ def set(self, key, value): 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) diff --git a/constance/backends/redisd.py b/constance/backends/redisd.py index 19948f96..74b8b9b3 100644 --- a/constance/backends/redisd.py +++ b/constance/backends/redisd.py @@ -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 @@ def __init__(self): 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 @@ def mget(self, keys): 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): diff --git a/constance/base.py b/constance/base.py index b7bdb245..172df171 100644 --- a/constance/base.py +++ b/constance/base.py @@ -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: diff --git a/constance/checks.py b/constance/checks.py index 253919fc..7ca8a518 100644 --- a/constance/checks.py +++ b/constance/checks.py @@ -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 diff --git a/constance/context_processors.py b/constance/context_processors.py index 3a3da33d..ab8d9220 100644 --- a/constance/context_processors.py +++ b/constance/context_processors.py @@ -12,4 +12,4 @@ def config(request): ) """ - return {"config": constance.config} + return {'config': constance.config} diff --git a/constance/forms.py b/constance/forms.py index 378ff5fd..b34bb51c 100644 --- a/constance/forms.py +++ b/constance/forms.py @@ -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.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 @@ 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 @@ def __init__(self, initial, request=None, *args, **kwargs): 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 @@ def clean_version(self): 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 @@ def clean(self): 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 diff --git a/constance/management/commands/constance.py b/constance/management/commands/constance.py index cab7e92c..436cd192 100644 --- a/constance/management/commands/constance.py +++ b/constance/management/commands/constance.py @@ -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 @@ def _subparsers_add_parser(self, subparsers, name, **kwargs): kwargs.pop('cmd') return subparsers.add_parser(name, **kwargs) - def handle(self, command, key=None, value=None, *args, **options): - if command == 'get': try: - self.stdout.write(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 @@ def handle(self, command, key=None, value=None, *args, **options): _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() diff --git a/constance/migrations/0001_initial.py b/constance/migrations/0001_initial.py index 6eb0bc6d..16efe611 100644 --- a/constance/migrations/0001_initial.py +++ b/constance/migrations/0001_initial.py @@ -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 = [] diff --git a/constance/migrations/0002_migrate_from_old_table.py b/constance/migrations/0002_migrate_from_old_table.py index da2fc69e..efb8f36e 100644 --- a/constance/migrations/0002_migrate_from_old_table.py +++ b/constance/migrations/0002_migrate_from_old_table.py @@ -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 diff --git a/constance/models.py b/constance/models.py index e04c4ae9..1f03904f 100644 --- a/constance/models.py +++ b/constance/models.py @@ -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): diff --git a/constance/settings.py b/constance/settings.py index fdddd82c..004d7d04 100644 --- a/constance/settings.py +++ b/constance/settings.py @@ -2,11 +2,7 @@ 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 @@ 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_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 @@ 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) diff --git a/constance/test/pytest.py b/constance/test/pytest.py index 757ce8b3..cf50fc46 100644 --- a/constance/test/pytest.py +++ b/constance/test/pytest.py @@ -3,8 +3,11 @@ 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 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.disable() -@pytest.fixture(name="override_config") +@pytest.fixture(name='override_config') def _override_config(): """ Make override_config available as a function fixture. diff --git a/constance/test/unittest.py b/constance/test/unittest.py index f90004a8..92ba510d 100644 --- a/constance/test/unittest.py +++ b/constance/test/unittest.py @@ -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 @@ def __call__(self, test_func): """ 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): diff --git a/constance/utils.py b/constance/utils.py index 41512395..130ca733 100644 --- a/constance/utils.py +++ b/constance/utils.py @@ -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 \ No newline at end of file + return initial diff --git a/docs/conf.py b/docs/conf.py index ebdfb3d1..718a942b 100644 --- a/docs/conf.py +++ b/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 @@ def get_version(): # 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 @@ def get_version(): 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/'), } - diff --git a/docs/extensions/settings.py b/docs/extensions/settings.py index 2551233a..7cf389a2 100644 --- a/docs/extensions/settings.py +++ b/docs/extensions/settings.py @@ -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', ) diff --git a/example/cheeseshop/apps/catalog/admin.py b/example/cheeseshop/apps/catalog/admin.py index 2887834c..5f9dffa8 100644 --- a/example/cheeseshop/apps/catalog/admin.py +++ b/example/cheeseshop/apps/catalog/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from cheeseshop.apps.catalog.models import Brand admin.site.register(Brand) diff --git a/example/cheeseshop/apps/catalog/migrations/0001_initial.py b/example/cheeseshop/apps/catalog/migrations/0001_initial.py index 0a1f1e00..cfbae891 100644 --- a/example/cheeseshop/apps/catalog/migrations/0001_initial.py +++ b/example/cheeseshop/apps/catalog/migrations/0001_initial.py @@ -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( diff --git a/example/cheeseshop/apps/catalog/models.py b/example/cheeseshop/apps/catalog/models.py index abe9ba53..215ede72 100644 --- a/example/cheeseshop/apps/catalog/models.py +++ b/example/cheeseshop/apps/catalog/models.py @@ -1,5 +1,5 @@ from django.db import models + class Brand(models.Model): name = models.CharField(max_length=75) - diff --git a/example/cheeseshop/apps/storage/admin.py b/example/cheeseshop/apps/storage/admin.py index ecad3830..428f7a75 100644 --- a/example/cheeseshop/apps/storage/admin.py +++ b/example/cheeseshop/apps/storage/admin.py @@ -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) diff --git a/example/cheeseshop/apps/storage/migrations/0001_initial.py b/example/cheeseshop/apps/storage/migrations/0001_initial.py index d0a06c3c..f5970beb 100644 --- a/example/cheeseshop/apps/storage/migrations/0001_initial.py +++ b/example/cheeseshop/apps/storage/migrations/0001_initial.py @@ -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( diff --git a/example/cheeseshop/apps/storage/models.py b/example/cheeseshop/apps/storage/models.py index 37bf8b73..367a9c11 100644 --- a/example/cheeseshop/apps/storage/models.py +++ b/example/cheeseshop/apps/storage/models.py @@ -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' - diff --git a/example/cheeseshop/fields.py b/example/cheeseshop/fields.py index c61d2f66..4959a050 100644 --- a/example/cheeseshop/fields.py +++ b/example/cheeseshop/fields.py @@ -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): diff --git a/example/cheeseshop/settings.py b/example/cheeseshop/settings.py index 6e922c96..d89ac768 100644 --- a/example/cheeseshop/settings.py +++ b/example/cheeseshop/settings.py @@ -93,10 +93,7 @@ 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'], diff --git a/example/cheeseshop/urls.py b/example/cheeseshop/urls.py index d34ee726..7b68c4be 100644 --- a/example/cheeseshop/urls.py +++ b/example/cheeseshop/urls.py @@ -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 = [ diff --git a/example/cheeseshop/wsgi.py b/example/cheeseshop/wsgi.py index 96a17c44..ce3dc338 100644 --- a/example/cheeseshop/wsgi.py +++ b/example/cheeseshop/wsgi.py @@ -2,6 +2,6 @@ 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() diff --git a/example/manage.py b/example/manage.py index 0ea77663..81f8fe58 100755 --- a/example/manage.py +++ b/example/manage.py @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 1fe1c6d2..00d64d04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/tests/backends/test_database.py b/tests/backends/test_database.py index 267892b8..62daa6ac 100644 --- a/tests/backends/test_database.py +++ b/tests/backends/test_database.py @@ -5,7 +5,6 @@ class TestDatabase(StorageTestsMixin, TestCase): - def setUp(self): self.old_backend = settings.BACKEND settings.BACKEND = 'constance.backends.database.DatabaseBackend' diff --git a/tests/backends/test_memory.py b/tests/backends/test_memory.py index e8069e98..6873556b 100644 --- a/tests/backends/test_memory.py +++ b/tests/backends/test_memory.py @@ -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' diff --git a/tests/backends/test_redis.py b/tests/backends/test_redis.py index fd2f0635..3d9426da 100644 --- a/tests/backends/test_redis.py +++ b/tests/backends/test_redis.py @@ -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 @@ def tearDown(self): class TestCachingRedis(TestRedis): - _BACKEND = 'constance.backends.redisd.CachingRedisBackend' diff --git a/tests/settings.py b/tests/settings.py index aea33593..19a9e763 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -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 @@ 'secondary': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', - } + }, } INSTALLED_APPS = ( @@ -34,7 +36,6 @@ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', - 'constance', 'constance.backends.database', ) @@ -46,10 +47,7 @@ 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 @@ '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'), diff --git a/tests/storage.py b/tests/storage.py index 8994be78..88c13190 100644 --- a/tests/storage.py +++ b/tests/storage.py @@ -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 @@ class StorageTestsMixin: - def setUp(self): self.config = Config() super().setUp() diff --git a/tests/test_admin.py b/tests/test_admin.py index 921ce102..67777c92 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -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 @@ def test_custom_auth(self): 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 @@ def test_linebreaks(self): 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 @@ def test_fieldset_headers(self): self.assertContains(response, '

Numbers

') self.assertContains(response, '

Text

') - @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 @@ def test_fieldset_tuple(self): self.assertContains(response, '

Numbers

') self.assertContains(response, '

Text

') - @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 @@ def test_collapsed_fieldsets(self): 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 @@ def test_submit_aware_datetime(self): 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 @@ def test_inconsistent_fieldset_submit(self): 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 @@ def test_fieldset_ordering_1(self): 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 @@ def test_fieldset_ordering_2(self): 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) diff --git a/tests/test_checks.py b/tests/test_checks.py index 4bf8f639..9d3be23c 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -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 @@ def test_get_inconsistent_fieldnames_none(self): 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 @@ def test_get_inconsistent_fieldnames_for_missing_keys(self): 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 @@ def test_get_inconsistent_fieldnames_for_extra_keys(self): 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. diff --git a/tests/test_cli.py b/tests/test_cli.py index 65ff436d..db0c3cb0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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 @@ def test_help(self): 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 @@ def test_list(self): 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 @@ def test_set(self): 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) diff --git a/tests/test_form.py b/tests/test_form.py index 7e8ac775..105d2efe 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -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): +class TestForm(TestCase): def test_form_field_types(self): - f = ConstanceForm({}) self.assertIsInstance(f.fields['INT_VALUE'], fields.IntegerField) diff --git a/tests/test_pytest_overrides.py b/tests/test_pytest_overrides.py index 7637f5b3..b6c4e0fe 100644 --- a/tests/test_pytest_overrides.py +++ b/tests/test_pytest_overrides.py @@ -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 @@ def test_method_decorator(self): """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 @@ def test_override_config_on_overridden_value(self): """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 diff --git a/tests/test_test_overrides.py b/tests/test_test_overrides.py index ddcc98fa..afcfe302 100644 --- a/tests/test_test_overrides.py +++ b/tests/test_test_overrides.py @@ -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 @@ def test_override_config_as_context_manager_changes_config_value(self): @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) diff --git a/tests/test_utils.py b/tests/test_utils.py index ad662860..8c80187e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -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): +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), + }, + ) diff --git a/tests/urls.py b/tests/urls.py index a5084872..dfc73621 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,8 +1,6 @@ from django.contrib import admin - from django.urls import path - urlpatterns = [ path('admin/', admin.site.urls), ]