Python 现代开发学习笔记

(十三)归属判断、筛选与分页查询

1. 先确定可以访问的记录

alice 登录成功,只能说明当前请求来自 alice。请求 /books/3 还要判断编号 3 是否属于她。列表则直接把归属条件放进查询,使 bob 的书不会进入 alice 的结果。

# 项目文件:api_project/src/bookapi/service.py
from sqlalchemy import func, select
from sqlalchemy.orm import Session

from .errors import Problem, commit_changes
from .models import Book
from .schemas import BookCreate, BookOut, BookPage, BookPatch


def owned_book(session: Session, book_id: int, owner_id: int) -> Book:
    book = session.get(Book, book_id)
    if book is None:
        raise Problem(404, "book_not_found", "没有找到这本书")
    if book.owner_id != owner_id:
        raise Problem(403, "forbidden", "这本书属于其他用户")
    return book


def list_books(session, owner_id, q, finished, offset, limit) -> BookPage:
    conditions = [Book.owner_id == owner_id]
    if q:
        # contains(autoescape=True) 把 % 和 _ 当作书名字符搜索。
        conditions.append(Book.title.contains(q, autoescape=True))
    if finished is not None:
        conditions.append(Book.finished == finished)
    total = session.scalar(select(func.count()).select_from(Book).where(*conditions))
    statement = (
        select(Book).where(*conditions).order_by(Book.id).offset(offset).limit(limit)
    )
    items = [BookOut.model_validate(book) for book in session.scalars(statement)]
    return BookPage(total=total, offset=offset, limit=limit, items=items)

owned_book 先按编号取得记录,找不到给 404;存在但归属不同给 403。后面的读取、修改和删除都调用它。新增时的 owner_id 来自已验证的 identity.user.id,输入模型也会拒绝客户端附加的同名字段。

alice 的合法令牌先通过身份校验,再用书籍编号查出 owner_id;bob 尝试相同编号时在归属比较处停止,记录保持原状

2. 让总数和分页使用同一组条件

conditions 先加入归属,再按本次输入加入书名与状态。where(*conditions) 使用第三章的参数拆包,把列表中的多个条件交给 where,它们组合为同时满足的条件。这里的 Book.finished == finished 产生 SQL 条件对象;它表达数据库比较,没有当场读取一本书。

func.count() 生成 SQL 计数表达式,select_from(Book) 指定从书籍表统计。总数查询和数据查询使用同一组条件;数据查询再加 order_by(Book.id)、offset 和 limit。session.scalars 迭代查询得到的一本本 Book 对象。

用 alice 的六本采集书目观察:未读书编号依次为 1、3、4、6。查询 finished=false&offset=1&limit=2,总数为 4,先跳过 1,返回 3、4。total 是符合筛选的总数,len(items) 才是本页条数。固定排序使“先跳过哪条”有明确答案。

从六本采集书目先筛出未读编号一三四六,统计四条,再跳过第一条取两条得到三和四,区分 total 与本页数量

书名使用 contains(q, autoescape=True),q="%" 查找真正包含百分号的书名。SQL 参数绑定负责把输入作为数据传入;autoescape 再处理 LIKE 的通配符含义。这两步解决不同的文本解释问题。

笔记正文与全部题解 · Ctrl / ⌘ K