
本文介绍如何优化 django 模型关系设计,并通过 prefetch_related 与 prefetch 高效查询全部商品,同时为每个商品动态标注其是否已加入当前用户的心愿单(或购物车),避免 n+1 查询问题。
本文介绍如何优化 django 模型关系设计,并通过 prefetch_related 与 prefetch 高效查询全部商品,同时为每个商品动态标注其是否已加入当前用户的心愿单(或购物车),避免 n+1 查询问题。
在实际电商类应用中,常需渲染商品列表页(如首页、分类页),并在每个商品卡片上显示“已收藏”或“已加入购物车”状态。若按传统方式对每个商品单独查询心愿单,将触发严重性能问题(N+1 查询)。为此,必须从模型设计和查询优化两方面协同解决。
✅ 第一步:修正模型关系设计
原始代码中 wishlist 模型使用 ForeignKey(Product),导致一个心愿单只能关联一个商品——这违背业务逻辑(心愿单应可容纳多个商品)。正确做法是采用 多对多关系(ManyToManyField):
# models.py
class Product(models.Model):
code = models.CharField(max_length=50, null=True, blank=True, unique=True)
name = models.CharField(max_length=100)
category_id = models.ForeignKey('Category', on_delete=models.CASCADE) # 注意引用名大小写
size_id = models.ForeignKey('Size', on_delete=models.CASCADE)
cost = models.IntegerField()
tax_id = models.ForeignKey('Tax', on_delete=models.CASCADE)
price = models.IntegerField()
class Wishlist(models.Model): # 类名建议 PascalCase
products = models.ManyToManyField(Product, related_name='wishlists') # 反向关系更清晰
user = models.ForeignKey('Account', on_delete=models.CASCADE, related_name='wishlists')
created_at = models.DateTimeField(auto_now_add=True) # 字段名推荐 snake_case
⚠️ 注意事项:
- related_name 是关键!它定义了从 Product 反向访问心愿单的属性名(如 product.wishlists.all());
- 若需支持同一用户多个心愿单(如“旅行清单”“生日礼物”),保留 Wishlist 模型并用 ManyToManyField;若仅需“单个全局心愿单”,可直接在 Account 模型中定义 wishlisted_products = models.ManyToManyField(Product, blank=True),更简洁。
✅ 第二步:高效查询 + 状态标注
假设当前登录用户为 request.user,目标是获取全部商品,并为每个商品添加 .in_wishlist 和 .in_cart 布尔属性:
# views.py 或 service logic
from django.db.models import Prefetch
from django.contrib.auth.models import AnonymousUser
def get_product_list_with_status(user):
if isinstance(user, AnonymousUser):
# 未登录用户:所有 in_wishlist/in_cart = False
products = Product.objects.all()
for p in products:
p.in_wishlist = False
p.in_cart = False
return products
# 已登录用户:预取关联数据
wishlist_prefetch = Prefetch(
'wishlists',
queryset=Wishlist.objects.filter(user=user),
to_attr='user_wishlists'
)
cart_prefetch = Prefetch(
'cart_set', # 假设 Cart 模型中定义了 ForeignKey(Product, related_name='cart_set')
queryset=Cart.objects.filter(user=user),
to_attr='user_carts'
)
products = Product.objects.prefetch_related(wishlist_prefetch, cart_prefetch)
# 动态添加状态属性(惰性计算,不修改数据库)
for product in products:
product.in_wishlist = bool(getattr(product, 'user_wishlists', []))
product.in_cart = bool(getattr(product, 'user_carts', []))
return products
# 在视图中调用
products = get_product_list_with_status(request.user)
✅ 第三步:模板中使用(Django Template)
<!-- product_list.html -->
{% for product in products %}
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>¥{{ product.price }}</p>
{% if product.in_wishlist %}
<span class="badge">★ 已收藏</span>
<button onclick="removeFromWishlist({{ product.id }})">取消收藏</button>
{% else %}
<button onclick="addToWishlist({{ product.id }})">❤ 收藏</button>
{% endif %}
{% if product.in_cart %}
<span class="badge">? 已在购物车</span>
{% else %}
<button onclick="addToCart({{ product.id }})">加入购物车</button>
{% endif %}
</div>
{% endfor %}
? 总结与最佳实践
- 模型即契约:错误的外键方向会从根本上限制功能扩展,务必根据真实业务关系选择 ForeignKey / ManyToManyField / OneToOneField;
- Prefetch 是性能关键:相比 select_related(用于 ForeignKey/OneToOne 正向查询),Prefetch 更适合处理反向多对多或多对一关系,且支持自定义 queryset 和 to_attr;
- 状态属性应惰性注入:避免在模型中硬编码业务状态字段(如 is_in_wishlist),而应在查询层动态附加,保持模型纯净;
- 考虑缓存与分页:对海量商品,结合 django.core.paginator 分页,并为高频访问的“用户心愿单 ID 集合”添加 Redis 缓存(如 user:{id}:wishlist_ids),进一步提升响应速度。
通过以上重构,你将获得可扩展、高性能、易维护的商品列表状态查询方案。











