同一个待办事项应用,六种写法,一集一集地走。
我学 py4web 的时候,写了第一个 app——一个待办事项。写完回头一看,四不像:既不是我习惯的 ASP Classic 手感,也不是 py4web 的正统写法。
那就来吧——先退化,把它打造成自己习惯的老样子;再掉头,看看现在的世界变成了什么样。
graph TD
A["第1集 todo_classic\nDAL + 手动路由\n起点:一个四不像"]
A -->|退化| B["第2集 todo_classic_-1\nDAL + 自动路由\n不用写路由了"]
B -->|退化| C["第3集 todo_classic_-2\n纯 SQL + 自动路由\n手感如 ASP"]
A -->|进化| D["第4集 todo_classic_1\n业务逻辑从模板收回 __init__.py\n路由 + 逻辑,单文件"]
D -->|进化| E["第5集 todo_classic_one\n官方正统写法\n拆分成 5 个文件,MVC 结构"]
E -->|进化| F["第6集 todo_classic_one_1\n前后端分离\nVue.js 3 + RESTful API"]
style A fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#ff9,stroke:#333,stroke-width:2px
style D fill:#9f9,stroke:#333,stroke-width:2px
style F fill:#69f,stroke:#333,stroke-width:2px
前三集往 ASP Classic 方向退化——丢掉路由、丢掉 ORM,退到自己最舒服的姿势。 退到头了,总不能固步自封吧。后面的集数掉头进化——收回逻辑、拆分脚手架、引入前后端分离,一步步走向现代 Web 开发。
每集代码独立运行,不需要按顺序学。按顺序看能看到每一步"改了什么、为什么改";跳着看也没问题,每集 README 都有和前后的对比。
| 集 | 目录 | 数据库 | 路由 | 代码在哪 | 什么手感 |
|---|---|---|---|---|---|
| 1 | todo_classic/ |
DAL | 手动 @action | 全在模板 | 四不像(起点) |
| 2 | todo_classic_-1/ |
DAL | 自动扫描 | 全在模板 | ORM + 不用写路由 |
| 3 | todo_classic_-2/ |
sqlite3 原生 | 自动扫描 | 全在模板 | ASP Classic + ADO |
| 4 | todo_classic_1/ |
DAL | 手动 @action | 全在 init.py | 掉头进化,单文件版 |
| 5 | todo_classic_one/ |
DAL | 手动 (controllers) | 控制器 + 模板 | MVC 脚手架 |
| 6 | todo_classic_one_1/ |
DAL | RESTful API | 后端 API + Vue.js | 前后端分离 |
| ASP Classic | Py4web | 说明 |
|---|---|---|
<% ... %> |
[[ ... ]] |
服务器端代码块 |
<%= var %> |
[[= var ]] |
输出变量值 |
Response.Redirect("x") |
redirect(URL("x")) |
页面跳转 |
Request.Form("x") |
request.forms.get("x") |
POST 表单值 |
Request.QueryString("id") |
request.query.get("id") |
URL 参数 |
conn.Execute("SELECT ...") |
db(db.tbl).select() 或 conn.execute(...) |
数据库查询 |
rs("field") |
row.field 或 row['field'] |
读取字段 |
If ... Then ... End If |
[[if ...:]] ... [[pass]] |
条件判断 |
For Each ... Next |
[[for x in y:]] ... [[pass]] |
循环 |
global.asa |
__init__.py |
应用初始化 |
在同一个 [[ ]] 块内嵌套 if 会导致编译失败(500 错误,ticket=unknown)。
替代方案:拆成多个 [[ ]] 块,或让 SQL 干活(第3集详细说明了做法)。
[[ ]] 块内写了 if,后面紧跟 [[if:]]...[[pass]],会报 missing "pass" in view。
替代方案:[[ ]] 块内不写 if。
form.submit() 不会触发表单的 onsubmit 事件。确认逻辑要放在触发提交的元素上:
<a href="#" onclick="if(confirm('确认删除?'))this.closest('form').submit()">删除</a>Py4web 的 lazy-mode 只监视 .py 文件,不自动重载 .html 模板。
新加的 app 目录不会被自动发现,必须重启服务。
pip install py4web
cd fromASPClassicToPy4web
python -m py4web run apps --port 8000BSD-3-Clause