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

如何在Django中为库存Item模型的每次实例更新创建历史记录?

使用Django实现库存更新历史记录的方案

当然可以完全用Django实现这个需求,根本不用写原生SQL触发器!我给你拆解几个实用的纯Django方案,维护起来也更顺手。

核心思路

先创建一个专门的历史记录模型,用来存储每次库存变更的细节,然后通过Django的内置机制(重写模型save方法或使用信号)自动触发记录操作,最后把历史记录开放给前端和后台管理。


1. 定义历史记录模型

首先创建ItemHistory模型,把你需要追踪的信息都存进去,比如关联的库存条目、操作类型、变更前后的数量、操作人、操作时间等:

from django.db import models
from django.contrib.auth.models import User
from .models import Item

class ItemHistory(models.Model):
    # 定义操作类型的可选值,方便后续筛选展示
    ACTION_CHOICES = [
        ('ISSUE', '发放'),
        ('RECEIVE', '接收'),
        ('ADJUST', '手动调整'),
    ]
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='history_records')
    # 操作人,允许为空(比如脚本自动更新的情况)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    action = models.CharField(max_length=10, choices=ACTION_CHOICES)
    old_amount = models.IntegerField(verbose_name='变更前库存')
    new_amount = models.IntegerField(verbose_name='变更后库存')
    # 记录本次操作的发放/接收数量,方便溯源
    issue_amount = models.IntegerField(default=0, verbose_name='本次发放数量')
    receive_amount = models.IntegerField(default=0, verbose_name='本次接收数量')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')

    def __str__(self):
        return f"{self.item} - {self.get_action_display()} | {self.created_at.strftime('%Y-%m-%d %H:%M')}"

2. 自动记录变更的两种实现方式

方式一:重写Item模型的save方法(简单直接)

如果你的库存更新都是通过调用Item实例的save()方法完成的,重写save方法是最直接的方式:

class Item(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    description = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='item')
    amount = models.IntegerField(default=0, blank=False)
    place = models.ForeignKey(Place, on_delete=models.CASCADE, related_name='place')
    issue_amount = models.IntegerField(default=0, blank=True)
    receive_amount = models.IntegerField(default=0, blank=True)

    def save(self, *args, **kwargs):
        # 获取更新前的库存数量(只有已存在的实例才有旧值)
        old_amount = self.amount if self.pk else 0
        # 先执行保存,拿到最新的库存值
        super().save(*args, **kwargs)
        # 只有库存数量变化时才记录历史
        new_amount = self.amount
        if old_amount != new_amount:
            # 根据发放/接收字段判断操作类型
            if self.issue_amount > 0:
                action_type = 'ISSUE'
            elif self.receive_amount > 0:
                action_type = 'RECEIVE'
            else:
                action_type = 'ADJUST'
            
            # 创建历史记录
            ItemHistory.objects.create(
                item=self,
                user=self.user,
                action=action_type,
                old_amount=old_amount,
                new_amount=new_amount,
                issue_amount=self.issue_amount,
                receive_amount=self.receive_amount
            )
            
            # 重置发放/接收的临时字段(如果不需要保留的话)
            self.issue_amount = 0
            self.receive_amount = 0
            super().save(update_fields=['issue_amount', 'receive_amount'])

方式二:使用Django信号(更通用灵活)

如果库存更新可能来自多个渠道(比如后台admin、API接口、定时脚本),用Django的post_save信号会更通用,能覆盖所有更新场景:

  1. 先在app目录下创建signals.py文件:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Item, ItemHistory

@receiver(post_save, sender=Item)
def log_item_update_history(sender, instance, created, **kwargs):
    # 只记录更新操作,忽略创建新条目
    if not created:
        # 获取更新前的旧数据
        old_instance = sender.objects.get(pk=instance.pk)
        old_amount = old_instance.amount
        new_amount = instance.amount

        if old_amount != new_amount:
            # 判断操作类型
            if instance.issue_amount > 0:
                action_type = 'ISSUE'
            elif instance.receive_amount > 0:
                action_type = 'RECEIVE'
            else:
                action_type = 'ADJUST'
            
            # 创建历史记录
            ItemHistory.objects.create(
                item=instance,
                user=instance.user,
                action=action_type,
                old_amount=old_amount,
                new_amount=new_amount,
                issue_amount=instance.issue_amount,
                receive_amount=instance.receive_amount
            )
            
            # 重置临时字段
            instance.issue_amount = 0
            instance.receive_amount = 0
            instance.save(update_fields=['issue_amount', 'receive_amount'])
  1. 在app的apps.py里注册信号:
from django.apps import AppConfig

class YourAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'your_app_name'

    def ready(self):
        # 导入信号使其生效
        import your_app_name.signals

3. 给前端和后台提供历史记录

后台管理集成

把ItemHistory注册到Django admin里,管理员可以直接查看和筛选历史:

from django.contrib import admin
from .models import Item, ItemHistory

# 定义历史记录的展示样式
class ItemHistoryAdmin(admin.ModelAdmin):
    list_display = ('item', 'get_action_display', 'old_amount', 'new_amount', 'user', 'created_at')
    list_filter = ('action', 'created_at', 'user')
    search_fields = ('item__description',)
    readonly_fields = ('item', 'action', 'old_amount', 'new_amount', 'user', 'created_at', 'issue_amount', 'receive_amount')

# 把历史记录作为内联组件添加到Item的admin页面,方便关联查看
class ItemHistoryInline(admin.TabularInline):
    model = ItemHistory
    extra = 0
    readonly_fields = ('action', 'old_amount', 'new_amount', 'user', 'created_at', 'issue_amount', 'receive_amount')

class ItemAdmin(admin.ModelAdmin):
    inlines = [ItemHistoryInline]
    # 其他Item的admin配置...

admin.site.register(Item, ItemAdmin)
admin.site.register(ItemHistory, ItemHistoryAdmin)

前端API接口

如果要给前端提供历史记录,可以用Django REST Framework写一个只读视图:

from rest_framework import viewsets
from .models import ItemHistory
from .serializers import ItemHistorySerializer

class ItemHistoryViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = ItemHistory.objects.all().order_by('-created_at')
    serializer_class = ItemHistorySerializer
    # 支持按库存条目过滤,比如/api/history/?item=1
    filterset_fields = ['item']

配置路由后,前端就能通过API获取到排序后的历史记录,还能按指定库存条目筛选。


注意事项

  • 性能优化:如果库存更新非常频繁,定期归档旧的历史记录可以避免数据库膨胀。
  • 用户字段准确性:如果是脚本或API自动更新,要确保user字段能正确赋值(可以设置为null);如果是后台admin操作,需要额外处理获取当前管理员的逻辑。
  • 扩展追踪字段:如果需要追踪place、description等其他字段的变更,只需在ItemHistory里添加对应的old_xxx和new_xxx字段,然后在记录逻辑里对比即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 01:12:46