bigywater.tech
Django · 38

自定义模型字段

自定义模型字段要写哪几个方法?各自什么时候被调用?为什么 deconstruct 那么重要?这篇写一个最小的 CSVField,给每个方法打点,把调用时机一步步记下来。

get_prep_value / from_db_value
写入数据库前调用前者,从数据库读出后调用后者;filter 的右值也走前者
to_python
full_clean() 与反序列化用它
deconstruct
漏写参数时,改了字段参数 makemigrations 也报 No changes detected

各方法什么时候被调用

实测 · 本机执行输出
构造 Post(tags=["a","b"])   → []
save()                        → ["get_prep_value(['a', 'b'])"]
Post.objects.get(pk)          → ["from_db_value('a,b')"]  tags = ['a', 'b']
Post.objects.filter(tags="a,b") → ["get_prep_value('a,b')", "from_db_value('a,b')"]
Post.objects.filter(tags=["a","b"]) → ["get_prep_value(['a', 'b'])", "from_db_value('a,b')"]
values_list("tags")           → ["from_db_value('a,b')"] [['a', 'b']]
full_clean()                  → ["to_python(['a', 'b'])"]
serializers.serialize("json") → ['value_to_string', "get_prep_value(['a', 'b'])"]  输出: [{"model": "lab.post", "pk": 1, "fields": {"title": "t", "tags": "a,b"}}]

规律:Python → 数据库用 get_prep_value(保存、过滤都走它);数据库 → Python用 from_db_value(取对象、values()、values_list() 都走它);校验 / 反序列化用 to_python;序列化用 value_to_string。

迁移与列类型

实测 · 本机执行输出
  sqlmigrate:
    BEGIN;
    --
    -- Create model Post
    --
    CREATE TABLE "lab_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(20) NOT NULL, "tags" text NOT NULL);
    COMMIT;
  迁移文件里: ('tags', lab.fields.CSVField(default=list)),
deconstruct(separator=";") → ('lab.fields.CSVField', [], {'separator': ';'})
deconstruct(默认)          → ('lab.fields.CSVField', [], {})

继承 TextField 时列类型自然是 text;要换类型就实现 db_type(本文没有测)。迁移文件里字段怎么写,由 deconstruct() 返回的 (name, path, args, kwargs) 决定。

deconstruct 漏写的后果

实测 · 本机执行输出
  改 separator=";" 后 makemigrations: ['  lab/migrations/0002_alter_post_tags.py', '    ~ Alter field tags on post', "Full migrations file '0002_alter_post_tags.py':"]
  同样的修改,但 deconstruct 里没有写 separator: No changes detected in app 'lab'

Django 用 deconstruct() 比较新旧字段是否相同。只要你的字段有构造参数,就必须在 deconstruct 里返回,否则改参数不会生成迁移,数据库和代码悄悄不一致。

分步演示:自定义字段的调用时机

自定义字段
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;调用日志由字段方法里的打点写入文件
    • 没有验证:db_type / rel_db_type 自定义列类型、formfield 在表单里的实际渲染、与 JSONField / ArrayField 的对比、在查询 lookup(如 contains)里的行为、bulk_create 时字段方法的调用
    京ICP备2026067502号-1京公网安备11010502063487号