Tortoise ORM 模型字段完全参考
以下参数所有字段类型都支持: db_default 可用表达式(需从 tortoise.fields.db_defaults 导入): 整数,对应数据库 INT(32位有符号,约 -21亿 ~ 21亿)。 大整数,对应数据库 BIGINT(64位)。常用于雪花 ID 等大数值主键。 小整数,对应 SMALLINT(-32768 ~ 32767)。适合状态值、枚举数字。 双精度浮点,对应 DOUBLE。 精确小数,对应 DECIMAL。 对应数据库 VARCHAR(max_length)。 无限长文本,对应 TEXT。不能加 index(大文本不适合索引)
官方文档:https://tortoise.github.io/fields.html
适用版本:tortoise-orm >= 0.20(2026-05-07 核实)
通用参数(所有字段共有)
以下参数所有字段类型都支持:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
default |
Any / callable |
None |
Python 层默认值。传入可调用对象(如 dict)则每次调用,避免共享可变默认值 |
db_default |
Expression |
NOT_PROVIDED |
数据库层面的默认值表达式(由数据库执行,Python 不传值时生效)。可用 Now()、RandomHex()、SqlDefault(sql) 等 |
null |
bool |
False |
是否允许数据库存储 NULL |
db_index |
bool |
None |
是否为该字段创建数据库索引 |
unique |
bool |
False |
是否添加唯一约束 |
primary_key |
bool |
None |
是否作为主键(每个模型只能有一个) |
source_field |
str |
None |
数据库列名,None 则使用字段名 |
description |
str |
None |
字段描述,会出现在 schema 中 |
validators |
list |
None |
验证器列表(在 save() 前运行) |
generated |
bool |
False |
标记为数据库自动生成,不在 INSERT 时提交 |
db_default 可用表达式(需从 tortoise.fields.db_defaults 导入):
| 表达式 | 说明 |
|---|---|
Now() |
CURRENT_TIMESTAMP,数据库当前时间 |
RandomHex() |
随机 32 字符十六进制字符串 |
SqlDefault(sql) |
任意 SQL 表达式字符串 |
数值类型
IntField
整数,对应数据库 INT(32位有符号,约 -21亿 ~ 21亿)。
from tortoise import fields
class Model(Model):
count = fields.IntField(default=0)
age = fields.IntField(null=True)
BigIntField
大整数,对应数据库 BIGINT(64位)。常用于雪花 ID 等大数值主键。
snowflake_id = fields.BigIntField(primary_key=True)
SmallIntField
小整数,对应 SMALLINT(-32768 ~ 32767)。适合状态值、枚举数字。
FloatField
双精度浮点,对应 DOUBLE。
注意:金融计算不要用
FloatField,使用DecimalField。
DecimalField(max_digits, decimal_places, ...)
精确小数,对应 DECIMAL。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
max_digits |
int |
✅ | 总位数(含小数位) |
decimal_places |
int |
✅ | 小数点后位数 |
price = fields.DecimalField(max_digits=10, decimal_places=2)
# max_digits=10, decimal_places=2 → 最大 99999999.99
字符串类型
CharField(max_length, ...)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
max_length |
int |
✅ | 最大字符数(数据库层面限制) |
对应数据库 VARCHAR(max_length)。
name = fields.CharField(max_length=100)
username = fields.CharField(max_length=50, unique=True, db_index=True)
TextField
无限长文本,对应 TEXT。不能加 index(大文本不适合索引)。
content = fields.TextField(null=True)
布尔类型
BooleanField
对应 BOOLEAN / TINYINT(1)。
is_active = fields.BooleanField(default=True)
is_deleted = fields.BooleanField(default=False)
时间日期类型
DatetimeField(auto_now=False, auto_now_add=False, ...)
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
auto_now |
bool |
False |
每次 save() 时自动更新为当前时间(update 时间戳) |
auto_now_add |
bool |
False |
仅在创建时设置为当前时间,之后不再更新 |
class Article(Model):
created_at = fields.DatetimeField(auto_now_add=True) # 创建时间
updated_at = fields.DatetimeField(auto_now=True) # 更新时间
published_at = fields.DatetimeField(null=True) # 手动设置
注意:
auto_now=True的字段自动设置editable=False,手动赋值会被忽略。
DateField
只存日期(无时分秒),对应 DATE。不支持 auto_now / auto_now_add 参数。
TimeField(auto_now=False, auto_now_add=False)
只存时间,对应 TIME。参数同 DatetimeField。
TimeDeltaField
存储时间间隔,Python 类型为 datetime.timedelta,对应 BIGINT(微秒数)。
二进制与特殊类型
BinaryField
存储二进制数据,对应 BLOB。Python 类型为 bytes。
thumbnail = fields.BinaryField(null=True)
JSONField
存储 JSON 数据,对应 JSON(MySQL 8+/PostgreSQL)或 TEXT(SQLite)。
Python 类型为 dict / list,ORM 自动序列化/反序列化。
metadata = fields.JSONField(default=dict) # 注意:传 dict 而不是 {}
tags = fields.JSONField(default=list) # 列表默认值
陷阱:
default={}共享同一个字典对象,务必用default=dict。
UUIDField
UUID 字段,Python 类型为 uuid.UUID。
import uuid
class Model(Model):
id = fields.UUIDField(primary_key=True, default=uuid.uuid4)
使用 UUID 作为主键适合分布式场景,避免自增 ID 暴露信息量。
关系字段
ForeignKeyField(to, related_name=None, on_delete=CASCADE, db_constraint=True, to_field=None, ...)
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
to |
str | Model |
✅ | 目标模型,格式 'app.ModelName' 或直接传模型类 |
related_name |
str | None | False |
None |
反向关系名称;None 自动生成为 <模型名小写>_set;False 禁用反向访问 |
on_delete |
str |
CASCADE |
父记录删除时的行为(见下表) |
db_constraint |
bool |
True |
是否在数据库创建外键约束(False 可提升迁移灵活性但失去 DB 级联保障) |
to_field |
str |
None |
指向目标模型的哪个字段(默认是主键) |
on_delete 可选值:
| 值 | 说明 |
|---|---|
fields.CASCADE |
父记录删除时,子记录一并删除 |
fields.RESTRICT |
有子记录时禁止删除父记录(抛出异常) |
fields.SET_NULL |
父记录删除时,子字段设为 NULL(需配合 null=True) |
fields.SET_DEFAULT |
父记录删除时,子字段设为 default |
fields.NO_ACTION |
数据库不执行任何操作(由应用层控制) |
class Student(Model):
clazz = fields.ForeignKeyField(
'models.Class',
related_name='students', # Class 实例通过 .students 访问关联学生
on_delete=fields.CASCADE,
null=True,
)
访问方式:
student = await Student.get(id=1).select_related('clazz')
print(student.clazz.name) # 正向访问(已预加载)
clazz = await Class.get(id=1).prefetch_related('students')
async for s in clazz.students: # 反向访问集合
print(s.name)
注意:未
select_related时,直接访问student.clazz不会自动查库(异步 ORM 无法隐式发 IO),需要await student.clazz或先预加载。
OneToOneField(to, related_name=None, on_delete=CASCADE, ...)
参数同 ForeignKeyField,但加了唯一约束,确保一对一关系。
class UserProfile(Model):
user = fields.OneToOneField('models.User', related_name='profile', on_delete=fields.CASCADE)
bio = fields.TextField(null=True)
ManyToManyField(to, through=None, forward_key=None, backward_key='', related_name='', on_delete=CASCADE, db_constraint=True, ...)
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
to |
str | Model |
✅ | 目标模型,格式 'app.ModelName' 或直接传模型类 |
through |
str |
None |
中间表模型;None 则自动创建(app__model1__model2) |
forward_key |
str |
None |
中间表中指向本模型的外键字段名 |
backward_key |
str |
'' |
中间表中指向目标模型的外键字段名 |
related_name |
str |
'' |
目标模型上的反向访问名 |
class Student(Model):
courses = fields.ManyToManyField('models.Course', related_name='students')
class Course(Model):
name = fields.CharField(max_length=100)
操作 M2M:
student = await Student.get(id=1)
course = await Course.get(id=2)
await student.courses.add(course) # 添加关联
await student.courses.remove(course) # 移除关联
await student.courses.clear() # 清空所有关联
courses = await student.courses.all() # 获取所有关联
await student.courses.filter(name='数学') # 在关联集合中过滤
枚举类型
CharEnumField(enum_type, description=None, max_length=0)
将 Python str 枚举映射为数据库 VARCHAR,存储枚举的值(.value)。
| 参数 | 说明 |
|---|---|
enum_type |
str 枚举类(必填) |
description |
字段描述 |
max_length |
VARCHAR 长度,0 表示自动从枚举值推断 |
from enum import Enum
from tortoise import fields
class Status(str, Enum):
active = 'active'
inactive = 'inactive'
deleted = 'deleted'
class User(Model):
status = fields.CharEnumField(Status, default=Status.active)
IntEnumField(enum_type, description=None)
将 Python int 枚举映射为数据库 INT,存储枚举的值(.value)。
| 参数 | 说明 |
|---|---|
enum_type |
int 枚举类(必填) |
description |
字段描述 |
from enum import IntEnum
class Priority(IntEnum):
low = 1
medium = 2
high = 3
class Task(Model):
priority = fields.IntEnumField(Priority, default=Priority.medium)
枚举字段的好处:数据库只存
'active'或1等原始值,ORM 自动将其转换为对应的枚举对象,防止使用魔法字符串/数字。
主键字段
IntField(primary_key=True) / BigIntField(primary_key=True)
自增整数主键(最常用)。
UUIDField(primary_key=True, default=uuid.uuid4)
UUID 主键,适合分布式场景。
自动主键
如果不定义任何 primary_key=True 字段,Tortoise 会自动添加 id = IntField(primary_key=True)。
字段验证器
from tortoise.validators import MinValueValidator, MaxValueValidator, RegexValidator
class Product(Model):
price = fields.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)]
)
phone = fields.CharField(
max_length=20,
validators=[RegexValidator(r'^\d{11}$', re.I)]
)
内置验证器:
MinValueValidator(min_value)— 数值最小值MaxValueValidator(max_value)— 数值最大值MinLengthValidator(min_length)— 字符串最小长度MaxLengthValidator(max_length)— 字符串最大长度RegexValidator(pattern, flags)— 正则匹配
自定义验证器:
from tortoise.validators import Validator
from tortoise.exceptions import ValidationError
class PositiveValidator(Validator):
def __call__(self, value):
if value <= 0:
raise ValidationError('值必须为正数')
完整模型示例
import uuid
from tortoise import Model, fields
class User(Model):
# 主键
id = fields.IntField(primary_key=True)
# 基本信息
username = fields.CharField(max_length=50, unique=True, db_index=True, description='用户名')
email = fields.CharField(max_length=254, unique=True)
password_hash = fields.CharField(max_length=128)
avatar = fields.CharField(max_length=500, null=True)
bio = fields.TextField(null=True)
# 状态
is_active = fields.BooleanField(default=True)
is_admin = fields.BooleanField(default=False)
# 时间
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
last_login = fields.DatetimeField(null=True)
# 扩展数据
extra = fields.JSONField(default=dict)
class Meta:
table = 'user' # 指定表名(默认为模型名小写)
ordering = ['-created_at'] # 默认排序
def __str__(self):
return self.username
class Meta 配置
| 属性 | 说明 |
|---|---|
table |
指定数据库表名 |
ordering |
默认排序字段列表,-field 表示降序 |
unique_together |
联合唯一约束,如 (('field1', 'field2'),) |
indexes |
联合索引,如 (('field1', 'field2'),) |
table_description |
表注释 |
manager |
自定义 Manager 类 |
class Meta:
table = 'user_profile'
ordering = ['-created_at']
unique_together = (('user_id', 'date'),) # 联合唯一
indexes = (('status', 'created_at'),) # 联合索引
常见踩坑
JSONField(default={})陷阱:多个实例共享同一个字典对象,改一个会影响所有。应用default=dict(传可调用对象)。- 未
await关系字段:异步 ORM 中访问未加载的关系会报错,需要提前select_related或prefetch_related。 auto_now=True字段无法手动设置:包含该字段的字典在update()时会被自动覆盖。CharField不能不指定max_length:这是必填参数,否则报错。on_delete=SET_NULL必须配null=True:否则数据库约束冲突。
最佳实践
可变类型默认值用可调用对象,不用字面量:JSONField(default={}) 或 JSONField(default=[]) 会让所有实例共享同一对象,导致数据污染。应传入函数引用。
# 错误
tags = fields.JSONField(default={})
# 正确
tags = fields.JSONField(default=dict)
metadata = fields.JSONField(default=list)
主键用 UUIDField(pk=True) 替代自增 ID(分布式场景):自增 ID 在分布式场景下难以合并,UUID 可在应用层生成,无需数据库往返。
import uuid
class Order(Model):
id = fields.UUIDField(pk=True, default=uuid.uuid4)
金额字段必须用 DecimalField,不用 FloatField:浮点数存在精度问题,金融计算应使用精确小数类型。
price = fields.DecimalField(max_digits=12, decimal_places=2)
时间字段统一用带时区的类型:DatetimeField(auto_now_add=True) 在 UTC 时区存储,业务层做时区转换,避免混用本地时间。
关联字段 on_delete 必须明确指定:不同业务语义对应不同策略,不要依赖默认值,显式声明避免歧义。
author = fields.ForeignKeyField('models.User', on_delete=fields.CASCADE)
category = fields.ForeignKeyField('models.Category', on_delete=fields.SET_NULL, null=True)
常见陷阱
陷阱:访问关系字段时抛出 NoValuesFetched
现象: 访问 instance.author 或 instance.tags 时抛出 tortoise.exceptions.NoValuesFetched。
原因: Tortoise ORM 的关系字段是懒加载的,查询时不自动 JOIN,必须显式预加载后才能访问。
解决: 在查询时使用 prefetch_related / select_related,或之后调用 fetch_related。
# 错误:直接访问未加载的关系
post = await Post.get(id=1)
print(post.author.name) # NoValuesFetched
# 正确方式一:查询时预加载
post = await Post.get(id=1).prefetch_related('author')
# 正确方式二:事后加载
await post.fetch_related('author')
print(post.author.name)
陷阱:auto_now 字段在 update() 时不更新
现象: 使用 Model.filter(...).update(status='done') 后,updated_at 字段没有变化。
原因: auto_now=True 只在 instance.save() 时生效(由 Python 层在 save 前赋值),批量 update() 直接执行 SQL,不经过 Python 层,所以不触发 auto_now。
解决: 批量更新时显式传入时间字段,或改用逐条 save()。
from datetime import datetime, timezone
await Post.filter(status='draft').update(
status='published',
updated_at=datetime.now(timezone.utc), # 显式传入
)
陷阱:SET_NULL 与 null=False 组合导致数据库约束冲突
现象: 删除关联记录时抛出数据库约束错误,或迁移时报错。
原因: on_delete=SET_NULL 要求数据库将外键列设为 NULL,但若该列未声明 null=True,数据库会拒绝。
解决: on_delete=SET_NULL 必须同时设置 null=True。
# 错误
category = fields.ForeignKeyField('models.Category', on_delete=fields.SET_NULL)
# 正确
category = fields.ForeignKeyField('models.Category', on_delete=fields.SET_NULL, null=True)