Django 实现保存前备份原记录数据
在一些场合,比如台账管理,因为多人维护同一套数据,为了确保修改后可追溯,或者可以查看历史修改,我们需要在数据保存前先保存原记录的数据
在这之前考虑的想法是在 model
的 save()
里来实现功能,在实际的保存 super().save()
前先保存,
后来测试时发现保存的是修改后的数据。
class DeviceInfo(models.Model):
...
def save(self, *args, **kwargs):
# 记录保存前先备份到 Log 表
if self.pk is not None:
save_history([self])
super().save(*args, **kwargs)
此方法行不通。
save_history()
后面会说到
后来发现可以 pre_save
,但是要以信号的形式来做,经过一番测试,终于实现了 pre_save
的方式,
下面给出整个的实现过程。
在这里我们以一个设备台账应用来实现保存前备份记录的功能
pre_save
信息实现记录保存前先备份
这里涉及两个应用 device
和 log
,以及 pre_save
信号的应用。
模型 | 模型描述 | 说明 |
---|---|---|
device | 设备管理 | 用于设备台账管理等 |
log | 后台记录 | 用于保存记录备份后、台日志等 |
一、 log
应用
1. History
历史记录模型
文件: log/models.py
from django.db import models
# Create your models here.
class History(models.Model):
"""数据历史记录"""
model = models.CharField('数据模型', max_length=50)
pkey = models.IntegerField('记录号')
json = models.TextField('JSON 数据')
log_time = models.DateTimeField('记录时间', auto_now_add=True)
class Meta:
verbose_name_plural = verbose_name = '历史记录'
def __str__(self):
return '%s(%s)' % (self.model, self.pkey)
2. History
管理后台
文件: log/admin.py
from django.contrib import admin
from .models import *
# Register your models here.
@admin.register(History)
class HistoryAdmin(admin.ModelAdmin):
pass
3. History
历史记录备份功能实现
文件: log/utils.py
import json
from django.core import serializers
from .models import History
def save_history(queryset):
"""保存查询集的记录到 Log 表中。
每一个查询集记录对应保存到 Log 的一个记录,好方便恢复时
"""
for rec in queryset:
se = json.dumps(json.loads(serializers.serialize('json', [rec])), ensure_ascii=False)
js = json.loads(se)[0]
model = js['model']
pkey = js['pk']
History.objects.create(model=model, pkey=pkey, json=se)
utils.py
里定义了一个 save_history()
函数,
函数:save_history()
参数:一个查询集 queryset
功能:将查询集内的所有记录以 json
格式逐个保存到 History
历史记录表
json.loads():把
json
文本转为json
对象,是一个list
json.dumps():把json
对象转为json
文本ensure_ascii=False
实现了中文正常显示
二、 Device
应用
1. DeviceInfo
设备台账模型
文件: device/models.py
import json
from django.core import serializers
from django.db import models
# Create your models here.
class DeviceInfo(models.Model):
"""显示设备的基本信息,这些信息是跟随设备的相对固定的。"""
name = models.CharField('设备名称', max_length=50)
brand = models.CharField('品牌', max_length=50)
model = models.CharField('型号', max_length=50)
production_date = models.DateField('生产日期')
record_date = models.DateTimeField('登记时间', auto_created=True)
...
class Meta:
verbose_name_plural = verbose_name = '设备基本信息'
def __str__(self):
return self.name
2. DeviceInfo
管理后台
文件: device/admin.py
from django.contrib import admin
from .models import *
# Register your models here.
@admin.register(DeviceInfo)
class DeviceInfoAdmin(admin.ModelAdmin):
pass
3. device
信号集
文件: device/signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import *
from log.utils import save_history
@receiver(pre_save, sender=DeviceInfo)
def save_history_device_info(sender, instance, **kwargs):
qs = sender.objects.filter(pk=instance.pk)
if qs is not None:
save_history(qs)
=================
这里是本文的重头戏了
=================
@receiver
信号接收器:这个装饰器定义了当接收到由 DeviceInfo
发送过来的 pre_save
事件通知时所要做的处理,
装饰器下面就是处理函数。
这个函数的第一个参数是 sender
,其后就是 pre_save
可接收的参数:”instance”, “raw”, “using”, “update_fields”。
这里我们只用到 instance
,后面的参数交给了 **kwargs
。
这时如果还有一个 model
模型 DeviceMore
也要在 pre_save
时做同样的备份操作,
那只要再加个接收器装饰器就行了。
这也达到的代码复用的效果。
@receiver(pre_save, sender=DeviceInfo)
@receiver(pre_save, sender=DeviceMore)
def save_history_device_info(sender, instance, **kwargs):
所有这个应用的信号处理都可以放在这个文件中来处理。
4. device
应用设置
文件: device/apps.py
from django.apps import AppConfig
class DeviceConfig(AppConfig):
name = 'device'
verbose_name = '设备管理'
def ready(self):
import device.signals
信号接收器定义好后,我们还应把接收器放到 apps
的 ready()
里以便在应用准备好时加载它。
5. device
应用头文件设置
文件: device/__ini__.py
default_app_config = 'device.apps.DeviceConfig'
在应用的头文件里我们设置了默认了应用设置项,然后在 settings.py
里我们像往常一样添加应用就可以了。
INSTALLED_APPS = [
...
'device',
]
现在在后台的 设备管理
里添加一个 设备基本信息
台账,然后查看一下 后台记录
的 历史记录
,
看看每次保存时是不是有都有在 历史记录
里添加了记录。
当然,首次添加台账时是不会有
历史记录
增加的。
三、关于 Django
里的信号
signals
信号在 Django
有广泛的应用,
信号处理机制好处是:
- 不改变原应用的功能代码,只需定义接收器
receiver
在收到需要的信号时插入需要处理过程 - 同一个信号
signals
可以有多个接收器receiver
接收,即多个处理过程 - 一个接收器
receiver
可以接收多个信号signals
- 可以自定义信号
signals
示例:
# 多个接收器可以共用同一个处理过程
@receiver(signal=pre_save, sender=DeviceInfo)
@receiver(signal=pre_save, sender=DeviceMore)
def save_history_device_info(sender, instance, **kwargs):
qs = sender.objects.filter(pk=instance.pk)
if qs is not None:
save_history(qs)
# 同一个信号可以有多个处理过程
@receiver(signal=pre_save, sender=DeviceInfo)
@receiver(signal=pre_save, sender=DeviceMore)
def print_info(sender, instance, **kwargs):
print('pre_save', sender, instance.pk)
# 同一个接收器里可以接收多个信息
@receiver(signal=[pre_save, pre_delete], sender=DeviceInfo)
def before_modify(sender, instance, **kwargs):
pass
当然好处不止这些,这只是我在这个应用过程是发现的,后面还会发现更多信号处理机制的好。
Django
的 Models
预先定义的信号(或者说是事件通知)有:
pre_init
:实例初始化前,可选参数:”instance”, “args”, “kwargs”,使用缓存post_init
:实例初始化后,可选参数:”instance”,使用缓存)pre_save
:数据保存前,可选参数:”instance”, “raw”, “using”, “update_fields”,使用缓存post_save
:数据保存后,可选参数:”instance”, “raw”, “created”, “using”, “update_fields”,使用缓存pre_delete
:数据删除前,可选参数:”instance”, “using”,使用缓存post_delete
:数据删除前后,可选参数:”instance”, “using”,使用缓存m2m_changed
:多对多数据改变时,可选参数:”action”, “instance”, “reverse”, “model”, “pk_set”, “using”,使用缓存pre_migrate
:数据库重整前,可选参数:”app_config”, “verbosity”, “interactive”, “using”post_migrate
:数据库重整后,可选参数:”app_config”, “verbosity”, “interactive”, “using”
四、附言
参考:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Django 权限机制的实现
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论