You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django视图ImageField测试问题:图片上传测试失败排查

Django图片上传缩放功能测试问题排查

问题描述

我在测试向accounts/profile_edit URL提交图片的功能,我的Profile模型会自动缩放尺寸超过(225,225)的图片,这正是我要测试的点。但编写测试代码后,client.post始终无法成功处理图片,请求排查问题。

测试代码

class TestProfileEditView(TestCase):
    # other tests...

    def test_profile_picture_resize(self):
        self.client.force_login(user=self.user)
        image = Image.new('RGB', (500, 500), (255, 255, 255))
        image_location = f'{settings.MEDIA_ROOT}/TestProfilePictureResize.png'
        image.save(image_location)

        with open(image_location ,'rb') as f:
            response = self.client.post(reverse('profile-edit-view'), {'picture': f})
            profile = Profile.objects.get(user=self.user.id)
            picture_path = profile.picture # falsely returns default picture location
            self.assertEqual(picture_path,
                     /media/profile_pictures/TestProfilePictureResize.png)
            self.assertTemplateUsed(response, 'user/profile.html') # returns False

视图代码(views.py)

@login_required
def profile_edit(request):
    if request.method == 'POST':
        user_form = UserEditForm(request.POST, instance=request.user)
        profile_form = ProfileEditForm(
            request.POST, request.FILES, instance=request.user.profile)
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, f'Profile Edited Successfully.')
            return redirect(reverse('profile-view'), request.user)
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(instance=request.user.profile)
    context = {
        'user_form': user_form,
        'profile_form': profile_form
    }
    return render(request, 'user/profile_edit.html', context)

模型代码(models.py)

class User(AbstractUser):
    email = models.EmailField(unique=True, blank=False, null=False)
    first_name = models.CharField(max_length=50, blank=False, null=False)
    last_name = models.CharField(max_length=50, blank=False, null=False)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["first_name", "last_name"]

    def __str__(self):
        return f"{self.first_name} {self.last_name}"


    class Profile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        picture = models.ImageField(
            default=f"default_profile_picture.png",
            upload_to="profile_pictures"
        )

        def save(self, *args, **kwargs):
            super().save(*args, **kwargs)
            with Image.open(self.picture.path) as image:
                if image.height > 225 or image.width > 225:
                    size = (225, 225)
                    image.resize(size, Image.ANTIALIAS).save(self.picture.path)
    
        def __str__(self):
            return f"{self.user.first_name} {self.user.last_name} Profile"

问题排查与修复方案

1. 模型类缩进错误

Profile类被错误定义在User类内部,导致Django ORM无法正确识别该模型,必须将Profile类与User类同级:

class User(AbstractUser):
    # ... User类代码 ...

class Profile(models.Model):
    # ... Profile类代码 ...

2. 表单验证失败

视图中同时验证user_form和profile_form,但测试仅提交了picture字段,user_form缺少User模型的必填字段(first_name、last_name等),导致表单无效,不会执行保存逻辑。测试时需传入user_form所需的有效数据:

# 测试代码中构造POST数据时添加user字段数据
post_data = {
    'first_name': self.user.first_name,
    'last_name': self.user.last_name,
    'email': self.user.email,
    'picture': f
}
response = self.client.post(reverse('profile-edit-view'), post_data, format='multipart')

3. 文件上传请求格式错误

Django测试客户端发送文件上传请求时,必须指定format='multipart',否则无法正确解析request.FILES:

response = self.client.post(
    reverse('profile-edit-view'),
    post_data,
    format='multipart'
)

4. 断言逻辑错误

  • profile.picture是ImageField对象,需用profile.picture.url或profile.picture.path获取路径
  • 视图成功后会重定向到profile-view,因此assertTemplateUsed不应检查user/profile.html,而是先判断响应状态码为302(重定向),再跟进重定向后的响应检查模板
  • Django会自动处理重名文件,不要硬编码文件名,可通过判断路径包含profile_pictures目录或检查图片尺寸来验证缩放功能

修正后的测试代码示例:

def test_profile_picture_resize(self):
    self.client.force_login(user=self.user)
    # 创建测试图片
    image = Image.new('RGB', (500, 500), (255, 255, 255))
    image_file = BytesIO()
    image.save(image_file, 'PNG')
    image_file.seek(0)

    # 构造包含用户表单数据的POST请求
    post_data = {
        'first_name': self.user.first_name,
        'last_name': self.user.last_name,
        'email': self.user.email,
        'picture': image_file
    }

    response = self.client.post(
        reverse('profile-edit-view'),
        post_data,
        format='multipart'
    )

    # 验证重定向
    self.assertEqual(response.status_code, 302)
    # 跟进重定向,验证目标模板
    redirect_response = self.client.get(response.url)
    self.assertTemplateUsed(redirect_response, 'user/profile.html')

    # 验证图片已上传并缩放
    profile = Profile.objects.get(user=self.user)
    self.assertIn('profile_pictures', profile.picture.url)
    # 检查图片实际尺寸
    with Image.open(profile.picture.path) as img:
        self.assertEqual(img.size, (225, 225))

5. 模型缩放逻辑优化

当前模型的save方法在每次保存时都会处理图片,包括首次保存和更新时,可添加判断避免重复处理:

def save(self, *args, **kwargs):
    # 保存前记录原图片路径
    old_picture = self.picture.path if self.pk else None
    super().save(*args, **kwargs)
    # 仅当图片有变化且尺寸超过限制时处理
    if self.picture.path != old_picture:
        with Image.open(self.picture.path) as image:
            if image.height > 225 or image.width > 225:
                size = (225, 225)
                image.resize(size, Image.Resampling.LANCZOS).save(self.picture.path)

注:Image.ANTIALIAS在Pillow 9.1.0后已被弃用,改用Image.Resampling.LANCZOS


内容的提问来源于stack exchange,提问作者Hossein Yousefian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:00:26