Adding Ribbon Tabs, Groups & Menus
Step-by-step developer guide. For the complete reference, see Ribbon & Action Menus.
A ribbon is built from three layers:
RibbonProfileSpec
└── Tab (RibbonTabSpec)
└── Group (created by menu items sharing the same group title)
└── Menu item (RibbonMenuItemSpec)
There is no separate group class. A group is created automatically from the group and
group_order values on menu items.
1. Define the profile
Define one profile per list/detail family. The profile key is the handoff between the list and detail pages.
from enezaframework.ribbon import (
RibbonMenuItemSpec,
RibbonProfileSpec,
RibbonTabSpec,
)
DOCUMENT_RIBBON = RibbonProfileSpec(
key="accountpayable.vendor",
name="Vendor",
default_tab="options",
tabs=(),
menus=(),
)
Attach it to the list view so list rows carry the profile key:
class VendorListView(BaseListView):
model = Vendor
ribbon_profile = DOCUMENT_RIBBON
Attach it to the detail view as well. This also makes direct/bookmarked detail URLs resolve the profile without relying on the list page:
class VendorDetailView(BaseDetailView):
model = Vendor
ribbon_profile = DOCUMENT_RIBBON
get_ribbon_profile() can be used instead of the class attribute when the profile is dynamic:
class VendorListView(BaseListView):
model = Vendor
def get_ribbon_profile(self):
return DOCUMENT_RIBBON
2. Add a tab
Use RibbonTabSpec:
tabs=(
RibbonTabSpec(
key="approval",
label="Approval",
context="both",
order=10,
),
),
Tab icons are optional. Omit icon for a text-only tab.
context controls where the tab is available:
| Context | Available in |
|---|---|
list |
List ribbon only |
detail |
Detail ribbon only |
both |
List and detail ribbons |
Use tabs for a shared tab. Use list_tabs or detail_tabs when the same tab key needs different
labels, ordering or visibility per context.
RibbonProfileSpec(
key="vendor",
list_tabs=(
RibbonTabSpec(key="approval", label="Approval", context="list", order=10),
),
detail_tabs=(
RibbonTabSpec(key="approval", label="Approval", context="detail", order=10),
),
)
Tab order is the order value. Base tabs (Home, Options, View) are merged automatically; a
custom tab is appended in its order position.
3. Add a group
Groups are declared on the menu items, not separately. Give related items the same group title:
menus=(
RibbonMenuItemSpec(
code="submit-for-review",
label="Submit for Review",
tab="approval",
group="Status",
group_order=1,
order=1,
kind="action",
url_name="vendor_submit",
),
RibbonMenuItemSpec(
code="approve",
label="Approve",
tab="approval",
group="Status",
group_order=1,
order=2,
kind="action",
url_name="vendor_approve",
),
)
Both items appear in one Status group inside the Approval tab.
groupis the visible group title.group_ordercontrols the group's position within the tab.ordercontrols the item's position within the group.
A group is limited to three items vertically. A fourth item automatically flows into a new column inside the same group. Add items in the order you want them to fill the grid.
4. Add a menu item
An item is attached by two values:
tab— the tab key (home,options,view, or a custom key such asapproval).group— the group title inside that tab.
If the group title already exists in the tab, the item joins it. If not, the group is created automatically. There is no separate group registration step.
Quick recipes
Add a button to an existing group
menus=(
RibbonMenuItemSpec(
code="vendor-export",
label="Export",
tab="options",
group="Output",
group_order=1,
order=2,
context="both",
kind="link",
url_name="vendor_export",
display_mode="full_page",
),
),
Add a new group
RibbonMenuItemSpec(
code="vendor-audit",
label="Audit",
tab="manage",
group="Compliance",
group_order=3,
order=1,
context="both",
kind="link",
url_name="vendor_audit",
)
The new Compliance group is created in the Manage tab because no other item uses that group title.
Add to the base Home, Options or View tabs
RibbonMenuItemSpec(
code="home-refresh",
label="Refresh record",
tab="home",
group="Record",
group_order=1,
order=1,
context="detail",
kind="action",
url_name="vendor_refresh",
)
Use tab="options" for the default action tab, tab="view" for display actions, or tab="home"
for primary actions.
Add to a custom tab
RibbonTabSpec(
key="approval",
label="Approval",
context="both",
order=10,
)
RibbonMenuItemSpec(
code="approve",
label="Approve",
tab="approval",
group="Status",
group_order=1,
order=1,
context="both",
kind="action",
url_name="vendor_approve",
confirm_message="Approve this vendor?",
)
Add list-only, detail-only or both
context="list" # list ribbon only, acts on the selected row
context="detail" # detail ribbon only, acts on the open record
context="both" # both ribbons
If the menu should appear in the list ribbon, the list view must also have ribbon_profile.
Add an action item
RibbonMenuItemSpec(
code="put-on-hold",
label="Put on Hold",
tab="options",
group="Status",
order=1,
context="both",
kind="action",
url_name="vendor_put_on_hold",
confirm_message="Put this vendor on hold?",
)
The action view:
from enezaframework.utils import auto_url_register
from enezaframework.views import BaseAction
@auto_url_register
class VendorPutOnHoldAction(BaseAction):
model = Vendor
action_name = "vendor_put_on_hold"
def process_action(self, pk):
Vendor.objects.filter(pk=pk).update(status="on_hold")
action_name="vendor_put_on_hold" registers the URL vendor_put_on_hold_action. The menu may use
either url_name="vendor_put_on_hold" or url_name="vendor_put_on_hold_action".
Add a report/link item
RibbonMenuItemSpec(
code="print",
label="Print",
icon="printer",
tab="options",
group="Output",
group_order=1,
order=1,
context="both",
kind="link",
url_name="vendor_report",
display_mode="full_page",
target="_blank",
)
Add a related-record item
RibbonMenuItemSpec(
code="bank-details",
label="Bank Details",
tab="manage",
group="Banking",
order=1,
context="both",
kind="related",
target_kind="list",
target_app_label="accountpayable",
target_model="vendorbankaccount",
parent_lookup="vendor",
display_mode="inline",
)
Add a dropdown
RibbonMenuItemSpec(
code="more",
label="More",
tab="options",
group="Actions",
order=1,
context="both",
kind="dropdown",
children=(
RibbonMenuItemSpec(
code="audit",
label="Audit",
kind="link",
url_name="vendor_audit",
),
RibbonMenuItemSpec(
code="archive",
label="Archive",
kind="action",
url_name="vendor_archive",
confirm_message="Archive this vendor?",
),
),
)
Add permissions
RibbonMenuItemSpec(
code="approve",
label="Approve",
tab="approval",
group="Status",
context="both",
kind="action",
url_name="vendor_approve",
permissions=("accountpayable.approve_vendor",),
groups=("Credit Control",),
)
All permissions must be held; if groups are listed, the user must belong to at least one.
Item ordering and columns
group_orderpositions the group inside the tab.orderpositions the item inside the group.- Groups show three items vertically. The fourth item flows into a new column in the same group, then the seventh into the next column.
Item kinds
RibbonMenuItemSpec supports five kinds:
Action
Runs a registered BaseAction URL. url_name may be the action name without the _action
suffix; the resolver tries both forms.
RibbonMenuItemSpec(
code="put-on-hold",
label="Put on Hold",
icon="check",
tab="approval",
group="Status",
kind="action",
url_name="vendor_put_on_hold",
confirm_message="Put this vendor on hold?",
)
The action view keeps the business logic:
from enezaframework.views import BaseAction
from enezaframework.utils import auto_url_register
@auto_url_register
class VendorPutOnHoldAction(BaseAction):
model = Vendor
action_name = "vendor_put_on_hold"
def process_action(self, pk):
Vendor.objects.filter(pk=pk).update(status="on_hold")
Link
Opens a normal URL. Use display_mode="full_page" for a report or another page.
RibbonMenuItemSpec(
code="print",
label="Print",
icon="printer",
tab="options",
group="Output",
kind="link",
url_name="vendor_report",
display_mode="full_page",
target="_blank",
)
Related record menu
Opens another record's list, setup page, detail page or inline panel.
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 |
Behavior |
|---|---|
inline |
Existing inline list/detail views in a panel. |
workspace |
Related list/detail/create/edit inside the parent content area. |
full_page |
Full browser navigation, useful for setup/master-detail pages. |
Dropdown
RibbonMenuItemSpec(
code="more",
label="More",
tab="options",
group="Actions",
kind="dropdown",
children=(
RibbonMenuItemSpec(
code="audit",
label="Audit",
kind="link",
url_name="vendor_audit",
),
RibbonMenuItemSpec(
code="archive",
label="Archive",
kind="action",
url_name="vendor_archive",
confirm_message="Archive this vendor?",
),
),
)
Only one dropdown level is supported.
Separator
RibbonMenuItemSpec(
code="status-separator",
label="",
tab="approval",
group="Status",
kind="separator",
)
5. Choose list, detail or both
Set the menu context explicitly. Do not rely on the tab context alone.
RibbonMenuItemSpec(
code="approve",
label="Approve",
tab="approval",
group="Status",
context="both", # list and detail
kind="action",
url_name="vendor_approve",
)
| Context | Appears in |
|---|---|
list |
List ribbon. Acts on the selected row. |
detail |
Detail ribbon. Acts on the open record. |
both |
Both. |
List-context items with requires_object=True are disabled until exactly one row is selected.
6. Available menu fields
| Field | Default | Purpose |
|---|---|---|
code |
required | Stable identifier. |
label |
required | Visible text. |
icon |
empty | Icon name loaded from static/icons. |
tab |
options |
Tab key. |
context |
detail |
list, detail or both. |
group, group_order, order |
General, 0, 0 |
Group title and ordering. |
kind |
action |
action, link, related, dropdown, separator. |
url_name |
empty | Django URL name. |
http_method |
POST for actions |
Actions are executed as POST. |
confirm_message |
empty | Confirmation prompt. |
display_mode |
workspace |
inline, workspace, full_page. |
target_kind |
custom |
list, setup, detail, custom. |
target_app_label, target_model |
empty | Related target model. |
target_url_name |
empty | Explicit target URL. |
parent_lookup |
empty | FK/O2O on the target model. |
requires_object |
True |
Requires a saved/selected record. |
show_in_edit, show_in_create |
True |
Mode visibility. |
permissions, groups |
empty | Optional visibility rules. |
children |
empty | Dropdown children. |
is_active |
True |
Enable/disable. |
7. Complete example
The Framework Test Document profile is a working example:
FRAMEWORK_TEST_DOCUMENT_RIBBON = RibbonProfileSpec(
key="frameworkapp.frameworktestdocument",
name="Framework Test Document",
default_tab="options",
tabs=(
RibbonTabSpec(
key="approval",
label="Approval",
context="both",
order=10,
),
),
menus=(
RibbonMenuItemSpec(
code="print-document",
label="Print",
icon="printer",
tab="options",
group="Output",
group_order=1,
order=1,
context="both",
kind="link",
url_name="frameworktestdocument_report",
display_mode="full_page",
target="_blank",
),
RibbonMenuItemSpec(
code="approve-document",
label="Approve",
icon="check",
tab="approval",
group="Status",
group_order=2,
order=2,
context="both",
kind="action",
url_name="frameworktestdocument_approve",
confirm_message="Approve this document?",
),
),
)
This produces:
- Options → Output → Print
- Approval → Status → Approve
- On both list and detail ribbons.
8. Checklist
- Define a
RibbonProfileSpecwith a stablekey. - Add
tabs(orlist_tabs/detail_tabs) if you need custom tabs. - Add
RibbonMenuItemSpecentries withtab,group,group_orderandorder. - Set menu
contexttolist,detailorboth. - Attach
ribbon_profileto the list view and detail view. - For custom actions, create a
BaseActionand setaction_name. - Check permissions and
requires_objectbehavior. - Refresh the page; invalid URL names fail fast in DEBUG.
Common mistakes
| Problem | Fix |
|---|---|
| Custom tab missing in list | Use context="both" in tabs, or add the tab to list_tabs. |
| Menus appear on list but should not | Set context="detail". |
| Menus missing on list | Ensure the list view has ribbon_profile and context="both"/"list". |
| Group not merging | Use the same exact group title on the items. |
| Fourth item goes below instead of a new column | The ribbon CSS handles this automatically; do not override .ez-ribbon-cmds with a flex column. |
| Action URL not found | Use the full app:url_name or the action name without _action; check URL_REGISTRY. |
| Direct detail URL loses the profile | Add ribbon_profile to the detail view or register the profile at import time. |
| List menu does nothing | Select exactly one row; requires_object items are disabled otherwise. |