🐸 青蛙编程网
首页 / Python / Flask / 6. 综合练习

综合练习:个人博客后端

这是什么:一个个人博客后端小项目,把 Flask 路由、模板、表单、数据库拧成一体。

为什么用:路由、模板、表单、数据库全部到齐,再不练手就只是四段互不相干的知识。做成一个真能跑的项目,才知道它们怎么配合。

如何用:本章先列清三个页面的网址和功能,再给出完整的 app.py 和模板,数据存进 SQLite。

我们要做什么

页面 网址 功能
文章列表页 / 展示所有文章标题,附「写新文章」入口
文章详情页 /post/<id> 展示一篇文章的标题和正文
写文章页 /new GET 显示表单,POST 提交入库

数据存 SQLite,重启不丢。

项目结构

my-blog/
├── app.py
├── blog.db              ← 第一次运行后自动生成
└── templates/
    ├── list.html
    ├── detail.html
    └── new.html

完整代码

app.py:

import sqlite3
from flask import Flask, render_template, request, redirect

app = Flask(__name__)

def db():
    conn = sqlite3.connect("blog.db")
    conn.row_factory = sqlite3.Row          # 行支持按列名取值
    conn.execute("""
    CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL
    )
    """)
    return conn

@app.route("/")
def index():
    conn = db()
    posts = conn.execute("SELECT * FROM posts ORDER BY id DESC").fetchall()
    conn.close()
    return render_template("list.html", posts=posts)

@app.route("/post/<int:post_id>")            # int: 只匹配整数
def post(post_id):
    conn = db()
    row = conn.execute("SELECT * FROM posts WHERE id = ?", (post_id,)).fetchone()
    conn.close()
    if row is None:
        return "文章不存在", 404             # 同时返回内容和状态码
    return render_template("detail.html", post=row)

@app.route("/new", methods=["GET", "POST"])
def new():
    if request.method == "POST":             # 表单提交进来
        title = request.form.get("title", "").strip()
        content = request.form.get("content", "").strip()
        if title and content:
            conn = db()
            conn.execute("INSERT INTO posts (title, content) VALUES (?, ?)",
                         (title, content))
            conn.commit()
            conn.close()
            return redirect("/")
    return render_template("new.html")       # GET 访问时显示表单

if __name__ == "__main__":
    app.run(debug=True)

templates/list.html:

<h1>我的博客 🐸</h1>
<p><a href="/new">✏️ 写新文章</a></p>
{% for post in posts %}
  <h3><a href="/post/{{ post['id'] }}">{{ post['title'] }}</a></h3>
{% endfor %}
{% if not posts %}<p>还没有文章,点上面链接写第一篇吧!</p>{% endif %}

templates/detail.html:

<h1>{{ post['title'] }}</h1>
<p>{{ post['content'] }}</p>
<p><a href="/">← 返回列表</a></p>

templates/new.html:

<h1>写新文章</h1>
<form method="post" action="/new">
  <p><input name="title" placeholder="文章标题" style="width: 300px;"></p>
  <p><textarea name="content" rows="8" cols="40" placeholder="正文"></textarea></p>
  <p><button type="submit">发布</button></p>
</form>

运行与体验

python app.py

访问 http://127.0.0.1:5000,走一遍完整流程:首页提示「还没有文章」→ 点「写新文章」→ 填标题和正文、发布 → 回到列表,文章出现了 → 点标题进入详情页。重启程序再刷新,文章依然在——SQLite 立功了。

关键点解读

代码 作用
/post/<int:post_id> <int:...> 限定只匹配整数,访问 /post/abc 直接 404
.fetchone() 取一行(没有就返回 None),对应「查单篇」;fetchall 是取全部
return "文章不存在", 404 Flask 允许同时返回内容和状态码,浏览器会看到 404
/new 的 GET + POST 同一个网址:GET 来→显示表单;POST 来→处理提交,一个函数管到底
if title and content: 服务端也要校验数据,不能全信前端

记忆口诀

路由分门户,模板管门面,表单收数据,SQLite 存家底。

动手改

  1. 给文章加发布时间:表加一列 created TEXT,插入时用 time.strftime("%Y-%m-%d %H:%M") 生成,详情页展示出来(改了表结构就删掉 blog.db 重新运行)
  2. 给详情页加「删除」链接:新路由 /delete/<int:post_id>,执行 DELETE FROM posts WHERE id = ? 后重定向回首页
  3. 在列表页显示「共 N 篇文章」(提示:SELECT COUNT(*) FROM posts,结果取 rows[0][0])

Flask 毕业 🎉 下一站 Pandas,玩转数据分析;想继续深挖 Web 后端,去 FastAPI 体验现代 API 开发

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。