Navigation registry
A content feature adds one entry to the public main menu from its init_feature(app), so the menu is composed from the installed features instead of written per product. Install a feature and its entry appears, remove it and re-derive the product and the entry goes.
Registering an entry
Call register_nav_item from splent_framework.nav.nav_registry inside init_feature(app).
from splent_framework.blueprints.base_blueprint import create_blueprint
from splent_framework.nav.nav_registry import register_nav_item
post_bp = create_blueprint(__name__)
def init_feature(app):
register_nav_item(key="post", label="Blog", href="/blog", order=50)
def inject_context_vars(app):
return {}
| Parameter | Required | Description |
|---|---|---|
key |
yes | Stable identifier, unique per feature, such as "post". Deduplicates entries and matches them against the override. |
label |
yes | Text in the menu, such as "Blog". |
href |
yes | Target URL or path, such as "/blog". |
order |
no | Sort weight, lower first. Defaults to 100. |
icon |
no | Icon identifier, for themes that render icons. Defaults to None. |
children |
no | A dropdown under the entry, as a list of {"label", "href"} dicts or a callable with no arguments that returns one. Defaults to None, a plain link. |
Registering the same key again replaces the entry. The entry is plain data, which the theme may reorder, relabel, hide or merge with custom links. To inject HTML into a layout slot instead, use a template hook.
Dropdowns
An entry with children stays a link that works with the keyboard and without JavaScript. Its children open on hover, focus or the caret button, and follow it in the stacked mobile menu. A child with "current": true is highlighted.
A callable is resolved on every render with resolve_children(item), so the dropdown can list content that changes at runtime. It never raises. Any iterable of entries works, and a lookup that fails (no database yet) or returns something else renders a plain link.
def _nav_children():
service = service_proxy("EditionsService")
current = service.current()
items = [{"label": current.name, "href": ..., "current": True}] if current else []
return items + [{"label": e.name, "href": ...} for e in service.past(current)]
register_nav_item(key="editions", label="Editions", href="/editions", order=12,
children=_nav_children)
This is how splent_feature_editions lists every edition, the current one first, as soon as an administrator publishes it.
Other functions
| Function | Returns or does |
|---|---|
get_nav_items() |
Every entry, sorted by (order, label). The theme calls it when it renders |
resolve_children(item) |
The rendered sub-entries of an entry |
clear_nav_items() |
Empties the registry. FeatureManager.register_features() calls it before loading features, so the registry holds only this product’s entries |
How the theme composes the menu
The theme’s nav.py runs compose_nav() on every render.
- It starts from the base entries of
get_nav_items(). - It loads the override JSON from the settings store, key
site_nav. - It drops override entries whose feature is not installed.
- It appends installed entries the override does not have yet.
- It applies
_safe_hrefto custom links. - It deduplicates by
key.
| Level | When | What it controls |
|---|---|---|
| Derivation | product:derive |
Which features registered an entry, the base menu |
| Runtime | The admin Menus editor | Order, visibility and labels of the entries, and custom links |
The Menus editor of splent_feature_theme, at /admin/menus, lets an administrator reorder, hide and relabel entries and add custom links. It saves the override under site_nav, which layers on the base and never replaces it.
With no override and no registered entry, the theme renders the product’s app.config["SITE_NAV"], the site.nav list described in Theme system.
See also
hooks.pyand template hooks. Inject HTML into layout slots- Feature settings. The same declare-then-compose pattern for admin settings
- Feature loading. When
init_featureruns and the registry is cleared