Django使用Signals監測model字段變化發送通知的一些技巧

這篇文章主要介紹了Django使用Signals監測model字段變化發送通知的一些小技巧,所有演示是基於Django2.0的,需要的朋友可以參考下

本文將介紹工單通知實現過程中的一些小技巧。所有演示均基於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 = ["[email protected]", "[email protected]"]

  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 = ["[email protected]", "[email protected]"]

  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
post_init
pre_save
post_save
pre_delete
post_delete
m2m_changed
class_prepared
management signals
pre_migrate
post_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字段變化發送通知的一些技巧,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章