Я знаю, что мой ответ приходит немного поздно, но вторая часть этого вопроса точно , что мне было нужно несколько дней назад.
Итак, обо всем по порядку:
Есть ли способ отменить удаление записи с помощью сигнала django pre_delete?
Не совсем, за исключением того, что предложено thedk. И, честно говоря, не должно быть никаких. Зачем? Потому что pre_delete предназначен для действия, которое должно произойти до удаления объекта. Если вы запретите удаление, оно больше не будет pre_delete (обратите внимание на замкнутый круг?)
существует ли способ сказать модели, что перед изменением файла сначала удалите исходный файл?
Да, есть, и вы правильно поняли. Я создал более общий код, который будет работать для любой модели, с которой связаны объекты File (см. Ниже). Тем не менее, вы должны заранее прочитать почему это поведение было удалено в Django 1.3 и посмотреть, влияет ли оно на вашу логику каким-либо образом. В основном это связано с тем, как вы обрабатываете откаты и множественные ссылки на один и тот же файл из разных моделей.
def delete_files_from_instance(instance, field_names):
for field_name in field_names:
field_value = getattr(instance, field_name, None)
if field_value:
if isinstance(field_value, File):
try:
os.remove(field_value.path)
except OSError:
pass
@receiver(pre_delete)
def on_delete(sender, instance, **kwargs):
# When an object is deleted, all associated files are also removed
delete_files_from_instance(instance, sender._meta.get_all_field_names())
@receiver(pre_save)
def on_update(sender, instance, **kwargs):
# When an object is updated, if any media files are replaced, the old ones should be deleted.
from_fixture = 'raw' in kwargs and kwargs['raw'] # this prevents errors when loading files from fixtures
is_valid_app = sender._meta.app_label in VALID_APPS # Define what apps are targeted by your code
if is_valid_app and not from_fixture:
try:
old_instance = sender.objects.filter(pk=instance.id).first()
if old_instance and old_instance is not None:
delete_files_from_instance(old_instance, sender._meta.get_all_field_names())
except LookupError:
pass
Имейте в виду, что это предполагает, что действие по удалению / обновлению будет успешным. В случае неудачи вы навсегда потеряли файл.
Лучшим подходом будет обработка удаления файлов в сигналах post_save / post_delete или создание задания cron, которое периодически очищает все файлы, на которые больше нет ссылок из базы данных.