Simple app to add configuration options to a Django project.
1from typing import TYPE_CHECKING, Optional, Sequence, Union
2
3from django.core.cache import caches
4from django.db import models
5
6from options import get_option_model
7from options.settings import (
8 DEFAULT_EXCLUDE_USER,
9 DEFAULT_OPTION_CACHE_ALIAS,
10 DEFAULT_OPTION_CACHE_TIMEOUT,
11)
12
13if TYPE_CHECKING:
14 from django.contrib.auth.models import User
15
16
17class OptionQuerySet(models.QuerySet):
18 def public(self) -> "OptionQuerySet":
19 """Gets public options."""
20 return self.filter(is_public=True)
21
22
23class OptionManager(models.Manager):
24 """Manager for options."""
25
26 cache_prefix = "_options_option_"
27
28 def __get_cached_value(
29 self, name: str
30 ) -> Optional[Union[int, float, str, Sequence]]:
31 return caches[DEFAULT_OPTION_CACHE_ALIAS].get(f"{self.cache_prefix}{name}")
32
33 def __set_cached_value(
34 self, name: str, value: Union[int, float, str, Sequence]
35 ) -> None:
36 caches[DEFAULT_OPTION_CACHE_ALIAS].set(
37 f"{self.cache_prefix}{name}", value, DEFAULT_OPTION_CACHE_TIMEOUT
38 )
39
40 def get_queryset(self) -> "OptionQuerySet":
41 return OptionQuerySet(self.model, using=self._db)
42
43 def public(self) -> "OptionQuerySet":
44 """Gets public options."""
45 return self.get_queryset().public()
46
47 def get_value(
48 self, name: str, default: Optional[Union[int, float, str, Sequence]] = None
49 ) -> Optional[Union[int, float, str, Sequence]]:
50 """Gets the value with the proper type."""
51 _cached_value = self.__get_cached_value(name=name)
52 if _cached_value is not None:
53 return _cached_value
54 try:
55 option = self.model.objects.get(name=name)
56 value = option.get_value()
57 except self.model.DoesNotExist:
58 value = default
59 self.__set_cached_value(name=name, value=value)
60 return value
61
62
63class UserOptionQuerySet(models.QuerySet):
64 def public(self) -> "UserOptionQuerySet":
65 """Gets public options."""
66 return self.filter(is_public=True)
67
68
69class UserOptionManager(models.Manager):
70 """Manager to handle user's custom options."""
71
72 cache_prefix = "_option_user_option_"
73
74 def __get_cached_value(
75 self, name: str, user: Optional["User"] = None
76 ) -> Optional[Union[int, float, str, Sequence]]:
77 key = (
78 f"{self.cache_prefix}{name}"
79 if user is None
80 else f"{self.cache_prefix}{name}_{user.pk}"
81 )
82 return caches[DEFAULT_OPTION_CACHE_ALIAS].get(key)
83
84 def __set_cached_value(
85 self,
86 name: str,
87 value: Union[int, float, str, Sequence],
88 user: Optional["User"] = None,
89 ) -> None:
90 key = (
91 f"{self.cache_prefix}{name}"
92 if user is None
93 else f"{self.cache_prefix}{name}_{user.pk}"
94 )
95 caches[DEFAULT_OPTION_CACHE_ALIAS].set(key, value, DEFAULT_OPTION_CACHE_TIMEOUT)
96
97 def get_queryset(self) -> "UserOptionQuerySet":
98 return OptionQuerySet(self.model, using=self._db)
99
100 def public(self) -> "UserOptionQuerySet":
101 """Gets public options."""
102 return self.get_queryset().public()
103
104 def filter_user_customizable(self) -> "UserOptionQuerySet":
105 """Returns option that the user can customize himself."""
106 return self.exclude(name__in=DEFAULT_EXCLUDE_USER)
107
108 def get_value(
109 self,
110 name,
111 user: Optional["User"] = None,
112 default: Optional[Union[int, float, str, Sequence]] = None,
113 ) -> Optional[Union[int, float, str, Sequence]]:
114 """Gets the value with the proper type."""
115 Option = get_option_model()
116 _cached_value = self.__get_cached_value(name=name, user=user)
117 if _cached_value is not None:
118 return _cached_value
119 if user is None:
120 value = Option.objects.get_value(name=name, default=default)
121 try:
122 option = self.model.objects.get(user=user, name=name)
123 value = option.get_value()
124 except self.model.DoesNotExist:
125 value = Option.objects.get_value(name=name, default=default)
126 self.__set_cached_value(name=name, value=value, user=user)
127 return value