django时间戳字段需区分auto_now_add(仅创建时赋值)和auto_now(每次save更新),二者互斥且不适用于bulk操作;应避免update_fields遗漏updated_at,推荐重写save()或用read_only_fields控制序列化。

Model 自动时间戳字段怎么声明
Django 的 models.DateTimeField 提供 auto_now 和 auto_now_add 两个参数,但它们互斥且有隐含行为,不是“自动管理”的万能开关。
常见错误是把创建时间和更新时间都设成 auto_now=True,结果两个字段每次 save 都被强制覆盖,无法保留原始创建时间。
-
auto_now_add=True:仅在第一次save()(即数据库 insert)时写入当前时间,之后不再更新;适合created_at -
auto_now=True:每次调用save()(包括 update)都会刷新为当前时间;适合updated_at,但会绕过字段赋值逻辑 - 二者不能同时用于同一字段,也不能混用在同一个 Model 中而不加约束——比如你手动传了
updated_at值,auto_now=True会直接忽略它
为什么 auto_now 在 bulk_create / raw SQL 里失效
因为 auto_now 和 auto_now_add 是 Django ORM 层的 Python 逻辑,在 bulk_create()、update()、原生 SQL 或 admin 批量操作中完全不触发。
这导致线上常出现「列表页 updated_at 全是 null」或「批量导入后 created_at 为 0001-01-01」这类问题。
- 如果必须用
bulk_create,得显式传入created_at=timezone.now()等值 - PostgreSQL 用户可考虑在数据库层加默认值:
default=django.db.models.F('created_at')不行,得用db_default(Django 4.2+)或 migration 中执行 raw SQL 设置 DEFAULT - MySQL 用户注意:
NOW()默认函数在迁移中不可靠,建议统一用 Python 层控制
updated_at 字段怎么避免被意外跳过
ORM 的 save(update_fields=[...]) 机制会跳过未列出的字段,包括 updated_at——哪怕它设了 auto_now=True,只要没出现在 update_fields 里,就不会更新。
这是最隐蔽的时间戳失步原因:后台任务、信号处理、DRF 序列化更新等场景下极易中招。
- 显式把
'updated_at'加进update_fields列表里,例如:obj.save(update_fields=['name', 'updated_at']) - 更稳妥的做法是去掉
auto_now,改用重写save()方法:if self.pk: self.updated_at = timezone.now() - Django 5.0+ 支持
pre_save信号监听,但信号无法感知update_fields,仍需自己判断是否为 update 操作
只读字段在序列化和表单里怎么配合时间戳
created_at 和 updated_at 通常不该由用户提交,但 DRF 的 ModelSerializer 或 Django Form 默认会包含所有字段,容易引发校验失败或静默丢弃。
错误现象包括:前端传了 created_at 字段却没生效、POST 提交报 DateTimeField received a naive datetime 错误。
- DRF 中用
read_only_fields = ['created_at', 'updated_at'],而不是exclude——后者会导致 create 时字段缺失 - Form 类中对应字段设为
disabled=True或widget=forms.HiddenInput(),但注意disabled字段不会提交,得补上initial - 所有时间字段务必用
timezone.now()而非datetime.now(),否则在 USE_TZ=True 时可能存成 naive 时间,后续比较或序列化出错










