如何在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信号会更通用,能覆盖所有更新场景:
- 先在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'])
- 在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
相关产品推荐
相关产品推荐

