百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

三、flask博客项目实战-之表单

myzbx 2025-03-05 19:30 36 浏览

一、概述

这是Flask Mega-Tutorial系列的第三部分,我将告诉你如何使用Web表单。

在第二章中我为应用主页创建了一个简单的模板,并使用诸如用户和用户动态的模拟对象。在本章中,我将解决这个应用程序中仍然存在的众多遗漏之一,那就是如何通过Web表单接受用户的输入。

Web表单是所有Web应用程序中最基本的组成部分之一。 我将使用表单来为用户发表动态和登录认证提供途径。

在继续阅读本章之前,确保你的microblog应用程序状态和上一章完结时一致,并且运行时不会报任何错误。

二、Flask-WTF简介和安装

在Flask中,处理应用程序中的Web表单,将使用到Flask-WTF扩展库,它是Flask和WTForms的简单集成,主要功能有:使用CSRF(Cross-site request forgery,译作 跨站请求伪造)令牌保护表单、文件上传、支持reCAPTCHA(译作 反全自动区分计算机和人类的图灵测试,简单点就是:验证码)。扩展是Flask生态系统中非常重要的一部分。今后还会需要更多的扩展。

我将使用Flask-WTF插件来处理本应用中的Web表单,它对WTForms进行了浅层次的封装以便和Flask完美结合。这是本应用引入的第一个Flask插件,但绝不是最后一个。插件是Flask生态中的举足轻重的一部分,Flask故意设计为只包含核心功能以保持代码的整洁,并暴露接口以对接解决不同问题的插件。

Flask插件都是常规的Python三方包,可以使用pip安装。 那就继续在你的虚拟环境中安装Flask-WTF吧:
进入虚拟环境中,安装Flask-WTF:

(venv) [root@python blog]# pip3 install flask-wtf

将附带安装WTForms,因为它是Flask-WTF的一部分。

2.1配置 configuration

目前为止,这个应用程序足够简单,无需担心它的配置。Flask(以及Flask扩展)在如何执行操作方面提供了很多自由,并需要做一些决定,并将这些决定作为一个配置变量列表传递给框架。

应用程序 有多种格式可指定配置选项。最基本的方案:在app.config这个字典中,将定义的变量作为键。形如:

app = Flask(__name__)app.config['SECRET_KEY'] = 'I am a secret, you can't guess.'#需要的话,可继续添加更多的变量

尽管上述语法可为Flask成功创建配置选项,但根据 关注点分离原则(Separation of concerns, SoC),所以不要将配置放在创建应用程序的相同位置,而是:将配置保存在单独的.py文件中,并使用类存储配置变量,将该.py文件放在项目顶级目录下。
/microblog/config.py:密钥配置

import os
class Config:
				SECRET_KEY = os.environ.get('SECRET_KEY') or 'you will never guess'

SECRET_KEY这个配置变量,将会被Flask及其扩展使用其值作为加密秘钥,用于生产签名或令牌。而Flask-WTF使用它来保护Web表单来免受CSFR攻击。

密钥的值 是具有两个术语的表达式,由or运算符连接。第一个术语是查找环境变量的值;第二个术语是一个硬编码的字符串。当然这个安全性还是很低的。当将应用程序部署在生产服务器上时,得设置一个安全级别高的。

其中os.environ是获取本机系统的各种信息(如环境变量等,你打印出来就明白了,哈哈),它是一个字典。我觉得os.environ.get('SECRET_KEY')在开发环境中并没有用,是None,不知部署后是什么。
有了上述这个配置文件,接下来得让Flask应用程序读取并应用它。在创建Flask应用程序实例后,就用app.config.from_object()方法完成:
app/__init__.py:Flask配置

from flask import Flask
from config import Config                #从config模块导入Config类


app = Flask(__name__)
app.config.from_object(Config)

from app import routes

查看刚才配置的密钥是什么:

(venv) [root@python blog]# python3
Python 3.9.0 (default, Oct 16 2020, 10:57:11)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
 >>> 

2.2用户登录表单

Flask-WTF扩展使用Python类来表示Web表单。表单类只是将表单的字段定义为类变量。

再次根据SoC(关注点分离原则),新建一个forms.py模块来存放Web表单类。在此,定义一个用户登录表单,要求用户输入用户名、密码,还包含“Remember Me”复选框、提交按钮。
app/forms.py:用户登录表单

from flask_wtf import FlaskForm        #从flask_wtf包中导入FlaskForm类
from wtforms import StringField,PasswordField,BooleanField,SubmitField       #导入这些类
from wtforms.validators import DataRequired 

class LoginForm(FlaskForm):    
					username = StringField('Username', validators=[DataRequired()])    
					password = PasswordField('Password', validators=[DataRequired()])    
					remember_me = BooleanField('Remember Me')
					submit = SubmitField('Sign In')

在Flask生态下,Flask扩展一般都使用flask_\这样的命名约定作为在模块中顶级导入的符号。在这个情况下,Flask-WTF的所有符号都在flask_wtf下,这也是FlaskForm基类在app/forms.py顶部导入的地方。

from wtforms import StringField,PasswordField,BooleanField,SubmitField

这条语句表示:这个用户登录表单的字段类型的4个类是直接从WTForms包导入的,因为Flask-WTF扩展是不提供自定义(字段类型?)版本。对于每个字段,将在LoginForm类中将对象创建为类变量。每个字段都有一个描述或标签作为第一个参数。

在某些字段中看到的可选参数validators将验证行为附加到字段中,如用户名、密码肯定是需要进行验证的。DataRequired验证器 只是简单地检查该字段不会提交为空。当然还有其他的验证器可用。

2.3用户登录-表单模板

有了上一步的登录表单,接下来得将表单添加到HTML模板中,让其在网页上呈现。
LoginForm类中定义的字段知道如何将自己渲染为HTML。
app/templates/login.html:用户登录表单模板

{% extends "base.html" %}
{% block content %}    
  

Sign In

{{ form.hidden_tag() }}

{{ form.username.label }}
{{ form.username(size=32) }}

{{ form.password.label }}
{{ form.password(size=32) }}

{{ form.remember_me() }} {{ form.remember_me.label }}

{{ form.submit() }}

{% endblock %}

这个用户登录表单模板使用了extends继承语句继承base.html模板,以确保一致的布局,即基础模板包含了所有页面的顶部导航栏。

在此的用户登录表单模板期望从LoginForm类实例化的表单对象作为参数给出,这个参数将由登录视图函数(目前还未编写)发送。

以下将讲述HTML知识,上述这段HTML代码中:

标签用作Web表单的容器。
其中action属性表示用于告知浏览器当用户在表单中输入信息提交时应使用的URL。该属性设置为空字符串时,表单将提交到当前位于地址栏的URL,即在页面上呈现表单的URL。
method属性用于指定在将表单提交到服务器时应使用的HTTP请求方法。默认情况下,是通过GET请求发送它。但几乎在所有情况下,使用POST请求会获得更好的用户体验,因为此类请求可在请求正文中提交表单数据,GET请求将表单字段添加到URL,会让浏览器地址栏变得混乱。
novalidate属性用于告知浏览器不对此表单中的字段运用验证,这有效地将此任务留给服务器中运行的Flask应用程序。当然,使用novalidate完全是可选的,但对于第一种形式,设置它是很重要的,因为这将允许在本章后面的测试服务器端验证。
form.hidden_tag()这个模板参数 生成一个隐藏字段,其中包括用来防止CSRF攻击的令牌。要使表单受保护,需要做的是包含此隐藏字段,并在Flask配置中定义的SECRET_KEY变量。
写过HTML Web表单的同学可能会发现这个模板中没有HTML字段,这是因为表单对象中的字段知道如何将自己呈现(渲染)为HTML,需要做的就是{ form..label }需要的字段标签、{ form.() }需要的字段。对于需要其他HTML属性的字段,可将这些属性作为参数传递。此模板中的用户名、密码字段将size作为参数添加到这个HTML标签作为属性。这还是可将CSS类、或ID附加到表单字段的方法。

2.4用户登录-表单视图

在编写完上一步的用户登录表单模板后,想要在浏览器中看到此表单的最后一步是:在应用程序中编写一个它的视图函数,用于渲染该模板。

因此,编写一个映射到/login URL的视图函数login(),并将其传递给模板进行渲染。在routes模块中增加代码:

app/routes.py:用户登录视图函数

from flask import render_template
from app import app
from app.forms import LoginForm

#...
 
@app.route('/login')
def login():
				form = LoginForm()          #表单实例化对象
        return render_template('login.html', title='Sign In', form=form)

上述视图函数很简单,从forms.py模块中导入LoginForm类,然后实例化该类,最后将其发送到模板。form=form,return中将form实例对象赋值给form变量,这将获得表单字段所需的全部内容。

为了便于访问登录表单,在基础模板中改进,即在导航栏中包含指向它的链接:
app/templates/base.html:导航栏中增加登录链接

Microblog: Home Login

此刻,运行应用程序就可浏览器中查看该表单了。效果:图略

2.5接收表单数据

尝试点击上述“Sign In”提交按钮,浏览器将出现405错误“Method Not Allowed”。图略

在上一步中,用户登录的视图函数执行了一半的工作,即可在网页上显示表单。但它没有处理用户提交的数据的逻辑。这是Flask-WTF让这项逻辑处理变得非常简单的优势。更新用户登录视图函数代码,它接受、验证用户提交的数据:
app/routes.py:接收登录凭据

from flask import render_template,flash,redirect 
@app.route('/login',methods=['GET','POST'])
def login():
				form = LoginForm()
				if form.validate_on_submit():
          				flash('Login requested for user {},remember_me={}'.format(form.username.data,form.remember_me.data))
									return redirect('/index')
				return render_template('login.html',title='Sign In',form=form)

在@app.routes()装饰器中参数methods作用是:告诉Flask这个视图函数接受GET和POST请求方法,覆盖默认值(即只接受GET请求)。HTTP协议中,GET请求是将信息返回给客户端(如浏览器)的请求,到目前为止,该应用程序中的所有请求都属于这种类型;POST 请求通常在浏览器上服务器提交表单数据时使用。上述出现“Method Not Allowed”,是因为浏览器尝试发送POST请求,而应用程序没有配置去接受它。

form.validate_on_submit()方法完成所有表单处理工作。当浏览器发送GET接收带有表单的网页请求时,此方法将返回False,此时函数会跳过if语句并直接在函数的最后一行呈现模板。
当用户在浏览器按下提交按钮时,浏览器发送POST请求,form.validate_on_submit()将收集所有数据,运行附加到字段的所有验证器,如果一切正常,它将返回True,表明数据有效且可由应用程序处理。但如果至少有一个字段未通过验证,则函数就会返回False,接着就像上述GET请求那样。
当form.validate_on_submit()返回True,这个登录视图函数将调用两个函数,分别是flash()、redirect(),均从flask包导入的。
flash() 用于向用户显示消息,如让用户知道某些操作是否成功。目前为止,将使用其机制作为临时解决方案,因为暂无用户登录
真实所需的基础结构,此时只是显示一条消息用于确认应用程序已收到凭据。
redirect()用于指示客户端(浏览器)自动导航到作为参数给出的其他页面(如上述代码中的/index页面,即重定向到应用程序的/index页面)。

当调用flash()函数时,Flask会存储该消息,但闪烁的消息不会神奇地出现在Web页面中。应用程序的模板需要以适用于站点布局的方式呈现/渲染这些闪烁的消息。因此,将这些消息添加到基础模板中,以便所有模板都继承此功能。更新基础模板:
app/templates/base.html:基础模板中的闪烁消息

    
  
  {% if title %}
{{ title }} - Microblog
	{% else %}
    Welcome to Microblog
  {% endif %}
    
   
    	
Microblog: Home Login

{% with messages = get_flashed_messages() %} {% if messages %}
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
{% endif %} {% endwith %} {% block content %} {% endblock %}

上述代码中,使用with结构将调用get_flashed_messages()的结果分配给变量messages,都在模板的上下文。这个get_flashed_messages()函数来自Flask,并返回flash()之前已注册的所有消息的列表。接着if语句判断messages是否具有某些内容,在这种情况下,一个ul标签被渲染成每个消息作为一个li标签列表项。而这种渲染风格看起来不太好,但Web应用程序样式化的主题将在稍后出现。

这些闪烁的消息的一个有趣属性是:一旦通过get_flashed_messages()请求它们,它们就会从列表中删除,因此它们在flash()调用后只出现一次。

运行程序,再次测试表单是如何工作的。确保将用户名或密码字段为空来提交表单,以查看DataRequired验证器如何暂停提交过程。

用户名或密码为空时提交表单,网页没反应。都不为空时,随意输入。
图略

点击Sign in按钮后,倒是出现了一条消息:Login requested for user 123456@qq.com,remember_me=Flase
图略

2.6增强字段验证

附加到表单字段的验证器可防止无效数据接受到应用程序中。应用程序处理无效表单输入的方式是重新显示表单,让用户进行必要的更正。

当提交无效数据时,却没有明显提示用户提交的数据有问题,只是重新返回表单,这将影响用户体检。因此,现在的任务是:通过在验证失败的每个字段傍边增加有意义的错误提示来改善用户体验。

实际上,表单验证器已经生成了这些描述性错误消息,因此,缺少的是在模板中用于渲染/呈现它们的一些额外逻辑。在用户登录模板的用户名、密码字段中添加字段验证消息:更新代码
app/templates/login.html:提示字段验证错误消息

{% extends "base.html" %}
{% block content %}
  

Sign In

{{ form.hidden_tag() }}

{{ form.username.label }}
{{ form.username(size=32) }}
{% for error in form.username.errors %} [{{ error }}] {% endfor %}

{{ form.password.label }}
{{ form.password(size=32) }}
{% for error in form.password.errors %} [{{ error }}] {% endfor %}

{{ form.remember_me() }} {{ form.remember_me.label }}

{{ form.submit() }}

{% endblock %}

上述代码中,只是在用户名、密码字段之后添加for循环,以红色字体消息渲染错误消息。一般规则下,任何附加验证器的字段都会通过form..errors添加错误消息。这将是一个列表,因为字段可以附加多个验证器,并且多个可能提供错误消息提示给用户。

如果尝试提交空用户名或密码的表单,将看到红色错误提示,效果:图略

2.7生成URL

用户登录表单现在比较完整了,下面将学习在模板包含链接和重定向的方法。 例:基础模板中的当前导航栏

Microblog: Home Login

登录视图函数还定义了传递给redirect()函数的链接:

@app.route('/login', methods=['GET', 'POST'])
def login():
				form = LoginForm()
				if form.validate_on_submit():
        # ...
        return redirect('/index')
					# ...

直接在模板、源文件中编写链接的一个问题是:如果将来某天要重新组织链接,将不得不修改整个应用程序的这个链接,搜索、替换。

为更好地控制这些链接,Flask提供了一个名为 url_for()函数,它使用URL的内部映射到视图函数来生成URL。例:url_for('login')返回/login;url_for('index')返回/index。url_for()中的参数就是端点名称,也就是视图函数的名字。

使用函数名称而不是URL的优点:URL比视图函数名称更可能发生变化;某些URL很可能包含动态组件,手动生成这URL需要连接多个元素,这极易出错,而url_for()能生成这些复杂的URL。

因此,今后每次应用程序要生成URL时,都使用url_for()。
更新基础模板中的代码:
app/templates/base.html:使用url_for()进行链接

...    
  
Microblog: Home Login
...

更新login()视图函数中的代码:
app/routes.py:对链接使用url_for()函数

from flask import render_template, flash, redirect, url_for 

# ...
  
@app.route('/login', methods=['GET', 'POST'])

def login():   
				form = LoginForm()
				if form.validate_on_submit():
        # ... 
        return redirect(url_for('index'))
				# ...

目前为止,项目结构:

microblog/
      venv/
      app/
      templates/
      					base.html
                index.html
                login.html
      __init__.py
      forms.py 
      routes.py
microblog.py

相关推荐

零基础入门AI智能体:详细了解什么是变量类型、JSON结构、Markdown格式

当品牌跳出固有框架,以跨界联动、场景创新叩击年轻群体的兴趣点,一场关于如何在迭代中保持鲜活的探索正在展开,既藏着破圈的巧思,也映照着与新一代对话的密码。在创建AI智能体时,我们会调用插件或大模型,而在...

C# 13模式匹配:递归模式与属性模式在真实代码中的性能影响分析

C#13对模式匹配的增强让复杂数据处理代码更简洁,但递归模式与属性模式的性能差异一直是开发者关注的焦点。在实际项目中,选择合适的模式不仅影响代码可读性,还可能导致执行效率的显著差异。本文结合真实测试...

零基础快速入门 VBA 系列 6 —— 常用对象(工作簿、工作表和区域)

上一节,我介绍了VBA内置函数以及如何自动打字和自动保存文件。这一节,我们来了解一下Excel常用对象。Excel常用对象Excel有很多对象,其中最常用也最重要的包括以下3个:1.Workbo...

不同生命数字的生肖龙!准到雷普!

属龙的人总在自信爆棚和自讨苦吃之间反复横跳?看完这届龙宝宝的日常我悟了。属龙的人好像天生自带矛盾体:领导力超强可人缘时好时坏,工作雷厉风行却总在爱情里翻车。关键年份的龙性格差异更大——76年龙靠谱但不...

仓颉编程语言基础-面向对象编程-属性(Properties)

属性是仓颉颉中一种强大的机制,它允许你封装对类(或接口interface、结构体struct、枚举enum、扩展extend)内部状态的访问。它看起来像一个普通的成员变量(字段),但在其背后,它通过...

Python中class对象/属性/方法/继承/多态/魔法方法详解

一、基础入门:认识类和对象1.类和对象的概念在Python中,类(class)是一种抽象的概念,用于定义对象的属性和行为,而对象(也称为实例)则是类的具体表现。比如,“汽车”可以是一个类,它有...

VBA基础入门:搞清楚对象、属性和方法就成功了一半

如果你刚接触VBA(VisualBasicforApplications),可能会被“对象”“属性”“方法”这些术语搞得一头雾水。但事实上,这三个概念是VBA编程的基石。只要理解它们之间的关系,...

P.O类型文推荐|年度编推合集(一百九十五篇)

点击左上方关注获取更多精彩推文目录2019年度编推35篇(1V1)《悖论》作者:流苏.txt(1V1)《桂花蒸》作者:大姑娘浪.txt(1V1)《豪门浪女》作者:奚行.txt...

Python参数传递内存大揭秘:可变对象 vs 不可变对象

90%的Python程序员不知道,函数参数传递中可变对象的修改竟会导致意想不到的副作用!一、参数传递的本质:对象引用传递在Python中,所有参数传递都是对象引用的传递。这意味着函数调用时传递的不是对...

JS 开发者必看!TC39 2025 最新动向,这些新语法要火?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。TC39第...

2025 年值得尝试的 5 个被低估的 JavaScript 库

这些JavaScript库可能不会在社交媒体或HackerNews上流行起来,但它们会显著提高您的工作效率和代码质量。JavaScript不再只是框架。虽然React、Vue和Sv...

Python自动化办公应用学习笔记30—函数的参数

一、函数的参数1.形参:o定义:在函数定义时,声明在函数名后面括号中的变量。o作用:它们是函数内部的占位符变量,用于接收函数被调用时传入的实际值。o生命周期:在函数被调用时创建,在函数执...

16种MBTI人格全解析|测完我沉默了三秒:原来我是这样的人?

MBTI性格测试火了这么久,你还不知道自己是哪一型?有人拿它当社交话题,有人拿它分析老板性格,还有人干脆当成择偶参考表。不废话,今天我一次性给你整理全部16种MBTI人格类型!看完你不仅能知道自己是谁...

JS基础与高级应用: 性能优化

在现代Web开发中,性能优化已成为前端工程师必须掌握的核心技能之一。本文从URL输入到页面加载完成的全过程出发,深入分析了HTTP协议的演进、域名解析、代码层面性能优化以及编译与渲染的最佳实践。通过节...

爱思创CSP-J/S初赛模拟赛线上开赛!助力冲入2024年CSP-J/S复赛!

CSP-J/S组初赛模拟赛爱思创,专注信奥教育19年,2022年CSP-J/S组赛事指定考点,特邀NOIP教练,开启全真实CSP-J/S组线上初赛模拟大赛!一、比赛对象:2024年备考CSP-J/S初...