You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django:如何在模型方法中跨模型取数并在Admin展示库存

Django Admin 实现跨模型库存汇总表格方案

嘿,作为Django新手能自己摸索到Admin定制这一步已经很棒啦!针对你想要在Admin里新增库存汇总表格的需求,其实不用特意创建一个空模型——这里有两种实用的方案,你可以根据自己的需求选择:

方案一:扩展现有GoodsItem的Admin页面(最简单)

既然你已经在Admin里注册了GoodsItem,直接给它的Admin配置添加一个自定义字段来展示可用库存就好,不用额外创建新模型。

步骤:修改你的admin.py

from django.contrib import admin
from .models import GoodsItem, FinishedGoodsItem, SoldGoodsItem

class GoodsItemAdmin(admin.ModelAdmin):
    # 把自定义的可用库存字段加入展示列表
    list_display = ('__str__', 'available_stock')

    def available_stock(self, obj):
        # 计算当前GoodsItem对应的所有Finished记录的总重量
        # 用aggregate求和,没有记录时返回0避免报错
        finished_total = obj.finished_name.aggregate(total=models.Sum('weight'))['total'] or 0
        # 计算当前GoodsItem对应的所有Sold记录的总重量
        sold_total = obj.sold_name.aggregate(total=models.Sum('weight'))['total'] or 0
        # 返回可用库存:完成总量 - 销售总量
        return finished_total - sold_total
    
    # 设置表格列的显示标题
    available_stock.short_description = '可用库存'
    # 如果需要支持排序,可以加上这行(方案二排序更高效)
    # available_stock.admin_order_field = 'available_stock'

# 注册时绑定自定义的Admin类
admin.site.register(GoodsItem, GoodsItemAdmin)
admin.site.register(FinishedGoodsItem)
admin.site.register(SoldGoodsItem)

关键说明:

  • obj.finished_name是你在FinishedGoodsItem里设置的related_name="finished_name",通过它可以直接反向查询当前GoodsItem对应的所有完成记录
  • aggregate是Django ORM的聚合函数,用来批量计算总和,or 0是为了处理没有对应记录的情况(避免返回None导致减法报错)

方案二:创建独立的库存汇总Admin页面(更清晰)

如果你想要一个单独的Admin页面展示汇总数据,不和GoodsItem的列表混在一起,可以创建一个虚拟模型(不生成数据库表),然后绑定自定义的Admin逻辑。

步骤1:在models.py里添加虚拟模型

from django.db import models

class StockSummary(models.Model):
    class Meta:
        managed = False  # 告诉Django不要为这个模型创建数据库表
        verbose_name = '库存汇总表'
        verbose_name_plural = '库存汇总表'

步骤2:在admin.py里注册并配置这个模型

from django.contrib import admin
from django.db.models import Sum, F
from .models import StockSummary, GoodsItem

class StockSummaryAdmin(admin.ModelAdmin):
    list_display = ('goods_item', 'available_stock')
    
    # 禁止添加、修改、删除操作(因为这是只读统计)
    actions = None
    def has_add_permission(self, request):
        return False
    def has_change_permission(self, request, obj=None):
        return False
    def has_delete_permission(self, request, obj=None):
        return False

    def get_queryset(self, request):
        # 用annotate批量计算每个GoodsItem的库存,性能更优(避免N+1查询)
        return GoodsItem.objects.annotate(
            # 计算完成总重量,没有记录时默认0
            finished_total=Sum('finished_name__weight', default=0),
            # 计算销售总重量,没有记录时默认0
            sold_total=Sum('sold_name__weight', default=0)
        ).annotate(
            # 用F表达式直接在数据库层面计算差值
            available_stock=F('finished_total') - F('sold_total')
        )

    def goods_item(self, obj):
        # 展示GoodsItem的字符串表示
        return str(obj)
    goods_item.short_description = 'GoodsItem'

admin.site.register(StockSummary, StockSummaryAdmin)

关键说明:

  • managed=False让Django不为这个模型创建数据库表,只是用来给Admin做展示载体
  • annotate是批量给每个GoodsItem添加统计字段,比方案一的每条单独查询更高效,数据量大时差异明显
  • F表达式用来直接在数据库层面计算差值,避免把数据拉到Python层面再计算,性能更好

额外提示(按日期过滤需求)

如果你需要按“截至最后一条记录日期”来计算(而不是所有记录),可以在聚合时添加过滤条件,比如:

from django.db.models import Q

# 假设要取每个GoodsItem对应的Finished记录中最新日期的所有记录总和
finished_latest_date = obj.finished_name.aggregate(max_date=models.Max('date'))['max_date']
finished_total = obj.finished_name.filter(date=finished_latest_date).aggregate(total=models.Sum('weight'))['total'] or 0

你可以根据自己的具体逻辑调整这个过滤条件。

内容的提问来源于stack exchange,提问作者rmbits

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:07:36