Django表单更新视图测试问题:如何在测试用例中传递修改值以更新模型实例
我来帮你分析下问题的核心原因,以及对应的解决办法:
问题根源拆解
请求数据未被正确解析
你的测试发送的是application/json格式的请求,但Django默认只会把application/x-www-form-urlencoded或multipart/form-data编码的表单数据解析到request.POST中。JSON数据会被存在request.body里,不会自动填充到request.POST,这就导致你的表单拿不到数据,form.data为空,form.is_valid()自然返回False。测试请求参数错误
self.client.post方法根本没有instance这个参数,你传的这个值完全不会被处理。另外,你用json.dumps把数据转成字符串也没必要——如果是模拟表单提交,直接传Python字典即可;如果要发JSON请求,后续得在视图里手动解析。视图保存逻辑冗余
你先调用form.save()拿到实例,修改字段后又调用form.save(),这会导致重复保存到数据库。正确的做法是用commit=False先获取未提交的实例,修改后再一次性保存。
解决办法1:适配表单提交的测试(推荐,若视图是为浏览器表单设计)
如果你的视图是给浏览器表单用的,那测试应该模拟正常的表单提交流程:
修正后的测试用例
class PharmacyParametrageTestCase(TestCase): def setUp(self): # 创建测试实例(主键asp_par_cle默认自增,这里创建后是1) Parametrage.objects.create(asp_par_loc='TR', asp_par_ale=1, asp_par_con=8) # 必须登录用户,因为视图里用到了request.user.username self.user = User.objects.create_user(username='testuser', password='testpass') self.client.login(username='testuser', password='testpass') def test_email_alert(self): # 直接传Python字典,client.post会自动编码成表单格式 response = self.client.post( reverse('pharmacy:parametrage', kwargs={'pk':1}), data={"asp_par_ale": 2}, follow=True ) self.assertEqual(response.status_code, 200) # 重新查询数据库获取最新实例(不要用之前的instance,数据库已经更新了) updated_param = Parametrage.objects.get(asp_par_cle=1) self.assertEqual(updated_param.asp_par_ale, 2) # 额外验证操作人信息是否正确写入 self.assertEqual(updated_param.opr_nom, 'testuser')
优化后的视图保存逻辑
def parametrage(request,pk): parametrage = get_object_or_404(Parametrage, asp_par_cle = pk) # 注意:如果你的ParametrageForm是普通ModelForm,不需要把request作为第一个参数! # 如果是自定义表单需要request,才保留第一个参数,否则改成下方注释的写法 # form = ParametrageForm(data=request.POST or None, instance=parametrage) form = ParametrageForm(request, data=request.POST or None, instance = parametrage) print('form.data',form.data) if form.is_valid(): # 用commit=False拿到未保存到数据库的实例 parametrage = form.save(commit=False) parametrage.opr_nom = request.user.username parametrage.opr_dat = timezone.now() parametrage.save() # 这里才真正写入数据库 return redirect('pharmacy:dashboard') return render(request, 'pharmacy/parametrage.html', {'form': form})
解决办法2:让视图支持JSON请求(若需要对接API)
如果你的视图需要同时支持浏览器表单和API的JSON请求,那要手动解析JSON数据:
适配JSON的视图
import json from django.http import JsonResponse def parametrage(request,pk): parametrage = get_object_or_404(Parametrage, asp_par_cle = pk) data = None if request.method == 'POST': if request.content_type == 'application/json': # 手动解析JSON数据 try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({'error': '无效的JSON格式'}, status=400) else: data = request.POST form = ParametrageForm(request, data=data or None, instance = parametrage) print('form.data',form.data) if form.is_valid(): parametrage = form.save(commit=False) parametrage.opr_nom = request.user.username parametrage.opr_dat = timezone.now() parametrage.save() # JSON请求返回JSON响应,表单请求返回跳转 if request.content_type == 'application/json': return JsonResponse({'status': 'success'}) return redirect('pharmacy:dashboard') # 验证失败时,JSON请求返回错误信息,表单请求返回页面 if request.content_type == 'application/json': return JsonResponse({'errors': form.errors}, status=400) return render(request, 'pharmacy/parametrage.html', {'form': form})
对应的JSON测试用例
class PharmacyParametrageTestCase(TestCase): def setUp(self): Parametrage.objects.create(asp_par_loc='TR', asp_par_ale=1, asp_par_con=8) self.user = User.objects.create_user(username='testuser', password='testpass') self.client.login(username='testuser', password='testpass') def test_email_alert(self): data = {"asp_par_ale":2} response = self.client.post( reverse('pharmacy:parametrage', kwargs={'pk':1}), data=json.dumps(data), content_type='application/json', follow=True ) self.assertEqual(response.status_code,200) updated_param = Parametrage.objects.get(asp_par_cle=1) self.assertEqual(updated_param.asp_par_ale,2)
额外提醒
一定要确认你的ParametrageForm是否真的需要把request作为构造函数的第一个参数。普通的Django ModelForm是不需要的,如果是你自定义的表单(比如需要用request做权限验证),那要确保表单的__init__方法正确处理这个参数。
内容的提问来源于stack exchange,提问作者Mereva

