Python Flask 教程:从入门到掌握 – wiki基地


Python Flask 教程:从入门到掌握

欢迎来到 Flask 的世界!Flask 是一个轻量级的 Python Web 框架,它不依赖外部库(除了 Werkzeug 和 Jinja2),这使得它非常灵活,你可以根据自己的需求选择性地添加组件。与全功能的框架(如 Django)不同,Flask 提供了构建 Web 应用的核心功能,而将数据库、表单验证、用户认证等高级功能留给开发者通过扩展来实现。这使得 Flask 成为开发小型到中型 Web 应用、API 接口或微服务的理想选择。

本教程将带你从零开始,一步步深入 Flask 的世界,最终掌握构建实际 Web 应用所需的技能。

目标读者:

  • 具备基本的 Python 编程知识。
  • 对 Web 开发感兴趣,想学习如何用 Python 构建网站或 API。
  • 希望学习一个灵活、易于上手的 Web 框架。

我们将涵盖的主题:

  1. 入门基础: 安装 Flask,创建第一个“Hello, World”应用。
  2. 路由与视图函数: 如何定义 URL,如何处理请求。
  3. 模板引擎: 使用 Jinja2 渲染动态 HTML 页面。
  4. 静态文件: 处理 CSS, JavaScript 和图片等静态资源。
  5. 请求与响应: 获取表单数据、URL 参数,发送不同类型的响应。
  6. 数据库集成: 使用 SQLAlchemy 操作数据库(以 SQLite 为例)。
  7. 表单处理: 使用 Flask-WTF 构建和验证 Web 表单。
  8. 用户认证基础: 简单的用户登录/注销概念介绍。
  9. 应用结构: 使用蓝图(Blueprints)组织大型应用。
  10. 错误处理: 创建自定义错误页面。
  11. 配置管理: 管理不同环境下的应用配置。
  12. 部署概念: 将 Flask 应用部署到生产环境。
  13. 进阶与生态: 探索 Flask 的扩展和更高级主题。

让我们开始吧!

第一部分:入门基础

1. 安装 Flask

首先,你需要安装 Python。推荐使用 Python 3.6 或更高版本。

在安装 Flask 之前,强烈建议使用虚拟环境(Virtual Environment)。虚拟环境可以隔离不同项目所需的 Python 包,避免版本冲突。

  • 创建虚拟环境:
    bash
    python -m venv venv

    这会在当前目录下创建一个名为 venv 的文件夹,其中包含了虚拟环境。

  • 激活虚拟环境:

    • 在 macOS/Linux 上:
      bash
      source venv/bin/activate
    • 在 Windows 上:
      bash
      venv\Scripts\activate

      激活后,你的终端提示符前会显示 (venv) 字样。
  • 安装 Flask:
    确保虚拟环境已激活,然后使用 pip 安装 Flask:
    bash
    pip install Flask

2. 第一个“Hello, World”应用

创建一个名为 app.py 的文件,输入以下代码:

“`python

app.py

from flask import Flask

创建 Flask 应用实例

name 是一个特殊的 Python 变量,用于指示模块名。

Flask 用它来确定应用根目录,以便找到资源文件(如模板和静态文件)。

app = Flask(name)

定义一个路由 (Route) 和对应的视图函数 (View Function)

@app.route(‘/’) 装饰器将 URL ‘/’ 绑定到 index 函数

@app.route(‘/’)
def index():
“””
这是处理根 URL ‘/’ 的视图函数。
它返回一个简单的字符串作为响应。
“””
return ‘Hello, World!’

如果直接运行这个脚本,启动开发服务器

if name == ‘main‘:
# debug=True 可以在开发期间提供有用的错误信息和自动重载功能
app.run(debug=True)
“`

  • from flask import Flask: 从 flask 库导入 Flask 类。
  • app = Flask(__name__): 创建一个 Flask 应用实例。__name__ 是 Python 的内置变量,表示当前模块的名称。Flask 需要知道它在哪里,以便查找模板和静态文件。
  • @app.route('/'): 这是一个装饰器,将 URL 路径 / 映射到紧随其后的 index 函数。当用户访问应用的根 URL 时,就会执行 index 函数。
  • def index():: 这是一个视图函数。它负责处理来自客户端的请求,并返回一个响应。在这里,它返回一个简单的字符串 'Hello, World!'。Flask 会自动将这个字符串封装成一个 HTTP 响应。
  • if __name__ == '__main__': app.run(debug=True): 这段代码确保只有当你直接运行 app.py 文件时,才会启动 Flask 的开发服务器。debug=True 开启调试模式,这在开发过程中非常有用,它会提供详细的错误报告,并在代码修改后自动重新加载应用。

3. 运行应用

在终端中,确保虚拟环境已激活,然后运行:

bash
python app.py

你会看到类似以下的输出:

* Serving Flask app 'app' (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: XXX-XXX-XXX

在浏览器中打开 http://127.0.0.1:5000/,你将看到 “Hello, World!”。恭喜,你已经成功创建并运行了第一个 Flask 应用!

第二部分:路由与视图函数

路由是将 URL 映射到 Python 函数的过程。视图函数是处理请求并返回响应的函数。

1. 定义更多路由

你可以在 app 实例上使用 @app.route() 装饰器定义多个路由:

“`python

app.py (续)

@app.route(‘/about’)
def about():
“””
处理 ‘/about’ URL 的视图函数。
“””
return ‘This is the About page.’

@app.route(‘/contact’)
def contact():
“””
处理 ‘/contact’ URL 的视图函数。
“””
return ‘Get in touch with us!’
“`

现在访问 http://127.0.0.1:5000/abouthttp://127.0.0.1:5000/contact,将看到对应的页面内容。

2. 动态路由

路由可以包含变量部分,允许你在 URL 中捕获参数。

“`python

app.py (续)

<> 中的部分是变量, 表示捕获一个字符串类型的变量,命名为 name

@app.route(‘/user/‘)
def show_user_profile(name):
“””
处理 ‘/user/‘ URL 的视图函数。
name 参数从 URL 中捕获并作为参数传递给函数。
“””
return f’User: {name}’

可以指定不同的变量类型,如

@app.route(‘/post/‘)
def show_post(post_id):
“””
处理 ‘/post/‘ URL 的视图函数。
post_id 参数从 URL 中捕获并作为整数传递给函数。
“””
return f’Post ID: {post_id}’

默认变量类型是 string

@app.route(‘/hello/‘)
def hello_name(name):
return f’Hello, {name}!’
“`

访问 http://1.27.0.0.1:5000/user/Alice 将显示 “User: Alice”,访问 http://127.0.0.1:5000/post/123 将显示 “Post ID: 123″。

3. URL 构建 (url_for)

在模板或重定向时,硬编码 URL 是不安全的,因为如果路由规则发生变化(例如 URL 路径或参数名改变),所有硬编码的 URL 都需要手动更新。Flask 提供了 url_for() 函数来动态生成 URL。它接收视图函数名作为第一个参数,以及任何 URL 变量作为关键字参数。

“`python

app.py (续)

from flask import url_for, redirect

@app.route(‘/’)
def index():
return ‘Hello, World! Go to About page.’

@app.route(‘/about’)
def about():
return ‘This is the About page. Go back to Home.’

@app.route(‘/profile/‘)
def profile(username):
return f'{username}\’s profile’

@app.route(‘/login’)
def login():
# 模拟登录后重定向到用户主页
# url_for(‘profile’, username=’testuser’) 会生成 ‘/profile/testuser’
return redirect(url_for(‘profile’, username=’testuser’))

… (其他路由和 app.run 代码)

“`

使用 url_for() 的好处:
* 当你的 URL 规则改变时,你只需要更新 @app.route() 装饰器,url_for() 会自动生成正确的 URL。
* 它能正确处理 URL 参数,并进行适当的编码。

第三部分:模板引擎

视图函数返回的通常不是简单的字符串,而是 HTML 页面。直接在 Python 代码中拼接 HTML 字符串非常繁琐且容易出错。Flask 使用 Jinja2 作为默认的模板引擎,它允许你使用类似 HTML 的语法编写模板文件,并在其中嵌入 Python 代码逻辑(如变量、循环、条件判断)。

1. 配置模板

Flask 默认会在应用根目录下的一个名为 templates 的文件夹中查找模板文件。你需要创建这个文件夹。

your_flask_app/
├── app.py
└── templates/
└── index.html
└── about.html

2. 编写模板文件 (templates/index.html)

“`html






My Flask App

Hello from Template!

This content is rendered using Jinja2.


“`

3. 在视图函数中渲染模板

使用 render_template 函数来渲染模板。

“`python

app.py (续)

from flask import render_template # 导入 render_template 函数

@app.route(‘/’)
def index():
“””
渲染 index.html 模板。
“””
return render_template(‘index.html’)

@app.route(‘/hello/‘)
def hello_name(name):
“””
渲染一个模板,并将变量传递给模板。
“””
return render_template(‘hello.html’, user_name=name) # 将 name 变量传递给模板,在模板中可以通过 user_name 访问
“`

创建 templates/hello.html:

“`html






Hello, {{ user_name }}! {# 使用双大括号 {{ }} 来显示变量的值 #}

Hello, {{ user_name }}!

{# 变量 user_name 的值会在这里渲染 #}

Welcome to my Flask application.


“`

访问 http://127.0.0.1:5000/ 将看到 index.html 的内容,访问 http://127.0.0.1:5000/hello/Alice 将看到 hello.html 的内容,其中 {{ user_name }} 被替换为 “Alice”。

4. Jinja2 语法基础

  • 变量: {{ variable_name }}。可以在 Python 视图函数中将变量作为关键字参数传递给 render_template
  • 控制结构: {% ... %} 用于编写控制语句,如 if/elsefor 循环。
    • 条件判断:
      html
      {% if user %}
      <p>Welcome, {{ user.name }}!</p>
      {% else %}
      <p>Please log in.</p>
      {% endif %}
    • 循环:
      html
      <ul>
      {% for item in items %}
      <li>{{ item }}</li>
      {% endfor %}
      </ul>
  • 注释: {# 这是注释 #},不会被渲染到最终的 HTML 中。
  • 模板继承: Jinja2 支持模板继承,可以定义一个基础模板,其他模板可以继承它并覆盖特定的块。这非常有利于网站整体布局的一致性。

    • 创建基础模板 templates/base.html:
      “`html
      <!DOCTYPE html>




      {% block title %}My App{% endblock %} {# 定义一个 block,子模板可以覆盖 #}
      {# 引入静态文件将在下一节介绍 #}

      My Awesome App

      <main>
          {% block content %}{% endblock %} {# 定义主要内容的 block #}
      </main>
      
      <footer>
          <p>&copy; 2023 My App</p>
      </footer>
      



      “`

    • 继承基础模板 templates/index.html:
      “`html
      {% extends “base.html” %} {# 继承 base.html 模板 #}

      {% block title %}Home – My App{% endblock %} {# 覆盖 title block #}

      {% block content %} {# 覆盖 content block #}

      Welcome to the Home Page

      This is the main content area.

      {% endblock %}
      * 继承基础模板 `templates/about.html`:html
      {% extends “base.html” %}

      {% block title %}About – My App{% endblock %}

      {% block content %}

      About Us

      Learn more about our project.

      {% endblock %}
      * 在视图函数中渲染时,只需渲染子模板即可:python
      @app.route(‘/’)
      def index():
      return render_template(‘index.html’)

      @app.route(‘/about’)
      def about():
      return render_template(‘about.html’)
      “`

第四部分:静态文件

静态文件是指不会动态生成,直接提供给客户端的文件,如 CSS 样式表、JavaScript 脚本、图片、字体等。

Flask 默认会在应用根目录下的一个名为 static 的文件夹中查找静态文件。

your_flask_app/
├── app.py
├── templates/
│ └── ...
└── static/
├── css/
│ └── style.css
└── images/
└── logo.png

1. 引用静态文件

在模板文件中,使用 url_for() 函数并指定端点名为 'static',然后通过 filename 参数指定静态文件相对于 static 目录的路径来引用静态文件。

“`html
{# templates/base.html (续) #}






{% block title %}My App{% endblock %}
{# 引入 CSS 文件 #}

{# 引入图片文件 #}
Logo

My Awesome App


{% block content %}{% endblock %}

© 2023 My App

{# 引入 JavaScript 文件 (如果需要的话) #}
{# #}

“`

创建 static/css/style.css:

“`css
/ static/css/style.css /
body {
font-family: sans-serif;
margin: 20px;
}

header {
background-color: #f0f0f0;
padding: 10px;
text-align: center;
}

main {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
}

footer {
margin-top: 20px;
text-align: center;
font-size: 0.8em;
color: #666;
}
“`

将一个图片文件(例如 logo.png)放入 static/images 目录。重新运行应用,你将在页面中看到应用了样式的元素和引入的图片。

第五部分:请求与响应

Web 应用的核心是处理来自客户端的请求(Request)并返回响应(Response)。Flask 提供了 request 对象来访问客户端发送的数据。

1. 获取请求数据

导入 request 对象:from flask import request

  • URL 查询参数 (Query Parameters): 在 URL 问号 ? 后的参数,如 /search?q=flask&page=1
    “`python
    # app.py (续)
    from flask import request

    @app.route(‘/search’)
    def search():
    query = request.args.get(‘q’) # 获取 q 参数的值,如果不存在则返回 None
    page = request.args.get(‘page’, type=int, default=1) # 获取 page 参数,尝试转为整数,失败或不存在则用默认值 1
    if query:
    return f’Searching for: “{query}”, Page: {page}’
    else:
    return ‘Please provide a search query (e.g., /search?q=python)’
    ``request.args` 是一个类字典对象,用于访问 GET 请求的查询参数。

  • 表单数据 (Form Data): 通过 POST 请求提交的表单数据。
    python
    # app.py (续)
    @app.route('/submit_form', methods=['GET', 'POST']) # 允许 GET 和 POST 方法
    def submit_form():
    if request.method == 'POST':
    # 获取表单字段的值
    username = request.form.get('username')
    password = request.form.get('password')
    # 处理数据 (例如保存到数据库)
    return f'Form submitted. Username: {username}, Password: {password}'
    # 如果是 GET 请求,通常是显示表单页面
    return '''
    <form method="post">
    Username: <input type="text" name="username"><br>
    Password: <input type="password" name="password"><br>
    <input type="submit" value="Submit">
    </form>
    '''

    request.form 是一个类字典对象,用于访问 POST 请求的表单数据。

  • JSON 数据: 用于处理 API 请求中发送的 JSON 数据。
    python
    # app.py (续)
    @app.route('/api/data', methods=['POST'])
    def api_data():
    if request.is_json:
    data = request.json # 获取解析后的 JSON 数据
    # 处理数据
    return {"received_data": data, "status": "success"}, 200 # 返回 JSON 响应和状态码
    else:
    return {"error": "Request must be JSON"}, 415 # 返回错误响应和状态码

    request.json 会尝试解析请求体中的 JSON 数据。request.is_json 可以检查请求的 Content-Type 是否是 JSON。

  • 请求头 (Headers): 访问 HTTP 请求头信息。
    python
    # app.py (续)
    @app.route('/headers')
    def show_headers():
    user_agent = request.headers.get('User-Agent') # 获取 User-Agent 头
    return f'Your User Agent is: {user_agent}'

    request.headers 是一个类字典对象,包含所有请求头。

2. 返回不同类型的响应

除了字符串和渲染的模板,视图函数还可以返回:

  • HTML: 通过字符串或模板渲染。
  • JSON: 使用 jsonify 函数 (从 flask 导入)。
    “`python
    from flask import jsonify

    @app.route(‘/users’)
    def get_users():
    # 假设从数据库获取用户列表
    users = [
    {‘id’: 1, ‘name’: ‘Alice’},
    {‘id’: 2, ‘name’: ‘Bob’}
    ]
    return jsonify(users) # 返回 JSON 格式的响应
    * **重定向 (Redirect):** 使用 `redirect` 函数。python
    from flask import redirect, url_for

    @app.route(‘/old-page’)
    def old_page():
    return redirect(url_for(‘index’)) # 重定向到 index 视图函数对应的 URL
    ``
    * **特定状态码:** 可以将响应内容和状态码一起返回,例如
    return “Not Found”, 404`。

第六部分:数据库集成

Web 应用通常需要存储和检索数据。虽然 Flask 本身不提供数据库功能,但可以轻松集成各种数据库和 ORM(对象关系映射)工具。SQLAlchemy 是 Python 生态中最流行的 ORM 之一,而 Flask-SQLAlchemy 是一个 Flask 扩展,简化了 SQLAlchemy 在 Flask 中的使用。

1. 安装 Flask-SQLAlchemy

bash
pip install Flask-SQLAlchemy

2. 配置数据库

在应用中配置数据库连接 URI 和创建 SQLAlchemy 实例。我们将使用简单易用的 SQLite 数据库。

“`python

app.py (续)

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy # 导入 SQLAlchemy

app = Flask(name)

数据库配置

SQLite 数据库文件位于应用根目录

app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///site.db’

禁止跟踪对象的修改,可以节省内存开销

app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False

db = SQLAlchemy(app) # 创建 SQLAlchemy 实例

定义一个简单的模型(数据库表)

class Post(db.Model):
id = db.Column(db.Integer, primary_key=True) # 主键,自动增长
title = db.Column(db.String(100), nullable=False) # 字符串类型,非空
content = db.Column(db.Text, nullable=False) # 文本类型,非空

def __repr__(self):
    # 用于打印对象时显示信息
    return f"Post('{self.title}', '{self.content[:20]}...')"

在应用上下文内创建数据库表

通常在第一次运行时执行一次

with app.app_context():
db.create_all()
# 可以在这里添加一些初始数据,但通常是在单独的脚本中做

… (其他路由和 app.run)

“`

  • app.config['SQLALCHEMY_DATABASE_URI']: 配置数据库的连接 URI。sqlite:///site.db 表示在应用根目录下创建一个名为 site.db 的 SQLite 数据库文件。
  • db = SQLAlchemy(app): 创建 SQLAlchemy 实例,并将其与 Flask 应用关联。
  • class Post(db.Model):: 定义一个名为 Post 的类,它继承自 db.Model。这个类代表数据库中的一张表。类属性(如 id, title, content)代表表中的列。
  • db.Column(...): 定义列的类型和约束。
  • with app.app_context(): db.create_all(): 在 Flask 应用的上下文环境中执行 db.create_all(),这会根据你定义的模型创建相应的数据库表。通常这个操作只需要执行一次(或者使用迁移工具)。

3. 数据库操作 (CRUD)

  • 创建 (Create):
    python
    # 创建一个新 Post 对象
    new_post = Post(title='First Post', content='This is the content of the first post.')
    # 将对象添加到会话
    db.session.add(new_post)
    # 提交会话,将对象保存到数据库
    db.session.commit()

  • 读取 (Read):
    “`python
    # 查询所有 Post
    all_posts = Post.query.all()

    根据主键查询单个 Post

    post_by_id = Post.query.get(1) # 查询 id 为 1 的 Post

    根据条件过滤查询

    查询 title 为 ‘First Post’ 的所有 Post

    posts_with_title = Post.query.filter_by(title=’First Post’).all()

    查询 title 包含 ‘Post’ 的所有 Post (使用 like)

    posts_like_title = Post.query.filter(Post.title.like(‘%Post%’)).all()
    “`

  • 更新 (Update):
    python
    post_to_update = Post.query.get(1)
    if post_to_update:
    post_to_update.title = 'Updated Title'
    post_to_update.content = 'New and improved content!'
    db.session.commit() # 提交会话以保存更改

  • 删除 (Delete):
    python
    post_to_delete = Post.query.get(2)
    if post_to_delete:
    db.session.delete(post_to_delete) # 从会话中删除对象
    db.session.commit() # 提交会话以从数据库中删除

4. 在视图函数中使用数据库

“`python

app.py (续)

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

… (app, config, db, Post Model definition) …

@app.route(‘/’)
def index():
# 从数据库获取所有 Post
posts = Post.query.all()
# 渲染模板并传递 posts 数据
return render_template(‘index.html’, posts=posts)

@app.route(‘/add_post’, methods=[‘GET’, ‘POST’])
def add_post():
if request.method == ‘POST’:
title = request.form[‘title’]
content = request.form[‘content’]
new_post = Post(title=title, content=content)
db.session.add(new_post)
db.session.commit()
return redirect(url_for(‘index’)) # 添加成功后重定向回首页
# GET 请求显示添加文章的表单
return render_template(‘add_post.html’) # 你需要创建 add_post.html 模板

@app.route(‘/post/‘)
def view_post(post_id):
# 根据 id 查询文章,如果找不到则返回 404 错误
post = Post.query.get_or_404(post_id)
return render_template(‘view_post.html’, post=post) # 你需要创建 view_post.html 模板

… (if name == ‘main‘: …)

“`

创建相应的模板文件(index.htmladd_post.htmlview_post.html),在其中使用 Jinja2 语法遍历和显示数据,以及创建表单。

第七部分:表单处理 (Flask-WTF)

处理 Web 表单涉及显示表单、验证用户输入、处理错误以及接收数据。 Flask-WTF 是一个 Flask 扩展,它集成了 WTForms 库,极大地简化了这些任务,特别是提供了 CSRF(跨站请求伪造)保护。

1. 安装 Flask-WTF

bash
pip install Flask-WTF

2. 配置 Secret Key

Flask-WTF 需要一个 Secret Key 来生成 CSRF token 和加密 session。

“`python

app.py (续)

app = Flask(name)

… SQLAlchemy config …

必须配置一个 Secret Key

在生产环境中,这个key应该是一个复杂、随机且保密的值,并且不应该直接写在代码中

app.config[‘SECRET_KEY’] = ‘your_very_secret_random_key_here’

db = SQLAlchemy(app)

… Post Model …

“`

重要: 在生产环境中,不要将 Secret Key 直接写在代码中,而是通过环境变量或其他安全方式配置。

3. 定义表单类

创建一个 Python 文件(例如 forms.py)来定义表单类:

“`python

forms.py

from flask_wtf import FlaskForm # 导入 FlaskForm
from wtforms import StringField, TextAreaField, SubmitField # 导入常用字段类型
from wtforms.validators import DataRequired, Length # 导入常用的验证器

class PostForm(FlaskForm):
# 定义一个标题字段,类型为字符串输入框
# DataRequired() 验证器确保字段不为空
# Length() 验证器限制字段长度
title = StringField(‘Title’, validators=[DataRequired(), Length(max=100)])

# 定义一个内容字段,类型为文本区域
content = TextAreaField('Content', validators=[DataRequired()])

# 定义一个提交按钮
submit = SubmitField('Submit')

“`

4. 在视图函数中使用表单

导入表单类,创建表单实例,并在视图函数中处理它。

“`python

app.py (续)

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from forms import PostForm # 从 forms.py 导入 PostForm

… (app, config, db, Post Model) …

@app.route(‘/add_post’, methods=[‘GET’, ‘POST’])
def add_post():
form = PostForm() # 创建表单实例

# form.validate_on_submit() 会在 POST 请求时验证表单数据,并在验证通过时返回 True
if form.validate_on_submit():
    # 从表单对象中获取数据
    title = form.title.data
    content = form.content.data
    new_post = Post(title=title, content=content)
    db.session.add(new_post)
    db.session.commit()
    # Flash 消息(可选,需要 Flask-Flash 扩展或手动实现)
    # flash('Your post has been created!', 'success')
    return redirect(url_for('index')) # 添加成功后重定向回首页

# 如果是 GET 请求或表单验证失败,渲染模板并传递表单对象
return render_template('add_post.html', form=form)

“`

5. 在模板中渲染表单

使用 Jinja2 语法渲染表单字段。

“`html
{# templates/add_post.html (假设继承了 base.html) #}
{% extends “base.html” %}

{% block title %}Add New Post – My App{% endblock %}

{% block content %}

Add New Post

{# action 属性可以省略,默认提交到当前 URL #}
{# method 必须是 POST #}

{{ form.csrf_token }} {# 必须包含 CSRF token 字段,用于安全验证 #}

    <div>
        {{ form.title.label }}<br> {# 渲染标签 #}
        {{ form.title() }} {# 渲染输入框本身 #}
        {# 显示验证错误信息 #}
        {% if form.title.errors %}
            <ul class="errors">
            {% for error in form.title.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </div>

    <div>
        {{ form.content.label }}<br>
        {{ form.content() }}
        {% if form.content.errors %}
            <ul class="errors">
            {% for error in form.content.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </div>

    <div>
        {{ form.submit() }} {# 渲染提交按钮 #}
    </div>
</form>

{% endblock %}
``{{ form.csrf_token }}是 Flask-WTF 提供的重要安全特性,务必包含在表单中。{{ form.field_name.label }}{{ form.field_name() }}会分别渲染字段的标签和输入控件。{{ form.field_name.errors }}` 是一个列表,包含该字段的验证错误信息。

第八部分:用户认证基础

用户认证(注册、登录、注销)是许多 Web 应用的常见需求。Flask 本身没有内置的认证系统,但 Flask-Login 扩展提供了一个灵活的框架来处理用户会话管理。

由于实现完整的用户认证系统涉及用户模型、密码哈希、会话管理、权限控制等多个方面,在这里只做概念介绍和 Flask-Login 的引入。完整的实现可以作为后续的练习。

1. 安装 Flask-Login

bash
pip install Flask-Login

2. 配置 Flask-Login

“`python

app.py (续)

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user # 导入 Flask-Login 相关的类和函数

… (app, config, db) …

配置 Flask-Login

login_manager = LoginManager()
login_manager.init_app(app) # 初始化 LoginManager
login_manager.login_view = ‘login’ # 设置未登录用户尝试访问 @login_required 页面时重定向到的视图函数名

login_manager.login_message = ‘请登录以访问此页面。’ # 可选:设置登录提示消息

login_manager.login_message_category = ‘info’ # 可选:设置消息类别

定义用户模型(需要实现 UserMixin 提供的必要属性和方法)

在实际应用中,需要包含密码字段(加密存储!)

class User(db.Model, UserMixin): # 继承 db.Model 用于数据库,继承 UserMixin 用于 Flask-Login
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
# password = db.Column(db.String(60), nullable=False) # 实际应用中存储密码哈希

# UserMixin 提供了 is_authenticated, is_active, is_anonymous, get_id() 方法

def __repr__(self):
    return f"User('{self.username}')"

Flask-Login 需要一个用户加载函数

这个函数接收用户 ID,并返回对应的用户对象

@login_manager.user_loader
def load_user(user_id):
# user_id 是一个字符串,需要转换为整数(如果你的用户 ID 是整数)
if user_id is not None:
return User.query.get(int(user_id))
return None

… (db.create_all() 也需要包含 User 表) …

with app.app_context():

db.create_all()

… (其他路由) …

“`

3. 认证相关的视图函数

  • 登录: 接收用户名/密码,验证后调用 login_user(user_object) 建立会话。
  • 注销: 调用 logout_user() 清除会话。
  • 需要登录的页面: 使用 @login_required 装饰器。

“`python

app.py (续)

… (imports, app, config, db, models, login_manager, load_user) …

示例登录视图 (简化版,不含密码验证和表单处理)

@app.route(‘/login’, methods=[‘GET’, ‘POST’])
def login():
# 在实际应用中,这里会处理登录表单,验证用户名和密码
if request.method == ‘POST’:
username = request.form.get(‘username’) # 假设从表单获取用户名
# 查找用户 (实际应验证密码)
user = User.query.filter_by(username=username).first()
if user: # 假设用户存在且密码正确 (简化处理)
login_user(user) # 登录用户,创建会话
# 获取登录前尝试访问的页面 URL,如果没有则重定向到首页
next_page = request.args.get(‘next’)
return redirect(next_page or url_for(‘index’))
else:
# 处理登录失败
pass # 在模板中显示错误消息

# GET 请求或登录失败时,渲染登录页面
return render_template('login.html') # 需要创建 login.html

@app.route(‘/logout’)
@login_required # 只有已登录用户才能访问此路由
def logout():
logout_user() # 注销用户,清除会话
return redirect(url_for(‘index’))

需要登录才能访问的页面示例

@app.route(‘/dashboard’)
@login_required # 只有已登录用户才能访问
def dashboard():
# current_user 对象代表当前已登录的用户
return f’Welcome to your dashboard, {current_user.username}!’

… (if name == ‘main‘: …)

``login_user(user_object)会将用户对象存储在会话中。logout_user()会从会话中移除用户。@login_required装饰器会自动检查用户是否已登录,如果未登录,则重定向到login_manager.login_view指定的视图。current_user` 可以在任何地方访问当前登录的用户对象(未登录时为匿名用户对象)。

第九部分:应用结构 (蓝图 Blueprints)

随着应用功能的增加,将所有路由、视图函数和模型都放在一个文件中会变得难以管理。Flask 提供了蓝图(Blueprints)来帮助你组织应用代码,将相关的部分分组到独立的模块中。

蓝图就像是微型应用,它可以定义自己的路由、模板目录、静态文件目录等。然后,你可以在主应用实例中注册一个或多个蓝图。

1. 创建蓝图文件

在一个新的文件夹中(例如 auth),创建一个 __init__.py 文件来创建蓝图实例,并定义相关的视图函数。

your_flask_app/
├── app.py # 主应用文件
├── config.py # 配置 (可选)
├── forms.py # 表单 (可选)
├── models.py # 模型 (可选)
├── templates/ # 主模板目录
├── static/ # 主静态文件目录
└── auth/ # auth 蓝图目录
├── __init__.py # 创建 auth 蓝图和定义路由
└── templates/ # auth 蓝图的模板目录
└── login.html
└── register.html

auth/__init__.py:

“`python
from flask import Blueprint, render_template, redirect, url_for, request, flash

导入你的 User 模型和任何 auth 相关的表单 (例如 LoginForm, RegistrationForm)

from ..models import User # 如果 models.py 在上级目录

from ..forms import LoginForm, RegistrationForm # 如果 forms.py 在上级目录

from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash # 用于密码加密

创建一个名为 ‘auth’ 的蓝图

name 是蓝图的导入名

url_prefix=’/auth’ 会给蓝图中的所有路由添加前缀 /auth

auth_bp = Blueprint(‘auth’, name, template_folder=’templates’) # 指定蓝图的模板目录

@auth_bp.route(‘/register’, methods=[‘GET’, ‘POST’])
def register():
# 这里应该使用 RegistrationForm
# if form.validate_on_submit():
# hashed_password = generate_password_hash(form.password.data)
# new_user = User(username=form.username.data, password=hashed_password)
# db.session.add(new_user)
# db.session.commit()
# flash(‘Your account has been created!’, ‘success’)
# return redirect(url_for(‘auth.login’)) # 使用蓝图名.视图函数名
# return render_template(‘register.html’, form=form)
return “Register Page (Implementation goes here)”

@auth_bp.route(‘/login’, methods=[‘GET’, ‘POST’])
def login():
# 这里应该使用 LoginForm
# if form.validate_on_submit():
# user = User.query.filter_by(username=form.username.data).first()
# if user and check_password_hash(user.password, form.password.data):
# login_user(user, remember=form.remember.data) # remember=True 可以记住用户
# next_page = request.args.get(‘next’)
# return redirect(next_page or url_for(‘index’)) # 重定向到主应用路由
# else:
# flash(‘Login Unsuccessful. Please check username and password’, ‘danger’)
# return render_template(‘login.html’, form=form)
return “Login Page (Implementation goes here)”

@auth_bp.route(‘/logout’)
@login_required
def logout():
logout_user()
# flash(‘You have been logged out.’, ‘info’)
return redirect(url_for(‘index’))
“`

2. 在主应用中注册蓝图

在你的主应用文件 (app.py) 中导入蓝图实例并注册它。

“`python

app.py (续)

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

… 其他导入 …

from auth import auth_bp # 导入 auth 蓝图实例

… (app, config, db, models, login_manager, load_user) …

注册蓝图

app.register_blueprint(auth_bp, url_prefix=’/auth’) # 可以再次指定 url_prefix,这里是 /auth

… (其他主应用路由,如 index, post, etc.) …

… (if name == ‘main‘: …)

“`

现在,访问 /auth/register/auth/login 将分别调用蓝图中定义的 registerlogin 视图函数。使用 url_for 时,需要加上蓝图的名称,例如 url_for('auth.login')。主应用中的视图函数则直接使用函数名,例如 url_for('index')

使用蓝图的好处:
* 模块化: 将相关功能分组,提高代码的可读性和可维护性。
* 可复用性: 蓝图可以在不同的 Flask 应用中复用。
* 解耦: 蓝图之间相对独立,减少耦合。

第十部分:错误处理

优雅地处理错误是构建健壮 Web 应用的重要部分。你可以自定义特定 HTTP 状态码(如 404 Not Found, 500 Internal Server Error)的错误页面。

“`python

app.py (续)

… (imports, app, config, db, blueprints, etc.) …

定义 404 错误处理器

@app.errorhandler(404)
def page_not_found(error):
“””
处理 404 Not Found 错误的视图函数。
“””
# 返回一个渲染的模板和 404 状态码
return render_template(‘404.html’), 404

定义 500 错误处理器

@app.errorhandler(500)
def internal_server_error(error):
“””
处理 500 Internal Server Error 错误的视图函数。
“””
return render_template(‘500.html’), 500

… (其他路由和 app.run) …

``
你需要创建
templates/404.htmltemplates/500.html` 模板文件来显示自定义错误信息。

第十一部分:配置管理

不同的运行环境(开发环境、测试环境、生产环境)通常需要不同的配置,例如数据库连接、调试模式开关、日志级别等。将这些配置集中管理是一个好习惯。

一种常见的方法是使用 Python 类来定义配置,然后根据环境变量或命令行参数加载相应的配置类。

1. 创建配置文件 (config.py)

“`python

config.py

import os

basedir = os.path.abspath(os.path.dirname(file)) # 获取应用根目录的绝对路径

class Config:
SECRET_KEY = os.environ.get(‘SECRET_KEY’) or ‘fallback-secret-key-for-dev’ # 从环境变量获取,否则使用默认值
# 数据库配置基础,具体的 URI 在子类中定义
SQLALCHEMY_DATABASE_URI = os.environ.get(‘DATABASE_URL’) or \
‘sqlite:///’ + os.path.join(basedir, ‘app.db’) # 默认使用 app.db
SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
DEBUG = True # 开启调试模式
# 可以覆盖父类的数据库 URI,使用不同的开发数据库
SQLALCHEMY_DATABASE_URI = os.environ.get(‘DEV_DATABASE_URL’) or \
‘sqlite:///’ + os.path.join(basedir, ‘data-dev.db’)

class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = ‘sqlite:///:memory:’ # 使用内存数据库进行测试

class ProductionConfig(Config):
DEBUG = False
# 在生产环境中,务必通过环境变量设置 DATABASE_URL 和 SECRET_KEY
# 例如:export DATABASE_URL=’postgresql://user:password@host:port/dbname’
# Flask 应用在生产环境不应该使用 debug=True

创建一个字典,根据配置名获取对应的配置类

config = {
‘development’: DevelopmentConfig,
‘testing’: TestingConfig,
‘production’: ProductionConfig,
‘default’: DevelopmentConfig # 默认使用开发配置
}
“`

2. 在应用工厂中使用配置

推荐使用应用工厂模式来创建 Flask 应用实例。应用工厂是一个函数,它负责创建、配置并返回 Flask 应用。这使得测试和管理多个配置更加方便。

app.py 修改为:

“`python

app.py

import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

导入配置对象和蓝图

from config import config
from auth import auth_bp

from .models import db as database # 如果 db 在 models.py 中定义并导入了

db = SQLAlchemy() # 在工厂外部创建 SQLAlchemy 实例,但不关联应用

login_manager = LoginManager()
login_manager.login_view = ‘auth.login’ # 指定蓝图中的登录视图

login_manager.login_message = ‘请登录以访问此页面。’

login_manager.login_message_category = ‘info’

User loader function (如果在 models.py 中定义 User 模型,这里需要导入)

from .models import User

@login_manager.user_loader

def load_user(user_id):

if user_id is not None:

return User.query.get(int(user_id))

return None

应用工厂函数

def create_app(config_name=’default’):
“””
创建并配置 Flask 应用实例。
:param config_name: 指定要加载的配置名 (‘development’, ‘testing’, ‘production’, ‘default’)
“””
app = Flask(name)

# 加载配置
app.config.from_object(config[config_name])

# 初始化扩展
db.init_app(app) # 将 db 实例与 app 关联
login_manager.init_app(app) # 将 login_manager 实例与 app 关联

# 注册蓝图
app.register_blueprint(auth_bp, url_prefix='/auth')

# 注册主应用路由 (可以在这里直接定义,或者创建另一个蓝图来组织)
@app.route('/')
def index():
    # 可以在这里查询数据库等
    return render_template('index.html')

# ... 添加其他主应用路由 ...

# 注册错误处理器
@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(error):
    return render_template('500.html'), 500


return app

如何运行应用 (通常用于开发环境)

if name == ‘main‘:
# 从环境变量获取配置名,如果没有则使用默认值 ‘development’
config_name = os.environ.get(‘FLASK_CONFIG’) or ‘default’
app = create_app(config_name)

# 在应用上下文内创建数据库表 (如果 db 在 models.py 中定义,需要导入 User 模型)
# from models import User # 确保 User 模型已加载
with app.app_context():
   db.create_all()
   # 可以在这里添加一些初始数据

app.run() # debug 模式由配置类中的 DEBUG 决定

“`

现在,你可以通过设置 FLASK_CONFIG 环境变量来选择不同的配置运行应用,例如:
* 在 Linux/macOS Bash 中:export FLASK_CONFIG='production' 然后 python app.py
* 在 Windows Command Prompt 中:set FLASK_CONFIG=production 然后 python app.py

第十二部分:部署概念

将 Flask 应用从开发环境迁移到生产环境需要一些额外的步骤:

  • 关闭调试模式: 生产环境绝不能开启 debug=True,否则会暴露敏感信息。这在配置管理中已经通过 ProductionConfig 处理了。
  • 使用生产级的 WSGI 服务器: Flask 内置的开发服务器不适合生产环境。你需要使用 Gunicorn 或 uWSGI 等生产级 WSGI (Web Server Gateway Interface) 服务器来运行应用。
  • 反向代理服务器: 通常会在 WSGI 服务器前面再放置一个反向代理服务器,如 Nginx 或 Apache。反向代理处理静态文件、负载均衡、SSL 加密等,并将动态请求转发给 WSGI 服务器。
  • 数据库: 在生产环境中使用更健壮的数据库系统,如 PostgreSQL、MySQL 等,而不是 SQLite (SQLite 更适合小型应用或开发测试)。
  • 依赖管理: 使用 pip freeze > requirements.txt 命令生成应用所需的所有库及其版本,并在生产环境中使用 pip install -r requirements.txt 进行安装,确保环境一致。
  • 日志记录: 配置适当的日志记录,以便监控应用运行状态和排查问题。
  • 环境变量: 将敏感配置(如 SECRET_KEY, 数据库连接信息)通过环境变量注入到应用中。

常见的部署平台包括:

  • Heroku: 托管 PaaS (Platform as a Service),配置简单。
  • PythonAnywhere: 易于使用的 Web Hosting。
  • AWS (Elastic Beanstalk, EC2): 灵活但需要更多配置。
  • Google Cloud (App Engine, Compute Engine): 类似的云平台。
  • Dokku/CapRover: 轻量级 PaaS 解决方案。
  • Docker: 将应用打包成容器,提供环境一致性。

部署的具体步骤因平台和选择的技术栈而异,需要查阅对应平台的文档。但核心思想是将开发环境的便利性换成生产环境的稳定性、安全性和可扩展性。

第十三部分:进阶与生态

掌握了基础知识后,你可以进一步探索 Flask 的更高级功能和庞大的扩展生态:

  • Flask Extensions:
    • Flask-Migrate: 数据库迁移工具 (基于 Alembic)。
    • Flask-RESTful/Flask-RESTx: 构建 RESTful API。
    • Flask-Caching: 缓存。
    • Flask-Mail: 发送邮件。
    • Flask-Limiter: 速率限制。
    • Flask-DebugToolbar: 开发时的调试工具。
    • 以及许多其他扩展,几乎可以满足各种常见需求。
  • 构建 RESTful API: Flask 非常适合构建 API。你可以返回 JSON 响应,处理不同的 HTTP 方法,并结合 Flask-RESTful 或 Flask-RESTx 简化开发。
  • 背景任务: 对于耗时的操作(如发送邮件、处理图片),不应该阻塞 Web 服务器。可以使用 Celery、RQ 等工具将这些任务推送到后台队列中处理。
  • 测试: Flask 提供了一个测试客户端,可以方便地对视图函数进行单元测试和集成测试。
  • 安全: 深入学习 Web 安全最佳实践,如 XSS 防御、SQL 注入防御(使用 ORM 会有很大帮助)、安全头部设置等。
  • 异步编程: 结合 asyncio 等库实现异步视图函数(需要 Flask 2.0+)。

Flask 的强大之处在于其核心简洁,而通过丰富的扩展生态能够构建出功能强大且灵活的应用。

总结

从创建第一个“Hello, World”应用,到掌握路由、模板、静态文件、请求处理,再到集成数据库、表单、用户认证,以及如何使用蓝图组织大型项目和管理配置,我们已经学习了构建 Flask Web 应用的基石。

Flask 的“微”特性赋予了你极高的自由度,但也意味着你需要自行选择和集成所需的组件。这使得它成为一个优秀的学习工具,能够帮助你深入理解 Web 开发的各个环节。

下一步:

  1. 动手实践: 理论知识是基础,但实践才是王道。尝试使用 Flask 构建一个小项目,例如一个博客、一个待办事项列表应用或一个简单的社交应用。
  2. 阅读文档: Flask 及其扩展的官方文档非常详细和清晰,是解决问题和深入学习的最佳资源。
  3. 学习更多扩展: 根据你的项目需求,学习并使用更多的 Flask 扩展。
  4. 探索部署: 尝试将你的应用部署到实际的服务器上,了解生产环境的挑战和最佳实践。

祝你在 Flask 的学习旅程中取得成功!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部