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