Django - post_init信号在模型实例保存时调用,甚至在实例创建之前,为什么?

12 投票
2 回答
9554 浏览
提问于 2025-04-16 10:01

我正在尝试写一个小应用程序,用来接收视频文件,并在上传后将它们转换成统一的格式(这样就可以添加到数据库里)。我在网上寻找最佳解决方案,决定使用Django的信号功能和Celery。不过现在我只是想先做一个概念验证,看看这个方法是否可行。

我想在上传新视频后执行一个video_replace()的方法(也就是说,数据库里新增了一行数据)。但是信号没有正常工作,或者我没有理解整个系统是怎么运作的。

我使用的是Django 1.2.3,并且用到了预定义的信号django.db.models.signals.post_init,这个信号应该在模型实例化后被调用(也就是数据库里新增了一行数据)。

from django.core.files.base import File
from django.db.models.signals import post_init
import os
import os.path
import subprocess

class Project(models.Model):
    video = models.FileField(upload_to="projects/videos")

    def replace_video(self):
        """Replace original video with an updated one."""

        # Video conversion process code goes here,
        # resulting in a new external video file.

        self.video.delete() # Delete the original video.
        self.video.save("newfile.webm", File(open("path/to/newfile.webm") ,"wb"))) # Save the new video instead.

        self.save() # Commit everything to database.

        os.remove("path/to/newfile.webm") # Remove original video copy after it was commited (copied) into the DB.

# ...
# ...

def handle_new_project(sender, **kwargs): 
    """Handels some additional tasks for a new added project. i.e. convert video to uniform format."""

    project = kwargs['instance']
    project.replace_video()

# Call 'Project.replace_video()' every time a new project is added.
post_init.connect(handle_new_project, sender=Project, dispatch_uid="new_project_added")

然而,post_init不仅在创建新模型实例时被调用,还会在以下情况下被调用…:

  1. 在模型实例化之前。我的意思是,当我第一次启动服务器时,它会被调用,那时数据库里甚至没有一行数据(因此不应该实例化任何模型对象)。这个实例的self.pkNone
  2. 在调用save()方法时。上面的代码在我执行self.save()时也会被执行。

实际上,它的工作方式和文档上说的不太一样。

我哪里做错了?请记住,这只是一个概念验证。我打算在确认它有效后把代码移到Celery上。但是,如果信号不正常工作,Celery也帮不了忙——每次我save()或更新视频时,信号都会被多次发送。

你觉得我不应该在replace_video()方法里调用save()吗?那我应该在哪里调用呢?我应该选择哪个信号?post_save不是一个好选择,因为它在我点击save()时也会被调用。

2 个回答

2

我知道这个问题已经很久了,但我遇到过类似的问题,解决起来很简单,只需要使用 post_save 信号,并检查一下是否是新创建的。

@receiver(post_save, sender=YourSender) 
   def when_init(sender, instance, created, **kwargs):
      if created:
         ...
24

你似乎对“实例化一个对象”这个概念有点困惑。其实这和数据库没有任何关系。实例化一个模型对象时,并不会把它保存到数据库里,这种情况下它的主键(pk)会是None:

MyObject(field1='foo', field2='bar')

而这个(间接地)是通过从数据库获取对象来实例化一个对象:

MyObject.objects.get(field1='baz')

在这两种情况下,post_init信号都会被发送,尽管它们都和保存到数据库没有关系。

如果你希望在保存的时候触发某些操作,可以选择重写save方法,或者使用pre_savepost_save信号。你可以通过检查对象的pk是否为None来判断它之前是否被保存过。

撰写回答