☰
Flask 入门实战:路由、模板与 SQLite 增删改查完整 Demo
2026/11/4 17:18:45 网站建设 项目流程

简介:这是一份面向Python Web开发初学者的Flask入门实践项目,以“试验楼小Demo”的形式呈现一个简易博文系统,涵盖用户登录登出、博文发布与查看等核心功能,适合刚接触Web开发、希望从零理解请求处理流程与项目结构的学习者动手练习。压缩包共8个文件,约5KB,以html模板、py主程序、sql建表脚本、db数据库文件及css样式为主,另有pyc编译文件,体量轻巧却覆盖了Flask路由与视图函数、Jinja2模板渲染、SQLite3数据存储、session身份验证、表单处理及错误处理等关键知识点,目录结构清晰,便于对照理解各模块职责。该资源已有3088人学习下载,读者可借此完整走通一个轻量Web应用从建表、编写视图到页面展示的基本链路,并参考其中的表结构设计与模板组织方式,为后续扩展功能或搭建自己的小项目积累可复用的实践思路。

1. 一个 Flask 试验楼 Demo:从零把请求、模板、SQLite 串起来

很多人学 Flask 卡在一个尴尬的位置:官方教程能跑通,但一合上文档就不知道从哪下手。这个试验楼小 Demo 就是冲着这个痛点来的——它不是完整业务系统,而是一套自己敲出来的最小可运行骨架,把路由注册、Jinja2 模板渲染、SQLite 增删改查、表单提交这几件事串成一条能跑通的链路。适合刚接触 Python web 开发、想找一个能改能拆的起点的人,也适合熟手拿它当快速验证某个想法的脚手架。下面按“它是什么、怎么跑起来、哪里会翻车”的顺序拆,代码都能直接抄。

2. 拆开这个 Demo 的骨架:路由、模板与 SQLite 三件套

2.1 目录结构与依赖,先让项目能启动

拿到一个 Flask 项目,第一件事不是读代码,而是看目录和依赖。这个 Demo 的结构很朴素,常见做法是长这样:

flask-demo/ ├── app.py # 入口,创建 app、注册路由 ├── db.py # SQLite 连接与初始化 ├── schema.sql # 建表语句 ├── templates/ │ ├── base.html # 公共布局 │ ├── index.html # 列表页 │ └── edit.html # 编辑页 ├── static/ │ └── style.css └── requirements.txt

依赖只有两个核心包,requirements.txt里写清楚版本区间,避免不同机器装出不同行为:

Flask>=2.3,<3.0

SQLite 是 Python 标准库自带的sqlite3,不需要额外装。虚拟环境建好之后:

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt

这里有个容易被忽略的点:app.py里创建应用时,模板目录和静态目录默认就是templates/和static/,如果你改了目录名,必须显式传template_folder和static_folder,否则启动不报错、访问 404,排查起来很费时间。启动命令用flask --app app run --debug,--debug打开后改代码自动重载,开发期省事,但上线前一定关掉。

2.2 路由与视图函数:请求进来之后发生了什么

Flask 的核心心智模型很简单:一个 URL 对应一个函数,函数返回字符串或渲染后的模板。这个 Demo 的列表页和新增接口大概是这样:

from flask import Flask, render_template, request, redirect, url_for import db app = Flask(__name__) @app.route("/") def index(): # 查询全部记录,交给模板渲染 items = db.get_all_items() return render_template("index.html", items=items) @app.route("/add", methods=["POST"]) def add_item(): # 表单字段名必须和模板里 input 的 name 一致 title = request.form.get("title", "").strip() if not title: return "标题不能为空", 400 db.insert_item(title) return redirect(url_for("index"))

逻辑说明:index只做查询和渲染,不掺业务判断;add_item只接受 POST,取表单字段、做最基础的非空校验、写库、重定向回列表页。重定向用url_for而不是硬编码路径,改路由时不会漏改。参数上,request.form.get第二个参数给默认值,避免字段缺失时抛KeyError;methods不写默认只允许 GET,POST 请求会直接 405,这是新手最常见的翻车点之一。

2.3 SQLite 连接管理:为什么不能每个请求都开新连接

SQLite 是文件数据库,连接开销小,但也不是随便开。这个 Demo 用db.py统一封装,常见做法是把连接挂在 Flask 的应用上下文g上,请求结束自动关闭:

import sqlite3 from flask import g DATABASE = "demo.db" def get_db(): # 每个请求复用同一个连接,避免频繁开关 if "db" not in g: g.db = sqlite3.connect(DATABASE) g.db.row_factory = sqlite3.Row # 让查询结果支持按列名取值 return g.db def close_db(e=None): db = g.pop("db", None) if db is not None: db.close() def init_app(app): app.teardown_appcontext(close_db)

row_factory = sqlite3.Row这一行值得单独说:不设的话查询结果是元组,模板里只能写item[0],设了之后可以写item["title"],可读性和抗改表能力都强很多。teardown_appcontext保证每个请求结束连接被回收,不会出现连接泄漏。建表语句放在schema.sql,初始化时执行一次即可,不要每次启动都CREATE TABLE,否则数据被反复覆盖。

2.4 模板渲染与表单:Jinja2 的两个高频用法

模板层是这个 Demo 里最容易被低估的部分。base.html定义公共骨架,子模板用{% extends %}继承,块用{% block %}占位:

<!-- base.html --> <!doctype html> <html> <head><link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"></head> <body> {% block content %}{% endblock %} </body> </html>

列表页循环渲染时,{{ item["title"] }}会自动做 HTML 转义,这是 Jinja2 默认行为,别为了“显示原样”去加|safe,那等于自己拆掉 XSS 防线。表单提交到/add,input的name必须和视图里request.form.get的键一致,不一致的表现是“提交成功但数据是空的”,不报错,纯靠对字段名。这一层没有玄学,就是字段名对齐和转义默认值两件事。

3. 从建表到增删改查:把一条数据完整走通

3.1 建表与初始化:schema 只跑一次

建表语句建议单独放文件,字段类型按 SQLite 的动态类型来,别照搬 MySQL 那套:

-- schema.sql DROP TABLE IF EXISTS items; CREATE TABLE items ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')) );

done用 0/1 存布尔,SQLite 没有原生布尔类型,这是通行做法。created_at用默认值在插入时自动填,省得应用层每次拼时间字符串。初始化脚本单独写一个init_db.py,只在需要重置时手动跑:

import sqlite3 conn = sqlite3.connect("demo.db") with open("schema.sql", encoding="utf-8") as f: conn.executescript(f.read()) conn.commit() conn.close()

注意executescript会隐式提交之前的事务,别把它塞进业务事务里。另外DROP TABLE IF EXISTS意味着这个脚本是“重置”语义,生产环境千万别这么写,Demo 里图省事可以。

3.2 增删改查四个函数:参数化查询是底线

db.py里补齐四个操作,全部用参数化占位符?,不要用字符串拼接:

def get_all_items(): rows = get_db().execute("SELECT * FROM items ORDER BY id DESC").fetchall() return rows def insert_item(title): db = get_db() db.execute("INSERT INTO items (title) VALUES (?)", (title,)) db.commit() def toggle_item(item_id): db = get_db() db.execute("UPDATE items SET done = 1 - done WHERE id = ?", (item_id,)) db.commit() def delete_item(item_id): db = get_db() db.execute("DELETE FROM items WHERE id = ?", (item_id,)) db.commit()

1 - done是个小技巧,一行完成布尔翻转,不用先查再改。参数化查询不只是防注入,还能让 SQLite 复用查询计划,批量插入时性能差别明显。每个写操作后必须commit(),忘了提交的表现是“接口返回 200 但数据没进库”,这种坑查起来最费劲。删除和翻转接口建议用 POST 而不是 GET,GET 被浏览器预取或爬虫命中会误删数据。

3.3 用 curl 验证接口:不依赖浏览器先跑通链路

页面还没调好之前,先用命令行验证后端逻辑,能快速定位问题出在视图还是模板:

# 新增一条 curl -X POST -d "title=第一条测试" http://127.0.0.1:5000/add -i # 查看列表页返回的 HTML curl http://127.0.0.1:5000/ | grep "第一条测试"

-i把响应头一起打出来,能看到 302 重定向和Location,确认跳转逻辑对。如果 POST 返回 400,先看字段名;返回 405,看methods;返回 500,看终端堆栈。这套顺序比在浏览器里点来点去快得多,尤其是模板报错被吞掉的时候。

4. 避坑排查:这个 Demo 最容易翻车的五个地方

4.1 现象:改了模板刷新没变化

原因通常是 Jinja2 模板缓存,或者--debug没开。解决:开发期用flask --app app run --debug启动,模板改动会自动失效缓存;如果仍不生效,检查是不是起了多个进程,改的那个没被访问到。

4.2 现象:数据库报database is locked

原因是有连接没关闭,或者长事务占着写锁。解决:确认teardown_appcontext注册了close_db;写操作后及时commit;不要在一个请求里开多个连接同时写。SQLite 同一时刻只允许一个写者,这是它的设计边界,不是 bug。

4.3 现象:中文写入后变成乱码或问号

原因是连接或文件编码不一致。解决:sqlite3.connect默认按 UTF-8 处理,问题多出在读schema.sql时没指定encoding="utf-8",以及终端本身编码不对。统一在打开文件时显式写encoding="utf-8",能消掉大部分乱码。

4.4 现象:表单提交后列表页看不到新数据

原因是重定向到了缓存页,或者查询用了旧连接。解决:确认insert_item里commit了;确认列表查询走的是同一个get_db();浏览器强刷一次排除前端缓存。如果 curl 能看到数据而浏览器看不到,基本就是缓存问题。

4.5 现象:部署后静态资源 404

原因是开发期用 Flask 自带服务器,静态文件由它托管;换到别的 WSGI 服务器后,静态路径规则变了。解决:确认url_for('static', filename=...)写法没硬编码路径;生产环境让前置服务器直接托管static/目录,Flask 只处理动态请求。

5. 把它改成你自己的试验楼:三个可落地的扩展点

这个 Demo 的价值不在功能,而在它足够小,小到你敢改。第一个扩展点是加一层简单的数据校验:现在只判空,可以引入wtforms或者手写一个validate_title,把长度、非法字符挡在写库之前,避免脏数据进 SQLite 后再清洗。第二个扩展点是分页,列表页数据一多就卡,SELECT ... LIMIT ? OFFSET ?配合request.args.get("page", 1, type=int)就能加上,注意type=int会在参数非法时回退默认值,比手动int()安全。第三个扩展点是把这个骨架接到真实场景,比如把items换成你自己的业务表,路由按资源拆成蓝图Blueprint,app.py只负责注册,代码量上去之后不会乱成一锅粥。

验证改动是否可靠,我一般会固定跑一遍这条链路:init_db.py重置库 →curl新增两条 → 浏览器看列表 → 翻转一条 → 删除一条 → 再curl确认库内状态。这套动作五分钟能跑完,但能挡住八成“看起来好了其实没写进去”的问题。血泪经验是:别在模板里写业务判断,别在视图里拼 SQL,别在生产环境跑DROP TABLE。从那以后我每次动数据库相关代码,都强制先跑一遍重置加增删改查的闭环,再谈页面好不好看。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询