1from django.core.cache import caches 2from django.core.cache.backends.locmem import LocMemCache 3from django.core.exceptions import ImproperlyConfigured 4from django.db import OperationalError, ProgrammingError 5from django.db.models.signals import post_save 6 7from .. import Backend 8from ... import settings, signals, config 9 10 11class DatabaseBackend(Backend): 12 def __init__(self): 13 from .models import Constance 14 self._model = Constance 15 self._prefix = settings.DATABASE_PREFIX 16 self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT 17 self._autofill_cachekey = 'autofilled' 18 19 if not self._model._meta.installed: 20 raise ImproperlyConfigured( 21 "The constance.backends.database app isn't installed " 22 "correctly. Make sure it's in your INSTALLED_APPS setting.") 23 24 if settings.DATABASE_CACHE_BACKEND: 25 self._cache = caches[settings.DATABASE_CACHE_BACKEND] 26 if isinstance(self._cache, LocMemCache): 27 raise ImproperlyConfigured( 28 "The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a " 29 "subclass of Django's local-memory backend (%r). Please " 30 "set it to a backend that supports cross-process caching." 31 % settings.DATABASE_CACHE_BACKEND) 32 else: 33 self._cache = None 34 self.autofill() 35 # Clear simple cache. 36 post_save.connect(self.clear, sender=self._model) 37 38 def add_prefix(self, key): 39 return "%s%s" % (self._prefix, key) 40 41 def autofill(self): 42 if not self._autofill_timeout or not self._cache: 43 return 44 full_cachekey = self.add_prefix(self._autofill_cachekey) 45 if self._cache.get(full_cachekey): 46 return 47 autofill_values = {} 48 autofill_values[full_cachekey] = 1 49 for key, value in self.mget(settings.CONFIG): 50 autofill_values[self.add_prefix(key)] = value 51 self._cache.set_many(autofill_values, timeout=self._autofill_timeout) 52 53 def mget(self, keys): 54 if not keys: 55 return 56 keys = {self.add_prefix(key): key for key in keys} 57 try: 58 stored = self._model._default_manager.filter(key__in=keys) 59 for const in stored: 60 yield keys[const.key], const.value 61 except (OperationalError, ProgrammingError): 62 pass 63 64 def get(self, key): 65 key = self.add_prefix(key) 66 if self._cache: 67 value = self._cache.get(key) 68 if value is None: 69 self.autofill() 70 value = self._cache.get(key) 71 else: 72 value = None 73 if value is None: 74 try: 75 value = self._model._default_manager.get(key=key).value 76 except (OperationalError, ProgrammingError, self._model.DoesNotExist): 77 pass 78 else: 79 if self._cache: 80 self._cache.add(key, value) 81 return value 82 83 def set(self, key, value): 84 key = self.add_prefix(key) 85 86 try: 87 constance = self._model._default_manager.get(key=key) 88 except (OperationalError, ProgrammingError): 89 # database is not created, noop 90 return 91 except self._model.DoesNotExist: 92 old_value = None 93 constance = self._model._default_manager.create(key=key, value=value) 94 else: 95 old_value = constance.value 96 constance.value = value 97 constance.save() 98 99 if self._cache: 100 self._cache.set(key, value) 101 102 signals.config_updated.send( 103 sender=config, key=key, old_value=old_value, new_value=value 104 ) 105 106 def clear(self, sender, instance, created, **kwargs): 107 if self._cache and not created: 108 keys = [self.add_prefix(k) for k in settings.CONFIG] 109 keys.append(self.add_prefix(self._autofill_cachekey)) 110 self._cache.delete_many(keys) 111 self.autofill() 112