Update 8 documentation files to reflect new URL scheme:
- Dev: /platforms/{code}/storefront/{store_code}/
- Prod: subdomain.platform.lu/ (root path = storefront)
- Rename DEFAULT_PLATFORM_CODE to MAIN_PLATFORM_CODE
- Replace hardcoded platform_id=1 with dynamic values
- Update route examples, middleware descriptions, code samples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
Menu Management Architecture
The Orion platform provides a module-driven menu system where each module defines its own menu items. The MenuDiscoveryService aggregates menus from all enabled modules, applying visibility configuration and permission filtering.
Overview
┌─────────────────────────────────────────────────────────────────────────┐
│ MODULE DEFINITIONS (Source of Truth) │
│ app/modules/*/definition.py │
│ │
│ Each module defines its menu items per FrontendType: │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ catalog.definition.py │ │ orders.definition.py │ │
│ │ menus={ADMIN: [...], │ │ menus={ADMIN: [...], │ │
│ │ STORE: [...]} │ │ STORE: [...]} │ │
│ └─────────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ MENU DISCOVERY SERVICE │
│ app/modules/core/services/menu_discovery_service.py │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 1. Collect menu items from all enabled modules │ │
│ │ 2. Filter by user permissions (super_admin_only) │ │
│ │ 3. Apply visibility overrides (AdminMenuConfig) │ │
│ │ 4. Sort by section/item order │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ VISIBILITY CONFIGURATION │
│ app/modules/core/models/admin_menu_config.py │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ AdminMenuConfig Table │ │
│ │ Stores visibility overrides (hidden items only) │ │
│ │ - Platform scope: applies to platform admins/stores │ │
│ │ - User scope: applies to specific super admin │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ FILTERED MENU OUTPUT │
│ │
│ Module Menus - Disabled Modules - Hidden Items = Visible Menu │
└─────────────────────────────────────────────────────────────────────────┘
Frontend Types
The system supports four distinct frontend types:
| Frontend | Description | Users |
|---|---|---|
PLATFORM |
Public marketing pages | Unauthenticated visitors |
ADMIN |
Admin panel | Super admins, platform admins |
STORE |
Store dashboard | Stores on a platform |
STOREFRONT |
Customer-facing shop | Shop customers |
from app.modules.enums import FrontendType
# Use in code
FrontendType.PLATFORM # "platform"
FrontendType.ADMIN # "admin"
FrontendType.STORE # "store"
FrontendType.STOREFRONT # "storefront"
Module-Driven Menus
Each module defines its menu items in definition.py using dataclasses:
Menu Item Definition
# app/modules/base.py
@dataclass
class MenuItemDefinition:
"""Single menu item definition."""
id: str # Unique identifier (e.g., "catalog.products")
label_key: str # i18n key for label
icon: str # Lucide icon name
route: str # URL path
order: int = 100 # Sort order within section
is_mandatory: bool = False # Cannot be hidden by user
is_super_admin_only: bool = False # Only visible to super admins
@dataclass
class MenuSectionDefinition:
"""Section containing menu items."""
id: str # Section identifier
label_key: str # i18n key for section label
icon: str # Section icon
order: int = 100 # Sort order
items: list[MenuItemDefinition] = field(default_factory=list)
is_super_admin_only: bool = False
Example Module Definition
# app/modules/catalog/definition.py
from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
from app.modules.enums import FrontendType
catalog_module = ModuleDefinition(
code="catalog",
name="Product Catalog",
description="Product and category management",
version="1.0.0",
is_core=True,
menus={
FrontendType.ADMIN: [
MenuSectionDefinition(
id="catalog",
label_key="menu.catalog",
icon="package",
order=30,
items=[
MenuItemDefinition(
id="products",
label_key="menu.products",
icon="box",
route="/admin/products",
order=10,
is_mandatory=True
),
MenuItemDefinition(
id="categories",
label_key="menu.categories",
icon="folder-tree",
route="/admin/categories",
order=20
),
]
)
],
FrontendType.STORE: [
MenuSectionDefinition(
id="products",
label_key="menu.my_products",
icon="package",
order=10,
items=[
MenuItemDefinition(
id="products",
label_key="menu.products",
icon="box",
route="/store/{store_code}/products",
order=10,
is_mandatory=True
),
]
)
],
}
)
Menu Discovery Service
The MenuDiscoveryService aggregates menus from all enabled modules:
from app.modules.core.services.menu_discovery_service import menu_discovery_service
# Get menu for a frontend type
sections = menu_discovery_service.get_menu_for_frontend(
db=db,
frontend_type=FrontendType.ADMIN,
platform_id=platform.id,
user_id=current_user.id,
is_super_admin=current_user.is_super_admin,
)
# Get all menu items for configuration UI
all_items = menu_discovery_service.get_all_menu_items(
db=db,
frontend_type=FrontendType.ADMIN,
platform_id=platform.id,
)
Discovery Flow
- Collect: Get menu definitions from all modules in
MODULESregistry - Filter by Module: Only include menus from enabled modules for the platform
- Filter by Permissions: Remove
super_admin_onlyitems for non-super admins - Apply Visibility: Check
AdminMenuConfigfor hidden items - Sort: Order sections and items by their
orderfield - Return: List of
MenuSectionDefinitionwith filtered items
Visibility Configuration
Opt-Out Model
The system uses an opt-out model:
- All menu items are visible by default
- Only hidden items are stored in the database
- This keeps the database small and makes the default state explicit
Mandatory Items
Certain menu items cannot be hidden. These are marked with is_mandatory=True in their definition:
MenuItemDefinition(
id="dashboard",
label_key="menu.dashboard",
icon="home",
route="/admin/dashboard",
is_mandatory=True, # Cannot be hidden
)
Scope Types
Menu configuration supports two scopes:
| Scope | Field | Description | Use Case |
|---|---|---|---|
| Platform | platform_id |
Applies to all users on platform | Hide features not used by platform |
| User | user_id |
Applies to specific super admin | Personal preference customization |
Important Rules:
- Exactly one scope must be set (platform XOR user)
- User scope is only allowed for admin frontend (super admins only)
- Store frontend only supports platform scope
Resolution Order
Admin Frontend:
Platform admin → Check platform config → Fall back to default (all visible)
Super admin → Check user config → Fall back to default (all visible)
Store Frontend:
Store → Check platform config → Fall back to default (all visible)
Database Model
AdminMenuConfig Table
CREATE TABLE admin_menu_configs (
id SERIAL PRIMARY KEY,
frontend_type VARCHAR(10) NOT NULL, -- 'admin' or 'store'
platform_id INTEGER REFERENCES platforms(id),
user_id INTEGER REFERENCES users(id),
menu_item_id VARCHAR(50) NOT NULL,
is_visible BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,
-- Constraints
CONSTRAINT uq_frontend_platform_menu_config
UNIQUE (frontend_type, platform_id, menu_item_id),
CONSTRAINT uq_frontend_user_menu_config
UNIQUE (frontend_type, user_id, menu_item_id),
CONSTRAINT ck_admin_menu_config_scope
CHECK ((platform_id IS NOT NULL AND user_id IS NULL) OR
(platform_id IS NULL AND user_id IS NOT NULL)),
CONSTRAINT ck_user_scope_admin_only
CHECK ((user_id IS NULL) OR (frontend_type = 'admin'))
);
Examples
from app.modules.core.models import AdminMenuConfig
from app.modules.enums import FrontendType
# Platform "OMS" hides inventory from admin panel
AdminMenuConfig(
frontend_type=FrontendType.ADMIN,
platform_id=oms_platform.id, # OMS platform
menu_item_id="inventory",
is_visible=False
)
# Platform "OMS" hides letzshop from store dashboard
AdminMenuConfig(
frontend_type=FrontendType.STORE,
platform_id=oms_platform.id, # OMS platform
menu_item_id="letzshop",
is_visible=False
)
# Super admin "john" hides code-quality from their admin panel
AdminMenuConfig(
frontend_type=FrontendType.ADMIN,
user_id=5,
menu_item_id="code-quality",
is_visible=False
)
Module Integration
Automatic Menu Discovery
When a module is enabled/disabled for a platform, the menu discovery service automatically includes/excludes its menu items:
# Module enablement check happens automatically in MenuDiscoveryService
enabled_modules = module_service.get_enabled_module_codes(db, platform_id)
# Only modules in enabled_modules have their menus included
for module_code, module_def in MODULES.items():
if module_code in enabled_modules:
# Include this module's menus
pass
Three-Layer Filtering
The final visible menu is computed by three layers:
- Module Definitions: All possible menu items from modules
- Module Enablement: Items from disabled modules are hidden
- Visibility Config: Explicitly hidden items are removed
Final Menu = Module Menu Items
- Items from Disabled Modules
- Items with is_visible=False in config
- Items not matching user role (super_admin_only)
Menu Service Integration
The MenuService provides the interface used by templates:
from app.modules.core.services import menu_service
# Get menu for rendering in templates
menu_data = menu_service.get_menu_for_rendering(
db=db,
frontend_type=FrontendType.ADMIN,
platform_id=platform_id,
user_id=user_id,
is_super_admin=is_super_admin,
store_code=store_code, # For store frontend
)
# Returns legacy format for template compatibility:
# {
# "frontend_type": "admin",
# "sections": [
# {
# "id": "main",
# "label": None,
# "items": [{"id": "dashboard", "label": "Dashboard", ...}]
# },
# ...
# ]
# }
Access Control
Route Protection
from app.api.deps import require_menu_access
@router.get("/admin/inventory")
async def inventory_page(
_access: bool = Depends(require_menu_access("inventory", FrontendType.ADMIN))
):
# Only accessible if menu item is visible for user's context
pass
Sidebar Rendering
The sidebar template filters items based on:
- Module enablement
- Visibility configuration
- User role (super admin check)
{% for section in menu_sections %}
{% if not section.super_admin_only or current_user.is_super_admin %}
{% for item in section.items %}
{% if item.id in visible_menu_items %}
<a href="{{ item.url }}">{{ item.label }}</a>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
UI for Menu Configuration
Platform Admin Menu Config
Located at /admin/platform-menu-config (accessible by super admins):
- Configure which menu items are visible for platform admins
- Configure which menu items are visible for stores on this platform
- Mandatory items cannot be unchecked
Personal Menu Config (Super Admins)
Located at /admin/my-menu:
- Super admins can customize their own admin panel menu
- Personal preferences that don't affect other users
- Useful for hiding rarely-used features
Adding Menu Items to a Module
- Define menu sections and items in your module's
definition.py:
# app/modules/mymodule/definition.py
from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
from app.modules.enums import FrontendType
mymodule = ModuleDefinition(
code="mymodule",
name="My Module",
description="Description of my module",
version="1.0.0",
menus={
FrontendType.ADMIN: [
MenuSectionDefinition(
id="mymodule",
label_key="mymodule.menu.section",
icon="star",
order=50, # Position in sidebar
items=[
MenuItemDefinition(
id="mymodule-main",
label_key="mymodule.menu.main",
icon="star",
route="/admin/mymodule",
order=10,
),
]
)
],
}
)
- Add translation keys in your module's locales:
// app/modules/mymodule/locales/en.json
{
"mymodule.menu.section": "My Module",
"mymodule.menu.main": "Main Page"
}
- The menu is automatically discovered - no registration needed.
Best Practices
Do
- Define menus in module
definition.pyusing dataclasses - Use translation keys (
label_key) for labels - Set appropriate
ordervalues for positioning - Mark essential items as
is_mandatory=True - Use
is_super_admin_only=Truefor admin-only features
Don't
- Hardcode menu item labels (use i18n keys)
- Store
is_visible=Truein database (default state, wastes space) - Allow hiding mandatory items via API
- Create menu items outside of module definitions
Related Documentation
- Module System - Module architecture and definitions
- Multi-Tenant System - Platform isolation
- Feature Gating - Tier-based access