【问题标题】:Django: how to automatically also delete file from storage when Clear Checkbox ticked?Django:勾选清除复选框时如何自动从存储中删除文件?
【发布时间】:2017-12-30 14:09:31
【问题描述】:

我有一个带有可选头像的用户配置文件模型,看起来像

#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 对象 当用户选择清除复选框时,它是完整的,而只是头像。

【问题讨论】:

    标签: python django storage


    【解决方案1】:

    像这样扩展 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)
    

    它还会在任何时候删除文件:

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

    【讨论】:

    • 谢谢!一个问题:如果用户尝试上传一个真正不同但名称与旧文件相同的新文件怎么办?
    • 如果您上传的文件与您首先上传的文件完全相同,它甚至会替换该文件,因为“数据”将是 InMemoryUploadedFile 或 TemporaryUploadedFile,而“文件”将是 ImageFieldFile。所以如果你真的想确定你可以检查他们的类是否相等,但我已经测试了很多次,它可以按预期工作。
    • 很好的解决方案。感谢您富有洞察力的示例,我解决了这个问题。我知道你发布这个已经很久了,但我想知道file 什么时候会等于data
    • 哦,我现在明白了。如果以前的图像存在并且“帖子”(或其他内容)被保存而没有对图像进行更改,file 等同于data,所以在这种情况下,不要删除图像。你太棒了。
    • 就我而言,我稍微修改了if 语句:if bool(file) == True and file != data。也许它会在未来帮助某人。
    【解决方案2】:

    根本不是最好的解决方案,但一种骇人听闻的方法可能是将每个用户的文件存储在他们的用户名下,然后捕获带有空 instance.avatar.namepre_save 信号以及用户文件存在于磁盘上的位置在预期的地方,删除它。呸。

     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)
    

    【讨论】:

      【解决方案3】:

      另一种可能的方法(不需要对文件进行特殊命名)是覆盖表单保存方法,然后使用旧头像作为 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)
      

      【讨论】:

        【解决方案4】:

        像下面这样使用 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)
        

        【讨论】:

          【解决方案5】:

          根据您的用例,您可以使用 Django 的 Singals: https://docs.djangoproject.com/en/dev/ref/signals/#post-delete

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

          【讨论】:

          • 我尝试了信号方法,我现在已经添加到我的 OP 中,我在另一篇文章中偶然发现了它,但这甚至没有触发。我不确定如何实现基于信号的方法?
          • 在保存时也使用信号,很难知道清除复选框是否被勾选...
          • 也许意见是要走的路,但我如何访问这个Clear复选框变量?
          猜你喜欢
          • 2011-06-20
          • 1970-01-01
          • 2021-12-07
          • 1970-01-01
          • 2016-01-02
          • 1970-01-01
          • 2019-05-07
          • 2019-03-13
          • 1970-01-01
          相关资源
          最近更新 更多