Django:如何在Vendor详情页展示关联的Product列表?
问题:Django供应商详情页关联产品列表展示需求
我需要在Vendor的基于类的详情视图中,同时展示该供应商的基本信息和其所有关联的Product列表。目前尝试用两个视图指向同一模板,但无法通过URL的PK筛选对应供应商的产品,求正确实现方案。
现有模型代码
from django.db import models import datetime import os class Vendor(models.Model): name = models.CharField(max_length=126, unique=True, blank=True, null=True) contact_number = models.CharField(max_length=126, blank=True, null=True) address = models.CharField(max_length=126, blank=True, null=True) email = models.EmailField(max_length=126, blank=True, null=True) is_active = models.BooleanField(default=True) def __str__(self): return self.name def productFilepath(instance, filename): year = datetime.now().strftime("%Y/") return os.path.join(instance.vendors.name, year, filename) class Product(models.Model): vendors = models.ForeignKey(Vendor, on_delete=models.CASCADE) collection = models.CharField(max_length=126, unique=True, blank=True, null=True) product = models.CharField(max_length=126, blank=True, null=True) size = models.CharField(max_length=126, blank=True, null=True) vendor_price = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True) retail_price = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True) sku = models.CharField(max_length=126, unique=True, blank=True, null=True) year = models.PositiveSmallIntegerField(blank=True, null=True) quarters_in_year = { 'q1': 'Q1', 'q2': 'Q2', 'q3': 'Q3', 'q4': 'Q4', } quarter = models.CharField(max_length=2, choices=quarters_in_year.items(), blank=True, null=True) image = models.FileField(upload_to=productFilepath, blank=True, null=True)
原视图与URL配置
视图代码
from django.views.generic import DetailView, ListView from .models import Vendor, Product class VendorDetailView(DetailView): model = Vendor template_name = 'deliveries/vendor_detail.html' class Productlist(ListView): model = Product template_name = 'deliveries/vendor_detail.html' def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs)
URL配置
from django.urls import path from . import views app_name = 'deliveries' urlpatterns = [ path('vendordetail/<int:pk>/', views.VendorDetailView.as_view(), name='vendordetail'), ]
原模板代码
{% extends "base.html" %} {% block style_block %} <style> #title { padding: 20px; } .add-button { background-color: #04AA6D; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; float: right; } </style> {% endblock %} {% block body_block %} <h1 id="title">{{ vendor.name }}</h1> <a href="{% url "deliveries:addproduct" %}"><button class="button add-button" type="button">Add Product</button></a> <ul> <li>{{ vendor.name }}</li> <li>{{ vendor.contact_number }}</li> <li>{{ vendor.email }}</li> <li>{{ vendor.address }}</li> </ul> <a href="{% url "deliveries:uploadproduct" %}"><button class="button add-button" type="button">Upload</button></a> <div> <h1>Products</h1> <table style="width:100%"> <tr> <th>SKU</th> <th>Collection</th> <th>Product Name</th> <th>Size</th> <th>Vendor Price</th> <th>Retail Price</th> </tr> {% for product in products %} <tr> <td>{{ product.sku }}</td> <td>{{ product.collection }}</td> <td>{{ product.product }}</td> <td>{{ product.size }}</td> <td>${{ product.retail_price }}</td> <td>${{ product.vendor_price }}</td> </tr> {% empty %} <li>No products yet.</li> {% endfor %} </table> </div> {% endblock %}
尝试过的无效方案
- 重写ListView的get方法,未实现筛选逻辑
def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs)
- 基于过滤的ListView实现,未正确关联供应商PK
class ProductList(ListView): model = Product template_name = 'deliveries/vendor_detail.html' context_object_name = 'products' def get_queryset(self): pk = self.kwargs['pk'] queryset = super().get_queryset() self.filterset = ProductFilter(self.request.GET, queryset=queryset) return self.filterset.qs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form'] = self.filterset.form return context
正确解决方案
不需要使用两个视图指向同一模板,单个VendorDetailView即可完成需求,核心是在详情视图中把关联产品列表注入到模板上下文:
修改后的视图代码
class VendorDetailView(DetailView): model = Vendor template_name = 'deliveries/vendor_detail.html' context_object_name = 'vendor' # 显式指定上下文变量名,可选,默认是object def get_context_data(self, **kwargs): # 获取父类生成的基础上下文 context = super().get_context_data(**kwargs) # 获取当前详情页的供应商实例 current_vendor = self.get_object() # 通过反向关联获取该供应商的所有产品,添加到上下文 context['products'] = current_vendor.product_set.all() return context
优化建议:规范外键命名与关联
为了代码更直观,建议修改Product模型的外键字段名,并设置related_name:
# Product模型中外键字段修改如下 vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, related_name='products')
修改后,视图中获取产品列表的代码可简化为:
context['products'] = current_vendor.products.all()
说明
- 每个HTTP请求只会触发一个视图,双视图指向同一模板的思路不成立,因为另一个视图不会被调用,模板无法获取对应上下文变量。
- 通过
DetailView的get_object()方法可以直接拿到当前供应商实例,利用Django的反向关联(模型名_set或自定义related_name)即可获取该供应商的所有产品。 - 模板无需修改,原有的
{% for product in products %}遍历逻辑可直接使用。
内容的提问来源于stack exchange,提问作者Gio
相关产品推荐
相关产品推荐

