Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: how to automatically also delete file from storage when Clear Checkbox ticked?

I have a user profile model with optional avatar that looks like

#models.py
class UserProfile(models.Model): 
     avatar = models.ImageField(upload_to=avatars null=True, blank=True)
     .
     .
     .

Then a form like:

#forms.py
class UserProfileForm(forms.ModelForm):
    avatar = forms.ImageField(required=False,....)
    class Meta:
    model = UserProfile

Finally, a view including

#views.py
def edit_profile(....)
    profile_obj = request.user.userprofile
    form = UserProfile(data=request.POST, files=request.FILES, instance=profile_obj)
    if form.is_valid():
        form.save()

Now when the template for editing the user's profile, and avatar, is rendered, it included a Clear checkbox, which when selected let's the user remove their avatar photo. It leaves myuser.avatar in the state <ImageFieldFile: None>, however it does not delete the file in the storage area of the site itself (i.e. the jpg,png or whatever). I've read that this is by design in Django 1.6, which is all well and good, but how do I override this feature, so that the file is indeed deleted to?

From the shell there exists no problem:

from myapp.models import UserProfile
user1==UserProfile.objects.all()[0]
user1.avatar.delete()

Removes the jpeg too.


EDIT:

I tried using a signal like:

#models.py
.
.
.
@receiver(post_delete, sender=UserProfile)
def avatar_post_delete_handler(sender, **kwargs):
    print 'DEBUG: avatar delete triggered'
    avatar = kwargs['instance']
    storage, path = avatar.original_image.storage, avatar.original_image.path
    storage.delete(path)

but this did not even trigger, I guess because I'm not deleting the UserProfile object in its entirety when the user selects the clear checkbox, but rather just the avatar.

like image 969
fpghost Avatar asked Mar 21 '23 10:03

fpghost


1 Answers

Extend the ImageField like this and use it instead:

class ImageField(models.ImageField):

    def save_form_data(self, instance, data):
        if data is not None: 
            file = getattr(instance, self.attname)
            if file != data:
                file.delete(save=False)
        super(ImageField, self).save_form_data(instance, data)

This will delete the old file if you replace it with the new one, or mark it to clear. Here is the explanation why.

Edit:

There is also an app django-smartfields that includes this functionality plus more, like automatic re-sizing, automatic conversion of images and videos, etc. It achieves it in a more complicated way though, using field descriptors and model customization. But it is very simple to use:

from smartfields import fields

class UserProfile(models.Model): 
    avatar = fields.ImageField(upload_to='avatars', blank=True)

It will also remove files whenever:

  • field value was replaced with a new one (either uploaded or set manually)
  • the model instance itself containing the field is deleted.
like image 120
lehins Avatar answered Apr 27 '23 01:04

lehins