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:
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?)
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.