零基础入门python24:分类 CRUD 与用户数据隔离

发布时间:2026/8/26 18:16:40

零基础入门python24:分类 CRUD 与用户数据隔离
零基础入门python24分类 CRUD 与用户数据隔离一、上一篇课后练习讲解上一篇要求配置会话过期和 Cookie 安全属性。开发环境可以使用较短过期时间方便测试生产环境要启用SESSION_COOKIE_SECURE、HTTPOnly和合适的SameSite策略。上一篇课后练习完整答案上一篇练习的要求已落实到下面完整文件先运行项目测试再用 curl 对照状态码和数据库持久化结果答案要点登录用 check_password_hashuser_loader 从数据库加载/me 依赖 current_user未登录统一 401不暴露邮箱是否存在。文件app/auth.py完整参考答案文件完整文件app/auth.pyfromflaskimportBlueprint,requestfromflask_loginimportlogin_user,login_required,current_userfromwerkzeug.securityimportcheck_password_hashfrom.modelsimportUser bpBlueprint(auth,__name__,url_prefix/api/auth)bp.post(/login)deflogin():datarequest.get_json(silentTrue)or{}userUser.query.filter_by(emailstr(data.get(email,)).casefold()).first()ifnotuserornotcheck_password_hash(user.password_hash,str(data.get(password,))):return{error:invalid_credentials},401login_user(user)return{id:user.id,email:user.email}bp.get(/me)login_requireddefme():return{id:current_user.id,email:current_user.email}完整参考答案文件本篇对应的交付源码完整文件flask-ledger/app/auth.pyfromflaskimportBlueprint,requestfromflask_loginimportcurrent_user,login_required,login_user,logout_userfrom.extensionsimportdbfrom.modelsimportUser bpBlueprint(auth,__name__,url_prefix/api/auth)bp.post(/register)defregister():datarequest.get_json(silentTrue)or{}emailstr(data.get(email,)).strip().lower()passwordstr(data.get(password,))ifnotinemail:return{message:邮箱格式不正确},400iflen(password)8:return{message:密码至少8位},400ifdb.session.scalar(db.select(User).where(User.emailemail)):return{message:邮箱已注册},409userUser(emailemail)user.set_password(password)db.session.add(user)db.session.commit()return{id:user.id,email:user.email},201bp.post(/login)deflogin():datarequest.get_json(silentTrue)or{}emailstr(data.get(email,)).strip().lower()userdb.session.scalar(db.select(User).where(User.emailemail))ifnotuserornotuser.check_password(str(data.get(password,))):return{message:邮箱或密码错误},401login_user(user)return{id:user.id,email:user.email}bp.post(/logout)login_requireddeflogout():logout_user()return{message:已退出}bp.get(/me)login_requireddefme():return{id:current_user.id,email:current_user.email}验收命令python -m pytest -qDjango 项目使用 python manage.py test。预期测试通过若失败先检查迁移、配置和事务回滚。二、本篇完成什么分类是账目的前置资源。本篇实现当前用户的分类新增、列表、修改和删除并验证用户 A 不能读取或修改用户 B 的分类。三、为什么每个查询都要带 user_id只用Category.query.get(category_id)会让用户猜 id 后读到别人的数据。正确做法是把资源所有权放进查询条件where(Category.id id, Category.user_id current_user.id)。这样即使客户端传入别人的 id查询也不会返回记录。四、关键实现bp.post(/api/categories)login_requireddefcreate_category():datarequest.get_json(silentTrue)or{}namestr(data.get(name,)).strip()ifnotname:return{message:分类名不能为空},400ifdb.session.scalar(db.select(Category).where(Category.user_idcurrent_user.id,Category.namename)):return{message:分类已存在},409rowCategory(namename,user_idcurrent_user.id)db.session.add(row)db.session.commit()return{id:row.id,name:row.name},201重复检查是用户范围内的检查数据库联合唯一约束是最终防线两者同时存在前者提供友好错误后者应对并发请求。五、验收用户 A 登录后创建“餐饮”列表能看到用户 B 登录后列表为空修改 A 的分类返回 404。测试同时覆盖未登录 401、空名称 400、重复名称 409。课后练习增加分类删除接口并阻止删除仍有账目的分类。项目增量分类 CRUD 与用户隔离分类也是用户资源。查询、修改和删除都必须带 current_user.id只根据分类 id 查询会导致越权读取。categoryCategory.query.filter_by(idcategory_id,user_idcurrent_user.id).first_or_404()category.nameform.name.strip()db.session.commit()名称唯一约束应按 user_id name 组合而不是全站唯一。删除仍被账目使用的分类要返回业务错误或要求迁移账目不能静默丢失历史。验收与课后练习用户 A 不能读取、修改或删除用户 B 的分类重复名称返回 409课后增加分类删除保护测试。五、分类 CRUD 的完整闭环分类接口不是简单的四个 SQL。每一步都要带上当前用户条件避免用户 A 通过猜 id 访问用户 B 的数据ledger_bp.get(/categories)login_requireddeflist_categories():rows(Category.query.filter_by(user_idcurrent_user.id).order_by(Category.name.asc()).all())return{items:[{id:row.id,name:row.name}forrowinrows]}ledger_bp.post(/categories)login_requireddefcreate_category():name(request.get_json(silentTrue)or{}).get(name,).strip()ifnot1len(name)30:return{error:name_length},400rowCategory(user_idcurrent_user.id,namename)db.session.add(row)try:db.session.commit()exceptIntegrityError:db.session.rollback()return{error:category_exists},409return{id:row.id,name:row.name},201ledger_bp.delete(/categories/int:category_id)login_requireddefdelete_category(category_id):rowCategory.query.filter_by(idcategory_id,user_idcurrent_user.id).first()ifrowisNone:# 对不存在和不属于自己的资源统一返回 404避免泄露资源存在性。return{error:not_found},404db.session.delete(row)db.session.commit()return,204删除分类前要决定已有账目如何处理本项目把category_id设为可空删除时置空如果产品要求禁止删除就应该在服务层先统计引用数量并返回 409。不要依赖 SQLite 的默认外键行为因为不同数据库的外键开关不同。六、分页和搜索的边界可以用page、size两个参数并限制size 100pagemax(request.args.get(page,1,typeint),1)sizemin(max(request.args.get(size,20,typeint),1),100)keywordrequest.args.get(q,).strip()queryCategory.query.filter_by(user_idcurrent_user.id)ifkeyword:queryquery.filter(Category.name.ilike(f%{keyword}%))totalquery.count()itemsquery.order_by(Category.id.desc()).offset((page-1)*size).limit(size).all()count()和列表查询是两次 SQL数据量很大时可以改为游标分页现在先保证契约清晰。不要直接把size拼到 SQL 字符串里ORM 参数化会自动处理类型。七、上一篇练习讲解与错误排查上一篇的/auth/me要复用current_user不能从请求体接收 user id。测试至少覆盖A 创建分类后能看到B 看不到 A 的分类重复名称返回 409删除不存在的 id 返回 404。IntegrityError未回滚会让后续请求一直处于“事务已失败”状态表现为PendingRollbackError遇到该错误第一时间执行db.session.rollback()。八、本篇练习实现分类更新PUT /categories/id要求新名称仍在 1—30 字且不能与当前用户其他分类重复补充一个“引用账目时拒绝删除”的模式开关。下一篇将把分类 id 接入账目创建并讲 Decimal 与日期输入。本篇结束完整模块文件下面是交付项目中真实存在的完整文件 flask-ledger/app/ledger.py。它覆盖本篇新增逻辑以及前文已经完成的依赖代码复制单个函数会丢失上下文因此这里提供整份文件。fromdatetimeimportdatefromdecimalimportDecimal,InvalidOperationfromflaskimportBlueprint,requestfromflask_loginimportcurrent_user,login_requiredfromsqlalchemyimportfuncfrom.extensionsimportdbfrom.modelsimportCategory,Transaction bpBlueprint(ledger,__name__,url_prefix/api)defowned_category(category_id:int):returndb.session.scalar(db.select(Category).where(Category.idcategory_id,Category.user_idcurrent_user.id))bp.get(/categories)login_requireddeflist_categories():rowsdb.session.scalars(db.select(Category).where(Category.user_idcurrent_user.id).order_by(Category.name)).all()return[{id:row.id,name:row.name}forrowinrows]bp.post(/categories)login_requireddefcreate_category():namestr((request.get_json(silentTrue)or{}).get(name,)).strip()ifnot1len(name)40:return{message:分类名称长度应为1到40},400existsdb.session.scalar(db.select(Category).where(Category.user_idcurrent_user.id,Category.namename))ifexists:return{message:分类已存在},409rowCategory(namename,user_idcurrent_user.id)db.session.add(row)db.session.commit()return{id:row.id,name:row.name},201bp.post(/transactions)login_requireddefcreate_transaction():datarequest.get_json(silentTrue)or{}try:amountDecimal(str(data.get(amount,0))).quantize(Decimal(0.01))happened_ondate.fromisoformat(str(data.get(happened_on,date.today())))category_idint(data.get(category_id))except(InvalidOperation,ValueError,TypeError):return{message:金额、日期或分类格式不正确},400kindstr(data.get(kind,))ifkindnotin{income,expense}oramount0:return{message:类型或金额不正确},400ifnotowned_category(category_id):return{message:分类不存在},404rowTransaction(kindkind,amountamount,notestr(data.get(note,))[:200],happened_onhappened_on,category_idcategory_id,user_idcurrent_user.id,)db.session.add(row)db.session.commit()returnrow.to_dict(),201bp.get(/transactions)login_requireddeflist_transactions():pagemax(request.args.get(page,1,typeint),1)sizemin(max(request.args.get(size,10,typeint),1),100)querydb.select(Transaction).where(Transaction.user_idcurrent_user.id)ifkind:request.args.get(kind):queryquery.where(Transaction.kindkind)ifcategory_id:request.args.get(category_id,typeint):queryquery.where(Transaction.category_idcategory_id)rowsdb.session.scalars(query.order_by(Transaction.happened_on.desc(),Transaction.id.desc()).offset((page-1)*size).limit(size)).all()return{page:page,size:size,items:[row.to_dict()forrowinrows]}bp.delete(/transactions/int:transaction_id)login_requireddefdelete_transaction(transaction_id:int):rowdb.session.scalar(db.select(Transaction).where(Transaction.idtransaction_id,Transaction.user_idcurrent_user.id))ifnotrow:return{message:账目不存在},404db.session.delete(row)db.session.commit()return,204bp.get(/statistics/monthly)login_requireddefmonthly_statistics():monthrequest.args.get(month,date.today().strftime(%Y-%m))try:startdate.fromisoformat(month-01)exceptValueError:return{message:月份格式应为YYYY-MM},400enddate(start.year(start.month12),1ifstart.month12elsestart.month1,1)rowsdb.session.execute(db.select(Transaction.kind,func.sum(Transaction.amount)).where(Transaction.user_idcurrent_user.id,Transaction.happened_onstart,Transaction.happened_onend).group_by(Transaction.kind)).all()totals{income:Decimal(0),expense:Decimal(0)}totals.update({kind:Decimal(total)forkind,totalinrows})return{month:month,income:str(totals[income]),expense:str(totals[expense]),balance:str(totals[income]-totals[expense])}

相关新闻

深度观察 | 实验可以外包,但数据和AI脑必须留下来

深度观察 | 实验可以外包,但数据和AI脑必须留下来

2026/8/26 18:16:40

先把话说在前面:把湿实验交给专业的 CRO(合同研发组织)来做,是一件既高效又划算的事。CRO 拥有标准化的实验平台、成熟的质控体系和规模化的产能,能帮研发企业省下大量重复建设的成本,让团队把精力放在更核…

flutter+极光推送

flutter+极光推送

2026/8/26 18:16:40

可以把推送理解成:服务器主动把一条消息送到用户手机上的 App,而不是 App 自己不断去服务器问“有没有新消息”。1. 什么是推送?比如你的 App 有一条客服消息:客服:你好,你的订单已经处理完成。正常流程是&…

Linux软件编程学习笔记(二)——标准IO

Linux软件编程学习笔记(二)——标准IO

2026/8/26 18:16:40

一、什么是 IOIO 是:I:Input 输入 O:Output 输出例如:键盘 ─────→ 程序输入程序 ─────→ 屏幕输出文件 ─────→ 程序输入程序 ─────→ 文件输出在 Linux 软件编程中,IO 主要包括&#x…

Kimi    LeetCode LCP 35. 电动车游城市 Python3实现

Kimi LeetCode LCP 35. 电动车游城市 Python3实现

2026/8/26 19:16:42

以下是 LCP 35. 电动车游城市 的 Python3 实现,采用 分层图最短路 Dijkstra 算法。---解题思路这是一道经典的分层图最短路问题。核心思想是将状态定义为 (城市, 电量) 二元组,然后在这个扩展的状态空间上运行 Dijkstra 算法 。状态空间: - …

有限 GBP 对滤波器的影响

有限 GBP 对滤波器的影响

2026/8/26 19:16:42

在对有源滤波器的研究中,我们假设运算放大器是理想的,这样我们就可以不用考虑运算放大器的特性,而是只研究滤波器的响应。理想情况(简化模型):​ 在最基础的分析中,通常假设运算放大器是“理想”…

[光学原理与应用-545]:光的十层理解:从表入里,由浅入深

[光学原理与应用-545]:光的十层理解:从表入里,由浅入深

2026/8/26 19:16:42

第一层:光的感知 —— 明暗与颜色最表层的理解。光让我们看见世界:白天与黑夜、影子、蓝天、彩虹、火焰。这是所有动物对光的本能认知 —— 光就是 "照亮",颜色就是光的 "样子"。这一层不涉及任何物理模型,只…

【中国方言题库|16】HarmonyOS ArkTS 多设备布局实战:适配手机、平板和 PC/2in1 的窗口变化

【中国方言题库|16】HarmonyOS ArkTS 多设备布局实战:适配手机、平板和 PC/2in1 的窗口变化

2026/8/26 19:16:42

HarmonyOS 应用做多设备适配,最容易出现一种“看起来已经适配”的假象:项目里有 sm、md、lg 三档断点,也写了底部导航和侧边导航,但真正改变窗口宽度时,外层导航没有切换;部分内容页已经变成三列&#xff0…

桥梁缺陷检测 桥梁病害识别数据集

桥梁缺陷检测 桥梁病害识别数据集

2026/8/26 19:16:42

🌉 桥梁缺陷 YOLO 数据集(6308 张 8 类缺陷) 6308 张实拍 8 类桥梁缺陷 YOLO 格式精细标注 直接用于 YOLOv5/v8/v11 训练 适用于桥梁健康监测、无人机智能巡检、基础设施数字化运维等场景,覆盖结构性病害与功能性病害的完整谱…

【2026年】实验室危化品智能追踪与通风联动

【2026年】实验室危化品智能追踪与通风联动

2026/8/26 19:06:42

危化品是实验室安全管理的高风险对象。传统管理依赖纸质台账与人工盘点,物品去向不清、存放超期、泄漏处置不及时等问题长期存在。随着实验室数字化推进,危化品管理正从"登记在册"走向"实时追踪",并逐步与通风系统联动&a…

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

2026/8/26 1:50:39

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

2026/8/26 1:49:16

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

2026/8/26 17:50:58

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

2026/8/26 0:05:45

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

Hermes接入团队协作后,我推翻了三个效率假设

Hermes接入团队协作后,我推翻了三个效率假设

2026/8/26 0:05:45

聊《Hermes真能提效吗?先看流程里最慢的那一步》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要团队把 Hermes 接进项目三个月后,交付速度没有提升反而慢了。复盘后发现,最先…

免费AI大模型调教指南:打造专属网文写作助手

免费AI大模型调教指南:打造专属网文写作助手

2026/8/26 0:05:45

1. 先搞清楚“AI小说扩展模式”到底能帮你做什么如果你是一个刚开始写网文、或者卡在L3级别以下的作者,最头疼的可能是情节推进不下去、人物对话干瘪,或者世界观设定不够丰满。自己对着空白文档硬憋,效率很低。这时候,一个能理解你…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/22 2:02:26

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/26 18:07:30

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/26 17:57:52

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…