Django UpdateView测试异常:POST请求未触发302重定向
Django UpdateView POST后未重定向的问题排查与测试优化
问题背景
开发了一个用于编辑Scooter模型的UpdateView,已配置get_success_url方法,预期POST请求处理表单成功后返回302状态码并重定向至该Scooter的详情页。但测试发现POST请求返回200状态码,response.context包含对象数据,表单被重新渲染而非执行重定向。
疑问
- 为何已配置正确的
get_success_url,UpdateView在POST后仍返回200状态码? - 在此类测试场景中,POST请求后使用
response.context是否合适?是否应改用refresh_from_db刷新数据库中的对象? - 有没有更合适的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
相关产品推荐
相关产品推荐

