Django中如何从无关联模型获取QuerySet以生成二级菜单
实现分类+品牌的二级菜单(无空项)
核心思路
要生成符合要求的二级菜单,需同时满足两个核心条件:
- 仅显示包含至少一个配件的分类
- 每个分类下仅显示有该分类对应配件的品牌
同时要避免N+1查询,保证页面加载性能。
方法一:数据整理式实现(直观易上手)
适合数据量较小的场景,通过直接查询分类-品牌的有效组合,整理成菜单结构:
1. 后端查询并整理数据
from django.db.models import Count def get_menu_context(): # 获取所有存在有效配件的分类-品牌组合(排除category/brand为null的无效项) category_brand_pairs = Part.objects.filter( category__isnull=False, brand__isnull=False ).values( 'category', 'category__name', 'category__slug', 'brand', 'brand__name', 'brand__slug' ).annotate( part_count=Count('id') ).order_by('category__name', 'brand__name') # 整理成「分类→品牌列表」的字典结构 menu_data = {} for pair in category_brand_pairs: cat_key = pair['category'] # 初始化分类条目 if cat_key not in menu_data: menu_data[cat_key] = { 'name': pair['category__name'], 'slug': pair['category__slug'], 'brands': [] } # 添加品牌(避免重复) brand_item = { 'id': pair['brand'], 'name': pair['brand__name'], 'slug': pair['brand__slug'] } if brand_item not in menu_data[cat_key]['brands']: menu_data[cat_key]['brands'].append(brand_item) # 转成有序列表(按分类名称排序) menu_list = sorted(menu_data.values(), key=lambda x: x['name']) return {'menu_list': menu_list}
2. 模板渲染二级菜单
<ul class="category-menu"> {% for category in menu_list %} <li class="category-item"> <a href="{% url 'category_detail' category.slug %}">{{ category.name }}</a> <ul class="brand-submenu"> {% for brand in category.brands %} <li><a href="{% url 'category_brand_parts' category.slug brand.slug %}">{{ brand.name }}</a></li> {% endfor %} </ul> </li> {% endfor %} </ul>
方法二:ORM预取优化(高性能方案)
适合数据量较大的场景,利用Django的Prefetch对象实现高效查询,彻底避免N+1问题:
1. 后端查询代码
from django.db.models import Prefetch, Count, Q, OuterRef def get_menu_context(): # 定义每个分类对应的有效品牌查询集:仅包含有该分类配件的品牌 valid_brands_query = Brand.objects.filter( parts__category=OuterRef('pk'), parts__isnull=False ).annotate( part_count=Count('parts') ).filter(part_count__gt=0).distinct() # 获取有配件的分类,并预取对应的有效品牌 categories = Category.objects.annotate( num_parts=Count('parts', filter=Q(parts__isnull=False)) ).filter(num_parts__gt=0).prefetch_related( Prefetch( 'parts__brand', queryset=valid_brands_query, to_attr='valid_brands' ) ) return {'categories': categories}
2. 模板渲染代码
<ul class="category-menu"> {% for category in categories %} <li class="category-item"> <a href="{% url 'category_detail' category.slug %}">{{ category.name }}</a> {% if category.valid_brands %} <ul class="brand-submenu"> {% for brand in category.valid_brands %} <li><a href="{% url 'category_brand_parts' category.slug brand.slug %}">{{ brand.name }}</a></li> {% endfor %} </ul> {% endif %} </li> {% endfor %} </ul>
关键注意事项
- 排除空关联:查询时必须过滤
category__isnull=False和brand__isnull=False,避免出现无意义的空项 - 去重处理:通过
distinct()或字典判断,避免同一个品牌在分类下重复出现 - 排序控制:通过
order_by保证分类和品牌的显示顺序符合业务需求 - 路由适配:模板中的
url标签需替换为你项目中实际的路由名称
内容的提问来源于stack exchange,提问作者Michael Romanov
相关产品推荐
相关产品推荐

