
最近在整理《Django实战2-自动化运维平台之配置管理》系列文档,配置管理是自动化运维的基础架构,包含IT架构中设备的基础信息管理。在设备管理中实现了记录设备变更的历史数据,包含Create、Update、Delete操作。这套文档还未正式对外发布,所以本节文档将会通过一个实例来介绍具体实现过程。在项目中经常会碰到需要记录ORM操作的历史纪录的需求。本节使用单独一节文档来介绍历史纪录的具体实现,可记录ORM模型的create、update、delete操作。可进行历史纪录差异化对比,可使用历史纪录还原数据实例。
文档直接从新建Django项目开始,项目中使用到的环境是 python3.6.2 , django-2.1.2,如果不清楚环境的搭建方法可以参考我之前的一篇文档:
Django实战1-权限管理功能实现-01:搭建开发环境
https://zhuanlan.zhihu.com/p/48419374
1 设备管理历史纪录功能展示
《Django实战2-自动化运维平台之配置管理》中实现的日志功能效果如下:

接下来我们就通过一个测试项目来介绍ORM历史纪录的具体实现。
2 ORM日志记录实现
在创建项目前,先在项目运行的python环境中安装依赖包:
(sandboxtest) C:UsersRobbieHan>pip install django
(sandboxtest) C:UsersRobbieHan>pip install django-simple-history
(sandboxtest) C:UsersRobbieHan>pip install ipython
其中django-simple-history是用来记录历史纪录的模块。
2.1 创建项目
打开Pycharm工具,选择File→New Project 在弹出窗口左侧选择Django,在Location选项下设置项目存放路径

展开 Project Interpreter: 选择Exisiting interperter,点后面的设置按钮,选择Add Local

在新的弹窗左侧,选择Virtualenv Environment, 右侧Interpreter中指定python虚拟环境路径,虚拟环境默认存放在用户目录下的Envs目录. 我的创建的虚拟环境路径是:C:UsersRobbieHanEnvssandboxtestScriptspython.exe

完成以上设置后,选择【Create】创建项目,项目创建完成后,点击pycharm右上角运行按钮运行项目,测试项目运行正常,可以访问django欢迎页面。
2.2 修改显示语言和时区
打开sandboxtest/sandboxtest/settings.py文件,找到LANGUAGE_CODE 修改如下:
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
2.3 创建资产管理APP
选择pycharm上方Tools,点击Run manage.py Task..., 这时在pycharm下方会打开一个窗口,输入startapp asset 回车创建资产管理app。
修改配置文件
打开sandboxtest/sandboxtest/settings.py文件 将simple_history 和 asset 添加到INSTALLED_APP ,同时将simple_history.middleware.HistoryRequestMiddleware添加到MIDDLEWARE
INSTALLED_APPS = [
# ...
'simple_history',
'asset',
]
# ...
MIDDLEWARE = [
# ...
'simple_history.middleware.HistoryRequestMiddleware',
]
2.4 创建资产管理模型
打开sandboxtest/asset/models.py,添加如下内容:
from django.db import models
from simple_history.models import HistoricalRecords
class Asset(models.Model):
number = models.CharField(max_length=100, verbose_name='资产编号')
type = models.CharField(max_length=100, verbose_name='资产类型')
ip_address = models.CharField(max_length=100, verbose_name='IP地址')
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
modify_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
changed_by = models.ForeignKey('auth.User', null=True, blank=True, on_delete=models.SET_NULL,)
history = HistoricalRecords(excluded_fields=['add_time', 'modify_time'])
class Meta:
verbose_name = '资产管理'
verbose_name_plural = verbose_name
def __str__(self):
return self.number
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
self.changed_by = value
执行makemigrations 和 migrate来生成数据表, 使用pycharm Tools,点击Run manage.py Task..., 在manage.py窗口输入下面命令:
makemigrations
migrate
使用Navicat 客户端工具打开sandboxtest/db.sqlite3数据库文件,查看生成的数据库表:


知识点介绍:
1、在创建asset模型时定义了一个history字段,创建了一个实例simple_history.models.HistoricalRecords,用来跟踪记录asset模型中数据实例的所有更改;
2、在history字段中使用了exclude_fields 用来排除不做操作记录的字段;
3、asset模型中创建了一个changed_by用来记录上次更改模型的用户,并通过_history_user来引用changed_by字段属性;
4、asset模型使用了记录实例,生成数据库文件的时候将会在数据库中创建一个日志文件asset_historicalasset(如上面所示),这个表的命名规则是[app_name]_historical[model_name]。比如还有一个asset app下还有一个模型叫做 device也配置了日志记录实例,则数据库中会多处一个日志表asset_historicaldevice;
5、查看上面的图片对别两张表中的数据,日志表中记录asset_asset表中被排除的字段之外的字段内容,当我们对数据做create,update,create操作时,这些数据都会被写入日志表;
6、当asset_asset中的实例数据被删除时,日志表中的数据还是保存的,可以通过日志来进行数据恢复;
7、如果想要在删除asset实例数据时,同时删除对应日志数据,只需要在history字段中配置cascade_delete_history=True。
2.4 将模型注册到后台
我们将使用djang admin后台管理来操作数据,生成数据操作日志信息。
打开sandboxtest/asset/admin.py,输入如下内容:
from django.contrib import admin
from .models import Asset
class AssetAdmin(admin.ModelAdmin):
list_display = ['number', 'type', 'ip_address', 'add_time', 'changed_by']
admin.site.register(Asset, AssetAdmin)
使用pycharm Tools,点击Run manage.py Task..., 在manage.py窗口输入下面命令来创建管理员用户:
manage.py@sandboxtest > createsuperuser
用户名 (leave blank to use 'robbiehan'): admin
电子邮件地址: robbie_han@outlook.com
Warning: Password input may be echoed.
Password: !qaz@wsx
Warning: Password input may be echoed.
Password (again): !qaz@wsx
Superuser created successfully.
Following files were affected
manage.py@sandboxtest >
运行项目访问后台管理页面,使用刚刚创建的用户登陆系统:
http://127.0.0.1:8000/admin/
可以看到系统中admin后台已经可以显示 资产管理 ,点击【新增】添加一条数据,保存数据后,可以在 资产管理页面选择这条数据查看详情,修改数据。

在数据详情修改页面,点击右上角的【历史】按钮,可以查看历史纪录(现在查看的是django admin自带的日志信息)

2.5 在Django Admin中使用simple_history
在2.4中我们将模型注册到后台,使用的是django admin默认的历史纪录,接下来将介绍如何在Django Admin中集成simple_history。
打开sandboxtest/asset/admin.py,注销掉原来的代码,添加如下内容:
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Asset
class AssetHistoryAdmin(SimpleHistoryAdmin):
list_display = ['number', 'type', 'ip_address', 'changed_by']
admin.site.register(Asset, AssetHistoryAdmin)
知识点介绍: 上面使用simple_history.admin.SimpleHistoryAdmin作为注册模型的继承类,将simple_history集成到Django Admin。
运行项目,访问后台管理页面刚才添加的数据,你可以对数据做些修改,比如修改资产类型,修改IP地址,保存后查看 历史:

通过点击历史记录中的OBJECT可以查看当前历史纪录的内容:

通过点击【REVERT】按钮,可以将历史纪录恢复到当前数据。
2.6 历史纪录的查询
在目中我们需要通过视图来展示历史纪录,接下来介绍下历史纪录的查询和数据的差异化对比,通过这些操作你可以获取想要的历史纪录,然后通过视图传递给模板来进行展示。
在Pycharm Terminal 终端中启用Django shell:
(sandboxtest) D:ProjectFilesandboxtest>python manage.py shell
Python 3.6.2 (v3.6.2:5fd33b5, Jul 8 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
以下操作都将在在Pycharm Terminal终端的Django shell下完成。
查询所有历史纪录:
In [1]: from asset.models import Asset
# 需要确保刚才通过django admin创建的数据还在,不然找不到数据会报错
In [2]: asset = Asset.objects.get(id=1)
In [3]: asset.history.all()
Out[3]: <QuerySet [<HistoricalAsset: sandbox01 as of 2018-12-20 17:07:56.843888>, <HistoricalAsset: sandbox01 as of 2018-12-20 16:46:02.288606>, <HistoricalAsset: sandbox01 as of 2018-12-20 16:45:41.492591>, <HistoricalAsset: san
dbox01 as of 2018-12-20 16:45:35.975759>]>
使用历史纪录来恢复模型(可以使用任意一条历史对象来恢复模型):
# 获取最早的历史记录(创建数据时生成的记录)来回复数据
In [4]: earliest_recode = asset.history.earliest()
In [5]: earliest_recode.instance.save()
刷新下数据库可以看到asset数据已经恢复到一开始创建时的内容。
可以在视图中将所有日志信息作为上下文传递给模板,然后进行遍历展示。也可以通过历史纪录组合自己想要的资产变更日志信息,例如一个简单的日志操作信息,记录操作日期、操作类型、操作时间:
In [6]: all_record = []
In [7]: ope_type = {'~': 'update', '+': 'create', '-': 'delete'}
In [8]: for history in asset.history.all():
...: record = {
...: 'history_user': history.history_user.username,
...: 'history_type': ope_type[history.history_type],
...: 'history_date': history.history_date
...: }
...: all_record.append(record)
...:
In [9]: all_record
Out[10]:
[{'history_user': 'admin',
'history_type': 'update',
'history_date': datetime.datetime(2018, 12, 20, 17, 36, 1, 190021)},
{'history_user': 'admin',
'history_type': 'update',
'history_date': datetime.datetime(2018, 12, 20, 17, 7, 56, 843888)},
{'history_user': 'admin',
'history_type': 'update',
'history_date': datetime.datetime(2018, 12, 20, 16, 46, 2, 288606)},
{'history_user': 'admin',
'history_type': 'update',
'history_date': datetime.datetime(2018, 12, 20, 16, 45, 41, 492591)},
{'history_user': 'admin',
'history_type': 'create',
'history_date': datetime.datetime(2018, 12, 20, 16, 45, 35, 975759)}]
你也可以写成一个函数,只需要给这个函数传递一个实例数据,就可以获取该实例的组合日志。
2.7 数据差异化对比
除了在2.6中演示的输出所有原始历史记录和自定义的组合历史纪录外,还可以对历史纪录进行差异化对比,来获取字段变更内容:
In [11]: new_record, old_record, *_ = asset.history.all()
In [12]: delta = new_record.diff_against(old_record)
In [13]: for change in delta.changes:
...: print("{} changed from {} to {}".format(change.field, change.old, change.new))
...:
type changed from 路由器 to 服务器
ip_address changed from 192.168.100.100 to 172.16.3.100
知识点介绍:
1、上面代码从历史纪录中获取了最近两条历史纪录new_record, old_record,其中*_ 用来做占位,把历史纪录中的其他数据全部解压给它;
2、diff_against()通过使用这个方法来获取new_record和old_record的差异数据;
3、通过遍历组合打印出有变化的字段数据。
到这里你就可以在项目中使用django-simple-history去记录ORM的操作历史,实现文档开头展示的日志内容。
轻量级办公管理系统项目开源地址: https:// github.com/RobbieHan/gi standard
点个关注,查看更多Django实战文档