注册
渲染注册模板
虽然通过命令能添加用户,但是网站上线运行后,必须要有界面能让普通用户注册。在讲解注册功能实现之前,先来说明一下我们提前准备好的模板。读者在获取到本项目后,可以在 pythonbbs 项目根目录下看到 awebsite 文件夹,这个文件夹中的模板是笔者为方便读者学习提前准备的,所有模板都是纯静态的。首先执行以下两步操作。
-
将 pythonbbs/awebsite/static 下的所有文件和文件夹全部复制到 pythonbbs/static 中。
-
将 pythonbbs/awebsite/templates 下的所有文件和文件夹全部复制到 pythonbbs/templates 中。
在后续的章节中,会指导读者修改其中的部分代码。
以上步骤完成后,读者可以看到在 pythonbbs/templates/front 下有一个 base.html 文件,代码如下。

base.html 文件是所有前台页面的父模板,在 <head></head> 标签中加载了以下文件。
-
jquery.min.js: 3.6.0 版本的 jQuery 文件。jQuery 文件可以快速寻找元素,发送 AJAX 请求。
-
bootstrap.min.css: 4.6.0 版本的 Bootstrap 样式文件。Bootstrap 提供了丰富的样式,可以快速构建网页界面。
-
bootstrap.min.js: 4.6.0 版本的 Bootstrap JavaScript 文件。Bootstrap 中的一些组件运行需要通过 bootstrap.min.js 来实现。
-
base.css:我们自己编写的样式文件,用于修改父模板的样式。
在 head 标签中还定义了两个 block,分别为 title、head。在 body 标签中定义了导航条,这样所有子模板不用重复写导航条代码即可拥有导航条。然后在 main-container 中定义了一个 body 的 block,子模板页面的编写就是在这个 block 中实现。
接下来再看 templates/front/register.html 文件,此文件用于注册页面,代码如下。

上述代码中,先是让 register.html 继承自 front/base.html,然后分别实现了 title、head 和 body 这 3 个 block。title 用来设置页面标题,head 中加载了一个自定义的 sign.css 文件,用来美化页面。body 这个 block 中的代码,除了添加一个 h1 标签外,还添加了输入框的表单,表单中有 5 个输入框和 1 个提交按钮。输入框分别用来收集邮箱、邮箱验证码、用户名、密码、确认密码的信息。我们先来渲染模板,读者看到注册页面后会有更直观的感受。回到 blueprints/user.py 中,添加以下代码。
from flask import Blueprint, render_template
bp = Blueprint("user", __name__, url_prefix="/user")
@bp.route("/register")
def register():
return render_template("front/register.html")
上述代码中,实现了 register 视图,用来渲染 front/register.html 模板。启动项目,在浏览器中访问 http://127.0.0.1:5000/user/register ,即可看到如图 9-20 所示的注册页面。

注册逻辑是,用户输入邮箱后单击 “发送验证码” 按钮,服务器就会给这个邮箱发送一个验证码,用户收到邮箱验证码后填入输入框,并完成其他信息的输入,最后单击 “立即注册” 按钮实现注册。
使用Flask-Mail发送邮箱验证码
注册功能的第一步,就是给用户输入的邮箱发送验证码,下面讲解在 Flask 中发送邮件。在 Flask 中发送邮件非常简单,总共分为 3 步,第 1 步安装 Flask-Mail,第 2 步配置邮箱参数,第 3 步发送邮件。
-
安装Flask-Mail
首先安装 Flask-Mail,打开 PyCharm 的 Terminal,然后输入并执行以下命令即可完成安装。
$ pip install flask-mail
Flask-Mail 插件的详细使用说明,可参考其官方文档 https://pythonhosted.org/Flask-Mail/ 。
-
配置邮箱参数
想要发送邮件,必须要有一个邮箱服务器。在本书中,为了方便读者学习,我们用个人版网易邮箱来讲解邮箱的配置和使用,读者也可以选择其他公司的邮箱服务,如 QQ 邮箱、新浪邮箱、Gmail 邮箱等,使用方式都大同小异。有的公司会搭建自己的邮箱服务器,有的公司会向第三方提供商购买邮箱服务,如网易企业邮箱、腾讯企业邮箱、阿里邮箱企业版等,这些服务商都有非常详细的接入教程,遇到问题还可以咨询客服。 使用 Flask-Mail 发送邮件需要使用 SMTP 协议(simple mail transfer protocol)。首先在个人版网易邮箱中开启 SMTP 服务,登录个人版网易邮箱,单击顶部的 “设置” 按钮,然后在弹出的菜单中选择 “POP3/SMTP/IMAP”,如图 9-21 所示。
Figure 2. 图9-21 个人网易邮箱设置按钮进入 SMTP 设置界面后,找到 “IMAP/SMTP服务”,然后单击 “开启” 超链接,如图 9-22 所示。
Figure 3. 图9-22 开启SMTP界面单击 “开启” 超链接后会弹出一个 “账号安全提示” 对话框,因为邮箱只是用于我们自己的项目,只要我们的代码不被公开,还是非常安全的,所以直接单击 “继续开启” 按钮即可,如图 9-23 所示。
Figure 4. 图9-23 账号安全提示单击 “继续开启” 按钮后,会弹出 “账号安全验证” 对话框,需要用手机发送短信验证码来验证是否是本人操作,按照提示信息进行操作即可,如图 9-24 所示。
图 9-24 中显示的二维码,可以使用网易邮箱大师 App 扫描并发送短信,或者单击 “手动发送短信”,会提示你如何手动发送短信。在发送完短信验证码后,单击 “我已发送” 按钮会弹出 “开启IMAP/SMTP” 对话框,显示生成的授权密码,因为授权密码只会显示一次,所以读者要复制后保存下来,如图 9-25 所示。
Figure 5. 图9-24 发送验证码开启邮箱Figure 6. 图9-25 显示授权密码开启个人邮箱的 SMTP 服务后,再回到 pythonbbs 项目中,打开 config.py 文件,在 DevelopmentConfig 中添加如下配置。
class DevelopmentConfig(BaseConfig): ... # 其他配置项 # 邮箱配置 MAIL_SERVER = "smtp.163.com" MAIL_USE_SSL = True MAIL_PORT = 465 MAIL_USERNAME = "邮箱账号" MAIL_PASSWORD = "开启SMTP服务时生成的授权码" MAIL_DEFAULT_SENDER = "邮箱账号"
上述代码中关于邮箱配置参数的说明如下。
-
MAIL_SERVER:邮箱服务器,如网易是smtp.163.com、QQ邮箱是smtp.qq.com、新浪邮箱是smtp.sina.com。
-
MAIL_USE_SSL:是否加密传输。设置MAIL_USE_SSL=True或MAIL_USE_TLS=True都可以实现加密传输,但是MAIL_USE_SSL用的是SSL/TLS协议,而MAIL_USE_TLS用的是STARTTLS协议,具体选择哪个,要根据邮箱服务商支持的协议来配置。网易邮箱不支持STARTTLS,因此使用MAIL_USE_SSL=True来配置加密。另外,如果配置MAIL_USE_SSL=True,那么MAIL_PORT应该设置为465;如果配置MAIL_USE_TLS=True,那么MAIL_PORT应该设置为587。
-
MAIL_USERNAME:发送邮件所用的用户名,设置成邮箱账号即可。
-
MAIL_PASSWORD:在开启邮箱 SMTP 服务时自动生成的授权密码。
-
MAIL_DEFAULT_SENDER:默认发送者,填邮箱账号即可。
执行以上操作后,即完成了邮箱的配置。
-
-
发送邮件
发送邮件要先创建一个 Flask-Mail 对象,其使用方式与 Flask-SQLAlchemy 类似。先在 exts.py 中创建一个 mail 变量,代码如下。
from flask_sqlalchemy import SQLAlchemy from flask_mail import Mail db = SQLAlchemy() mail = Mail()
下面再回到 app.py 中,从 exts.py 中导入 mail 变量,并进行初始化,代码如下。
from exts import db, mail # 初始化数据库 db.init_app(app) # 初始化邮件 mail.init_app(app)
以上代码完成了 Flask-Mail 对象的初始化,后续就可以使用 mail 变量发送邮件了。接着在 blueprints/user.py 中输入以下代码。
... from flask_mail import Message from exts import mail ... @bp.route("/mail/captcha") def mail_captcha(): # 创建邮件消息 message = Message(subject="我是邮件主题", recipients=['目标邮箱地址'], body='我是邮件内容') # 发送邮件 mail.send(message) return "success"
上述代码中,我们首先创建了一个 mail_captcha 视图函数,然后创建了一个对象 flask_mail.Message, Message 类传递的参数如下。
-
subject:邮件主题。
-
recipients:收件方,可以指定多个邮箱地址。
-
body:邮件内容。
创建 Message 对象后,再调用 mail.send 方法就可以把邮件发送出去了。
现在已经能正常发送邮件了,接下来再把验证码的逻辑加进去,验证码用四位随机的数字,需要用到 Python 中的 random 模块。由于接收验证码的邮箱地址是用户填写的,并不固定,所以我们通过查询字符串的形式接收目标用户的邮箱地址。在 mail_captcha 视图函数中,将代码修改如下。
from flask import Blueprint, render_template, request from flask_mail import Message from exts import mail import random bp = Blueprint("user", __name__, url_prefix="/user") @bp.route("/mail/captcha") def mail_captcha(): email = request.args.get("mail") # 获取邮箱地址 digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] captcha = "".join(random.sample(digits, 4)) # 随机生成四位验证码 body = f"【知了Python论坛】您的注册验证码是:{captcha},请勿告诉别人!" # 创建邮件消息 message = Message(subject="我是邮件主题", recipients=[email], body=body) # 发送邮件 mail.send(message) return "success"
上述代码中,首先通过在查询到的字符串中的 mail 参数中获取目标用户的邮箱地址。接下来定义了一个 0~9 的数字列表,类型都是字符串,然后调用 random.sample 方法随机取 4 个值,因为 random.sample 方法的返回结果是列表类型,所以需要使用字符串的 join 方法将 random.sample 方法返回的结果合并成字符串,最后再把验证码拼接到 body 中发给目标用户。
-
使用Flask-Caching和Redis缓存验证码
虽然现在能通过邮箱发送验证码,但是服务器并没有记录邮箱与验证码的映射关系。下次用户提交邮箱和验证码时,我们并不知道该验证码是否正确。邮箱验证码这类数据并不是非常重要,可以存储到缓存中,如 Memcached 或者 Redis,考虑到后期会用 Celery 来异步发送邮件,Celery 又需要 Redis 作为中间人,因此选择 Redis 作为缓存。关于 Redis 的使用,读者可以参考第 8 章。
要在 Python 中使用 Redis,首先要安装 redis 包。打开 PyCharm 的 Terminal,输入以下命令。
$ pip install redis
在 Flask 中使用 Redis,可以借助第三方插件 Flask-Caching 实现。使用 Flask-Caching 操作 Redis 有以下好处。
-
缓存系统:Flask-Caching 支持许多缓存系统,如纯内存、Memcached、Redis、FileSystem 等。
-
上层接口:Flask-Caching 对不同的缓存系统提供了统一的上层接口。以后如果不用 Redis 作为缓存了,只要修改配置即可,不需要修改上层操作缓存的代码。
-
缓存内容:Flask-Caching 可以对 Flask 项目中的许多内容进行缓存,如视图缓存,模板缓存等。
打开 PyCharm 的 Terminal,输入以下命令完成 Flask-Caching 的安装。
$ pip install flask-caching
Flask-Caching 支持许多缓存系统,可通过在 app.config 中设置不同的 CACHE_TYPE 选择具体的缓存系统。Flask-Caching 支持的常见 CACHE_TYPE 缓存配置如表 9-2 所示。

完整的 CACHE_TYPE 配置请参考官方文档 https://flask-caching.readthedocs.io/en/latest/index.html#configuring-flask-caching 。
我们的项目选择的是 RedisCache,其参数配置如表 9-3 所示。

回到 pythonbbs 项目中,打开 config.py 文件,然后在 DevelopmentConfig 中添加 Flask-Caching 的配置信息,代码如下。
class DevelopmentConfig(BaseConfig):
...
# 缓存配置
CACHE_TYPE = "RedisCache"
CACHE_REDIS_HOST = "127.0.0.1"
CACHE_REDIS_PORT = 6379
上述代码中,配置了缓存类型为 Redis,以及 Redis 服务器的域名和端口号。
接下来创建一个 Flask-Caching 对象,打开 exts.py 文件,然后输入以下代码。
...
from flask_caching import Cache
...
cache = Cache()
再回到 app.py 文件中,从 exts.py 文件中导入 cache 变量,并且进行初始化,代码如下。
from exts import db, mail, cache
from flask import Flask
app = Flask(__name__)
# 初始化缓存
cache.init_app(app)
Flask-Caching 初始化完成后,就可以用它来缓存数据了。我们回到 blueprints/user.py 文件中,先从 exts.py 文件中导入 cache 对象,然后将 email_captcha 代码修改如下。
...
from exts import mail, cache
...
@bp.route("/mail/captcha")
def mail_captcha():
email = request.args.get("mail")
digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
captcha = "".join(random.sample(digits, 4))
body = f"【知了Python论坛】您的注册验证码是:{captcha},请勿告诉别人!"
message = Message(subject="我是邮件主题", recipients=[email], body=body)
mail.send(message)
cache.set(email, captcha, timeout=100)
return "success"
这样就完成了邮箱和验证码的缓存,以后要验证的时候,通过 cache.get(mail) 方法从缓存中获取即可。
使用Celery发送邮件
现在的 mail_captcha 视图函数虽然可以正常发送邮件,但是用户体验不好,一方面,发送邮件需要发起网络请求,必须要等邮件发送成功后浏览器才能收到响应,用户等待时间过长。另一方面,发送邮件这种耗时操作会导致线程被长时间占用,从而导致无法服务其他请求,造成服务器资源紧张。对于这些耗时的操作,我们一般通过异步的方式来实现,其中最常用、最稳定的方式就是通过 Celery 来实现。
Celery 是一个任务调度框架,是用纯 Python 实现的,可以轻松地集成到 Flask 项目中。Celery 由五大模块组成,分别是 Task(任务)、Broker(中间人)、Celery Beat(调度器)、Worker(消费者)、Backend(存储)。
步骤如下:首先在程序中定义好 Task,然后启动 Celery,Celery 读取 Task 并存放到 Broker 中,Broker 是具有存储能力的服务,如 Redis、RabbitMQ、数据库等。Celery Beat 会根据配置,或者由开发者手动控制,分配任务给 Worker,Worker 在完成任务后把结果存储到 Backend 中,Backend 也是具有存储能力的服务,一般为了方便,会和 Broker 使用同一个服务,Backend 不是必需的,如果没有设置 Backend,将不会存储结果。Celery 的工作原理如图 9-26 所示。

Celery 非常适用以下两种场景。
-
异步任务:对于一些会阻塞程序的耗时操作,如发送邮件、发送短信、视频转码等,非常适合放到 Celery 中执行。
-
定时任务:可以通过配置实现定时执行任务,如定时抓取数据、定时发送报告等。
Celery 是一个第三方Python 框架,需要单独安装。打开 PyCharm 的 Terminal,输入以下命令即可完成安装。
$ pip install celery
Celery 框架安装完成后,首先在 config.py 下的 DevelopmentConfig 中添加 Broker 和 Backend 配置信息,代码如下。
class DevelopmentConfig(BaseConfig):
...
# Celery配置
# 格式:redis://:password@hostname:port/db_number
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/0"
再回到 pythonbbs 项目,在项目根路径下创建一个 bbs_celery.py 文件,然后输入以下代码。

上述代码中,定义了发送邮件的函数 send_mail,还定义了函数 make_celery。在 make_celery 函数中,将 send_mail 添加到 celery 任务中。
下面再回到 app.py 中,导入 make_celery 函数,并创建一个 Celery 对象,代码如下。
from bbs_celery import make_celery
...
# 构建celery
celery = make_celery(app)
下面回到 blueprints/user.py 的 email_captcha 视图函数中,把之前发送邮件的代码删除,改成用 celery 任务的方式发送,代码如下。
...
from flask import current_app
...
@bp.route("/mail/captcha")
def mail_captcha():
email = request.args.get("mail")
digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
captcha = "".join(random.sample(digits, 4))
subject = "【知了Python论坛】注册验证码"
body = f"【知了Python论坛】您的注册验证码是:{captcha},请勿告诉别人!"
# 通过 Celery 发送邮件任务
current_app.celery.send_task("send_mail", (email, subject, body))
# 将验证码存入缓存
cache.set(email, captcha, timeout=100)
return "success"
接下来运行 Celery,如果读者是在 Windows 系统下运行 Celery,那么还需要安装一个第三方库:gevent,安装命令如下。
$ pip install gevent
在 PyCharm 的 Terminal 下,输入以下命令,启动 Celery 程序。
$ celery -A app.celery worker -P gevent -l info
如果是在 Linux 系统下运行,则无须 “-P gevent” 参数。更多关于 Celery 的使用请参阅官方文档 https://docs.celeryproject.org/en/stable/ 。
打开浏览器,输入 http://127.0.0.1:5000/user/mail/captcha?mail=邮箱账号 ,浏览器可以立即收到响应,并且也能收到邮件。
RESTful API
RESTful 也叫作 REST(representational state transfer,表现层状态转换),是 RoyFielding 博士在 2000 年提出的一种万维网架构风格,其主要作用是提供一种软件之间通过 HTTP/HTTPS 协议交互数据的风格。RESTful API 有以下特点。
(1)直观简短的 URL 地址,如以下 URL。
-
/post/list:帖子列表。
-
/posts/111:主键为 111 的帖子详情。
-
/post/111/comments:主键为 111 的帖子下所有的评论。
(2)数据传输格式,如 JSON、XML 等。JSON 因具有格式清晰、体积小等优点,已经取代 XML,成为大多数互联网产品的首选格式了。
(3)操作资源的方法,根据操作资源的目的,选择不同的 method。如获取资源用 GET、创建资源用 POST、替换资源用 PUT、删除资源用 DELETE。
(4)响应的状态码。服务器根据情况返回对应的状态码。2xx代表操作成功、3xx代表重定向、4xx代表客户端有错误、5xx代表服务器出现错误。
RESTful API 是一种风格,不是规范,读者在产品开发中,不应拘泥于严格的 RESTful API,而应该根据自身需求灵活调整。
在 Flask 中提供了 jsonify 方法,用于返回 JSON 格式的数据。下面使用 jsonify 方法封装一个用于返回 RESTful API 风格的模块。在 pythonbbs 根路径下,创建一个名叫 utils 的包,这个包主要用来存放一些工具类模块,接着在 utils 包下创建一个 restful.py 文件,然后输入以下代码。
from flask import jsonify
class HttpCode(object):
# 响应正常
ok = 200
# 没有登录错误
unloginerror = 401
# 没有权限错误
permissionerror = 403
# 客户端参数错误
paramserror = 400
# 服务器错误
servererror = 500
@staticmethod
def _restful_result(code, message, data):
return jsonify({"message": message or "", "data": data or {}}), code
@staticmethod
def ok(message=None, data=None):
return HttpCode._restful_result(code=HttpCode.ok, message=message, data=data)
@staticmethod
def unlogin_error(message="没有登录!"):
return HttpCode._restful_result(code=HttpCode.unloginerror, message=message, data=None)
@staticmethod
def permission_error(message="没有权限访问!"):
return HttpCode._restful_result(code=HttpCode.permissionerror, message=message, data=None)
@staticmethod
def params_error(message="参数错误!"):
return HttpCode._restful_result(code=HttpCode.paramserror, message=message, data=None)
@staticmethod
def server_error(message="服务器开小差啦!"):
return HttpCode._restful_result(code=HttpCode.servererror, message=message or '服务器内部错误', data=None)
上述代码中,我们针对不同的状态码添加了对应的函数,状态码和对应函数说明如表 9-4 所示。

表 9-4 中的函数,不管什么状态码,不管是否需要给客户端返回数据,最终调用的都是 _restful_result 函数,这个函数中封装了返回数据的格式,返回数据的格式类似如下。
{
"message": "...",
"data": ...
}
保持格式一致有个好处,客户端在任何情况下都可以获取到这两个参数,不会因为某些 API 下没有某个参数而导致代码抛出异常。
在网站开发中,RESTful API 一般用于 AJAX(asynchronous JavaScript and XML)请求。AJAX 是使用 JavaScript 语言异步发送请求的,根据响应对页面进行局部更新。发送邮箱验证码的请求非常适合使用 AJAX 方式,因为用传统表单的形式,会导致注册页面发生跳转,所以将 blueprints/user.py 中的 email_captcha 的代码修改为如下形式。
from utils import restful
@bp.route("/mail/captcha")
def mail_captcha():
try:
email = request.args.get("mail")
digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
captcha = "".join(random.sample(digits, 4))
subject = "【知了Python论坛】注册验证码"
body = f"【知了Python论坛】您的注册验证码是:{captcha},请勿告诉别人!"
current_app.celery.send_task("send_mail", (email, subject, body))
cache.set(email, captcha, timeout=100)
return restful.ok()
except Exception as e:
print(e)
return restful.server_error()
如果视图函数正常执行,并且邮件也能发送成功,那么返回状态码 200,否则返回服务器错误状态码 500。
CSRF保护
只要网页是通过模板渲染的,并且交互过程中存在非 GET 请求,那么就必须要开启 CSRF 保护。关于 CSRF 的详细介绍读者可以参考第 6 章。开启 CSRF 保护需要使用 flask-wtf 中的 CSRFProtect,首先在 PyCharm 的 Terminal 下输入以下命令安装 flask-wtf。
$ pip install flask-wtf
回到 app.py 中,添加以下代码。
from flask_wtf import CSRFProtect
...
# CSRF保护
CSRFProtect(app)
这样以后所有的非 GET 请求都必须在请求头或者请求体中加上 csrf_token,否则会出现状态码为 400、提示信息为 Bad Request 的错误。注册功能用表单即可实现,因此需要在 templates/front/register.html 的 form 标签下添加以下代码。
...
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
...
以后单击 “立即注册” 按钮,会自动把 csrf_token 所对应的值提交给视图函数。
使用AJAX获取邮箱验证码
在注册页面中,当单击 “发送验证码” 按钮后,这时用户一般不希望看到页面的刷新,所以要使用异步发送获取验证码的请求,也就是 AJAX 技术。我们使用 jQuery 提供的 $.ajax 方法来实现,考虑到项目已经启动了 CSRF 保护,每次发送请求都要在请求体或者请求头中添加 csrf_token。为了方便,先在 templates/front/base.html 的 head 标签中添加以下代码。
<meta name="csrf-token" content="{{ csrf_token() }}">
读者按照 9.3.1 节的复制操作后,在 static/common 下有一个 zlajax.js 文件,这个文件是对 $.ajax 做了一层封装,在非 GET 请求之前,先从模板的 meta 标签中读取 csrf-token 的值,然后将读取到的值设置到请求头中,这样就不需要每次发送非 GET 请求前都手动设置 csrf_token 了。zlajax.js 代码如下。

考虑到很多页面都需要使用 AJAX,所以把 zlajax.js 文件放到 templates/front/base.html 的 head 标签中,这样以后所有页面就都能使用这个文件了,代码如下。
<script src="{{ url_for('static', filename='common/zlajax.js') }}"></script>
因为 zlajax.js 依赖 jQuery,所以必须要把 zlajax.js 放到 jQuery 文件后面。
接下来在 static/front/js 下创建一个 register.js 文件,这个文件用于绑定 “发送验证码” 按钮的单击事件,并且执行 AJAX 请求,代码如下。
$(function () {
$('#captcha-btn').on("click", function(event) {
event.preventDefault(); // 阻止默认行为
// 获取邮箱
var email = $("input[name='email']").val();
zlajax.get({
url: "/user/mail/captcha?mail=" + email
}).done(function (result) {
alert("验证码发送成功!");
}).fail(function (error) {
alert(error.message);
});
});
});
上述代码中,为 id 为 captcha-btn 的按钮绑定了单击事件。单击按钮后,先是获取用户输入的邮箱,然后通过 zlajax.get 方法发送请求,URL 不需要带域名,向以 /
开头的 URL 发送请求,浏览器会自动使用当前域名。如果请求成功,会执行 done 中的函数,如果请求失败,则会执行 fail 中的函数。这里不管成功还是失败,我们都使用 alert 函数反馈结果。由于 js 文件必须要加载到模板中才能生效,所以打开 /templates/front/register.html 文件,然后将 head 这个 block 中的代码修改如下。
...
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='front/css/sign.css') }}">
<script src="{{ url_for('static', filename='front/js/register.js') }}"></script>
{% endblock %}
...
实现注册功能
邮箱验证码可以正常获取后,我们再来完善注册功能。首先在 templates/front/register.html 中的 form 标签上添加 action 和 method 属性,代码如下。
...
<form action="{{ url_for('user.register') }}" method="post" id="register-form">
...
</form>
...
action 属性的作用是,在单击 “提交” 按钮时,将数据发送到某个 URL,这里是提交到 user.register 视图函数中。method 属性用来表示用什么方法发送数据,表单数据通常采用 POST 方法。把表单数据提交到视图函数后,需要先对表单数据做验证,在 pythonbbs 项目根路径下创建一个名叫 forms 的 Python Package,然后创建 user.py 文件,代码如下。

上述代码中,创建了一个 RegisterForm 表单类,然后定义了 email、captcha、username、password 和 confirm_password 这 5 个字段,并且分别指定了验证器,其中 Email 邮箱验证器必须要安装第三方库 email_validator,打开 PyCharm 的 Terminal,输入以下命令完成安装。
$ pip install email_validator
除此之外,还对 email 和 captcha 单独做了验证。验证 email 的目的是判断邮箱是否已经被注册过,验证 captcha 的目的是判断验证码是否正确。
考虑到以后在视图函数中的表单验证失败后,需要把错误信息传到模板中。我们定义一个父类,用于从 form.errors 中提取所有字符串类型的错误信息。在 pythonbbs/forms 下新建一个 baseform.py 文件,然后输入以下代码。
from wtforms import Form
class BaseForm(Form):
@property
def messages(self):
message_list = []
if self.errors:
for errors in self.errors.values():
message_list.extend(errors)
return message_list
将 RegisterForm 的继承关系修改为如下。
...
from .baseform import BaseForm
class RegisterForm(BaseForm):
...
以后可以通过 form.messages 获取所有字符串类型的错误信息,用以传给模板进行渲染,这样用户就能知道是哪里输入错了。
下面再把 RegisterForm 导入 blueprints/user.py,完善 register 视图函数,代码如下。

上述代码中,register 视图函数同时支持 GET 和 POST 两种 method 的请求。因此需要设置 @bp.route 中的 methods 参数为 ['GET', 'POST']。在 register 视图函数中,通过判断 request.method 来执行对应操作。如果是 GET 请求,那么就返回模板;如果是 POST 请求,则构建 RegisterForm 对象,在表单验证通过的情况下,创建 UserModel 对象并保存到数据库中。注册完成后,需要跳转到登录页面,让用户自行登录,这里为了跳转登录页面时不报错,创建了一个非常简单的 login 视图函数。如果验证失败,则把表单的错误信息添加到 flash 中,重新加载注册页面。
在表单验证失败的情况下,由于视图函数已经把错误消息添加到 flash 中了,所以模板中可以通过 get_flashed_messages 获取所有的错误消息。在 templates/front/register.html 中的 “立即注册” 按钮上添加以下代码。

在浏览器中访问 http://127.0.0.1:5000/user/register ,然后按照规则输入数据,即可完成注册。如果数据格式输入错误,邮箱已存在,或者验证码错误等,都会在注册页面中显示错误信息,如图 9-27 所示。
