Visibility Checks#
Control which users see which menu items using check functions.
Basic Usage#
Pass a check parameter to control visibility:
Menu(
"main_nav",
children=[
MenuItem(name="home", view_name="home", check=True), # Always visible
MenuItem(name="hidden", view_name="hidden", check=False), # Never visible
MenuItem(
name="admin",
view_name="admin:index",
check=lambda request: request.user.is_staff, # Check function
),
],
)
Check Function Signature#
Check functions receive request and optional keyword arguments:
def my_check(request, **kwargs):
"""
Return True to show item, False to hide.
Args:
request: Django HttpRequest object
**kwargs: Additional arguments from process()
"""
return request.user.is_authenticated
Built-in Checks#
Django-flex-menus provides common check functions in flex_menu.checks.
User Status Checks#
from flex_menu.checks import (
user_is_authenticated,
user_is_anonymous,
user_is_staff,
user_is_superuser,
user_is_active,
)
Menu(
"main_nav",
children=[
MenuItem(name="dashboard", view_name="dashboard", check=user_is_authenticated),
MenuItem(name="admin", view_name="admin:index", check=user_is_staff),
MenuItem(name="login", view_name="login", check=user_is_anonymous),
],
)
Group Checks#
from flex_menu.checks import user_in_any_group, user_in_all_groups
Menu(
"content_nav",
children=[
MenuItem(name="editors", view_name="editors", check=user_in_any_group("editors", "admins")),
MenuItem(name="super_editors", view_name="super_editors", check=user_in_all_groups("editors", "verified")),
],
)
Permission Checks#
from flex_menu.checks import has_any_permission, has_all_permissions
Menu(
"blog_nav",
children=[
MenuItem(name="content", view_name="content", check=has_any_permission("blog.add_post", "blog.change_post")),
MenuItem(name="publish", view_name="publish", check=has_all_permissions("blog.add_post", "blog.publish_post")),
],
)
Request Checks#
from flex_menu.checks import is_ajax, is_secure, method_is
# Only for AJAX requests
MenuItem(
name="ajax_menu",
url="/api/menu/",
check=is_ajax,
)
# Only for HTTPS
MenuItem(
name="secure",
url="/secure/",
check=is_secure,
)
# Only for specific HTTP methods
MenuItem(
name="post_only",
url="/submit/",
check=method_is("POST"),
)
User Attribute Checks#
from flex_menu.checks import user_attribute_equals
# Check custom user attribute
MenuItem(
name="premium",
url="/premium/",
check=user_attribute_equals("is_premium", True),
)
Combining Checks#
AND Logic#
All checks must pass:
from flex_menu.checks import combine_checks, user_is_authenticated, user_is_staff
MenuItem(
name="admin_panel",
url="/admin/",
check=combine_checks(
user_is_authenticated,
user_is_staff,
operator="and",
),
)
OR Logic#
At least one check must pass:
from flex_menu.checks import combine_checks, user_is_staff, user_is_superuser
MenuItem(
name="moderation",
url="/moderate/",
check=combine_checks(
user_is_staff,
user_is_superuser,
operator="or",
),
)
NOT Logic#
Invert a check:
from flex_menu.checks import negate_check, user_is_staff
# Show to non-staff only
MenuItem(
name="limited",
url="/limited/",
check=negate_check(user_is_staff),
)
Complex Logic#
Combine multiple operations:
from flex_menu.checks import (
combine_checks,
user_is_authenticated,
user_in_any_group,
has_any_permission,
)
# Authenticated AND (in group OR has permission)
check_inner = combine_checks(
user_in_any_group("premium", "staff"),
has_any_permission("app.special_access"),
operator="or",
)
check_final = combine_checks(
user_is_authenticated,
check_inner,
operator="and",
)
MenuItem(
name="special",
url="/special/",
check=check_final,
)
Custom Check Functions#
Create your own check functions:
def user_has_profile(request, **kwargs):
"""Show only if user has a complete profile."""
if not request.user.is_authenticated:
return False
return hasattr(request.user, 'profile') and request.user.profile.is_complete
def business_hours(request, **kwargs):
"""Show only during business hours."""
from datetime import datetime
hour = datetime.now().hour
return 9 <= hour < 17
MenuItem(
name="profile_settings",
url="/profile/",
check=user_has_profile,
)
MenuItem(
name="support",
url="/support/",
check=business_hours,
)
Context-Aware Checks#
Pass additional context to check functions via template tags. This is useful for checking against database objects or other dynamic data.
In your menu definition:
def can_edit_project(request, project=None, **kwargs):
"""Check if user can edit the given project."""
if not request.user.is_authenticated:
return False
if not project:
return False
return project.owner == request.user or request.user.is_staff
MenuItem(
name="project_menu",
children=[
MenuItem(name="view", view_name="project_detail"),
MenuItem(name="edit", view_name="project_edit", check=can_edit_project),
MenuItem(name="delete", view_name="project_delete", check=can_edit_project),
],
)
In your template:
{% render_menu 'project_menu' renderer='bootstrap5' project=project %}
In your view (alternative):
def project_detail(request, pk):
project = get_object_or_404(Project, pk=pk)
return render(request, 'project.html', {
'project': project,
})
{# In template - pass project from context #}
{% render_menu 'project_menu' renderer='bootstrap5' project=project %}
Multiple context variables:
def check_permissions(request, user=None, resource=None, action=None, **kwargs):
"""Generic permission check."""
if not request.user.is_authenticated:
return False
# Your permission logic here
return has_permission(request.user, resource, action)
MenuItem(
name="actions",
children=[
MenuItem(
name="edit",
view_name="edit",
check=lambda request, **kw: check_permissions(
request, resource=kw.get('resource'), action='edit', **kw
),
),
],
)
{% render_menu 'actions' renderer='simple' resource=document action='edit' %}
Kwargs Usage Summary:
All keyword arguments passed to render_menu or process_menu are used in two ways:
Passed to ALL check functions - Every check function receives all kwargs for context-aware decisions
Filtered for URL resolution - For
view_nameitems, only URL pattern parameters are used; for callable URLs, all kwargs are passed
URL Parameter Filtering:
When using view_name, django-flex-menus automatically inspects the URL pattern to determine which parameters it needs:
# URL pattern: path('posts/<int:pk>/edit/', views.edit_post, name='post_edit')
MenuItem(name="edit", view_name="post_edit") # Only uses 'pk' from kwargs
{# Pass many kwargs - 'post_edit' only gets 'pk', but check functions get all #}
{% render_menu 'menu' renderer='simple' pk=post.pk user=user project=project %}
This filtering means:
✅ You can safely pass extra context without breaking URL resolution
✅ Check functions always receive full context
✅ Failed URL resolution makes items invisible (not errors)
✅ Different menu items can need different URL parameters
Important
If you pass kwargs that a view_name doesn’t expect, reverse() will fail and that item becomes invisible (not an error). This allows mixing items with different URL requirements in the same menu.
Parent-Child Visibility#
Important
If a parent has no visible children, the parent is automatically hidden.
Menu(
"main_nav",
children=[
MenuItem(
name="admin",
children=[
MenuItem(name="users", view_name="admin:users", check=user_is_staff),
MenuItem(name="settings", view_name="admin:settings", check=user_is_superuser),
],
),
],
)
# Result:
# - Non-staff: admin section hidden (no visible children)
# - Staff (not super): admin section visible with "users" only
# - Superuser: admin section visible with both children
Debug Check#
Show items only in DEBUG mode:
from flex_menu.checks import debug_mode_only
MenuItem(
name="debug_toolbar",
url="/debug/",
check=debug_mode_only,
)
Check Function Reference#
User Status#
user_is_authenticated(request)- User logged inuser_is_anonymous(request)- User not logged inuser_is_staff(request)- User has staff statususer_is_superuser(request)- User has superuser statususer_is_active(request)- User account active
Groups#
user_in_any_group(*groups)- User in at least one groupuser_in_all_groups(*groups)- User in all groups
Permissions#
has_any_permission(*perms)- User has at least one permissionhas_all_permissions(*perms)- User has all permissions
Request#
is_ajax(request)- AJAX requestis_secure(request)- HTTPS requestmethod_is(*methods)- HTTP method matches
Attributes#
user_attribute_equals(attr, value)- User attribute equals value
Combinators#
combine_checks(*checks, operator='and')- Combine multiple checksnegate_check(check)- Invert check result
Other#
debug_mode_only(request)- Show only when DEBUG=True
Best Practices#
Use built-in checks - Prefer provided functions over custom lambdas
Test visibility - Verify menus with different user types
Fail closed - Return False by default in custom checks
Handle unauthenticated - Check
request.user.is_authenticatedbefore accessing user attributesParent visibility - Remember parents hide if all children are hidden
Combine wisely - Keep complex check logic readable with helper functions