如何修改Django代码实现跨3张表访问外键字段?
问题描述
我尝试从三张关联表中获取数据,最初使用以下代码:
cart_c = Cart.objects.select_related('item').filter(user=2) context = {'cart_c': cart_c}
得到QuerySet cart_c后,无法直接访问ItemPhoto.photo字段。于是改用prefetch_related关联三张表:
items = Item.objects.prefetch_related('item_photo', 'cart_item').filter(cart__user=2) context = {'items': items}
但现在只能通过items[0].cart_item.values()[0].get('quantity')这类繁琐的方式访问ItemPhoto.photo和Cart.quantity,在Django模板里用起来很不简洁。想知道怎么修改代码,传递合适的QuerySet或字典到模板,能便捷访问Item.name、Item.price、ItemPhoto.photo和Cart.quantity字段。
模型定义
class Item(models.Model): # 商品 name = models.CharField("商品名称", max_length=255, blank=False, default="") price = models.DecimalField("价格", max_digits=12, decimal_places=2, default=0.00, blank=False, ) class ItemPhoto(models.Model): # 商品图片 item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="item_photo", verbose_name="商品ID",) photo = models.ImageField("商品图片", blank=True, upload_to=get_itemimage_path) class Cart(models.Model): # 购物车 user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="用户", on_delete=models.CASCADE, null=False, related_name="cart_user",) item = models.ForeignKey(Item, verbose_name="商品ID", on_delete=models.CASCADE, related_name="cart_item", null=False,) quantity = models.PositiveSmallIntegerField("数量", default=0)
环境
Python 3.9 / Django 4.1
解决方案
方法一:从Cart模型出发,预取关联数据
直接以Cart为查询起点,用select_related获取关联的Item,再用prefetch_related预取Item对应的ItemPhoto,这样购物车的数量、商品信息、图片都能直接链式访问:
from django.db.models import Prefetch # 预取当前用户购物车对应的商品图片,并自定义属性名 cart_items = Cart.objects.select_related('item')\ .prefetch_related( Prefetch( 'item__item_photo', queryset=ItemPhoto.objects.all(), to_attr='photos' ) )\ .filter(user=2) context = {'cart_items': cart_items}
模板使用示例
模板中可直接循环购物车条目,链式访问各字段:
{% for cart in cart_items %} <div class="cart-item"> <p>商品名称:{{ cart.item.name }}</p> <p>价格:{{ cart.item.price }}</p> <p>数量:{{ cart.quantity }}</p> {% if cart.item.photos %} <img src="{{ cart.item.photos.0.photo.url }}" alt="{{ cart.item.name }}"> {% endif %} </div> {% endfor %}
方法二:从Item出发,精准预取当前用户的购物车记录
如果需要以Item为查询起点,可用Prefetch对象只筛选当前用户的购物车条目,避免获取无关数据,同时简化模板访问:
from django.db.models import Prefetch # 只预取当前用户的购物车条目,并自定义属性名 user_cart_prefetch = Prefetch( 'cart_item', queryset=Cart.objects.filter(user=2), to_attr='user_cart' ) items = Item.objects.prefetch_related('item_photo', user_cart_prefetch)\ .filter(cart__user=2) context = {'items': items}
模板使用示例
由于每个商品对应当前用户只有一条购物车记录,可直接通过user_cart.0访问数量:
{% for item in items %} <div class="item"> <p>商品名称:{{ item.name }}</p> <p>价格:{{ item.price }}</p> <p>数量:{{ item.user_cart.0.quantity }}</p> {% if item.item_photo %} <img src="{{ item.item_photo.0.photo.url }}" alt="{{ item.name }}"> {% endif %} </div> {% endfor %}
补充说明
- 使用
to_attr可以给预取的结果定义更直观的名称,比默认的管理器名更便于模板调用。 - 若一个商品对应多张图片,可通过
{% for photo in item.item_photo %}循环展示;仅需首张图片时,取item.item_photo.0即可。 - 优先从
Cart出发查询更贴合业务逻辑,因为我们要获取的是用户的购物车条目,每个条目天然关联商品和数量,数据结构更清晰。
内容的提问来源于stack exchange,提问作者K-Yuuma
相关产品推荐
相关产品推荐

