【问题标题】:How do I get Django Admin to delete files when I remove an object from the database/model?当我从数据库/模型中删除对象时,如何让 Django Admin 删除文件?
【发布时间】:2011-07-19 09:06:11
【问题描述】:

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

【问题讨论】:

  • 嗯,其实应该的。检查上传文件夹的文件权限(更改为 0777)。
  • Django 移除了自动删除功能(适用于看到上述评论的 Google 员工)。

标签: django file storage django-models imagefield


【解决方案1】:

试试django-cleanup

pip install django-cleanup

settings.py

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

【讨论】:

【解决方案2】:

Django 2.x 解决方案:

无需安装任何软件包!在 Django 2 中很容易处理。我尝试过使用 Django 2 和 SFTP 存储的以下解决方案(但我认为它适用于任何存储)

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

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

【讨论】:

    【解决方案3】:

    您可以接收pre_delete 或post_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)
    

    【讨论】:

    • 如果instance.file 字段不为空,或者它可以(至少尝试)删除整个 MEDIA_ROOT 目录,请务必添加检查。这甚至适用于ImageField(null=False) 字段。
    • 谢谢。一般来说,我建议使用post_delete 信号,因为在删除因任何原因而失败的情况下它更安全。然后既不会删除模型,也不会删除文件,以保持数据一致。如果我对post_delete 和pre_delete 信号的理解有误,请纠正我。
    • 请注意,如果您替换模型实例上的文件,这不会删除旧文件
    • 这对我在 Django 1.8 外部管理员中不起作用。有新的方法吗?
    • 它在 Django 1.11 中工作。您应该使用 pre_delete 信号,** post_delete** 将不起作用。
    【解决方案4】:

    如果您的项目中已经有许多未使用的文件并且想要删除它们,您可以使用 django 实用程序django-unused-media

    【讨论】:

      【解决方案5】:

      您需要删除delete 和update 上的实际文件。

      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)
      

      【讨论】:

      • 很好的解决方案!
      【解决方案6】:

      使用 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
      

      【讨论】:

        【解决方案7】:

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

        核心/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)
        """--------------------------------------------------------------------------"""
        

        核心/signals.py

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

        【讨论】:

          【解决方案8】:

          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 字段是我的Photo 模型中的ImageField。

          【讨论】:

          • 对于使用 Amazon S3 作为存储后端(通过 django-storages)的任何人,这个特定的答案都不起作用。您将获得NotImplementedError: This backend doesn't support absolute paths. 您可以通过将文件字段的名称传递给storage.delete() 而不是文件字段的路径来轻松解决此问题。例如,将此答案的最后两行替换为storage, name = photo.original_image.storage, photo.original_image.name 然后storage.delete(name)。
          • @Sean +1,我在 1.7 中使用该调整来删除由 django-imagekit 在 S3 上通过 django-storages 生成的缩略图。 docs.djangoproject.com/en/dev/ref/files/storage/… 。注意:如果您只是使用 ImageField(或 FileField),则可以改用 mymodel.myimagefield.delete(save=False)。 docs.djangoproject.com/en/dev/ref/files/file/…
          • @user2616836 你能在post_delete 上使用mymodel.myimagefield.delete(save=False) 吗?换句话说,我可以看到我可以删除文件,但是当具有图像字段的模型被删除时,你可以删除文件吗?
          • @eugene 是的,你可以,它有效(我不知道为什么)。在post_delete 你做instance.myimagefield.delete(save=False),注意instance 的使用。
          【解决方案9】:

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

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

          我忘记了文件前的“self”,它在全局命名空间中查找时不起作用。

          【讨论】:

            【解决方案10】:

            我可能有一个特殊情况,因为我在文件字段中使用带有动态目录名称的 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)
            

            【讨论】:

            • 这是一个非常糟糕的主意。您不仅会删除整个目录而不是单个文件(可能会影响其他文件),即使实际的对象删除失败,您也会这样做。
            • 如果您正在解决我遇到的问题,这不是一个坏主意;)正如我提到的,我有一个独特的用例,其中被删除的模型是父模型。孩子将文件写入父文件夹,因此如果您删除了父文件夹,则所需的行为是删除文件夹中的所有文件。不过,关于操作顺序的要点很好。当时我没有想到。
            • 我仍然希望在删除子文件时删除单个子文件;然后如果需要,可以在父目录为空时删除它。
            • 这是有道理的,因为您要取出子对象,但如果父对象被销毁,一次一个地遍历子对象似乎很乏味且没有必要。无论如何,我现在看到我给出的答案对 OP 问题不够具体。感谢 cmets,您让我考虑使用不那么生硬的乐器。
            【解决方案11】:

            此代码在 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)
            

            在删除模型之前获取存储和路径很重要,否则如果删除,后者也会持续无效。

            【讨论】:

            • 这对我不起作用(Django 1.5)和 Django 1.3 CHANGELOG 状态:“在 Django 1.3 中,删除模型时不会调用 FileField 的 delete() 方法。如果你需要清理孤立文件,您需要自己处理(例如,使用可以手动运行或计划通过例如 cron 定期运行的自定义管理命令)。”
            • 这个解决方案是错误的! delete 并不总是在删除行时调用,您必须使用信号。
            【解决方案12】:

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

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

            当然,FileField 自动删除被移除的原因同样适用于此。如果您删除在其他地方引用的文件,您将遇到问题。

            在我的情况下,这似乎很合适,因为我有一个专用的文件模型来管理我的所有文件。

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

            【讨论】:

            【解决方案13】:

            此功能将在 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)
            

            【讨论】:

            • 您是否有示例说明如何在模型中使用它来删除文件?我正在查看文档并查看如何从数据库中删除对象的示例,但没有看到任何文件删除实现。
            • 此方法是错误的,因为它不适用于批量删除(如管理员的“删除所选”功能)。例如MyModel.objects.all()[0].delete() 将删除文件,而MyModel.objects.all().delete() 不会。使用信号。
            猜你喜欢
            • 1970-01-01
            • 2020-09-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-01-03
            相关资源
            最近更新 更多