Django中的信号
笔者最近遇到一个需求:
现在需要当在数据表中的某个模型中新增一条记录后,需要将这条记录的生成信息添加到logging模块中,即每生成一条数据记录,就打印出相应的日志。笔者首先的需求是考虑使用装饰器,例如对每个执行新增记录的视图函数加上装饰器,每当执行这个视图函数时候,就先执行这个装饰器。但是这样有个弊端:例如我需要拿到当前插入到数据表中的记录,这是拿不到的!比如我想在日志中看到哪张表生成了什么样的表记录,使用装饰器是拿不到,装饰器的作用仅仅是在为当前函数添加新的功能!在执行视图函数之前要先执行装饰器,此时压根拿不到插入到数据表中的记录,另外如果这个视图函数中涉及到插入多张表中的数据,那么仅仅执行装饰器函数只能是获取到当前某一个记录,无法获取到多条记录。所以装饰器这种方法不可行。
Django提供一种信号机制。其实就是观察者模式,又叫发布-订阅(Publish/Subscribe) 。当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。用于在框架执行操作时解耦。
【01】Django内置的信号量
Model signals
pre_init # django的modal执行其构造方法前,自动触发
post_init # django的modal执行其构造方法后,自动触发
pre_save # django的modal对象保存前,自动触发
post_save # django的modal对象保存后,自动触发
pre_delete # django的modal对象删除前,自动触发
post_delete # django的modal对象删除后,自动触发
m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
pre_migrate # 执行migrate命令前,自动触发
post_migrate # 执行migrate命令后,自动触发
Request/response signals
request_started # 请求到来前,自动触发
request_finished # 请求结束后,自动触发
got_request_exception # 请求异常后,自动触发
Test signals
setting_changed # 使用test测试修改配置文件时,自动触发
template_rendered # 使用test测试渲染模板时,自动触发
Database Wrappers
connection_created # 创建数据库连接时,自动触发
笔者使用最多的是post_save和post_delete两种信号,对于像update这样的操作,是不会出发信号的!我们以Django官方的例子为例来说明下信号量的使用,目前数据库中有两本书籍的记录,现在我想做的是:只要添加或者删除一条书籍记录,那就执行信号量绑定的函数,告诉我们是哪张表的记录发生了改变!
为了方便演示,我们直接将信号量相关的代码定义在应用的init文件下:
from django.db.models.signals import post_delete, post_save
def callback(sender, **kwargs):
"""
carson666
<class 'app01.models.Book'>
{'signal': <django.db.models.signals.ModelSignal object at 0x0000025C9B99AD30>,
'instance': <Book: Book object>, 'created': True, 'update_fields': None, 'raw':
False, 'using': 'default'}"""
print("carson666")
print(sender) # <class 'app01.models.Book'>
print(kwargs)
post_save.connect(callback) # post_save信号与callback绑定关系
其中post_save.connect(callback)的意思就是我们将信号post_save与函数callback绑定在一起,一旦检测到数据表中新增了记录,那么就执行callback函数。我们从打印结果看到,kwargs中的created为True,表示是新增一条记录。因此触发了与信号量绑定在一起的函数callback的执行。我们来看看当我们删除记录和修改记录时,它是否会改变:
def add(request):
# book_obj = Book.objects.create(title="go", price=120) # 执行post_save信号量
# Book.objects.filter(title='linux').update(price=666)
Book.objects.get(id=2).delete() # 执行post_delete信号量
return HttpResponse("删除书籍对象成功!!")
# return HttpResponse("创建书籍对象成功,开始触发信号")
# return HttpResponse("书籍对象价格修改成功")
最终的信号量定义如下:
from django.db.models.signals import post_delete, post_save
def callback(sender, **kwargs):
"""
carson666
<class 'app01.models.Book'>
{'signal': <django.db.models.signals.ModelSignal object at 0x0000025C9B99AD30>,
'instance': <Book: Book object>, 'created': True, 'update_fields': None, 'raw': False, 'using': 'default'}
:param sender:
:param kwargs:
:return:
"""
print("carson666")
print(sender) # <class 'app01.models.Book'>
print(kwargs)
def delete_success(sender, **kwargs):
print("删除书籍对象成功,信号量执行")
post_save.connect(callback) # post_save信号与callback绑定关系
post_delete.connect(delete_success)
另外在上面打印的kwargs中,我们重点关注两个参数:instance,表示我们创建的实例对象,另外一个参数就是created参数,表示是否是新建实例对象。理解这两个参数很重要,后面我们将会进行具体案例的解析。
【02】信号量的使用方式二:
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Book)
def my_callback(sender, **kwargs):
print("Request finished!")
【03】Django信号量结合DRF的实际使用举例
笔者在真实项目中就使用了信号量来解决实际的问题。
场景一:利用Django信号量来修改用户的密码。
例如用户使用手机号码注册的时候,传递过来的是手机号码,明文密码,现在我们肯定需要对密码加密,因此就可以使用Django的信号量:
定义一个signals.py文件:
# -*- coding: utf-8 -*-
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
# 使用django信号量实现用户密码修改
"""
sender表示接受哪个model传递过来的
另外还需要判断一下这个created参数是否为True,表示是否是新建用户的时候
这里可以让代码的分离性比较好,推荐使用Django信号量
"""
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
if created:
# instance就是我们的user对象
password = instance.password
instance.set_password(password)
instance.save()
然后在apps.py中,在ready函数中导入信号量:
使用场景二
需求:我们通常会使用淘宝或者京东购物,简单的购物车功能中,当用户收藏了某件商品,那么回创建一个用户收藏商品对象;同时在商品表中,商品的收藏数就要加1;如果用户取消收藏了该商品,那么对应商品的收藏数就要减一。这个就可以使用Django中的信号量解决,先来看看两张数据表,商品表:
class Goods(models.Model): """ 商品 注意这里的category指向了第三级类别,表示这个商品属于哪个类别 我们这里总共有三个类别 """ category = models.ForeignKey(GoodsCategory, verbose_name="商品类目") goods_sn = models.CharField(max_length=50, default="", verbose_name="商品唯一货号") name = models.CharField(max_length=100, verbose_name="商品名") click_num = models.IntegerField(default=0, verbose_name="点击数") sold_num = models.IntegerField(default=0, verbose_name="商品销售量") fav_num = models.IntegerField(default=0, verbose_name="收藏数") goods_num = models.IntegerField(default=0, verbose_name="库存数") market_price = models.FloatField(default=0, verbose_name="市场价格") shop_price = models.FloatField(default=0, verbose_name="本店价格") goods_brief = models.TextField(max_length=500, verbose_name="商品简短描述") goods_desc = UEditorField(verbose_name=u"内容", imagePath="goods/images/", width=1000, height=300, filePath="goods/files/", default='') ship_free = models.BooleanField(default=True, verbose_name="是否承担运费") goods_front_image = models.ImageField(upload_to="goods/images/", null=True, blank=True, verbose_name="封面图") is_new = models.BooleanField(default=False, verbose_name="是否新品") is_hot = models.BooleanField(default=False, verbose_name="是否热销") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间") class Meta: verbose_name = '商品' verbose_name_plural = verbose_name def __str__(self): return self.name
用户收藏表:
class UserFav(models.Model):
"""
用户收藏
"""
user = models.ForeignKey(User, verbose_name="用户")
goods = models.ForeignKey(Goods, verbose_name="商品", help_text="商品id")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")
class Meta:
verbose_name = '用户收藏'
verbose_name_plural = verbose_name
unique_together = ("user", "goods") # 设置联合唯一主键
def __str__(self):
return self.user.username
也就是说一旦UserFav这张表中新增一条记录,那么对应goods表中的收藏数fav_num就要+1,来看看这个信号量是怎么定义的:
# -*- coding: utf-8 -*-
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from user_operation.models import UserFav
@receiver(post_save, sender=UserFav)
# 这个信号的意思就是一旦UserFav这张表中新增一条记录,那么post_save监测到这个信号,那就直接执行对应的绑定函数
# create_userfav
def create_userfav(sender, instance=None, created=False, **kwargs):
if created:
"""
当用户创建了用户收藏商品对象后,才将商品的收藏数目+1
"""
# instance就是我们的UserFav实例对象
goods = instance.goods
goods.fav_num += 1
goods.save()
同理,如果用户取消收藏商品,那么商品的收藏数目就要减一,如下:
# 用户取消收藏某件商品,那么商品的收藏数就要减1
@receiver(post_delete, sender=UserFav)
def delete_userfav(sender, instance=None, created=False, **kwargs):
goods = instance.goods
goods.fav_num -= 1
goods.save()
使用方法如下:
以上就是Django信号量结合DRF的实践场景!欢迎评论和学习!
参考资料:
【01】http://sabinemaennel.ch/django/signals-in-django/
【02】https://docs.djangoproject.com/en/1.10/topics/signals/
【03】http://www.weiguda.com/blog/38/
【04】http://www.python88.com/topic/151
【05】https://pylixm.cc/posts/2017-01-24-Django-signal.html