zoukankan      html  css  js  c++  java
  • 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审批,后续有状态变更将会自动通知你。
    
    工单详情:%s" % _d,
                        reciever_list=[_u]
                    )
                except Exception as e:
                    print('Error:' + str(e))
    
                try:
                    Email(
                        subject="[待审批]-[overmind]数据迁移工单",
                        content="你有工单需要审批,点击下方工单详情链接及时审批。
    
    工单详情:%s" % _d,
                        reciever_list=self.dba_list
                    )
                except Exception as e:
                    print('Error:' + str(e))
            elif _s == 6:
                try:
                    Email(
                        subject="[执行中]-[overmind]数据迁移工单",
                        content="数据迁移工单已通过DBA审核,正在执行中,后续有状态变更将会自动通知你。
    
    工单详情:%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。
    
    工单详情:%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审批,后续有状态变更将会自动通知你。
    
    工单详情:%s" %_d,
                    "reciever_list": [_u],
                }, {
                    "subject": "[待审批]-[overmind]数据迁移工单",
                    "content": "你有工单需要审批,点击下方工单详情链接及时审批。
    
    工单详情:%s" %_d,
                    "reciever_list": self.dba_list,
                }],
                6: [{
                    "subject": "[执行中]-[overmind]数据迁移工单",
                    "content": "数据迁移工单已通过DBA审核,正在执行中,后续有状态变更将会自动通知你。
    
    工单详情:%s" %_d,
                    "reciever_list": [_u] + self.dba_list,
                }],
                7: [{
                    "subject": "[已完成]-[overmind]数据迁移工单",
                    "content": "数据迁移工单已自动完成迁移,请检查最终状态,如有任何疑问随时联系DBA。
    
    工单详情:%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主要分为几类

    1. 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:没用过字面意思理解吧
    2. management signals
      • pre_migrate:migrate之前触发
      • post_migrate:migrate之后触发
    3. request/response signals
      • request_started:请求开始时触发
      • request_finished:请求完成后触发
      • got_request_exception:请求异常时触发
    4. test signals
      • setting_changed:配置改变时触发
      • template_rendered:模板渲染时触发
    5. 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_initpost_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'
    

    绑定成功后就可以在每次工单状态发生变化时发送邮件了


    长按关注公众号查看更多原创文章

    如果你觉得文章对你有帮助,请转发分享给更多的人。如果你觉得读的不尽兴,推荐阅读以下文章:

  • 相关阅读:
    js判断是否为数字
    MongoDB 复制集机制及原理
    MongoDB 聚合查询
    C#中时间相关知识点小结
    C#中的小数和百分数计算
    WPF使用VisualTreeHelper进行复杂命中测试
    WPF使用VisualTreeHelperj简单进行命中测试
    WPF Data Binding数据驱动
    WPF Binding中的RelativeSource属性
    WPF特效:流光动画效果
  • 原文地址:https://www.cnblogs.com/37Y37/p/9981570.html
Copyright © 2011-2022 走看看