Add SiteSetting model for admin-configurable settings
This commit is contained in:
1 parent
ac38227dac
commit
f03b3bfdaf
1 file changed
+98
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Key/value store for admin-configurable site settings.
|
||||
|
||||
Values are strings on disk; the typed helpers below do the coercion. Reads
|
||||
are cached per process and the cache is dropped on write, so the settings
|
||||
page is authoritative without hitting the database on every request.
|
||||
|
||||
Created by: Jess Rogerson (yelling commands at Claude.AI)
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
from app import db
|
||||
|
||||
# Defaults are the behaviour LIM had before settings existed, except where
|
||||
# noted — anything not yet stored falls back to these.
|
||||
DEFAULTS = {
|
||||
'allow_registration': 'true',
|
||||
# Visibility given to newly created sets.
|
||||
'default_visibility': 'private',
|
||||
# Send logged-in users straight to their dashboard instead of the
|
||||
# marketing homepage.
|
||||
'home_redirect_dashboard': 'true',
|
||||
}
|
||||
|
||||
VISIBILITIES = ('private', 'shared', 'public')
|
||||
|
||||
_cache = {}
|
||||
_cache_lock = threading.Lock()
|
||||
|
||||
|
||||
class SiteSetting(db.Model):
|
||||
"""One configurable setting."""
|
||||
|
||||
__tablename__ = 'site_settings'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(64), unique=True, nullable=False, index=True)
|
||||
value = db.Column(db.Text, nullable=True)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow,
|
||||
onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<SiteSetting {self.key}={self.value!r}>'
|
||||
|
||||
# -- reads --------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def get(cls, key, default=None):
|
||||
with _cache_lock:
|
||||
if key in _cache:
|
||||
return _cache[key]
|
||||
try:
|
||||
row = cls.query.filter_by(key=key).first()
|
||||
except Exception:
|
||||
# Table may not exist yet (first boot, before create_all).
|
||||
return DEFAULTS.get(key, default)
|
||||
value = row.value if row else DEFAULTS.get(key, default)
|
||||
with _cache_lock:
|
||||
_cache[key] = value
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def get_bool(cls, key, default=False):
|
||||
value = cls.get(key)
|
||||
if value is None:
|
||||
return default
|
||||
return str(value).strip().lower() in ('1', 'true', 'yes', 'on')
|
||||
|
||||
@classmethod
|
||||
def all_settings(cls):
|
||||
merged = dict(DEFAULTS)
|
||||
try:
|
||||
for row in cls.query.all():
|
||||
merged[row.key] = row.value
|
||||
except Exception:
|
||||
pass
|
||||
return merged
|
||||
|
||||
# -- writes -------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, value):
|
||||
if isinstance(value, bool):
|
||||
value = 'true' if value else 'false'
|
||||
row = cls.query.filter_by(key=key).first()
|
||||
if row:
|
||||
row.value = value
|
||||
else:
|
||||
db.session.add(cls(key=key, value=value))
|
||||
cls.clear_cache()
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
with _cache_lock:
|
||||
_cache.clear()
|
||||
Reference in new issue
Block a user