Django Admin实现站点-商品-价格关联及内联价格编辑
Django Admin 多站点商品分价实现方案
模型层修改(models.py)
直接用原生ManyToManyField关联Site和Item的方式无法存储「同商品在不同站点售价不同」的差异化数据,需要替换为带中间表的多对多关系,把价格字段存在中间关联表中:
- 保留原有Department、Item模型逻辑不变
- 新增
SiteItem中间表,外键分别关联Site和Item,额外增加price字段存储对应站点的商品售价,增加联合唯一约束避免同站点同商品重复建档 - 修改Site模型的
items字段,指定through参数指向新建的中间表
修改后的完整models.py代码:
from django.db import models class Department(models.Model): number = models.IntegerField(unique=True) description = models.CharField(max_length=30) def __str__(self): return f"{self.number}: {self.description}" class Meta: ordering = ["number"] class Item(models.Model): upc = models.CharField(max_length=30, unique=True, verbose_name="UPC") description = models.CharField(max_length=30) department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True) def __str__(self): return f"{self.upc}: {self.description}" class Meta: ordering = ["upc"] # 站点-商品关联中间表,存储对应站点的商品专属售价 class SiteItem(models.Model): site = models.ForeignKey("Site", on_delete=models.CASCADE, related_name="site_items") item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="site_items") price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="售价(美元)") class Meta: # 约束同一站点下同个商品仅能存在一条价格记录 constraints = [ models.UniqueConstraint(fields=["site", "item"], name="unique_site_item_price") ] ordering = ["item__upc"] def __str__(self): return f"{self.site} - {self.item}: ${self.price}" class Site(models.Model): site = models.IntegerField(unique=True) address = models.CharField(max_length=50) # 指定through参数关联自定义中间表 items = models.ManyToManyField(Item, through=SiteItem, related_name="sites") def __str__(self): return str(self.site) class Meta: ordering = ["site"]
修改完模型后依次执行
python manage.py makemigrations、python manage.py migrate同步数据库结构。如果之前已经存在Site和Item的关联存量数据,迁移时会提示给price字段设置临时默认值,输入0即可,迁移完成后再到后台修正实际售价。
后台配置修改(admin.py)
替换原来的内联类为基于中间表SiteItem的TabularInline,把商品基础信息(UPC、描述、所属部门)配置为只读列,价格字段设为可编辑列,就能实现「商品描述右侧直接修改价格」的内联效果,完全不需要自定义模板。
修改后的完整admin.py代码:
from django.contrib import admin from .models import Site, Department, Item, SiteItem class SiteItemInline(admin.TabularInline): model = SiteItem can_delete = False verbose_name = '站点在售商品' verbose_name_plural = '站点在售商品' extra = 0 # 按展示顺序配置列:商品选择器、只读商品属性、可编辑价格 fields = ("item", "item_upc", "item_description", "item_department", "price") # 商品基础信息设为只读,避免在站点编辑页误改全局商品属性 readonly_fields = ("item_upc", "item_description", "item_department") # 取关联商品的UPC字段 def item_upc(self, obj): return obj.item.upc item_upc.short_description = "UPC" # 取关联商品的描述字段 def item_description(self, obj): return obj.item.description item_description.short_description = "商品描述" # 取关联商品的所属部门字段 def item_department(self, obj): return obj.item.department item_department.short_description = "所属部门" # 保留原有配置,禁用商品选择框的关联新增/修改/删除按钮 def formfield_for_dbfield(self, db_field, request, **kwargs): formfield = super().formfield_for_dbfield(db_field, request, **kwargs) if db_field.name == "item": formfield.widget.can_delete_related = False formfield.widget.can_change_related = False formfield.widget.can_add_related = False return formfield @admin.register(Site) class SiteAdmin(admin.ModelAdmin): fields = ("site", "address") list_display = ("site", "address") inlines = (SiteItemInline, ) @admin.register(Department) class DepartmentAdmin(admin.ModelAdmin): fields = ("number", "description") list_display = ("number", "description") @admin.register(Item) class ItemAdmin(admin.ModelAdmin): fields = ("upc", "description", "department") list_display = ("upc", "description", "department") save_as = True def formfield_for_dbfield(self, *args, **kwargs): formfield = super().formfield_for_dbfield(*args, **kwargs) formfield.widget.can_delete_related = False formfield.widget.can_change_related = False formfield.widget.can_add_related = False return formfield
配置完成后打开站点编辑页,内联表格会逐行展示该站点所有在售商品,价格列支持直接输入修改,保存后数据会直接写入中间表,和参考截图的交互效果完全一致。
内容的提问来源于stack exchange,提问作者p98376
相关产品推荐
相关产品推荐

