DjangoはFileFieldを削除します


91

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)

次に、「pythonmanage.pyシェル」でこれを行います。

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

データベースからは削除されますが、サーバー上のファイルは削除されません。他に何を試すことができますか?

ありがとう!


default_storageを直接使用するのはどうですか?docs.djangoproject.com/en/dev/topics/files
MGP

回答:


141

Django 1.3より前は、対応するモデルインスタンスを削除すると、ファイルはファイルシステムから自動的に削除されていました。おそらく新しいバージョンのDjangoを使用しているため、ファイルシステムからファイルを自分で削除するように実装する必要があります。

これはいくつかの方法で実行できます。そのうちの1つは、pre_deleteまたはpost_deleteシグナルを使用することです。

私が現在選択している方法は、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()(たとえば、を一括更新することによってQuerySet)新しいファイルにポイントすると、シグナルが実行されないため、古いファイルは横になり続けます。従来のファイル処理方法を使用する場合、これは発生しません。
  • 私が作成したアプリの1つには、このコードが本番環境に含まれていると思いますが、それでも自己責任で使用してください。
  • コーディングスタイル:この例ではfileフィールド名として使用していますが、組み込みのfileオブジェクト識別子と衝突するため、適切なスタイルではありません。

も参照してください

  • FieldFile.delete()Django 1.11モデルフィールドリファレンス(FieldFileクラスについて説明していますが.delete()、フィールドを直接呼び出すことに注意してください:FileField対応するFieldFileインスタンスへのインスタンスプロキシ、およびフィールドのメソッドであるかのようにそのメソッドにアクセスします)

    モデルが削除されても、関連ファイルは削除されないことに注意してください。孤立したファイルをクリーンアップする必要がある場合は、自分で処理する必要があります(たとえば、手動で実行するか、cronなどを介して定期的に実行するようにスケジュールできるカスタム管理コマンドを使用します)。

  • Djangoがファイルを自動的に削除しない理由:Django1.3のリリースノートへの入力

    以前のDjangoバージョンでは、を含むモデルインスタンスFileFieldが削除されるFileFieldと、それ自体がバックエンドストレージからファイルも削除していました。これにより、ロールバックされたトランザクションや、同じファイルを参照する異なるモデルのフィールドなど、いくつかのデータ損失シナリオへの扉が開かれました。Django 1.3では、モデルが削除されると、FileFielddelete()メソッドは呼び出されません。孤立したファイルのクリーンアップが必要な場合は、自分で処理する必要があります(たとえば、手動で実行するか、cronなどを介して定期的に実行するようにスケジュールできるカスタム管理コマンドを使用します)。

  • pre_delete信号のみの使用例


2
はい。ただし、適切なチェックを必ず行ってください。(
ちょっと待ってください

7
instance.song.delete(save=False)正しいdjangoストレージエンジンを使用しているため、おそらくを使用することをお勧めします。
エドゥアルド

1
最近では、コードをコピーすることはめったにありませんが、SOから直接自分で書くことはできず、限られた変更で機能します。素晴らしい助け、ありがとう!
GJStein 2015

インスタンスが存在するが、以前に画像が保存されていないos.path.isfile(old_file.path)場合old_file.path、エラーが発生するために失敗するというバグが見つかりました(フィールドにファイルが関連付けられていません)。if old_file:の呼び出しの直前に追加して修正しましたos.path.isfile()
three_pineapples 2016

@three_pineapplesは理にかなっています。ファイルフィールドのNOTNULL制約がバイパスされたか、ある時点で終了しなかった可能性があります。その場合、一部のオブジェクトでは空になります。
アントンストロガノフ2016

77

django-cleanupを試してください。モデルを削除すると、FileFieldでdeleteメソッドが自動的に呼び出されます。

pip install django-cleanup

settings.py

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

かっこいい、デフォルトでFileFieldに追加する必要があります。ありがとうございます。
megajoe

アップロード中にもファイルを削除しています
chiragsoni19年

ワオ。私はこれを起こさないようにしようとしていましたが、なぜそうなのか理解できませんでした。誰かがこの数年前にインストールし、それを忘れていました。ありがとう。
ryan285 6119

4
では、なぜDjangoはそもそもファイルフィールド削除機能を削除したのでしょうか?
ハヌル

あなたは伝説です!
marlonjd

31

.deleteDjango> = 1.10で以下に示すファイルフィールドのメソッドを呼び出すことで、ファイルシステムからファイルを削除できます。

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

7
受け入れられた答えであり、シンプルでうまく機能するはずです。
ニコライシンダ

14

モデルの削除関数を上書きしてファイルが存在するかどうかを確認し、スーパー関数を呼び出す前に削除することもできます。

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)

8
queryset.delete()このソリューションでは、呼び出しによってファイルがクリーンアップされないことに注意してください。クエリセットを繰り返し処理し、.delete()各オブジェクトを呼び出す必要があります。
スコットウッドオール2015

私はDjangoを初めて使用します。これは良いことですが、モデルがdeleteメソッドをオーバーライドした抽象クラスから継承している場合、これは抽象クラスからのそれをオーバーライドしませんか?信号を使用する方が私にはよく見えます
タイパン

8

Django 2.xソリューション:

これは、ファイルの削除を処理するために非常に簡単ですジャンゴ2。Django2とSFTPStorage、さらにFTP STORAGEを使用して次のソリューションを試しましたが、deleteメソッドを実装した他のストレージマネージャーで動作すると確信しています。(deleteメソッドはstorage抽象的なメソッドの1つです。)

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.song.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.song.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.song.name)
        super().delete()

これ.delete()で、objectsサブクエリの最後に使用できます。私は最も単純なものを書きましたが、CustomManager削除したオブジェクトや必要なものについて何かを返すことで、より適切に行うことができます。


1
ええ、私が質問を投稿してから、彼らはその機能を追加したと思います。
マルコスアグア

1
それでも削除は、Song.objects.all()。delete()を呼び出すwennとは呼ばれません。インスタンスがon_delete = models.CASCADEによって削除される場合も同様です。
ヘイマン

@HeyMan私はそれを解決し、今の私の解決策を編集しました:)
Hamidreza

4

モデルが削除されるか、新しいファイルがアップロードされるたびに古いファイルを削除するアプリは次のとおりです: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)

3

@アントンストロガノフ

ファイルが変更されたときにコードに何かが欠けています。新しいファイルを作成するとエラーが発生します。これは、新しいファイルであるため、パスが見つかりませんでした。関数のコードを変更し、try / exception文を追加しましたが、うまく機能します。

@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で何かが変更された可能性があります。try:ただし、ブロック内の特定の例外をキャッチすることをお勧めします(AttributeErrorおそらく?)。
アントンストロガノフ2013

別のストレージ(Amazon S3など)に移行すると問題が発生するため、osライブラリを使用することはあまりお勧めできません。
Igor Pomaranskiy 2014

@IgorPomaranskiy os.removeを使用すると、Amazon S3のようなストレージで何が起こりますか?
ダニエルゴンザレスフェルナンデス

@DanielGonzálezFernández失敗すると思います(存在しないパスに関する何かのようなエラーで)。そのため、Djangoはストレージに抽象化を使用します。
Igor Pomaranskiy

0

このコードは、新しい画像(ロゴフィールド)をアップロードするたびに実行され、ロゴがすでに存在するかどうかを確認し、存在する場合は閉じてディスクから削除します。もちろん、レシーバー機能でも同じ手順を実行できます。お役に立てれば。

 #  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='')
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.