社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  问与答

Django使用Signals监测model字段变化发送通知

37丫37 • 6 年前 • 892 次点击  

上一篇文章《运维效率之数据迁移自动化》中讲到了工单通知,本文将介绍工单通知实现过程中的一些小技巧。所有演示均基于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主要分为几类 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'

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


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

如果你觉得文章对你有帮助,请转发分享给更多的人。如果你觉得读的不尽兴,推荐阅读以下文章: - Django model转字典的几种方法 - Django配置Celery执行异步任务和定时任务

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/26540
 
892 次点击