bigywater.tech
Django · 61

Fixture、dumpdata 与 loaddata

fixture 是测试和初始数据的老朋友,但它绕过了很多你以为一定会发生的事:自定义 save()、auto_now、约束顺序。这篇逐项验证 dumpdata 与 loaddata 的行为。

不调用 save()
loaddata 不执行模型自定义的 save();post_save 信号仍触发,raw=True
覆盖
同 pk 再装载一次是 update,不是新增
0.19 vs 1.41 s
1 万行:dumpdata vs loaddata

dumpdata 的输出

实测 · 本机执行输出
[{"model": "lab.author", "pk": 1, "fields": {"name": "ann"}}, {"model": "lab.book", "pk": 1, "fields": {"title": "b1!", "author": 1, "updated": "2026-09-30T12:35:27.052Z"}}]

--natural-foreign: [{"model": "lab.book", "pk": 1, "fields": {"title": "b1!", "author": ["ann"], "updated": "2026-09-30T12:35:27.052Z"}}]

--natural-primary + --natural-foreign(Author): [{"model": "lab.author", "fields": {"name": "ann"}}]

格式: ['json', 'jsonl', 'xml']
jsonl 首行: {"model": "lab.author","pk": 1,"fields": {"name": "ann"}}

默认导出所有指定 app 的所有对象;跨环境用 natural key 避免数字 id 冲突。contenttypes、auth.permission 之类由迁移自动创建的数据一般要 --exclude(本文没有测)。

loaddata 绕过了什么

实测 · 本机执行输出
title = 'from-fixture' (自定义 save() 会给标题加 "!"——这里没加,说明 loaddata 没调用它)
updated = 2000-01-01 00:00:00+00:00 (auto_now 没有被改成现在的时间)
post_save 触发次数: 1  其中 raw=True: 1

用 Model.save_base(raw=True) 保存。所以:自定义 save() 不执行、auto_now/auto_now_add 用文件里的值、信号处理函数收到 raw=True。

覆盖与错误

实测 · 本机执行输出
pk=7 的作者名字变成: zed2 ; 作者总数: 1
多余字段 → DeserializationError: Problem installing fixture '/var/folders/km/b69gr9cs3dv8qsm8rjjb4qqc0000gn/T/tmpocham7ol/h.json': Error deserializing ob
外键指向不存在的作者 → IntegrityError: Problem installing fixtures: The row in table 'lab_book' with primary key '11' has an invalid foreign key: lab_book.author_id contains a val

fixture 是「按 pk 写入」,重复装载幂等;数据有误时报错并整体回滚(本文只验证了报错,没有验证回滚范围)。

natural key

实测 · 本机执行输出
[{"model": "lab.author", "fields": {"name": "ann"}}, {"model": "lab.book", "pk": 10, "fields": {"title": "nk!", "author": ["ann"], "updated": "2026-09-30T12:35:27.622Z"}}]

要在模型上实现 natural_key() 与管理器的 get_by_natural_key()(本文只实现了前者,装载路径没有测)。

速度

实测 · 本机执行输出
dumpdata 1 万行: 0.19 s,文件 1140 KiB;loaddata 1 万行: 1.41 s;表里 10000 行

SQLite 单机数字。

分步演示:fixture 的行为

fixture
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite
    • 没有验证:loaddata 装载失败时的事务回滚范围、fixtures 目录的搜索顺序与 FIXTURE_DIRS、压缩格式(.gz)、PostgreSQL 上的序列重置(loaddata 之后 pk 序列是否需要 sqlsequencereset)、natural key 在装载路径上的 get_by_natural_key
    京ICP备2026067502号-1京公网安备11010502063487号