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
相关产品推荐
相关产品推荐

