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'
绑定成功后就可以在每次工单状态发生变化时发送邮件了
如果你觉得文章对你有帮助,请转发分享给更多的人。如果你觉得读的不尽兴,推荐阅读以下文章:
![](/img/my/wx.png)
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
webpack4配置详解之常用插件分享
前言 继上一次webpack的基础配置分享之后,本次将分享一些工作中项目常用的配置插件、也会包含一些自己了解过觉得不错的插件,如有分析不到位的,欢迎纠错,嗯,这些东西文档都有,大佬可绕过。 Wepack4之后废弃了很多的插件,这里只会讲解webpack4还支持的(包含4之前插件),已经废弃的将不再阐述。 上一次的分享之后,有部分掘金网友留言质疑:骗小白的赞、是否原创、是否是抄别人等等,当然也有很多的网友支持和鼓励,不管褒贬,苏南都非常的感谢,是你们让我认识到自己的不足与优劣。 大家的留言,让我想起了自己刚入门前端初期的心酸,基本靠自己自学,没有人带,遇到问题像无头的苍蝇,到处乱撞网上一顿搜索,百度不曾欺我,在点了一个又一个的广告之后,翻过十页八页之后终于找到了问题的解决方案。 执着于对前端的热爱,常常一个问题卡到深夜,初入前端的我曾一度感叹在编辑器写一些东西,在网页上就能跑,甚至感叹 js 写上一个 alert hello world,浏览器就会自动弹出一个窗口,感觉全世界都在向你招手,当时的兴奋是难以形容的,甚至幻想着未来有一天,可能有千万、亿万的用户,在用你写的东西。 这几天一直在...
- 下一篇
三, 跨语言微服务框架 - Istio官方示例(自动注入.请求路由.流量控制.故障注入)
基础的Istio环境已经搭建完成,我们需要开始了解Istio提供作为微服务网格的各种机制,也就是本文标题的自动注入.请求路由.故障注入.流量切换,官方很给力的准备的实例项目也不需要大家自己编写demo来进行测试,那就来时跑跑看吧. 附上: 喵了个咪的博客:w-blog.cn Istio官方地址:https://preliminary.istio.io/zh Istio中文文档:https://preliminary.istio.io/zh/docs/ PS : 此处基于当前最新istio版本1.0.3版本进行搭建和演示 一. 自动注入(sidecar) 说到服务网格必然是侵入式的包裹着你的服务,在中间过程完成了一些列工作,那么就到了我们的第一个主题自动注入,大家在通过webui或者是kubectl创建的时候默认并不会触发Istio的容器注入,那么有以下几种方式可以让istio注入生效: 使用命令注入istioctl来创建容器 > istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f - 开启name...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程