splent-feature-settings 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- splent_feature_settings-0.2.1/MANIFEST.in +8 -0
- splent_feature_settings-0.2.1/PKG-INFO +5 -0
- splent_feature_settings-0.2.1/pyproject.toml +55 -0
- splent_feature_settings-0.2.1/setup.cfg +4 -0
- splent_feature_settings-0.2.1/src/splent_feature_settings.egg-info/PKG-INFO +5 -0
- splent_feature_settings-0.2.1/src/splent_feature_settings.egg-info/SOURCES.txt +26 -0
- splent_feature_settings-0.2.1/src/splent_feature_settings.egg-info/dependency_links.txt +1 -0
- splent_feature_settings-0.2.1/src/splent_feature_settings.egg-info/top_level.txt +1 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/__init__.py +22 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/assets/dist/splent_feature_settings.bundle.js +20 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/assets/dist/splent_feature_settings.bundle.js.map +1 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/commands.py +21 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/config.py +19 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/forms.py +6 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/hooks.py +26 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/migrations/alembic.ini +36 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/migrations/env.py +9 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/migrations/script.py.mako +24 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/migrations/versions/settings0001_initial.py +29 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/models.py +16 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/repositories.py +7 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/routes.py +46 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/seeders.py +10 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/services.py +39 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/signals.py +19 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/templates/settings/admin/feature.html +58 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/templates/settings/admin/index.html +25 -0
- splent_feature_settings-0.2.1/src/splent_io/splent_feature_settings/templates/settings/index.html +10 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Compiled frontend bundles
|
|
2
|
+
recursive-include src/splent_io/splent_feature_settings/assets/dist *.js *.css *.map
|
|
3
|
+
|
|
4
|
+
# Jinja templates (feature views + hook fragments)
|
|
5
|
+
recursive-include src/splent_io/splent_feature_settings/templates *.html
|
|
6
|
+
|
|
7
|
+
# Alembic migration config and scripts
|
|
8
|
+
recursive-include src/splent_io/splent_feature_settings/migrations *.py *.ini *.mako
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80.3.1", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "splent_feature_settings"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.setuptools]
|
|
16
|
+
package-dir = { "" = "src" }
|
|
17
|
+
include-package-data = true
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
21
|
+
exclude = ["*.tests", "*.tests.*"]
|
|
22
|
+
|
|
23
|
+
[tool.splent]
|
|
24
|
+
cli_version = "1.11.0"
|
|
25
|
+
|
|
26
|
+
# ── Feature Contract (auto-generated) ────────────────────────────────────────
|
|
27
|
+
# Do not edit manually — re-run `splent feature:contract --write` to refresh.
|
|
28
|
+
# Preserved across refreshes: description, category, tags, env, and
|
|
29
|
+
# requires.features_manual (dependencies invisible to static analysis).
|
|
30
|
+
[tool.splent.contract]
|
|
31
|
+
description = "Key-value site settings storage used by theme and admin"
|
|
32
|
+
archetype = "full"
|
|
33
|
+
|
|
34
|
+
[tool.splent.contract.provides]
|
|
35
|
+
routes = ["/admin/settings", "/admin/settings/<feature>"]
|
|
36
|
+
blueprints = []
|
|
37
|
+
models = ["Setting"]
|
|
38
|
+
commands = ["hello"]
|
|
39
|
+
hooks = ["layout.authenticated_sidebar"]
|
|
40
|
+
services = ["SettingsService"]
|
|
41
|
+
signals = []
|
|
42
|
+
translations = []
|
|
43
|
+
docker = []
|
|
44
|
+
|
|
45
|
+
[tool.splent.contract.requires]
|
|
46
|
+
features = []
|
|
47
|
+
env_vars = []
|
|
48
|
+
signals = []
|
|
49
|
+
|
|
50
|
+
[tool.splent.contract.extensible]
|
|
51
|
+
services = ["SettingsService"]
|
|
52
|
+
templates = ["settings/admin/feature.html", "settings/admin/index.html", "settings/index.html"]
|
|
53
|
+
models = ["Setting"]
|
|
54
|
+
hooks = ["layout.authenticated_sidebar"]
|
|
55
|
+
routes = false
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/splent_feature_settings.egg-info/PKG-INFO
|
|
4
|
+
src/splent_feature_settings.egg-info/SOURCES.txt
|
|
5
|
+
src/splent_feature_settings.egg-info/dependency_links.txt
|
|
6
|
+
src/splent_feature_settings.egg-info/top_level.txt
|
|
7
|
+
src/splent_io/splent_feature_settings/__init__.py
|
|
8
|
+
src/splent_io/splent_feature_settings/commands.py
|
|
9
|
+
src/splent_io/splent_feature_settings/config.py
|
|
10
|
+
src/splent_io/splent_feature_settings/forms.py
|
|
11
|
+
src/splent_io/splent_feature_settings/hooks.py
|
|
12
|
+
src/splent_io/splent_feature_settings/models.py
|
|
13
|
+
src/splent_io/splent_feature_settings/repositories.py
|
|
14
|
+
src/splent_io/splent_feature_settings/routes.py
|
|
15
|
+
src/splent_io/splent_feature_settings/seeders.py
|
|
16
|
+
src/splent_io/splent_feature_settings/services.py
|
|
17
|
+
src/splent_io/splent_feature_settings/signals.py
|
|
18
|
+
src/splent_io/splent_feature_settings/assets/dist/splent_feature_settings.bundle.js
|
|
19
|
+
src/splent_io/splent_feature_settings/assets/dist/splent_feature_settings.bundle.js.map
|
|
20
|
+
src/splent_io/splent_feature_settings/migrations/alembic.ini
|
|
21
|
+
src/splent_io/splent_feature_settings/migrations/env.py
|
|
22
|
+
src/splent_io/splent_feature_settings/migrations/script.py.mako
|
|
23
|
+
src/splent_io/splent_feature_settings/migrations/versions/settings0001_initial.py
|
|
24
|
+
src/splent_io/splent_feature_settings/templates/settings/index.html
|
|
25
|
+
src/splent_io/splent_feature_settings/templates/settings/admin/feature.html
|
|
26
|
+
src/splent_io/splent_feature_settings/templates/settings/admin/index.html
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
splent_io
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from splent_framework.blueprints.base_blueprint import create_blueprint
|
|
2
|
+
from splent_framework.services.service_locator import register_service, service_proxy
|
|
3
|
+
|
|
4
|
+
from splent_io.splent_feature_settings.services import SettingsService
|
|
5
|
+
|
|
6
|
+
settings_bp = create_blueprint(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def init_feature(app):
|
|
10
|
+
register_service(app, "SettingsService", SettingsService)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def inject_context_vars(app):
|
|
14
|
+
# {{ setting('key', default) }} — read a runtime, admin-editable setting,
|
|
15
|
+
# falling back to the given default. Safe to call even mid-request.
|
|
16
|
+
def setting(key, default=""):
|
|
17
|
+
try:
|
|
18
|
+
return service_proxy("SettingsService").get(key, default)
|
|
19
|
+
except Exception:
|
|
20
|
+
return default
|
|
21
|
+
|
|
22
|
+
return {"setting": setting}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/******/ (() => { // webpackBootstrap
|
|
2
|
+
/*!*********************************************************************************************!*\
|
|
3
|
+
!*** ../splent_feature_settings/src/splent_io/splent_feature_settings/assets/js/scripts.js ***!
|
|
4
|
+
\*********************************************************************************************/
|
|
5
|
+
// Entry point for splent_feature_settings frontend assets.
|
|
6
|
+
// Add your JavaScript here. Webpack compiles this into assets/dist/splent_feature_settings.bundle.js
|
|
7
|
+
//
|
|
8
|
+
// To load the compiled bundle in the product layout, register it in hooks.py:
|
|
9
|
+
//
|
|
10
|
+
// from splent_framework.hooks.template_hooks import register_template_hook
|
|
11
|
+
// from flask import url_for
|
|
12
|
+
//
|
|
13
|
+
// def settings_scripts():
|
|
14
|
+
// return '<script src="' + url_for("settings.assets", subfolder="dist", filename="splent_feature_settings.bundle.js") + '"></script>'
|
|
15
|
+
//
|
|
16
|
+
// register_template_hook("layout.scripts", settings_scripts)
|
|
17
|
+
|
|
18
|
+
/******/ })()
|
|
19
|
+
;
|
|
20
|
+
//# sourceMappingURL=splent_feature_settings.bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splent_feature_settings.bundle.js","mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://diversolab_app/../splent_feature_settings/src/splent_io/splent_feature_settings/assets/js/scripts.js"],"sourcesContent":["// Entry point for splent_feature_settings frontend assets.\n// Add your JavaScript here. Webpack compiles this into assets/dist/splent_feature_settings.bundle.js\n//\n// To load the compiled bundle in the product layout, register it in hooks.py:\n//\n// from splent_framework.hooks.template_hooks import register_template_hook\n// from flask import url_for\n//\n// def settings_scripts():\n// return '<script src=\"' + url_for(\"settings.assets\", subfolder=\"dist\", filename=\"splent_feature_settings.bundle.js\") + '\"></script>'\n//\n// register_template_hook(\"layout.scripts\", settings_scripts)\n"],"names":[],"sourceRoot":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI commands contributed by splent_feature_settings.
|
|
3
|
+
|
|
4
|
+
These commands are auto-discovered by the framework and exposed in the
|
|
5
|
+
SPLENT CLI under the ``feature:settings`` group.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
splent feature:settings hello
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command("hello")
|
|
16
|
+
def hello():
|
|
17
|
+
"""Example command — replace with your own."""
|
|
18
|
+
click.echo(" Hello from splent_feature_settings!")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
cli_commands = [hello]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
settings feature configuration.
|
|
3
|
+
|
|
4
|
+
Injects environment variables into Flask app.config.
|
|
5
|
+
Add your feature's env vars here so the framework can track them.
|
|
6
|
+
|
|
7
|
+
To regenerate from source code: splent feature:inject-config splent_feature_settings
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os # noqa: F401 — used when adding env vars below
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def inject_config(app):
|
|
14
|
+
app.config.update(
|
|
15
|
+
{
|
|
16
|
+
# Add your feature's env vars here, e.g.:
|
|
17
|
+
# "MY_VAR": os.getenv("MY_VAR", "default_value"),
|
|
18
|
+
}
|
|
19
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""settings injects a single "Settings" entry into the admin sidebar, which
|
|
2
|
+
lists every feature that declared a settings schema (via register_settings).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from flask import request, url_for
|
|
6
|
+
|
|
7
|
+
from splent_framework.hooks.template_hooks import register_template_hook
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def settings_admin_link():
|
|
11
|
+
active = (
|
|
12
|
+
"active"
|
|
13
|
+
if request.endpoint and request.endpoint.startswith("settings.admin")
|
|
14
|
+
else ""
|
|
15
|
+
)
|
|
16
|
+
return (
|
|
17
|
+
f'<li class="sidebar-item {active}">'
|
|
18
|
+
f'<a class="sidebar-link" href="{url_for("settings.admin_index")}">'
|
|
19
|
+
'<i class="align-middle" data-feather="settings"></i> '
|
|
20
|
+
'<span class="align-middle">Settings</span>'
|
|
21
|
+
"</a>"
|
|
22
|
+
"</li>"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
register_template_hook("layout.authenticated_sidebar", settings_admin_link)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = migrations
|
|
3
|
+
|
|
4
|
+
[loggers]
|
|
5
|
+
keys = root,sqlalchemy,alembic
|
|
6
|
+
|
|
7
|
+
[handlers]
|
|
8
|
+
keys = console
|
|
9
|
+
|
|
10
|
+
[formatters]
|
|
11
|
+
keys = generic
|
|
12
|
+
|
|
13
|
+
[logger_root]
|
|
14
|
+
level = WARN
|
|
15
|
+
handlers = console
|
|
16
|
+
qualname =
|
|
17
|
+
|
|
18
|
+
[logger_sqlalchemy]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers =
|
|
21
|
+
qualname = sqlalchemy.engine
|
|
22
|
+
|
|
23
|
+
[logger_alembic]
|
|
24
|
+
level = INFO
|
|
25
|
+
handlers =
|
|
26
|
+
qualname = alembic
|
|
27
|
+
|
|
28
|
+
[handler_console]
|
|
29
|
+
class = StreamHandler
|
|
30
|
+
args = (sys.stderr,)
|
|
31
|
+
level = NOTSET
|
|
32
|
+
formatter = generic
|
|
33
|
+
|
|
34
|
+
[formatter_generic]
|
|
35
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
36
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Alembic migration environment for splent_feature_settings."""
|
|
2
|
+
|
|
3
|
+
from splent_io.splent_feature_settings import models # noqa
|
|
4
|
+
from splent_framework.migrations.feature_env import run_feature_migrations
|
|
5
|
+
|
|
6
|
+
FEATURE_NAME = "splent_feature_settings"
|
|
7
|
+
FEATURE_TABLES = set()
|
|
8
|
+
|
|
9
|
+
run_feature_migrations(FEATURE_NAME, FEATURE_TABLES)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
${imports if imports else ""}
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = ${repr(up_revision)}
|
|
14
|
+
down_revision = ${repr(down_revision)}
|
|
15
|
+
branch_labels = ${repr(branch_labels)}
|
|
16
|
+
depends_on = ${repr(depends_on)}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
${upgrades if upgrades else "pass"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def downgrade():
|
|
24
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""setting table (runtime key/value settings).
|
|
2
|
+
|
|
3
|
+
Revision ID: settings0001
|
|
4
|
+
Revises:
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sqlalchemy as sa
|
|
8
|
+
from alembic import op
|
|
9
|
+
|
|
10
|
+
revision = "settings0001"
|
|
11
|
+
down_revision = None
|
|
12
|
+
branch_labels = None
|
|
13
|
+
depends_on = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def upgrade():
|
|
17
|
+
op.create_table(
|
|
18
|
+
"setting",
|
|
19
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
20
|
+
sa.Column("key", sa.String(length=191), nullable=False),
|
|
21
|
+
sa.Column("value", sa.Text(), nullable=True),
|
|
22
|
+
sa.PrimaryKeyConstraint("id"),
|
|
23
|
+
)
|
|
24
|
+
op.create_index(op.f("ix_setting_key"), "setting", ["key"], unique=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def downgrade():
|
|
28
|
+
op.drop_index(op.f("ix_setting_key"), table_name="setting")
|
|
29
|
+
op.drop_table("setting")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from splent_framework.db import db
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Setting(db.Model):
|
|
5
|
+
"""A single runtime, admin-editable key/value setting (the WP-options
|
|
6
|
+
analogue). Features read these via SettingsService / the {{ setting() }}
|
|
7
|
+
template helper, falling back to product config and defaults."""
|
|
8
|
+
|
|
9
|
+
__tablename__ = "setting"
|
|
10
|
+
|
|
11
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
12
|
+
key = db.Column(db.String(191), nullable=False, unique=True, index=True)
|
|
13
|
+
value = db.Column(db.Text, default="")
|
|
14
|
+
|
|
15
|
+
def __repr__(self):
|
|
16
|
+
return f"Setting<{self.key}>"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Generic admin panels for feature settings.
|
|
2
|
+
|
|
3
|
+
Any feature that declares a settings schema (via the framework's
|
|
4
|
+
register_settings) gets an admin panel here automatically — one page per
|
|
5
|
+
feature — that reads/writes through SettingsService. No per-feature panel code.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from flask import abort, flash, redirect, render_template, request, url_for
|
|
9
|
+
from flask_login import login_required
|
|
10
|
+
|
|
11
|
+
from splent_io.splent_feature_settings import settings_bp
|
|
12
|
+
from splent_framework.services.service_locator import service_proxy
|
|
13
|
+
from splent_framework.settings.settings_schema import (
|
|
14
|
+
get_config,
|
|
15
|
+
get_schema,
|
|
16
|
+
get_schemas,
|
|
17
|
+
setting_key,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@settings_bp.route("/admin/settings", methods=["GET"])
|
|
22
|
+
@login_required
|
|
23
|
+
def admin_index():
|
|
24
|
+
return render_template("settings/admin/index.html", schemas=get_schemas())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@settings_bp.route("/admin/settings/<feature>", methods=["GET", "POST"])
|
|
28
|
+
@login_required
|
|
29
|
+
def admin_feature(feature):
|
|
30
|
+
schema = get_schema(feature)
|
|
31
|
+
if schema is None:
|
|
32
|
+
abort(404)
|
|
33
|
+
if request.method == "POST":
|
|
34
|
+
values = {}
|
|
35
|
+
for f in schema["fields"]:
|
|
36
|
+
key = setting_key(feature, f["key"])
|
|
37
|
+
if f["type"] == "bool":
|
|
38
|
+
values[key] = "1" if f["key"] in request.form else "0"
|
|
39
|
+
else:
|
|
40
|
+
values[key] = (request.form.get(f["key"]) or "").strip()
|
|
41
|
+
service_proxy("SettingsService").set_many(values)
|
|
42
|
+
flash(f"{schema['title']} settings saved.", "success")
|
|
43
|
+
return redirect(url_for("settings.admin_feature", feature=feature))
|
|
44
|
+
return render_template(
|
|
45
|
+
"settings/admin/feature.html", schema=schema, cfg=get_config(feature)
|
|
46
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from splent_io.splent_feature_settings.models import Setting
|
|
2
|
+
from splent_io.splent_feature_settings.repositories import SettingsRepository
|
|
3
|
+
from splent_framework.db import db
|
|
4
|
+
from splent_framework.services.BaseService import BaseService
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SettingsService(BaseService):
|
|
8
|
+
"""Runtime key/value settings, editable from the admin (WP-options style)."""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__(SettingsRepository())
|
|
12
|
+
|
|
13
|
+
def get(self, key, default=None):
|
|
14
|
+
s = Setting.query.filter_by(key=key).first()
|
|
15
|
+
if s is None or s.value in (None, ""):
|
|
16
|
+
return default
|
|
17
|
+
return s.value
|
|
18
|
+
|
|
19
|
+
def set(self, key, value):
|
|
20
|
+
s = Setting.query.filter_by(key=key).first()
|
|
21
|
+
if s is None:
|
|
22
|
+
s = Setting(key=key, value=value or "")
|
|
23
|
+
db.session.add(s)
|
|
24
|
+
else:
|
|
25
|
+
s.value = value or ""
|
|
26
|
+
db.session.commit()
|
|
27
|
+
return s
|
|
28
|
+
|
|
29
|
+
def set_many(self, mapping):
|
|
30
|
+
for key, value in mapping.items():
|
|
31
|
+
s = Setting.query.filter_by(key=key).first()
|
|
32
|
+
if s is None:
|
|
33
|
+
db.session.add(Setting(key=key, value=value or ""))
|
|
34
|
+
else:
|
|
35
|
+
s.value = value or ""
|
|
36
|
+
db.session.commit()
|
|
37
|
+
|
|
38
|
+
def all_dict(self):
|
|
39
|
+
return {s.key: s.value for s in Setting.query.all()}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signals for splent_feature_settings.
|
|
3
|
+
|
|
4
|
+
Use ``define_signal`` to declare signals this feature emits.
|
|
5
|
+
Use ``connect_signal`` to listen to signals from other features.
|
|
6
|
+
|
|
7
|
+
Examples::
|
|
8
|
+
|
|
9
|
+
# Define a signal (this feature emits it):
|
|
10
|
+
from splent_framework.signals.signal_utils import define_signal
|
|
11
|
+
item_created = define_signal("item-created", "splent_feature_settings")
|
|
12
|
+
|
|
13
|
+
# Connect to a signal from another feature:
|
|
14
|
+
from splent_framework.signals.signal_utils import connect_signal
|
|
15
|
+
|
|
16
|
+
@connect_signal("user-registered", "splent_feature_settings")
|
|
17
|
+
def on_user_registered(sender, user, **kwargs):
|
|
18
|
+
pass
|
|
19
|
+
"""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{% extends "base_template.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ schema.title }} settings{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="d-flex flex-column align-items-start gap-2 mb-4">
|
|
7
|
+
<h1 class="h3 mb-0">{{ schema.title }} <small class="text-muted">settings</small></h1>
|
|
8
|
+
<a href="{{ url_for('settings.admin_index') }}" class="btn btn-sm btn-outline-secondary">All settings</a>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
12
|
+
{% for category, message in messages %}
|
|
13
|
+
<div class="alert alert-{{ 'danger' if category == 'danger' else 'success' }} py-2">{{ message }}</div>
|
|
14
|
+
{% endfor %}
|
|
15
|
+
{% endwith %}
|
|
16
|
+
|
|
17
|
+
<form method="POST">
|
|
18
|
+
<div class="card">
|
|
19
|
+
<div class="card-body">
|
|
20
|
+
{% for f in schema.fields %}
|
|
21
|
+
<div class="mb-3">
|
|
22
|
+
{% if f.type == 'bool' %}
|
|
23
|
+
<div class="form-check form-switch">
|
|
24
|
+
<input class="form-check-input" type="checkbox" role="switch"
|
|
25
|
+
name="{{ f.key }}" id="{{ f.key }}" {% if cfg[f.key] %}checked{% endif %}>
|
|
26
|
+
<label class="form-check-label" for="{{ f.key }}">{{ f.label }}</label>
|
|
27
|
+
</div>
|
|
28
|
+
{% elif f.type == 'select' %}
|
|
29
|
+
<label class="form-label" for="{{ f.key }}">{{ f.label }}</label>
|
|
30
|
+
<select class="form-select" name="{{ f.key }}" id="{{ f.key }}">
|
|
31
|
+
{% for value, label in f.options %}
|
|
32
|
+
<option value="{{ value }}" {% if cfg[f.key] == value %}selected{% endif %}>{{ label }}</option>
|
|
33
|
+
{% endfor %}
|
|
34
|
+
</select>
|
|
35
|
+
{% elif f.type == 'color' %}
|
|
36
|
+
<label class="form-label" for="{{ f.key }}">{{ f.label }}</label>
|
|
37
|
+
<input type="color" class="form-control form-control-color"
|
|
38
|
+
name="{{ f.key }}" id="{{ f.key }}" value="{{ cfg[f.key] }}">
|
|
39
|
+
{% elif f.type == 'int' %}
|
|
40
|
+
<label class="form-label" for="{{ f.key }}">{{ f.label }}</label>
|
|
41
|
+
<input type="number" class="form-control"
|
|
42
|
+
name="{{ f.key }}" id="{{ f.key }}" value="{{ cfg[f.key] }}">
|
|
43
|
+
{% else %}
|
|
44
|
+
<label class="form-label" for="{{ f.key }}">{{ f.label }}</label>
|
|
45
|
+
<input type="text" class="form-control"
|
|
46
|
+
name="{{ f.key }}" id="{{ f.key }}" value="{{ cfg[f.key] }}">
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% if f.help %}<small class="text-muted d-block mt-1">{{ f.help }}</small>{% endif %}
|
|
49
|
+
</div>
|
|
50
|
+
{% endfor %}
|
|
51
|
+
</div>
|
|
52
|
+
<div class="card-footer d-flex justify-content-between align-items-center">
|
|
53
|
+
<span class="text-muted small">Changes apply immediately.</span>
|
|
54
|
+
<button class="btn btn-primary" type="submit">Save</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</form>
|
|
58
|
+
{% endblock %}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{% extends "base_template.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Settings{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<h1 class="h3 mb-2">Settings</h1>
|
|
7
|
+
<p class="text-muted">Configure the behaviour of installed features.</p>
|
|
8
|
+
|
|
9
|
+
{% if not schemas %}
|
|
10
|
+
<p class="text-muted">No configurable features installed.</p>
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
<div class="row g-3">
|
|
14
|
+
{% for s in schemas %}
|
|
15
|
+
<div class="col-md-4 col-sm-6">
|
|
16
|
+
<a class="card text-decoration-none h-100" href="{{ url_for('settings.admin_feature', feature=s.feature) }}">
|
|
17
|
+
<div class="card-body d-flex align-items-center gap-2">
|
|
18
|
+
<i class="align-middle" data-feather="{{ s.icon }}"></i>
|
|
19
|
+
<span class="h6 mb-0">{{ s.title }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</a>
|
|
22
|
+
</div>
|
|
23
|
+
{% endfor %}
|
|
24
|
+
</div>
|
|
25
|
+
{% endblock %}
|