在[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からの機能)

由于有很多选择,所以请试试看有可能使用的东西。

bannerAds