データベース/モデルからオブジェクトを削除するときに、Django Adminにファイルを削除させるにはどうすればよいですか?


85

標準のImageFieldで1.2.5を使用し、組み込みのストレージバックエンドを使用しています。ファイルは正常にアップロードされますが、管理者からエントリを削除しても、サーバー上の実際のファイルは削除されません。


うーん、実際にはそうすべきです。アップロードフォルダのファイル権限を確認してください(0777に変更してください)。
Torsten Engelbrecht 2011年

5
Djangoは自動削除機能を削除しました(上記のコメントを見たGoogle社員向け)。
マーク

回答:


100

pre_deleteorpost_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

47
ありがとう。一般的に、post_delete何らかの理由で削除が失敗した場合の方が安全であるため、シグナルを使用することをお勧めします。次に、モデルもファイルも削除されず、データの一貫性が保たれます。私の理解post_deletepre_deleteシグナルが間違っている場合は、私を訂正してください。
toto_tico 2013

9
モデルインスタンスのファイルを置き換えても、古いファイルは削除されないことに注意してください
マークを付け

3
これは、管理者以外のDjango1.8では機能しません。それを行うための新しい方法はありますか?
カリフ2015

驚くばかり。長い間これを探していました
RL Shyam 2017年

46

django-cleanupをお試しください

pip install django-cleanup

settings.py

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

1
とても素晴らしいパッケージです。ありがとうございました!:)
ボージャックホースマン2015

3
限定的なテストの後、このパッケージがDjango1.10で引き続き機能することを確認できます。
coderGuy123 2016

1
いいですね、これはとても簡単です
Tunn 2017

いいね。Django2.0で動作します。また、ストレージバックエンド(django-storages.readthedocs.io/en/latest/backends/…)としてS3を使用しており、S3からファイルを削除しています。
ルートバーン2018年

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
(django-storagesを介して)ストレージバックエンドとしてAmazon S3を使用している人にとって、この特定の答えは機能しません。あなたは買ってあげるNotImplementedError: This backend doesn't support absolute paths.あなたが簡単にファイルのフィールドの名前を渡すことで、この問題を解決することができstorage.delete()代わりに、ファイルフィールドのパスの。たとえば、この回答の最後の2行をstorage, name = photo.original_image.storage, photo.original_image.namethenに置き換えstorage.delete(name)ます。
ショーンアズリン2014

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?つまり、ファイルを削除できることはわかりますが、イメージフィールドを持つモデルが削除されたときにファイルを削除できますか?
eugene 2015年

1
@eugeneはい、できます。機能します(理由はわかりませんが)。ではpost_deleteあなたはinstance.myimagefield.delete(save=False)、の使用を注意してくださいinstance
user2616836 2015年

17

このコードは、管理パネルを備えたDjango1.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は次のように述べています。「Django1.3では、モデルが削除されると、FileFieldのdelete()メソッドは呼び出されません。孤立したファイルのクリーンアップが必要な場合は、自分で処理する必要があります(たとえば、手動で実行したり、cronなどを介して定期的に実行するようにスケジュールしたりできるカスタム管理コマンドを使用します)。」
darrinm 2013年

4
この解決策は間違っています!delete行が削除されたときに常に呼び出されるとは限らないため、シグナルを使用する必要があります。
lvella 2013

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
デフォルトではモデルがdbに保存されるpost_deleteため、おそらく機能しません。docs.djangoproject.com/ en / 1.3 / ref / models / fieldsfile_field.delete()file_field.delete(False)
Adam Jurczyk 2012年

3

多分それは少し遅いです。しかし、私にとって最も簡単な方法は、post_saveシグナルを使用することです。シグナルはQuerySetの削除プロセス中でも実行されますが、[model] .delete()メソッドはQuerySetの削除プロセスでは実行されないため、オーバーライドするのに最適なオプションではないことを覚えておいてください。

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 / signal.py

import os

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

1

この機能はDjango1.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()ません。信号を使用します。
アントニーハッチキンス2013

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ソリューション:

パッケージをインストールする必要はありません!Django2での処理は非常に簡単ですます。Django2とSFTPストレージを使用して次のソリューションを試しました(ただし、どのストレージでも機能すると思います)

まず、カスタムマネージャーを作成します。したがって、objectsメソッドを使用してモデルのファイルを削除できるようにする場合は、[カスタムマネージャー] [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 2017

私が抱えていた問題に取り組んでいるのであれば、それは悪い考えではありません;)先に述べたように、削除されるモデルが親モデルであるというユニークなユースケースがありました。子は親フォルダーにファイルを書き込んだため、親を削除した場合、フォルダー内のすべてのファイルが削除されるという望ましい動作が行われました。ただし、操作の順序には良い点があります。当時、それは私には起こりませんでした。
carruthd 2017

子が削除されたときに、個々の子ファイルを削除したいのですが。次に、必要に応じて、親ディレクトリが空のときに削除できます。
tbm 2017

子オブジェクトを取り出すのでそれは理にかなっていますが、親オブジェクトが破壊された場合、一度に1つずつ子をステップスルーするのは面倒で不必要に思えます。とにかく、私が出した答えはOPの質問に十分具体的ではなかったことがわかりました。コメントありがとうございます。今後は鈍器の使用を考えさせられました。
carruthd 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.