Python之Flask开发框架(第二篇) — 模板、表单与数据库
在上一篇教程中,我们掌握了 Flask 的基础知识,包括安装、第一个应用、路由和视图函数。本篇将带领大家进入 Flask 开发的更核心领域:模板渲染、表单处理和数据库操作。通过这些内容,你将能够构建出功能完整、交互友好且具备数据持久化能力的 Web 应用。
1. 模板渲染
在实际项目中,我们通常不会在视图函数中直接返回 HTML 字符串,而是将 HTML 代码放在独立的模板文件中,并通过模板引擎动态填充数据。Flask 默认使用Jinja2模板引擎。
1.1 基本模板语法
首先,在项目根目录下创建templates文件夹,用于存放所有模板文件。创建一个简单的模板index.html:
<!DOCTYPEhtml><html><head><title>{{ title }}</title></head><body><h1>Hello, {{ name }}!</h1></body></html>在视图函数中,使用render_template函数渲染模板并传递参数:
fromflaskimportrender_template@app.route('/')defindex():returnrender_template('index.html',title='Home',name='Alice')Jinja2 支持多种语法:
- 变量:
{{ variable }} - 控制结构:
{% if user %} <p>Hello, {{ user }}</p> {% else %} <p>Hello, Guest</p> {% endif %} {% for item in items %} <li>{{ item }}</li> {% endfor %} - 过滤器:通过
|对变量进行格式化,如{{ name|upper }}、{{ date|datetime }}(需自定义)。 - 宏:类似函数,可复用模板片段:
{% macro input(name, value='', type='text') %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> {% endmacro %}
1.2 模板继承
模板继承是 Jinja2 最强大的功能之一,可以避免重复代码。定义一个基础模板base.html:
<!DOCTYPEhtml><html><head><title>{% block title %}{% endblock %} - My App</title></head><body><header><h1>My App</h1></header><main>{% block content %}{% endblock %}</main><footer><p>©2023</p></footer></body></html>子模板通过extends继承,并填充block:
{% extends "base.html" %} {% block title %}Home{% endblock %} {% block content %}<p>Welcome to the homepage!</p>{% endblock %}1.3 静态文件管理
静态文件(CSS、JS、图片)应放在static文件夹中。在模板中,使用url_for('static', filename='style.css')生成正确路径:
<linkrel="stylesheet"href="{{ url_for('static', filename='style.css') }}">2. 表单处理
表单是用户与 Web 应用交互的主要方式。Flask 本身提供了基础的请求数据处理,但推荐使用Flask-WTF扩展来简化表单创建、验证和 CSRF 保护。
2.1 安装 Flask-WTF
pipinstallflask-wtf2.2 定义表单类
表单类继承自FlaskForm,字段类型由 WTForms 提供。创建一个简单的登录表单:
fromflask_wtfimportFlaskFormfromwtformsimportStringField,PasswordField,SubmitFieldfromwtforms.validatorsimportDataRequired,Email,LengthclassLoginForm(FlaskForm):email=StringField('Email',validators=[DataRequired(),Email()])password=PasswordField('Password',validators=[DataRequired(),Length(min=6)])submit=SubmitField('Login')2.3 在视图中使用表单
在视图函数中创建表单实例,并检查validate_on_submit()来处理提交。
fromflaskimportrender_template,redirect,url_for@app.route('/login',methods=['GET','POST'])deflogin():form=LoginForm()ifform.validate_on_submit():# 表单验证通过,处理登录逻辑email=form.email.data password=form.password.data# 实际开发中应验证数据库中的用户returnredirect(url_for('index'))returnrender_template('login.html',form=form)2.4 在模板中渲染表单
Jinja2 可以方便地渲染表单字段和错误信息:
{% extends "base.html" %} {% block content %}<formmethod="POST"action="">{{ form.hidden_tag() }}<!-- 生成 CSRF token --><div>{{ form.email.label }}<br>{{ form.email(size=32) }}<br>{% for error in form.email.errors %}<spanstyle="color:red;">[{{ error }}]</span>{% endfor %}</div><div>{{ form.password.label }}<br>{{ form.password(size=32) }}<br>{% for error in form.password.errors %}<spanstyle="color:red;">[{{ error }}]</span>{% endfor %}</div><div>{{ form.submit() }}</div></form>{% endblock %}2.5 CSRF 保护
Flask-WTF 默认启用 CSRF 保护。你需要在应用配置中设置一个密钥:
app.config['SECRET_KEY']='your-secret-key-here'表单中的{{ form.hidden_tag() }}会生成一个隐藏字段,包含 CSRF 令牌。
3. 数据库操作
Flask 本身不绑定数据库,但推荐使用Flask-SQLAlchemy作为 ORM 工具,它简化了数据库操作。我们还会使用Flask-Migrate来管理数据库迁移。
3.1 安装扩展
pipinstallflask-sqlalchemy flask-migrate3.2 配置数据库
在应用配置中设置数据库 URI(以 SQLite 为例):
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///app.db'app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False初始化 SQLAlchemy 和 Migrate:
fromflask_sqlalchemyimportSQLAlchemyfromflask_migrateimportMigrate db=SQLAlchemy(app)migrate=Migrate(app,db)3.3 定义模型
模型类继承自db.Model,每个类对应一张数据库表。例如,创建一个用户模型:
classUser(db.Model):id=db.Column(db.Integer,primary_key=True)username=db.Column(db.String(64),unique=True,nullable=False)email=db.Column(db.String(120),unique=True,nullable=False)password_hash=db.Column(db.String(128))def__repr__(self):returnf'<User{self.username}>'3.4 数据库迁移
使用 Flask-Migrate 可以自动化地生成数据库迁移脚本,避免手动操作。
初始化迁移仓库(只需一次):
flask db init生成迁移脚本(当模型改变时):
flask db migrate-m"create user table"应用迁移到数据库:
flask db upgrade3.5 基本 CRUD 操作
在视图函数中操作数据库。示例:注册用户
fromwerkzeug.securityimportgenerate_password_hash@app.route('/register',methods=['GET','POST'])defregister():form=RegistrationForm()ifform.validate_on_submit():# 检查用户名或邮箱是否已存在user=User.query.filter_by(username=form.username.data).first()ifuser:flash('Username already exists','danger')returnredirect(url_for('register'))# 创建新用户new_user=User(username=form.username.data,email=form.email.data,password_hash=generate_password_hash(form.password.data))db.session.add(new_user)db.session.commit()flash('Registration successful!','success')returnredirect(url_for('login'))returnrender_template('register.html',form=form)常用查询方法:
- 查询所有:
User.query.all() - 按条件查询:
User.query.filter_by(username='alice').first() - 获取主键:
User.query.get(1) - 复杂过滤:
User.query.filter(User.email.endswith('@example.com')).all() - 分页:
User.query.paginate(page=1, per_page=20)
3.6 关系映射
定义一对多关系。例如,一个用户可以有多个文章:
classUser(db.Model):# ...posts=db.relationship('Post',backref='author',lazy='dynamic')classPost(db.Model):id=db.Column(db.Integer,primary_key=True)title=db.Column(db.String(100))body=db.Column(db.Text)user_id=db.Column(db.Integer,db.ForeignKey('user.id'))db.relationship定义在 User 模型上,表示从 User 到 Post 的关系。backref='author'使得可以从 Post 对象直接访问post.author获取作者。lazy='dynamic'表示user.posts返回一个查询对象,而不是立即加载所有文章。
多对多关系需要借助关联表。例如,用户和角色:
# 关联表roles_users=db.Table('roles_users',db.Column('user_id',db.Integer,db.ForeignKey('user.id')),db.Column('role_id',db.Integer,db.ForeignKey('role.id')))classRole(db.Model):id=db.Column(db.Integer,primary_key=True)name=db.Column(db.String(50),unique=True)users=db.relationship('User',secondary=roles_users,backref='roles')4. 蓝图(Blueprint)
当应用规模变大时,将所有路由放在一个文件中会变得混乱。Flask 提供了蓝图来模块化路由和视图。
4.1 定义蓝图
创建一个auth蓝图,处理认证相关路由:
# auth.pyfromflaskimportBlueprint,render_template auth_bp=Blueprint('auth',__name__,url_prefix='/auth')@auth_bp.route('/login')deflogin():returnrender_template('auth/login.html')4.2 注册蓝图
在应用工厂中注册蓝图:
defcreate_app():app=Flask(__name__)# ...from.authimportauth_bp app.register_blueprint(auth_bp)returnapp4.3 蓝图中的 url_for
在模板或视图中,使用带蓝图名的url_for:
url_for('auth.login')# 生成 /auth/login5. 错误处理
Flask 允许自定义错误页面,提升用户体验。
5.1 使用 errorhandler 装饰器
@app.errorhandler(404)defpage_not_found(e):returnrender_template('404.html'),404@app.errorhandler(500)definternal_server_error(e):returnrender_template('500.html'),5005.2 在蓝图中自定义错误
蓝图也可以有自己的错误处理器,但作用范围仅限于该蓝图下的路由。
总结
在本篇教程中,我们学习了:
- 模板渲染:使用 Jinja2 模板引擎,包括继承、过滤器、静态文件管理。
- 表单处理:通过 Flask-WTF 定义表单类、验证数据、CSRF 保护。
- 数据库操作:使用 Flask-SQLAlchemy 定义模型、执行 CRUD、管理关系,并利用 Flask-Migrate 进行数据库版本控制。
- 蓝图:将应用模块化,便于组织和扩展。
- 错误处理:自定义错误页面,提升用户友好度。
掌握了这些内容后,你已经具备了构建完整 Web 应用的能力。在第三篇(也是最后一篇)教程中,我们将深入探讨Flask 的高级主题,包括:中间件与扩展、项目部署,并给出一个综合示例,将所学知识串联起来。
请继续关注第三篇,我们将在那里完成 Flask 学习的最后一块拼图。如果你对第二篇有任何疑问,欢迎随时提出。
