Django - 无法使用信号删除对象更改时的旧文件

Django - Unable to delete old file on object change using signals

我有以下信号要从我的硬盘中删除旧的封底和 postcover_tn(缩略图)。如果我只是通过我的表单删除文件并调用 save() 就可以正常工作,但是如果我想用我上传的新文件覆盖旧文件,旧文件仍在我的 fs 上,知道如何解决这个问题吗? :

signals.py

@receiver(models.signals.pre_save, sender=Post)
def post_auto_delete_files_on_change(sender, instance, **kwargs):
    """
    Deletes old file from filesystem
    when corresponding object is updated
    with new file.
    """
    if not instance.pk:
        return False

    try:
        old_postcover = sender.objects.get(pk=instance.pk).postcover
        old_postcover_tn = sender.objects.get(pk=instance.pk).postcover_tn
    except sender.DoesNotExist:
        return False
    if not old_postcover:
        return

    new_postcover = instance.postcover
    if not old_postcover == new_postcover:
        if os.path.isfile(old_postcover.path):
            os.remove(old_postcover.path)
    new_postcover_tn = instance.postcover_tn
    if not old_postcover_tn == new_postcover_tn:
        if os.path.isfile(old_postcover.path):
            os.remove(old_postcover.path)

postcover_tn 是在 Post 的 save() 上生成的,如果您可能对此感到疑惑的话。

问题来了

由于您正在处理 post 保存 信号,因此在信号处理程序执行之前,实例上的数据已经插入到数据库中。

这意味着上面代码中的 sender.objects.get(pk=instance.pk).postcoverinstance.postcover 获取相同的东西——新保存的 postcover.

因此,您在代码中命名为 old_postcover 的那个东西实际上是 new postcover。真正的 old postcover 已被永久覆盖,并且仍在您的文件系统中。


题外话

现在,这部分代码的主体...

if not old_postcover == new_postcover:
    if os.path.isfile(old_postcover.path):
        os.remove(old_postcover.path)
        os.remove(old_postcover_tn.path)

...永远不会达到 运行,因为 old_postcovernew_postcover 确实是一回事。


如何解决这个问题?

您可以使用预保存信号处理程序。

在处理程序中,您使用 sender.objects.get(pk=instance.pk).postcover 从数据库中获取 old postcover(检查后,就像您在代码中所做的那样,以确保实例确实有 pk)。

然后删除这个旧的 post封面,就大功告成了。


这个解决方案的问题

走这条路我可以立即看到的问题是,您在删除旧数据时并不知道数据库是否会首先接受新数据。


但往好的方面看

但是,如果您只是通过 ModelForm 更改 post 封面,则对表单上的 is_valid() 方法的调用将对实例执行所有验证,因此那么您可以确信在处理程序执行时,实例上的新数据已经过验证并将被数据库接受。

我是这样工作的:

models.py:

def save(self, *args, **kwargs):
    super(Post, self).save(*args, **kwargs)
    if self.postcover:
        if not (self.postcover_tn and os.path.exists(self.postcover_tn.path)):
            image = Image.open(self.postcover)
            outputIoStream = BytesIO()
            baseheight = 400
            hpercent = baseheight / image.size[1]
            wsize = int(image.size[0] * hpercent)
            imageTemproaryResized = image.resize((wsize, baseheight))
            imageTemproaryResized.save(outputIoStream, format='PNG')
            outputIoStream.seek(0)
            self.postcover = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                  "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                  sys.getsizeof(outputIoStream), None)
            image = Image.open(self.postcover)
            outputIoStream = BytesIO()
            baseheight = 175
            hpercent = baseheight / image.size[1]
            wsize = int(image.size[0] * hpercent)
            imageTemproaryResized = image.resize((wsize, baseheight))
            imageTemproaryResized.save(outputIoStream, format='PNG')
            outputIoStream.seek(0)
            self.postcover_tn = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                  "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                  sys.getsizeof(outputIoStream), None)
    elif self.postcover_tn:
        self.postcover_tn.delete()

    super(Post, self).save(*args, **kwargs)

signals.py

@receiver(models.signals.pre_save, sender=Post)
def post_auto_delete_files_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding object is updated
with new file.
"""
if not instance.pk:
    return False

try:
    old_postcover = sender.objects.get(pk=instance.pk).postcover
    old_postcover_tn = sender.objects.get(pk=instance.pk).postcover_tn
except sender.DoesNotExist:
    return False
if not old_postcover:
    return

new_postcover = instance.postcover
new_postcover_tn = instance.postcover_tn
if not old_postcover == new_postcover:
    if os.path.isfile(old_postcover.path):
        os.remove(old_postcover.path)
        if old_postcover_tn == new_postcover_tn:
            if os.path.isfile(old_postcover_tn.path):
                os.remove(old_postcover_tn.path)

@mfonism 感谢您的提示,它们确实帮助我理解了。