Django 删除 FileField

我在姜戈建一个网络应用。我有一个上传文件的模型,但我不能删除该文件。这是我的代码:

class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)


def delete(self, *args, **kwargs):
# You have to prepare what you need before delete the model
storage, path = self.song.storage, self.song.path
# Delete the model before the file
super(Song, self).delete(*args, **kwargs)
# Delete the file after the model
storage.delete(path)

然后,在 python manage.py shell中我这样做:

song = Song.objects.get(pk=1)
song.delete()

它从数据库中删除记录,但不删除服务器上的文件。 我还能试什么?

谢谢!

92664 次浏览

在 Django 1.3之前,当您删除相应的模型实例时,文件会自动从文件系统中删除。您可能正在使用较新的 Django 版本,因此必须自己实现从文件系统中删除文件。

简单的基于信号的样本

在撰写本文时,我选择的方法是混合使用 post_deletepre_save信号,这使得每当相应的模型被删除或者它们的文件被更改时,过时的文件就会被删除。

基于一个假设的 MediaFile模型:

import os
import uuid


from django.db import models
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _




class MediaFile(models.Model):
file = models.FileField(_("file"),
upload_to=lambda instance, filename: str(uuid.uuid4()))




# These two auto-delete files from filesystem when they are unneeded:


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


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


try:
old_file = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False


new_file = instance.file
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
  • 我认为我前段时间开发的一个应用程序在生产环境中使用了这段代码,但是使用这段代码的风险由你自己承担。
  • 例如,有一个 可能的数据丢失场景: 如果您的 save()方法调用恰好位于回滚的事务中,那么您的数据可能最终引用一个不存在的文件。您可以考虑将文件删除逻辑封装到 transaction.on_commit()中,类似于 transaction.on_commit(lambda: os.remove(old_file.path))正如米哈伊尔的评论所暗示的django-cleanup做一些类似的事情
  • 边缘情况: 如果您的应用程序上传一个新文件,并指出模型实例的新文件没有调用 save()(例如批量更新 QuerySet) ,旧文件将继续躺在那里,因为信号不会运行。如果使用传统的文件处理方法,就不会发生这种情况。
  • 编码风格: 此示例使用 file作为字段名,这不是一种好的风格,因为它与内置的 file对象标识符冲突。

附录: 定期清理

实际上,您可能希望 还有运行一个定期任务来处理孤立文件清理,以防运行时故障阻止删除某些文件。考虑到这一点,您可以完全去掉信号处理程序,并使用这样的任务 机制来处理不敏感的数据和不太大的文件。

不管是哪种方式,如果您使用 处理敏感数据,那么最好是再三检查,以确保在生产环境中永远不会错过及时删除数据,从而避免任何相关的负债。

参见

  • Django 1.11模型字段引用中的 FieldFile.delete() (注意,它描述了 FieldFile类,但是您可以直接在字段上调用 .delete(): FileField实例代理相应的 FieldFile实例,并且您可以像访问字段的方法一样访问它们)

    请注意,当删除模型时,不会删除相关文件。如果需要清理孤立文件,则需要自己处理(例如,使用自定义管理命令,可以手动运行,也可以通过 cron 定期运行)。

  • 为什么 Django 不会自动删除文件: < a href = “ https://docs.djangoproject.com/en/stat/release/1.3/# delete-a-model-doesn-t-delete-association-files”rel = “ noReferrer”> Django 1.3发布说明中的条目

    在早期的 Django 版本中,当包含 FileField的模型实例被删除时,FileField自己也会从后端存储中删除该文件。这为几种数据丢失场景打开了大门,包括回滚事务和引用同一文件的不同模型上的字段。在 Django 1.3中,当删除一个模型时,不会调用 FileFielddelete()方法。如果需要清理孤立文件,则需要自己处理(例如,使用自定义管理命令,可以手动运行,也可以通过 cron 定期运行)。

  • 仅使用 pre_delete信号的示例

@ Anton Strogonoff

我在代码中遗漏了一些东西,当一个文件发生变化时,如果你创建一个新的文件生成一个错误,因为是一个新的文件一个没有找到的路径。我修改了函数代码,添加了一个 try/夹杂的句子,效果很好。

@receiver(models.signals.pre_save, sender=MediaFile)
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 = MediaFile.objects.get(pk=instance.pk).file
except MediaFile.DoesNotExist:
return False


new_file = instance.file
if not old_file == new_file:
try:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
except Exception:
return False

这是一个应用程序,将删除旧的文件,每当模型被删除或一个新的文件上传: Django-Smarfields

from django.db import models
from smartfields import fields


class Song(models.Model):
song = fields.FileField(upload_to='/songs/')
image = fields.ImageField(upload_to='/pictures/', blank=True)

您还可以简单地覆盖模型的 delete 函数来检查文件是否存在,并在调用 super 函数之前删除它。

import os


class Excel(models.Model):
upload_file = models.FileField(upload_to='/excels/', blank =True)
uploaded_on = models.DateTimeField(editable=False)




def delete(self,*args,**kwargs):
if os.path.isfile(self.upload_file.path):
os.remove(self.upload_file.path)


super(Excel, self).delete(*args,**kwargs)

尝试 姜戈,清理,它会在您删除模型时自动调用 FileField 上的 delete 方法。

pip install django-cleanup

设置

INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig',
)

您可以通过调用文件字段的 .delete方法从文件系统中删除文件,如下所示,Django > = 1.10:

obj = Song.objects.get(pk=1)
obj.song.delete()

这个代码将运行每次我上传一个新的图像(标志字段) ,并检查是否一个标志已经存在,如果是这样,关闭它,并从磁盘上删除它。同样的过程当然也可以在接收函数中进行。希望这个能帮上忙。

 #  Returns the file path with a folder named by the company under /media/uploads
def logo_file_path(instance, filename):
company_instance = Company.objects.get(pk=instance.pk)
if company_instance.logo:
logo = company_instance.logo
if logo.file:
if os.path.isfile(logo.path):
logo.file.close()
os.remove(logo.path)


return 'uploads/{0}/{1}'.format(instance.name.lower(), filename)




class Company(models.Model):
name = models.CharField(_("Company"), null=False, blank=False, unique=True, max_length=100)
logo = models.ImageField(upload_to=logo_file_path, default='')

Django 2. x 解决方案:

姜戈2号中很容易处理文件删除。我尝试过使用 Django 2和 SFTP STORAGE 以及 FTP STORAGE 来实现下面的解决方案,并且我非常肯定它将与实现了 delete方法的任何其他存储管理器一起工作。(delete方法是 storage抽象方法之一,它被认为可以从存储器中物理地删除文件!)

重写模型的 delete方法的方式是实例在删除自己之前删除它的 FileFields:

class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)


def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()

对我来说很简单。 如果要在删除之前检查文件是否存在,可以使用 storage.exists。例如,self.song.storage.exists(self.song.name)将返回一个 boolean,表示歌曲是否存在。它看起来是这样的:

def delete(self, using=None, keep_parents=False):
# assuming that you use same storage for all files in this model:
storage = self.song.storage


if storage.exists(self.song.name):
storage.delete(self.song.name)


if storage.exists(self.image.name):
storage.delete(self.image.name)


super().delete()

编辑(补充) :

正如 @ HeyMan所提到的,使用这个调用 Song.objects.all().delete()的解决方案不会删除文件!这是因为 Song.objects.all().delete()正在运行 默认管理器的删除查询。因此,如果您希望能够使用 objects方法删除模型的文件,您必须编写并使用 客户经理(仅用于覆盖其删除查询) :

class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()

为了将 CustomManager分配给模型,你必须在你的模型中初始化 objects:

class Song(models.Model):
name = models.CharField(blank=True, max_length=100)
author = models.ForeignKey(User, to_field='id', related_name="id_user2")
song = models.FileField(upload_to='/songs/')
image = models.ImageField(upload_to='/pictures/', blank=True)
date_upload = models.DateField(auto_now_add=True)
    

objects = CustomManager() # just add this line of code inside of your model


def delete(self, using=None, keep_parents=False):
self.song.storage.delete(self.song.name)
self.image.storage.delete(self.image.name)
super().delete()

现在您可以在任何 objects子查询的末尾使用 .delete()。我编写了最简单的 CustomManager,但是您可以通过返回一些关于您删除的对象或任何您想要的对象的内容来做得更好。

对于那些在 Django 的新版本(目前是3.1)中寻找答案的人来说。

我发现这个 网站和它的工作为我没有任何改变,只是添加到你的 models.py:

from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.db import models
 

""" Only delete the file if no other instances of that model are using it"""
def delete_file_if_unused(model,instance,field,instance_file_field):
dynamic_field = {}
dynamic_field[field.name] = instance_file_field.name
other_refs_exist = model.objects.filter(**dynamic_field).exclude(pk=instance.pk).exists()
if not other_refs_exist:
instance_file_field.delete(False)
""" Whenever ANY model is deleted, if it has a file field on it, delete the associated file too"""
@receiver(post_delete)
def delete_files_when_row_deleted_from_db(sender, instance, **kwargs):
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
instance_file_field = getattr(instance,field.name)
delete_file_if_unused(sender,instance,field,instance_file_field)
            

""" Delete the file if something else get uploaded in its place"""
@receiver(pre_save)
def delete_files_when_file_changed(sender,instance, **kwargs):
# Don't run on initial save
if not instance.pk:
return
for field in sender._meta.concrete_fields:
if isinstance(field,models.FileField):
#its got a file field. Let's see if it changed
try:
instance_in_db = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
# We are probably in a transaction and the PK is just temporary
# Don't worry about deleting attachments if they aren't actually saved yet.
return
instance_in_db_file_field = getattr(instance_in_db,field.name)
instance_file_field = getattr(instance,field.name)
if instance_in_db_file_field.name != instance_file_field.name:
delete_file_if_unused(sender,instance,field,instance_in_db_file_field)