You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
8.0 KiB
239 lines
8.0 KiB
try:
|
|
from collections.abc import Mapping
|
|
except ImportError:
|
|
from collections import Mapping
|
|
|
|
from .settings import preferences_settings
|
|
from .exceptions import CachedValueNotFound, DoesNotExist
|
|
from .signals import preference_updated
|
|
|
|
|
|
class PreferencesManager(Mapping):
|
|
|
|
"""Handle retrieving / caching of preferences"""
|
|
|
|
def __init__(self, model, registry, **kwargs):
|
|
self.model = model
|
|
self.registry = registry
|
|
self.instance = kwargs.get("instance")
|
|
|
|
@property
|
|
def queryset(self):
|
|
qs = self.model.objects.all()
|
|
if self.instance:
|
|
qs = qs.filter(instance=self.instance)
|
|
return qs
|
|
|
|
@property
|
|
def cache(self):
|
|
from django.core.cache import caches
|
|
|
|
return caches[preferences_settings.CACHE_NAME]
|
|
|
|
def __getitem__(self, key):
|
|
return self.get(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
section, name = self.parse_lookup(key)
|
|
preference = self.registry.get(section=section, name=name, fallback=False)
|
|
preference.validate(value)
|
|
self.update_db_pref(section=section, name=name, value=value)
|
|
|
|
def __repr__(self):
|
|
return repr(self.all())
|
|
|
|
def __iter__(self):
|
|
return self.all().__iter__()
|
|
|
|
def __len__(self):
|
|
return len(self.all())
|
|
|
|
def by_name(self):
|
|
"""Return a dictionary with preferences identifiers and values, but without the section name in the identifier"""
|
|
return {
|
|
key.split(preferences_settings.SECTION_KEY_SEPARATOR)[-1]: value
|
|
for key, value in self.all().items()
|
|
}
|
|
|
|
def get_by_name(self, name):
|
|
return self.get(self.registry.get_by_name(name).identifier())
|
|
|
|
def get_cache_key(self, section, name):
|
|
"""Return the cache key corresponding to a given preference"""
|
|
if not self.instance:
|
|
return "dynamic_preferences_{0}_{1}_{2}".format(
|
|
self.model.__name__, section, name
|
|
)
|
|
return "dynamic_preferences_{0}_{1}_{2}_{3}".format(
|
|
self.model.__name__, self.instance.pk, section, name, self.instance.pk
|
|
)
|
|
|
|
def from_cache(self, section, name):
|
|
"""Return a preference raw_value from cache"""
|
|
cached_value = self.cache.get(
|
|
self.get_cache_key(section, name), CachedValueNotFound
|
|
)
|
|
|
|
if cached_value is CachedValueNotFound:
|
|
raise CachedValueNotFound
|
|
|
|
if cached_value == preferences_settings.CACHE_NONE_VALUE:
|
|
cached_value = None
|
|
return self.registry.get(section=section, name=name).serializer.deserialize(
|
|
cached_value
|
|
)
|
|
|
|
def many_from_cache(self, preferences):
|
|
"""
|
|
Return cached value for given preferences
|
|
missing preferences will be skipped
|
|
"""
|
|
keys = {p: self.get_cache_key(p.section.name, p.name) for p in preferences}
|
|
cached = self.cache.get_many(list(keys.values()))
|
|
|
|
for k, v in cached.items():
|
|
# we replace dummy cached values by None here, if needed
|
|
if v == preferences_settings.CACHE_NONE_VALUE:
|
|
cached[k] = None
|
|
|
|
# we have to remap returned value since the underlying cached keys
|
|
# are not usable for an end user
|
|
return {
|
|
p.identifier(): p.serializer.deserialize(cached[k])
|
|
for p, k in keys.items()
|
|
if k in cached
|
|
}
|
|
|
|
def to_cache(self, pref):
|
|
"""
|
|
Update/create the cache value for the given preference model instance
|
|
"""
|
|
key = self.get_cache_key(pref.section, pref.name)
|
|
value = pref.raw_value
|
|
if value is None or value == "":
|
|
# some cache backends refuse to cache None or empty values
|
|
# resulting in more DB queries, so we cache an arbitrary value
|
|
# to ensure the cache is hot (even with empty values)
|
|
value = preferences_settings.CACHE_NONE_VALUE
|
|
self.cache.set(key, value)
|
|
|
|
def pref_obj(self, section, name):
|
|
return self.registry.get(section=section, name=name)
|
|
|
|
def parse_lookup(self, lookup):
|
|
try:
|
|
section, name = lookup.split(preferences_settings.SECTION_KEY_SEPARATOR)
|
|
except ValueError:
|
|
name = lookup
|
|
section = None
|
|
return section, name
|
|
|
|
def get(self, key, no_cache=False):
|
|
"""Return the value of a single preference using a dotted path key
|
|
:arg no_cache: if true, the cache is bypassed
|
|
"""
|
|
section, name = self.parse_lookup(key)
|
|
preference = self.registry.get(section=section, name=name, fallback=False)
|
|
if no_cache or not preferences_settings.ENABLE_CACHE:
|
|
return self.get_db_pref(section=section, name=name).value
|
|
|
|
try:
|
|
return self.from_cache(section, name)
|
|
except CachedValueNotFound:
|
|
pass
|
|
|
|
db_pref = self.get_db_pref(section=section, name=name)
|
|
self.to_cache(db_pref)
|
|
return db_pref.value
|
|
|
|
def get_db_pref(self, section, name):
|
|
try:
|
|
pref = self.queryset.get(section=section, name=name)
|
|
except self.model.DoesNotExist:
|
|
pref_obj = self.pref_obj(section=section, name=name)
|
|
pref = self.create_db_pref(
|
|
section=section, name=name, value=pref_obj.get("default")
|
|
)
|
|
|
|
return pref
|
|
|
|
def update_db_pref(self, section, name, value):
|
|
try:
|
|
db_pref = self.queryset.get(section=section, name=name)
|
|
old_value = db_pref.value
|
|
db_pref.value = value
|
|
db_pref.save()
|
|
preference_updated.send(
|
|
sender=self.__class__,
|
|
section=section,
|
|
name=name,
|
|
old_value=old_value,
|
|
new_value=value,
|
|
)
|
|
except self.model.DoesNotExist:
|
|
return self.create_db_pref(section, name, value)
|
|
|
|
return db_pref
|
|
|
|
def create_db_pref(self, section, name, value):
|
|
kwargs = {
|
|
"section": section,
|
|
"name": name,
|
|
}
|
|
if self.instance:
|
|
kwargs["instance"] = self.instance
|
|
|
|
# this is a just a shortcut to get the raw, serialized value
|
|
# so we can pass it to get_or_create
|
|
m = self.model(**kwargs)
|
|
m.value = value
|
|
raw_value = m.raw_value
|
|
|
|
db_pref, created = self.model.objects.get_or_create(**kwargs)
|
|
if created and db_pref.raw_value != raw_value:
|
|
db_pref.raw_value = raw_value
|
|
db_pref.save()
|
|
|
|
return db_pref
|
|
|
|
def all(self):
|
|
"""Return a dictionary containing all preferences by section
|
|
Loaded from cache or from db in case of cold cache
|
|
"""
|
|
if not preferences_settings.ENABLE_CACHE:
|
|
return self.load_from_db()
|
|
|
|
preferences = self.registry.preferences()
|
|
|
|
# first we hit the cache once for all existing preferences
|
|
a = self.many_from_cache(preferences)
|
|
if len(a) == len(preferences):
|
|
return a # avoid database hit if not necessary
|
|
|
|
# then we fill those that miss, but exist in the database
|
|
# (just hit the database for all of them, filtering is complicated, and
|
|
# in most cases you'd need to grab the majority of them anyway)
|
|
a.update(self.load_from_db(cache=True))
|
|
return a
|
|
|
|
def load_from_db(self, cache=False):
|
|
"""Return a dictionary of preferences by section directly from DB"""
|
|
a = {}
|
|
db_prefs = {p.preference.identifier(): p for p in self.queryset}
|
|
for preference in self.registry.preferences():
|
|
try:
|
|
db_pref = db_prefs[preference.identifier()]
|
|
except KeyError:
|
|
db_pref = self.create_db_pref(
|
|
section=preference.section.name,
|
|
name=preference.name,
|
|
value=preference.get("default"),
|
|
)
|
|
else:
|
|
# cache if create_db_pref() hasn't already done so
|
|
if cache:
|
|
self.to_cache(db_pref)
|
|
|
|
a[preference.identifier()] = db_pref.value
|
|
|
|
return a
|