注册

渲染注册模板

虽然通过命令能添加用户,但是网站上线运行后,必须要有界面能让普通用户注册。在讲解注册功能实现之前,先来说明一下我们提前准备好的模板。读者在获取到本项目后,可以在 pythonbbs 项目根目录下看到 awebsite 文件夹,这个文件夹中的模板是笔者为方便读者学习提前准备的,所有模板都是纯静态的。首先执行以下两步操作。

  • 将 pythonbbs/awebsite/static 下的所有文件和文件夹全部复制到 pythonbbs/static 中。

  • 将 pythonbbs/awebsite/templates 下的所有文件和文件夹全部复制到 pythonbbs/templates 中。

在后续的章节中,会指导读者修改其中的部分代码。

以上步骤完成后,读者可以看到在 pythonbbs/templates/front 下有一个 base.html 文件,代码如下。

image 2025 01 21 22 08 20 530

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 文件,此文件用于注册页面,代码如下。

image 2025 01 21 22 10 06 210

上述代码中,先是让 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 所示的注册页面。

image 2025 01 21 22 12 06 738
Figure 1. 图9-20 注册页面

注册逻辑是,用户输入邮箱后单击 “发送验证码” 按钮,服务器就会给这个邮箱发送一个验证码,用户收到邮箱验证码后填入输入框,并完成其他信息的输入,最后单击 “立即注册” 按钮实现注册。

使用Flask-Mail发送邮箱验证码

注册功能的第一步,就是给用户输入的邮箱发送验证码,下面讲解在 Flask 中发送邮件。在 Flask 中发送邮件非常简单,总共分为 3 步,第 1 步安装 Flask-Mail,第 2 步配置邮箱参数,第 3 步发送邮件。

  1. 安装Flask-Mail

    首先安装 Flask-Mail,打开 PyCharm 的 Terminal,然后输入并执行以下命令即可完成安装。

    $ pip install flask-mail

    Flask-Mail 插件的详细使用说明,可参考其官方文档 https://pythonhosted.org/Flask-Mail/

  2. 配置邮箱参数

    想要发送邮件,必须要有一个邮箱服务器。在本书中,为了方便读者学习,我们用个人版网易邮箱来讲解邮箱的配置和使用,读者也可以选择其他公司的邮箱服务,如 QQ 邮箱、新浪邮箱、Gmail 邮箱等,使用方式都大同小异。有的公司会搭建自己的邮箱服务器,有的公司会向第三方提供商购买邮箱服务,如网易企业邮箱、腾讯企业邮箱、阿里邮箱企业版等,这些服务商都有非常详细的接入教程,遇到问题还可以咨询客服。 使用 Flask-Mail 发送邮件需要使用 SMTP 协议(simple mail transfer protocol)。首先在个人版网易邮箱中开启 SMTP 服务,登录个人版网易邮箱,单击顶部的 “设置” 按钮,然后在弹出的菜单中选择 “POP3/SMTP/IMAP”,如图 9-21 所示。

    image 2025 01 21 22 15 09 191
    Figure 2. 图9-21 个人网易邮箱设置按钮

    进入 SMTP 设置界面后,找到 “IMAP/SMTP服务”,然后单击 “开启” 超链接,如图 9-22 所示。

    image 2025 01 21 22 16 15 984
    Figure 3. 图9-22 开启SMTP界面

    单击 “开启” 超链接后会弹出一个 “账号安全提示” 对话框,因为邮箱只是用于我们自己的项目,只要我们的代码不被公开,还是非常安全的,所以直接单击 “继续开启” 按钮即可,如图 9-23 所示。

    image 2025 01 21 22 17 03 135
    Figure 4. 图9-23 账号安全提示

    单击 “继续开启” 按钮后,会弹出 “账号安全验证” 对话框,需要用手机发送短信验证码来验证是否是本人操作,按照提示信息进行操作即可,如图 9-24 所示。

    图 9-24 中显示的二维码,可以使用网易邮箱大师 App 扫描并发送短信,或者单击 “手动发送短信”,会提示你如何手动发送短信。在发送完短信验证码后,单击 “我已发送” 按钮会弹出 “开启IMAP/SMTP” 对话框,显示生成的授权密码,因为授权密码只会显示一次,所以读者要复制后保存下来,如图 9-25 所示。

    image 2025 01 21 22 18 01 769
    Figure 5. 图9-24 发送验证码开启邮箱
    image 2025 01 21 22 18 22 745
    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:默认发送者,填邮箱账号即可。

    执行以上操作后,即完成了邮箱的配置。

  3. 发送邮件

    发送邮件要先创建一个 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 所示。

image 2025 01 22 11 56 37 894
Figure 7. 表9-2 CACHE_TYPE配置

我们的项目选择的是 RedisCache,其参数配置如表 9-3 所示。

image 2025 01 22 11 57 23 622
Figure 8. 表9-3 RedisCache下可配参数

回到 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 所示。

image 2025 01 22 12 05 55 408
Figure 9. 图9-26 Celery工作原理

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 文件,然后输入以下代码。

image 2025 01 22 12 08 52 312

上述代码中,定义了发送邮件的函数 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 所示。

image 2025 01 22 12 16 23 166
Figure 10. 表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 代码如下。

image 2025 01 22 14 19 56 578

考虑到很多页面都需要使用 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 文件,代码如下。

image 2025 01 22 14 57 00 986

上述代码中,创建了一个 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 视图函数,代码如下。

image 2025 01 22 15 00 12 689

上述代码中,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 中的 “立即注册” 按钮上添加以下代码。

image 2025 01 22 15 01 52 137

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

image 2025 01 22 15 02 17 818
Figure 11. 图9-27 注册页面显示错误信息