from tortoise.models import Model
模型示例:
class Tournament(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField(description="名称",min_length=2,max_length=50)
created = fields.DatetimeField(auto_now_add=True)
def __str__(self):
return self.name
class Event(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField(null=True,index=True)
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
modified = fields.DatetimeField(auto_now=True)
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
def __str__(self):
return self.name
class Team(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField()
code = fields.CharField(null=False, max_length=64, default="", description="编码")
def __str__(self):
return self.name
模型 字段内常用标签方法说明:
primary_key
:设置为主键 也可以写为 pk=True
,默认是False。
null
:是否允许字段为空,null=True
表示允许为空,程序默认是null=False
,即不许为空。
default
:设置默认值。
unique
:设置是否为唯一字段,unique=True
表示该字段在表中唯一,默认是False。
index
:设置索引。index=True
表示设置该字段为索引,默认是False。
description
:设置字段描述内容。
max_length
:接收最长长度。
min_length
:接收最短长度。
max_digits
:针对DecimalField
,该字段允许的最大位数(包括整数部分和小数部分的所有数字)。
decimal_places
:针对DecimalField
,指定该字段的小数位数。
choices
:设置预选字段,示例如下:
STATUS_CHOICES = [("active", "Active"), ("inactive", "Inactive")]
status = fields.CharField(max_length=10, choices=STATUS_CHOICES)
auto_now_add=True
:仅在创建时自动设置当前时间,之后不可更改,即在调用.create方法或保存新实例的时候,自动设置当前时间。
auto_now=True
:每次保存(创建或更新)时都自动设置当前时间。
__str__(self)
方法:定义当对象被转换为字符串时应该返回什么内容.
Meta 方法
Meta用于定义模型的元数据
class Foo(Model):
...
class Meta:
table="custom_table"
unique_together=(("field_a", "field_b"), )
indexes = [("name", "team")] # 创建 (name, team) 组合索引
db_table_prefix = "tenant_" # 所有表名前加上 "tenant_"
ordering = ["-name"] # 默认按 name 字段降序排列
table
:用于指定数据库中的表名,如果未指定,则默认使用小写化的模型类名作为表名。
table_description
:表的描述
unique_together
:定义一个或多个字段组合上的唯一约束,确保这些字段组合在表中是唯一的。如果只是需要一个字段是唯一的可以使用字段内标明unique=True
。如果是多字段组合唯一,就使用unique_together
indexes
:定义一个或多个字段组合上的索引。
ordering
:定义默认的排序规则。接受一个由字段名称组成的列表或元组,支持前缀 -
表示降序排列。
abstract
:标记模型为抽象基类。抽象基类不会在数据库中创建对应的表,但其字段和方法可以被子类继承。默认是False。
schema
:配置表所在的模式名称。
manager
:指定 manager
以覆盖默认管理器
ForeignKeyField
用于定义一对多的关系,允许你通过外键字段将一个模型实例关联到另一个模型实例。
在事件模型中,有定义一个外键引用(完整设定见模型示例)。
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
related_name
:是一个关键字参数,是自定义从关联模型访问当前模型实例集合的管理器名称,在这个例子中表示Event
模型中的 tournament
字段是一个外键,指向 Tournament
模型。我们为 related_name
设置了 'events'
。这意味着对于每一个 Tournament
实例,我们可以使用 .events
来访问与之相关的所有 Event
实例。
await Tournament.first().prefetch_related("events")
first()
:从 Tournament
模型中获取第一个记录。
prefetch_related
:预先加载与 Tournament
相关的 events
数据。
ManyToManyField
用于定义多对多的关系,允许两个模型之间的双向关联,并且支持自定义中间表以添加额外信息。
participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
模型的方法
简单讲解tortoise orm的增删改查方法以及穿插讲解相对比较常用的其他方法。
创建数据
all()
:Python 的内置函数,该函数会遍历这个列表,都为真(即非空、非零、非 None
等)返回True,反之返回False。
raise HTTPException(...)
:FastAPI 中用于抛出 HTTP 异常的一种方式。也可以使用其他类型,比如BaseException等,具体使用方式可以自行查询。
create
:类方法,用一种简洁的方式创建并保存一个新的数据库记录。
#接口定义
@router.post("/add", summary="新增团队")
async def add(name_c: str, code_c: str) -> R:
await team_service.creat_team(name_c, code_c)
return R(data={}, success=True)
#实现方法
async def creat_team(name_c: str, code_c: str):
if not all([name_c, code_c]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="名称或编码必填")
await Team.create(name=name_c, code=code_c)
更新数据
TeamParam
: Pydantic 模型,用于定义和验证 API 请求体中的数据。
@atomic()
:Tortoise ORM 提供的一个装饰器,用于确保数据库操作在事务中执行。它保证了一系列数据库操作要么全部成功完成,要么全部回滚,不会出现部分成功的情况。
filter
:根据条件获取多个记录,并返回一个查询集。
update
:更新符合条件的所有记录,一般结合filter方法使用。
exists
:检查是否有符合某个条件的记录。
# Pydantic 模型
class TeamParam(BaseModel):
name: str = Field(..., description="团队名称", min_length=1)
code: str = Field(..., description="团队编码", min_length=1)
#定义接口
@router.post("/edit", summary="编辑团队")
async def edit(role: TeamParam) -> R:
await team_service.edit(role)
return R(data={}, success=True)
#实现方法
@atomic()
async def edit(team: TeamParam):
# 检查角色是否存在
if not await Team.filter(id=team.id).exists():
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="角色不存在或已被删除")
if not all([team.name, team.code]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="名称或编码必填")
# 更新角色信息
await Team.filter(id=team.id).update(
name=team.name,
code=team.code
)
删除数据
id__in
:特殊的查询操作符,表示“包含在”或“属于”。这里的 id
是模型中的字段名,而 __in
是操作符,用来指定该字段的值必须存在于提供的列表中。(双下划线)
delete
:从数据库中删除实例。
#模型定义
class BatchRemoveParam(BatchOperatorParam):
"""批量删除参数"""
pass
#接口定义
@router.delete("/delete", summary="删除团队")
async def delete(param: BatchRemoveParam) -> R:
await team_service.delete(param)
return R(data={}, success=True)
#接口实现
@atomic()
async def delete(param: BatchRemoveParam):
if not await Team.filter(id__in=param.ids).exists():
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="角色不存在或已被删除")
await Team.filter(id__in=param.ids).delete()
查询数据
prefetch_related
:一次性预先加载相关对象,避免了 N+1 查询问题。通常用于处理多对多关系或多级外键关系。
#返回给前端的视图
class TeamView(BaseModel):
def __init__(self, model: TeamDTO = None):
if model:
super().__init__(model.__dict__)
else:
super().__init__()
id: str = Field(None, description="主键")
name: str = Field(None, description="名称")
code: str = Field(None, description="编码")
#定义接口
@router.get("/select", summary="查看team列表")
async def select(name: str|None=None, code: str|None=None) -> R[List[TeamView]]:
return R(await team_service.page_list(name,code))
#实现方法
@atomic()
async def page_list(name: str, code: str):
# 列表应该返回名称,编号,和类型,
# 构建基础查询
query = TeamModel.filter(is_delete=False)
# 如果提供了 team.name,team.code,则添加过滤条件
if name is not None:
query = query.filter(name=name)
elif code is not None:
query = query.filter(code=code)
# 查询数据返回team_view
return await team_get_data(query,False)
#假设有一个关联的一对多的菜单
async def team_get_data(query, single: bool) -> Union[TeamView, List[TeamView]]:
roles = await query.prefetch_related('link_menus').all()
# 将结果转换为 DTO 并添加菜单信息
team_dtos = [
TeamDTO(
name=team.name,
code=team.code,
type=team.type,
menus=[MenuDTO(name=m.name, code=m.code) for m in await team.link_menus.all()]
)
for team in teams
]
if single:
if len(team_dtos) > 1:
return TeamView(model=role_dtos[0])
return [TeamView(model=team_dto) for team_dto in team_dtos]
其他相对常用的方法:
exclude()
- 创建带有给定排除条件的 QuerySet。
all()
- 创建不带过滤条件的 QuerySet。
annotate()
- 创建带有给定注释的 QuerySet。
first()
-将查询集限制为一个对象,并返回一个对象而不是列表。
get()
-用于根据条件获取单个对象。
specific = await Team.get(code="ADMIN_TEAM")
update_or_create()
-尝试根据条件获取记录,如果存在则更新,否则创建新记录。它返回一个元组 (instance, created)
,其中 created
是一个布尔值,表示是否创建了新记录,instance
是模型实例。
get_or_create()
-尝试根据条件获取记录,如果不存在则创建并返回新记录。它也返回一个元组 (instance, created)
。
count()
-返回查询结果的数量,而不实际加载数据。
count = await Team.filter(type=TeamTypeEnum.ADMIN).count()
limit()
-限制查询返回的结果数量。
top_roles = await Team.all().limit(5)
order_by
-根据指定字段对查询结果进行排序。可以是升序(默认)或降序(使用 -
前缀)。
# 按名称升序排序
sorted_asc = await Team.all().order_by("name")
# 按名称降序排序
sorted_desc = await Team.all().order_by("-name")
# 按多个字段排序
multi_sorted = await Team.all().order_by("type", "-created_at")
group_by
-对查询结果进行分组,通常与聚合函数一起使用。
offset()
-方法用于指定查询结果的起始位置(偏移量),通常与 limit()
结合使用来实现分页功能。它告诉数据库从第几条记录开始返回数据。
# 分页参数
page_size = 10 # 每页显示的记录数
page_number = 2 # 当前页码
# 获取第 2 页的数据(即第 11 到第 20 条记录)
roles_page = await RoleModel.all().offset(page_size * (page_number - 1)).limit(page_size)
values
-返回包含指定字段值的字典列表,而不是模型实例。values_list()
返回元祖列表
ids_and_names = await Team.all().values("id", "name")
distinct
-去除重复的记录,确保查询结果中的每一行都是唯一的。
only
-只选择指定的字段,忽略其他字段,返回的是模型实例。
selected_fields = await Team.all().only("id", "name")
filter内部常用的方法:
not
in
- 检查字段的值是否在传入的列表中
# 查找 ID 在 [1, 2, 3] 中的团队
teams_in_list = await Team.filter(id__in=[1, 2, 3])
not_in
,使用方法同in
gte
- 大于或等于传入的值
# 查找 ID 大于或等于 10 的团队
teams_gte_10 = await Team.filter(id__gte=10)
gt
- 大于传入的值
lte
- 小于或等于传入的值
lt
- 小于传入的值
range
- 在两个给定值之间
# 查找 ID 在 5 到 15 之间的团队
teams_in_range = await Team.filter(id__range=(5, 15))
isnull
- 字段为 null
# 查找 description 为空的团队
teams_with_null_desc = await Team.filter(description__isnull=True)
not_isnull
- 字段不为 null
contains
- 字段包含指定子字符串
# 查找名称包含 "Red" 的团队
teams_contains_red = await Team.filter(name__contains="Red")
icontains
- 不区分大小写的 contains
startswith
- 字段是否以值开头
istartswith
- 不区分大小写的 startswith
endswith
- 字段是否以值结尾
iendswith
- 不区分大小写的 endswith
iexact
- 不区分大小写的相等
search
- 全文搜索
# 使用全文搜索查找名称中包含 "red" 或 "blue" 的团队
teams_search_red_or_blue = await Team.filter(name__search="red | blue")
过滤日期部分,注意目前只支持 PostgreSQL 和 MySQL,但不支持 SQLite:
class DatePart(Enum):
year = "YEAR"
quarter = "QUARTER"
month = "MONTH"
week = "WEEK"
day = "DAY"
hour = "HOUR"
minute = "MINUTE"
second = "SECOND"
microsecond = "MICROSECOND"
teams = await Team.filter(created_at__year=2020)
teams = await Team.filter(created_at__month=12)
teams = await Team.filter(created_at__day=5)
内容大多来自官网,也可前往官网