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

Django表单更新视图测试问题:如何在测试用例中传递修改值以更新模型实例

我来帮你分析下问题的核心原因,以及对应的解决办法:

问题根源拆解

  1. 请求数据未被正确解析
    你的测试发送的是application/json格式的请求,但Django默认只会把application/x-www-form-urlencoded或multipart/form-data编码的表单数据解析到request.POST中。JSON数据会被存在request.body里,不会自动填充到request.POST,这就导致你的表单拿不到数据,form.data为空,form.is_valid()自然返回False。

  2. 测试请求参数错误
    self.client.post方法根本没有instance这个参数,你传的这个值完全不会被处理。另外,你用json.dumps把数据转成字符串也没必要——如果是模拟表单提交,直接传Python字典即可;如果要发JSON请求,后续得在视图里手动解析。

  3. 视图保存逻辑冗余
    你先调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 15:44:06