Files
orion/docs/architecture/menu-management.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

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=1,
    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=1,
)

Discovery Flow

  1. Collect: Get menu definitions from all modules in MODULES registry
  2. Filter by Module: Only include menus from enabled modules for the platform
  3. Filter by Permissions: Remove super_admin_only items for non-super admins
  4. Apply Visibility: Check AdminMenuConfig for hidden items
  5. Sort: Order sections and items by their order field
  6. Return: List of MenuSectionDefinition with 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=1,
    menu_item_id="inventory",
    is_visible=False
)

# Platform "OMS" hides letzshop from store dashboard
AdminMenuConfig(
    frontend_type=FrontendType.STORE,
    platform_id=1,
    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:

  1. Module Definitions: All possible menu items from modules
  2. Module Enablement: Items from disabled modules are hidden
  3. 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:

  1. Module enablement
  2. Visibility configuration
  3. 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

  1. 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,
                    ),
                ]
            )
        ],
    }
)
  1. Add translation keys in your module's locales:
// app/modules/mymodule/locales/en.json
{
  "mymodule.menu.section": "My Module",
  "mymodule.menu.main": "Main Page"
}
  1. The menu is automatically discovered - no registration needed.

Best Practices

Do

  • Define menus in module definition.py using dataclasses
  • Use translation keys (label_key) for labels
  • Set appropriate order values for positioning
  • Mark essential items as is_mandatory=True
  • Use is_super_admin_only=True for admin-only features

Don't

  • Hardcode menu item labels (use i18n keys)
  • Store is_visible=True in database (default state, wastes space)
  • Allow hiding mandatory items via API
  • Create menu items outside of module definitions