使用Tortoise-ORM和FastAPI构建评论系统
title: 使用Tortoise-ORM和FastAPI构建评论系统
date: 2025/04/25 21:37:36
updated: 2025/04/25 21:37:36
author: cmdragon
excerpt:
在models.py中定义了Comment模型,包含id、content、created_at、updated_at字段,并与User和Article模型建立外键关系。schemas.py中定义了CommentBase、CommentCreate、CommentUpdate和CommentResponse等Pydantic模型,用于数据验证和响应。路由层实现了创建、获取和删除评论的API,使用get_or_none处理不存在的评论,并捕获异常。测试接口通过requests进行创建和异常测试。常见报错包括外键约束失败、验证错误和事件循环未关闭,需检查外键值、请求体匹配和正确关闭事件循环。
categories:
- 后端开发
- FastAPI
tags:
- Tortoise-ORM
- Pydantic
- FastAPI
- 评论系统
- 数据库模型
- 数据验证
- 接口测试

扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
探索数千个预构建的 AI 应用,开启你的下一个伟大创意:https://tools.cmdragon.cn/
一、Tortoise-ORM模型定义
我们首先在models.py中定义评论模型:
from tortoise.models import Model
from tortoise import fields
class Comment(Model):
id = fields.IntField(pk=True)
content = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
# 外键关系
user = fields.ForeignKeyField('models.User', related_name='comments')
article = fields.ForeignKeyField('models.Article', related_name='comments')
class Meta:
table = "comments"
indexes = ("created_at", "user_id", "article_id")
def __str__(self):
return f"Comment {self.id} by {self.user.username}"
代码解析:
auto_now_add会在创建时自动记录时间- 通过
related_name建立双向关联查询路径 - 复合索引提升常用查询条件的效率
- 继承Model基类获得ORM能力
二、Pydantic模型定义
在schemas.py中定义数据验证模型:
from pydantic import BaseModel
from datetime import datetime
class CommentBase(BaseModel):
content: str
user_id: int
article_id: int
class CommentCreate(CommentBase):
pass
class CommentUpdate(BaseModel):
content: str
class CommentResponse(CommentBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
验证要点:
- 创建模型继承自基础模型
- 更新模型仅允许修改内容字段
- 响应模型启用orm_mode以兼容ORM对象
- 时间字段自动转换时间格式
三、路由层实现
核心路由实现在comments.py中:
from fastapi import APIRouter, Depends, HTTPException
from .models import Comment
from .schemas import CommentCreate, CommentResponse
router = APIRouter(prefix="/comments", tags=["comments"])
@router.post("/", response_model=CommentResponse)
async def create_comment(comment: CommentCreate):
try:
comment_obj = await Comment.create(**comment.dict())
return await CommentResponse.from_tortoise_orm(comment_obj)
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"创建评论失败: {str(e)}"
)
@router.get("/{comment_id}", response_model=CommentResponse)
async def get_comment(comment_id: int):
comment = await Comment.get_or_none(id=comment_id)
if not comment:
raise HTTPException(status_code=404, detail="评论不存在")
return comment
@router.delete("/{comment_id}")
async def delete_comment(comment_id: int):
deleted_count = await Comment.filter(id=comment_id).delete()
if not deleted_count:
raise HTTPException(status_code=404, detail="评论不存在")
return {"message": "评论删除成功"}
技术要点:
- 使用
get_or_none替代get避免直接抛出异常 - 批量删除返回影响行数作为判断依据
- 异常处理覆盖数据库操作的各种失败场景
四、测试接口
使用requests测试接口:
import requests
BASE_URL = "http://localhost:8000/comments"
# 创建测试
def test_create_comment():
data = {
"content": "优质技术文章!",
"user_id": 1,
"article_id": 1
}
response = requests.post(BASE_URL, json=data)
assert response.status_code == 200
print(response.json())
# 异常测试
def test_invalid_user():
data = {
"content": "错误测试",
"user_id": 999,
"article_id": 1
}
response = requests.post(BASE_URL, json=data)
assert response.status_code == 400
print(response.json())
五、课后Quiz
- 当查询不存在的评论ID时,应该返回什么HTTP状态码?
A) 200
B) 404
C) 500
D) 400
答案:B) 404。get_or_none方法会返回None,触发自定义的404异常
- 如何实现评论的软删除功能?
A) 直接删除数据库记录
B) 添加is_deleted字段
C) 使用数据库回收站功能
D) 修改内容为"已删除"
答案:B) 添加布尔型is_deleted字段,查询时过滤已删除的记录
六、常见报错处理
报错:
tortoise.exceptions.IntegrityError: FOREIGN KEY constraint failed
原因:尝试关联不存在的用户或文章ID
解决:检查外键值是否存在,添加数据库约束报错:
pydantic.error_wrappers.ValidationError
原因:请求体缺少必填字段或字段类型错误
解决:检查请求体是否匹配schema定义,使用try-except捕获验证错误报错:
RuntimeError: Event loop is closed
原因:异步操作未正确关闭
解决:在main.py中添加关闭事件循环的hook:
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
app = FastAPI()
register_tortoise(
app,
db_url="sqlite://db.sqlite3",
modules={"models": ["app.models"]},
generate_schemas=True,
add_exception_handlers=True,
)
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:使用Tortoise-ORM和FastAPI构建评论系统 | cmdragon's Blog
往期文章归档:
- 分层架构在博客评论功能中的应用与实现 | cmdragon's Blog
- 深入解析事务基础与原子操作原理 | cmdragon's Blog
- 掌握Tortoise-ORM高级异步查询技巧 | cmdragon's Blog
- FastAPI与Tortoise-ORM实现关系型数据库关联 | cmdragon's Blog
- Tortoise-ORM与FastAPI集成:异步模型定义与实践 | cmdragon's Blog
- 异步编程与Tortoise-ORM框架 | cmdragon's Blog
- FastAPI数据库集成与事务管理 | cmdragon's Blog
- FastAPI与SQLAlchemy数据库集成 | cmdragon's Blog
- FastAPI与SQLAlchemy数据库集成与CRUD操作 | cmdragon's Blog
- FastAPI与SQLAlchemy同步数据库集成 | cmdragon's Blog
- SQLAlchemy 核心概念与同步引擎配置详解 | cmdragon's Blog
- FastAPI依赖注入性能优化策略 | cmdragon's Blog
- FastAPI安全认证中的依赖组合 | cmdragon's Blog
- FastAPI依赖注入系统及调试技巧 | cmdragon's Blog
- FastAPI依赖覆盖与测试环境模拟 | cmdragon's Blog
- FastAPI中的依赖注入与数据库事务管理 | cmdragon's Blog
- FastAPI依赖注入实践:工厂模式与实例复用的优化策略 | cmdragon's Blog
- FastAPI依赖注入:链式调用与多级参数传递 | cmdragon's Blog
- FastAPI依赖注入:从基础概念到应用 | cmdragon's Blog
- FastAPI中实现动态条件必填字段的实践 | cmdragon's Blog
- FastAPI中Pydantic异步分布式唯一性校验 | cmdragon's Blog
- 掌握FastAPI与Pydantic的跨字段验证技巧 | cmdragon's Blog
- FastAPI中的Pydantic密码验证机制与实现 | cmdragon's Blog
- 深入掌握FastAPI与OpenAPI规范的高级适配技巧 | cmdragon's Blog
- Pydantic字段元数据指南:从基础到企业级文档增强 | cmdragon's Blog
- Pydantic Schema生成指南:自定义JSON Schema | cmdragon's Blog
- Pydantic递归模型深度校验36计:从无限嵌套到亿级数据的优化法则 | cmdragon's Blog
- Pydantic异步校验器深:构建高并发验证系统 | cmdragon's Blog
- Pydantic根校验器:构建跨字段验证系统 | cmdragon's Blog
- Pydantic配置继承抽象基类模式 | cmdragon's Blog
- Pydantic多态模型:用鉴别器构建类型安全的API接口 | cmdragon's Blog
- FastAPI性能优化指南:参数解析与惰性加载 | cmdragon's Blog
- FastAPI依赖注入:参数共享与逻辑复用 | cmdragon's Blog
- FastAPI安全防护指南:构建坚不可摧的参数处理体系 | cmdragon's Blog
使用Tortoise-ORM和FastAPI构建评论系统的更多相关文章
- Hexo快速构建个人小站-Fulid主题下添加Valine评论系统(三)
Hexo目录: Hexo快速构建个人小站-Hexo初始化和将项目托管在Github(一) Hexo快速构建个人小站-自定义域名和自定义主题(二) 背景交代: 前面两章完成了Hexo的初始化和部分自定义 ...
- 能动手绝不多说:开源评论系统remark42上手指南
能动手绝不多说:开源评论系统 remark42 上手指南 前言 写博客嘛, 谁不喜欢自己倒腾一下呢. 从自建系统到 Github Page, 从 Jekyll 到 Hexo, 年轻的时候谁不喜欢多折腾 ...
- 在TensorFlow中基于lstm构建分词系统笔记
在TensorFlow中基于lstm构建分词系统笔记(一) https://www.jianshu.com/p/ccb805b9f014 前言 我打算基于lstm构建一个分词系统,通过这个例子来学习下 ...
- 应用集成-在Hexo、Hugo博客框架中使用Gitalk基于Github上仓库项目的issue无后端服务评论系统实践
关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x00 Gi ...
- jQuery Mobile案例,最近用Moon.Web和Moon.Orm做了一套系统
一.简介 先说说,我们的主题.jQuery Mobile,最近用Moon.Web和Moon.Orm做了一套系统 jQuery Mobile是jQuery 在手机上和平板设备上的版本.jQuery ...
- Linux From Scratch(从零开始构建Linux系统,简称LFS)- Version 7.7(二)
七. 构建临时系统 1. 通用编译指南 a. 确认是否正确设置了 LFS 环境变量 echo $LFS b. 假定你已经正确地设置了宿主系统的符号链接: 1)shell 使用的是 bash. 2)sh ...
- 多说评论系统API调用和本地身份说明(JWT)
多说评论系统是一个非常好用的第三方评论插件,聚合了大多数的SNS平台账号登录和分享功能,UI也很不错. 作为网站快速接入评论系统,多说是一个比较好的选择,其也提供了一些实用的API去满足定制化需求. ...
- 门户级UGC系统的技术进化路线——新浪新闻评论系统的架构演进和经验总结(转)
add by zhj:先收藏了 摘要:评论系统是所有门户网站的核心标准服务组件之一.本文作者曾负责新浪网评论系统多年,这套系统不仅服务于门户新闻业务,还包括调查.投票等产品,经历了从单机到多机再到集群 ...
- Ajax制作无刷新评论系统
index.html <script src="jquery.min.js"></script> <script> $(function(){ ...
- 给hexo添加评论系统
默认主题 landscape 文件目录,comments为新建的 _config.yml layout -- _partial -- article.ejs |- comments -- disqus ...
随机推荐
- 用python做时间序列预测二:时间序列的一般数据格式和可视化
本文将介绍如何通过python来读取.展现时间序列数据. 读取 时间序列数据一般用cvs等电子表格的形式存储,这里以cvs为例: from dateutil.parser import parse f ...
- 深入浅出 Vue3:组件与模板基础全解析
一.Vue3 组件结构详解 1.1 单文件组件(SFC)架构 Vue3采用.vue单文件组件模式,一个典型组件包含三个区块: <template> <!-- 组件的HTML模板 -- ...
- ABB机器人本体维修保养方法
ABB机器人维修保养一般可以参照机器人保养手册里面的描述,这种保养一般分为两种计时方式,一两年内进行一次基础保养或者机器人运行时间不超过10000小时.在对机器人本体进行保养的时候,我们该如何操作呢? ...
- .NET 使用 DeepSeek R1 开发智能 AI 客户端
前言 最近 DeepSeek 可太火了,在人工智能领域引起了广泛的关注,其强大的自然语言处理能力和智能搜索功能让大家跃跃欲试. 对于 .NET 技术栈的开发来说,一个常见的问题是:能否在 .NET 程 ...
- STM32的SYSTICK 定时器(系统滴答定时器)
什么是SysTick? 这是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一 ...
- Vue实现企业微信扫码登录
Vue实现企业微信扫码登录 企业微信扫码登录原理 构建企业微信登录二维码 获取访问令牌access_token 请求方式:GET(HTTPS)请求URL:https://qyapi.wei ...
- D pid(16916) tid(19140) 14:05:45 EdgeSnapFeature::PostExitSizeMove: WM_TWINVIEW_SHOW_GRIDLINES -> off
D pid(16916) tid(19140) 14:05:45 EdgeSnapFeature::PostExitSizeMove: WM_TWINVIEW_SHOW_GRIDLINES -> ...
- 题解:洛谷P11557 [ROIR 2016 Day 2] 有趣数字
题目传送门. 考虑数位 dp,也就是记忆化搜索,设置一个搜索函数 \(dfs\),有三个参数,一个是当前位数,表示搜到哪一位了,一个是从第一位到上一个位数是否全部顶上界,从第一位到上一位全部顶上界的意 ...
- 复杂任务分解:Tree of Thought
像搭乐高一样玩转AI思考 今天要带大家解锁一个让AI从"单细胞生物"进化成"八爪鱼思考者"的神技--Tree of Thought(思维树).准备好了吗?我们要 ...
- pandas 判断列是否包含某个字符串
亲测第二种好用 in 语句 不包含使用not in food = df['日期'].values.tolist() if '休息' in food: print(food) if df['共计小时'] ...