引言
Flask是Python Web开发领域最受欢迎的微框架之一,其轻量、灵活和易于扩展的特性。
Flask 简介
2.1 什么是Flask
Flask是一个轻量级的Python Web应用框架,Flask被称为"微框架",因为它保持核心简单但可扩展,不强制依赖特定的库或工具,给予开发者极大的灵活性和控制力。
Flask的主要特点包括:
轻量且高效:核心简洁,启动迅速,资源占用低
灵活性:不强制特定项目结构或组件选择
易于学习:API设计直观,学习曲线平缓
可扩展性:通过丰富的扩展生态系统增强功能
强大的路由系统:支持URL变量和HTTP方法
内置开发服务器:便于本地测试和开发
RESTful支持:轻松构建符合REST规范的API
2.2 Flask
安装Flask
使用pip安装Flask非常简单:
java
pip install flask
建议在虚拟环境中安装Flask,以避免依赖冲突(建议使用miniconda):
java
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境(Windows)
venv\Scripts\activate
# 激活虚拟环境(Linux/Mac)
source venv/bin/activate
安装Flask
java
pip install flask
验证安装:
python -c "import flask; print(flask.__version__)"
Flask 基础知识
第一个Flask应用
创建一个最简单的Flask应用只需几行代码:
java
from flask import Flask
# 创建Flask应用实例
app = Flask(__name__)
# 定义路由和视图函数
@app.route('/')
def hello_world():
return 'Hello, World!'
启动应用
if name == 'main ':
app.run(debug=True)
将上述代码保存为app.py并运行:
java
python app.py
打开浏览器访问http://127.0.0.1:5000/即可看到"Hello, World!"消息。
应用实例
Flask应用的核心是Flask类 的实例,通常命名为app:
java
app = Flask(__name__)
# 参数__name__是Python的特殊变量,它会传递当前模块的名称给Flask。这有助于Flask找到资源文件的位置。
路由系统
路由是将URL映射到视图函数的机制。Flask使用装饰器来定义路由:
java
@app.route('/user/<username>')
def show_user_profile(username):
return f'User {username}'
Flask支持在URL中包含变量,类型可以是:
字符串(默认):
整数:int:post_id
浮点数:float:score
路径:path:subpath
UUID:uuid:id
示例:
java
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post {post_id}'
路由可以限定接受的HTTP方法:
java
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 处理表单提交
return '处理登录'
else:
# 显示登录表单
return '显示登录表单'
视图函数
视图函数是处理请求并返回响应的Python函数。视图函数可以返回:
字符串:直接显示为HTML
HTML模板渲染结果
JSON响应
重定向
自定义响应对象
示例:
java
from flask import render_template, jsonify, redirect, url_for
@app.route('/template')
def template_example():
return render_template('example.html', name='Flask')
@app.route('/api/data')
def api_data():
return jsonify({"name": "Flask", "type": "framework"})
@app.route('/redirect')
def redirect_example():
return redirect(url_for('hello_world'))
请求对象
Flask通过request对象提供对客户端请求数据的访问:
python
from flask import request
@app.route('/submit', methods=['POST'])
def submit():
# 获取表单数据
username = request.form.get('username')
# 获取URL参数
page = request.args.get('page', 1, type=int)
# 获取JSON数据
data = request.get_json()
# 获取文件
file = request.files.get('upload')
return f'Received: {username}'
3.6 响应对象
视图函数可以返回一个元组来设置响应的状态码和头信息:
python
@app.route('/response')
def custom_response():
return 'Custom response', 201, {'X-Custom-Header': 'value'}
也可以使用make_response函数创建自定义响应:
python
from flask import make_response
@app.route('/cookie')
def set_cookie():
resp = make_response('Cookie设置成功')
resp.set_cookie('username', 'flask_user')
return resp
模板系统
Flask使用Jinja2作为默认的模板引擎,它功能强大且易于使用。
Jinja2模板基础
Jinja2模板是包含静态内容和动态内容占位符的文件。默认情况下,Flask在应用的templates目录中查找模板。
一个基本的HTML模板示例(templates/index.html):
html
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% else %}
<p>No messages.</p>
{% endif %}
</body>
</html>
在视图中渲染该模板:
python
@app.route('/')
def index():
return render_template('index.html',
title='Flask Template',
name='User',
messages=['Message 1', 'Message 2'])
模板语法
Jinja2模板支持三种主要的语法结构:
python
变量:{{ variable }}
控制结构:{% if condition %} ... {% endif %}
注释:{# This is a comment #}
4.2.1 变量与过滤器
变量可以通过过滤器进行转换:
python
{{ name|capitalize }}
{{ text|truncate(100) }}
{{ data|tojson }}
常用的过滤器:
python
safe: 标记内容为安全,不进行转义
escape: HTML转义
capitalize: 首字母大写
lower/upper: 转换大小写
trim: 去除首尾空白
striptags: 移除HTML标签
default: 提供默认值
4.2.2 控制结构
条件语句:
python
{% if user.is_authenticated %}
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('login') }}">Login</a>
{% endif %}
循环:
python
<ul>
{% for item in items %}
<li>{{ loop.index }} - {{ item.name }}</li>
{% else %}
<li>No items found.</li>
{% endfor %}
</ul>
4.3 模板继承
Jinja2支持模板继承,这是一种强大的组织模板的方式。
基础模板(base.html):
python
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block styles %}{% endblock %}
</head>
<body>
<header>
<nav>{% block nav %}{% endblock %}</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}© 2023 Flask应用{% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>
子模板(page.html):
python
{% extends "base.html" %}
{% block title %}页面标题{% endblock %}
{% block content %}
<h1>页面内容</h1>
<p>这是页面的具体内容。</p>
{% endblock %}
4.4 静态文件
Flask自动为静态文件添加路由。默认情况下,静态文件应放在应用的static目录中。
在模板中引用静态文件:
python
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<img src="{{ url_for('static', filename='images/logo.png') }}">
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
4.5 URL生成
使用url_for()函数动态生成URL,避免硬编码:
python
<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('user_profile', username='john') }}">用户资料</a>
<a href="{{ url_for('static', filename='style.css') }}">样式表</a>
- 表单处理
Web应用几乎都需要处理用户输入的表单数据。Flask提供了多种方式处理表单提交。
5.1 基本表单处理
最简单的表单处理方式是直接使用Flask的request对象:
python
from flask import request, redirect, url_for, render_template
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# 验证用户名和密码
if username == 'admin' and password == 'secret':
return redirect(url_for('dashboard'))
else:
error = '无效的用户名或密码'
return render_template('login.html', error=error)
# GET请求显示表单
return render_template('login.html')
python
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>登录</h1>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<button type="submit">登录</button>
</form>
</body>
</html>
5.2 使用Flask-WTF扩展
对于复杂表单,推荐使用Flask-WTF扩展,它结合了WTForms库,提供了表单验证、CSRF保护等功能。
安装Flask-WTF:
python
pip install flask-wtf
配置应用:
python
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # 用于CSRF保护
定义表单类:
python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class LoginForm(FlaskForm):
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
submit = SubmitField('登录')
在视图中使用表单:
python
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# 表单验证通过
email = form.email.data
password = form.password.data
# 处理登录逻辑
return redirect(url_for('dashboard'))
return render_template('login_wtf.html', form=form)
带有WTForms的模板(login_wtf.html):
python
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
{{ form.hidden_tag() }}
<div>
{{ form.email.label }}
{{ form.email }}
{% if form.email.errors %}
<ul>
{% for error in form.email.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div>
{{ form.password.label }}
{{ form.password }}
{% if form.password.errors %}
<ul>
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{{ form.submit }}
</form>
</body>
</html>
文件上传
处理文件上传需要在表单中添加enctype="multipart/form-data"属性:
python
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
在Flask中处理上传文件:
python
from werkzeug.utils import secure_filename
import os
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# 检查是否有文件部分
if 'file' not in request.files:
return '没有文件部分'
file = request.files['file']
# 如果用户未选择文件,浏览器会提交一个没有文件名的空部分
if file.filename == '':
return '未选择文件'
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return f'文件 {filename} 上传成功'
return '''
<!doctype html>
<title>上传文件</title>
<h1>上传文件</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
'''
5.4 表单验证
WTForms提供了丰富的验证器:
DataRequired:字段不能为空
Email:必须是有效的电子邮件地址
Length:字符串长度限制
NumberRange:数值范围限制
EqualTo:字段必须与另一个字段值相等(如密码确认)
URL:必须是有效的URL
Regexp:必须匹配正则表达式
自定义验证示例:
python
from wtforms import ValidationError
class RegistrationForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(min=4, max=20)])
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
confirm_password = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('注册')
def validate_username(self, username):
# 检查用户名是否已存在
if username.data == 'admin':
raise ValidationError('该用户名已被使用,请选择其他用户名。')
数据库集成
Flask本身不包含数据库抽象层,但可以与各种数据库解决方案集成。最常用的是SQLAlchemy ORM通过Flask-SQLAlchemy扩展。
安装:
python
pip install flask-sqlalchemy
基本配置:
python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
数据库URL格式因数据库类型而异:
python
SQLite: sqlite:///site.db
MySQL: mysql://username:password@localhost/db_name
PostgreSQL: postgresql://username:password@localhost/db_name
定义模型
使用SQLAlchemy定义数据库模型(表):
python
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
# 一对多关系
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
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)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
# 外键
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
6.3 创建和迁移数据库
创建数据库表:
python
# 在Python交互式shell中
from app import db
db.create_all()
对于更复杂的迁移,可以使用Flask-Migrate扩展(基于Alembic):
python
pip install flask-migrate
配置Flask-Migrate:
python
from flask_migrate import Migrate
migrate = Migrate(app, db)
然后可以使用命令行管理迁移:
python
flask db init # 初始化迁移仓库
flask db migrate # 创建迁移脚本
flask db upgrade # 应用迁移到数据库
基本CRUD操作
创建记录
python
@app.route('/add_user', methods=['POST'])
def add_user():
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
user = User(username=username, email=email, password=password)
db.session.add(user)
db.session.commit()
return f'用户 {username} 已添加'
查询记录
python
@app.route('/users')
def get_users():
users = User.query.all()
return render_template('users.html', users=users)
@app.route('/user/<int:user_id>')
def get_user(user_id):
user = User.query.get_or_404(user_id)
return render_template('user.html', user=user)
常用查询方法:
python
# 获取所有记录
User.query.all()
# 获取指定ID的记录
User.query.get(1)
User.query.get_or_404(1) # ID不存在时返回404错误
# 条件查询
User.query.filter_by(username='john').first()
User.query.filter(User.email.endswith('@example.com')).all()
# 排序
User.query.order_by(User.username).all()
# 限制结果数量
User.query.limit(10).all()
# 分页
users = User.query.paginate(page=2, per_page=20)
for user in users.items:
print(user.username)
更新记录
python
@app.route('/update_user/<int:user_id>', methods=['POST'])
def update_user(user_id):
user = User.query.get_or_404(user_id)
user.username = request.form.get('username', user.username)
user.email = request.form.get('email', user.email)
db.session.commit()
return f'用户 {user.username} 已更新'
python
删除记录
@app.route('/delete_user/<int:user_id>', methods=['POST'])
def delete_user(user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return f'用户 {user.username} 已删除'
对于中大型Flask应用,推荐以下目录结构:
python
myapp/
├── app/
│ ├── __init__.py # 应用工厂
│ ├── models/ # 数据库模型
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── post.py
│ ├── views/ # 视图函数和蓝图
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── auth.py
│ │ └── api.py
│ ├── forms/ # 表单类
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ └── main.py
│ ├── static/ # 静态文件
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── templates/ # HTML模板
│ │ ├── base.html
│ │ ├── main/
│ │ ├── auth/
│ │ └── errors/
│ └── utils/ # 工具函数
│ ├── __init__.py
│ └── helpers.py
├── migrations/ # 数据库迁移
├── tests/ # 测试用例
│ ├── __init__.py
│ ├── test_user.py
│ └── test_api.py
├── venv/ # 虚拟环境
├── config.py # 配置文件
├── requirements.txt # 依赖包列表
├── run.py # 应用入口
└── README.md # 项目说明