使用signals进行工作
Django 自带了一个信号调度程序允许receiver函数在某个动作出现时候去获取通知。当你需要你的代码执行某些时间的同时发生些其他时间的时候,信号非常有用。 我们还可以创建一个自定义的信号,让别人在某个事件发生的时候可以获得通知。
Django自带的models提供了几个信号,它们位于django.db.models.signals。举几个常见例子:
- pre_save:调用model的save()方法前发送信号
- post_save:调用model的save()方法后发送信号
- pre_delete:调用model活着QuerySets的delete()方法前发送信号
- post_delete:同理,调用delete()后发送信号
- m2m_changed:当一个模型上的ManyToManyField字段被改变的时候发送信号
使用场景:
打个比方,我们想要获取热门图片。我们可以使用Django的聚合函数来获取图片,通过用户喜欢的数量进行排序。
from django.db.models import Count
from image.models import Image
images_by_popularity = Image.objects.annotate(
total_likes=Count('user_like')).order_by('-total_likes')
但是,我们发现,每次通过统计图片的总喜欢数量在进行排序比直接使用一个已经存储好的用统计数的字段进行排序要消耗更多的性能。我们可以给模型额外添加一个正整数字段,用非规范化的计数来提升涉及该字段查询的性能。那么,问题来了,我们该如何保持这个字段是更新过的。
编辑images应用下的models.py文件,给Image模型添加以下字段:
total_likes = models.PositiveIntegerFiled(db_index=True, default=0)
total_likes字段用来存储每张图片的总喜欢数,默认值为0。
非规范化数据在你想用它们来过滤或者排序查询集(QuerSets)的时候非常有用。
在使用非规范化字段之前我们必须先考虑其他几种提高性能的方法,例如数据库索引,最优化查询以及在开始规范化你的数据之前进行缓存。
运行以下命令将新添加的字段迁移到数据库中:
python manage.py makemigrations images
你会看到如下输出:
Migrations for 'images':
0002_image_total_likes.py:
- Add field total_likes to image
接着继续运行以下命令来应用迁移:
python manage.py migrate images
输出中会包含以下内容:
Applying images.0002_image_total_likes... OK_
我们要给m2m_changed信号附加一个receiver接收函数。
在images应用目录下创建一个命名为signals.py的新文件并给该文件添加如下代码:
from django.db.models.signals import m2m_changed
from django.dsipatch import receiver
from .models import Image
@receiver(m2m_changed, sender=Image.users_like.through)
def users_like_changed(sender, instance, **kwargs):
Image.objects.filter(pk=instance.id).update(total_likes=instance.user_like.conut())
这里注意一个问题在@receiver里调用save()经实测不生效,必须要使用update强制更新某一字段
首先,我们使用receiver()装饰器将users_like_changed函数注册成一个receiver函数,然后我们将该函数附加给m2m_changed信号,并将函数与Image.user_like.through实例连接,这样,只有当m2m_changed信号被Image.user_like.through执行的时候函数才被调用。还有一个可以替代的方式来注册一个receiver函数,由使用Signal对象的connect()
方法组成。
Django的信号是同步阻塞的。不要使用异步任务导致信号混乱,但是我们可以联合两者来执行异步任务当我们的代码只接受一个信号通知。
我们必须要给出一个信号来连接我们的receiver函数,只有这样它才会在信号发送的时候被调用。有一个推荐的方法来注册我们的信号是在我们的应用配置类中倒入它们到ready()方法中。Django提供一个应用注册允许我们对我们的应用进行配置和内省。
典型的应用配置类
Django允许你指定配置类给你的应用们。为了提供一个自定义配置给你的应用,创建一个继承django.apps的Appconfig类的自定义类。这个应用配置类允许你为应用存储元数据和配置并提供内省。
为了注册你的信号receiver函数,当你使用receiver()装饰器的时候,你需要倒入信号模块,这些信号模块被包含在你的应用的Appconfig类中的ready()方法中。这个方法在应用注册别完整填充的时候就调用,其他给你应用的初始化都可以包含在这个方法中。
在images应用下应该有一个app.py文件,没有的话自己创建。为该文件添加如下代码:
from django.apps import Appconfig
class ImageConfig(Appconfig):
name = 'images'
verbose_name = 'Image bookmarks'
def ready(self):
import images.signals
name属性定义该应用完整的python路径。
verbose_name属性设置了这个应用的人类可读名字,它会在管理站点中显示。
ready()方法就是我们为这个应用导入信号的地方。
现在我们需要告诉Django我们的应用配置位于哪里。编辑位于images应用目录下的init.py文件添加如下内容:
defualt_app_config = 'images.apps.ImagesConfig'
原先的查询:
images_by_popularity = Image.objects.annotate(
likes=Count('users_like')).order_by('-likes')
现在我们可以用新的查询来代替上面的查询:
```python`
images_by_popularity = Image.objects.order_by('-total_likes')
以上查询只需要很少的SQL查询性能。
信号个人理解:
当一个信号发送后接受到信号的receiver函数同时作出了响应的操作。
网友评论