Django delete FileField

7fyelxc5  于 2023-03-24  发布在  Go
关注(0)|答案(9)|浏览(133)

我正在Django中构建一个Web应用程序。我有一个上传文件的模型,但我不能删除文件。下面是我的代码:

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()

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

pkln4tw6

pkln4tw61#

在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()方法调用碰巧在一个被回滚的事务中,你的数据可能最终引用了一个不存在的文件。你可以考虑将文件删除逻辑 Package 到transaction.on_commit()中,就像Mikhail的评论中建议的那样。
  • 边缘情况:如果您的应用上传了一个新文件,并将模型示例指向新文件,而没有调用save()(例如,通过批量更新QuerySet),则旧文件将继续存在,因为信号不会运行。如果您使用传统的文件处理方法,则不会发生这种情况。
  • 编码样式:这个例子使用file作为字段名,这不是一个好的样式,因为它与内置的file对象标识符冲突。

附录:定期清理

实际上,你可能还想运行一个定期任务来处理孤儿文件清理,以防运行时故障阻止某些文件被删除。考虑到这一点,你可能会完全摆脱信号处理程序,并使这样的任务成为处理不敏感数据和不那么大的文件的机制。
无论哪种方式,如果你正在处理敏感数据,最好是双重或三重检查,确保你永远不会在生产中及时删除数据,以避免任何相关的责任。

另请参见

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

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

  • 为什么Django不会自动删除文件:Django 1.3版本说明中的条目

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

  • 仅使用pre_delete信号的示例
gr8qqesn

gr8qqesn2#

尝试django-cleanup,当您删除模型时,它会自动调用FileField上的delete方法。

pip install django-cleanup

settings.py

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

hec6srdp3#

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

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

wr98u20j4#

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

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)
oymdgrw7

oymdgrw75#

Django 2.x解决方案:

Django 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()正在运行Default Manager的删除查询。因此,如果您希望能够通过使用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,但是你可以通过返回一些关于你删除的对象或任何你想要的东西来做得更好。

zhte4eai

zhte4eai6#

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

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)
esyap4oy

esyap4oy7#

对于那些在较新版本的Django(目前为3.1)中寻找答案的人。
我找到了这个website,它对我没有任何改变,只要把它添加到你的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)
nr7wwzry

nr7wwzry8#

@Anton Strogonoff
我在代码中缺少了一些东西,当一个文件改变时,如果你创建一个新文件会产生一个错误,因为是一个新文件,没有找到路径。我修改了函数的代码,并添加了一个try/except语句,它工作得很好。

@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
fkvaft9z

fkvaft9z9#

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

#  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='')

相关问题