Use double quotes

This commit is contained in:
Rémy Hubscher 2025-10-07 11:25:07 +02:00
parent 6f12039ae7
commit 6d01cffbc0
No known key found for this signature in database
GPG key ID: 71FB501114D76F22
43 changed files with 620 additions and 621 deletions

View file

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

View file

@ -6,9 +6,9 @@ from constance.checks import check_fieldsets
class ConstanceConfig(AppConfig): class ConstanceConfig(AppConfig):
name = 'constance' name = "constance"
verbose_name = _('Constance') verbose_name = _("Constance")
default_auto_field = 'django.db.models.AutoField' default_auto_field = "django.db.models.AutoField"
def ready(self): 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._model = Constance
self._prefix = settings.DATABASE_PREFIX self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled' self._autofill_cachekey = "autofilled"
if self._model._meta.app_config is None: if self._model._meta.app_config is None:
raise ImproperlyConfigured( raise ImproperlyConfigured(
@ -34,9 +34,9 @@ class DatabaseBackend(Backend):
self._cache = caches[settings.DATABASE_CACHE_BACKEND] self._cache = caches[settings.DATABASE_CACHE_BACKEND]
if isinstance(self._cache, LocMemCache): if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured( 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 " 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: else:
self._cache = None self._cache = None
@ -45,7 +45,7 @@ class DatabaseBackend(Backend):
post_save.connect(self.clear, sender=self._model) post_save.connect(self.clear, sender=self._model)
def add_prefix(self, key): def add_prefix(self, key):
return f'{self._prefix}{key}' return f"{self._prefix}{key}"
def autofill(self): def autofill(self):
if not self._autofill_timeout or not self._cache: if not self._autofill_timeout or not self._cache:
@ -111,7 +111,7 @@ class DatabaseBackend(Backend):
if not created: if not created:
old_value = loads(constance.value) old_value = loads(constance.value)
constance.value = dumps(value) constance.value = dumps(value)
constance.save(update_fields=['value']) constance.save(update_fields=["value"])
else: else:
old_value = None old_value = None

View file

@ -23,14 +23,14 @@ class RedisBackend(Backend):
try: try:
import redis import redis
except ImportError: 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): if isinstance(settings.REDIS_CONNECTION, str):
self._rd = redis.from_url(settings.REDIS_CONNECTION) self._rd = redis.from_url(settings.REDIS_CONNECTION)
else: else:
self._rd = redis.Redis(**settings.REDIS_CONNECTION) self._rd = redis.Redis(**settings.REDIS_CONNECTION)
def add_prefix(self, key): def add_prefix(self, key):
return f'{self._prefix}{key}' return f"{self._prefix}{key}"
def get(self, key): def get(self, key):
value = self._rd.get(self.add_prefix(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.""" """The global config wrapper that handles the backend."""
def __init__(self): 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): def __getattr__(self, key):
try: try:

View file

@ -14,22 +14,22 @@ def check_fieldsets(*args, **kwargs) -> list[CheckMessage]:
errors = [] 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() missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys: if missing_keys:
check = checks.Warning( check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.'), _("CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG."),
hint=', '.join(sorted(missing_keys)), hint=", ".join(sorted(missing_keys)),
obj='settings.CONSTANCE_CONFIG', obj="settings.CONSTANCE_CONFIG",
id='constance.E001', id="constance.E001",
) )
errors.append(check) errors.append(check)
if extra_keys: if extra_keys:
check = checks.Warning( check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG.'), _("CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG."),
hint=', '.join(sorted(extra_keys)), hint=", ".join(sorted(extra_keys)),
obj='settings.CONSTANCE_CONFIG', obj="settings.CONSTANCE_CONFIG",
id='constance.E002', id="constance.E002",
) )
errors.append(check) errors.append(check)
return errors return errors
@ -53,8 +53,8 @@ def get_inconsistent_fieldnames() -> tuple[set, set]:
for _fieldset_title, fields_list in fieldset_items: for _fieldset_title, fields_list in fieldset_items:
# fields_list can be a dictionary, when a fieldset is defined as collapsible # fields_list can be a dictionary, when a fieldset is defined as collapsible
# https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing # https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing
if isinstance(fields_list, dict) and 'fields' in fields_list: if isinstance(fields_list, dict) and "fields" in fields_list:
fields_list = fields_list['fields'] fields_list = fields_list["fields"]
unique_field_names.update(fields_list) unique_field_names.update(fields_list)
if not unique_field_names: if not unique_field_names:
return unique_field_names, unique_field_names return unique_field_names, unique_field_names

View file

@ -14,7 +14,7 @@ from typing import TypeVar
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DEFAULT_DISCRIMINATOR = 'default' DEFAULT_DISCRIMINATOR = "default"
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
@ -24,11 +24,11 @@ class JSONEncoder(json.JSONEncoder):
for discriminator, (t, _, encoder) in _codecs.items(): for discriminator, (t, _, encoder) in _codecs.items():
if isinstance(o, t): if isinstance(o, t):
return _as(discriminator, encoder(o)) 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]: 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): 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.""" """Deserialize json string to object."""
if first_level: if first_level:
return _loads(s, object_hook=object_hook, **kwargs) 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()} return {k: loads(v, first_level=False) for k, v in s.items()}
if isinstance(s, list): if isinstance(s, list):
return list(loads(v, first_level=False) for v in s) 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: def object_hook(o: dict) -> Any:
"""Hook function to perform custom deserialization.""" """Hook function to perform custom deserialization."""
if o.keys() == {'__type__', '__value__'}: if o.keys() == {"__type__", "__value__"}:
if o['__type__'] == DEFAULT_DISCRIMINATOR: if o["__type__"] == DEFAULT_DISCRIMINATOR:
return o['__value__'] return o["__value__"]
codec = _codecs.get(o['__type__']) codec = _codecs.get(o["__type__"])
if not codec: if not codec:
raise ValueError(f'Unsupported type: {o["__type__"]}') raise ValueError(f"Unsupported type: {o['__type__']}")
return codec[1](o['__value__']) return codec[1](o["__value__"])
if '__type__' not in o and '__value__' not in o: if "__type__" not in o and "__value__" not in o:
return o return o
logger.error('Cannot deserialize object: %s', o) logger.error("Cannot deserialize object: %s", o)
raise ValueError(f'Invalid object: {o}') raise ValueError(f"Invalid object: {o}")
T = TypeVar('T') T = TypeVar("T")
class Encoder(Protocol[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]): def register_type(t: type[T], discriminator: str, encoder: Encoder[T], decoder: Decoder[T]):
if not discriminator: if not discriminator:
raise ValueError('Discriminator must be specified') raise ValueError("Discriminator must be specified")
if _codecs.get(discriminator) or discriminator == DEFAULT_DISCRIMINATOR: 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) _codecs[discriminator] = (t, decoder, encoder)
@ -90,12 +90,12 @@ _codecs: dict[str, tuple[type, Decoder, Encoder]] = {}
def _register_default_types(): def _register_default_types():
# NOTE: datetime should be registered before date, because datetime is also instance of date. # NOTE: datetime should be registered before date, because datetime is also instance of date.
register_type(datetime, 'datetime', datetime.isoformat, datetime.fromisoformat) register_type(datetime, "datetime", datetime.isoformat, datetime.fromisoformat)
register_type(date, 'date', lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date()) 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(time, "time", lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, 'decimal', str, Decimal) register_type(Decimal, "decimal", str, Decimal)
register_type(uuid.UUID, 'uuid', lambda o: o.hex, uuid.UUID) 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(timedelta, "timedelta", lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))
_register_default_types() _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() 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 = ( STRING_LIKE = (
fields.CharField, fields.CharField,
{ {
'widget': forms.Textarea(attrs={'rows': 3}), "widget": forms.Textarea(attrs={"rows": 3}),
'required': False, "required": False,
}, },
) )
FIELDS = { FIELDS = {
bool: (fields.BooleanField, {'required': False}), bool: (fields.BooleanField, {"required": False}),
int: INTEGER_LIKE, int: INTEGER_LIKE,
Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}), Decimal: (fields.DecimalField, {"widget": NUMERIC_WIDGET}),
str: STRING_LIKE, str: STRING_LIKE,
datetime: (fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}), datetime: (fields.SplitDateTimeField, {"widget": widgets.AdminSplitDateTime}),
timedelta: (fields.DurationField, {'widget': widgets.AdminTextInputWidget}), timedelta: (fields.DurationField, {"widget": widgets.AdminTextInputWidget}),
date: (fields.DateField, {'widget': widgets.AdminDateWidget}), date: (fields.DateField, {"widget": widgets.AdminDateWidget}),
time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}), time: (fields.TimeField, {"widget": widgets.AdminTimeWidget}),
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}), float: (fields.FloatField, {"widget": NUMERIC_WIDGET}),
} }
@ -58,12 +58,12 @@ def parse_additional_fields(fields):
field[0] = import_string(field[0]) field[0] = import_string(field[0])
if 'widget' in field[1]: if "widget" in field[1]:
klass = import_string(field[1]['widget']) 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]: if "widget_kwargs" in field[1]:
del field[1]['widget_kwargs'] del field[1]["widget_kwargs"]
fields[key] = field fields[key] = field
@ -80,7 +80,7 @@ class ConstanceForm(forms.Form):
super().__init__(*args, initial=initial, **kwargs) super().__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.sha256() 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: if only_view:
messages.warning( messages.warning(
request, request,
@ -94,13 +94,13 @@ class ConstanceForm(forms.Form):
if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type): if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type):
raise ImproperlyConfigured( raise ImproperlyConfigured(
_( _(
'Default value type must be ' "Default value type must be "
'equal to declared config ' "equal to declared config "
'parameter type. Please fix ' "parameter type. Please fix "
'the default value of ' "the default value of "
"'%(name)s'." "'%(name)s'."
) )
% {'name': name} % {"name": name}
) )
else: else:
config_type = type(default) config_type = type(default)
@ -109,19 +109,19 @@ class ConstanceForm(forms.Form):
raise ImproperlyConfigured( raise ImproperlyConfigured(
_( _(
"Constance doesn't support " "Constance doesn't support "
'config values of the type ' "config values of the type "
'%(config_type)s. Please fix ' "%(config_type)s. Please fix "
"the value of '%(name)s'." "the value of '%(name)s'."
) )
% {'config_type': config_type, 'name': name} % {"config_type": config_type, "name": name}
) )
field_class, kwargs = FIELDS[config_type] field_class, kwargs = FIELDS[config_type]
if only_view: if only_view:
kwargs['disabled'] = True kwargs["disabled"] = True
self.fields[name] = field_class(label=name, **kwargs) self.fields[name] = field_class(label=name, **kwargs)
version_hash.update(smart_bytes(initial.get(name, ''))) version_hash.update(smart_bytes(initial.get(name, "")))
self.initial['version'] = version_hash.hexdigest() self.initial["version"] = version_hash.hexdigest()
def save(self): def save(self):
for file_field in self.files: for file_field in self.files:
@ -142,14 +142,14 @@ class ConstanceForm(forms.Form):
setattr(config, name, new) setattr(config, name, new)
def clean_version(self): def clean_version(self):
value = self.cleaned_data['version'] value = self.cleaned_data["version"]
if settings.IGNORE_ADMIN_VERSION_CHECK: if settings.IGNORE_ADMIN_VERSION_CHECK:
return value return value
if value != self.initial['version']: if value != self.initial["version"]:
raise forms.ValidationError( 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 return value
@ -162,7 +162,7 @@ class ConstanceForm(forms.Form):
missing_keys, extra_keys = get_inconsistent_fieldnames() missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys or extra_keys: if missing_keys or extra_keys:
raise forms.ValidationError( 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 return cleaned_data

View file

@ -26,36 +26,36 @@ def _set_constance_value(key, value):
class Command(BaseCommand): 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' GET = "get"
SET = 'set' SET = "set"
LIST = 'list' LIST = "list"
REMOVE_STALE_KEYS = 'remove_stale_keys' REMOVE_STALE_KEYS = "remove_stale_keys"
def add_arguments(self, parser): def add_arguments(self, parser):
subparsers = parser.add_subparsers(dest='command') subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser(self.LIST, help='list all Constance keys and their values') 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 = 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.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 = 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.add_argument("key", help="name of the key to set", metavar="KEY")
# use nargs='+' so that we pass a list to MultiValueField (eg SplitDateTimeField) # 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( subparsers.add_parser(
self.REMOVE_STALE_KEYS, 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): def handle(self, command, key=None, value=None, *args, **options):
if command == self.GET: if command == self.GET:
try: try:
self.stdout.write(str(getattr(config, key)), ending='\n') self.stdout.write(str(getattr(config, key)), ending="\n")
except AttributeError as e: 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: elif command == self.SET:
try: try:
if len(value) == 1: if len(value) == 1:
@ -63,21 +63,21 @@ class Command(BaseCommand):
value = value[0] value = value[0]
_set_constance_value(key, value) _set_constance_value(key, value)
except KeyError as e: 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: except ValidationError as e:
raise CommandError(', '.join(e)) from e raise CommandError(", ".join(e)) from e
elif command == self.LIST: elif command == self.LIST:
for k, v in get_values().items(): 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: elif command == self.REMOVE_STALE_KEYS:
actual_keys = settings.CONSTANCE_CONFIG.keys() actual_keys = settings.CONSTANCE_CONFIG.keys()
stale_records = Constance.objects.exclude(key__in=actual_keys) stale_records = Constance.objects.exclude(key__in=actual_keys)
if stale_records: 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: 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: 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() stale_records.delete()
else: else:
raise CommandError('Invalid command') raise CommandError("Invalid command")

View file

@ -9,16 +9,16 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Constance', name="Constance",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
('key', models.CharField(max_length=255, unique=True)), ("key", models.CharField(max_length=255, unique=True)),
('value', models.TextField(blank=True, editable=False, null=True)), ("value", models.TextField(blank=True, editable=False, null=True)),
], ],
options={ options={
'verbose_name': 'constance', "verbose_name": "constance",
'verbose_name_plural': 'constances', "verbose_name_plural": "constances",
'permissions': [('change_config', 'Can change config'), ('view_config', 'Can view config')], "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. On new installations just ignore error that table does not exist.
""" """
connection = schema_editor.connection connection = schema_editor.connection
quoted_string = ', '.join([connection.ops.quote_name(item) for item in ['id', 'key', 'value']]) quoted_string = ", ".join([connection.ops.quote_name(item) for item in ["id", "key", "value"]])
old_table_name = 'constance_config' old_table_name = "constance_config"
with connection.cursor() as cursor: with connection.cursor() as cursor:
if old_table_name not in connection.introspection.table_names(): 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 return
cursor.execute( 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]) sequence_sql = connection.ops.sequence_reset_sql(no_style(), [Constance])
with connection.cursor() as cursor: with connection.cursor() as cursor:
for sql in sequence_sql: for sql in sequence_sql:
@ -32,7 +32,7 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [('constance', '0001_initial')] dependencies = [("constance", "0001_initial")]
atomic = False atomic = False

View file

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

View file

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

View file

@ -1,29 +1,29 @@
from django.conf import settings 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 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) @pytest.hookimpl(trylast=True)
def pytest_configure(config): # pragma: no cover def pytest_configure(config): # pragma: no cover
"""Register override_config marker.""" """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) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item): # pragma: no cover def pytest_runtest_call(item): # pragma: no cover
"""Validate constance override marker params. Run test with overridden config.""" """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 is not None:
if marker.args: 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): with override_config(**marker.kwargs):
yield yield
else: else:
@ -59,7 +59,7 @@ class override_config(ContextDecorator):
self.disable() self.disable()
@pytest.fixture(name='override_config') @pytest.fixture(name="override_config")
def _override_config(): def _override_config():
"""Make override_config available as a function fixture.""" """Make override_config available as a function fixture."""
return override_config return override_config

View file

@ -6,7 +6,7 @@ from django.test.utils import override_settings
from constance import config from constance import config
__all__ = ('override_config',) __all__ = ("override_config",)
class override_config(override_settings): class override_config(override_settings):
@ -24,7 +24,7 @@ class override_config(override_settings):
"""Modify the decorated function to override config values.""" """Modify the decorated function to override config values."""
if isinstance(test_func, type): if isinstance(test_func, type):
if not issubclass(test_func, SimpleTestCase): 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) return self.modify_test_case(test_func)
@wraps(test_func) @wraps(test_func)

View file

@ -7,7 +7,7 @@ config = LazyConfig()
def import_module_attr(path): def import_module_attr(path):
package, module = path.rsplit('.', 1) package, module = path.rsplit(".", 1)
return getattr(import_module(package), module) 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. :raises AttributeError: If any key is not found in the configuration.
""" """
if not isinstance(keys, (list, tuple, set)): 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 # Prepare default initial mapping
default_initial = {name: options[0] for name, options in settings.CONFIG.items() if name in keys} 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(): def get_version():
with open('../pyproject.toml') as f: with open("../pyproject.toml") as f:
for line in f: for line in f:
match = re.match(r'version = "(.*)"', line) match = re.match(r'version = "(.*)"', line)
if match: if match:
return match.group(1) 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, # 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 # 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. # 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("extensions"))
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath(".."))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'django-constance' project = "django-constance"
project_copyright = datetime.now().year.__str__() + ', Jazzband' project_copyright = datetime.now().year.__str__() + ", Jazzband"
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = get_version() release = get_version()
# The short X.Y version # The short X.Y version
version = '.'.join(release.split('.')[:3]) version = ".".join(release.split(".")[:3])
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [ extensions = [
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
'sphinx.ext.todo', "sphinx.ext.todo",
'sphinx_search.extension', "sphinx_search.extension",
'settings', "settings",
] ]
templates_path = ['_templates'] templates_path = ["_templates"]
source_suffix = '.rst' source_suffix = ".rst"
root_doc = 'index' root_doc = "index"
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
pygments_style = 'sphinx' pygments_style = "sphinx"
html_last_updated_fmt = '' html_last_updated_fmt = ""
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme' html_theme = "sphinx_rtd_theme"
html_static_path = ['_static'] html_static_path = ["_static"]
htmlhelp_basename = 'django-constancedoc' htmlhelp_basename = "django-constancedoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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_elements = {}
latex_documents = [ 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 --------------------------------------- # -- Options for manual page output ---------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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 ------------------------------------------- # -- Options for Texinfo output -------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output
texinfo_documents = [ texinfo_documents = [
( (
'index', "index",
'django-constance', "django-constance",
'django-constance Documentation', "django-constance Documentation",
'Jazzband', "Jazzband",
'django-constance', "django-constance",
'One line description of project.', "One line description of project.",
'Miscellaneous', "Miscellaneous",
), ),
] ]
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = { intersphinx_mapping = {
'python': ('https://docs.python.org/3', None), "python": ("https://docs.python.org/3", None),
'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'), "django": ("https://docs.djangoproject.com/en/dev/", "https://docs.djangoproject.com/en/dev/_objects/"),
} }

View file

@ -1,6 +1,6 @@
def setup(app): def setup(app):
app.add_crossref_type( app.add_crossref_type(
directivename='setting', directivename="setting",
rolename='setting', rolename="setting",
indextemplate='pair: %s; setting', indextemplate="pair: %s; setting",
) )

View file

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

View file

@ -7,23 +7,23 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Shelf', name="Shelf",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=75)), ("name", models.CharField(max_length=75)),
], ],
options={ options={
'verbose_name_plural': 'shelves', "verbose_name_plural": "shelves",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Supply', name="Supply",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=75)), ("name", models.CharField(max_length=75)),
], ],
options={ 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) name = models.CharField(max_length=75)
class Meta: class Meta:
verbose_name_plural = 'shelves' verbose_name_plural = "shelves"
class Supply(models.Model): class Supply(models.Model):
name = models.CharField(max_length=75) name = models.CharField(max_length=75)
class Meta: 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): def widget_attrs(self, widget: widgets.Widget):
attrs = super().widget_attrs(widget) attrs = super().widget_attrs(widget)
attrs['rows'] = self.rows attrs["rows"] = self.rows
return attrs return attrs
def to_python(self, value): 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 SITE_ID = 1
# SECURITY WARNING: keep the secret key used in production secret! # 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -32,122 +32,122 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.sites', "django.contrib.sites",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'cheeseshop.apps.catalog', "cheeseshop.apps.catalog",
'cheeseshop.apps.storage', "cheeseshop.apps.storage",
'constance', "constance",
) )
MIDDLEWARE = ( MIDDLEWARE = (
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
) )
ROOT_URLCONF = 'cheeseshop.urls' ROOT_URLCONF = "cheeseshop.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'cheeseshop.wsgi.application' WSGI_APPLICATION = "cheeseshop.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases # https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': '/tmp/cheeseshop.db', "NAME": "/tmp/cheeseshop.db",
} }
} }
CONSTANCE_REDIS_CONNECTION = { CONSTANCE_REDIS_CONNECTION = {
'host': 'localhost', "host": "localhost",
'port': 6379, "port": 6379,
'db': 0, "db": 0,
} }
CONSTANCE_ADDITIONAL_FIELDS = { CONSTANCE_ADDITIONAL_FIELDS = {
'yes_no_null_select': [ "yes_no_null_select": [
'django.forms.fields.ChoiceField', "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',), "email": ("django.forms.fields.EmailField",),
'json_field': ['cheeseshop.fields.JsonField'], "json_field": ["cheeseshop.fields.JsonField"],
'image_field': ['django.forms.ImageField', {}], "image_field": ["django.forms.ImageField", {}],
} }
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
'BANNER': ('The National Cheese Emporium', 'name of the shop'), "BANNER": ("The National Cheese Emporium", "name of the shop"),
'OWNER': ('Mr. Henry Wensleydale', 'owner of the shop'), "OWNER": ("Mr. Henry Wensleydale", "owner of the shop"),
'OWNER_EMAIL': ('henry@example.com', 'contact email for owner', 'email'), "OWNER_EMAIL": ("henry@example.com", "contact email for owner", "email"),
'MUSICIANS': (4, 'number of musicians inside the shop'), "MUSICIANS": (4, "number of musicians inside the shop"),
'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"), "DATE_ESTABLISHED": (date(1972, 11, 30), "the shop's first opening"),
'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'), "MY_SELECT_KEY": ("yes", "select yes or no", "yes_no_null_select"),
'MULTILINE': ('Line one\nLine two', 'multiline string'), "MULTILINE": ("Line one\nLine two", "multiline string"),
'JSON_DATA': ( "JSON_DATA": (
{'a': 1_000, 'b': 'test', 'max': 30_000_000}, {"a": 1_000, "b": "test", "max": 30_000_000},
'Some test data for json', "Some test data for json",
'json_field', "json_field",
), ),
'LOGO': ( "LOGO": (
'', "",
'Logo image file', "Logo image file",
'image_field', "image_field",
), ),
} }
CONSTANCE_CONFIG_FIELDSETS = { CONSTANCE_CONFIG_FIELDSETS = {
'Cheese shop general info': [ "Cheese shop general info": [
'BANNER', "BANNER",
'OWNER', "OWNER",
'OWNER_EMAIL', "OWNER_EMAIL",
'MUSICIANS', "MUSICIANS",
'DATE_ESTABLISHED', "DATE_ESTABLISHED",
'LOGO', "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 = { CACHES = {
'default': { "default": {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
'LOCATION': '127.0.0.1:11211', "LOCATION": "127.0.0.1:11211",
} }
} }
CONSTANCE_DATABASE_CACHE_BACKEND = 'default' CONSTANCE_DATABASE_CACHE_BACKEND = "default"
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/ # 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 USE_I18N = True
@ -159,12 +159,12 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/ # 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() admin.autodiscover()
urlpatterns = [ urlpatterns = [
re_path('admin/', admin.site.urls), re_path("admin/", admin.site.urls),
] ]
if settings.DEBUG: if settings.DEBUG:

View file

@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application 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() application = get_wsgi_application()

View file

@ -2,8 +2,8 @@
import os import os
import sys import sys
if __name__ == '__main__': if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cheeseshop.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cheeseshop.settings")
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ from tests.storage import StorageTestsMixin
class TestRedis(StorageTestsMixin, TestCase): class TestRedis(StorageTestsMixin, TestCase):
_BACKEND = 'constance.backends.redisd.RedisBackend' _BACKEND = "constance.backends.redisd.RedisBackend"
def setUp(self): def setUp(self):
self.old_backend = settings.BACKEND self.old_backend = settings.BACKEND
@ -19,4 +19,4 @@ class TestRedis(StorageTestsMixin, TestCase):
class TestCachingRedis(TestRedis): 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 datetime import timedelta
from decimal import Decimal from decimal import Decimal
SECRET_KEY = 'cheese' SECRET_KEY = "cheese"
MIDDLEWARE = ( MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', "django.contrib.auth.middleware.SessionAuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
) )
DATABASE_ENGINE = 'sqlite3' DATABASE_ENGINE = "sqlite3"
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': ':memory:', "NAME": ":memory:",
}, },
'secondary': { "secondary": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': ':memory:', "NAME": ":memory:",
}, },
} }
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'constance', "constance",
'constance.backends.database', "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 = { CONSTANCE_ADDITIONAL_FIELDS = {
'yes_no_null_select': [ "yes_no_null_select": [
'django.forms.fields.ChoiceField', "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 # note this intentionally uses a tuple so that we can test immutable
'email': ('django.forms.fields.EmailField',), "email": ("django.forms.fields.EmailField",),
'array': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}], "array": ["django.forms.fields.CharField", {"widget": "django.forms.Textarea"}],
'json': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}], "json": ["django.forms.fields.CharField", {"widget": "django.forms.Textarea"}],
} }
USE_TZ = True USE_TZ = True
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
'INT_VALUE': (1, 'some int'), "INT_VALUE": (1, "some int"),
'BOOL_VALUE': (True, 'true or false'), "BOOL_VALUE": (True, "true or false"),
'STRING_VALUE': ('Hello world', 'greetings'), "STRING_VALUE": ("Hello world", "greetings"),
'DECIMAL_VALUE': (Decimal('0.1'), 'the first release version'), "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'), "FLOAT_VALUE": (3.1415926536, "PI"),
'DATE_VALUE': (date(2010, 12, 24), 'Merry Chrismas'), "DATE_VALUE": (date(2010, 12, 24), "Merry Chrismas"),
'TIME_VALUE': (time(23, 59, 59), 'And happy New Year'), "TIME_VALUE": (time(23, 59, 59), "And happy New Year"),
'TIMEDELTA_VALUE': (timedelta(days=1, hours=2, minutes=3), 'Interval'), "TIMEDELTA_VALUE": (timedelta(days=1, hours=2, minutes=3), "Interval"),
'CHOICE_VALUE': ('yes', 'select yes or no', 'yes_no_null_select'), "CHOICE_VALUE": ("yes", "select yes or no", "yes_no_null_select"),
'LINEBREAK_VALUE': ('Spam spam', 'eggs\neggs'), "LINEBREAK_VALUE": ("Spam spam", "eggs\neggs"),
'EMAIL_VALUE': ('test@example.com', 'An email', 'email'), "EMAIL_VALUE": ("test@example.com", "An email", "email"),
'LIST_VALUE': ([1, '1', date(2019, 1, 1)], 'A list', 'array'), "LIST_VALUE": ([1, "1", date(2019, 1, 1)], "A list", "array"),
'JSON_VALUE': ( "JSON_VALUE": (
{ {
'key': 'value', "key": "value",
'key2': 2, "key2": 2,
'key3': [1, 2, 3], "key3": [1, 2, 3],
'key4': {'key': 'value'}, "key4": {"key": "value"},
'key5': date(2019, 1, 1), "key5": date(2019, 1, 1),
'key6': None, "key6": None,
}, },
'A JSON object', "A JSON object",
'json', "json",
), ),
} }
DEBUG = True DEBUG = True
STATIC_ROOT = './static/' STATIC_ROOT = "./static/"
STATIC_URL = '/static/' STATIC_URL = "/static/"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.i18n', "django.template.context_processors.i18n",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.template.context_processors.static', "django.template.context_processors.static",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
'constance.context_processors.config', "constance.context_processors.config",
], ],
}, },
}, },

View file

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

View file

@ -23,196 +23,196 @@ class TestAdmin(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.rf = RequestFactory() self.rf = RequestFactory()
self.superuser = User.objects.create_superuser('admin', 'nimda', 'a@a.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 = User.objects.create_user("normal", "nimda", "b@b.cz")
self.normaluser.is_staff = True self.normaluser.is_staff = True
self.normaluser.save() self.normaluser.save()
self.options = admin.site._registry[self.model] self.options = admin.site._registry[self.model]
def test_changelist(self): def test_changelist(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_custom_auth(self): def test_custom_auth(self):
settings.SUPERUSER_ONLY = False settings.SUPERUSER_ONLY = False
self.client.login(username='normal', password='nimda') self.client.login(username="normal", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.normaluser 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')) self.assertFalse(request.user.has_perm("constance.change_config"))
# reload user to reset permission cache # 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.objects.get(pk=self.normaluser.pk)
request.user.user_permissions.add(Permission.objects.get(codename='change_config')) request.user.user_permissions.add(Permission.objects.get(codename="change_config"))
self.assertTrue(request.user.has_perm('constance.change_config')) self.assertTrue(request.user.has_perm("constance.change_config"))
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_linebreaks(self): def test_linebreaks(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
self.assertContains(response, 'LINEBREAK_VALUE') self.assertContains(response, "LINEBREAK_VALUE")
self.assertContains(response, linebreaksbr('eggs\neggs')) self.assertContains(response, linebreaksbr("eggs\neggs"))
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{ {
'Numbers': ('INT_VALUE',), "Numbers": ("INT_VALUE",),
'Text': ('STRING_VALUE',), "Text": ("STRING_VALUE",),
}, },
) )
def test_fieldset_headers(self): def test_fieldset_headers(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
self.assertContains(response, '<h2>Numbers</h2>') self.assertContains(response, "<h2>Numbers</h2>")
self.assertContains(response, '<h2>Text</h2>') self.assertContains(response, "<h2>Text</h2>")
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
( (
('Numbers', ('INT_VALUE',)), ("Numbers", ("INT_VALUE",)),
('Text', ('STRING_VALUE',)), ("Text", ("STRING_VALUE",)),
), ),
) )
def test_fieldset_tuple(self): def test_fieldset_tuple(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
self.assertContains(response, '<h2>Numbers</h2>') self.assertContains(response, "<h2>Numbers</h2>")
self.assertContains(response, '<h2>Text</h2>') self.assertContains(response, "<h2>Text</h2>")
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{ {
'Numbers': { "Numbers": {
'fields': ( "fields": (
'INT_VALUE', "INT_VALUE",
'DECIMAL_VALUE', "DECIMAL_VALUE",
), ),
'collapse': True, "collapse": True,
}, },
'Text': { "Text": {
'fields': ( "fields": (
'STRING_VALUE', "STRING_VALUE",
'LINEBREAK_VALUE', "LINEBREAK_VALUE",
), ),
'collapse': True, "collapse": True,
}, },
}, },
) )
def test_collapsed_fieldsets(self): def test_collapsed_fieldsets(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) 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( @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.settings.IGNORE_ADMIN_VERSION_CHECK", True)
@mock.patch('constance.forms.ConstanceForm.save', lambda _: None) @mock.patch("constance.forms.ConstanceForm.save", lambda _: None)
@mock.patch('constance.forms.ConstanceForm.is_valid', lambda _: True) @mock.patch("constance.forms.ConstanceForm.is_valid", lambda _: True)
def test_submit(self): def test_submit(self):
""" """
Test that submitting the admin page results in an http redirect when Test that submitting the admin page results in an http redirect when
everything is in order. 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( request = self.rf.post(
'/admin/constance/config/', "/admin/constance/config/",
data={ data={
**initial_value, **initial_value,
'version': '123', "version": "123",
}, },
) )
request.user = self.superuser request.user = self.superuser
request._dont_enforce_csrf_checks = True request._dont_enforce_csrf_checks = True
with mock.patch('django.contrib.messages.add_message') as mock_message, mock.patch.object( with mock.patch("django.contrib.messages.add_message") as mock_message, mock.patch.object(
ConstanceForm, '__init__', **initial_value, return_value=None ConstanceForm, "__init__", **initial_value, return_value=None
) as mock_form: ) as mock_form:
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
mock_form.assert_called_with(data=request.POST, files=request.FILES, initial=initial_value, request=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) self.assertIsInstance(response, HttpResponseRedirect)
@mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('MULTILINE',)}) @mock.patch("constance.settings.CONFIG_FIELDSETS", {"FieldSetOne": ("MULTILINE",)})
@mock.patch( @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): def test_newlines_normalization(self):
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.post( request = self.rf.post(
'/admin/constance/config/', "/admin/constance/config/",
data={ data={
'MULTILINE': 'Hello\r\nWorld', "MULTILINE": "Hello\r\nWorld",
'version': '123', "version": "123",
}, },
) )
request.user = self.superuser request.user = self.superuser
request._dont_enforce_csrf_checks = True 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, {}) response = self.options.changelist_view(request, {})
self.assertIsInstance(response, HttpResponseRedirect) self.assertIsInstance(response, HttpResponseRedirect)
self.assertEqual(get_values()['MULTILINE'], 'Hello\nWorld') self.assertEqual(get_values()["MULTILINE"], "Hello\nWorld")
@mock.patch( @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("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
@mock.patch('tests.redis_mockup.Connection.set', mock.MagicMock()) @mock.patch("tests.redis_mockup.Connection.set", mock.MagicMock())
def test_submit_aware_datetime(self): def test_submit_aware_datetime(self):
""" """
Test that submitting the admin page results in an http redirect when Test that submitting the admin page results in an http redirect when
everything is in order. everything is in order.
""" """
request = self.rf.post( request = self.rf.post(
'/admin/constance/config/', "/admin/constance/config/",
data={ data={
'DATETIME_VALUE_0': '2019-08-07', "DATETIME_VALUE_0": "2019-08-07",
'DATETIME_VALUE_1': '19:17:01', "DATETIME_VALUE_1": "19:17:01",
'version': '123', "version": "123",
}, },
) )
request.user = self.superuser request.user = self.superuser
request._dont_enforce_csrf_checks = True 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, {}) response = self.options.changelist_view(request, {})
self.assertIsInstance(response, HttpResponseRedirect) self.assertIsInstance(response, HttpResponseRedirect)
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{ {
'Numbers': ('INT_VALUE',), "Numbers": ("INT_VALUE",),
'Text': ('STRING_VALUE',), "Text": ("STRING_VALUE",),
}, },
) )
def test_inconsistent_fieldset_submit(self): 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 Test that the admin page warns users if the CONFIG_FIELDSETS setting
doesn't account for every field in CONFIG. doesn't account for every field in CONFIG.
""" """
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.post('/admin/constance/config/', data=None) request = self.rf.post("/admin/constance/config/", data=None)
request.user = self.superuser request.user = self.superuser
request._dont_enforce_csrf_checks = True 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, {}) response = self.options.changelist_view(request, {})
self.assertContains(response, 'is missing field(s)') self.assertContains(response, "is missing field(s)")
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{ {
'Fieldsets': ( "Fieldsets": (
'STRING_VALUE', "STRING_VALUE",
'INT_VALUE', "INT_VALUE",
), ),
}, },
) )
def test_fieldset_ordering_1(self): def test_fieldset_ordering_1(self):
"""Ordering of inner list should be preserved.""" """Ordering of inner list should be preserved."""
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
response.render() response.render()
content_str = response.content.decode() 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( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{ {
'Fieldsets': ( "Fieldsets": (
'INT_VALUE', "INT_VALUE",
'STRING_VALUE', "STRING_VALUE",
), ),
}, },
) )
def test_fieldset_ordering_2(self): def test_fieldset_ordering_2(self):
"""Ordering of inner list should be preserved.""" """Ordering of inner list should be preserved."""
self.client.login(username='admin', password='nimda') self.client.login(username="admin", password="nimda")
request = self.rf.get('/admin/constance/config/') request = self.rf.get("/admin/constance/config/")
request.user = self.superuser request.user = self.superuser
response = self.options.changelist_view(request, {}) response = self.options.changelist_view(request, {})
response.render() response.render()
content_str = response.content.decode() 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): def test_labels(self):
self.assertEqual(type(self.model._meta.label), str) self.assertEqual(type(self.model._meta.label), str)

View file

@ -8,7 +8,7 @@ from constance.checks import get_inconsistent_fieldnames
class ChecksTestCase(TestCase): 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): def test_get_inconsistent_fieldnames_none(self):
""" """
Test that get_inconsistent_fieldnames returns an empty data and no checks fail Test that get_inconsistent_fieldnames returns an empty data and no checks fail
@ -19,8 +19,8 @@ class ChecksTestCase(TestCase):
self.assertFalse(extra_keys) self.assertFalse(extra_keys)
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{'Set1': list(settings.CONFIG.keys())[:-1]}, {"Set1": list(settings.CONFIG.keys())[:-1]},
) )
def test_get_inconsistent_fieldnames_for_missing_keys(self): def test_get_inconsistent_fieldnames_for_missing_keys(self):
""" """
@ -33,8 +33,8 @@ class ChecksTestCase(TestCase):
self.assertEqual(1, len(check_fieldsets())) self.assertEqual(1, len(check_fieldsets()))
@mock.patch( @mock.patch(
'constance.settings.CONFIG_FIELDSETS', "constance.settings.CONFIG_FIELDSETS",
{'Set1': [*settings.CONFIG.keys(), 'FORGOTTEN_KEY']}, {"Set1": [*settings.CONFIG.keys(), "FORGOTTEN_KEY"]},
) )
def test_get_inconsistent_fieldnames_for_extra_keys(self): def test_get_inconsistent_fieldnames_for_extra_keys(self):
""" """
@ -46,7 +46,7 @@ class ChecksTestCase(TestCase):
self.assertTrue(extra_keys) self.assertTrue(extra_keys)
self.assertEqual(1, len(check_fieldsets())) self.assertEqual(1, len(check_fieldsets()))
@mock.patch('constance.settings.CONFIG_FIELDSETS', {}) @mock.patch("constance.settings.CONFIG_FIELDSETS", {})
def test_check_fieldsets(self): def test_check_fieldsets(self):
"""check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined.""" """check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined."""
del settings.CONFIG_FIELDSETS del settings.CONFIG_FIELDSETS

View file

@ -20,10 +20,10 @@ class CliTestCase(TransactionTestCase):
def test_help(self): def test_help(self):
with contextlib.suppress(SystemExit): with contextlib.suppress(SystemExit):
call_command('constance', '--help') call_command("constance", "--help")
def test_list(self): def test_list(self):
call_command('constance', 'list', stdout=self.out) call_command("constance", "list", stdout=self.out)
self.assertEqual( self.assertEqual(
set(self.out.getvalue().splitlines()), set(self.out.getvalue().splitlines()),
@ -51,16 +51,16 @@ class CliTestCase(TransactionTestCase):
) )
def test_get(self): 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): 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) expected = datetime(2011, 9, 24, 12, 30, 25)
if settings.USE_TZ: if settings.USE_TZ:
@ -70,50 +70,50 @@ class CliTestCase(TransactionTestCase):
def test_get_invalid_name(self): def test_get_invalid_name(self):
self.assertRaisesMessage( self.assertRaisesMessage(
CommandError, 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, call_command,
'constance', "constance",
'get', "get",
'NOT_A_REAL_CONFIG', "NOT_A_REAL_CONFIG",
) )
def test_set_invalid_name(self): def test_set_invalid_name(self):
self.assertRaisesMessage( self.assertRaisesMessage(
CommandError, 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, call_command,
'constance', "constance",
'set', "set",
'NOT_A_REAL_CONFIG', "NOT_A_REAL_CONFIG",
'foo', "foo",
) )
def test_set_invalid_value(self): def test_set_invalid_value(self):
self.assertRaisesMessage( self.assertRaisesMessage(
CommandError, CommandError,
'Enter a valid email address.', "Enter a valid email address.",
call_command, call_command,
'constance', "constance",
'set', "set",
'EMAIL_VALUE', "EMAIL_VALUE",
'not a valid email', "not a valid email",
) )
def test_set_invalid_multi_value(self): def test_set_invalid_multi_value(self):
self.assertRaisesMessage( self.assertRaisesMessage(
CommandError, CommandError,
'Enter a list of values.', "Enter a list of values.",
call_command, call_command,
'constance', "constance",
'set', "set",
'DATETIME_VALUE', "DATETIME_VALUE",
'2011-09-24 12:30:25', "2011-09-24 12:30:25",
) )
def test_delete_stale_records(self): def test_delete_stale_records(self):
initial_count = Constance.objects.count() initial_count = Constance.objects.count()
Constance.objects.create(key='STALE_KEY', value=None) Constance.objects.create(key="STALE_KEY", value=None)
call_command('constance', 'remove_stale_keys', stdout=self.out) call_command("constance", "remove_stale_keys", stdout=self.out)
self.assertEqual(Constance.objects.count(), initial_count, msg=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.datetime = datetime(2023, 10, 5, 15, 30, 0)
self.date = date(2023, 10, 5) self.date = date(2023, 10, 5)
self.time = time(15, 30, 0) self.time = time(15, 30, 0)
self.decimal = Decimal('10.5') self.decimal = Decimal("10.5")
self.uuid = uuid.UUID('12345678123456781234567812345678') self.uuid = uuid.UUID("12345678123456781234567812345678")
self.string = 'test' self.string = "test"
self.integer = 42 self.integer = 42
self.float = 3.14 self.float = 3.14
self.boolean = True self.boolean = True
self.none = None self.none = None
self.timedelta = timedelta(days=1, hours=2, minutes=3) self.timedelta = timedelta(days=1, hours=2, minutes=3)
self.list = [1, 2, self.date] 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): def test_serializes_and_deserializes_default_types(self):
self.assertEqual(dumps(self.datetime), '{"__type__": "datetime", "__value__": "2023-10-05T15:30:00"}') 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))) self.assertEqual(t, loads(dumps(t)))
def test_invalid_deserialization(self): def test_invalid_deserialization(self):
with self.assertRaisesRegex(ValueError, 'Expecting value'): with self.assertRaisesRegex(ValueError, "Expecting value"):
loads('THIS_IS_NOT_RIGHT') loads("THIS_IS_NOT_RIGHT")
with self.assertRaisesRegex(ValueError, 'Invalid object'): with self.assertRaisesRegex(ValueError, "Invalid object"):
loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test", "THIS_IS_NOT_RIGHT": "THIS_IS_NOT_RIGHT"}') 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"}') loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test"}')
def test_handles_unknown_type(self): def test_handles_unknown_type(self):
class UnknownType: class UnknownType:
pass 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()) dumps(UnknownType())
def test_custom_type_serialization(self): def test_custom_type_serialization(self):
@ -84,25 +84,25 @@ class TestJSONSerialization(TestCase):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
register_type(CustomType, 'custom', lambda o: o.value, lambda o: CustomType(o)) register_type(CustomType, "custom", lambda o: o.value, lambda o: CustomType(o))
custom_data = CustomType('test') custom_data = CustomType("test")
json_data = dumps(custom_data) json_data = dumps(custom_data)
self.assertEqual(json_data, '{"__type__": "custom", "__value__": "test"}') self.assertEqual(json_data, '{"__type__": "custom", "__value__": "test"}')
deserialized_data = loads(json_data) deserialized_data = loads(json_data)
self.assertTrue(isinstance(deserialized_data, CustomType)) self.assertTrue(isinstance(deserialized_data, CustomType))
self.assertEqual(deserialized_data.value, 'test') self.assertEqual(deserialized_data.value, "test")
def test_register_known_type(self): def test_register_known_type(self):
with self.assertRaisesRegex(ValueError, 'Discriminator must be specified'): with self.assertRaisesRegex(ValueError, "Discriminator must be specified"):
register_type(int, '', lambda o: o.value, lambda o: int(o)) register_type(int, "", lambda o: o.value, lambda o: int(o))
with self.assertRaisesRegex(ValueError, 'Type with discriminator default is already registered'): 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, "default", lambda o: o.value, lambda o: int(o))
register_type(int, 'new_custom_type', 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'): 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)) register_type(int, "new_custom_type", lambda o: o.value, lambda o: int(o))
def test_nested_collections(self): def test_nested_collections(self):
data = {'key': [[[[{'key': self.date}]]]]} data = {"key": [[[[{"key": self.date}]]]]}
self.assertEqual( self.assertEqual(
dumps(data), dumps(data),
( (

View file

@ -8,16 +8,16 @@ class TestForm(TestCase):
def test_form_field_types(self): def test_form_field_types(self):
f = ConstanceForm({}) f = ConstanceForm({})
self.assertIsInstance(f.fields['INT_VALUE'], fields.IntegerField) self.assertIsInstance(f.fields["INT_VALUE"], fields.IntegerField)
self.assertIsInstance(f.fields['BOOL_VALUE'], fields.BooleanField) self.assertIsInstance(f.fields["BOOL_VALUE"], fields.BooleanField)
self.assertIsInstance(f.fields['STRING_VALUE'], fields.CharField) self.assertIsInstance(f.fields["STRING_VALUE"], fields.CharField)
self.assertIsInstance(f.fields['DECIMAL_VALUE'], fields.DecimalField) self.assertIsInstance(f.fields["DECIMAL_VALUE"], fields.DecimalField)
self.assertIsInstance(f.fields['DATETIME_VALUE'], fields.SplitDateTimeField) self.assertIsInstance(f.fields["DATETIME_VALUE"], fields.SplitDateTimeField)
self.assertIsInstance(f.fields['TIMEDELTA_VALUE'], fields.DurationField) self.assertIsInstance(f.fields["TIMEDELTA_VALUE"], fields.DurationField)
self.assertIsInstance(f.fields['FLOAT_VALUE'], fields.FloatField) self.assertIsInstance(f.fields["FLOAT_VALUE"], fields.FloatField)
self.assertIsInstance(f.fields['DATE_VALUE'], fields.DateField) self.assertIsInstance(f.fields["DATE_VALUE"], fields.DateField)
self.assertIsInstance(f.fields['TIME_VALUE'], fields.TimeField) self.assertIsInstance(f.fields["TIME_VALUE"], fields.TimeField)
# from CONSTANCE_ADDITIONAL_FIELDS # from CONSTANCE_ADDITIONAL_FIELDS
self.assertIsInstance(f.fields['CHOICE_VALUE'], fields.ChoiceField) self.assertIsInstance(f.fields["CHOICE_VALUE"], fields.ChoiceField)
self.assertIsInstance(f.fields['EMAIL_VALUE'], fields.EmailField) 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 that the class decorator changes config.BOOL_VALUE."""
assert not 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): def test_override_config_on_overridden_value(self):
"""Ensure that method mark decorator changes already overridden value for class.""" """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): def test_fixture_override_config(override_config):
""" """
@ -66,7 +66,7 @@ except ImportError:
class PytestTests(unittest.TestCase): class PytestTests(unittest.TestCase):
def setUp(self): 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): def test_do_not_skip_silently(self):
"""If no at least one test present, unittest silently skips module.""" """If no at least one test present, unittest silently skips module."""

View file

@ -11,32 +11,32 @@ from constance.utils import get_values_for_keys
class UtilsTestCase(TestCase): class UtilsTestCase(TestCase):
def test_set_value_validation(self): 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( self.assertRaisesMessage(
ValidationError, ValidationError,
'Enter a valid email address.', "Enter a valid email address.",
_set_constance_value, _set_constance_value,
'EMAIL_VALUE', "EMAIL_VALUE",
'not a valid email', "not a valid email",
) )
self.assertRaisesMessage( self.assertRaisesMessage(
ValidationError, ValidationError,
'Enter a valid date.', "Enter a valid date.",
_set_constance_value, _set_constance_value,
'DATETIME_VALUE', "DATETIME_VALUE",
( (
'2000-00-00', "2000-00-00",
'99:99:99', "99:99:99",
), ),
) )
self.assertRaisesMessage( self.assertRaisesMessage(
ValidationError, ValidationError,
'Enter a valid time.', "Enter a valid time.",
_set_constance_value, _set_constance_value,
'DATETIME_VALUE', "DATETIME_VALUE",
( (
'2016-01-01', "2016-01-01",
'99:99:99', "99:99:99",
), ),
) )
@ -44,37 +44,37 @@ class UtilsTestCase(TestCase):
self.assertEqual( self.assertEqual(
get_values(), get_values(),
{ {
'FLOAT_VALUE': 3.1415926536, "FLOAT_VALUE": 3.1415926536,
'BOOL_VALUE': True, "BOOL_VALUE": True,
'EMAIL_VALUE': 'test@example.com', "EMAIL_VALUE": "test@example.com",
'INT_VALUE': 1, "INT_VALUE": 1,
'CHOICE_VALUE': 'yes', "CHOICE_VALUE": "yes",
'TIME_VALUE': datetime.time(23, 59, 59), "TIME_VALUE": datetime.time(23, 59, 59),
'DATE_VALUE': datetime.date(2010, 12, 24), "DATE_VALUE": datetime.date(2010, 12, 24),
'TIMEDELTA_VALUE': datetime.timedelta(days=1, hours=2, minutes=3), "TIMEDELTA_VALUE": datetime.timedelta(days=1, hours=2, minutes=3),
'LINEBREAK_VALUE': 'Spam spam', "LINEBREAK_VALUE": "Spam spam",
'DECIMAL_VALUE': Decimal('0.1'), "DECIMAL_VALUE": Decimal("0.1"),
'STRING_VALUE': 'Hello world', "STRING_VALUE": "Hello world",
'DATETIME_VALUE': datetime.datetime(2010, 8, 23, 11, 29, 24), "DATETIME_VALUE": datetime.datetime(2010, 8, 23, 11, 29, 24),
'LIST_VALUE': [1, '1', datetime.date(2019, 1, 1)], "LIST_VALUE": [1, "1", datetime.date(2019, 1, 1)],
'JSON_VALUE': { "JSON_VALUE": {
'key': 'value', "key": "value",
'key2': 2, "key2": 2,
'key3': [1, 2, 3], "key3": [1, 2, 3],
'key4': {'key': 'value'}, "key4": {"key": "value"},
'key5': datetime.date(2019, 1, 1), "key5": datetime.date(2019, 1, 1),
'key6': None, "key6": None,
}, },
}, },
) )
def test_get_values_for_keys(self): def test_get_values_for_keys(self):
self.assertEqual( 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, "BOOL_VALUE": True,
'CHOICE_VALUE': 'yes', "CHOICE_VALUE": "yes",
'LINEBREAK_VALUE': 'Spam spam', "LINEBREAK_VALUE": "Spam spam",
}, },
) )
@ -87,9 +87,9 @@ class UtilsTestCase(TestCase):
AttributeError, AttributeError,
'"OLD_VALUE, BOLD_VALUE" keys not found in configuration.', '"OLD_VALUE, BOLD_VALUE" keys not found in configuration.',
get_values_for_keys, 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): def test_get_values_for_keys_invalid_input_type(self):
with self.assertRaises(TypeError): 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 from django.urls import path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
] ]