Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 42 additions & 42 deletions constance/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@


class ConstanceAdmin(admin.ModelAdmin):
change_list_template = 'admin/constance/change_list.html'
change_list_template = "admin/constance/change_list.html"
change_list_form = ConstanceForm

def __init__(self, model, admin_site):
model._meta.concrete_model = Config
super().__init__(model, admin_site)

def get_urls(self):
info = f'{self.model._meta.app_label}_{self.model._meta.module_name}'
info = f"{self.model._meta.app_label}_{self.model._meta.module_name}"
return [
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_changelist'),
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_add'),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_changelist"),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"),
]

def get_config_value(self, name, options, form, initial):
Expand All @@ -52,23 +52,23 @@ def get_config_value(self, name, options, form, initial):

form_field = form[name]
config_value = {
'name': name,
'default': localize(default),
'raw_default': default,
'help_text': _(help_text),
'value': localize(value),
'modified': localize(value) != localize(default),
'form_field': form_field,
'is_date': isinstance(default, date),
'is_datetime': isinstance(default, datetime),
'is_checkbox': isinstance(form_field.field.widget, forms.CheckboxInput),
'is_file': isinstance(form_field.field.widget, forms.FileInput),
"name": name,
"default": localize(default),
"raw_default": default,
"help_text": _(help_text),
"value": localize(value),
"modified": localize(value) != localize(default),
"form_field": form_field,
"is_date": isinstance(default, date),
"is_datetime": isinstance(default, datetime),
"is_checkbox": isinstance(form_field.field.widget, forms.CheckboxInput),
"is_file": isinstance(form_field.field.widget, forms.FileInput),
}
if field_type and field_type in settings.ADDITIONAL_FIELDS:
serialized_default = form[name].field.prepare_value(default)
config_value['default'] = serialized_default
config_value['raw_default'] = serialized_default
config_value['value'] = form[name].field.prepare_value(value)
config_value["default"] = serialized_default
config_value["raw_default"] = serialized_default
config_value["value"] = form[name].field.prepare_value(value)

return config_value

Expand All @@ -85,47 +85,47 @@ def changelist_view(self, request, extra_context=None):
initial = get_values()
form_cls = self.get_changelist_form(request)
form = form_cls(initial=initial, request=request)
if request.method == 'POST' and request.user.has_perm('constance.change_config'):
if request.method == "POST" and request.user.has_perm("constance.change_config"):
form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS, _('Live settings updated successfully.'))
return HttpResponseRedirect('.')
messages.add_message(request, messages.ERROR, _('Failed to update live settings.'))
messages.add_message(request, messages.SUCCESS, _("Live settings updated successfully."))
return HttpResponseRedirect(".")
messages.add_message(request, messages.ERROR, _("Failed to update live settings."))
context = dict(
self.admin_site.each_context(request),
config_values=[],
title=self.model._meta.app_config.verbose_name,
app_label='constance',
app_label="constance",
opts=self.model._meta,
form=form,
media=self.media + form.media,
icon_type='svg',
icon_type="svg",
django_version=get_version(),
)
for name, options in settings.CONFIG.items():
context['config_values'].append(self.get_config_value(name, options, form, initial))
context["config_values"].append(self.get_config_value(name, options, form, initial))

if settings.CONFIG_FIELDSETS:
if isinstance(settings.CONFIG_FIELDSETS, dict):
fieldset_items = settings.CONFIG_FIELDSETS.items()
else:
fieldset_items = settings.CONFIG_FIELDSETS

context['fieldsets'] = []
context["fieldsets"] = []
for fieldset_title, fieldset_data in fieldset_items:
if isinstance(fieldset_data, dict):
fields_list = fieldset_data['fields']
collapse = fieldset_data.get('collapse', False)
fields_list = fieldset_data["fields"]
collapse = fieldset_data.get("collapse", False)
else:
fields_list = fieldset_data
collapse = False

absent_fields = [field for field in fields_list if field not in settings.CONFIG]
if any(absent_fields):
raise ValueError(
'CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}'.format(
', '.join(absent_fields)
"CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}".format(
", ".join(absent_fields)
)
)

Expand All @@ -135,16 +135,16 @@ def changelist_view(self, request, extra_context=None):
options = settings.CONFIG.get(name)
if options:
config_values.append(self.get_config_value(name, options, form, initial))
fieldset_context = {'title': fieldset_title, 'config_values': config_values}
fieldset_context = {"title": fieldset_title, "config_values": config_values}

if collapse:
fieldset_context['collapse'] = True
context['fieldsets'].append(fieldset_context)
fieldset_context["collapse"] = True
context["fieldsets"].append(fieldset_context)
if not isinstance(settings.CONFIG_FIELDSETS, (OrderedDict, tuple)):
context['fieldsets'].sort(key=itemgetter('title'))
context["fieldsets"].sort(key=itemgetter("title"))

if not isinstance(settings.CONFIG, OrderedDict):
context['config_values'].sort(key=itemgetter('name'))
context["config_values"].sort(key=itemgetter("name"))
request.current_app = self.admin_site.name
return TemplateResponse(request, self.change_list_template, context)

Expand All @@ -162,11 +162,11 @@ def has_change_permission(self, request, obj=None):

class Config:
class Meta:
app_label = 'constance'
object_name = 'Config'
app_label = "constance"
object_name = "Config"
concrete_model = None
model_name = module_name = 'config'
verbose_name_plural = _('config')
model_name = module_name = "config"
verbose_name_plural = _("config")
abstract = False
swapped = False
is_composite_pk = False
Expand All @@ -175,19 +175,19 @@ def get_ordered_objects(self):
return False

def get_change_permission(self):
return f'change_{self.model_name}'
return f"change_{self.model_name}"

@property
def app_config(self):
return apps.get_app_config(self.app_label)

@property
def label(self):
return f'{self.app_label}.{self.object_name}'
return f"{self.app_label}.{self.object_name}"

@property
def label_lower(self):
return f'{self.app_label}.{self.model_name}'
return f"{self.app_label}.{self.model_name}"

_meta = Meta()

Expand Down
8 changes: 4 additions & 4 deletions constance/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@


class ConstanceConfig(AppConfig):
name = 'constance'
verbose_name = _('Constance')
default_auto_field = 'django.db.models.AutoField'
name = "constance"
verbose_name = _("Constance")
default_auto_field = "django.db.models.AutoField"

def ready(self):
checks.register(check_fieldsets, 'constance')
checks.register(check_fieldsets, "constance")
10 changes: 5 additions & 5 deletions constance/backends/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(self):
self._model = Constance
self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled'
self._autofill_cachekey = "autofilled"

if self._model._meta.app_config is None:
raise ImproperlyConfigured(
Expand All @@ -34,9 +34,9 @@ def __init__(self):
self._cache = caches[settings.DATABASE_CACHE_BACKEND]
if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured(
'The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a '
"The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a "
f"subclass of Django's local-memory backend ({settings.DATABASE_CACHE_BACKEND!r}). Please "
'set it to a backend that supports cross-process caching.'
"set it to a backend that supports cross-process caching."
)
else:
self._cache = None
Expand All @@ -45,7 +45,7 @@ def __init__(self):
post_save.connect(self.clear, sender=self._model)

def add_prefix(self, key):
return f'{self._prefix}{key}'
return f"{self._prefix}{key}"

def autofill(self):
if not self._autofill_timeout or not self._cache:
Expand Down Expand Up @@ -111,7 +111,7 @@ def set(self, key, value):
if not created:
old_value = loads(constance.value)
constance.value = dumps(value)
constance.save(update_fields=['value'])
constance.save(update_fields=["value"])
else:
old_value = None

Expand Down
4 changes: 2 additions & 2 deletions constance/backends/redisd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ def __init__(self):
try:
import redis
except ImportError:
raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.') from None
raise ImproperlyConfigured("The Redis backend requires redis-py to be installed.") from None
if isinstance(settings.REDIS_CONNECTION, str):
self._rd = redis.from_url(settings.REDIS_CONNECTION)
else:
self._rd = redis.Redis(**settings.REDIS_CONNECTION)

def add_prefix(self, key):
return f'{self._prefix}{key}'
return f"{self._prefix}{key}"

def get(self, key):
value = self._rd.get(self.add_prefix(key))
Expand Down
2 changes: 1 addition & 1 deletion constance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Config:
"""The global config wrapper that handles the backend."""

def __init__(self):
super().__setattr__('_backend', utils.import_module_attr(settings.BACKEND)())
super().__setattr__("_backend", utils.import_module_attr(settings.BACKEND)())

def __getattr__(self, key):
try:
Expand Down
22 changes: 11 additions & 11 deletions constance/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@ def check_fieldsets(*args, **kwargs) -> list[CheckMessage]:

errors = []

if hasattr(settings, 'CONFIG_FIELDSETS') and settings.CONFIG_FIELDSETS:
if hasattr(settings, "CONFIG_FIELDSETS") and settings.CONFIG_FIELDSETS:
missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys:
check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.'),
hint=', '.join(sorted(missing_keys)),
obj='settings.CONSTANCE_CONFIG',
id='constance.E001',
_("CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG."),
hint=", ".join(sorted(missing_keys)),
obj="settings.CONSTANCE_CONFIG",
id="constance.E001",
)
errors.append(check)
if extra_keys:
check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG.'),
hint=', '.join(sorted(extra_keys)),
obj='settings.CONSTANCE_CONFIG',
id='constance.E002',
_("CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG."),
hint=", ".join(sorted(extra_keys)),
obj="settings.CONSTANCE_CONFIG",
id="constance.E002",
)
errors.append(check)
return errors
Expand All @@ -53,8 +53,8 @@ def get_inconsistent_fieldnames() -> tuple[set, set]:
for _fieldset_title, fields_list in fieldset_items:
# fields_list can be a dictionary, when a fieldset is defined as collapsible
# https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing
if isinstance(fields_list, dict) and 'fields' in fields_list:
fields_list = fields_list['fields']
if isinstance(fields_list, dict) and "fields" in fields_list:
fields_list = fields_list["fields"]
unique_field_names.update(fields_list)
if not unique_field_names:
return unique_field_names, unique_field_names
Expand Down
44 changes: 22 additions & 22 deletions constance/codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

logger = logging.getLogger(__name__)

DEFAULT_DISCRIMINATOR = 'default'
DEFAULT_DISCRIMINATOR = "default"


class JSONEncoder(json.JSONEncoder):
Expand All @@ -24,11 +24,11 @@ def default(self, o):
for discriminator, (t, _, encoder) in _codecs.items():
if isinstance(o, t):
return _as(discriminator, encoder(o))
raise TypeError(f'Object of type {o.__class__.__name__} is not JSON serializable')
raise TypeError(f"Object of type {o.__class__.__name__} is not JSON serializable")


def _as(discriminator: str, v: Any) -> dict[str, Any]:
return {'__type__': discriminator, '__value__': v}
return {"__type__": discriminator, "__value__": v}


def dumps(obj, _dumps=json.dumps, cls=JSONEncoder, default_kwargs=None, **kwargs):
Expand All @@ -44,7 +44,7 @@ def loads(s, _loads=json.loads, *, first_level=True, **kwargs):
"""Deserialize json string to object."""
if first_level:
return _loads(s, object_hook=object_hook, **kwargs)
if isinstance(s, dict) and '__type__' not in s and '__value__' not in s:
if isinstance(s, dict) and "__type__" not in s and "__value__" not in s:
return {k: loads(v, first_level=False) for k, v in s.items()}
if isinstance(s, list):
return list(loads(v, first_level=False) for v in s)
Expand All @@ -53,20 +53,20 @@ def loads(s, _loads=json.loads, *, first_level=True, **kwargs):

def object_hook(o: dict) -> Any:
"""Hook function to perform custom deserialization."""
if o.keys() == {'__type__', '__value__'}:
if o['__type__'] == DEFAULT_DISCRIMINATOR:
return o['__value__']
codec = _codecs.get(o['__type__'])
if o.keys() == {"__type__", "__value__"}:
if o["__type__"] == DEFAULT_DISCRIMINATOR:
return o["__value__"]
codec = _codecs.get(o["__type__"])
if not codec:
raise ValueError(f'Unsupported type: {o["__type__"]}')
return codec[1](o['__value__'])
if '__type__' not in o and '__value__' not in o:
raise ValueError(f"Unsupported type: {o['__type__']}")
return codec[1](o["__value__"])
if "__type__" not in o and "__value__" not in o:
return o
logger.error('Cannot deserialize object: %s', o)
raise ValueError(f'Invalid object: {o}')
logger.error("Cannot deserialize object: %s", o)
raise ValueError(f"Invalid object: {o}")


T = TypeVar('T')
T = TypeVar("T")


class Encoder(Protocol[T]):
Expand All @@ -79,9 +79,9 @@ def __call__(self, value: str, /) -> T: ... # pragma: no cover

def register_type(t: type[T], discriminator: str, encoder: Encoder[T], decoder: Decoder[T]):
if not discriminator:
raise ValueError('Discriminator must be specified')
raise ValueError("Discriminator must be specified")
if _codecs.get(discriminator) or discriminator == DEFAULT_DISCRIMINATOR:
raise ValueError(f'Type with discriminator {discriminator} is already registered')
raise ValueError(f"Type with discriminator {discriminator} is already registered")
_codecs[discriminator] = (t, decoder, encoder)


Expand All @@ -90,12 +90,12 @@ def register_type(t: type[T], discriminator: str, encoder: Encoder[T], decoder:

def _register_default_types():
# NOTE: datetime should be registered before date, because datetime is also instance of date.
register_type(datetime, 'datetime', datetime.isoformat, datetime.fromisoformat)
register_type(date, 'date', lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date())
register_type(time, 'time', lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, 'decimal', str, Decimal)
register_type(uuid.UUID, 'uuid', lambda o: o.hex, uuid.UUID)
register_type(timedelta, 'timedelta', lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))
register_type(datetime, "datetime", datetime.isoformat, datetime.fromisoformat)
register_type(date, "date", lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date())
register_type(time, "time", lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, "decimal", str, Decimal)
register_type(uuid.UUID, "uuid", lambda o: o.hex, uuid.UUID)
register_type(timedelta, "timedelta", lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))


_register_default_types()
Loading