Flask web 表单、蓝本与代码模块化

本文探讨了WTForms在Flask应用中的关键作用,包括表单数据输入、验证、UI定制及CSRF防御。作者介绍了如何使用Flask-WTF创建表单、内置验证器的使用以及前端与后端交互的原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

表单

表单是实现一个web程序非常重要的部分,依我之见,表单是整个应用系统的数据输入部分,不仅在整个程序的正常运行中起着非常大的作用,还深刻影响到整个应用系统的稳定性和性能,也是系统安全比较关心的一个部分。在此书中使用的是基于WTForms拓展的Flask-WTF,这个拓展不仅能在后端清晰地声明好表单的形式,还可以便捷的实现表单的验证,结合jinja渲染系统,还可以实现其在前端的表现。我在深入阅读中发现,定义好的扩展可以在后端程序和渲染时分别设置其html标签的属性,这样一来,一方面可以结合Bootstrap之类的ui框架,通过class属性设置其外观,另一方面在后端定义可以方便的实现复用属性部分的编写,前端设置可以实现不同页面的个性化需求。

具体用法,首先需要在表单模块中从flask-form导入FlaskForm作为后续表单类的父类,然后再从wtforms中导入需要使用到的表单类型和验证器,然后定义表单即可。其中验证器一般是html自身可以实现的简单表单验证,例如长度限制,数字范围、网址邮箱电话等,也有非常实用的html似乎无法直接实现的如正则表达式,限定值等等使用功能。需要注意的是,虽然在导入时验证器是以类的形式导入的,但在使用时需要加上括号,标识其为可调用对象。一个例子:

from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField, TextAreaField
from wtforms.validators import DataRequired, Length

class MessageForm(FlaskForm):
    title = StringField(
        'title',
        validators=[DataRequired(), Length(1, 50)],	# 需要打括号 必要时传入参数
        render_kw={'placeholder': 'Message title / 标题'}
    )
    body = TextAreaField('message', validators=[DataRequired(), Length(1, 140)])
    submit = SubmitField()

对于前端显示的html代码,可以通过前面说的两种方式传入属性。第一种方式就是如上面的代码显示的,在后端定义时,在对应的条目建立时,传入一个render_kw的键值对形式的参数。另一种方法就是在jinja模板中,如下书写:

<div class="col-lg-6 mb-4">
    {{ form.title(class_='form-control') }}
</div>

需要注意的是,表单类并不作为程序运行的上下文供模板调用,需要实例化后传入模板。在模板中使用时,如果要设置html标签的class属性,需要在后面加一个下划线,与python的class关键字有所区别。那么自定义验证器输出后是否可以在前端实现?(我猜自定义的可能不行,涉及到JavaScript的部分还是需要自己写)html自身不能实现的部分前端验证是什么原理?前端使用JavaScript写的一些规则是否仍然有效?

然而,WTForms做的仅仅是服务端的验证,稍微复杂一点的验证器需还是要是通过自己在前端手写JavaScript代码实现,所以以上的的问题很多都是根本不存在的。这个拓展在jinja模板中输出了html代码后,在上传视图函数接收前就不管了,前端依然是正常的处理,JavaScript也依然可以起作用。

后端又是如何处理呢?WTForms需要将用户上传的信息先储存在一个实例化的表单对象中,然后通过调用这个对象的validate()方法验证信息。通过验证后,进行处理(计算、调用数据库等),最后给客户端重定向一个GET请求,并给予用户适当的提示。在这个过程中,FlaskForm基类会自动从上下文中获取请求中的表单信息,无需手动执行。需要注意的是,向数据库内写入数据时,需要调用字段的data属性,而不是将整个表单的字段类写入。

关于CSRF

在这个扩展中,提到了很多关于防御CSRF攻击的事情。CSRF是Cross-site request forgery,跨站伪造请求攻击。它相比于XSS攻击来说,需要更严格的条件,因此在防御上也更有难度。当用户A与访问被攻击网站B,还存在一个恶意网站C,一般来说,进行CSRF攻击需要用户已经登陆了网站B,并且没有关闭网站B的页面(session有效)。在这个条件下,用户A访问网站C,触发C以用户A的身份请求对网站B进行操作,从而实现攻击,而请求网站B这个操作是A不知情的。这个攻击过程,需要注意,网站C并没有窃取任何用户A的信息,只是通过用户A的浏览器,在A不知情的情况下向B发送了攻击请求。

防御手段之一就是检查请求头的reference,如果不是来自于站内或者是指定的URL,就拒绝请求。然而这是不靠谱的,请求头很容易被篡改。在WTForms中,是使用csrf-token来防御的,在表单中添加一段随机的、加密的、有时效的token字符串字段,这也是一个非常常用的方法。在学习时看见有评论在提问,既然攻击者能伪造用户身份,你一条字符串怎么就不能伪造?首先,字符串不仅仅是随机的,更是被加密的,需要有服务端密钥才能正确伪造;其次,攻击者是通过浏览器发送请求,伪造用户身份,身份数据保存在浏览器cookies与session中,攻击对象是客户端,即使用户现在打开了一个有csrf-token的页面,他没办法从另一个页面中获取这个数据。还有一种方式是验证码,这种方式不仅可以防御CSRF攻击,还可以减少网络爬虫对服务端的骚扰。

蓝本与模块化

蓝本

当我第一次在flask中看到蓝本这个名词的的时候,我内心里马上浮现出一种“宏伟蓝图”的形象,以为这是一个了不得部分,他是不是可以提供各种有力的扩展,帮我搞定各种难题?随着我深入的学习,我发现我想错了。蓝本只是将原来app.py中的视图函数单独拿出来,根据功能或者域名,将视图函数们模块化与主程序分离。只不过视图函数是整个web程序中比较重要的一部分,牵涉到环境、路由和资源加载等一系列问题,值得单独拿出来研究。

蓝本的的引入,就不得不深入理解一下前面提到的关于url、端点和视图函数之间的关系。这三层之间,结合前面的路由表,可以看出,其实是一个两层映射的关系。url规则和端点的映射,端点和视图函数的映射。多个url可以对应到一个端点上,例如/ /home /index三条url都可以绑定到首页的端点上。那为什么不直接对应到视图函数的函数名名上呢,需要中间进行一层端点的映射?蓝图的出现可以解决这个问题,通过端点的引入,可以为每个蓝图内部创建本地的命名空间。比如一个CMS系统,用户访问的网址首页是www.websit.com/index,后台管理的网址首页是admin.websit.com/index,在url规则看来,他们都是/index,但是他们的前缀有所不同(通过注册蓝图时的subdomain参数实现),为了能对应到不同蓝本的同名视图函数上,需要使用带前缀的端点,类似于类的调用:admin.index pub.index

模块化

之前在数据库部分讨论过,flask的app实例需要创建后才能继续加载其他的拓展,这给不同拓展的相关代码的模块化处理带来了不便。作者当时的解决方法是先将app实例化,拓展对象也实例化完成后,通过函数传参的方式传递到拓展代码中,实现模块化。然而这种方法不仅代码冗余,逻辑上也不容易理解,虽然实现了模块化,但单独的拓展模块代码的代码可读性非常低(拓展和app在代码中只是参数名,没有上下文供理解)

事实上有更优的解决方案,flask及其拓展开发者们早就考虑到了这个问题,大部分flask相关拓展都可以在没有app实例的情况下先行定义,然后再在创建程序实例时使用object.init_app(app)方法将拓展初始化。这种做法不仅实现了代码层面的模块化,更是架构上的模块化,flask实例不用写死在程序中,可以在主程序的__init__.py文件中组织工厂函数(面向对象编程中创建对象实例的函数)

回到上面关于蓝本的讨论,蓝本使得冗长的视图函数代码可以模块化组织。开发者可以通过业务的实际需求将所有的函数进一步模块化,一套程序多个蓝本,将不同的程序功能分开,便于开发、维护。除了数据库模型和蓝本外,表单模型,模板渲染模型(本项目为Bootstrap)也可以模块化处理。

<think>好的,我现在需要处理用户关于如何使用Flask实现登录功能的查询。首先,用户明确提到了站内引用,所以我应该先检查提供的引用内容,看是否有相关信息可用。 引用[1]提到使用Flask进行基于令牌的认证,并指向一个仓库:flask-jwt-auth。这可能涉及到JWT(JSON Web Tokens)的实现,适合API或单页应用的认证。引用[2]讨论了Flask的工厂模式和蓝图,特别是处理认证相关的蓝图。这提示用户可能需要结构化的项目布局,比如使用蓝本来组织认证路由和视图。引用[3]是翻译自一个外部教程,可能包含更多Flask基础内容,但链接不可访问,可能需要依赖现有信息。 用户需要的是登录功能的示例代码和教程,所以核心点应包括路由处理、表单验证、会话管理、密哈希等。考虑到引用[1]和[2]的内容,我应该结合蓝图和JWT或传统的会话认证来回答。但用户的问题更偏向于传统的登录功能,可能涉及表单提交和会话管理,而引用中的JWT可能更适合API场景,但也可以作为参考。 接下来需要确保回答结构清晰,分步骤说明。步骤可能包括:安装依赖、初始化应用、配置数据库(如使用Flask-SQLAlchemy)、创建用户模型、设置路由(登录、注册、注销)、处理表单数据、使用Flask-Login扩展管理用户会话、密哈希处理(如使用Werkzeug)等。 需要检查引用中是否有具体的代码示例。引用[1]提到了使用蓝图,引用[2]提到认证蓝图的创建,但没有详细代码。可能需要综合外部知识,但确保符合用户提供的引用框架。比如,使用工厂模式创建应用实例,使用认证蓝图处理登录路由,使用JWT或传统的cookie-based会话。 同时,用户要求生成相关问题,可能需要围绕Flask登录功能的其他方面,比如密重置、会话安全、第三方登录等。但需要根据引用内容来生成,可能引用中没有这些内容,所以问题可能需要更基础,比如使用Flask-Login的步骤,如何保护路由,如何处理用户认证等。 最后,确保回答中正确引用给定的站内引用,比如在提到蓝图或JWT时引用对应的标记。注意引用标识的位置要自然,例如在讨论蓝图结构时引用[^2],在提到基于令牌的认证时引用[^1]。 总结步骤: 1. 使用Flask应用工厂和蓝图组织代码(引用[2])。 2. 创建认证蓝图,处理登录、注册等路由。 3. 使用Flask-Login管理用户会话。 4. 实现密哈希存储(如使用Werkzeug)。 5. 处理表单提交和验证。 6. 示例代码展示关键部分,如用户模型、视图函数。 7. 引用相关站内内容作为参考。 确保回答符合用户要求的中文,使用正确的LaTeX格式(虽然问题中可能较少涉及数学公式,但需遵循规则),并生成相关问题。</think>以下是如何使用Flask实现登录功能的逐步说明及示例代码,结合了蓝图架构和用户认证的最佳实践: ### 1. 项目结构搭建 使用**Flask应用工厂模式**分离认证功能模块[^2]: ``` myapp/ ├── auth/ │ ├── __init__.py │ ├── forms.py # 登录/注册表单 │ └── routes.py # 认证相关路由 ├── templates/ │ └── auth/ │ ├── login.html └── __init__.py # 应用工厂 ``` ### 2. 核心依赖安装 ```bash pip install flask flask-sqlalchemy flask-login werkzeug ``` ### 3. 用户模型定义 ```python # models.py from werkzeug.security import generate_password_hash, check_password_hash from flask_login import UserMixin class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True) password_hash = db.Column(db.String(128)) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) ``` ### 4. 登录路由实现 ```python # auth/routes.py from flask import Blueprint, render_template, redirect, url_for, flash from flask_login import login_user from .forms import LoginForm bp = Blueprint('auth', __name__) @bp.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user and user.check_password(form.password.data): login_user(user, remember=form.remember_me.data) return redirect(url_for('main.index')) flash('无效的用户名或密') return render_template('auth/login.html', form=form) ``` ### 5. 登录表单设计 ```python # auth/forms.py from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField from wtforms.validators import DataRequired class LoginForm(FlaskForm): username = StringField('用户名', validators=[DataRequired()]) password = PasswordField('密', validators=[DataRequired()]) remember_me = BooleanField('记住我') ``` ### 6. 模板示例 (login.html) ```html <form method="POST"> {{ form.hidden_tag() }} <div> {{ form.username.label }} {{ form.username() }} </div> <div> {{ form.password.label }} {{ form.password() }} </div> <div> {{ form.remember_me() }} {{ form.remember_me.label }} </div> <button type="submit">登录</button> </form> ``` ### 7. 初始化配置 ```python # __init__.py from flask import Flask from flask_login import LoginManager def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = 'your-secret-key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' # 初始化扩展 db.init_app(app) login_manager = LoginManager(app) # 注册蓝图 from auth.routes import bp as auth_bp app.register_blueprint(auth_bp, url_prefix='/auth') return app ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值