You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何在所有页面的base模板中动态显示数据库生成的菜单?

解决Django中全局菜单显示问题

你现在遇到的问题本质是:只有首页视图传递了menus变量给模板,其他页面的视图没有传递,所以跳转后模板找不到这个变量,菜单就不显示了。

最优雅的解决方案是使用Django的上下文处理器(Context Processor),它能在所有模板渲染时自动注入全局变量,不用在每个视图里重复传递menus。

步骤1:创建上下文处理器

在你的home应用下新建一个context_processors.py文件,把原来的菜单生成逻辑移到这里(做一点调整,因为上下文处理器必须返回字典,不能直接返回响应):

from django.utils.html import format_html
from .models import User

def menu_processor(request):
    menus_html = ""
    # 处理未登录用户的情况
    if "user_id" not in request.session:
        return {"menus": format_html(menus_html)}
    
    try:
        current_user = User.objects.get(id=request.session.get("user_id"))
        # 获取用户的第一个角色(和你原来的逻辑保持一致)
        current_user_role = current_user.role.all()[:1].get()
    except (User.DoesNotExist, IndexError):
        # 处理无权限/用户不存在的情况
        menus_html = '<li><a href="javascript:void(0);">无权限访问</a></li>'
        return {"menus": format_html(menus_html)}
    
    # 获取菜单数据
    top_menus = current_user_role.nav.filter(parent=None).all()
    all_sub_menus = current_user_role.nav.exclude(parent__isnull=True).all()

    # 递归生成子菜单的辅助函数
    def generate_sub_menus(parent_id, sub_menu_list):
        sub_html = ""
        sub_menus = sub_menu_list.filter(parent=parent_id).all()
        for sub_menu in sub_menus:
            if not sub_menu.hasChildNav():
                sub_html += '<li><a href="{url}" target="{target}"><i class="icon {icon}" style="padding-left: 15px;"></i><span style="padding-left: 10px;">{name}</span></a></li>'
            else:
                sub_html += '<li class="submenu"><a href="#" target="{target}"><i class="icon {icon}" style="padding-left: 15px;"></i><span style="padding-left: 10px;">{name}</span></a><ul>{child_menus}</ul></li>'
                # 递归替换子菜单内容
                sub_html = sub_html.replace("{child_menus}", generate_sub_menus(sub_menu.id, sub_menu_list))
            # 填充当前子菜单的变量
            sub_html = sub_html.format(
                url=sub_menu.url,
                icon=sub_menu.icon,
                target=sub_menu.target,
                name=sub_menu.name
            )
        return sub_html

    # 生成顶级菜单
    if not top_menus:
        menus_html = '<li><a href="javascript:void(0);">Index</a></li>'
    else:
        for menu in top_menus:
            if not menu.hasChildNav():
                menus_html += '<li><a href="{url}" target="{target}"><i class="icon {icon}"></i><span>{name}</span></a></li>'
            else:
                menus_html += '<li class="submenu"><a href="#" target="{target}"><i class="icon {icon}"></i><span>{name}</span></a><ul style="display: none;">{child_menus}</ul></li>'
                # 插入子菜单
                menus_html = menus_html.replace("{child_menus}", generate_sub_menus(menu.id, all_sub_menus))
            # 填充当前顶级菜单的变量
            menus_html = menus_html.format(
                url=menu.url,
                icon=menu.icon,
                target=menu.target,
                name=menu.name
            )

    # 返回包含菜单HTML的字典,这个字典会自动注入到所有模板的上下文
    return {"menus": format_html(menus_html)}

步骤2:配置上下文处理器

打开项目根目录的settings.py,找到TEMPLATES配置项,在OPTIONS的context_processors列表中添加你刚创建的处理器:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
                # 添加这一行
                "home.context_processors.menu_processor",
            ],
        },
    },
]

步骤3:简化视图和模板

现在你可以去掉dashboard视图中传递menus的代码了,因为上下文处理器会自动注入:

def dashboard(request):
    if 'user_id' not in request.session:
        return HttpResponseRedirect('login')
    # 只需要传递其他必要变量
    context = {'username': request.session.get('user_name')}
    return render(request, 'home/index.html', context)

你的base.html可以保持原样,因为{{ menus }}现在会自动被上下文处理器填充:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    {% block css %}{% endblock %}
</head>
<body>
    <!--sidebar-menu-->
    <div id="sidebar">
        <ul>
            {% block navbar %}{{ menus }}{% endblock %}
        </ul>
        <!--sidebar-menu-->
    </div>
    {% block content %}{% endblock %}
</body>
</html>

为什么这样管用?

上下文处理器会在每个模板渲染请求时自动执行,它返回的字典会被合并到模板的上下文中。也就是说,不管你访问哪个页面,只要模板继承了base.html,{{ menus }}变量都会存在,菜单自然就能显示了。

内容的提问来源于stack exchange,提问作者Jason Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:52:51