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 自动生成为 <模型名小写>_setFalse 禁用反向访问
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'),)     # 联合索引

常见踩坑

  1. JSONField(default={}) 陷阱:多个实例共享同一个字典对象,改一个会影响所有。应用 default=dict(传可调用对象)。
  2. await 关系字段:异步 ORM 中访问未加载的关系会报错,需要提前 select_relatedprefetch_related
  3. auto_now=True 字段无法手动设置:包含该字段的字典在 update() 时会被自动覆盖。
  4. CharField 不能不指定 max_length:这是必填参数,否则报错。
  5. 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.authorinstance.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_NULLnull=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)

参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog