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
View Code

用户收藏表:

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

 

posted @ 2018-01-24 20:53  哀乐之巅写年华  阅读(579)  评论(0编辑  收藏  举报