Django使用Signals监测model字段变化发送通知
上一篇文章《运维效率之数据迁移自动化》中讲到了工单通知,本文将介绍工单通知实现过程中的一些小技巧。所有演示均基于Django2.0
阅读此篇文章你可以:
- 解锁一个python if的使用新姿势
- 获取一个利用signals做通知的真实案例
背景说明
先看看工单表简化后的结构
class Ticket(models.Model):
'''工单表'''
STATE = (
(1, '待审批'),
(2, '已撤销'),
(3, '已通过'),
(4, '被拒绝'),
(5, '已挂起'),
(6, '执行中'),
(7, '已完成'),
(8, '已失败')
)
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
create_user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name='创建用户')
state = models.IntegerField(choices=STATE, default=1, verbose_name='工单状态')
Ticket工单表有一个state字段标识当前工单状态,这个状态会随着工单的进行而改变,每当工单状态改变时就需要发送通知给相应的用户,例如工单创建时,需要发送给创建者一个工单创建成功的通知,同时发送给审核者一个待审核的通知
通知
每一个状态的变化都需要通知,为了代码易读及解耦,我们需要写一个单独的通知类,当需要通知的时候调用一下就好了。通知类中需要判断当前工单的状态,那么通常会写成下边这样
class Notify:
def __init__(self):
self.dba_list = ["dba1@ops-coffee.cn", "dba2@ops-coffee.cn"]
def migration(self, pk):
'''迁移通知'''
_t = Ticket.objects.get(id=pk)
_u = _t.create_user.username
_s = _t.state
_d = "https://ops-coffee.cn/workflow/migration/%d/" % (_t.id)
if _s == 1:
try:
Email(
subject="[已提交]-[overmind]数据迁移工单",
content="你的数据迁移工单已提交,正在等待DBA审批,后续有状态变更将会自动通知你。\r\n\r\n工单详情:%s" % _d,
reciever_list=[_u]
)
except Exception as e:
print('Error:' + str(e))
try:
Email(
subject="[待审批]-[overmind]数据迁移工单",
content="你有工单需要审批,点击下方工单详情链接及时审批。\r\n\r\n工单详情:%s" % _d,
reciever_list=self.dba_list
)
except Exception as e:
print('Error:' + str(e))
elif _s == 6:
try:
Email(
subject="[执行中]-[overmind]数据迁移工单",
content="数据迁移工单已通过DBA审核,正在执行中,后续有状态变更将会自动通知你。\r\n\r\n工单详情:%s" % _d,
reciever_list=[_u] + self.dba_list,
)
except Exception as e:
print('Error:' + str(e))
elif _s == 7:
try:
Email(
subject="[已完成]-[overmind]数据迁移工单",
content="数据迁移工单已自动完成迁移,请检查最终状态,如有任何疑问随时联系DBA。\r\n\r\n工单详情:%s" % _d,
reciever_list=[_u] + self.dba_list,
)
except Exception as e:
print('Error:' + str(e))
以上的代码可以看出来写了很多重复的try代码,并且当状态越多,if判断越复杂时重复的代码也会越来越多,有没有更简洁的方式呢?
看看下边的代码,维护一个状态字典,然后用一个if判断就可以实现上边一堆if的代码写法,是不是就简洁了很多
class Notify:
def __init__(self):
self.dba_list = ["dba1@ops-coffee.cn", "dba2@ops-coffee.cn"]
def migration(self, pk):
'''迁移通知'''
_t = Ticket.objects.get(id=pk)
_u = _t.create_user.username
_s = _t.state
_d = "https://ops-coffee.cn/workflow/migration/%d/" %(_t.id)
smap = {
1: [{
"subject": "[已提交]-[overmind]数据迁移工单",
"content": "你的数据迁移工单已提交,正在等待DBA审批,后续有状态变更将会自动通知你。\r\n\r\n工单详情:%s" %_d,
"reciever_list": [_u],
}, {
"subject": "[待审批]-[overmind]数据迁移工单",
"content": "你有工单需要审批,点击下方工单详情链接及时审批。\r\n\r\n工单详情:%s" %_d,
"reciever_list": self.dba_list,
}],
6: [{
"subject": "[执行中]-[overmind]数据迁移工单",
"content": "数据迁移工单已通过DBA审核,正在执行中,后续有状态变更将会自动通知你。\r\n\r\n工单详情:%s" %_d,
"reciever_list": [_u] + self.dba_list,
}],
7: [{
"subject": "[已完成]-[overmind]数据迁移工单",
"content": "数据迁移工单已自动完成迁移,请检查最终状态,如有任何疑问随时联系DBA。\r\n\r\n工单详情:%s" %_d,
"reciever_list": [_u] + self.dba_list,
}]
}
_list = smap[_s]
for i in range(0, len(_list)):
try:
Email(
subject=_list[i]['subject'],
content=_list[i]['content'],
reciever_list=_list[i]['reciever_list']
)
except Exception as e:
print('Error:' +str(e))
在构造字典的时候采用了状态做key,通知变量做value,同时一个状态可能会产生多个不同的通知,所以value采用列表的方式,这样即可轻松实现一个状态多条通知,每条通知都可以发给不同的人,有不同的主题,不同的内容。
Signals
上边我们已经写好了发送通知的类,在view里每次修改工单状态之后调用下通知类即可实现通知发送,但这样通知跟view强耦合,且通知会分散在view中的多个地方,造成代码冗余且不够优雅。我们需要一个简单优雅的方式来实现,signals可以说是非常有用了
Signals是Django自带的一个信号调度程序。如果你对svn或者git之类的hooks有了解,这个理解起来就简单多了,通俗来说就是当你的程序产生一个事件时,会通过signals自动触发其他的事件。就比如我们这个工单系统通知,当工单状态产生变化时自动发送邮件给相关人。
Django内部已经定义好了一些signal供我们使用,如果不能满足我们也可以自定义signal,其中Django内部定义的signal主要分为几类
- model signals
pre_init
:model初始化前触发post_init
:model初始化后触发pre_save
:save()方法前触发post_save
:save()方法后触发pre_delete
:delete()方法前触发post_delete
:delete()方法后触发m2m_changed
:ManyToManyField字段改变时触发class_prepared
:没用过字面意思理解吧
- management signals
pre_migrate
:migrate之前触发post_migrate
:migrate之后触发
- request/response signals
request_started
:请求开始时触发request_finished
:请求完成后触发got_request_exception
:请求异常时触发
- test signals
setting_changed
:配置改变时触发template_rendered
:模板渲染时触发
- Database Wrappers
connection_created
:连接建立时触发
那么信号究竟该如何使用呢?下边一个实际的例子来说明下信号的使用
就以我们发送通知的需求为例,workflow是一个普通的app,第一步需要新建workflow/signals.py
文件绑定signal
from django.db.models import signals
from django.dispatch import receiver
from workflow.models import Ticket
from workflow.backends.notify import Notify
@receiver(signals.post_init, sender=Ticket)
def migrate_notify_init(instance, **kwargs):
instance.old_state = instance.state
@receiver(signals.post_save, sender=Ticket)
def migrate_notify_post(instance, created, **kwargs):
if created or instance.old_state != instance.state:
Notify().migration(instance.id)
这里用到了两个signal,post_init
和post_save
在model初始化之后通过post_init
信号获取到state的值作为初始状态值,在每次model执行save方法后调用post_save
信号获取到新的状态值,对两次状态值做比较如果不一致则表示状态有更新发送通知
是上边的判断只能判断到状态变更了发通知,但工单在第一次创建时old_state和state是一样的,所以也需要在save之后判断下这次操作是不是新建,如果是新建同样需要发送通知
第二步加载signal,需要修改两个配置文件
config1:workflow/apps.py
from django.apps import AppConfig
class WorkflowConfig(AppConfig):
name = 'workflow'
def ready(self):
import workflow.signals
config2:workflow/__init__.py
default_app_config = 'workflow.apps.WorkflowConfig'
绑定成功后就可以在每次工单状态发生变化时发送邮件了
如果你觉得文章对你有帮助,请转发分享给更多的人。如果你觉得读的不尽兴,推荐阅读以下文章:
Django使用Signals监测model字段变化发送通知的更多相关文章
- django【ORM】model字段类型
1.AutoField 一个自增的IntegerField,一般不直接使用,Django会自动给每张表添加一个自增的primary key. 2.BigIntegerField 64位整数, -922 ...
- Django Model字段加密的优雅实现
早前的一篇文章Django开发密码管理表实例有写我们写了个密码管理工具来实现对密码的管理,当时加密解密的功能在view层实现,一直运行稳定所以也没有过多关注实现是否优雅的问题.最近要多加几个密码表再次 ...
- Django model字段类型清单
转载:<Django model字段类型清单> Django 通过 models 实现数据库的创建.修改.删除等操作,本文为模型中一般常用的类型的清单,便于查询和使用: AutoField ...
- Django文档——Model字段类型(Field Types)
大部分内容参考自http://wrongwaycn.github.io/django11/topics/db/models/index.html#topics-db-models ,内容是django ...
- Django model 字段类型及选项解析---转载
model field 类型1.AutoField() 自增的IntegerField,通常不用自己设置,若没有设置主键,Django会自动添加它为主键字段,Django会自动给每张表添加一个自增的p ...
- Django model字段类型(转)
AutoField 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.(参阅 _自 ...
- django中model字段与属性
model field 类型1.AutoField 一个自增的IntegerField,一般不直接使用,Django会自动给每张表添加一个自增的primary key. 2.BigIntege ...
- Django model 字段类型清单
Django model字段类型清单 Django 通过 models 实现数据库的创建.修改.删除等操作,本文为模型中一般常用的类型的清单,便于查询和使用: AutoField 一个自动递增的整型字 ...
- django的model字段在保存的时候做预处理怎么办?
django的model字段在保存的时候做预处理怎么办? 比如这个model: class Book(Model): publish_date = DateField() 但是在保存时,用户输入数据是 ...
随机推荐
- C#——字符串
aaarticlea/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAAA8CAMAAAA6wCQxAAAAGFBMVEX////w9f5kb9TGyu3b6P6hp
- wchar_t,char,string,wstring等的总结
一.LPSTR LPCSTR LPTSTR LPCTSTR等 确定的类型: LPSTR = CHAR * = char * LPCSTR = const CHAR * = char * //c意为co ...
- 2.3.4volatile的原子性
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也不具备原子性. 测试 package com.cky.thread; /** * Created by edi ...
- Codeforces Round #540 (Div. 3)--1118D1 - Coffee and Coursework (Easy version)
https://codeforces.com/contest/1118/problem/D1 能做完的天数最大不超过n,因为假如每天一杯咖啡,每杯咖啡容量大于1 首先对容量进行从大到小的排序, sor ...
- C#期末大作业 消消乐 2017-06-01 18:11 275人阅读 评论(0) 收藏
邻近期末,忙于刷题之余意识到期末大作业来不及了,匆匆赶下了作业,虽说做的很是粗糙,但完全原创的 下载链接 https://pan.baidu.com/s/1cCNLr4 大体的做大约3天完成了: 第一 ...
- common.php
<?php /** * */ class Common { if(!function_exists('is_php')) { function is_php($version = '5.0.0' ...
- spark 中划分stage的思路
窄依赖指父RDD的每一个分区最多被一个子RDD的分区所用,表现为 一个父RDD的分区对应于一个子RDD的分区 两个父RDD的分区对应于一个子RDD 的分区. 宽依赖指子RDD的每个分区都要依赖于父RD ...
- Mining Twitter Data with Python
目录 1.Collecting data 1.1 Register Your App 1.2 Accessing the Data 1.3 Streaming 2.Text Pre-processin ...
- shell 命令 netstat 查看端口占用
netstat 查看 8888端口的占用情况
- To datafix AR DATE
http://www.cnblogs.com/benio/archive/2012/07/07/2580203.html AR transactions should be created on 6- ...