Django удалить FileField

93

Я создаю веб-приложение на 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

Ответы:

142

До 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)
  • Пограничный случай: если ваше приложение загружает новый файл и указывает экземпляр модели на новый файл без вызова 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«S 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 на поле файла было обойдено или не вышло в какой-то момент, и в этом случае у некоторых объектов оно будет пустым.
Антон Строгонов
78

Попробуйте django-cleanup , он автоматически вызывает метод удаления в FileField, когда вы удаляете модель.

pip install django-cleanup

settings.py

INSTALLED_APPS = (
     ...
    'django_cleanup', # should go after your apps
)
un1t
источник
Круто, его нужно добавить в FileField по умолчанию, спасибо!
megajoe
Он также
удаляет
Вау. Я пытался добиться, чтобы этого не произошло, и не мог понять, почему это произошло. Кто-то установил это много лет назад и забыл об этом. Спасибо.
ryan28561
4
Итак, почему Django вообще удалил функцию удаления файлового поля?
ha-neul
Вы легенда !!
marlonjd
32

Вы можете удалить файл из файловой системы с помощью .deleteметода вызова поля файла, показанного ниже, с Django> = 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()каждый объект.
Скотт Вудалл,
Я новичок в Django. Это хорошо, но что, если модель унаследовала от абстрактного класса, который переопределил метод удаления, не переопределит ли это это от абстрактного класса? Мне кажется, что использование сигналов лучше
theTypan
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методов, вы должны написать и использовать Custom Manager (просто для переопределения его запроса на удаление):

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 (). То же самое, когда экземпляр удаляется 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)
Lehins
источник
3

@Anton Strogonoff

Мне что-то не хватает в коде при изменении файла, если вы создаете новый файл, возникает ошибка, потому что новый файл не нашел путь. Я изменил код функции и добавил предложение try / except, и оно работает хорошо.

@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
Разработчик Java
источник
Я с этим не сталкивался - это может быть ошибка в моем коде или что-то измененное в Django. Я бы посоветовал уловить конкретное исключение в вашем try:блоке ( AttributeErrorвозможно?).
Антон Строгонов
Использовать библиотеку ОС не очень хорошая идея, поскольку вы столкнетесь с проблемами при переходе на другое хранилище (например, Amazon S3).
Игорь Помаранский 08
@IgorPomaranskiy, что произойдет в хранилище вроде Amazon S3 при использовании os.remove ??
Даниэль Гонсалес Фернандес
@ DanielGonzálezFernández Я предполагаю, что это не удастся (с ошибкой, как что-то о несуществующем пути). Вот почему Django использует абстракции для хранилищ.
Игорь Помаранский
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='')
LanfeaR
источник