[Fixed]-How to cancel a delete in django signal

1đź‘Ť

This is not be possible when using the built-in Django signals. The “send()” and “send_robust()” methods on signals return a list of 2-tuples — (receiver, response). So, if you have the proper code to handle the responses from each receiver, it is possible that you could prevent some action based on the return value of one signal handler.

The contrib.comments app does this, allowing any receiver which returns False to “cancel” a signal action. See lines 111-120:

However, the core Django code which issues the pre_delete, pre_save, etc signals, does not have any of this special handling. All those signals do is notify the receivers that something has happened.

👤shadfc

1đź‘Ť

I would try a little hack workaround:

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)

and the view

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)

Raising exception in signal should brake delete() method execution while returning exception to the place it was invoked. You could create your own Exception subclass to except only certain type of exception (you almost never should use except with no args).

👤thedk

1đź‘Ť

I know my answer comes a bit late, but the second part of this question is exactly what I needed a few days ago.

So, first things first:

  1. Is there a way to cancel a deletion of record using django pre_delete signal?

Not really, except for the one proposed by thedk. And honestly, there shouldn’t be any. Why? Because pre_delete is meant for an action that is suppose to happen before deleting an object. If you prevent the deletion, it is no longer pre_delete (notice the vicious circle?)

  1. is there a way to say to a model that before changing a file delete first the original file?

Yes, there is, and you got it pretty much right. I created a more generic code, which will work for any model which has File objects associated (see below). However, you should forehand read why this behaviour was removed in Django 1.3 and see if it affects your logic in any way. It is mainly related to how you handle rollbacks and multiple references to the same file from different models.

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

Please bear in mind that this assumes that your delete/update action will be successful. In case it fails, you have permanently lost a file.

A better approach would be to handle file deletion in the post_save/post_delete signals, or to create a cron job which periodically cleans up all files which are no longer referenced from the database.

👤AdelaN

Leave a comment