Django Model重写save方法及update批量更新踩坑详解

Miles
Miles
发布时间:2026-10-15| 2人浏览过
原创

本文详解Django中重写Model的save方法以实现入库前数据校验的最佳实践,并深入剖析使用QuerySet的update方法进行批量更新时不会触发save、pre_save及post_save信号的常见陷阱。通过对比代码示例,提供确保调用save方法的解决方案,并补充说明save对未修改字段的覆盖行为,帮助开发者避免数据一致性问题。

这是一个非常实用的开发技巧。

在Django开发中,若需对存入数据库的数据进行校验,通常有哪些实现路径?

一种直观的做法是在View层处理:每次接收请求时,先对提交数据进行校验,若校验失败直接返回错误信息,不执行数据库写入;若校验通过,再调用create或update方法将数据持久化。

上述方式逻辑简单且易于理解,但随之带来的问题是,必须在所有接收数据的接口处重复编写校验逻辑。是否存在更优雅的方案?参考前文《Django使用Signals监测model字段变化发送通知》,可以利用Signals机制,注册pre_save信号。该信号在数据库数据变更前触发,在此处执行校验即可避免在View层多处重复校验。

然而,本文重点讨论另一种更为常用的方式:直接重写Model的save方法。

重写save方法实现数据校验

关于重写save方法的实现细节

save方法的核心职责是将对象持久化至数据库。若希望在数据入库前执行自定义处理逻辑,除Signals外,重写save方法是另一种有效手段。具体实现参考以下示例。

假设我们定义了如下Model结构:


class TempTask(models.Model):
  ...
  
  exechost = models.CharField(max_length=64, default='localhost', verbose_name='执行主机')
  execuser = models.ForeignKey(ExecUser, null=True, on_delete=models.PROTECT, db_constraint=False)

其中,exechost字段默认值为'localhost',execuser字段默认为空。现有业务需求要求:当exechost的值不为'localhost'时,其格式必须符合'ip:port'规范,且此时execuser字段不能为空。

针对此类较为复杂的校验逻辑,我们可以通过重写save方法来实现。


class TempTask(models.Model):
  ...

  def save(self, *args, **kwargs):
    if self.exechost and (self.exechost.strip() != 'localhost'):
      if len(self.exechost.split(':')) != 2:
        raise ValidationError('执行主机格式错误,应为ip:port格式')

      if not self.execuser:
        raise ValidationError('当执行主机存在时执行用户不能为空')

    super().save(*args, **kwargs)

在save函数内部可以执行各种自定义逻辑,但必须注意:最后务必调用super().save()方法以执行父类的保存逻辑,否则数据无法真正写入数据库。

如此实现后,当执行create语句插入数据时,系统会先执行save方法中的校验逻辑。

示例代码如下: TempTask.objects.create(**postdata)

update批量更新的陷阱与解决

update批量更新的潜在问题

本以为一切顺利,准备休息时却发现:新增数据能正常触发校验,但更新数据时校验却未生效。相关更新代码如下:

示例代码: TempTask.objects.filter(id=pk).update(**postdata)

经过排查,问题根源在于官方文档中的明确说明。

官方指出: 'Unfortunately, there isn't a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.' (遗憾的是,当批量创建或更新对象时,由于不会调用save()、pre_save和post_save,因此没有变通方法。)

这意味着,使用QuerySet的update方法进行批量更新时,不会为每个对象调用save()方法,pre_save和post_save信号同样不会被触发。与此类似,Model的delete()方法在批量删除时也不会被调用,尽管此时仍可使用pre_delete或post_delete信号。

解决该问题的方案很简单,只需将更新代码调整为以下形式,以确保显式调用save方法:


_t = TempTask.objects.get(id=pk)
_t.__dict__.update(**postdata)
_t.save()

补充知识点:Django Model的save方法会对未更改的字段执行保存操作。

请参考以下代码逻辑:


obj = Obj.objects.get(id=1)
print obj.name #此时name的值假定为'abc'
 
def handler(oid):
  obj = Obj.objects.get(id=oid)
  obj.name = '123'
  obj.save()
handler(obj.id)
obj.age = 10
obj.save()
print obj.name 

最终name的值仍为'abc'。需要注意的是,save()在保存时,即使其他字段未修改,也会将内存中的当前值重新写入数据库。这意味着,若在内存中修改了其他字段但未调用save,而由子函数或其他进程直接修改数据库后,再次对原对象调用save(),其他进程更改的值会被覆盖。

以上关于Django Model重写save方法及update批量更新陷阱的详解即为全部内容,希望能对大家有所帮助,欢迎支持本站。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。