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