Django - 从亚马逊S3删除文件

18

我有一个问题,在管理员中删除对象时,与之关联的文件不会被删除。经过一些研究,我决定在模型中实现post_delete来解决这个问题。 出于某种原因,即使在搜索了许多指南和代码片段后,我仍无法使s3删除该文件,也许这里有人知道。 我使用的是django 1.5和boto。 以下是我的模型代码:

from django.db import models
from django.contrib.auth.models import User
from fileservice.formatChecker import ContentTypeRestrictedFileField
from south.modelsinspector import add_introspection_rules
import os
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from django.core.files.storage import default_storage as storage
add_introspection_rules([
    (
        [ContentTypeRestrictedFileField], # Class(es) these apply to
        [],         # Positional arguments (not used)
        {           # Keyword argument
            "content_types": ["content_types", {}],
            "max_upload_size": ["max_upload_size", {}]
        },
    ),
], ["^fileservice\.formatChecker\.ContentTypeRestrictedFileField"])

class Contentfile(models.Model):
    content = ContentTypeRestrictedFileField(upload_to='uploads/', content_types=['video/mp4', 'application/pdf', 'image/gif', 'image/jpeg', 'image/png'],max_upload_size=5242880,blank=True, null=True, help_text='Upload a file to add it to the content the app displayes')
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)
    title = models.CharField(max_length=255, unique=True)
    file_type = models.CharField(max_length=5)
    published = models.BooleanField(default=True)
    file_owner = models.ForeignKey(User, related_name='Contentfiles')

    class Meta:
        ordering = ["title"]

    def __unicode__(self):
        return self.title

    def save(self, *args, **kwargs):
        file_name = os.path.basename(self.content.name)
        self.file_type = file_name.split('.')[-1]
        self.title = file_name.split('.')[0]
        self.published = True
        super(Contentfile, self).save(*args, **kwargs)



@receiver(models.signals.post_delete, sender=Contentfile)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    """Deletes file from filesystem
    when corresponding `MediaFile` object is deleted.
    """
    if instance.content:
        if os.path.isfile(storage.open(instance.content.path)):
            os.remove(storage.open(instance.content.path))

@receiver(models.signals.pre_save, sender=Contentfile)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """Deletes file from filesystem
    when corresponding `MediaFile` object is changed.
    """
    if not instance.pk:
        return False

    try:
        old_file = Contentfile.objects.get(pk=instance.pk).content
    except Conentfile.DoesNotExist:
        return False

    new_file = instance.content
    if not old_file == new_file:
        if os.path.isfile(storage.open(old_file.path)):
            os.remove(storage.open(old_file.path))
4个回答

32

做post_delete要安全得多。如果出了问题,你会开始错过S3文件,因为你的DB记录是完好无损的,你不会注意到它。post_delete将更安全,因为在你删除数据库记录之后,S3删除操作很少会失败。此外,即使文件删除失败,你也将留下一堆无引用的S3文件,它们是无害的,可以轻松清理。

@receiver(models.signals.post_delete, sender=Picture)
def remove_file_from_s3(sender, instance, using, **kwargs):
    instance.img.delete(save=False)

David,你能详细说明一下你答案中的“清理未引用的S3文件”部分吗?你会如何从S3中删除没有数据库引用的文件,同时保留有引用的文件? - apiljic
@David Dehghan... 我也对最后一部分感兴趣,如何轻松删除未被引用的S3文件? - gabn88
1
我不知道有什么特殊的魔法可以删除未被引用的S3文件。您需要运行一个批处理作业,将所有S3文件的ID转储,并运行查询以获取您的DB文件ID并比较这两个列表。目前我们已经忽略了这个问题。可能我们在S3中有孤立的项目,但在我们的情况下,成本足够便宜,我们可以忽略它。 - David Dehghan

11
您需要调用 FieldFiledelete() 方法来删除S3中的文件。在您的情况下,在调用它的地方添加一个pre_delete 信号即可:
@receiver(models.signals.pre_delete, sender=ContentFile)
def remove_file_from_s3(sender, instance, using):
    instance.content.delete(save=False)

我有点不愿意添加pre_delete方法,因为例如如果删除出现任何问题,我将面临相反的情况,即在数据库中表示该文件但已从服务器中删除。 - Yoav Schwartz
哈哈,我误读了你的帖子,以为它有一个pre_delete处理程序。不管怎样,我猜随你高兴吧。 - tuomur
不管怎样,那已经不是重点了。方法奏效了,我接受了你的答案。非常感谢! - Yoav Schwartz
能否添加一个示例来说明模型是如何工作的?不太清楚那个实例从哪里来,信号函数在哪里等。 - mgPePe
添加了一个完整的信号处理程序示例。 - tuomur
由于我之前的评论,我已经更改了接受的答案,因为它更好地反映了我最终使用的内容。 - Yoav Schwartz

7
尝试使用django-cleanup,它可以在您删除模型时自动调用FileField的delete方法。

0

我通过在数据库和AWS S3中删除文件来解决了这个问题。

from django.db import models
from django.dispatch import receiver
from django.views import generic
from project.models import ContentFile
from django.contrib.auth.mixins import LoginRequiredMixin,UserPassesTestMixin

class DeleteFileView(LoginRequiredMixin,UserPassesTestMixin,generic.DeleteView):
   model = ContentFile
   template_name = 'file-delete.html'
   success_url = 'redirect-to'

   #Check if the owner of the file is the one trying to delete a file
   def test_func(self):
      obj = self.get_object()
      if obj.user == self.request.user:
        return True
      return False

    #This code does magic for S3 file deletion 
    @receiver(models.signals.pre_delete, sender=ContentFile)
    def remove_file_from_s3(sender, instance, using, **kwargs):
       instance.image_file.delete(save=False)

我正在使用pre_delete,你可以查看Django文档。在数据库中删除文件引用是由DeleteView完成的,希望这能帮助到某些人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接