在[Django]中使用[django.dispatch.receiver]来处理在数据创建和保存时触发的信号
django.dispatch.receiver()的用法是什么?
有时候我们想要在特定的表格数据发生更改时记录日志,比如说只有当商品信息发生更改时才想要记录日志。
这种情况下可以使用信号(signal)!
Django中包含一个“信号分发器”,它帮助我们解耦的应用程序在框架的其他地方发生操作时得到通知。简而言之,信号允许某些发送者通知一组接收者某个动作已经发生。当许多代码片段可能对相同事件感兴趣时,它们特别有用。
我们不明白这个意思呢。让我们来看个例子吧。
当一个新记录被添加到数据库中时,会产生相应的日志和通知。
如果您使用print进行输出,当然您也可以尝试使用logger来进行日志记录,请试一下。
from django.db.models.signals import post_save
from django.dispatch import receiver
from webtestapp.models import Product
@receiver(post_save, sender=Product)
def create_product(sender, instance, created, **kwargs):
print('作成時のテスト')
if created:
print('[Product]: 商品情報を作成しました')
from django.apps import AppConfig
class WebtestappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'webtestapp'
def ready(self):
from webtestapp import signals
在apps.py文件中添加ready。只需要导入即可隐式地使用它。
你可以试试在Django shell中执行一下
>>> from webtestapp.models import Product
>>> Product.objects.create(name='sample', price=100, category='daily', description='サンプル')
作成時のテスト
[Product]: 商品情報を作成しました
<Product: sample>
如果明确地写下来的话
在之前的例子中,我们只是将import标记为ready。如果需要明确写出来,可以写成以下形式。我认为这样更安全。
from django.apps import AppConfig
from django.core.signals import request_finished
class WebtestappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'webtestapp'
def ready(self):
from webtestapp import signals
request_finished.connect(signals.create_product)
在将记录添加到时,同时创建其他数据。如果是更新,则记录日志。
例如,当创建了相当重要的新数据时,希望将其作为历史或日志数据保存在另一个表中。假设当发生更改时,希望进行日志记录。apps.py与示例1相同。
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.forms import model_to_dict
from webtestapp.models import Product, ProductHistory
@receiver(post_save, sender=Product)
def create_product(sender, instance, created, **kwargs):
data = model_to_dict(instance, fields=[field.name for field in instance._meta.fields])
# idをproduct_idに変更
data['product_id'] = data.pop('id')
if created:
print('[Product]: 商品情報を作成しました')
ProductHistory.objects.create(**data)
print('履歴を作成')
else:
print('[Product]: 商品情報を更新しました')
>>> from webtestapp.models import Product, ProductHistory
>>> new_p = Product.objects.create(name='sample3', price=300, category='daily', description='サンプル3')
[Product]: 商品情報を作成しました
>>> ph = ProductHistory.objects.get(product_id=new_p.id)
>>> ph
<ProductHistory: sample3>
>>> new_p.price = 500
>>> new_p.save()
[Product]: 商品情報を更新しました
我们可以将新建和更新的处理分开了。
填补
如果每次保存都会调用处理的话,那么只要价格发生变化时才想记录日志或调用处理,可以使用pre_save信号来从数据库获取数据,然后比较更新后的实例值,如果不同就记录日志。(如果保存过程中出现错误,可能只会记录日志)。※ pre_save信号会在将数据保存到数据库之前进行处理。
请注意
-
- querysetのupdate()使うとsave()メソッドを使っていないのでsignal処理が動きません。
-
- というか、その他にもupdate()使うとauto_now効かないとかあるので使用するときには注意必要ですね、、
-
- model_to_dictは面倒な書き方してますが
- many_to_manyなければmodel_to_dict(instance)で良いはずです。
本次使用的模型定义
class Product(models.Model):
CATEGORY = (
('daily', '日用品'),
('child', '子供向け'),
('sports', 'スポーツ'),
)
name = models.CharField(max_length=100)
price = models.IntegerField()
category = models.CharField(max_length=50, choices=CATEGORY)
description = models.CharField(max_length=2000, blank=True)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.name
class ProductHistory(models.Model):
CATEGORY = (
('daily', '日用品'),
('child', '子供向け'),
('sports', 'スポーツ'),
)
product_id = models.IntegerField(null=True)
name = models.CharField(max_length=100)
price = models.IntegerField()
category = models.CharField(max_length=50, choices=CATEGORY)
description = models.CharField(max_length=2000, blank=True)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
在单元测试等场合要注意的事项。
在执行单元测试等时,有可能会多次执行相同的内容。为了应对这种情况,据说最好给予不重复的字符串。
阻止重复信号
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
我没有试过,但你只需要自己决定字符串并将其作为dispatch_uid参数传递即可。
解释使用过的Signal
对这次使用的signal进行简单解释。
发表保存
在Django的数据库模型中,post_save信号
在调用save()方法之后执行
参数
receiver_function(sender, instance, created, raw, using, update_fields)
-
- sender:
モデルクラス渡す
instance:
saveしたモデルインスタンス。保存したデータのID使ってログ出すとか色々使えそう。
created: boolean
レコード追加されたときにTrueなる。更新処理の時はFalseなのでよく使うはず
raw: boolean
モデルが直で保存される場合 (フィクスチャをロードするときとか?)、True。Trueの時、DBの他のレコードいじると問題出るかも。
using:
使用されるデータベースエイリアス
update_fields:
更新されるフィールド名。save()に渡した引数つまり更新するフィールドが入る。特定のフィールドを更新されたらログ出したいとかで。
更新の時にフィールド何も渡さなかった場合はNoneになる。instace.save()みたいに全部保存するとNoneになるので使いにくいかも。
关于update_fields的补充说明
我将尝试打印update_fields的数据。
>>> new_p.price = 1000
>>> new_p.save(update_fields=['price'])
frozenset({'price'})
[Product]: 商品情報を更新しました
在使用update_fields参数时,frozenset({‘price’})接收到的值。
如果使用”new_p.save(update_fields=[‘price’])”而不是”new_p.save()”,那么会变为None。
其他的信号
因为这次没有使用的signal很多,所以我会将官方网站的链接放在这里。
-
- pre_save: 保存する前にやらせたい処理がある場合に
-
- post_delete: 削除されたときにやらせたい処理書く(Django4.1からの機能)
-
- m2m_change: モデルでManyToManyFieldが変更されたときに行う
- pre_migrate、post_migrate: 本番環境でmigration不安なときに正常に終了したらlogとか通知処理させたいとか(Django 4.0からの機能)
由于有很多选择,所以请试试看有可能使用的东西。