搜索功能具体实现代码
# 导入必要的模块和函数
from fastapi import APIRouter, HTTPException # 导入FastAPI的APIRouter和HTTPException
from app.db.models import MbBill, MbCategory, MbAsset # 导入数据库模型
from pydantic import BaseModel, Field # 导入Pydantic的BaseModel和Field
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator # 导入Tortoise ORM的Pydantic集成工具
from tortoise.functions import Trim, Sum, Count # 导入Tortoise ORM的SQL函数(尽管此处未使用Trim, Sum, Count)
from typing import Union, Optional # 导入类型注解(此处仅使用了Union和Optional的导入,但代码中未直接使用)
from decimal import Decimal # 导入Decimal类型(代码中未直接使用)
from utility import func # 导入自定义的工具模块
from .items import MonthPagerItem # 导入自定义的MonthPagerItem类(但在此代码中未使用)
# 定义路由的名称
name = "统计"
# 创建APIRouter实例,设置前缀和标签
router = APIRouter(
prefix="/stat",
tags=[name],
responses={404: {"description": "Not found"}},
)
# 定义一个类(尽管在后续代码中未被实例化或使用)
class MonthItem():
month: datetime # 注意:这里缺少了datetime的导入,应添加from datetime import datetime
# 定义一个响应模板(但模板中的字段在函数内部未被直接使用)
RES = {
"code": 0,
"result": {
"expend": 10000,
"income": 20000,
"expendAvg": 15000,
}
}
# 获取搜索分组分页账单的路由处理函数
@router.get("/pages/search/{keyword}", summary=name, description=name, responses=func.Responses(name, RES))
async def stat_pages(keyword: str):
# 创建一个Pydantic模型,用于从MbBill模型中排除一些字段
bills = pydantic_model_creator(MbBill, exclude=(
"isDel", "created", "modified", "category.created", "category.modified", "category.isDel", "category.sort",
"asset.created", "asset.modified", "asset.isDel", "asset.sort"))
# 从数据库中查询未删除且描述中包含关键词的账单,并按时间降序排序
res = await MbBill.filter(isDel=0, description__contains=keyword).order_by('-time')
# 初始化一个集合来存储查询结果的唯一日期
days = set()
for r in res:
days.add(r.time.date())
# 初始化响应列表,按日期分组
RES = [] # 注意:这里重用了RES变量,可能会与之前的响应模板混淆
Days = list(days)
Days.sort() # 对日期进行升序排序(但这里排序后并未直接用于控制显示顺序)
for d in Days:
RES.append({'date': d, 'items': []})
# 遍历查询结果,将账单按日期分组
for r in res:
for d in RES:
if r.time.date() == d['date']:
# 注意:这里对每个账单都执行了额外的数据库查询,性能不佳
r2 = await bills.from_tortoise_orm(await MbBill.get(id=r.id))
d['items'].append(r2)
# 返回响应,包含查询总数(应为len(res))和按日期分组的账单列表
return {
"code": 0,
"result": {
"total": len(res), # 添加查询总数
"items": RES
}
}
其中实现筛选功能的代码段是res = await MbBill.filter(isDel=0, description__contains=keyword).order_by('-time')
这行代码的含义是:
isDel=0
:筛选出isDel
字段等于0的记录,即未删除的账单记录。description__contains=keyword
:筛选出description
字段中包含keyword
(即函数参数中传入的搜索关键词)的记录。
然后,通过.order_by('-time')
对这些筛选后的记录按照time
字段进行降序排序。
因此,筛选标准主要是基于账单的删除状态(isDel
)和描述(description
)字段来进行的。如果账单记录未被删除(isDel=0
)且其描述中包含指定的关键词(keyword
),那么这些记录就会被选出来并进行后续的排序和分组处理。
如何增加筛选标准
在Tortoise ORM中,你可以通过filter()
方法链式地添加多个筛选条件。如果你想要在你的stat_pages
函数中增添筛选标准,你可以直接在filter()
方法中继续添加条件。
例如,如果你想要基于账单的类别(category
)或资产(asset
)来进一步筛选账单,你可以这样做(假设category
和asset
是MbBill
模型中的外键字段,并且你已经有了它们对应的ID或名称等筛选条件):
示例# 假设你有了额外的筛选条件,比如category_id和asset_id
category_id = 1 # 示例,实际使用时可以是函数参数或其他逻辑获取
asset_id = 2 # 示例,实际使用时可以是函数参数或其他逻辑获取
# 增添筛选标准
res = await MbBill.filter(
isDel=0,
description__contains=keyword,
category_id=category_id, # 新增的筛选条件
asset_id=asset_id # 新增的筛选条件
).order_by('-time')
然而,需要注意的是,如果你的MbBill
模型中category
和asset
是以外键形式存在的,并且你想要通过它们的相关字段(比如名称而不是ID)来筛选,那么你可能需要使用到__
操作符来访问关联模型的字段,但这通常涉及到更复杂的查询,并且可能需要使用annotate()
或prefetch_related()
等方法来预先加载关联数据。
由此可见如若想扩大可搜索范围数据库模型很重要
数据库模型解析
# 引入Tortoise ORM的基础模型和字段
from tortoise.models import Model
from tortoise import fields
from tortoise.fields import IntField, CharField, DateField, FloatField, DatetimeField, DecimalField
# 定义一个基础模型类,包含通用字段
class BaseModel(Model):
# 添加数据时间,自动设置为记录创建时间
created = fields.DatetimeField(null=True, auto_now_add=True)
# 修改数据时间,自动更新为每次记录修改的时间
modified = fields.DatetimeField(null=True, auto_now=True)
# 逻辑删除标记,0表示未删除,非0表示已删除
isDel = IntField(default=0)
# 微信ID或所有者标识,为空时表示公用
userid = CharField(null=True, max_length=20)
# 自定义方法,用于执行原始SQL查询并返回结果
@classmethod
async def get_by_query(cls, query):
db = Tortoise.get_connection("default")
result = await db.execute_query_dict(query)
return result
class Meta:
# 标记为抽象类,不会被Tortoise ORM创建对应的数据库表
abstract = True
# 账户表模型
class MbAsset(BaseModel):
# 主键ID
id = IntField(pk=True)
# 父级ID,用于构建层级关系
pid = IntField(default=0)
# 账户名称
name = CharField(max_length=50)
# 类型字段(当前未使用,可按需添加)
# type = CharField(max_length=20)
# 图标路径
iconPath = CharField(max_length=20)
# 账户余额
amount = FloatField(default=0, null=True)
# 排序字段
sort = IntField(default=0)
# 反向关联到账单表,用于查询该账户下的所有账单
bills: fields.ReverseRelation["MbBill"]
class Meta:
# 指定数据库表名
table = "asset"
# 账单类别模型
class MbCategory(BaseModel):
# 主键ID
id = IntField(pk=True)
# 父级ID,用于构建层级关系
pid = IntField(default=0)
# 类别名称
name = CharField(max_length=255)
# 类别类型,1表示支出,0表示收入
type = IntField(null=True)
# 图标路径
iconPath = CharField(max_length=20)
# 预算金额
budget = FloatField(default=0, null=True)
# 排序字段
sort = IntField(default=0)
# 反向关联到账单表,用于查询该类别下的所有账单
bills: fields.ReverseRelation["MbBill"]
class Meta:
# 指定数据库表名
table = "category"
# 账单模型
class MbBill(BaseModel):
# 主键ID
id = IntField(pk=True, description="ID")
# 账单类型(根据业务需求定义)
type = IntField()
# 账单金额,使用DecimalField以精确表示金额
amount = DecimalField(max_digits=10, decimal_places=2, null=False, default=0)
# 外键关联到账单类别表
category: fields.ForeignKeyRelation["MbCategory"] = \
fields.ForeignKeyField("models.MbCategory", related_name="bills")
# 外键关联到账户表
asset: fields.ForeignKeyRelation["MbAsset"] = \
fields.ForeignKeyField("models.MbAsset", related_name="bills")
# 账单描述
description = CharField(max_length=255, null=True)
# 账单地址(如购物地点)
address = CharField(max_length=255, null=True)
# 账单时间,自动设置为记录创建时间
time = DatetimeField(null=True, auto_now_add=True)
class Meta:
# 指定数据库表名
table = "bill"
# 设置默认排序字段
ordering = ["created"]
# 预购清单分组实体模型
class PreOrderGroup(BaseModel):
# 主键ID
id = IntField(pk=True, description="ID")
# 关联的账单ID
billId = IntField(default=0)
# 分组名
name = CharField(max_length=20, null=False)
# 分组描述
description = CharField(max_length=255, null=True)
# 预购清单项目模型
class PreOrder(BaseModel):
# 主键ID
id = IntField(pk=True, description="ID")
# 外键关联到预购清单分组实体
orderGroup: fields.ForeignKeyRelation["PreOrderGroup"] = \
fields.ForeignKeyField("models.PreOrderGroup")
# 预算金额
preAmount = DecimalField(max_digits=10, decimal_places=2, null=False, default=0)
# 实际支付金额
realAmount = DecimalField(max_digits=10, decimal_places=2, null=False, default=0)
# 时间戳,自动设置为记录创建时间
time = DatetimeField(null=True, auto_now_add=True)
# 购买状态,0表示未购买,1表示已购买
status = IntField(null=False, default=0)
# 项目描述
description = CharField(max_length=255, null=True)