Как отменить удаление в django-сигнале

Есть ли способ отменить удаление записи с использованием сигнала django pre_delete?

Пример:

def on_delete(sender,**kwargs):
  if not <some condition>:
    #cancel the deletion
 # else continue with the deletion
pre_delete.connect(on_delete,sender=MyModel)

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

def on_save(sender,**kwargs):
  obj = kwargs['instance']
  try:
    id = obj.pk
    # find the file
    original_file = sender.objects.get(pk=id)
    # delete the original file before uploading a new file
    original_file.file.delete()
  except ....

pre_save.connect(on_save,sender=ModelWithFileUpload)

(в django 1.2 они автоматически удаляют файл при изменении или удалении, но в django 1.3 они удалили эту функцию)

Заранее спасибо

Ответ 1

Я бы попробовал небольшое обходное решение:

def on_delete(sender,**kwargs):
  if not <some condition>:
    raise Exception('Do not delete')#cancel the deletion
 # else continue with the deletion
pre_delete.connect(on_delete,sender=MyModel)

и представление

def on_save(sender,**kwargs):
  obj = kwargs['instance']
  try:
    id = obj.pk
    # find the file
    original_file = sender.objects.get(pk=id)
    # delete the original file before uploading a new file
  except ... :
    # oder exceptions 

  try:
    original_file.file.delete()
  except:
    pass #not deleted

pre_save.connect(on_save,sender=ModelWithFileUpload)

Приостановка исключения в сигнале должна прерывать выполнение метода delete(), возвращая исключение в том месте, где оно было вызвано. Вы можете создать свой собственный подкласс Exception, за исключением только определенного типа исключения (вы почти никогда не должны использовать, кроме как без аргументов).

Ответ 2

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

Итак, сначала сначала:

  • Есть ли способ отменить удаление записи с использованием сигнала django pre_delete?

На самом деле, за исключением предложения, предложенного thedk. И, честно говоря, их не должно быть. Зачем? Поскольку pre_delete предназначен для действия, которое должно произойти до удаления объекта. Если вы запретили удаление, оно перестает быть pre_delete (обратите внимание на порочный круг?)

  1. Есть ли способ сказать модели, что перед изменением файла сначала удалите исходный файл?

Да, есть, и вы получили это в значительной степени правильно. Я создал более общий код, который будет работать для любой модели, связанной с файлами (см. Ниже). Тем не менее, вы должны перевести читать почему, это поведение было удалено в 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, которое периодически очищает все файлы, которые больше не ссылаются на базу данных.

Ответ 3

Это невозможно при использовании встроенных сигналов Django. Методы "send()" и "send_robust()" по сигналам возвращают список из 2-х кортежей - (приемник, ответ). Таким образом, если у вас есть правильный код для обработки ответов от каждого получателя, возможно, что вы можете предотвратить некоторые действия на основе возвращаемого значения одного обработчика сигнала.

Приложение contrib.com делает это, разрешая любому получателю, который возвращает False, чтобы "отменить" действие сигнала. Смотрите строки 111-120:

Тем не менее, основной код Django, который выдает сигналы pre_delete, pre_save и т.д., не имеет никакой особой обработки. Все эти сигналы сообщают получателям, что что-то произошло.