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

Django中如何从无关联模型获取QuerySet以生成二级菜单

实现分类+品牌的二级菜单(无空项)

核心思路

要生成符合要求的二级菜单,需同时满足两个核心条件:

  1. 仅显示包含至少一个配件的分类
  2. 每个分类下仅显示有该分类对应配件的品牌
    同时要避免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>

关键注意事项

  1. 排除空关联:查询时必须过滤category__isnull=False和brand__isnull=False,避免出现无意义的空项
  2. 去重处理:通过distinct()或字典判断,避免同一个品牌在分类下重复出现
  3. 排序控制:通过order_by保证分类和品牌的显示顺序符合业务需求
  4. 路由适配:模板中的url标签需替换为你项目中实际的路由名称

内容的提问来源于stack exchange,提问作者Michael Romanov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 00:42:52