Замена изображения Django не удаляет оригинал - PullRequest
24 голосов
/ 09 декабря 2010

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

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

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

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

Ответы [ 8 ]

26 голосов
/ 01 декабря 2011

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

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 и других, чтобы проверить, правильно ли удалены внутренние файлы. Верь только тому, что ты тестируешь .

13 голосов
/ 04 февраля 2013

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

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

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

8 голосов
/ 04 июля 2012

Код в следующем рабочем примере после загрузки изображения в 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)
2 голосов
/ 12 сентября 2012

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

1 голос
/ 28 ноября 2014

Ниже приведен код, который может работать с 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)

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

1 голос
/ 17 января 2011

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

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

0 голосов
/ 01 ноября 2018

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

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)
0 голосов
/ 17 января 2011

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

import os

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