Замена изображения Django не удаляет оригинал

В Django, если у вас есть ImageFile в модели, удаление удалит связанный файл с диска, а также удалит запись из базы данных.

Разве замена образа не должна также удалять ненужный файл с диска? Вместо этого я вижу, что он сохраняет оригинал и добавляет замену.

Теперь удаление объекта не удалит исходный файл, а только замену.

Есть ли хорошие стратегии для этого? Я не хочу иметь кучу потерянных файлов, если мои пользователи часто заменяют свои изображения.


person Shige Abe    schedule 09.12.2010    source источник
comment
Вы пытались использовать метод сохранения в модели, чтобы проверить, обновляется ли файл, и при необходимости удалить старый файл?   -  person PhoebeB    schedule 09.12.2010
comment
У меня похожая проблема, и я добавил вопрос по ней. Возможно, в ближайшие дни вы найдете то, что ищете...   -  person Ory Band    schedule 25.01.2011


Ответы (8)


Лучшая стратегия, которую я нашел, - это создать собственный метод сохранения в модели:

class Photo(models.Model):

    image = ImageField(...) # works with FileField also

    def save(self, *args, **kwargs):
        # delete old file when replacing by updating the file
        try:
            this = Photo.objects.get(id=self.id)
            if this.image != self.image:
                this.image.delete(save=False)
        except: pass # when new photo then we do nothing, normal case          
        super(Photo, self).save(*args, **kwargs)

И будьте осторожны, так как при обновлении, которое не удаляет серверный файл, удаление модели экземпляра (здесь фото) не удаляет серверный файл, во всяком случае, не в Django 1.3, вы для этого нужно добавить больше пользовательского кода (или регулярно выполнять какую-то грязную работу cron).

Наконец, проверьте все ваши случаи обновления/удаления с вашими отношениями ForeignKey, ManytoMany и другими, чтобы проверить, правильно ли удалены внутренние файлы. Верьте только тому, что проверяете.

person Geoffroy CALA    schedule 01.12.2011
comment
После почти 3 лет использования на 2 веб-сайтах я могу подтвердить, что этот метод готов к работе и не вызывает никаких проблем. Теперь я использую этот метод на третьем веб-сайте с Django 1.6.2. и до сих пор отлично работает. - person Geoffroy CALA; 24.02.2014
comment
использовал его в функциях save_model() и delete_model() класса ModelAdmin в django 1.9, и он работает как шарм - person samix73; 24.05.2016
comment
Я вижу, что к имени файла по-прежнему добавляется хэш-код, чтобы избежать конфликтов с исходным именем файла. Это может быть недостатком? например, если имя файла было указано как username.jpg, сначала хранилище сохраняется как username.jpg, но впоследствии оно обновляется до username_lksjdflaj.jpg. То есть разрешение конфликта имен файлов все же произошло... - person Pan Yan; 07.01.2017
comment
сегодня я протестировал ваше решение на Django 3.0.5, оно работает. Спасибо! - person IlConte; 22.04.2020

Разве замена образа не должна также удалять ненужный файл с диска?

Раньше FileField стремился очистить потерянные файлы. Но это изменилось в Django 1.2:

В более ранних версиях Django при удалении экземпляра модели, содержащего FileField, FileField брал на себя также удаление файла из внутреннего хранилища. Это открыло двери для нескольких потенциально серьезных сценариев потери данных, включая откаты транзакций и поля в разных моделях, ссылающиеся на один и тот же файл. В Django 1.2.5 FileField никогда не будет удалять файлы из внутреннего хранилища.

person Filip Dupanović    schedule 04.02.2013

Код в следующем рабочем примере после загрузки изображения в поле ImageField определит, существует ли файл с таким же именем, и в этом случае удалит этот файл перед сохранением нового.

Его можно легко изменить так, чтобы он удалял старый файл независимо от имени файла. Но это не то, что я хотел в своем проекте.

Добавьте следующий класс:

from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
    def _save(self, name, content):
        if self.exists(name):
            self.delete(name)
        return super(OverwriteStorage, self)._save(name, content)

    def get_available_name(self, name):
        return name

И используйте его с ImageField следующим образом:

class MyModel(models.Model):
    myfield = models.ImageField(
        'description of purpose',
        upload_to='folder_name',
        storage=OverwriteStorage(),  ### using OverwriteStorage here
        max_length=500,
        null=True,
        blank=True,
        height_field='height',
        width_field='width'
    )
    height = models.IntegerField(blank=True, null=True)
    width = models.IntegerField(blank=True, null=True)
person thnee    schedule 04.07.2012
comment
@Philip007Philip007 Хм, это было давно, я написал это .. но я почти уверен, что это имя файла? - person thnee; 28.05.2013
comment
что делает max_length в поле изображения?? - person dietbacon; 09.01.2015
comment
@dietbacon Из документации Django: экземпляры FileField создаются в вашей базе данных как столбцы varchar с максимальной длиной по умолчанию 100 символов. Как и в случае с другими полями, вы можете изменить максимальную длину с помощью аргумента max_length. - person thnee; 12.01.2015

Если вы не используете транзакции или не боитесь потерять файлы при откате транзакции, вы можете использовать django- очистка

person un1t    schedule 12.09.2012

Было несколько обращений по этой проблеме, хотя, скорее всего, она не попадет в ядро. Наиболее полным является http://code.djangoproject.com/ticket/11663. Патчи и комментарии к заявкам могут дать вам некоторое направление, если вы ищете решение.

Вы также можете рассмотреть возможность использования другого StorageBackend, такого как система хранения файлов с перезаписью, указанная во фрагменте 976 Django. .org/snippets/976/. Вы можете изменить хранилище по умолчанию на этот бэкэнд или переопределить его в каждом объявлении FileField/ImageField.

person Mark Lavin    schedule 17.01.2011
comment
Я думаю, что есть проблема с фрагментом 976. Рассмотрим модель UserProfile, в которой есть поле изображения профиля, использующее этот бэкэнд OverwriteStorage с upload_to=%Y/%m/%d. Два пользователя в один и тот же день загружают аватарку с именем me.jpg. Первая загрузка будет в 2012/02/11/me.jpg. Вторая загрузка удалит и заменит это изображение и приведет к тому, что два поля изображения UserProfile будут ссылаться на одно и то же изображение. - person chris; 11.02.2012
comment
Ожидаемое поведение OverwriteStorage — заменить файл. Это означает, что обеспечение уникальности имени ложится на плечи разработчика сайта, а не серверной части. - person Mark Lavin; 11.02.2012

Вот код, который может работать с upload_to=... или blank=True или без них, а также когда отправленный файл имеет то же имя, что и старый.

(синтаксис py3, протестирован на Django 1.7)

class Attachment(models.Model):

    document = models.FileField(...)  # or ImageField

    def delete(self, *args, **kwargs):
        self.document.delete(save=False)
        super().delete(*args, **kwargs)

    def save(self, *args, **kwargs):
        if self.pk:
            old = self.__class__._default_manager.get(pk=self.pk)
            if old.document.name and (not self.document._committed or not self.document.name):
                old.document.delete(save=False)
        super().save(*args, **kwargs)

Помните, что такое решение применимо только в том случае, если вы находитесь в нетранзакционном контексте (без отката, потому что файл окончательно потерян)

person DjPostman    schedule 28.11.2014
comment
для тех, кто все еще использует python 2.x, используйте super(Attachment,self) вместо super() - person Deep 3015; 21.05.2017

Я использовал простой метод с popen, поэтому, когда я сохраняю свою модель Info, я удаляю прежний файл перед связыванием с новым:

import os

try:
    os.popen("rm %s" % str(info.photo.path))
except:
    #deal with error
    pass
info.photo = nd['photo']
person Mermoz    schedule 17.01.2011

Сохраняю исходный файл и если он изменился - удаляю.

class Document(models.Model):
    document = FileField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._document = self.document

    def save(self, *args, **kwargs):
        if self.document != self._document:
            self._document.delete()
            super().save(*args, **kwargs)
person Victor K    schedule 01.11.2018