Django 实现保存前备份原记录数据

发布于 2024-06-23 07:51:04 字数 9148 浏览 15 评论 0

在一些场合,比如台账管理,因为多人维护同一套数据,为了确保修改后可追溯,或者可以查看历史修改,我们需要在数据保存前先保存原记录的数据

在这之前考虑的想法是在 modelsave() 里来实现功能,在实际的保存 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 信息实现记录保存前先备份

这里涉及两个应用 devicelog ,以及 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

信号接收器定义好后,我们还应把接收器放到 appsready() 里以便在应用准备好时加载它。

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

当然好处不止这些,这只是我在这个应用过程是发现的,后面还会发现更多信号处理机制的好。

DjangoModels 预先定义的信号(或者说是事件通知)有:

  • 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

热血少△年

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

我们的影子

文章 0 评论 0

素年丶

文章 0 评论 0

南笙

文章 0 评论 0

18215568913

文章 0 评论 0

qq_xk7Ean

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文