使用bulk_create批量创建中发送post_save信号

更多内容请点击 我的博客 查看,欢迎来访。

关键字:bulk_create无法使用post_save

要解决的问题:当使用bulk_create批量保存数据时,无法接收post_save信号。

如何解决:创建Manager,重写bulk_create方法,models中使用该Manager

批量创建对象

staff_device = []

for i in range(len(device_number_list)):
    # 数据合并进行处理
    staff_device.append(
        RegisterDevice(
            staff=staff,
            operator=operator
        )
    )

RegisterDevice.objects.bulk_create(staff_device)

这是我的信号

from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from .models import RegisterDevice


@receiver(post_save, sender=RegisterDevice)
def register_device_save_handler(sender, instance=None, created=False, **kwargs):
    if created:
        date = instance.created_time
        operate_mode = instance.operate_mode
        # 创建统计的日期
        count_date = CountDate.objects.get_or_create(date=date)[0]  # (, False)  # 如果有为False

        count_op_num = CountOpNum.objects.get_or_create(date=count_date, op_mode=operate_mode)[0]
        count_op_num.num += 1
        count_op_num.save()

这个代码主要是用于监听RegisterDevice保存数据的信号,然后将统计数据+1

现在这个信号不在批量创建中触发。

查看bulk_create说明和源码

如前所述,bulk_create不触发这些信号

bulk-create

这有几点需要注意:

  • 不会调用模型的save()方法,也不会发送pre_savepost_save信号。
  • 它不适用于多表继承方案中的子模型。
  • 如果模型的主键是AutoField,它不会像save()那样检索和设置主键属性,除非数据库后端支持它(当前是PostgreSQL)。
  • 它不适用于多对多关系。
  • 它将objs转换为一个列表,如果它是一个生成器,它会完全评估objs。 强制转换允许检查所有对象,以便可以首先插入具有手动设置主键的任何对象。

源码如下:

# `django\db\models\query.py`源码
class QuerySet:
    # ............
    def bulk_create(self, objs, batch_size=None):
        """
        Insert each of the instances into the database. Do *not* call
        save() on each of the instances, do not send any pre/post_save
        signals, and do not set the primary key attribute if it is an
        autoincrement field (except if features.can_return_ids_from_bulk_insert=True).
        Multi-table models are not supported.
        """
        # When you bulk insert you don't get the primary keys back (if it's an
        # autoincrement, except if can_return_ids_from_bulk_insert=True), so
        # you can't insert into the child tables which references this. There
        # are two workarounds:
        # 1) This could be implemented if you didn't have an autoincrement pk
        # 2) You could do it by doing O(n) normal inserts into the parent
        #    tables to get the primary keys back and then doing a single bulk
        #    insert into the childmost table.
        # We currently set the primary keys on the objects when using
        # PostgreSQL via the RETURNING ID clause. It should be possible for
        # Oracle as well, but the semantics for extracting the primary keys is
        # trickier so it's not done yet.
        assert batch_size is None or batch_size > 0
        # Check that the parents share the same concrete model with the our
        # model to detect the inheritance pattern ConcreteGrandParent ->
        # MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy
        # would not identify that case as involving multiple tables.
        for parent in self.model._meta.get_parent_list():
            if parent._meta.concrete_model is not self.model._meta.concrete_model:
                raise ValueError("Can't bulk create a multi-table inherited model")
        if not objs:
            return objs
        self._for_write = True
        connection = connections[self.db]
        fields = self.model._meta.concrete_fields
        objs = list(objs)
        self._populate_pk_values(objs)
        with transaction.atomic(using=self.db, savepoint=False):
            objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs)
            if objs_with_pk:
                self._batched_insert(objs_with_pk, fields, batch_size)
                for obj_with_pk in objs_with_pk:
                    obj_with_pk._state.adding = False
                    obj_with_pk._state.db = self.db
            if objs_without_pk:
                fields = [f for f in fields if not isinstance(f, AutoField)]
                ids = self._batched_insert(objs_without_pk, fields, batch_size)
                if connection.features.can_return_ids_from_bulk_insert:
                    assert len(ids) == len(objs_without_pk)
                for obj_without_pk, pk in zip(objs_without_pk, ids):
                    obj_without_pk.pk = pk
                    obj_without_pk._state.adding = False
                    obj_without_pk._state.db = self.db

        return objs

手动发送信号

所以必须手动触发它们。如果想让所有模型都这样,可以覆盖bulk_create方法,并在该方法中发送信号

models.py中创建类-模型管理器

class BulkCreateManager(models.Manager):
    def bulk_create(self, objs, **kwargs):
        # 发送信号
        from django.db.models.signals import post_save
        for item in objs:
            post_save.send(item.__class__, instance=item, created=True)

        return super().bulk_create(objs, **kwargs)

使用模型管理器

class RegisterDevice(models.Model):
    staff = models.ForeignKey(RegisterStaff, on_delete=models.CASCADE, related_name='devices', verbose_name='使用人')
    # ...........
    created_time = models.DateTimeField(default=timezone.now, verbose_name='创建时间')

    objects = BulkCreateManager()  # 需要添加的位置

    # 其他代码

你可能感兴趣的:(使用bulk_create批量创建中发送post_save信号)