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
相关产品推荐
相关产品推荐

