Model tabs provide a powerful navigation system that allows you to create custom tab-based navigation across different models, views, and pages in your Django admin interface.
Overview
Model tabs are configured at the site level and allow you to group related models or views into a tabbed interface. This is useful for creating dashboard-like views or organizing related administrative tasks.
Configuration
Model tabs are configured in your admin site settings using the TABS configuration:
from django.contrib import admin
from unfold.admin import ModelAdmin
from unfold.sites import UnfoldAdminSite
class CustomAdminSite ( UnfoldAdminSite ):
def get_tabs_list ( self , request ):
return [
{
'models' : [ 'app_name.modelname' ],
'items' : [
{ 'link' : '/admin/app/model/' , 'title' : 'All Items' },
{ 'link' : '/admin/app/model/?status=active' , 'title' : 'Active' },
{ 'link' : '/admin/app/model/?status=archived' , 'title' : 'Archived' },
]
}
]
admin_site = CustomAdminSite( name = 'unfoldadmin' )
Model tabs are defined in the TABS configuration and can be customized per request.
Tab Structure
Each tab configuration consists of:
List of model identifiers that should display these tabs. Can be strings in 'app.model' format or dictionaries with additional options.
List of tab items to display. Each item must have link and title properties.
Specific page identifier if tabs should only appear on certain pages (e.g., 'changelist', 'changeform').
Basic Example
Create tabs for filtering products by status:
from unfold.sites import UnfoldAdminSite
class MyAdminSite ( UnfoldAdminSite ):
def get_tabs_list ( self , request ):
return [
{
'models' : [ 'shop.product' ],
'items' : [
{
'title' : 'All Products' ,
'link' : '/admin/shop/product/' ,
},
{
'title' : 'In Stock' ,
'link' : '/admin/shop/product/?stock__gt=0' ,
},
{
'title' : 'Out of Stock' ,
'link' : '/admin/shop/product/?stock=0' ,
},
{
'title' : 'Discontinued' ,
'link' : '/admin/shop/product/?status=discontinued' ,
},
]
}
]
Model-Specific Tabs
You can specify different tabs for changelist and detail views:
def get_tabs_list ( self , request ):
return [
{
'models' : [
{ 'name' : 'shop.product' , 'detail' : False } # Only on changelist
],
'items' : [
{ 'title' : 'All Products' , 'link' : '/admin/shop/product/' },
{ 'title' : 'Featured' , 'link' : '/admin/shop/product/?featured=true' },
]
},
{
'models' : [
{ 'name' : 'shop.product' , 'detail' : True } # Only on detail view
],
'items' : [
{ 'title' : 'Overview' , 'link' : '?tab=overview' },
{ 'title' : 'Analytics' , 'link' : '?tab=analytics' },
]
}
]
Use the detail key in model dictionaries to control whether tabs appear on the changelist (detail=False) or change form (detail=True).
Page-Specific Tabs
Tabs can be limited to specific page types:
def get_tabs_list ( self , request ):
return [
{
'page' : 'changelist' , # Only on list view
'items' : [
{ 'title' : 'Dashboard' , 'link' : '/admin/dashboard/' },
{ 'title' : 'Reports' , 'link' : '/admin/reports/' },
]
}
]
Dynamic Tab Generation
Generate tabs dynamically based on user permissions or other criteria:
class MyAdminSite ( UnfoldAdminSite ):
def get_tabs_list ( self , request ):
tabs = [
{
'models' : [ 'shop.product' ],
'items' : [
{ 'title' : 'All Products' , 'link' : '/admin/shop/product/' },
]
}
]
# Add admin-only tabs
if request.user.is_superuser:
tabs[ 0 ][ 'items' ].extend([
{ 'title' : 'Pending Review' , 'link' : '/admin/shop/product/?status=pending' },
{ 'title' : 'Flagged' , 'link' : '/admin/shop/product/?flagged=true' },
])
return tabs
Active Tab Detection
The system automatically detects the active tab by comparing:
The current URL path
Query parameters
# The active tab is automatically highlighted when:
# - The link matches the current path
# - All query params in the link match the current query params
{
'title' : 'Active Products' ,
'link' : '/admin/shop/product/?status=active' ,
# Will be active when viewing /admin/shop/product/?status=active
# But not when viewing /admin/shop/product/?status=active&page=2
}
For active tab detection to work correctly, ensure your tab links include all necessary query parameters.
Using Callbacks for Links
Generate links dynamically using the link_callback option:
from django.urls import reverse
def get_tabs_list ( self , request ):
return [
{
'models' : [ 'shop.product' ],
'items' : [
{
'title' : 'All Products' ,
'link_callback' : lambda req : reverse( 'admin:shop_product_changelist' ),
},
{
'title' : 'My Products' ,
'link_callback' : lambda req : f " { reverse( 'admin:shop_product_changelist' ) } ?owner= { req.user.id } " ,
},
]
}
]
Use link_callback when you need to generate links dynamically based on the request object.
Model tabs work alongside sidebar navigation. When a sidebar link is active, the system checks if any associated model tabs should also be highlighted:
class MyAdminSite ( UnfoldAdminSite ):
def get_navigation ( self , request ):
return [
{
'title' : 'Products' ,
'link' : '/admin/shop/product/' ,
# This sidebar link will be active when any product tab is active
},
]
def get_tabs_list ( self , request ):
return [
{
'models' : [ 'shop.product' ],
'items' : [
{ 'title' : 'All' , 'link' : '/admin/shop/product/' },
{ 'title' : 'Active' , 'link' : '/admin/shop/product/?status=active' },
]
}
]
Permissions
Filter tabs based on user permissions:
class MyAdminSite ( UnfoldAdminSite ):
def get_tabs_list ( self , request ):
tabs = [
{
'models' : [ 'shop.product' ],
'items' : []
}
]
# Check permissions before adding items
from shop.models import Product
opts = Product._meta
if request.user.has_perm( f ' { opts.app_label } .view_ { opts.model_name } ' ):
tabs[ 0 ][ 'items' ].append({
'title' : 'All Products' ,
'link' : '/admin/shop/product/' ,
})
if request.user.has_perm( f ' { opts.app_label } .change_ { opts.model_name } ' ):
tabs[ 0 ][ 'items' ].append({
'title' : 'Pending Approval' ,
'link' : '/admin/shop/product/?status=pending' ,
})
return tabs
Best Practices
Use meaningful tab titles
Choose clear, action-oriented titles that describe what users will see. # Good
{ 'title' : 'Active Orders' , 'link' : '...' }
{ 'title' : 'Pending Shipment' , 'link' : '...' }
# Avoid
{ 'title' : 'Tab 1' , 'link' : '...' }
{ 'title' : 'Other' , 'link' : '...' }
Keep query parameters consistent
Use consistent query parameter names across your tabs for better maintainability. # Good - consistent parameter names
{ 'title' : 'Active' , 'link' : '/admin/shop/product/?status=active' }
{ 'title' : 'Inactive' , 'link' : '/admin/shop/product/?status=inactive' }
# Avoid - inconsistent parameters
{ 'title' : 'Active' , 'link' : '/admin/shop/product/?status=active' }
{ 'title' : 'Inactive' , 'link' : '/admin/shop/product/?is_active=false' }
Too many tabs can overwhelm users. Consider grouping or using a different navigation pattern if you need more than 6-7 tabs.
Tab navigation adapts to smaller screens, but test your tab configuration on mobile devices to ensure a good experience.
Technical Details
Model tabs are processed through:
unfold.sites.UnfoldAdminSite.get_tabs_list() - Returns tab configuration
unfold.sites.UnfoldAdminSite._get_is_tab_active() - Determines active tab
unfold.templatetags.unfold.tab_list - Renders tabs in templates
Template tag usage:
{% load unfold %}
{% tab_list 'changelist' opts %}
Inline Tabs Organize related model inlines in tabs
Fieldset Tabs Create tabs within fieldsets
Sidebar Navigation Configure the sidebar navigation
Custom Pages Add custom pages to the admin