Django видаляє FileField


91

Я створюю веб-програму в Django. У мене є модель, яка завантажує файл, але я не можу його видалити. Ось мій код:

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" я роблю це:

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

Він видаляє з бази даних, але не файл на сервері. Що ще я можу спробувати?

Дякую!


А як щодо безпосереднього використання default_storage? docs.djangoproject.com/en/dev/topics/files
MGP

Відповіді:


141

До Django 1.3 файл видалявся з файлової системи автоматично, коли ви видаляли відповідний екземпляр моделі. Ймовірно, ви використовуєте новішу версію Django, тому вам доведеться самостійно здійснити видалення файлу з файлової системи.

Ви можете зробити це кількома способами, один із яких використовує сигнал pre_deleteабо post_delete.

Приклад

Наразі моїм методом вибору є поєднання сигналів post_deleteі pre_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)
  • Випадок Edge: якщо ваш додаток завантажує новий файл і вказує екземпляр моделі на новий файл без виклику save()(наприклад, шляхом масового оновлення a QuerySet), старий файл буде продовжувати лежати, оскільки сигнали не запускатимуться. Це не трапляється, якщо ви використовуєте звичайні методи обробки файлів.
  • Я думаю, що в одній із створених мною програм є цей код у виробництві, але тим не менше я використовую його на власний ризик.
  • Стиль кодування: цей приклад використовує fileяк ім'я поля, що не є вдалим стилем, оскільки воно зіткнеться з вбудованим fileідентифікатором об'єкта.

Дивіться також

  • FieldFile.delete()у посиланні на поле моделі Django 1.11 (зверніть увагу, що воно описує FieldFileклас, але ви зателефонуєте .delete()безпосередньо до поля: FileFieldпроксі-сервери екземпляра до відповідного FieldFileекземпляра, і ви отримуєте доступ до його методів, як якщо б вони були полями)

    Зверніть увагу, що при видаленні моделі пов'язані файли не видаляються. Якщо вам потрібно очистити осиротілі файли, вам доведеться обробляти це самостійно (наприклад, за допомогою спеціальної команди управління, яку можна запускати вручну або планувати періодичну роботу, наприклад, cron).

  • Чому Django не видаляє файли автоматично: запис у примітках до випуску для Django 1.3

    У попередніх версіях Django, коли примірник моделі, що містив a, FileFieldбув видалений, FileFieldвін взяв на себе також можливість видалити файл із внутрішньої пам’яті. Це відкрило двері для декількох сценаріїв втрати даних, включаючи відкотированние транзакції та поля на різних моделях, що посилаються на один і той же файл. У Django 1.3, коли модель видаляється FileField, delete()метод ' не буде викликаний. Якщо вам потрібно очищення застарілих файлів, вам доведеться обробляти це самостійно (наприклад, за допомогою власної команди управління, яку можна запускати вручну або планувати періодичну роботу, наприклад, cron).

  • Приклад використання лише pre_deleteсигналу


2
Так, але обов’язково виконайте відповідні перевірки. (Дайте мені секунду, я опублікую код, який я знайшов у використанні в реальній системі.)
Антон Строгонов,

7
Ймовірно, це краще використовувати instance.song.delete(save=False), оскільки воно використовує правильний механізм зберігання django.
Едуардо

1
Сьогодні рідко я копіюю код, але я не міг би писати себе безпосередньо з SO, і це працює з обмеженими змінами. Фантастична допомога, дякую!
GJStein

Знайдено помилку в цьому, якщо екземпляр існує, але жодне зображення раніше не було збережено, то os.path.isfile(old_file.path)не вдається, оскільки old_file.pathвикликає помилку (із полем не пов'язано жодного файлу). Я виправив це, додавши if old_file:безпосередньо перед викликом до os.path.isfile().
three_pineapples

@three_pineapples має сенс. Можливо, обмеження NOT NULL у полі файлу було обійдено або в якийсь момент не вийшло, і в цьому випадку деякі об’єкти мали б його порожнім.
Антон Строгонов

77

Спробуйте django-cleanup , він автоматично викликає метод видалення на FileField при видаленні моделі.

pip install django-cleanup

settings.py

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

Класно, його потрібно додати до FileField за замовчуванням, дякую!
megajoe

Він також видаляє файл під час завантаження
chirag soni

Ого. Я намагався, щоб цього не сталося, і я не міг зрозуміти, чому це було. Хтось встановив це багато років тому і забув про це. Дякую.
ryan28561

4
Отже, чому Django взагалі видалив функцію видалення файлового поля?
ха-

Ви легенда !!
marlonjd

31

Ви можете видалити файл із файлової системи за допомогою .deleteметоду виклику поля файлу, як показано нижче з Django> = 1.10:

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

6
Повинна бути прийнята відповідь, проста і справедлива.
Микола Шиндаров

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()кожен об’єкт.
Скотт Вудолл

Я новачок у Джанго. Це добре, але що, якби модель успадковувала абстрактний клас, який перевизначив метод видалення, чи не замінить це абстрактний клас? Використання сигналів мені здається кращим
Типан

8

Рішення 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.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
Однак видалення не називається wenn, викликаючи Song.objects.all (). Delete (). Те саме, коли Екземпляр видаляється on_delete = models.CASCADE.
HeyMan

@HeyMan Я вирішив це і відредагував своє рішення прямо зараз :)
Хамідреза

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

@Anton Strogonoff

Я щось пропускаю в коді при зміні файлу. Якщо ви створюєте новий файл, генерує помилку, оскільки новий файл не знайшов шлях. Я змінив код функції та додав пропозицію try / Osim, і це працює добре.

@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можливо?).
Антон Строгонов

Не дуже хороша ідея використовувати бібліотеку os, оскільки у вас виникнуть проблеми, якщо ви перейдете на інше сховище (Amazon S3, наприклад).
Ігор Помаранський

@IgorPomaranskiy, що станеться в сховищі, як Amazon S3, коли ти використовуєш os.remove ??
Даніель Гонсалес Фернандес

@ DanielGonzálezFernández Я думаю, це не вдасться (з помилкою, як щось про неіснуючий шлях). Ось чому Джанго використовує абстракції для зберігання.
Ігор Помаранський

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='')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.