Django单元测试中UpdateView提交POST后未更新数据库对象
Django UpdateView单元测试POST请求后数据库未更新问题
问题现象
编写Django视图单元测试,测试UpdateView处理POST请求逻辑时出现异常:测试仅提交Employee实例first_name字段的修改,执行后断言失败,数据库中对应字段值未更新。
查看response.context返回结果,上下文中的object、employee对象均显示为修改后的<Employee: John Smith>,但绑定的EmployeeUpdateForm状态为bound=True, valid=False。
相关上下文输出
[{'True': True, 'False': False, 'None': None}, {'csrf_token': <SimpleLazyObject: <function csrf.<locals>._get_val at 0x7f070ed6eee0>>, 'request': <WSGIRequest: POST '/employees/7/update'>, 'user': <SimpleLazyObject: <User: testuser@test.com>>, 'perms': <django.contrib.auth.context_processors.PermWrapper object at 0x7f070ecb33d0>, 'messages': <django.contrib.messages.storage.fallback.FallbackStorage object at 0x7f070ed1b130>, 'DEFAULT_MESSAGE_LEVELS': {'DEBUG': 10, 'INFO': 20, 'SUCCESS': 25, 'WARNING': 30, 'ERROR': 40}}, {}, {'object': <Employee: John Smith>, 'employee': <Employee: John Smith>, 'form': <EmployeeUpdateForm bound=True, valid=False, fields=(first_name;last_name;user;position;reports_to;birthday;hire_date;address;phone_number;pfp)>, 'view': <employees.views.EmployeesUpdateView object at 0x7f070ed1b310>}]
测试代码
class TestEmployeesUpdateView(TestCase): def setUp(self): self.test_user = User.objects.create_user( username='test_user', email='testuser@test.com', password='Testing12345') self.test_employee = Employee.objects.create( first_name='Bob', last_name='Smith', user=self.test_user, position='SM', birthday=date(year=1995, month=3, day=20), hire_date=date(year=2019, month=2, day=15), address='...', ) self.client = Client() def test_updateview_post(self): self.client.force_login(user=self.test_user) response = self.client.post(reverse('employees:employee-update', kwargs={'pk': self.test_employee.pk}), {'first_name': 'John'}) self.test_employee.refresh_from_db() self.assertEqual(self.test_employee.first_name, 'John')
视图代码
class EmployeesUpdateView(LoginRequiredMixin, UpdateView): model = Employee template_name = 'employees/employee_details.html' form_class = EmployeeUpdateForm
报错信息
FAIL: test_updateview_post (employees.tests.test_views.TestEmployeesUpdateView) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/main/dev/project1/employees/tests/test_views.py", line 63, in test_updateview_post self.assertEqual(self.test_employee.first_name, 'John') AssertionError: 'Bob' != 'John' - Bob + John
问题原因
核心问题是表单校验失败,Django的UpdateView处理POST请求有固定逻辑:
- 将提交的POST数据绑定到表单,关联到待更新的模型实例
- 执行表单全量校验
- 只有校验完全通过,才会调用
form.save()把修改写入数据库,之后返回302重定向响应 - 如果校验失败,直接返回200响应,用绑定了提交数据的实例和带错误信息的表单渲染模板,不会触发任何数据库写入操作
上下文里看到object、employee显示为John Smith,只是数据绑定阶段给内存中的临时实例赋了传入的first_name值,这个修改从来没有被持久化到数据库,所以调用refresh_from_db()之后读到的还是原来的Bob。
校验失败的原因非常明确:POST请求只传了first_name一个字段,但EmployeeUpdateForm一共包含first_name、last_name、user、position、reports_to、birthday、hire_date、address、phone_number、pfp10个字段,Django表单默认所有字段都是必填项,漏传任何必填字段都会直接导致校验不通过。
修复方案
- 测试提交时补全所有必填字段,不需要修改的字段直接传原有值即可,保证表单能通过校验:
def test_updateview_post(self): self.client.force_login(user=self.test_user) post_data = { 'first_name': 'John', 'last_name': self.test_employee.last_name, 'user': self.test_user.pk, 'position': self.test_employee.position, 'birthday': self.test_employee.birthday.strftime('%Y-%m-%d'), 'hire_date': self.test_employee.hire_date.strftime('%Y-%m-%d'), 'address': self.test_employee.address, # 非必填字段可以不传,必填字段按表单实际要求补全 } response = self.client.post( reverse('employees:employee-update', kwargs={'pk': self.test_employee.pk}), post_data ) # 可加断言确认更新成功返回302重定向 self.assertEqual(response.status_code, 302) self.test_employee.refresh_from_db() self.assertEqual(self.test_employee.first_name, 'John')
- 如果业务上确实支持只提交部分字段完成更新,就修改
EmployeeUpdateForm配置,将不需要每次提交的字段设置为required=False,或者通过Meta.fields明确限定表单处理的字段范围。 - 调试这类问题时,可以直接打印
response.context['form'].errors,会输出具体的字段校验错误原因,不需要逐字段排查。
内容的提问来源于stack exchange,提问作者user5622143
相关产品推荐
相关产品推荐

