例子还是用一个通用的小项目,不涉及任何真实业务。我们就做一个最简单的 「备忘录列表」:能看列表,能新增一条。
1. 这篇学完你能做什么
- 给 Flask 接上数据库
- 定义一张表(模型)
- 查询数据并显示在网页上
- 简单分页(数据多的时候不会一页挤爆)
暂时不学登录、权限、部署。先把 「数据库 → 页面」 这条链路跑通。
2. 先装两个包
在项目目录里执行:
pip install Flask-SQLAlchemy Flask-Migrate
简单理解:
| 包 | 干什么 |
|---|---|
| Flask-SQLAlchemy | 让 Flask 方便地使用数据库 |
| Flask-Migrate | 以后改表结构时用(这篇先装着,下一篇再用) |
入门阶段,数据库先用 SQLite 就够了------一个文件,不用装 MySQL,本地跑起来最快。
3. 改一下 app/__init__.py
在上一篇的基础上,加上数据库配置:
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(name)
app.config"SECRET_KEY" = "dev-secret-key"
数据库文件会放在项目根目录的 instance 文件夹里
basedir = os.path.abspath(os.path.dirname(file))
app.config"SQLALCHEMY_DATABASE_URI" = "sqlite:///" + os.path.join(
basedir, "..", "instance", "app.db"
)
app.config"SQLALCHEMY_TRACK_MODIFICATIONS" = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
from app.home import home
from app.admin import admin
app.register_blueprint(home)
app.register_blueprint(admin, url_prefix="/admin")
这里记住两行就够了:
db = SQLAlchemy(app)
migrate = Migrate(app, db)
db 后面写模型、查数据都要用到它。
4. 定义一张表:app/models.py
新建这个文件,写一个「备忘录」模型:
from datetime import datetime
from app import db
class Note(db.Model):
tablename = "note"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text)
addtime = db.Column(db.DateTime, default=datetime.now)
def repr(self):
return "<Note %r>" % self.title
可以把它理解成:
| 字段 | 含义 |
|---|---|
id |
主键,每条记录的唯一编号 |
title |
标题 |
content |
正文(可以为空) |
addtime |
创建时间 |
类 = 表,属性 = 字段。 这是 ORM 最核心的想法。
5. 第一次创建数据库表
在项目根目录新建 init_db.py(只用一次):
from app import app, db
from app.models import Note
with app.app_context():
db.create_all()
print("数据库表创建成功")
先建目录,再运行:
mkdir -p instance
python init_db.py
成功后会出现 instance/app.db,说明 SQLite 数据库已经建好了。
小提示:
db.create_all()适合入门和本地练习。项目变大、要改表结构时,再用 Flask-Migrate,后面专门讲。
6. 手动塞几条测试数据(可选)
方便先看列表效果,可以临时跑一下:
from app import app, db
from app.models import Note
with app.app_context():
if Note.query.count() == 0:
db.session.add(Note(title="第一条", content="今天学 Flask 数据库"))
db.session.add(Note(title="第二条", content="做一个列表页"))
db.session.commit()
print("测试数据已写入")
7. 写列表页视图
在 app/home/views.py 里加一个路由:
from flask import render_template, request
from app.home import home
from app.models import Note
@home.route("/notes/")
def note_list():
page = request.args.get("page", 1, type=int)
page_data = Note.query.order_by(Note.addtime.desc()).paginate(
page=page,
per_page=10,
)
return render_template("home/note_list.html", page_data=page_data)
逐行看:
Note.query--- 查note表.order_by(Note.addtime.desc())--- 新的排在前面.paginate(page=page, per_page=10)--- 每页 10 条page_data--- 传给模板,里面既有数据,也有分页信息
8. 写模板:app/templates/home/note_list.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>备忘录列表</title>
<style>
body { font-family: sans-serif; max-width: 720px; margin: 40px auto; }
.item { border-bottom: 1px solid #eee; padding: 12px 0; }
.time { color: #888; font-size: 14px; }
.pager a { margin-right: 8px; }
</style>
</head>
<body>
<h1>备忘录</h1>
<p><a href="{{ url_for('home.note_add') }}">+ 新增一条</a></p>
{% for note in page_data.items %}
<div class="item">
<h3>{{ note.title }}</h3>
<p>{{ note.content or '(无内容)' }}</p>
<div class="time">{{ note.addtime.strftime('%Y-%m-%d %H:%M') }}</div>
</div>
{% else %}
<p>还没有数据,先去新增一条吧。</p>
{% endfor %}
<div class="pager">
{% if page_data.has_prev %}
<a href="{{ url_for('home.note_list', page=page_data.prev_num) }}">上一页</a>
{% endif %}
<span>第 {{ page_data.page }} / {{ page_data.pages or 1 }} 页</span>
{% if page_data.has_next %}
<a href="{{ url_for('home.note_list', page=page_data.next_num) }}">下一页</a>
{% endif %}
</div>
</body>
</html>
浏览器打开:
能看到列表,说明 数据库 → Flask → 模板 已经通了。
9. 顺手做一个「新增」页面
列表有了,再加一个最简单的写入。
视图 app/home/views.py
from flask import render_template, request, redirect, url_for, flash
from app import db
from app.home import home
from app.models import Note
@home.route("/notes/add/", methods="GET", "POST")
def note_add():
if request.method == "POST":
title = (request.form.get("title") or "").strip()
content = (request.form.get("content") or "").strip()
if not title:
flash("标题不能为空")
return redirect(url_for("home.note_add"))
note = Note(title=title, content=content)
db.session.add(note)
db.session.commit()
return redirect(url_for("home.note_list"))
return render_template("home/note_add.html")
模板 app/templates/home/note_add.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增备忘录</title>
</head>
<body>
<h1>新增备忘录</h1>
{% with messages = get_flashed_messages() %}
{% for msg in messages %}
<p style="color:red;">{{ msg }}</p>
{% endfor %}
{% endwith %}
<form method="post">
<p>
标题:<br>
<input type="text" name="title" required>
</p>
<p>
内容:<br>
<textarea name="content" rows="5" cols="40"></textarea>
</p>
<button type="submit">保存</button>
<a href="{{ url_for('home.note_list') }}">返回列表</a>
</form>
</body>
</html>
新增数据的固定三步,以后会经常写:
db.session.add(note) # 1. 放进会话
db.session.commit() # 2. 真正写入数据库
return redirect(...) # 3. 跳回列表
10. 新手常踩的 4 个坑
坑 1:忘了 app.app_context()
在 Flask 外面直接 db.create_all() 或 Note.query...,可能报错。
脚本里要包一层:
with app.app_context():
...
坑 2:改了模型,表没跟着变
改了 models.py 里的字段,旧表不会自动更新。
入门阶段可以删 instance/app.db 再跑 init_db.py;正式项目用 Migrate。
坑 3:db.session.add 后忘了 commit
只 add 不 commit,刷新页面数据还是空的。
坑 4:循环导入
models.py 里 from app import db,__init__.py 里又 import models,顺序不对会报错。
按这篇的结构写一般没问题:先在 __init__.py 创建 db,再写 models.py。
11. 小结
这一篇核心就四步:
定义模型 → 建表 → 视图里查询 → 模板里展示
对应代码:
| 步骤 | 在哪 |
|---|---|
| 定义模型 | app/models.py |
| 建表 | db.create_all() |
| 查数据 | Note.query...paginate() |
| 展示 | render_template(...) |
到这里,你已经有一个「能存、能看、能加、能分页」的小功能了。比纯静态页面进了一大步。