使用 aiohttp 和 Tortoise ORM 搭建一个带界面的异步 web 网站

使用 aiohttp 和 Tortoise ORM 搭建一个带界面的异步 web 网站。这是一个很好的技术组合,可以充分利用 Python 的异步特性。

安装必要的包

pip install aiohttp aiohttp_jinja2 tortoise-orm

项目结构

创建一个基本的项目结构:

project/
├── main.py          # 应用入口
├── models.py        # Tortoise ORM 模型
├── routes.py        # 路由定义
├── views.py         # 视图处理函数
├── static/          # 静态文件 (CSS, JS等)
└── templates/       # Jinja2 模板

定义数据库模型

在 models.py 中使用 Tortoise ORM 定义模型:

from tortoise import fields
from tortoise.models import Model


class User(Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(max_length=50, unique=True)
    nickname = fields.CharField(max_length=50, null=True)
    email = fields.CharField(max_length=100)
    created_at = fields.DatetimeField(auto_now_add=True)

    class Meta:
        table = "users"

    def __str__(self):
        return self.username

创建视图和路由

在 views.py 中定义视图函数:

# views.py 扩展版
import aiohttp_jinja2
from aiohttp import web
from models import User

@aiohttp_jinja2.template('index.html')
async def index(request):
    # 获取所有用户
    users = await User.all()
    title = 'hello word'
    return {'users': users,'title':title}

async def add_user(request):
    data = await request.post()
    # 创建新用户
    user = await User.create(username=data['username'], email=data['email'])
    return web.HTTPFound('/')

@aiohttp_jinja2.template('user.html')
async def get_user(request):
    user_id = request.match_info.get('id')
    # 根据ID查找用户
    user = await User.get_or_none(id=user_id)
    if not user:
        raise web.HTTPNotFound(text="用户不存在")
    return {'user': user}

async def update_user(request):
    user_id = request.match_info.get('id')
    data = await request.post()
    # 更新用户信息
    user = await User.get(id=user_id)
    user.username = data['username']
    user.email = data['email']
    await user.save()
    return web.HTTPFound('/')

async def delete_user(request):
    user_id = request.match_info.get('id')
    # 删除用户
    user = await User.get(id=user_id)
    await user.delete()
    return web.HTTPFound('/')

在 routes.py 中定义路由:

from views import index, add_user, get_user, update_user, delete_user


def setup_routes(app):
    app.router.add_get('/', index)
    app.router.add_post('/add_user', add_user)
    app.router.add_get('/user/{id}', get_user)
    app.router.add_post('/user/{id}/update', update_user)
    app.router.add_post('/user/{id}/delete', delete_user)

创建模板

在 templates/index.html 中定义一个简单的模板:

<!DOCTYPE html>
<html>
<head>
    <title>用户管理</title>
    <link rel="stylesheet" href="/static/css/style.css">
    <script src="/static/js/script.js"></script>
</head>
<body>
    <h1>用户列表</h1>

    <ul>
    {% for user in users %}
        <li>{{ user.username }} ({{ user.email }})</li>
    {% endfor %}
    </ul>
    {{title}}
    <h2>添加新用户</h2>
    <form action="/add_user" method="post">
        <div>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="email">邮箱:</label>
            <input type="email" id="email" name="email" required>
        </div>
        <button type="submit">添加</button>
    </form>

    <script src="{{ static('js/script.js') }}"></script>
</body>
</html>

主应用入口

在 main.py 中创建主应用:

# main.py 修改版,添加端口配置
import aiohttp_jinja2
import jinja2
import argparse, os
from aiohttp import web
from tortoise.contrib.aiohttp import register_tortoise

from routes import setup_routes


async def init_app():
    app = web.Application()

    # 获取当前文件的目录路径
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))

    # 设置Jinja2模板
    aiohttp_jinja2.setup(
        app,
        loader=jinja2.FileSystemLoader(os.path.join(BASE_DIR, 'templates'))
    )
    # 设置静态文件根URL
    app[aiohttp_jinja2.static_root_key] = '/static'
    # 设置静态文件
    app.router.add_static('/static/',
                          path=os.path.join(BASE_DIR, 'static'),
                          name='static')

    # 设置路由
    setup_routes(app)

    # 设置Tortoise ORM
    register_tortoise(
        app,
        db_url="sqlite://db.sqlite3",
        modules={"models": ["models"]},
        generate_schemas=True
    )

    return app


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Run the web application")
    parser.add_argument('--host', type=str, default='127.0.0.1', help='Host to run the app on')
    parser.add_argument('--port', type=int, default=8000, help='Port to run the app on')
    args = parser.parse_args()

    web.run_app(init_app(), host=args.host, port=args.port)

添加一些基本的样式

在 static/css/style.css 中:

body {
    font-family: Arial, sans-serif;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

form div {
    margin-bottom: 10px;
}

input {
    padding: 5px;
    width: 300px;
}

button {
    padding: 8px 16px;
    background-color: #4CAF50;
    color: white;
    border: none;
    cursor: pointer;
}

运行应用

python main.py

添加数据库迁移支持

对于更复杂的应用,推荐使用 Tortoise ORM 的迁移工具 aerich:

pip install aerich

然后,创建一个 config.py 文件来存储 Tortoise ORM 配置:

# config.py
TORTOISE_ORM = {
    "connections": {
        "default": "sqlite://db.sqlite3",
    },
    "apps": {
        "models": {
            "models": ["models", "aerich.models"],
            "default_connection": "default",
        },
    },
    "use_tz": False,
    "timezone": "Asia/Shanghai"
}

接着初始化 aerich 并创建迁移:

# 初始化 aerich
aerich init -t config.TORTOISE_ORM
# 创建第一个迁移
aerich init-db
# 之后当模型变更时创建迁移
aerich migrate --name add_new_field
# 应用迁移
aerich upgrade

基本的应用程序提供了:

异步数据库访问使用 Tortoise ORM 使用 aiohttp 的 web 服务器 带有 Jinja2 模板的前端界面 简单的用户管理功能

你可以从这个基础上继续扩展,添加更多功能,如用户认证、更复杂的表单处理、API端点等。需要注意的是,所有的数据库操作都应该使用 await 关键字,以确保异步操作的正确执行。

文档信息

版权声明:可自由转载(请注明转载出处)-非商用-非衍生

发表时间:2025年3月1日 19:26