Ribbon & Action Menus
Developer guide for the Enezaframework. Part of the Developer documentation. For a step-by-step recipe, see Adding Ribbon Tabs, Groups & Menus.
The record ribbon is driven by a ribbon profile. A profile is defined by the list view, passed to the detail page as a short profile key, and resolved on the server into tabs, groups, buttons, dropdowns, related-record workspaces and inline panels.
This guide covers:
- What Changed
- Quick Start
- Tabs, Groups and Menu Items
- Menu Kinds
- Related Record Menus
- Business Actions
- List-Context Menus
- Permissions and Entity Overrides
- Database Overlay
- Backward Compatibility
- Troubleshooting
What Changed
Every generic record page now supports:
| Tab | Purpose |
|---|---|
Home |
Primary record actions contributed by a profile. |
Options |
Default tab for developer-defined actions and menus. |
View |
Display/refresh controls. |
Personalization |
Reserved key for future user preferences; hidden until it has items. |
Additional tabs (for example Manage or Reports) are added by the profile. The existing detail action
bar remains in place, including the workflow dropdown. Report screens in enezareporting are not changed.
The important rule is:
Define the profile once on the list view. The list sends
?ribbon_profile=<key>to detail URLs; code and optional database overlays are resolved by the framework.
Quick Start
from enezaframework.ribbon import (
RibbonMenuItemSpec,
RibbonProfileSpec,
RibbonTabSpec,
)
from enezaframework.views import BaseDetailView, BaseListView
from enezaframework.utils import auto_url_register
VENDOR_PROFILE = RibbonProfileSpec(
key="accountpayable.vendor",
name="Vendor",
detail_tabs=[
RibbonTabSpec(key="manage", label="Manage", order=20),
],
menus=[
RibbonMenuItemSpec(
code="bank-details",
label="Bank Details",
icon="building",
tab="manage",
group="Banking",
kind="related",
display_mode="inline",
target_kind="list",
target_model="vendorbankaccount",
parent_lookup="vendor",
relation_name="bank_accounts",
),
],
)
@auto_url_register
class VendorListView(BaseListView):
model = VendorTable
def get_ribbon_profile(self):
return VENDOR_PROFILE
def set_list_fields(self):
return ["code", "name"]
def set_search_fields(self):
return ["code", "name"]
@auto_url_register
class VendorDetailView(BaseDetailView):
model = VendorTable
default_ribbon_profile = "accountpayable.vendor"
When a row opens the detail page, the framework appends the profile key automatically. Direct detail
links use default_ribbon_profile.
The profile may also be declared as a class attribute:
class VendorListView(BaseListView):
ribbon_profile = VENDOR_PROFILE
get_ribbon_profile() is the recommended hook when the profile depends on the request.
Tabs, Groups and Menu Items
RibbonProfileSpec
| Field | Meaning |
|---|---|
key |
Stable key used in ?ribbon_profile=. |
name |
Diagnostic name. |
owner_app_label, owner_model |
Optional; derived from the list view model. |
tabs |
Shared tab definitions. Use context="both" for tabs available in list and detail. |
list_tabs, detail_tabs |
Optional context-specific tab definitions. Omit to use the base tabs. |
menus |
Menu/action definitions. |
default_tab |
Tab selected when no ribbon_tab is supplied. |
replace_tabs |
When True, profile tabs replace the base tabs instead of merging. |
RibbonTabSpec
key, label, context (list, detail or both) and order. icon is optional.
RibbonMenuItemSpec
| Field | Default | Meaning |
|---|---|---|
code |
required | Stable identifier for overrides and list row data attributes. |
label |
required | Button/menu text. |
icon |
empty | Icon name resolved by the framework. |
tab |
options |
Tab key. |
context |
detail |
list, detail or both. |
group, group_order, order |
General, 0, 0 |
Ribbon grouping/sorting. |
kind |
action |
action, link, related, dropdown, separator. |
url_name |
empty | Django URL name. |
http_method |
POST for actions |
GET or POST. |
target |
#detail-container |
HTMX target. |
swap |
innerHTML |
HTMX swap style. |
confirm_message |
empty | Confirmation text. |
hx_vals |
{} |
Extra HTMX values. |
display_mode |
workspace |
inline, workspace, full_page. |
target_kind |
custom |
list, setup, detail, custom. |
target_app_label, target_model |
empty | Related model. |
target_url_name |
empty | Explicit target URL. |
parent_lookup |
empty | FK/O2O field on the target model. |
relation_name, relation_type |
empty, one_to_many |
Inline metadata. |
requires_object |
True |
Disable in create mode or with no selected row. |
show_in_edit, show_in_create |
True |
Visibility flags. |
permissions, groups |
empty | Visibility rules. |
children |
empty | One dropdown level. |
is_active |
True |
Enable/disable. |
Menu code may contain letters, numbers, underscores and hyphens.
Menu Kinds
Action
Runs a registered BaseAction URL. The framework appends return_url, the profile key and the active tab
so the user returns to the same context.
RibbonMenuItemSpec(
code="put-on-hold",
label="Put on Hold",
tab="options",
kind="action",
url_name="vendor_put_on_hold", # resolves vendor_put_on_hold_action
http_method="POST",
confirm_message="Put this vendor on hold?",
)
Link
RibbonMenuItemSpec(
code="open-website",
label="Website",
kind="link",
url_name="accountpayable:vendor_website", # or a full URL name
display_mode="full_page",
)
Dropdown
RibbonMenuItemSpec(
code="more",
label="More",
kind="dropdown",
children=(
RibbonMenuItemSpec(code="audit", label="Audit", kind="link", url_name="..."),
RibbonMenuItemSpec(code="archive", label="Archive", kind="action", url_name="..."),
),
)
Only one dropdown level is supported.
Separator
RibbonMenuItemSpec(code="sep-1", label="", kind="separator")
Related Record Menus
A related menu targets another record page.
RibbonMenuItemSpec(
code="bank-details",
label="Bank Details",
tab="manage",
group="Banking",
kind="related",
target_kind="list",
target_app_label="accountpayable",
target_model="vendorbankaccount",
parent_lookup="vendor",
display_mode="inline",
)
display_mode
| Mode | Behavior |
|---|---|
inline |
Opens the existing inline list/detail workspace for the related model. Best for child records such as bank accounts. |
workspace |
Opens the related list, detail, create and edit inside the parent content area. The parent ribbon stays visible. |
full_page |
Navigates the whole browser to the target, for example a setup/master-detail page. |
target_kind
| Kind | Behavior |
|---|---|
list |
Resolve {app}:{model}_list. |
detail |
Resolve the related object and open {app}:{model}_detail. |
setup |
Requires target_url_name; opens the setup page as a full page with return_url. |
custom |
Requires target_url_name; the developer owns the target template. |
For list/workspace menus, the framework adds the parent record identity to the target URL and filters the
target list when parent_lookup is configured. For inline menus, the existing inline views are reused and
the parent context is passed through main_object_pk.
Inline mode and full templates
A vendor bank-account menu can use display_mode="inline" to reuse the inline views. The inline list,
detail, create and delete behavior stays the same as a normal inline section.
If the related records need the full list workspace (filters, paging, local New/Edit/Delete), use
display_mode="workspace". The related record list opens inside #detail-container; its detail, edit and
create views stay in the same workspace, and Back returns to the parent record with the Manage tab active.
Business Actions
BaseAction remains the execution contract. A menu only points at the action URL; the action view keeps
its own business logic and permission checks.
from enezaframework.views import BaseAction
@auto_url_register
class VendorPutOnHoldAction(BaseAction):
model = VendorTable
def process_action(self, pk):
vendor = VendorTable.objects.get(pk=pk)
vendor.status = "on_hold"
vendor.save()
The framework passes return_url, ribbon_profile and ribbon_tab to the action. If no safe return URL
is supplied, BaseAction redirects to the record detail as before.
List-Context Menus
Set context="list" to add a menu to the list ribbon. Such menus act on exactly one selected row. The
framework attaches the selected row’s resolved action URL to the row and enables the ribbon item only when
one row is selected.
RibbonMenuItemSpec(
code="put-on-hold",
label="Put on Hold",
context="list",
tab="options",
kind="action",
url_name="vendor_put_on_hold",
)
Menus with requires_object=False can be static links and do not depend on selection.
Permissions and Entity Overrides
Menus may declare Django permissions and/or group names:
RibbonMenuItemSpec(
code="bank-details",
label="Bank Details",
permissions=("accountpayable.view_vendorbankaccount",),
groups=("Credit Control",),
)
All listed permissions must be held; if groups are supplied, the user must belong to at least one. Hidden items are not rendered. The action view must still enforce access because a user can call the URL directly.
Developer code profiles apply to every entity. The optional database overlay can add an entity-specific override (see below).
Database Overlay
The database overlay is optional. It is useful when a deployment needs to add or override menus without a
code release. The models live in enezaframework/models/ribbon.py:
| Model | Purpose |
|---|---|
RibbonProfile |
Optional profile record; entity=NULL is global. |
RibbonTab |
Tabs for a profile. |
RibbonMenu |
Actions, links, related menus and dropdown children. |
Resolution order:
- Base
get_ribbon_tabs(). - Code profile.
- Global DB overlay.
- Entity-specific DB overlay.
An item with the same code in a later layer replaces the earlier item. If no profile resolves, the base
ribbon is rendered unchanged.
There is intentionally no in-app setup screen for these models in this iteration. Create records through the ORM, shell or Django admin if the models are registered in the project.
Backward Compatibility
The following continue to work unchanged:
get_ribbon_tabs()overrides.action_listandprint_actionsin the detail action bar.main_actionsand the workflow dropdown innavactions.html.WorkflowMixin.- Setup, journal, parameter, attachment and reporting screens.
If a view does not define a profile, no profile query parameter is added and the base tabs are used. A missing or invalid profile key falls back to the detail view’s default profile, then the base tabs.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Menu does not render | is_active=False, wrong context, failed permission/group check, or invalid target configuration. |
| Menu is disabled in create mode | requires_object=True and the record is not saved yet. This is expected. |
| List menu stays disabled | Zero or multiple rows are selected, or the row has no resolved URL for that menu code. |
| URL error in DEBUG | The url_name/target_url_name could not be reversed. Use the full app:name form. |
| Inline menu is empty | The target inline views are not registered under {model}_list, {model}_detail, {model}_create and {model}_delete. |
| Workspace returns a nested shell | The workspace request is missing partial=1. The framework adds it for internal workspace navigation; custom links should include it when targeting #ez-workspace-body. |
| Refresh loses a custom profile | Set default_ribbon_profile on the detail view or register the profile at import time. |