如何在 Django 中高效分离外键并实现按分类展示商品的模板逻辑

本文讲解如何通过 django 视图层预处理数据,避免在模板中执行复杂逻辑(如去重、分组),从而安全、高效地实现“按分类聚合商品”的展示需求。

在 Django 中,模板(Template)不是编程环境——它不支持变量赋值(如 {% used_category = [] %})、列表操作或复杂条件判断。你遇到的 TemplateSyntaxError: Invalid block tag 正是因为 Django 模板语言禁止直接声明和修改变量,这与 Python 的语义有本质区别。

✅ 正确做法是:将数据组织逻辑移至 视图(View)层,利用 Django ORM 的能力提前完成分组与关联,再将结构化数据传递给模板。

✅ 推荐方案:使用 prefetch_related + 字典分组(视图层处理)

首先,优化模型(补充 related_name 提升可读性):

class Category(models.Model):
    name = models.CharField(max_length=64)  # 建议字段名语义更清晰

    def __str__(self):
        return self.name

class ShopItem(models.Model):
    name = models.CharField(max_length=64)  # 同上,避免驼峰命名(PEP 8)
    price = models.IntegerField()
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        related_name='items'  # 关键!便于反向查询
    )

    def __str__(self):
        return self.name

然后,在视图中组织好“分类 → 商品列表”的嵌套结构:

# views.py
from django.shortcuts import render
from .models import Category

def shop_items_by_category(request):
    # 预加载每个分类下的所有商品,避免 N+1 查询
    categories = Category.objects.prefetch_related('items').all()

    # 可选:只取有商品的分类(更高效)
    # categories = Category.objects.filter(items__isnull=False).distinct().prefetch_related('items')

    return render(request, 'shop/shop_list.html', {
        'categories': categories
    })

✅ 模板中简洁渲染(无逻辑、无赋值、高性能)



  {% for category in categories %}
    
    {% for item in category.items.all %}
      
    {% endfor %}
  {% endfor %}
{{ category.name }}
{{ item.name }} ${{ item.price }}

⚠️ 注意事项与最佳实践

  • 禁止在模板中尝试模拟 Python 列表操作(如 used_category.append(...)),Django 模板语言不支持。
  • ✅ 使用 prefetch_related('items') 而非 select_related(),因为这是“一对多”关系(Category ← ShopItem)。
  • ✅ 若分类数量庞大但仅需展示有商品的分类,用 Category.objects.filter(items__isnull=False).distinct() 提升效率。
  • ✅ 在模型中定义 related_name 是良好实践,让 category.items.all 更直观、可读性更强。
  • ✅ 如需前端排序(如按价格降序),可在视图中使用 Prefetch 自定义查询集:
    from django.db.models import Prefetch
    categories = Category.objects.prefetch_related(
        Prefetch('items', queryset=ShopItem.objects.order_by('-price'))
    ).all()

通过将数据组织逻辑交由视图和 ORM 完成,你不仅解决了模板语法错误,还显著提升了性能、可维护性与代码可测试性——这才是 Django MVT 架构的正确打开方式。