问题
I have the following signal to delete the old postcover and postcover_tn (thumbnail) from my hard disk. This is working fine if I just delete the files trough my form and call save() but if I want to overwrite the old files with the new ones I upload the old ones are are still on my fs, any idea how to fix this?:
signals.py
@receiver(models.signals.pre_save, sender=Post)
def post_auto_delete_files_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_postcover = sender.objects.get(pk=instance.pk).postcover
old_postcover_tn = sender.objects.get(pk=instance.pk).postcover_tn
except sender.DoesNotExist:
return False
if not old_postcover:
return
new_postcover = instance.postcover
if not old_postcover == new_postcover:
if os.path.isfile(old_postcover.path):
os.remove(old_postcover.path)
new_postcover_tn = instance.postcover_tn
if not old_postcover_tn == new_postcover_tn:
if os.path.isfile(old_postcover.path):
os.remove(old_postcover.path)
postcover_tn gets generated on save() of Post, just if you might wonder about that.
回答1:
Here's the Problem
Since you're handling a post save signal, the data on the instance has already been inserted into the database before the signal handler executes.
This means that sender.objects.get(pk=instance.pk).postcover
and instance.postcover
in your code above fetch the same thing — the newly saved postcover.
So, that thing you're naming old_postcover
in your code, is actually the new postcover. The real old postcover has been overwritten for good, and is still on your File System.
Digression
Now, the body of this part of the code...
if not old_postcover == new_postcover:
if os.path.isfile(old_postcover.path):
os.remove(old_postcover.path)
os.remove(old_postcover_tn.path)
... never gets to run, because old_postcover
and new_postcover
are indeed the same thing.
How To Fix This?
You could use a pre save signal handler.
In the handler, you grab the old postcover from the database with sender.objects.get(pk=instance.pk).postcover
(after checking, like you did in your code, to ensure the instance does really have a pk).
Then you delete this old postcover, and you're done.
The Problem with this Solution
The problem I can immediately see with going this route is that you're deleting old data without knowing whether the new data will be accepted by the database in the first place.
But Looking on the Bright Side
However, if you'll only ever be changing postcovers through ModelForm
s, the call to the is_valid()
method on the form performs all validation on the instance, so then you can be confident that at the point the handler executes, the new data on the instance has been validated and will be accepted by the database.
回答2:
I got it working like this:
models.py:
def save(self, *args, **kwargs):
super(Post, self).save(*args, **kwargs)
if self.postcover:
if not (self.postcover_tn and os.path.exists(self.postcover_tn.path)):
image = Image.open(self.postcover)
outputIoStream = BytesIO()
baseheight = 400
hpercent = baseheight / image.size[1]
wsize = int(image.size[0] * hpercent)
imageTemproaryResized = image.resize((wsize, baseheight))
imageTemproaryResized.save(outputIoStream, format='PNG')
outputIoStream.seek(0)
self.postcover = InMemoryUploadedFile(outputIoStream, 'ImageField',
"%s.png" % self.postcover.name.split('.')[0], 'image/png',
sys.getsizeof(outputIoStream), None)
image = Image.open(self.postcover)
outputIoStream = BytesIO()
baseheight = 175
hpercent = baseheight / image.size[1]
wsize = int(image.size[0] * hpercent)
imageTemproaryResized = image.resize((wsize, baseheight))
imageTemproaryResized.save(outputIoStream, format='PNG')
outputIoStream.seek(0)
self.postcover_tn = InMemoryUploadedFile(outputIoStream, 'ImageField',
"%s.png" % self.postcover.name.split('.')[0], 'image/png',
sys.getsizeof(outputIoStream), None)
elif self.postcover_tn:
self.postcover_tn.delete()
super(Post, self).save(*args, **kwargs)
signals.py
@receiver(models.signals.pre_save, sender=Post)
def post_auto_delete_files_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_postcover = sender.objects.get(pk=instance.pk).postcover
old_postcover_tn = sender.objects.get(pk=instance.pk).postcover_tn
except sender.DoesNotExist:
return False
if not old_postcover:
return
new_postcover = instance.postcover
new_postcover_tn = instance.postcover_tn
if not old_postcover == new_postcover:
if os.path.isfile(old_postcover.path):
os.remove(old_postcover.path)
if old_postcover_tn == new_postcover_tn:
if os.path.isfile(old_postcover_tn.path):
os.remove(old_postcover_tn.path)
@mfonism thanks for your hints, they really helped me understanding.
来源:https://stackoverflow.com/questions/60082945/django-unable-to-delete-old-file-on-object-change-using-signals