Django基于类的通用ListView如何使用多个关联模型
实现方案
你不需要单独把多个关联模型的数据手动塞进上下文,这些模型都通过外键和Product关联,直接在Product的查询集上预加载关联数据即可。另外你当前视图里手写的分页逻辑是冗余的:ListView本身已经内置了分页处理,设置paginate_by参数后会自动完成分页逻辑,不需要重复实例化Paginator。
第一步:修改视图,预加载关联数据
重写get_queryset方法,用prefetch_related预加载一对多的关联数据,避免循环取数据时产生N+1查询问题,优化后的视图代码如下:
from django.views import generic from .models import Product class ProductListView(generic.ListView): model = Product template_name = 'products/list.html' context_object_name = 'products' paginate_by = 2 def get_queryset(self): # 预加载所有关联数据,查询时一次性捞出关联表数据,避免N+1性能问题 return super().get_queryset().prefetch_related( 'productimage_set', # 关联商品图 'productvariant_set', # 关联商品规格 'productvariantprice_set' # 关联规格对应的价格库存 ) # 原有手写的分页逻辑可全部删除,ListView会自动处理分页、传分页对象到模板
如果需要对关联数据做过滤/排序,比如只取启用的规格、按顺序展示商品图,可以用
Prefetch对象自定义关联查询集,示例:from django.db.models import Prefetch def get_queryset(self): return super().get_queryset().prefetch_related( Prefetch( 'productimage_set', queryset=ProductImage.objects.all().order_by('id') ), Prefetch( 'productvariant_set', # 额外预加载规格关联的Variant主表,过滤未启用的规格项 queryset=ProductVariant.objects.select_related('variant').filter(variant__active=True) ), 'productvariantprice_set' )
第二步:模板中直接调用关联数据
预加载完成后,在模板里循环商品列表时,直接通过Django默认的反向关联关系就能取到对应的数据,示例写法:
{% for product in products %} <div class="product-item"> <h3>{{ product.title }}</h3> <p>SKU: {{ product.sku }}</p> <p>{{ product.description }}</p> <!-- 渲染商品图片 --> <div class="product-images"> {% for img in product.productimage_set.all %} <img src="{{ img.file_path }}" alt="{{ product.title }}"> {% endfor %} </div> <!-- 渲染商品规格 --> <div class="product-variants"> {% for pv in product.productvariant_set.all %} <span>{{ pv.variant.title }}: {{ pv.variant_title }}</span> {% endfor %} </div> <!-- 渲染规格对应的价格和库存 --> <div class="product-stock-price"> {% for pvp in product.productvariantprice_set.all %} <p>售价:{{ pvp.price }} 元,库存:{{ pvp.stock }}</p> {% endfor %} </div> </div> {% endfor %} <!-- 分页器直接用ListView自动传入的page_obj即可,不需要手动构造分页对象 --> <div class="pagination"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}">上一页</a> {% endif %} <span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span> {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}">下一页</a> {% endif %} </div>
补充说明
- 如果你觉得默认的反向关联名(比如
productimage_set)不够直观,可以在模型外键字段定义时加上related_name参数,比如给ProductImage的product外键加related_name="images",之后预加载和模板调用的时候直接用images代替productimage_set即可,语义更清晰。 - 所有关联数据通过ORM预加载完成后,模板调用时不会再额外发起SQL查询,性能比单独查询其他模型再手动匹配商品ID高很多,也不需要写额外的数据匹配逻辑。
内容的提问来源于stack exchange,提问作者Md.Muntasir Mamun
相关产品推荐
相关产品推荐

