Django:当Clear Checkbox勾选时,如何自动删除存储中的文件?

时间:2014-02-27 22:32:45

标签: python django storage

我有一个用户个人资料模型,其可选的头像看起来像

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

然后是一个表格:

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

最后,包含

的视图
#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()

现在,当用于编辑用户的个人资料和头像的模板被渲染时,它包含一个Clear复选框,当选中该选项时,用户将移除他们的头像照片。它将myuser.avatar留在状态<ImageFieldFile: None>中,但它不会删除站点本身存储区域中的文件(即jpg,png或其他)。我已经读到这是Django 1.6中的设计,这一切都很好,但是我如何覆盖这个功能,以便确实删除文件?

从shell中没有问题:

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

也删除jpeg


修改

我尝试使用如下信号:

#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)

但这甚至没有触发,我猜是因为我没有删除UserProfile对象 当用户选择清除复选框时,完整地,而不仅仅是头像。

5 个答案:

答案 0 :(得分:6)

像这样扩展ImageField并改为使用它:

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)

如果您将旧文件替换为旧文件,则会删除旧文件,或将其标记为清除。 Here解释原因。

修改

还有一个应用django-smartfields,其中包含此功能以及更多功能,例如自动重新调整大小,自动转换图像和视频等等。它使用字段描述符和模型以更复杂的方式实现它定制。但它使用起来非常简单:

from smartfields import fields

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

它还会在以下情况下删除文件:

  • 字段值已替换为新值(上传或手动设置)
  • 删除包含该字段的模型实例。

答案 1 :(得分:0)

根本不是最好的解决方案,但一种骇人的方式可能是为每个用户仅用他们的用户名存储文件,然后用空pre_save捕获instance.avatar.name信号并在用户所在的位置文件存在于预期位置的磁盘上,将其删除。呸。

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

#save the avatar for each user as their username
def update_filename(instance, filename):
    path = "avatars"
    format = instance.user.username
    return os.path.join(path, format)

#if current instance has empty avatar.name
#and the file exists on disk where expected for user
#deduce user has clicked clear, delete file for user. 
@receiver(pre_save, sender=UserProfile)
def avatar_pre_save_handler(sender, instance, **kwargs):

    avatar_filepath = settings.MEDIA_ROOT +'/avatars/'+ instance.user.username
    if not instance.avatar.name and os.path.isfile(avatar_filepath):
        os.remove(avatar_filepath)

答案 2 :(得分:0)

另一种可能的方式(不需要特殊的文件命名)是覆盖表单保存方法,然后使用旧化身作为kwarg调用它:

#forms.py
class UserProfileForm(forms.ModelForm):
    def save(self, commit=True, *args, **kwargs):
        instance = super(UserProfileForm, self).save(commit=False)
        old_avatar_name = kwargs.pop('old_avatar_name', None)
        new_avatar_name = None
        if self.cleaned_data['avatar']:
            new_avatar_name = self.cleaned_data['avatar'].name
        if old_avatar_name != new_avatar_name:
            old_avatar_filepath = settings.MEDIA_ROOT +'/'+ old_avatar_name
                if os.path.isfile(old_avatar_filepath):
                    os.remove(old_avatar_filepath)

         if commit:
             instance.save()
         return instance

然后在视图中:

def edit_profile(request,....):

    .
    .
    .
    try:
        profile_obj = request.user.userprofile
    except ObjectDoesNotExist:
        return HttpResponseRedirect(reverse('profiles_create_profile'))

    if profile_obj.avatar.name:
        avatar_kwargs={'old_avatar_name': profile_obj.avatar.name}
    else:
        avatar_kwargs={}
    .
    .
    .
    if form.is_valid():
        form.save(**avatar_kwargs)

答案 3 :(得分:0)

使用下面的Mixin

class ImageDeleteMixin(object):

  def delete(self, *args, **kwargs):
    if self.avatar:
        storage, path = self.avatar.storage, self.avatar.path
        super(ImageDeleteMixin, self).delete(*args, **kwargs)
        storage.delete(path)
    else:
        super(ImageDeleteMixin, self).delete(*args, **kwargs)

  def save(self, *args, **kwargs):
     if self.id:
        old_instance = self.__class__._default_manager.get(pk=self.pk)
        if (
         old_instance.avatar != self.avatar and old_instance.avatar and
         old_instance.avatar.path
        ):
            storage, path = old_instance.avatar.storage, old_instance.avatar.path
            super(ImageDeleteMixin, self).save(*args, **kwargs)
            storage.delete(path)
            return
    return super(ImageDeleteMixin, self).save(*args, **kwargs)


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

答案 4 :(得分:-1)

根据您的使用情况,您可以挂钩Django信号: https://docs.djangoproject.com/en/dev/ref/signals/#post-delete

或者,在视图中,当“已检查”变量被发送回服务器时,删除头像!