Django图片字段设置:防默认图误删、清旧文件及排障
Django ImageField本地缩略图管理问题排查
背景与初始实现
尽管多数现代网站采用OSS存储图片,但仍希望搭建后端本地管理缩略图。Django的ImageField使用存在难点,修改图片引用涉及models.py、views.py、forms.py三处。
此前直接通过request.user.profile.image = self.files['image']设置图片,模型中为ImageField指定了默认值:
class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) image = ProcessedImageField( default='profile/default.jpg', upload_to='profile', processors=[ResizeToFill(*THUMBNAIL_SIZE)], format='JPEG', options={'quality': THUMBNAIL_QUALITY}, )
初始实现的问题
测试后发现两个问题:
- 默认图片被误删
- 重置非默认图片时,旧文件未删除,占用存储空间
自定义全局函数及异常
为此编写全局setImage函数,意图统一处理图片设置:
from django.conf import settings def setImage(instance, attr, file): """ instance will be saved """ if file: ifield = getattr(instance, attr) # old reference, can be default iurl = ifield.url # default durl = settings.MEDIA_URL + instance._meta.get_field(attr).get_default() if iurl != durl: # old reference is not default # delete old reference and free up space ifield.delete(save=True) # set new file setattr(ifield, attr, file) instance.save()
但测试发现图片始终无法设置,已排除表单enctype、Ajax参数、模型save方法重写等问题。日志显示新文件已传入,但保存后仍为默认图:
====== Screen Shot 2022-11-03 at 10.59.41 pm.png /media/profile/default.jpg /media/profile/default.jpg ----res Screen Shot 2022-11-03 at 10.59.41 pm.png Screen Shot 2022-11-03 at 10.59.41 pm.png --- ins tracey --- after save /media/profile/default.jpg profile/default.jpg
请问为何图片无法设置?
解决方案
修正setImage函数
经测试发现仅一行变量错误,修正后的setImage函数:
def setImage(instance, attr, file): """ instance will be saved """ if file: ifield = getattr(instance, attr) iurl = ifield.url durl = settings.MEDIA_URL + instance._meta.get_field(attr).get_default() if iurl != durl: ifield.delete(save=True) setattr(instance, attr, file) instance.save()
错误说明:原代码中setattr(ifield, attr, file)是给ImageField对象本身设置属性,而非给模型实例设置属性,导致新文件无法被正确关联到模型实例上,修正为直接给instance设置对应属性即可。
表单更新验证的clean_image方法
针对表单更新验证,提供可用的clean_image方法:
def clean_image(form, attr:str, field:str): """ form.instance.attr -> ImageFile form.cleaned_data[field] -> ImageFile """ upload_img = form.cleaned_data[field] if form.instance: # condition: update # create does not matter ifield = getattr(form.instance, attr) iurl = ifield.url if isinstance(upload_img, InMemoryUploadedFile): # upload new file # else, the file is not changed durl = settings.MEDIA_URL + form.instance._meta.get_field(attr).get_default() if iurl != durl: ifield.delete(save=True) return upload_img
使用示例
class Project(models.Model): image = ProcessedImageField( default='projects/default.jpg', upload_to='projects', processors=[ResizeToFill(*THUMBNAIL_SIZE)], options={'quality': THUMBNAIL_QUALITY}, format='JPEG', ) class ProjectCreateForm(forms.ModelForm): class Meta: model = Project fields = ['name', 'image'] def clean_image(self): return clean_image(self, 'image', 'image')
内容的提问来源于stack exchange,提问作者Weilory
相关产品推荐
相关产品推荐

