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

Django UpdateView测试异常:POST请求未触发302重定向

Django UpdateView POST后未重定向的问题排查与测试优化

问题背景

开发了一个用于编辑Scooter模型的UpdateView,已配置get_success_url方法,预期POST请求处理表单成功后返回302状态码并重定向至该Scooter的详情页。但测试发现POST请求返回200状态码,response.context包含对象数据,表单被重新渲染而非执行重定向。

疑问

  1. 为何已配置正确的get_success_url,UpdateView在POST后仍返回200状态码?
  2. 在此类测试场景中,POST请求后使用response.context是否合适?是否应改用refresh_from_db刷新数据库中的对象?
  3. 有没有更合适的Django UpdateView测试方法,避免POST请求后的行为不一致问题?

问题解答

1. POST后返回200的原因

你的POST请求表单验证失败了,所以UpdateView会重新渲染表单(返回200)而不是执行重定向。
看ScooterUpdateView配置的fields包含weekly_price、monthly_price、deposit_amount这些字段,但你POST的data里只传了available和daily_price。如果这些字段在模型中是默认的null=False且blank=False,表单会因为缺少必填字段验证不通过,导致视图停留在表单页面,返回200。

2. POST后使用response.context是否合适?

完全不合适。
当表单验证失败时,response.context里的对象是绑定了POST数据但未保存到数据库的表单实例,并不是数据库里的真实数据。你的测试用例之所以能通过,只是因为表单把POST数据临时绑定到了上下文实例中,但实际上数据库里的数据根本没被修改。
如果要验证修改是否成功写入数据库,必须用refresh_from_db()重新从数据库拉取对象:

available_scooter.refresh_from_db()
assert available_scooter.available == False
assert available_scooter.daily_price == 200

3. 更合适的UpdateView测试方法

推荐分场景拆分测试,覆盖成功、失败两种情况:

  • GET请求测试:验证页面正常渲染,上下文数据正确
  • POST成功场景:传递完整合法数据,验证返回302重定向、数据库数据更新、重定向地址正确
  • POST失败场景:传递不合法数据(比如缺少必填字段),验证返回200、表单存在错误提示、数据库数据未被修改

修正后的测试用例示例:

@pytest.mark.django_db
def test_scooter_update_success(client, superuser_user, available_scooter):
    url = reverse('scooter-update', kwargs={'scooter_id': available_scooter.id})
    client.force_login(superuser_user)
    
    # 测试POST成功
    response = client.post(url, data={
        'available': False,
        'daily_price': 200,
        'weekly_price': 1200,  # 补充所有必填字段
        'monthly_price': 4000,
        'deposit_amount': 1000,
    })
    # 验证重定向状态码和地址
    assert response.status_code == 302
    assert response.url == reverse('scooter-detail', kwargs={'scooter_id': available_scooter.id})
    # 验证数据库数据更新
    available_scooter.refresh_from_db()
    assert available_scooter.available == False
    assert available_scooter.daily_price == 200

@pytest.mark.django_db
def test_scooter_update_failure(client, superuser_user, available_scooter):
    url = reverse('scooter-update', kwargs={'scooter_id': available_scooter.id})
    client.force_login(superuser_user)
    
    # 测试POST失败(缺少必填字段)
    response = client.post(url, data={
        'available': False,
        'daily_price': 200,
        # 故意不填其他必填字段
    })
    # 验证返回表单页面
    assert response.status_code == 200
    # 验证表单存在错误
    assert 'weekly_price' in response.context['form'].errors
    # 验证数据库数据未修改
    available_scooter.refresh_from_db()
    assert available_scooter.available == True
    assert available_scooter.daily_price == 100

相关代码

原测试代码

@pytest.mark.django_db
def test_scooter_detail_edit_data(client, superuser_user, available_scooter):
    url = reverse('scooter-update', kwargs={'scooter_id': available_scooter.id})
    client.force_login(superuser_user)
    response = client.get(url)
    assert response.status_code == 200
    assert response.context['scooter'].available == True
    assert response.context['scooter'].daily_price == 100
    response = client.post(url, data={
        'available': False,
        'daily_price': 200,
    })
    assert response.status_code == 200
    assert response.context['scooter'].available == False
    assert response.context['scooter'].daily_price == 200

View代码

class ScooterUpdateView(UpdateView):
    model = Scooter
    template_name = 'scooters/scooter_edit.html'
    pk_url_kwarg = 'scooter_id'
    fields = [
        'available',
        'image',
        'daily_price',
        'weekly_price',
        'monthly_price',
        'deposit_amount',
    ]

    def get_queryset(self):
        if self.request.user.is_superuser:
            return Scooter.objects.all()
        raise Http404

    def get_success_url(self):
        return reverse_lazy('scooter-detail', kwargs={'scooter_id': self.object.pk})

Fixture代码

@fixture
def available_scooter():
    scooter = Scooter.objects.create(
        brand="Yamaha",
        scooter_model="XMax 300",
        capacity=300,
        year=2022,
        registration_number="KR12345",
        available=True,
        image=None,
        daily_price=100,
        weekly_price=600,
        monthly_price=2000,
        deposit_amount=500,
    )
    return scooter

原测试结果

tests/tests.py::test_scooter_detail_edit_data PASSED

内容的提问来源于stack exchange,提问作者Michał Kalicki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:39:57