Django用户模型每次调用save()重复上传头像且路径叠加如何解决
问题原因
- 重复拼接路径的核心原因:你每次执行
save方法时,无论头像是否修改,都会调用reduce_image_size生成新的File对象,此时新File的name属性已经是上一次拼接完成的相对路径avatar/用户名/文件名.jpg,Django的ImageField检测到赋值了新的文件对象,会再次调用upload_to指定的规则,把avatar/用户名/再拼到已有路径前面,最终就出现了路径重复叠加的问题。 - 原有判断逻辑失效的两个错误:
- 用
os.path.exists判断MEDIA_URL拼接的路径,MEDIA_URL是对外访问的URL前缀,不是服务器本地的文件存储路径,操作系统无法直接识别URL路径,应该用MEDIA_ROOT拼接本地文件系统路径才能正确判断文件是否存在。 - 没有判断头像是否真的发生了变更,仅判断文件是否存在无法区分「新上传的头像」和「已经存储过的旧头像」。
- 用
修复方案
修改User模型的save方法即可,参考代码如下:
import os from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models from django.core.files import File from PIL import Image from io import BytesIO def _upload_path(instance, filename): return instance.get_upload_path(filename) def reduce_image_size(avatar): img = Image.open(avatar).convert('RGB') thumb_io = BytesIO() img.save(thumb_io, 'jpeg', quality=50) new_image = File(thumb_io, name=avatar.name) return new_image class User(AbstractUser): avatar = models.ImageField(upload_to=_upload_path, blank=True, null=True) def get_upload_path(self, filename): return "avatar/" + str(self.username) + "/" + filename def save(self, *args, **kwargs): # 已存在的用户先校验头像是否变更 if self.pk: orig_user = User.objects.get(pk=self.pk) # 头像无变更直接跳过处理逻辑 if orig_user.avatar == self.avatar: super().save(*args, **kwargs) return # 仅新上传/变更头像时执行压缩逻辑 if self.avatar: file_local_path = os.path.join(settings.MEDIA_ROOT, self.get_upload_path(self.avatar.name)) if not os.path.exists(file_local_path): new_image = reduce_image_size(self.avatar) self.avatar = new_image super().save(*args, **kwargs)
补充说明:
- 如果你的业务支持用户修改用户名,建议用用户ID代替用户名作为头像路径的组成部分,避免用户名变更后原有头像无法访问的问题。
- 也可以直接使用
django-imagekit这类成熟的第三方库处理头像压缩、路径生成逻辑,不需要手动在save方法中实现,能减少类似的边界问题。
内容的提问来源于stack exchange,提问作者Dariun
相关产品推荐
相关产品推荐

