Django как удалить файл

Обновлено: 07.07.2024

Это перевод постов из одного интересного блога. Оригинал ч.1, ч.2 и ч.3. Описанный подход работает для Django 1.3 и выше.

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

  1. Файла прописанные в коде и в шаблонах, назовем их STATIC файлы.
  2. Файлы, который используются в коде, но известны только в процессе работы кода, назовем их MEDIA файлы. Прим.: например файлы загруженные пользователем. Имя файлы мы не знаем и в коде оно не прописано.

Это уловная классификация и основана на определениях из документации Django.

Настройка

Рассмотрим один из вариантов настройки:

Что такое _PATH должно быть понятно. В проекте находится папка files , которая содержит папку media для MEDIA файлов и папка static для STATIC файлов. Очень важно разделить их таким образом. Потом можете получить много проблем не разделив их изначально. Это позволит вам легко использовать различные версии файлов, расположив их по разным папкам.

Но почему files в папке проекта? Потому что это удобно при разработке. На "продакшине" рекомендуется вынести files из проекта и изменить MEDIA_ROOT и STATIC_ROOT .

STATIC файлы располагаются в <_PATH>/static . Это используемые js и css файлы. Приложение django.contrib.staticfiles соберет все STATIC файлы из STATICFILES_DIRS и из соответствующих папок приложений и перенесет в STATIC_ROOT .

  • В STATIC добавляются файлы из проекта(смотрите STATICFILES_DIRS и FileSystemFinder ), а так же AppDirectoriesFinder добавит файлы из папки static в приложениях.
  • MEDIA и STATIC файла лучше разделить(смотрите STATIC_ROOT и MEDIA_ROOT ).
  • STATIC_ROOT и MEDIA_ROOT лучше вынести из проекта на "продакшине".

И не забудьте добавить "слеш" в конце STATIC_URL и MEDIA_URL !

Как настроить выдачу файлов для dev-сервера

Как упоминалось выше, лучше отдавать файлы через специальный сервер, например Nginx или Apache. Но при разработке это лишние проблемы. Можно легко настроить выдачу файлов средствами Django. Предполагается что при разработке DEBUG всегда True , а на сервере - False . Добавим в urls.py:

Как использовать STATIC файлы в шаблонах

Как использовать MEDIA файлы в шаблоне

Это на много проще. Объект FieldFile , возвращаемый ImageField и FileField , содержит path и url :

Как добавить STATIC файлы

Так как имена STATIC файлов используются в коде, шаблонах, лучше всего хранить их в VCS вместе с кодом. STATIC файлы собираются(находят и копируются или через simlink) с помощью "finder"-ов. Вам не обязательно знать как все это происходит, главное хранить файлы в нужном месте.

При разработке независимого приложения для Django файлы нужно хранить в папке static в вашем приложении:

Файлы, которые относятся конкретно к проекту нужно хранить в папка указанных в STATICFILES_DIRS :

Как загрузить MEDIA файлы

MEDIA файлы в основном используются в моделях с полями FileField и ImageField. Например:

I'm building a web app in Django. I have a model that uploads a file, but I can not delete the file. Here is my code:

Then, in python manage.py shell I do this:

It deletes the record from the database but not the file on server. What else can I try?


51k 15 15 gold badges 98 98 silver badges 109 109 bronze badges 5,713 7 7 gold badges 25 25 silver badges 57 57 bronze badges

9 Answers 9

Before Django 1.3, the file was deleted from the filesystem automatically when you deleted the corresponding model instance. You are probably using a newer Django version, so you'll have to implement deleting the file from the filesystem yourself.

Simple signal-based sample

My method of choice at the time of writing is a mix of post_delete and pre_save signals, which makes it so that obsolete files are deleted whenever corresponding models are deleted or have their files changed.

Based on a hypothetical MediaFile model:

  • I think one of the apps I’ve built a while back used this code in production, but nevertheless use at your own risk.
  • For example, there’s a possible data loss scenario: your data might end up referencing a nonexistent file if your save() method call happens to be within a transaction that gets rolled back. You could consider wrapping file-removing logic into transaction.on_commit() , along the lines of transaction.on_commit(lambda: os.remove(old_file.path)) , as suggested in Mikhail’s comment. django-cleanup library does something along those lines.
  • Edge case: if your app uploads a new file and points model instance to the new file without calling save() (e.g. by bulk updating a QuerySet ), the old file will keep lying around because signals won’t be run. This doesn’t happen if you use conventional file handling methods.
  • Coding style: this example uses file as field name, which is not a good style because it clashes with the built-in file object identifier.

Addendum: periodic cleanup

Realistically, you may want to also run a periodic task to handle orphan file cleanup in case a runtime failure prevents some file from being removed. With that in mind, you could probably get rid of signal handlers altogether, and make such a task the mechanism for dealing with insensitive data and not-so-large files.

Either way though, if you are handling sensitive data, it’s always better to double- or triple- check that you never fail to timely delete data in production to avoid any associated liabilities.

See also

FieldFile.delete() in Django 1.11 model field reference (note that it describes the FieldFile class, but you’d call .delete() directly on the field: FileField instance proxies to the corresponding FieldFile instance, and you access its methods as if they were field’s)

Note that when a model is deleted, related files are not deleted. If you need to cleanup orphaned files, you’ll need to handle it yourself (for instance, with a custom management command that can be run manually or scheduled to run periodically via e.g. cron).

Why Django doesn’t delete files automatically: entry in release notes for Django 1.3


Рано или поздно перед разработчиками встаёт задача удаления ненужных данных. И чем сложнее сервис, тем больше нюансов необходимо учесть. В данной статье я расскажу, как мы реализовали «удаление» в базе данных с сотней связей.

Предыстория

Все проверки и инциденты журналируются для отслеживания динамики характеристик серверов, поэтому объем базы данных достиг порядка сотни миллионов записей. Периодически появляются новые серверы, а старые перестают использоваться. Информацию о неиспользуемых серверах необходимо удалить из системы Monitoring, чтобы: а) не перегружать интерфейс лишней информацией, и б) освободить уникальные идентификаторы.

Удаление

Я не зря в заголовке статьи слово «удаление» написал в кавычках. Убрать объект из системы можно несколькими способами:

  • полностью удалив из базы данных;
  • пометив объекты как удалённые и скрыв из интерфейса. В качестве маркера можно использовать Boolean, или DateTime для более точного журналирования.

Изначально использовался первый подход, когда мы просто выполняли object.delete() и объект удалялся со всеми зависимостями. Но со временем нам пришлось отказаться от такого подхода, так как один объект мог иметь зависимости с миллионами других объектов, и каскадное удаление жёстко блокировало таблицы. А так как сервис каждую секунду выполняет по тысяче проверок и журналирует их, то блокировка таблиц приводила к серьёзному замедлению сервиса, что было недопустимо для нас.

Для избежания долгих блокировок мы решили удалять данные порциями. Это позволило бы в промежутки времени между удалениями объектов записывать актуальные данные мониторинга. Список всех объектов, которые будут удалены каскадно, можно получить методом, который применяется в панели администратора при удалении объекта (при подтверждении удаления):


Ситуация улучшилась: нагрузка распределилась по времени, новые данные стали записываться быстрее. Но мы сразу же натолкнулись на следующий подводный камень. Дело в том, что список удаляемых объектов формируется в самом начале удаления, и если в процессе «порционного» удаления добавляются новые зависимые объекты, то родительский элемент не может быть удалён.

Мы сразу отказались от идеи при ошибке в рекурсивном удалении снова собирать данные о новых зависимостях или запрещать добавлять зависимые записи при удалении, потому что а) можно уйти в бесконечный цикл или б) придётся найти по всему коду добавления всех зависимых объектов.

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

Тогда мы решили использовать декораторы для переопределения менеджера запросов. Далее лучше увидеть код, чем писать сотню слов.


Декоратор exclude_objects_for_deleted_hosts(fields) для указанных полей модели fields автоматически для каждого запроса добавляет фильтр exclude , который как раз убирает записи, которые не должны отображаться в интерфейсе.

Теперь достаточно для всех моделей, на которые каким-либо образом повлияет удаление, добавить декоратор:


Теперь для того, чтобы удалить объект Host , достаточно изменить атрибут is_deleted :


Все запросы будут автоматически исключать записи, ссылающиеся на удаленные объекты:


Получается такой SQL-запрос:


Как видно, в запросе добавлены дополнительные join'ы для указанных в декораторе полей и проверки `is_deleted` = TRUE .

Немного о цифрах

Логично, что дополнительные join'ы и условия увеличивают время выполнения запроса. Исследование этого вопроса показало, что степень «осложнения» зависит от структуры БД, количества записей и наличия индексов.

Конкретно в нашем случае за каждый уровень зависимости запрос штрафуется примерно на 30 %. Это максимальный штраф, которой мы получаем на самой большой таблице с миллионами записей, на таблицах поменьше штраф снижается до нескольких процентов. Благо, у нас настроены необходимые индексы, а для большинства критичных запросов необходимые join'ы уже были, поэтому большой разницы в производительности мы не ощутили.

Уникальные идентификаторы

Перед тем, как удалить данные, возможно, потребуется освободить идентификаторы, которые планируется использовать в будущем, потому что это может породить ошибку неуникального значения при создании нового объекта. Несмотря на то, что в Django-приложении не будет видно удалённых объектов, они всё равно будут находиться в базе данных. Поэтому для удаляемых объектов к идентификатору мы дописывает uuid.

Эксплуатация

Для каждой новой модели или зависимости необходимо обновить декоратор, если он нужен. Для упрощения поиска зависимых моделей мы написали «умный» тест:


Тест рекурсивно проверяет все модели на наличие зависимости от удаляемой модели, потом смотрит, был ли для данной модели установлен декоратор на требуемые поля. Если что-то пропущено, тест деликатно подскажет, куда нужно добавить декоратор.


Рано или поздно перед разработчиками встаёт задача удаления ненужных данных. И чем сложнее сервис, тем больше нюансов необходимо учесть. В данной статье я расскажу, как мы реализовали «удаление» в базе данных с сотней связей.

Предыстория

Все проверки и инциденты журналируются для отслеживания динамики характеристик серверов, поэтому объем базы данных достиг порядка сотни миллионов записей. Периодически появляются новые серверы, а старые перестают использоваться. Информацию о неиспользуемых серверах необходимо удалить из системы Monitoring, чтобы: а) не перегружать интерфейс лишней информацией, и б) освободить уникальные идентификаторы.

Удаление

Я не зря в заголовке статьи слово «удаление» написал в кавычках. Убрать объект из системы можно несколькими способами:

  • полностью удалив из базы данных;
  • пометив объекты как удалённые и скрыв из интерфейса. В качестве маркера можно использовать Boolean, или DateTime для более точного журналирования.

Изначально использовался первый подход, когда мы просто выполняли object.delete() и объект удалялся со всеми зависимостями. Но со временем нам пришлось отказаться от такого подхода, так как один объект мог иметь зависимости с миллионами других объектов, и каскадное удаление жёстко блокировало таблицы. А так как сервис каждую секунду выполняет по тысяче проверок и журналирует их, то блокировка таблиц приводила к серьёзному замедлению сервиса, что было недопустимо для нас.

Для избежания долгих блокировок мы решили удалять данные порциями. Это позволило бы в промежутки времени между удалениями объектов записывать актуальные данные мониторинга. Список всех объектов, которые будут удалены каскадно, можно получить методом, который применяется в панели администратора при удалении объекта (при подтверждении удаления):


Ситуация улучшилась: нагрузка распределилась по времени, новые данные стали записываться быстрее. Но мы сразу же натолкнулись на следующий подводный камень. Дело в том, что список удаляемых объектов формируется в самом начале удаления, и если в процессе «порционного» удаления добавляются новые зависимые объекты, то родительский элемент не может быть удалён.

Мы сразу отказались от идеи при ошибке в рекурсивном удалении снова собирать данные о новых зависимостях или запрещать добавлять зависимые записи при удалении, потому что а) можно уйти в бесконечный цикл или б) придётся найти по всему коду добавления всех зависимых объектов.

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

Тогда мы решили использовать декораторы для переопределения менеджера запросов. Далее лучше увидеть код, чем писать сотню слов.


Декоратор exclude_objects_for_deleted_hosts(fields) для указанных полей модели fields автоматически для каждого запроса добавляет фильтр exclude , который как раз убирает записи, которые не должны отображаться в интерфейсе.

Теперь достаточно для всех моделей, на которые каким-либо образом повлияет удаление, добавить декоратор:


Теперь для того, чтобы удалить объект Host , достаточно изменить атрибут is_deleted :


Все запросы будут автоматически исключать записи, ссылающиеся на удаленные объекты:


Получается такой SQL-запрос:


Как видно, в запросе добавлены дополнительные join'ы для указанных в декораторе полей и проверки `is_deleted` = TRUE .

Немного о цифрах

Логично, что дополнительные join'ы и условия увеличивают время выполнения запроса. Исследование этого вопроса показало, что степень «осложнения» зависит от структуры БД, количества записей и наличия индексов.

Конкретно в нашем случае за каждый уровень зависимости запрос штрафуется примерно на 30 %. Это максимальный штраф, которой мы получаем на самой большой таблице с миллионами записей, на таблицах поменьше штраф снижается до нескольких процентов. Благо, у нас настроены необходимые индексы, а для большинства критичных запросов необходимые join'ы уже были, поэтому большой разницы в производительности мы не ощутили.

Уникальные идентификаторы

Перед тем, как удалить данные, возможно, потребуется освободить идентификаторы, которые планируется использовать в будущем, потому что это может породить ошибку неуникального значения при создании нового объекта. Несмотря на то, что в Django-приложении не будет видно удалённых объектов, они всё равно будут находиться в базе данных. Поэтому для удаляемых объектов к идентификатору мы дописывает uuid.

Эксплуатация

Для каждой новой модели или зависимости необходимо обновить декоратор, если он нужен. Для упрощения поиска зависимых моделей мы написали «умный» тест:


Тест рекурсивно проверяет все модели на наличие зависимости от удаляемой модели, потом смотрит, был ли для данной модели установлен декоратор на требуемые поля. Если что-то пропущено, тест деликатно подскажет, куда нужно добавить декоратор.

Есть модель, в ней есть несколько полей типа FileField и ImageField.

Имеется ли какой-нибудь документированный и короткий способ сделать так, чтобы:

а)При удалении модели автоматом удалялись все связанные с ней файлы

б)При изменении модели автоматом удалялись старые версии файлов, если загружены новые (или, соответственно, не менялось ничего).


Оба пункта решаются сниппетом с обработкой post_delete и pre_save событий. Но в нём я делаю дополнительное обращение к базе, чтобы узнать местоположение старого файла.

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

1)можно ли решить пункты а) и б) встроенными средствами

2)какие еще красивые решения вы видите?

Для начала нужно сказать, для чего вообще было введен такое поведение. Дело в том, что удаление объекта (вы, кстати, ошибочно называете объекты моделями) еще не означает, что он ушел и больше не вернется. Удаление вполне может происходить в транзакции, которая после откатится. Разработчики Джанги верно определили, что это задача не выполнения приложения, а его администрирования. Поэтому лучшим решением будет создать для вашего приложения действие менеджмента, которое будет запускаться по крону. В нем вы можете выбирать все реально существующие файлы и копировать их в отдельное место, после чего менять это место с папкой, в которую закачиваются файлы. Если файлы хранятся на локальной машине, лучше создавать симлинки, сэкономите и время и место на диске.

Тут посоветовали django-cleanup -- это действительно хорошее решение, но когда в каких-то моделях нужно удалять, а в других не нужно, или даже разное поведение с файлами в разных местах (где-то удалять файлы, а где-то оставлять), то есть другое решение:

Допустим есть модель:


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

Читайте также: