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

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请求有固定逻辑:

  1. 将提交的POST数据绑定到表单,关联到待更新的模型实例
  2. 执行表单全量校验
  3. 只有校验完全通过,才会调用form.save()把修改写入数据库,之后返回302重定向响应
  4. 如果校验失败,直接返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:33:43