从数据库/模型中删除对象时,如何让Django Admin删除文件?


85

我将1.2.5与标准ImageField一起使用,并使用内置的存储后端。文件上传正常,但是当我从管理员删除条目时,服务器上的实际文件不会删除。


嗯,实际上应该。检查您的上传文件夹上的文件权限(更改为0777)。
Torsten Engelbrecht

5
Django删除了自动删除功能(适用于看到上述评论的Google员工)。
2015年

Answers:


99

您可以接收pre_deletepost_delete信号(请参见下面的@toto_tico注释),并在FileField对象上调用delete()方法,因此(在models.py中):

class MyModel(models.Model):
    file = models.FileField()
    ...

# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

@receiver(pre_delete, sender=MyModel)
def mymodel_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.file.delete(False)

10
确保添加检查instance.file字段是否为空,或者可以(至少尝试删除)整个MEDIA_ROOT目录。这甚至适用于ImageField(null=False)字段。
安东尼·哈奇金斯,2013年

46
谢谢。通常,我建议使用该post_delete信号,因为在由于某种原因导致删除失败的情况下,它更安全。然后,既不保留模型,也不删除文件,以保持数据一致。如果我的理解post_deletepre_delete信号不正确,请纠正我。
toto_tico

9
请注意,如果您替换模型实例上的文件,这不会删除旧文件
标记

3
在admin外部的Django 1.8中,这对我不起作用。有新的方法吗?
哈里发2015年

太棒了 长期以来一直在寻找它
RL Shyam

46

尝试django-cleanup

pip install django-cleanup

settings.py

INSTALLED_APPS = (
    ...
    'django_cleanup', # should go after your apps
)

1
非常棒的包装。谢谢!:)
BoJack Horseman

3
经过有限测试后,我可以确认该软件包仍适用于Django 1.10。
CoderGuy123 '16

1
很好,这很容易
Tunn

真好 适用于Django 2.0。我还将S3用作存储后端(django-storages.readthedocs.io/en/latest/backends/…),它很乐意从S3删除文件。
routeburn

35

Django 1.5解决方案:我出于各种内部原因使用post_delete。

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

@receiver(post_delete, sender=Photo)
def photo_post_delete_handler(sender, **kwargs):
    photo = kwargs['instance']
    storage, path = photo.original_image.storage, photo.original_image.path
    storage.delete(path)

我将其粘贴在models.py文件的底部。

original_image字段ImageField在我的Photo模型中。


7
对于使用Amazon S3作为存储后端(通过django-storages)的任何人,此特定答案将不起作用。您将获得NotImplementedError: This backend doesn't support absolute paths.可以轻松地解决此问题,只需将文件字段的名称传递给storage.delete()而不是文件字段的路径即可。例如,用storage, name = photo.original_image.storage, photo.original_image.namethen代替该答案的最后两行storage.delete(name)
肖恩·阿兹林

2
@Sean +1,我正在1.7中使用该调整功能,以通过django-storages删除S3上django-imagekit生成的缩略图。docs.djangoproject.com/en/dev/ref/files/storage/…。注意:如果仅使用ImageField(或FileField),则可以使用mymodel.myimagefield.delete(save=False)docs.djangoproject.com/en/dev/ref/files/file/...
user2616836

@ user2616836您可以使用mymodel.myimagefield.delete(save=False)post_delete?换句话说,我可以删除文件,但是当具有图像字段的模型被删除时,可以删除文件吗?
尤金2015年

1
@eugene是的,可以,它可以工作(不过我不确定为什么)。在post_delete执行操作时instance.myimagefield.delete(save=False),请注意的使用instance
user2616836 2015年

17

此代码还可以通过“管理”面板在Django 1.4上很好地运行。

class ImageModel(models.Model):
    image = ImageField(...)

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

在删除模型之前获取存储空间和路径很重要,否则删除后模型也将保持无效。


3
这对我不起作用(Django 1.5),而Django 1.3 CHANGELOG指出:“在Django 1.3中,删除模型时,不会调用FileField的delete()方法。如果需要清理孤立的文件,则可以则需要您自己处理(例如,使用可手动运行或计划通过cron定期运行的自定义管理命令)。”
darrinm

4
这个解决方法是错误的!delete在删除行时并不总是被调用,您必须使用信号。
lvella

11

需要删除同时在实际的文件deleteupdate

from django.db import models

class MyImageModel(models.Model):
    image = models.ImageField(upload_to='images')

    def remove_on_image_update(self):
        try:
            # is the object in the database yet?
            obj = MyImageModel.objects.get(id=self.id)
        except MyImageModel.DoesNotExist:
            # object is not in db, nothing to worry about
            return
        # is the save due to an update of the actual image file?
        if obj.image and self.image and obj.image != self.image:
            # delete the old image file from the storage in favor of the new file
            obj.image.delete()

    def delete(self, *args, **kwargs):
        # object is being removed from db, remove the file from storage first
        self.image.delete()
        return super(MyImageModel, self).delete(*args, **kwargs)

    def save(self, *args, **kwargs):
        # object is possibly being updated, if so, clean up.
        self.remove_on_image_update()
        return super(MyImageModel, self).save(*args, **kwargs)

很好的解决方案!
AlexKh

6

您可以考虑使用pre_delete或post_delete信号:

https://docs.djangoproject.com/en/dev/topics/signals/

当然,删除FileField自动删除的相同原因也适用于此。如果删除在其他地方引用的文件,则会出现问题。

就我而言,这似乎是适当的,因为我有一个专用的文件模型来管理所有文件。

注意:由于某种原因,post_delete似乎无法正常工作。文件被删除,但是数据库记录仍然保留,这与我所期望的完全相反,即使在错误情况下也是如此。pre_delete工作正常。


3
可能post_delete不会工作,因为file_field.delete()在默认情况下保存模型数据库,尝试file_field.delete(False) docs.djangoproject.com/en/1.3/ref/models/fields/...
亚当Jurczyk

3

也许有点晚了。但是对我来说,最简单的方法是使用post_save信号。只是要记住,即使在QuerySet删除过程中也会执行信号,但是在QuerySet删除过程中不会执行[model] .delete()方法,因此这不是重写它的最佳选择。

core / models.py:

from django.db import models
from django.db.models.signals import post_delete
from core.signals import delete_image_slide
SLIDE1_IMGS = 'slide1_imgs/'

class Slide1(models.Model):
    title = models.CharField(max_length = 200)
    description = models.CharField(max_length = 200)
    image = models.ImageField(upload_to = SLIDE1_IMGS, null = True, blank = True)
    video_embed = models.TextField(null = True, blank = True)
    enabled = models.BooleanField(default = True)

"""---------------------------- SLIDE 1 -------------------------------------"""
post_delete.connect(delete_image_slide, Slide1)
"""--------------------------------------------------------------------------"""

core / signals.py

import os

def delete_image_slide(sender, **kwargs):
    slide = kwargs.get('instance')
    try:
        os.remove(slide.image.path)
    except:
        pass

1

此功能将在Django 1.3中删除,因此我不会依赖它。

您可以delete在完全从数据库中删除条目之前,覆盖相关模型的方法以删除文件。

编辑:

这是一个简单的例子。

class MyModel(models.Model):

    self.somefile = models.FileField(...)

    def delete(self, *args, **kwargs):
        somefile.delete()

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

您是否有示例说明如何在模型中使用该示例来删除文件?我正在查看文档,并查看了有关如何从数据库中删除对象的示例,但未看到有关文件删除的任何实现。
narkeeso 2011年

2
此方法是错误的,因为它不适用于批量删除(例如管理员的“删除所选”功能)。例如,MyModel.objects.all()[0].delete()将删除文件,而MyModel.objects.all().delete()不会删除。使用信号。
Antony Hatchkins

1

确保使用post_delete是正确的方法。有时候,虽然事情可能会出错,但文件不会被删除。当然,在使用post_delete之前,您可能有一堆旧文件没有被删除。我创建了一个函数,该函数根据对象引用的文件不存在然后删除对象来删除对象的文件,如果文件中没有对象,则删除对象,也可以基于“活动”标志删除对象。对象..我添加到大多数模型中的东西。您必须向其传递要检查的对象,对象文件的路径,文件字段以及删除不活动对象的标志:

def cleanup_model_objects(m_objects, model_path, file_field='image', clear_inactive=False):
    # PART 1 ------------------------- INVALID OBJECTS
    #Creates photo_file list based on photo path, takes all files there
    model_path_list = os.listdir(model_path)

    #Gets photo image path for each photo object
    model_files = list()
    invalid_files = list()
    valid_files = list()
    for obj in m_objects:

        exec("f = ntpath.basename(obj." + file_field + ".path)")  # select the appropriate file/image field

        model_files.append(f)  # Checks for valid and invalid objects (using file path)
        if f not in model_path_list:
            invalid_files.append(f)
            obj.delete()
        else:
            valid_files.append(f)

    print "Total objects", len(model_files)
    print "Valid objects:", len(valid_files)
    print "Objects without file deleted:", len(invalid_files)

    # PART 2 ------------------------- INVALID FILES
    print "Files in model file path:", len(model_path_list)

    #Checks for valid and invalid files
    invalid_files = list()
    valid_files = list()
    for f in model_path_list:
        if f not in model_files:
            invalid_files.append(f)
        else:
            valid_files.append(f)
    print "Valid files:", len(valid_files)
    print "Files without model object to delete:", len(invalid_files)

    for f in invalid_files:
        os.unlink(os.path.join(model_path, f))

    # PART 3 ------------------------- INACTIVE PHOTOS
    if clear_inactive:
        #inactive_photos = Photo.objects.filter(active=False)
        inactive_objects = m_objects.filter(active=False)
        print "Inactive Objects to Delete:", inactive_objects.count()
        for obj in inactive_objects:
            obj.delete()
    print "Done cleaning model."

这是您可以使用的方式:

photos = Photo.objects.all()
photos_path, tail = ntpath.split(photos[0].image.path)  # Gets dir of photos path, this may be different for you
print "Photos -------------->"
cleanup_model_objects(photos, photos_path, file_field='image', clear_inactive=False)  # image file is default

0

确保在文件之前写“ self ”。所以上面的例子应该是

def delete(self, *args, **kwargs):
        self.somefile.delete()

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

我已经忘记了文件之前的“自我”,并且在全局命名空间中查找时仍然无法正常工作。



0

Django 2.x解决方案:

无需安装任何软件包!在Django 2中非常容易处理。我已经尝试过使用Django 2和SFTP存储的以下解决方案(但是我认为它可以与任何存储一起使用)

首先编写一个自定义管理器。因此,如果您希望能够使用objects方法删除模型文件,则必须编写并使用[Custom Manager] [3](用于的覆盖delete()方法objects):

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

现在,您必须先删除,image然后再删除模型本身,然后再将模型分配CustomManager给模型,您必须objects在模型内部进行初始化:

class MyModel(models.Model):
    image = models.ImageField(upload_to='/pictures/', blank=True)
    objects = CustomManager() # add CustomManager to model
    def delete(self, using=None, keep_parents=False):

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

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

-1

我可能有一个特殊情况,因为我在具有动态目录名称的文件字段上使用了upload_to选项,但是我发现的解决方案是使用os.rmdir。

在型号中:

import os

...

class Some_Model(models.Model):
     save_path = models.CharField(max_length=50)
     ...
     def delete(self, *args,**kwargs):
          os.rmdir(os.path.join(settings.MEDIA_ROOT, self.save_path)
          super(Some_Model,self).delete(*args, **kwargs)

1
这是一个非常糟糕的主意。您不仅会删除整个目录,而且会删除单个文件(可能会影响其他文件),即使实际删除对象失败也会这样做。
tbm

如果您正在解决我遇到的问题,这不是一个坏主意;)正如我提到的,我有一个独特的用例,其中要删除的模型是父模型。子级将文件写入父级文件夹,因此,如果删除父级,则所需的行为是删除该文件夹中的所有文件。虽然在操作顺序上很重要。当时我没有想到。
carruthd '17

我仍然希望在删除子级后删除单个子级文件;那么如果需要,您可以在父目录为空时将其删除。
tbm

这在您取出子对象时很有意义,但是如果父对象被破坏,则一次穿过一个子对象似乎很乏味且不必要。无论如何,我现在看到的是我给出的答案还不足以解决OP问题。感谢您的评论,您让我开始考虑使用一种不太钝的工具。
carruthd '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.