Settings¶
This part covers how FlaskBB’s settings system works. This is especially useful if you plan on developing a plugin or want to contribute to FlaskBB itself.
Settings are not hardcoded config values - they’re a registry of
declarative definitions backed by DB rows. A setting is defined once as a
SettingDefinition grouped into a
SettingGroup, registered into the
SettingsRegistry at startup, and its
value is persisted as a Setting row.
Registering setting groups¶
Setting groups are registered via two pluggy hooks, both defined in
flaskbb.plugins.spec:
flaskbb_load_internal_setting_groupsFlaskBB’s own hook for its built-in setting groups (
general,auth,misc,appearance, …). Plugin authors must not implement this hook.flaskbb_load_setting_groupsThe hook third-party plugins implement to register their own
SettingGroup. See Developing new Plugins for a full example.
Which hook found a group determines where it shows up in the admin UI - “FlaskBB Settings” for the internal hook, “Plugin Settings” for the plugin hook - nothing about the group itself controls this.
Both hooks collect every implementation’s return value (a single
SettingGroup or a list of them); there’s no firstresult.
Setting definitions¶
A SettingDefinition is never
used directly - pick the subclass matching the value’s type. Each subclass
knows how to render itself as a WTForms field
(wtf_field()) and
how to serialize/deserialize its value to/from the JSON text stored in the
DB.
Definition |
Rendered As |
Parsed & Saved as |
|---|---|---|
single value |
||
- class flaskbb.core.settings.definitions.SettingDefinition(key: str, value: Any, name: str, description: str)[source]¶
Base class for a single setting. Not used directly - use one of the value_type-specific subclasses below (IntSetting, BoolSetting, …).
- class flaskbb.core.settings.definitions.StringSetting(key: str, value: Any, name: str, description: str, min: int | None = None, max: int | None = None)[source]¶
- class flaskbb.core.settings.definitions.IntSetting(key: str, value: Any, name: str, description: str, min: int | None = None, max: int | None = None)[source]¶
- class flaskbb.core.settings.definitions.BoolSetting(key: str, value: Any, name: str, description: str)[source]¶
- class flaskbb.core.settings.definitions.SelectSetting(key: str, value: Any, name: str, description: str, choices: collections.abc.Callable[[], list[tuple[typing.Any, str]]], coerce: type = <class 'str'>)[source]¶
- class flaskbb.core.settings.definitions.SelectMultipleSetting(key: str, value: Any, name: str, description: str, choices: collections.abc.Callable[[], list[tuple[typing.Any, str]]], coerce: type = <class 'str'>)[source]¶
Every definition takes key, value (the default), name (human
readable label) and description. StringSetting and IntSetting
additionally accept optional min/max bounds (string length or
numeric range, validated via WTForms validators). SelectSetting and
SelectMultipleSetting require a choices callable returning a list of
(value, label) pairs, and accept an optional coerce type (defaults
to str) to coerce the selected value(s).
Example:
from flaskbb.core.settings import BoolSetting, IntSetting, SettingGroup
SETTINGS = SettingGroup(
key="my_plugin",
name="My Plugin Settings",
description="Settings for My Plugin.",
settings=(
BoolSetting(
key="ENABLED",
value=True,
name="Enabled",
description="Whether My Plugin is active.",
),
IntSetting(
key="RECENT_ITEMS",
value=10,
min=1,
name="Number of Recent Items",
description="How many items to show.",
),
),
)
Setting keys¶
A setting’s key only has to be unique within its own group - uniqueness is
scoped to (group_key, key), not global. How a setting is exposed to the
rest of the app (config-style access, form field names) differs between
core and plugin settings, via
flaskbb.core.settings.models.display_key():
Core settings (registered through
flaskbb_load_internal_setting_groups) are exposed unprefixed:PROJECT_TITLE.Plugin settings (registered through
flaskbb_load_setting_groups) are exposed prefixed with their group key, uppercased:PORTAL_FORUM_IDSfor a setting keyedFORUM_IDSin theportalgroup. This is what avoids collisions between plugins (and with core).
The registry¶
- class flaskbb.core.settings.registry.SettingsRegistry[source]¶
-
- resolve_display_key(display_key: str) tuple[str, str][source]¶
Reverses a GROUPKEY_KEY display key (e.g. “PORTAL_FORUM_IDS”) back into its (group_key, raw_key) pair (e.g. (“portal”, “FORUM_IDS”)).
Raises KeyError if no registered (group_key, key) pair produces this display key.
- load_from_internal(plugin_manager: FlaskBBPluginManager) None[source]¶
Call flaskbb_load_internal_setting_groups - core’s own hook. Only FlaskBB’s own hookimpls should implement this one.
- load_from_plugins(plugin_manager: FlaskBBPluginManager) None[source]¶
Call flaskbb_load_setting_groups - the public hook that third-party plugins implement for their own SettingGroups.
Implementations may return a single SettingGroup or a list of them (mirrors the flaskbb_load_post_markdown_class convention of collecting-and-composing hook results rather than calling register_group directly from plugin code).
The module-level singleton flaskbb.core.settings.setting_registry is
what every part of FlaskBB (admin forms, the settings model, the plugin
registry) queries against - plugins never construct their own registry.
Storage and the Setting model¶
Note
For a full list of available methods, visit Setting Model.
- class flaskbb.core.settings.models.Setting(key, value, group_key)[source]
- classmethod as_dict() dict[str, Any][source]
Load and deserialize every setting value from the DB.
Core settings stay unprefixed (settings.PROJECT_TITLE), plugin settings are exposed prefixed with their group_key (settings.PORTAL_FORUM_IDS).
- classmethod invalidate_cache()[source]
Invalidates this objects cached metadata.
- classmethod update(group_key: str, settings: dict[str, Any], *, exclude: Iterable[str] = frozenset({'csrf_token'})) None[source]
Save a settings group’s form data to the DB and invalidate the cache in one step.
- Parameters:
group_key – the SettingGroup.key whose settings are being saved (e.g. “general”, “portal”). Required because (group_key, key) together identify a setting.
settings – dict of {setting_key: new_value}, typically form.data from the WTForm built via build_form(group)
exclude – keys to skip. Defaults to just “csrf_token” because forms usually contain this key.
- classmethod diff_group(group_key: str) SettingsDiff[source]
Compares a group’s currently registered SettingDefinitions against what actually has DB rows tagged with this group_key.
- Returns:
SettingsDiff listing which definition keys have no DB row yet and which DB rows no longer match any definition .
- classmethod prune_group(group_key: str) None[source]
Removes settings from a group that are no longer used. For example, removed from the SettingDefinitions file.
Values are stored as JSON text (not PickleType) - deliberately, to
avoid the arbitrary code execution risk of unpickling untrusted data, and
because every setting value type (int, bool, str, list[str])
is JSON-safe anyway.
The whole settings table is cached as a flat dict via
as_dict(), keyed by each
setting’s display key. Any write path
(update(),
install_group(),
prune_group(),
remove_group()) invalidates
this cache itself - never write Setting rows directly via the session
and skip these methods, or the cache will go stale.
Reading and writing settings at runtime¶
Application code reads settings through the flaskbb_config proxy
(flaskbb.core.settings.proxy.FlaskBBConfigProxy), which supports
both attribute and dict-style access against the cached values from
as_dict():
from flaskbb.core.settings import flaskbb_config
flaskbb_config.PROJECT_TITLE
flaskbb_config["PROJECT_TITLE"]
Writing through the proxy (flaskbb_config["KEY"] = value or
flaskbb_config.update(...)) resolves the display key back to its
(group_key, key) pair and delegates to
update(), so plugin settings
work the same way as core ones. Individual keys can’t be deleted through
the proxy - a group’s settings are tied to its lifecycle, removed together
via remove_group() (e.g. on
plugin uninstall).