Merge pull request #641 from jazzband/chore/run-black

This commit is contained in:
Rémy HUBSCHER 2025-10-07 11:59:21 +02:00 committed by GitHub
commit c6b2c44671
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 625 additions and 622 deletions

View file

@ -25,7 +25,7 @@ config = LazyConfig()
class ConstanceAdmin(admin.ModelAdmin):
change_list_template = 'admin/constance/change_list.html'
change_list_template = "admin/constance/change_list.html"
change_list_form = ConstanceForm
def __init__(self, model, admin_site):
@ -33,10 +33,10 @@ class ConstanceAdmin(admin.ModelAdmin):
super().__init__(model, admin_site)
def get_urls(self):
info = f'{self.model._meta.app_label}_{self.model._meta.module_name}'
info = f"{self.model._meta.app_label}_{self.model._meta.module_name}"
return [
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_changelist'),
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_add'),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_changelist"),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"),
]
def get_config_value(self, name, options, form, initial):
@ -52,23 +52,23 @@ class ConstanceAdmin(admin.ModelAdmin):
form_field = form[name]
config_value = {
'name': name,
'default': localize(default),
'raw_default': default,
'help_text': _(help_text),
'value': localize(value),
'modified': localize(value) != localize(default),
'form_field': form_field,
'is_date': isinstance(default, date),
'is_datetime': isinstance(default, datetime),
'is_checkbox': isinstance(form_field.field.widget, forms.CheckboxInput),
'is_file': isinstance(form_field.field.widget, forms.FileInput),
"name": name,
"default": localize(default),
"raw_default": default,
"help_text": _(help_text),
"value": localize(value),
"modified": localize(value) != localize(default),
"form_field": form_field,
"is_date": isinstance(default, date),
"is_datetime": isinstance(default, datetime),
"is_checkbox": isinstance(form_field.field.widget, forms.CheckboxInput),
"is_file": isinstance(form_field.field.widget, forms.FileInput),
}
if field_type and field_type in settings.ADDITIONAL_FIELDS:
serialized_default = form[name].field.prepare_value(default)
config_value['default'] = serialized_default
config_value['raw_default'] = serialized_default
config_value['value'] = form[name].field.prepare_value(value)
config_value["default"] = serialized_default
config_value["raw_default"] = serialized_default
config_value["value"] = form[name].field.prepare_value(value)
return config_value
@ -85,26 +85,26 @@ class ConstanceAdmin(admin.ModelAdmin):
initial = get_values()
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'):
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)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS, _('Live settings updated successfully.'))
return HttpResponseRedirect('.')
messages.add_message(request, messages.ERROR, _('Failed to update live settings.'))
messages.add_message(request, messages.SUCCESS, _("Live settings updated successfully."))
return HttpResponseRedirect(".")
messages.add_message(request, messages.ERROR, _("Failed to update live settings."))
context = dict(
self.admin_site.each_context(request),
config_values=[],
title=self.model._meta.app_config.verbose_name,
app_label='constance',
app_label="constance",
opts=self.model._meta,
form=form,
media=self.media + form.media,
icon_type='svg',
icon_type="svg",
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):
@ -112,11 +112,11 @@ class ConstanceAdmin(admin.ModelAdmin):
else:
fieldset_items = settings.CONFIG_FIELDSETS
context['fieldsets'] = []
context["fieldsets"] = []
for fieldset_title, fieldset_data in fieldset_items:
if isinstance(fieldset_data, dict):
fields_list = fieldset_data['fields']
collapse = fieldset_data.get('collapse', False)
fields_list = fieldset_data["fields"]
collapse = fieldset_data.get("collapse", False)
else:
fields_list = fieldset_data
collapse = False
@ -124,8 +124,8 @@ class ConstanceAdmin(admin.ModelAdmin):
absent_fields = [field for field in fields_list if field not in settings.CONFIG]
if any(absent_fields):
raise ValueError(
'CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}'.format(
', '.join(absent_fields)
"CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}".format(
", ".join(absent_fields)
)
)
@ -135,16 +135,16 @@ class ConstanceAdmin(admin.ModelAdmin):
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}
fieldset_context = {"title": fieldset_title, "config_values": config_values}
if collapse:
fieldset_context['collapse'] = True
context['fieldsets'].append(fieldset_context)
fieldset_context["collapse"] = True
context["fieldsets"].append(fieldset_context)
if not isinstance(settings.CONFIG_FIELDSETS, (OrderedDict, tuple)):
context['fieldsets'].sort(key=itemgetter('title'))
context["fieldsets"].sort(key=itemgetter("title"))
if not isinstance(settings.CONFIG, OrderedDict):
context['config_values'].sort(key=itemgetter('name'))
context["config_values"].sort(key=itemgetter("name"))
request.current_app = self.admin_site.name
return TemplateResponse(request, self.change_list_template, context)
@ -162,11 +162,11 @@ class ConstanceAdmin(admin.ModelAdmin):
class Config:
class Meta:
app_label = 'constance'
object_name = 'Config'
app_label = "constance"
object_name = "Config"
concrete_model = None
model_name = module_name = 'config'
verbose_name_plural = _('config')
model_name = module_name = "config"
verbose_name_plural = _("config")
abstract = False
swapped = False
is_composite_pk = False
@ -175,7 +175,7 @@ class Config:
return False
def get_change_permission(self):
return f'change_{self.model_name}'
return f"change_{self.model_name}"
@property
def app_config(self):
@ -183,11 +183,11 @@ class Config:
@property
def label(self):
return f'{self.app_label}.{self.object_name}'
return f"{self.app_label}.{self.object_name}"
@property
def label_lower(self):
return f'{self.app_label}.{self.model_name}'
return f"{self.app_label}.{self.model_name}"
_meta = Meta()

View file

@ -6,9 +6,9 @@ from constance.checks import check_fieldsets
class ConstanceConfig(AppConfig):
name = 'constance'
verbose_name = _('Constance')
default_auto_field = 'django.db.models.AutoField'
name = "constance"
verbose_name = _("Constance")
default_auto_field = "django.db.models.AutoField"
def ready(self):
checks.register(check_fieldsets, 'constance')
checks.register(check_fieldsets, "constance")

View file

@ -22,7 +22,7 @@ class DatabaseBackend(Backend):
self._model = Constance
self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled'
self._autofill_cachekey = "autofilled"
if self._model._meta.app_config is None:
raise ImproperlyConfigured(
@ -34,9 +34,9 @@ class DatabaseBackend(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 "
f"subclass of Django's local-memory backend ({settings.DATABASE_CACHE_BACKEND!r}). Please "
'set it to a backend that supports cross-process caching.'
"set it to a backend that supports cross-process caching."
)
else:
self._cache = None
@ -45,7 +45,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:
@ -111,7 +111,7 @@ class DatabaseBackend(Backend):
if not created:
old_value = loads(constance.value)
constance.value = dumps(value)
constance.save(update_fields=['value'])
constance.save(update_fields=["value"])
else:
old_value = None

View file

@ -23,14 +23,14 @@ class RedisBackend(Backend):
try:
import redis
except ImportError:
raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.') from None
raise ImproperlyConfigured("The Redis backend requires redis-py to be installed.") from None
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))

View file

@ -6,7 +6,7 @@ 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

@ -14,22 +14,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
@ -53,8 +53,8 @@ def get_inconsistent_fieldnames() -> tuple[set, set]:
for _fieldset_title, fields_list in fieldset_items:
# fields_list can be a dictionary, when a fieldset is defined as collapsible
# https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing
if isinstance(fields_list, dict) and 'fields' in fields_list:
fields_list = fields_list['fields']
if isinstance(fields_list, dict) and "fields" in fields_list:
fields_list = fields_list["fields"]
unique_field_names.update(fields_list)
if not unique_field_names:
return unique_field_names, unique_field_names

View file

@ -14,7 +14,7 @@ from typing import TypeVar
logger = logging.getLogger(__name__)
DEFAULT_DISCRIMINATOR = 'default'
DEFAULT_DISCRIMINATOR = "default"
class JSONEncoder(json.JSONEncoder):
@ -24,11 +24,11 @@ class JSONEncoder(json.JSONEncoder):
for discriminator, (t, _, encoder) in _codecs.items():
if isinstance(o, t):
return _as(discriminator, encoder(o))
raise TypeError(f'Object of type {o.__class__.__name__} is not JSON serializable')
raise TypeError(f"Object of type {o.__class__.__name__} is not JSON serializable")
def _as(discriminator: str, v: Any) -> dict[str, Any]:
return {'__type__': discriminator, '__value__': v}
return {"__type__": discriminator, "__value__": v}
def dumps(obj, _dumps=json.dumps, cls=JSONEncoder, default_kwargs=None, **kwargs):
@ -44,7 +44,7 @@ def loads(s, _loads=json.loads, *, first_level=True, **kwargs):
"""Deserialize json string to object."""
if first_level:
return _loads(s, object_hook=object_hook, **kwargs)
if isinstance(s, dict) and '__type__' not in s and '__value__' not in s:
if isinstance(s, dict) and "__type__" not in s and "__value__" not in s:
return {k: loads(v, first_level=False) for k, v in s.items()}
if isinstance(s, list):
return list(loads(v, first_level=False) for v in s)
@ -53,20 +53,20 @@ def loads(s, _loads=json.loads, *, first_level=True, **kwargs):
def object_hook(o: dict) -> Any:
"""Hook function to perform custom deserialization."""
if o.keys() == {'__type__', '__value__'}:
if o['__type__'] == DEFAULT_DISCRIMINATOR:
return o['__value__']
codec = _codecs.get(o['__type__'])
if o.keys() == {"__type__", "__value__"}:
if o["__type__"] == DEFAULT_DISCRIMINATOR:
return o["__value__"]
codec = _codecs.get(o["__type__"])
if not codec:
raise ValueError(f'Unsupported type: {o["__type__"]}')
return codec[1](o['__value__'])
if '__type__' not in o and '__value__' not in o:
raise ValueError(f"Unsupported type: {o['__type__']}")
return codec[1](o["__value__"])
if "__type__" not in o and "__value__" not in o:
return o
logger.error('Cannot deserialize object: %s', o)
raise ValueError(f'Invalid object: {o}')
logger.error("Cannot deserialize object: %s", o)
raise ValueError(f"Invalid object: {o}")
T = TypeVar('T')
T = TypeVar("T")
class Encoder(Protocol[T]):
@ -79,9 +79,9 @@ class Decoder(Protocol[T]):
def register_type(t: type[T], discriminator: str, encoder: Encoder[T], decoder: Decoder[T]):
if not discriminator:
raise ValueError('Discriminator must be specified')
raise ValueError("Discriminator must be specified")
if _codecs.get(discriminator) or discriminator == DEFAULT_DISCRIMINATOR:
raise ValueError(f'Type with discriminator {discriminator} is already registered')
raise ValueError(f"Type with discriminator {discriminator} is already registered")
_codecs[discriminator] = (t, decoder, encoder)
@ -90,12 +90,12 @@ _codecs: dict[str, tuple[type, Decoder, Encoder]] = {}
def _register_default_types():
# NOTE: datetime should be registered before date, because datetime is also instance of date.
register_type(datetime, 'datetime', datetime.isoformat, datetime.fromisoformat)
register_type(date, 'date', lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date())
register_type(time, 'time', lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, 'decimal', str, Decimal)
register_type(uuid.UUID, 'uuid', lambda o: o.hex, uuid.UUID)
register_type(timedelta, 'timedelta', lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))
register_type(datetime, "datetime", datetime.isoformat, datetime.fromisoformat)
register_type(date, "date", lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date())
register_type(time, "time", lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, "decimal", str, Decimal)
register_type(uuid.UUID, "uuid", lambda o: o.hex, uuid.UUID)
register_type(timedelta, "timedelta", lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))
_register_default_types()

View file

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

View file

@ -25,27 +25,27 @@ from .checks import get_inconsistent_fieldnames
config = LazyConfig()
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10})
NUMERIC_WIDGET = forms.TextInput(attrs={"size": 10})
INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET})
INTEGER_LIKE = (fields.IntegerField, {"widget": NUMERIC_WIDGET})
STRING_LIKE = (
fields.CharField,
{
'widget': forms.Textarea(attrs={'rows': 3}),
'required': False,
"widget": forms.Textarea(attrs={"rows": 3}),
"required": False,
},
)
FIELDS = {
bool: (fields.BooleanField, {'required': False}),
bool: (fields.BooleanField, {"required": False}),
int: INTEGER_LIKE,
Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}),
Decimal: (fields.DecimalField, {"widget": NUMERIC_WIDGET}),
str: STRING_LIKE,
datetime: (fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}),
timedelta: (fields.DurationField, {'widget': widgets.AdminTextInputWidget}),
date: (fields.DateField, {'widget': widgets.AdminDateWidget}),
time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}),
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}),
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}),
}
@ -58,12 +58,12 @@ def parse_additional_fields(fields):
field[0] = import_string(field[0])
if 'widget' in field[1]:
klass = import_string(field[1]['widget'])
field[1]['widget'] = klass(**(field[1].get('widget_kwargs', {}) or {}))
if "widget" in field[1]:
klass = import_string(field[1]["widget"])
field[1]["widget"] = klass(**(field[1].get("widget_kwargs", {}) or {}))
if 'widget_kwargs' in field[1]:
del field[1]['widget_kwargs']
if "widget_kwargs" in field[1]:
del field[1]["widget_kwargs"]
fields[key] = field
@ -80,7 +80,7 @@ class ConstanceForm(forms.Form):
super().__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.sha256()
only_view = request and not request.user.has_perm('constance.change_config')
only_view = request and not request.user.has_perm("constance.change_config")
if only_view:
messages.warning(
request,
@ -94,13 +94,13 @@ class ConstanceForm(forms.Form):
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 '
"Default value type must be "
"equal to declared config "
"parameter type. Please fix "
"the default value of "
"'%(name)s'."
)
% {'name': name}
% {"name": name}
)
else:
config_type = type(default)
@ -109,19 +109,19 @@ class ConstanceForm(forms.Form):
raise ImproperlyConfigured(
_(
"Constance doesn't support "
'config values of the type '
'%(config_type)s. Please fix '
"config values of the type "
"%(config_type)s. Please fix "
"the value of '%(name)s'."
)
% {'config_type': config_type, 'name': name}
% {"config_type": config_type, "name": name}
)
field_class, kwargs = FIELDS[config_type]
if only_view:
kwargs['disabled'] = True
kwargs["disabled"] = True
self.fields[name] = field_class(label=name, **kwargs)
version_hash.update(smart_bytes(initial.get(name, '')))
self.initial['version'] = version_hash.hexdigest()
version_hash.update(smart_bytes(initial.get(name, "")))
self.initial["version"] = version_hash.hexdigest()
def save(self):
for file_field in self.files:
@ -142,18 +142,14 @@ class ConstanceForm(forms.Form):
setattr(config, name, new)
def clean_version(self):
value = self.cleaned_data['version']
value = self.cleaned_data["version"]
if settings.IGNORE_ADMIN_VERSION_CHECK:
return value
if value != self.initial['version']:
if value != self.initial["version"]:
raise forms.ValidationError(
_(
'The settings have been modified '
'by someone else. Please reload the '
'form and resubmit your changes.'
)
_("The settings have been modified by someone else. Please reload the form and resubmit your changes.")
)
return value
@ -166,7 +162,7 @@ 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.')
_("CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.")
)
return cleaned_data

View file

@ -26,36 +26,36 @@ def _set_constance_value(key, value):
class Command(BaseCommand):
help = _('Get/Set In-database config settings handled by Constance')
help = _("Get/Set In-database config settings handled by Constance")
GET = 'get'
SET = 'set'
LIST = 'list'
REMOVE_STALE_KEYS = 'remove_stale_keys'
GET = "get"
SET = "set"
LIST = "list"
REMOVE_STALE_KEYS = "remove_stale_keys"
def add_arguments(self, parser):
subparsers = parser.add_subparsers(dest='command')
subparsers.add_parser(self.LIST, help='list all Constance keys and their values')
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser(self.LIST, help="list all Constance keys and their values")
parser_get = subparsers.add_parser(self.GET, help='get the value of a Constance key')
parser_get.add_argument('key', help='name of the key to get', metavar='KEY')
parser_get = subparsers.add_parser(self.GET, help="get the value of a Constance key")
parser_get.add_argument("key", help="name of the key to get", metavar="KEY")
parser_set = subparsers.add_parser(self.SET, help='set the value of a Constance key')
parser_set.add_argument('key', help='name of the key to set', metavar='KEY')
parser_set = subparsers.add_parser(self.SET, help="set the value of a Constance key")
parser_set.add_argument("key", help="name of the key to set", metavar="KEY")
# use nargs='+' so that we pass a list to MultiValueField (eg SplitDateTimeField)
parser_set.add_argument('value', help='value to set', metavar='VALUE', nargs='+')
parser_set.add_argument("value", help="value to set", metavar="VALUE", nargs="+")
subparsers.add_parser(
self.REMOVE_STALE_KEYS,
help='delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys)',
help="delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys)",
)
def handle(self, command, key=None, value=None, *args, **options):
if command == self.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(f'{key} is not defined in settings.CONSTANCE_CONFIG') from e
raise CommandError(f"{key} is not defined in settings.CONSTANCE_CONFIG") from e
elif command == self.SET:
try:
if len(value) == 1:
@ -63,21 +63,21 @@ class Command(BaseCommand):
value = value[0]
_set_constance_value(key, value)
except KeyError as e:
raise CommandError(f'{key} is not defined in settings.CONSTANCE_CONFIG') from e
raise CommandError(f"{key} is not defined in settings.CONSTANCE_CONFIG") from e
except ValidationError as e:
raise CommandError(', '.join(e)) from e
raise CommandError(", ".join(e)) from e
elif command == self.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 == self.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 the database.', ending='\n')
self.stdout.write("There are no stale records in the 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()
else:
raise CommandError('Invalid command')
raise CommandError("Invalid command")

View file

@ -9,16 +9,16 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Constance',
name="Constance",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=255, unique=True)),
('value', models.TextField(blank=True, editable=False, null=True)),
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("key", models.CharField(max_length=255, unique=True)),
("value", models.TextField(blank=True, editable=False, null=True)),
],
options={
'verbose_name': 'constance',
'verbose_name_plural': 'constances',
'permissions': [('change_config', 'Can change config'), ('view_config', 'Can view config')],
"verbose_name": "constance",
"verbose_name_plural": "constances",
"permissions": [("change_config", "Can change config"), ("view_config", "Can view config")],
},
),
]

View file

@ -12,19 +12,19 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
On new installations just ignore error that table does not exist.
"""
connection = schema_editor.connection
quoted_string = ', '.join([connection.ops.quote_name(item) for item in ['id', 'key', 'value']])
old_table_name = 'constance_config'
quoted_string = ", ".join([connection.ops.quote_name(item) for item in ["id", "key", "value"]])
old_table_name = "constance_config"
with connection.cursor() as cursor:
if old_table_name not in connection.introspection.table_names():
logger.info('Old table does not exist, skipping')
logger.info("Old table does not exist, skipping")
return
cursor.execute(
f'INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM {old_table_name}', # noqa: S608
f"INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM {old_table_name}", # noqa: S608
[],
)
cursor.execute(f'DROP TABLE {old_table_name}', [])
cursor.execute(f"DROP TABLE {old_table_name}", [])
Constance = apps.get_model('constance', 'Constance')
Constance = apps.get_model("constance", "Constance")
sequence_sql = connection.ops.sequence_reset_sql(no_style(), [Constance])
with connection.cursor() as cursor:
for sql in sequence_sql:
@ -32,7 +32,7 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
class Migration(migrations.Migration):
dependencies = [('constance', '0001_initial')]
dependencies = [("constance", "0001_initial")]
atomic = False

View file

@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
def is_already_migrated(value):
try:
data = json.loads(value)
if isinstance(data, dict) and set(data.keys()) == {'__type__', '__value__'}:
if isinstance(data, dict) and set(data.keys()) == {"__type__", "__value__"}:
return True
except (json.JSONDecodeError, TypeError, UnicodeDecodeError):
return False
@ -23,19 +23,22 @@ def is_already_migrated(value):
def import_module_attr(path):
package, module = path.rsplit('.', 1)
package, module = path.rsplit(".", 1)
return getattr(import_module(package), module)
def migrate_pickled_data(apps, schema_editor) -> None: # pragma: no cover
Constance = apps.get_model('constance', 'Constance')
Constance = apps.get_model("constance", "Constance")
for constance in Constance.objects.exclude(value=None):
if not is_already_migrated(constance.value):
constance.value = dumps(pickle.loads(b64decode(constance.value.encode()))) # noqa: S301
constance.save(update_fields=['value'])
constance.save(update_fields=["value"])
if settings.BACKEND in ('constance.backends.redisd.RedisBackend', 'constance.backends.redisd.CachingRedisBackend'):
if settings.BACKEND in (
"constance.backends.redisd.RedisBackend",
"constance.backends.redisd.CachingRedisBackend",
):
import redis
_prefix = settings.REDIS_PREFIX
@ -49,7 +52,7 @@ def migrate_pickled_data(apps, schema_editor) -> None: # pragma: no cover
_rd = redis.Redis(**settings.REDIS_CONNECTION)
redis_migrated_data = {}
for key in settings.CONFIG:
prefixed_key = f'{_prefix}{key}'
prefixed_key = f"{_prefix}{key}"
value = _rd.get(prefixed_key)
if value is not None and not is_already_migrated(value):
redis_migrated_data[prefixed_key] = dumps(pickle.loads(value)) # noqa: S301
@ -58,7 +61,7 @@ def migrate_pickled_data(apps, schema_editor) -> None: # pragma: no cover
class Migration(migrations.Migration):
dependencies = [('constance', '0002_migrate_from_old_table')]
dependencies = [("constance", "0002_migrate_from_old_table")]
operations = [
migrations.RunPython(migrate_pickled_data),

View file

@ -7,11 +7,11 @@ class Constance(models.Model):
value = models.TextField(null=True, blank=True, editable=False)
class Meta:
verbose_name = _('constance')
verbose_name_plural = _('constances')
verbose_name = _("constance")
verbose_name_plural = _("constances")
permissions = [
('change_config', 'Can change config'),
('view_config', 'Can view config'),
("change_config", "Can change config"),
("view_config", "Can view config"),
]
def __str__(self):

View file

@ -1,29 +1,29 @@
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', {})
CONFIG = getattr(settings, "CONSTANCE_CONFIG", {})
CONFIG_FIELDSETS = getattr(settings, 'CONSTANCE_CONFIG_FIELDSETS', {})
CONFIG_FIELDSETS = getattr(settings, "CONSTANCE_CONFIG_FIELDSETS", {})
ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {})
ADDITIONAL_FIELDS = getattr(settings, "CONSTANCE_ADDITIONAL_FIELDS", {})
FILE_ROOT = getattr(settings, 'CONSTANCE_FILE_ROOT', '')
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', '')
DATABASE_PREFIX = getattr(settings, "CONSTANCE_DATABASE_PREFIX", "")
REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
REDIS_PREFIX = getattr(settings, "CONSTANCE_REDIS_PREFIX", "constance:")
REDIS_CACHE_TIMEOUT = getattr(settings, 'CONSTANCE_REDIS_CACHE_TIMEOUT', 60)
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', {})
REDIS_CONNECTION = getattr(settings, "CONSTANCE_REDIS_CONNECTION", {})
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)
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

@ -1,3 +1,3 @@
from .unittest import override_config # pragma: no cover
__all__ = ['override_config']
__all__ = ["override_config"]

View file

@ -14,16 +14,16 @@ from constance import config as constance_config
@pytest.hookimpl(trylast=True)
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)
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:
@ -59,7 +59,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."""
return override_config

View file

@ -6,7 +6,7 @@ from django.test.utils import override_settings
from constance import config
__all__ = ('override_config',)
__all__ = ("override_config",)
class override_config(override_settings):
@ -24,7 +24,7 @@ class override_config(override_settings):
"""Modify the decorated function to override config values."""
if isinstance(test_func, type):
if not issubclass(test_func, SimpleTestCase):
raise Exception('Only subclasses of Django SimpleTestCase can be decorated with override_config')
raise Exception("Only subclasses of Django SimpleTestCase can be decorated with override_config")
return self.modify_test_case(test_func)
@wraps(test_func)
@ -50,6 +50,7 @@ class override_config(override_settings):
def _pre_setup(inner_self):
self.enable()
original_pre_setup(inner_self)
else:
@classmethod

View file

@ -7,7 +7,7 @@ config = LazyConfig()
def import_module_attr(path):
package, module = path.rsplit('.', 1)
package, module = path.rsplit(".", 1)
return getattr(import_module(package), module)
@ -31,7 +31,7 @@ def get_values_for_keys(keys):
:raises AttributeError: If any key is not found in the configuration.
"""
if not isinstance(keys, (list, tuple, set)):
raise TypeError('keys must be a list, tuple, or set of strings')
raise TypeError("keys must be a list, tuple, or set of strings")
# Prepare default initial mapping
default_initial = {name: options[0] for name, options in settings.CONFIG.items() if name in keys}

View file

@ -10,56 +10,56 @@ from datetime import datetime
def get_version():
with open('../pyproject.toml') as f:
with open("../pyproject.toml") as f:
for line in f:
match = re.match(r'version = "(.*)"', line)
if match:
return match.group(1)
return '0.0.0'
return "0.0.0"
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('extensions'))
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath("extensions"))
sys.path.insert(0, os.path.abspath(".."))
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'django-constance'
project_copyright = datetime.now().year.__str__() + ', Jazzband'
project = "django-constance"
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
extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx_search.extension',
'settings',
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx_search.extension",
"settings",
]
templates_path = ['_templates']
source_suffix = '.rst'
root_doc = 'index'
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
html_last_updated_fmt = ''
templates_path = ["_templates"]
source_suffix = ".rst"
root_doc = "index"
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
pygments_style = "sphinx"
html_last_updated_fmt = ""
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
htmlhelp_basename = 'django-constancedoc'
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
htmlhelp_basename = "django-constancedoc"
# -- Options for LaTeX output ---------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output
@ -67,31 +67,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/'),
"python": ("https://docs.python.org/3", None),
"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

@ -7,10 +7,10 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Brand',
name="Brand",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=75)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
),
]

View file

@ -7,23 +7,23 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Shelf',
name="Shelf",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=75)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
options={
'verbose_name_plural': 'shelves',
"verbose_name_plural": "shelves",
},
),
migrations.CreateModel(
name='Supply',
name="Supply",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=75)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
options={
'verbose_name_plural': 'supplies',
"verbose_name_plural": "supplies",
},
),
]

View file

@ -5,11 +5,11 @@ class Shelf(models.Model):
name = models.CharField(max_length=75)
class Meta:
verbose_name_plural = 'shelves'
verbose_name_plural = "shelves"
class Supply(models.Model):
name = models.CharField(max_length=75)
class Meta:
verbose_name_plural = 'supplies'
verbose_name_plural = "supplies"

View file

@ -13,7 +13,7 @@ class JsonField(fields.CharField):
def widget_attrs(self, widget: widgets.Widget):
attrs = super().widget_attrs(widget)
attrs['rows'] = self.rows
attrs["rows"] = self.rows
return attrs
def to_python(self, value):

View file

@ -21,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SITE_ID = 1
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'hdx64#m+lnc_0ffoyehbk&7gk1&*9uar$pcfcm-%$km#p0$k=6'
SECRET_KEY = "hdx64#m+lnc_0ffoyehbk&7gk1&*9uar$pcfcm-%$km#p0$k=6"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@ -32,122 +32,122 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'cheeseshop.apps.catalog',
'cheeseshop.apps.storage',
'constance',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
"cheeseshop.apps.catalog",
"cheeseshop.apps.storage",
"constance",
)
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
)
ROOT_URLCONF = 'cheeseshop.urls'
ROOT_URLCONF = "cheeseshop.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'cheeseshop.wsgi.application'
WSGI_APPLICATION = "cheeseshop.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/tmp/cheeseshop.db',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/tmp/cheeseshop.db",
}
}
CONSTANCE_REDIS_CONNECTION = {
'host': 'localhost',
'port': 6379,
'db': 0,
"host": "localhost",
"port": 6379,
"db": 0,
}
CONSTANCE_ADDITIONAL_FIELDS = {
'yes_no_null_select': [
'django.forms.fields.ChoiceField',
{'widget': 'django.forms.Select', 'choices': ((None, '-----'), ('yes', 'Yes'), ('no', 'No'))},
"yes_no_null_select": [
"django.forms.fields.ChoiceField",
{"widget": "django.forms.Select", "choices": ((None, "-----"), ("yes", "Yes"), ("no", "No"))},
],
'email': ('django.forms.fields.EmailField',),
'json_field': ['cheeseshop.fields.JsonField'],
'image_field': ['django.forms.ImageField', {}],
"email": ("django.forms.fields.EmailField",),
"json_field": ["cheeseshop.fields.JsonField"],
"image_field": ["django.forms.ImageField", {}],
}
CONSTANCE_CONFIG = {
'BANNER': ('The National Cheese Emporium', 'name of the shop'),
'OWNER': ('Mr. Henry Wensleydale', 'owner of the shop'),
'OWNER_EMAIL': ('henry@example.com', 'contact email for owner', 'email'),
'MUSICIANS': (4, 'number of musicians inside the shop'),
'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"),
'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'),
'MULTILINE': ('Line one\nLine two', 'multiline string'),
'JSON_DATA': (
{'a': 1_000, 'b': 'test', 'max': 30_000_000},
'Some test data for json',
'json_field',
"BANNER": ("The National Cheese Emporium", "name of the shop"),
"OWNER": ("Mr. Henry Wensleydale", "owner of the shop"),
"OWNER_EMAIL": ("henry@example.com", "contact email for owner", "email"),
"MUSICIANS": (4, "number of musicians inside the shop"),
"DATE_ESTABLISHED": (date(1972, 11, 30), "the shop's first opening"),
"MY_SELECT_KEY": ("yes", "select yes or no", "yes_no_null_select"),
"MULTILINE": ("Line one\nLine two", "multiline string"),
"JSON_DATA": (
{"a": 1_000, "b": "test", "max": 30_000_000},
"Some test data for json",
"json_field",
),
'LOGO': (
'',
'Logo image file',
'image_field',
"LOGO": (
"",
"Logo image file",
"image_field",
),
}
CONSTANCE_CONFIG_FIELDSETS = {
'Cheese shop general info': [
'BANNER',
'OWNER',
'OWNER_EMAIL',
'MUSICIANS',
'DATE_ESTABLISHED',
'LOGO',
"Cheese shop general info": [
"BANNER",
"OWNER",
"OWNER_EMAIL",
"MUSICIANS",
"DATE_ESTABLISHED",
"LOGO",
],
'Awkward test settings': ['MY_SELECT_KEY', 'MULTILINE', 'JSON_DATA'],
"Awkward test settings": ["MY_SELECT_KEY", "MULTILINE", "JSON_DATA"],
}
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
}
}
CONSTANCE_DATABASE_CACHE_BACKEND = 'default'
CONSTANCE_DATABASE_CACHE_BACKEND = "default"
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'America/Chicago'
TIME_ZONE = "America/Chicago"
USE_I18N = True
@ -159,12 +159,12 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = '/static/'
STATIC_URL = "/static/"
MEDIA_URL = '/media/'
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
CONSTANCE_FILE_ROOT = 'constance'
CONSTANCE_FILE_ROOT = "constance"
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View file

@ -6,7 +6,7 @@ from django.urls import re_path
admin.autodiscover()
urlpatterns = [
re_path('admin/', admin.site.urls),
re_path("admin/", admin.site.urls),
]
if settings.DEBUG:

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

@ -64,7 +64,6 @@ line-length = 120
indent-width = 4
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

View file

@ -7,7 +7,7 @@ from tests.storage import StorageTestsMixin
class TestDatabase(StorageTestsMixin, TestCase):
def setUp(self):
self.old_backend = settings.BACKEND
settings.BACKEND = 'constance.backends.database.DatabaseBackend'
settings.BACKEND = "constance.backends.database.DatabaseBackend"
super().setUp()
def test_database_queries(self):

View file

@ -7,7 +7,7 @@ from tests.storage import StorageTestsMixin
class TestMemory(StorageTestsMixin, TestCase):
def setUp(self):
self.old_backend = settings.BACKEND
settings.BACKEND = 'constance.backends.memory.MemoryBackend'
settings.BACKEND = "constance.backends.memory.MemoryBackend"
super().setUp()
self.config._backend._storage = {}

View file

@ -5,7 +5,7 @@ from tests.storage import StorageTestsMixin
class TestRedis(StorageTestsMixin, TestCase):
_BACKEND = 'constance.backends.redisd.RedisBackend'
_BACKEND = "constance.backends.redisd.RedisBackend"
def setUp(self):
self.old_backend = settings.BACKEND
@ -19,4 +19,4 @@ class TestRedis(StorageTestsMixin, TestCase):
class TestCachingRedis(TestRedis):
_BACKEND = 'constance.backends.redisd.CachingRedisBackend'
_BACKEND = "constance.backends.redisd.CachingRedisBackend"

View file

@ -4,107 +4,107 @@ from datetime import time
from datetime import timedelta
from decimal import Decimal
SECRET_KEY = 'cheese'
SECRET_KEY = "cheese"
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.auth.middleware.SessionAuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
)
DATABASE_ENGINE = 'sqlite3'
DATABASE_ENGINE = "sqlite3"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
},
'secondary': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
"secondary": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
},
}
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.staticfiles',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'constance',
'constance.backends.database',
"django.contrib.admin",
"django.contrib.staticfiles",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"constance",
"constance.backends.database",
)
ROOT_URLCONF = 'tests.urls'
ROOT_URLCONF = "tests.urls"
CONSTANCE_REDIS_CONNECTION_CLASS = 'tests.redis_mockup.Connection'
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'))},
"yes_no_null_select": [
"django.forms.fields.ChoiceField",
{"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',),
'array': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}],
'json': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}],
"email": ("django.forms.fields.EmailField",),
"array": ["django.forms.fields.CharField", {"widget": "django.forms.Textarea"}],
"json": ["django.forms.fields.CharField", {"widget": "django.forms.Textarea"}],
}
USE_TZ = True
CONSTANCE_CONFIG = {
'INT_VALUE': (1, 'some int'),
'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'),
'FLOAT_VALUE': (3.1415926536, 'PI'),
'DATE_VALUE': (date(2010, 12, 24), 'Merry Chrismas'),
'TIME_VALUE': (time(23, 59, 59), 'And happy New Year'),
'TIMEDELTA_VALUE': (timedelta(days=1, hours=2, minutes=3), 'Interval'),
'CHOICE_VALUE': ('yes', 'select yes or no', 'yes_no_null_select'),
'LINEBREAK_VALUE': ('Spam spam', 'eggs\neggs'),
'EMAIL_VALUE': ('test@example.com', 'An email', 'email'),
'LIST_VALUE': ([1, '1', date(2019, 1, 1)], 'A list', 'array'),
'JSON_VALUE': (
"INT_VALUE": (1, "some int"),
"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"),
"FLOAT_VALUE": (3.1415926536, "PI"),
"DATE_VALUE": (date(2010, 12, 24), "Merry Chrismas"),
"TIME_VALUE": (time(23, 59, 59), "And happy New Year"),
"TIMEDELTA_VALUE": (timedelta(days=1, hours=2, minutes=3), "Interval"),
"CHOICE_VALUE": ("yes", "select yes or no", "yes_no_null_select"),
"LINEBREAK_VALUE": ("Spam spam", "eggs\neggs"),
"EMAIL_VALUE": ("test@example.com", "An email", "email"),
"LIST_VALUE": ([1, "1", date(2019, 1, 1)], "A list", "array"),
"JSON_VALUE": (
{
'key': 'value',
'key2': 2,
'key3': [1, 2, 3],
'key4': {'key': 'value'},
'key5': date(2019, 1, 1),
'key6': None,
"key": "value",
"key2": 2,
"key3": [1, 2, 3],
"key4": {"key": "value"},
"key5": date(2019, 1, 1),
"key6": None,
},
'A JSON object',
'json',
"A JSON object",
"json",
),
}
DEBUG = True
STATIC_ROOT = './static/'
STATIC_ROOT = "./static/"
STATIC_URL = '/static/'
STATIC_URL = "/static/"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.template.context_processors.static',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'constance.context_processors.config',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.request",
"django.template.context_processors.static",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"constance.context_processors.config",
],
},
},

View file

@ -16,60 +16,60 @@ class StorageTestsMixin:
def test_store(self):
self.assertEqual(self.config.INT_VALUE, 1)
self.assertEqual(self.config.BOOL_VALUE, True)
self.assertEqual(self.config.STRING_VALUE, 'Hello world')
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('0.1'))
self.assertEqual(self.config.STRING_VALUE, "Hello world")
self.assertEqual(self.config.DECIMAL_VALUE, Decimal("0.1"))
self.assertEqual(self.config.DATETIME_VALUE, datetime(2010, 8, 23, 11, 29, 24))
self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536)
self.assertEqual(self.config.DATE_VALUE, date(2010, 12, 24))
self.assertEqual(self.config.TIME_VALUE, time(23, 59, 59))
self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=1, hours=2, minutes=3))
self.assertEqual(self.config.CHOICE_VALUE, 'yes')
self.assertEqual(self.config.EMAIL_VALUE, 'test@example.com')
self.assertEqual(self.config.LIST_VALUE, [1, '1', date(2019, 1, 1)])
self.assertEqual(self.config.CHOICE_VALUE, "yes")
self.assertEqual(self.config.EMAIL_VALUE, "test@example.com")
self.assertEqual(self.config.LIST_VALUE, [1, "1", date(2019, 1, 1)])
self.assertEqual(
self.config.JSON_VALUE,
{
'key': 'value',
'key2': 2,
'key3': [1, 2, 3],
'key4': {'key': 'value'},
'key5': date(2019, 1, 1),
'key6': None,
"key": "value",
"key2": 2,
"key3": [1, 2, 3],
"key4": {"key": "value"},
"key5": date(2019, 1, 1),
"key6": None,
},
)
# set values
self.config.INT_VALUE = 100
self.config.BOOL_VALUE = False
self.config.STRING_VALUE = 'Beware the weeping angel'
self.config.DECIMAL_VALUE = Decimal('1.2')
self.config.STRING_VALUE = "Beware the weeping angel"
self.config.DECIMAL_VALUE = Decimal("1.2")
self.config.DATETIME_VALUE = datetime(1977, 10, 2)
self.config.FLOAT_VALUE = 2.718281845905
self.config.DATE_VALUE = date(2001, 12, 20)
self.config.TIME_VALUE = time(1, 59, 0)
self.config.TIMEDELTA_VALUE = timedelta(days=2, hours=3, minutes=4)
self.config.CHOICE_VALUE = 'no'
self.config.EMAIL_VALUE = 'foo@bar.com'
self.config.CHOICE_VALUE = "no"
self.config.EMAIL_VALUE = "foo@bar.com"
self.config.LIST_VALUE = [1, date(2020, 2, 2)]
self.config.JSON_VALUE = {'key': 'OK'}
self.config.JSON_VALUE = {"key": "OK"}
# read again
self.assertEqual(self.config.INT_VALUE, 100)
self.assertEqual(self.config.BOOL_VALUE, False)
self.assertEqual(self.config.STRING_VALUE, 'Beware the weeping angel')
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2'))
self.assertEqual(self.config.STRING_VALUE, "Beware the weeping angel")
self.assertEqual(self.config.DECIMAL_VALUE, Decimal("1.2"))
self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2))
self.assertEqual(self.config.FLOAT_VALUE, 2.718281845905)
self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20))
self.assertEqual(self.config.TIME_VALUE, time(1, 59, 0))
self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=2, hours=3, minutes=4))
self.assertEqual(self.config.CHOICE_VALUE, 'no')
self.assertEqual(self.config.EMAIL_VALUE, 'foo@bar.com')
self.assertEqual(self.config.CHOICE_VALUE, "no")
self.assertEqual(self.config.EMAIL_VALUE, "foo@bar.com")
self.assertEqual(self.config.LIST_VALUE, [1, date(2020, 2, 2)])
self.assertEqual(self.config.JSON_VALUE, {'key': 'OK'})
self.assertEqual(self.config.JSON_VALUE, {"key": "OK"})
def test_nonexistent(self):
self.assertRaises(AttributeError, getattr, self.config, 'NON_EXISTENT')
self.assertRaises(AttributeError, getattr, self.config, "NON_EXISTENT")
with self.assertRaises(AttributeError):
self.config.NON_EXISTENT = 1
@ -77,15 +77,15 @@ class StorageTestsMixin:
def test_missing_values(self):
# set some values and leave out others
self.config.BOOL_VALUE = False
self.config.DECIMAL_VALUE = Decimal('1.2')
self.config.DECIMAL_VALUE = Decimal("1.2")
self.config.DATETIME_VALUE = datetime(1977, 10, 2)
self.config.DATE_VALUE = date(2001, 12, 20)
self.config.TIME_VALUE = time(1, 59, 0)
self.assertEqual(self.config.INT_VALUE, 1) # this should be the default value
self.assertEqual(self.config.BOOL_VALUE, False)
self.assertEqual(self.config.STRING_VALUE, 'Hello world') # this should be the default value
self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2'))
self.assertEqual(self.config.STRING_VALUE, "Hello world") # this should be the default value
self.assertEqual(self.config.DECIMAL_VALUE, Decimal("1.2"))
self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2))
self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536) # this should be the default value
self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20))
@ -96,12 +96,12 @@ class StorageTestsMixin:
# Check corner cases such as falsy values
self.config.INT_VALUE = 0
self.config.BOOL_VALUE = False
self.config.STRING_VALUE = ''
self.config.STRING_VALUE = ""
values = dict(self.config._backend.mget(settings.CONFIG))
self.assertEqual(values['INT_VALUE'], 0)
self.assertEqual(values['BOOL_VALUE'], False)
self.assertEqual(values['STRING_VALUE'], '')
self.assertEqual(values["INT_VALUE"], 0)
self.assertEqual(values["BOOL_VALUE"], False)
self.assertEqual(values["STRING_VALUE"], "")
def test_backend_does_not_return_none_values(self):
result = dict(self.config._backend.mget(settings.CONFIG))

View file

@ -23,196 +23,196 @@ class TestAdmin(TestCase):
def setUp(self):
super().setUp()
self.rf = RequestFactory()
self.superuser = User.objects.create_superuser('admin', 'nimda', 'a@a.cz')
self.normaluser = User.objects.create_user('normal', 'nimda', 'b@b.cz')
self.superuser = User.objects.create_superuser("admin", "nimda", "a@a.cz")
self.normaluser = User.objects.create_user("normal", "nimda", "b@b.cz")
self.normaluser.is_staff = True
self.normaluser.save()
self.options = admin.site._registry[self.model]
def test_changelist(self):
self.client.login(username='admin', password='nimda')
request = self.rf.get('/admin/constance/config/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
self.assertEqual(response.status_code, 200)
def test_custom_auth(self):
settings.SUPERUSER_ONLY = False
self.client.login(username='normal', password='nimda')
request = self.rf.get('/admin/constance/config/')
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.assertFalse(request.user.has_perm('constance.change_config'))
self.assertFalse(request.user.has_perm("constance.change_config"))
# reload user to reset permission cache
request = self.rf.get('/admin/constance/config/')
request = self.rf.get("/admin/constance/config/")
request.user = User.objects.get(pk=self.normaluser.pk)
request.user.user_permissions.add(Permission.objects.get(codename='change_config'))
self.assertTrue(request.user.has_perm('constance.change_config'))
request.user.user_permissions.add(Permission.objects.get(codename="change_config"))
self.assertTrue(request.user.has_perm("constance.change_config"))
response = self.options.changelist_view(request, {})
self.assertEqual(response.status_code, 200)
def test_linebreaks(self):
self.client.login(username='admin', password='nimda')
request = self.rf.get('/admin/constance/config/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
self.assertContains(response, 'LINEBREAK_VALUE')
self.assertContains(response, linebreaksbr('eggs\neggs'))
self.assertContains(response, "LINEBREAK_VALUE")
self.assertContains(response, linebreaksbr("eggs\neggs"))
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
"constance.settings.CONFIG_FIELDSETS",
{
'Numbers': ('INT_VALUE',),
'Text': ('STRING_VALUE',),
"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/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
self.assertContains(response, '<h2>Numbers</h2>')
self.assertContains(response, '<h2>Text</h2>')
self.assertContains(response, "<h2>Numbers</h2>")
self.assertContains(response, "<h2>Text</h2>")
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
"constance.settings.CONFIG_FIELDSETS",
(
('Numbers', ('INT_VALUE',)),
('Text', ('STRING_VALUE',)),
("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/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
self.assertContains(response, '<h2>Numbers</h2>')
self.assertContains(response, '<h2>Text</h2>')
self.assertContains(response, "<h2>Numbers</h2>")
self.assertContains(response, "<h2>Text</h2>")
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
"constance.settings.CONFIG_FIELDSETS",
{
'Numbers': {
'fields': (
'INT_VALUE',
'DECIMAL_VALUE',
"Numbers": {
"fields": (
"INT_VALUE",
"DECIMAL_VALUE",
),
'collapse': True,
"collapse": True,
},
'Text': {
'fields': (
'STRING_VALUE',
'LINEBREAK_VALUE',
"Text": {
"fields": (
"STRING_VALUE",
"LINEBREAK_VALUE",
),
'collapse': True,
"collapse": True,
},
},
)
def test_collapsed_fieldsets(self):
self.client.login(username='admin', password='nimda')
request = self.rf.get('/admin/constance/config/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
self.assertContains(response, 'module collapse')
self.assertContains(response, "module collapse")
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('INT_VALUE',)})
@mock.patch("constance.settings.CONFIG_FIELDSETS", {"FieldSetOne": ("INT_VALUE",)})
@mock.patch(
'constance.settings.CONFIG',
"constance.settings.CONFIG",
{
'INT_VALUE': (1, 'some int'),
"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.settings.IGNORE_ADMIN_VERSION_CHECK", 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')
self.client.login(username="admin", password="nimda")
request = self.rf.post(
'/admin/constance/config/',
"/admin/constance/config/",
data={
**initial_value,
'version': '123',
"version": "123",
},
)
request.user = self.superuser
request._dont_enforce_csrf_checks = True
with mock.patch('django.contrib.messages.add_message') as mock_message, mock.patch.object(
ConstanceForm, '__init__', **initial_value, return_value=None
with mock.patch("django.contrib.messages.add_message") as mock_message, 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)
mock_message.assert_called_with(request, 25, _('Live settings updated successfully.'))
mock_message.assert_called_with(request, 25, _("Live settings updated successfully."))
self.assertIsInstance(response, HttpResponseRedirect)
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('MULTILINE',)})
@mock.patch("constance.settings.CONFIG_FIELDSETS", {"FieldSetOne": ("MULTILINE",)})
@mock.patch(
'constance.settings.CONFIG',
"constance.settings.CONFIG",
{
'MULTILINE': ('Hello\nWorld', 'multiline value'),
"MULTILINE": ("Hello\nWorld", "multiline value"),
},
)
@mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True)
@mock.patch("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
def test_newlines_normalization(self):
self.client.login(username='admin', password='nimda')
self.client.login(username="admin", password="nimda")
request = self.rf.post(
'/admin/constance/config/',
"/admin/constance/config/",
data={
'MULTILINE': 'Hello\r\nWorld',
'version': '123',
"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')
self.assertEqual(get_values()["MULTILINE"], "Hello\nWorld")
@mock.patch(
'constance.settings.CONFIG',
"constance.settings.CONFIG",
{
'DATETIME_VALUE': (datetime(2019, 8, 7, 18, 40, 0), 'some naive datetime'),
"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())
@mock.patch("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
@mock.patch("tests.redis_mockup.Connection.set", mock.MagicMock())
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/',
"/admin/constance/config/",
data={
'DATETIME_VALUE_0': '2019-08-07',
'DATETIME_VALUE_1': '19:17:01',
'version': '123',
"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',
"constance.settings.CONFIG_FIELDSETS",
{
'Numbers': ('INT_VALUE',),
'Text': ('STRING_VALUE',),
"Numbers": ("INT_VALUE",),
"Text": ("STRING_VALUE",),
},
)
def test_inconsistent_fieldset_submit(self):
@ -220,51 +220,51 @@ class TestAdmin(TestCase):
Test that the admin page warns users if the CONFIG_FIELDSETS setting
doesn't account for every field in CONFIG.
"""
self.client.login(username='admin', password='nimda')
request = self.rf.post('/admin/constance/config/', data=None)
self.client.login(username="admin", password="nimda")
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)')
self.assertContains(response, "is missing field(s)")
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
"constance.settings.CONFIG_FIELDSETS",
{
'Fieldsets': (
'STRING_VALUE',
'INT_VALUE',
"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')
request = self.rf.get('/admin/constance/config/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
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',
"constance.settings.CONFIG_FIELDSETS",
{
'Fieldsets': (
'INT_VALUE',
'STRING_VALUE',
"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')
request = self.rf.get('/admin/constance/config/')
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
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

@ -8,7 +8,7 @@ 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
@ -19,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):
"""
@ -33,8 +33,8 @@ class ChecksTestCase(TestCase):
self.assertEqual(1, len(check_fieldsets()))
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
{'Set1': [*settings.CONFIG.keys(), 'FORGOTTEN_KEY']},
"constance.settings.CONFIG_FIELDSETS",
{"Set1": [*settings.CONFIG.keys(), "FORGOTTEN_KEY"]},
)
def test_get_inconsistent_fieldnames_for_extra_keys(self):
"""
@ -46,7 +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."""
del settings.CONFIG_FIELDSETS

View file

@ -20,10 +20,10 @@ class CliTestCase(TransactionTestCase):
def test_help(self):
with contextlib.suppress(SystemExit):
call_command('constance', '--help')
call_command("constance", "--help")
def test_list(self):
call_command('constance', 'list', stdout=self.out)
call_command("constance", "list", stdout=self.out)
self.assertEqual(
set(self.out.getvalue().splitlines()),
@ -51,16 +51,16 @@ class CliTestCase(TransactionTestCase):
)
def test_get(self):
call_command('constance', *('get EMAIL_VALUE'.split()), stdout=self.out)
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)
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)
call_command("constance", *("set", "DATETIME_VALUE", "2011-09-24", "12:30:25"), stdout=self.out)
expected = datetime(2011, 9, 24, 12, 30, 25)
if settings.USE_TZ:
@ -70,50 +70,50 @@ class CliTestCase(TransactionTestCase):
def test_get_invalid_name(self):
self.assertRaisesMessage(
CommandError,
'NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG',
"NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG",
call_command,
'constance',
'get',
'NOT_A_REAL_CONFIG',
"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',
"NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG",
call_command,
'constance',
'set',
'NOT_A_REAL_CONFIG',
'foo',
"constance",
"set",
"NOT_A_REAL_CONFIG",
"foo",
)
def test_set_invalid_value(self):
self.assertRaisesMessage(
CommandError,
'Enter a valid email address.',
"Enter a valid email address.",
call_command,
'constance',
'set',
'EMAIL_VALUE',
'not a valid email',
"constance",
"set",
"EMAIL_VALUE",
"not a valid email",
)
def test_set_invalid_multi_value(self):
self.assertRaisesMessage(
CommandError,
'Enter a list of values.',
"Enter a list of values.",
call_command,
'constance',
'set',
'DATETIME_VALUE',
'2011-09-24 12:30:25',
"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)
call_command('constance', 'remove_stale_keys', stdout=self.out)
Constance.objects.create(key="STALE_KEY", value=None)
call_command("constance", "remove_stale_keys", stdout=self.out)
self.assertEqual(Constance.objects.count(), initial_count, msg=self.out)

View file

@ -16,16 +16,16 @@ class TestJSONSerialization(TestCase):
self.datetime = datetime(2023, 10, 5, 15, 30, 0)
self.date = date(2023, 10, 5)
self.time = time(15, 30, 0)
self.decimal = Decimal('10.5')
self.uuid = uuid.UUID('12345678123456781234567812345678')
self.string = 'test'
self.decimal = Decimal("10.5")
self.uuid = uuid.UUID("12345678123456781234567812345678")
self.string = "test"
self.integer = 42
self.float = 3.14
self.boolean = True
self.none = None
self.timedelta = timedelta(days=1, hours=2, minutes=3)
self.list = [1, 2, self.date]
self.dict = {'key': self.date, 'key2': 1}
self.dict = {"key": self.date, "key2": 1}
def test_serializes_and_deserializes_default_types(self):
self.assertEqual(dumps(self.datetime), '{"__type__": "datetime", "__value__": "2023-10-05T15:30:00"}')
@ -65,18 +65,18 @@ class TestJSONSerialization(TestCase):
self.assertEqual(t, loads(dumps(t)))
def test_invalid_deserialization(self):
with self.assertRaisesRegex(ValueError, 'Expecting value'):
loads('THIS_IS_NOT_RIGHT')
with self.assertRaisesRegex(ValueError, 'Invalid object'):
with self.assertRaisesRegex(ValueError, "Expecting value"):
loads("THIS_IS_NOT_RIGHT")
with self.assertRaisesRegex(ValueError, "Invalid object"):
loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test", "THIS_IS_NOT_RIGHT": "THIS_IS_NOT_RIGHT"}')
with self.assertRaisesRegex(ValueError, 'Unsupported type'):
with self.assertRaisesRegex(ValueError, "Unsupported type"):
loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test"}')
def test_handles_unknown_type(self):
class UnknownType:
pass
with self.assertRaisesRegex(TypeError, 'Object of type UnknownType is not JSON serializable'):
with self.assertRaisesRegex(TypeError, "Object of type UnknownType is not JSON serializable"):
dumps(UnknownType())
def test_custom_type_serialization(self):
@ -84,25 +84,25 @@ class TestJSONSerialization(TestCase):
def __init__(self, value):
self.value = value
register_type(CustomType, 'custom', lambda o: o.value, lambda o: CustomType(o))
custom_data = CustomType('test')
register_type(CustomType, "custom", lambda o: o.value, lambda o: CustomType(o))
custom_data = CustomType("test")
json_data = dumps(custom_data)
self.assertEqual(json_data, '{"__type__": "custom", "__value__": "test"}')
deserialized_data = loads(json_data)
self.assertTrue(isinstance(deserialized_data, CustomType))
self.assertEqual(deserialized_data.value, 'test')
self.assertEqual(deserialized_data.value, "test")
def test_register_known_type(self):
with self.assertRaisesRegex(ValueError, 'Discriminator must be specified'):
register_type(int, '', lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, 'Type with discriminator default is already registered'):
register_type(int, 'default', lambda o: o.value, lambda o: int(o))
register_type(int, 'new_custom_type', lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, 'Type with discriminator new_custom_type is already registered'):
register_type(int, 'new_custom_type', lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, "Discriminator must be specified"):
register_type(int, "", lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, "Type with discriminator default is already registered"):
register_type(int, "default", lambda o: o.value, lambda o: int(o))
register_type(int, "new_custom_type", lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, "Type with discriminator new_custom_type is already registered"):
register_type(int, "new_custom_type", lambda o: o.value, lambda o: int(o))
def test_nested_collections(self):
data = {'key': [[[[{'key': self.date}]]]]}
data = {"key": [[[[{"key": self.date}]]]]}
self.assertEqual(
dumps(data),
(

View file

@ -8,16 +8,16 @@ class TestForm(TestCase):
def test_form_field_types(self):
f = ConstanceForm({})
self.assertIsInstance(f.fields['INT_VALUE'], fields.IntegerField)
self.assertIsInstance(f.fields['BOOL_VALUE'], fields.BooleanField)
self.assertIsInstance(f.fields['STRING_VALUE'], fields.CharField)
self.assertIsInstance(f.fields['DECIMAL_VALUE'], fields.DecimalField)
self.assertIsInstance(f.fields['DATETIME_VALUE'], fields.SplitDateTimeField)
self.assertIsInstance(f.fields['TIMEDELTA_VALUE'], fields.DurationField)
self.assertIsInstance(f.fields['FLOAT_VALUE'], fields.FloatField)
self.assertIsInstance(f.fields['DATE_VALUE'], fields.DateField)
self.assertIsInstance(f.fields['TIME_VALUE'], fields.TimeField)
self.assertIsInstance(f.fields["INT_VALUE"], fields.IntegerField)
self.assertIsInstance(f.fields["BOOL_VALUE"], fields.BooleanField)
self.assertIsInstance(f.fields["STRING_VALUE"], fields.CharField)
self.assertIsInstance(f.fields["DECIMAL_VALUE"], fields.DecimalField)
self.assertIsInstance(f.fields["DATETIME_VALUE"], fields.SplitDateTimeField)
self.assertIsInstance(f.fields["TIMEDELTA_VALUE"], fields.DurationField)
self.assertIsInstance(f.fields["FLOAT_VALUE"], fields.FloatField)
self.assertIsInstance(f.fields["DATE_VALUE"], fields.DateField)
self.assertIsInstance(f.fields["TIME_VALUE"], fields.TimeField)
# from CONSTANCE_ADDITIONAL_FIELDS
self.assertIsInstance(f.fields['CHOICE_VALUE'], fields.ChoiceField)
self.assertIsInstance(f.fields['EMAIL_VALUE'], fields.EmailField)
self.assertIsInstance(f.fields["CHOICE_VALUE"], fields.ChoiceField)
self.assertIsInstance(f.fields["EMAIL_VALUE"], fields.EmailField)

View file

@ -42,10 +42,10 @@ try:
"""Assert that the class decorator changes config.BOOL_VALUE."""
assert not config.BOOL_VALUE
@pytest.mark.override_config(BOOL_VALUE='True')
@pytest.mark.override_config(BOOL_VALUE="True")
def test_override_config_on_overridden_value(self):
"""Ensure that method mark decorator changes already overridden value for class."""
assert config.BOOL_VALUE == 'True'
assert config.BOOL_VALUE == "True"
def test_fixture_override_config(override_config):
"""
@ -66,7 +66,7 @@ except ImportError:
class PytestTests(unittest.TestCase):
def setUp(self):
self.skipTest('Skip all pytest tests when using unittest')
self.skipTest("Skip all pytest tests when using unittest")
def test_do_not_skip_silently(self):
"""If no at least one test present, unittest silently skips module."""

View file

@ -11,28 +11,32 @@ from constance.utils import get_values_for_keys
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 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'
ValidationError,
"Enter a valid email address.",
_set_constance_value,
"EMAIL_VALUE",
"not a valid email",
)
self.assertRaisesMessage(
ValidationError,
'Enter a valid date.',
"Enter a valid date.",
_set_constance_value,
'DATETIME_VALUE',
"DATETIME_VALUE",
(
'2000-00-00',
'99:99:99',
"2000-00-00",
"99:99:99",
),
)
self.assertRaisesMessage(
ValidationError,
'Enter a valid time.',
"Enter a valid time.",
_set_constance_value,
'DATETIME_VALUE',
"DATETIME_VALUE",
(
'2016-01-01',
'99:99:99',
"2016-01-01",
"99:99:99",
),
)
@ -40,37 +44,37 @@ class UtilsTestCase(TestCase):
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),
'LIST_VALUE': [1, '1', datetime.date(2019, 1, 1)],
'JSON_VALUE': {
'key': 'value',
'key2': 2,
'key3': [1, 2, 3],
'key4': {'key': 'value'},
'key5': datetime.date(2019, 1, 1),
'key6': None,
"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),
"LIST_VALUE": [1, "1", datetime.date(2019, 1, 1)],
"JSON_VALUE": {
"key": "value",
"key2": 2,
"key3": [1, 2, 3],
"key4": {"key": "value"},
"key5": datetime.date(2019, 1, 1),
"key6": None,
},
},
)
def test_get_values_for_keys(self):
self.assertEqual(
get_values_for_keys(['BOOL_VALUE', 'CHOICE_VALUE', 'LINEBREAK_VALUE']),
get_values_for_keys(["BOOL_VALUE", "CHOICE_VALUE", "LINEBREAK_VALUE"]),
{
'BOOL_VALUE': True,
'CHOICE_VALUE': 'yes',
'LINEBREAK_VALUE': 'Spam spam',
"BOOL_VALUE": True,
"CHOICE_VALUE": "yes",
"LINEBREAK_VALUE": "Spam spam",
},
)
@ -83,9 +87,9 @@ class UtilsTestCase(TestCase):
AttributeError,
'"OLD_VALUE, BOLD_VALUE" keys not found in configuration.',
get_values_for_keys,
['BOOL_VALUE', 'OLD_VALUE', 'BOLD_VALUE'],
["BOOL_VALUE", "OLD_VALUE", "BOLD_VALUE"],
)
def test_get_values_for_keys_invalid_input_type(self):
with self.assertRaises(TypeError):
get_values_for_keys('key1')
get_values_for_keys("key1")

View file

@ -2,5 +2,5 @@ from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
path("admin/", admin.site.urls),
]