当REST接口提交了model记录之后,希望能够触发一个特定的操作。
Django支持定制save()
相关的操作哦,例如使用post_save()
信号或者 pre_save()
。
在models.py
中添加
@receiver(pre_save, sender=MainModel)
def save_a_historicmodel(sender, **kwargs):
#do your save historicmodel logic here
或者
def save_a_historicmodel(sender, instance, created, **kwargs):
print "Post save was triggered! Instance:", instance
signals.post_save.connect(save_a_historicmodel, sender=MainModel)
这样,每次MainModel
保存时候信号都会触发。
详细文档见 post_save
Django Signals是一个当某些事件发生时,允许解耦的应用程序(decoupled applications
)获得通知的策略。
decoupled applications
- 解耦应用程序低耦合,高内聚---模块之间低耦合,模块内部高内聚。一个系统有多个模块组成,在划分模块时,要把功能关系紧密的放到一个模块中(高内聚),功能关系远的放到其它模块中。模块之间的联系越少越好,接口越简单越好(低耦合,细线通信)。如果划分的模块之间接口很复杂,说明功能划分得不太合理,模块之间的耦合太高了,同时也说明某模块的内聚也不高。 - 程序设计经常提到的解耦,到底是要解除什么之间的耦合?
例如,每次一个给定的模型(Model)实例被更新需要废弃掉一个缓存页面,但是,基于这个模型的一系列位置需要更新。此时可以使用singals
,hook一些代码片段在这个指定model保存动作被触发时执行。
另一种常用的案例是使用Profile策略通过一对一(one-to-one)关系来扩展定制Django User。通常会使用一个信号调度器(signal dispatcher
)来监听用户的post_save
事件以便能够更新Profile实例。 -- 可以参考How to Extend Django User Model
Observer Design Pattern
(观察者设计模式)
在信号机制中有两个关键因素:发送者和接收者。正如名字所示,发送者是负责调度一个信号,接收者则是负责接受信号并作一些事情。
receiver
接收者必须是一个功能模块(function
)或者是一个实例方法(instance method
)才能接收信号。
sender
发送者必须是一个Python对象,或者是None才能从任何发送者这里接收事件。
连接发送者和接收者之间的是"信号调度器"(signal dispatchers
),是通过connect
方法的Signal
实例。
Django核心已经定义了ModelSignal
,也就是一个Signal
子类,允许发送者以app_label.ModelName
形式快速指定为一个字符串。但是,你需要使用Signal
类来创建定制的信号。
要接收一个信号,需要注册一个receiver
功能,这样当信号被通过Signal.connect()
方法发送的时候来获取调用,
这里举例内建信号post_save
。这段代码位于django.db.models.signals
模块。这个信号在模型完成save
方法后触发:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
def save_profile(sender, instance, **kwargs):
instance.profile.save()
post_save.connect(save_profile, sender=User)
contrib
意思是捐献,通常捐献的开源代码位于这个目录下
以上案例中,save_profile
就是接收者功能模块,User
是发送者而post_save
是信号。可以解读为:每次当一个User
实例完成执行它的save
方法后,则save_profile
功能就被执行。
如果抑制sender
参数,类似:post_save.connect(save_profile)
,则save_profile
功能就会在任何Django模型执行save
方法的时候执行。
另一种注册信号的方法是使用@receiver
装饰器(decorator)
def receiver(signal, **kwargs)
这里signal
参数可以是一个信号实例(Signal
instance)或者是一个信号实例的列表/元组(a list/tuple of Signal
instances)。
装饰器
(decorator)是指代码运行期间动态添加功能的方式(不修改函数的定义) - 廖雪峰的Python 2.7教程:装饰器
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
如果希望注册 receiver function到不同的信号,可以如下:
@receiver([post_save, post_delete], sender=User)
根据你注册应用程序信号的不同位置,可能由于导入代码产生不同的边缘影响。所以,建议避免将Signal
代码放在models
方法或者应用程序的根方法中。
Django文档建议将信号存放在app的配置文件。以下是一个名为profiles
的Django app:
profiles/signals.py
:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from cmdbox.profiles.models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
profiles/app.py
:
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class ProfilesConfig(AppConfig):
name = 'cmdbox.profiles'
verbose_name = _('profiles')
def ready(self):
import cmdbox.profiles.signals # noqa
profiles/__init__.py
:
default_app_config = 'cmdbox.profiles.apps.ProfilesConfig'
在以上案例中,只时在ready()
方法中导入的signals
模块会工作,因为这里使用了@receiver()
装饰器。如果使用connect()
方式来连接receiver function
,则参考以下案例。
profiles/signals.py
:
from cmdbox.profiles.models import Profile
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
profiles/app.py
:
from django.apps import AppConfig
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from cmdbox.profiles.signals import create_user_profile, save_user_profile
class ProfilesConfig(AppConfig):
name = 'cmdbox.profiles'
verbose_name = _('profiles')
def ready(self):
post_save.connect(create_user_profile, sender=User)
post_save.connect(save_user_profile, sender=User)
profiles/__init__.py
:
default_app_config = 'cmdbox.profiles.apps.ProfilesConfig'
现在有一个问题,就是如果通过Signals
触发了某个动作,而这个动作恰好就是要处理刚才Model
中post_save
保存的那行记录,该如何处理呢?
这里甬道的就是instance
,这个instance
就是刚才保存的那行数据库记录。举例:
signals.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models.signals import post_save
from django.dispatch import receiver
from notify.views import send_msg
from .models import PizzaCooked
@receiver(post_save, sender=PizzaCooked)
def create_pizzacooked_notify(sender, instance, created, **kwargs):
if created:
customer = instance.customer
pizza_name = instance.pizza_name
pizza_size = instance.pizza_size
cook_time = instance.cook_time
msg = "通知:\r亲爱的%s,您订购的 %s %s 披萨已经于 %s 制作完成,请取用" % (customer, pizza_name, pizza_size, cook_time)
send_msg(msg)
以上案例,即通过Signal的post_save
触发消息发送,其中消息发送内容就是从instance
也就是数据库插入数据字段组合得到。
以下是一些有用的Django信号(常用)
- django.db.models.signals.
pre_init
:
receiver_function(sender, *args, **kwargs)
- django.db.models.signals.
post_init
:
receiver_function(sender, instance)
- django.db.models.signals.
pre_save
:
receiver_function(sender, instance, raw, using, update_fields)
- django.db.models.signals.
post_save
:
receiver_function(sender, instance, created, raw, using, update_fields)
- django.db.models.signals.
pre_delete
:
receiver_function(sender, instance, using)
- django.db.models.signals.
post_delete
:
receiver_function(sender, instance, using)
- django.db.models.signals.
m2m_changed
:
receiver_function(sender, instance, action, reverse, model, pk_set, using)
- django.core.signals.
request_started
:
receiver_function(sender, environ)
- django.core.signals.
request_finished
:
receiver_function(sender, environ)
- django.core.signals.
got_request_exception
:
receiver_function(sender, request)
完整列表参考官方文档
- Django - How to trigger an action when a specific model is saved?
- How to Create Django Signals - 非常详尽的参考文档,本文根据这篇博文翻译整理