Django信号signals----解决多对多字段同步更新
信号signals介绍
Django 包含一个“信号调度器”,它帮助已解耦的应用程序在框架中的其它地方发生操作时可以得到通知。简而言之,信号允许某些 发送器 通知一组 接收器 某些操作已经发生。当许多代码段可能对同一事件感兴趣时,它们特别有用。
Django 提供了 内置信号集 使用户代码能够获得 Django 自身某些操作的通知。其中包括一些有用的通知:
-
django.db.models.signals.pre_save
&django.db.models.signals.post_save
一个模型的 save()
方法被调用之前或之后发出。
-
django.db.models.signals.pre_delete
&django.db.models.signals.post_delete
一个模型的 delete()
方法或查询结果集的 delete()
方法被调用之前或之后发出。
django.db.models.signals.m2m_changed
一个模型的 ManyToManyField
更改后发出。
-
django.core.signals.request_started
&django.core.signals.request_finished
Django 发起或结束一个 HTTP 请求后发出。
所遇问题
为了实现每次后台更新Task模型的内容,执行器模型Executor也能及时的更新,重写了admin.py中TaskAdmin类的save_model函数,该函数会捕获在admin后台界面对Task模型进行数据save(),并可以增补一些操作,我添加了同步对Executor模型的save()操作。
但是遇到一个问题:由于Task模型的recipient字段为多对多外键,在数据库中是以中间表的形式存在(绑定Task模型的task_name和User模型的user_id),每次在admin后台进行Task模型的增改时,Executor模型并不能同步更新为正确的recipient。
原因:save_model提供的obj,并不是最新的obj,其中依旧存储着更新操作前的recipient,如果是初次创建则为空。
#models.py
from django.contrib.auth.models import User
class Task(models.Model):
task_name = models.CharField(verbose_name="需求名", max_length=128, primary_key=True)
task_content = models.CharField(verbose_name="需求内容", max_length=255)
recipient = models.ManyToManyField(User, related_name='recipient_user', verbose_name='需求接收人群', blank=True)
class Executor(models.Model):
task_name = models.CharField(verbose_name="需求名", max_length=128, primary_key=True)
task_content = models.CharField(verbose_name="需求内容", max_length=255)
recipient = models.CharField(verbose_name="接收人群", max_length=128)
#admin.py
from django.contrib import admin
from .models import Task,Executor
class TaskAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
recipient_str = ','.join([user.email for user in obj.recipient.all() if user.email])
Executor.objects.update_or_create(name=obj.task_name,
defaults={
"name": obj.task_name,
"task_content": obj. task_content,
"recipient": recipient_str
}).save()
admin.site.register(Task, TaskAdmin)
解决方法
在models.py同级别路径下,增加一个signals.py文件,它会自动捕获recipient关系表的变化。
sender
中间模型类描述 ManyToManyField
。当定义了多对多字段时,这个类会自动创建;可以使用多对多字段上的 through
属性来访问它。
instance
多对多关系被更新的实例。这可以是 sender
的实例,或者是 ManyToManyField
所关联的类的实例。
action
参数有6种:
1. "pre_add"
在一个或多个对象被添加到关系 之前 发送。
2. "post_add"
在一个或多个对象被添加到关系 之后 发送。
3. "pre_remove"
在一个或多个对象从关系中删除 之前 发送。
4. "post_remove"
在一个或多个对象从关系中删除 之后 发送。
5."pre_clear"
在关系被清除 之前 发送。
6. "post_clear"
在关系被清除 之后 发送。
我们只需要关心post打头的函数,即recipient关系表操作后信号。
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from .models import Task,Executor
@receiver(m2m_changed, sender=Task.recipient.through)
def executor_create_or_update(sender, **kwargs):
instance = kwargs.pop('instance', False)
action = kwargs.pop('action', False)
if 'post' in action: # 操作完成后进行更改
recipient_str = ','.join([user.email for user in instance.recipient.all() if user.email])
print(recipient_str)
Executor.objects.filter(name=instance.task_name).update(recipient=recipient_str)