[记录]基于Flask Web全栈开发实战-项目实战·上篇(黄勇 • 著)

Micheal_Dad 2024-07-09 11:03:02 阅读 67

说明

基于书籍《Flask Web全栈开发实战》【黄勇·著】第9章项目实战

创建项目

代码环境:pycharm

File->New Project->Flask

在这里插入图片描述

安装相应的python 包

<code># 用于flask在使用ORM模型操作数据库

pip install flask-sqlalchemy

# Python操作数据库的驱动程序

pip install pymysql

# 对密码加密和解密

pip install cryptography

# 用于将ORM模型的变更同步到数据库中

pip install flask-migrate

config.py文件

在根目录下,常见一个config.py的python文件,用来存放配置项。

class BaseConfig:

SECRET_KEY = 'linql_test'

SQLALCHEMY_TRACK_MODIFICATIONS = False

# 开发环境

class DevelopmentConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' #服务器地址

PORT = 3306 #默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' #数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

# 测试环境

class TestingConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' # 服务器地址

PORT = 3306 # 默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' # 数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

# 生产部署环境

class ProductionConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' # 服务器地址

PORT = 3306 # 默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' # 数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

在app.py中,绑定配置

from flask import Flask

import config

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

exts.py文件

在根目录里创建exts.py文件,主要用来存放一些第三方插件的对象。

如:SQLAlchemy对象、Flask-Mail对象等。

目的,是为了防止循环引用。

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

回到app.py中,然后导入db变量,再通过db.init_app(app)完成初始化。

from flask import Flask

import config

from exts import db

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

blueprints模块

通过蓝图来模块化,创建一个blueprints的包,用于存放蓝图模块。

在项目名称上右击,New->Python Package

并在,blueprints下,分别创建名为:cms、front和user的python文件

在这里插入图片描述

在cms.py中创建蓝图对象

<code>from flask import Blueprint

bp = Blueprint("cms",__name__,url_prefix="/cms")code>

在front.py中创建蓝图对象

from flask import Blueprint

bp = Blueprint("front",__name__,url_prefix="")code>

在user.py中创建蓝图对象

from flask import Blueprint

bp = Blueprint("user",__name__,url_prefix="/user")code>

创建了蓝图对象,并指定了url前缀,因front是面向前台的,所以url为空

创建蓝图对象后,还需要在app.py中完成注册。

from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

models模块

在根目录下,创建一个名为models的Python Package,然后在models下分别创建user.py和post.py,用来存放与用户和帖子相关的ORM模型。

在这里插入图片描述

创建用户相关模型

创建权限和角色模型

用户系统最核心的部分就是用户相关的ORM模型。

该系统的前台和后台用的是同一个用户系统,而后台系统中需要角色和权限管理。

首先,来添加权限ORM模型,在models/user.py中添加以下代码:

<code>from exts import db

from datetime import datetime

from enum import Enum

# PermissionEnum 枚举类型

# 存放权限类型的枚举

class PermissionEnum(Enum):

BOARD = "板块"

POST = "帖子"

COMMENT = "评论"

FRONT_USER = "前台用户"

CMS_USER = "后台用户"

# PermissionModel模型

# name 从PermissionEnum 枚举取

class PermissionModel(db.Model):

__tablename__="permission"code>

id = db.Column(db.Integer,primary_key=True,autoincrement=True)

name = db.Column(db.Enum(PermissionEnum),nullable=False,unique=True)

# 中间表

# role_id来引用role表

# permission_id来引用permission表

role_permission_table=db.Table(

"role_permission_table",

db.Column("role_id",db.Integer,db.ForeignKey("role.id")),

db.Column("permission_id",db.Integer,db.ForeignKey("permission.id"))

)

class RoleModel(db.Model):

__tablename__="role"code>

# 主键id

id = db.Column(db.Integer,primary_key=True,autoincrement=True)

# 角色名称

name = db.Column(db.String(100),nullable=False)

# 角色描述

desc = db.Column(db.String(100),nullable=False)

# 创建时间

create_time = db.Column(db.Datetime,default=datetime.now)

# 关系属性(RoleModel和PermissionModel属于多对多关系)

# role_permission_table 中间表

permissions = db.relationship("PermissionModel",secondary=role_permission_table,backref="roles")code>

在PermissionModel和RoleModel创建完成后,将模型映射到数据库中,需要借助Flask-Migrate插件

在app.py中,创建Migrate对象

from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app,db)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

为了迁移时能让程序识别到models/user.py中的ORM模型,需要导入

from models import user

---------------------------------------------------------------------------

ModuleNotFoundError Traceback (most recent call last)

Cell In[5], line 1

----> 1 from models import user

ModuleNotFoundError: No module named 'models'

在pyCharm的Terminal中执行以下命令,以生成迁移脚本,并完成迁移脚本的执行。

(pythonWeb) linql@localhost flaskDemo1 % flask db migrate -m "create permission and role model"

Error: Path doesn't exist: '/Users/linql/Desktop/code_Deeplearn/flaskDemo1/migrations'. Please use the 'init' command to create a new scripts folder.

上面运行,会报错。提示需要先“init”

# 先执行

flask db init

# 再执行

flask db migrate -m "create permission and role model"

在这里插入图片描述

执行以上命令后,已经为ORM模型生成了迁移脚本。

但,此时并没有真正同步到数据库中,还需要执行以下命令才会实现同步到数据库中。

<code>flask db upgrade

在这里插入图片描述

在这里插入图片描述

创建权限和角色

Flask 项目 如何创建命令

在flask安装时,默认会安装click库

click库,主要作用是用来实现命令,Flask已经针对click库进行了集成,通过app.cli即可访问到click对象。

下面是一个简单的命令测试

<code>from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

from models import user

import click

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app,db)

@app.route('/')

def hello_world():

return 'Hello World!'

@app.cli.command("my-command")

def my_command():

click.echo("这是我的自定义命令!")

if __name__ == '__main__':

app.run()

通过装饰器@app.cli.command,将my_command函数添加到命令中,并且指定命令的名称为my-command。

然后,再在pycharm的terminal中输入以下命令。

flask my-command

在这里插入图片描述

在app.py中,实现一个名叫create-permission的命令。

针对每个模块分别添加一个权限。

<code>from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

from models.user import PermissionEnum,PermissionModel,RoleModel

import click

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app,db)

@app.route('/')

def hello_world():

return 'Hello World!'

# 测试命令

@app.cli.command("my-command")

def my_command():

click.echo("这是我的自定义命令!")

# 创建create-permission命令

@app.cli.command("create-permission")

def create_permission():

for permission_name in dir(PermissionEnum):

if permission_name.startswith("__"):

continue

permission = PermissionModel(name=getattr(PermissionEnum,permission_name))

db.session.add(permission)

db.session.commit()

click.echo("权限添加成功")

if __name__ == '__main__':

app.run()

然后,再在pycharm的terminal中输入以下命令。

flask create-permission

在这里插入图片描述

在这里插入图片描述

权限创建成功后,再添加角色。

创建3个角色,分别为:稽查、运营、管理员。

角色名,稽查,权限:帖子、评论

角色名,运营,权限:板块、帖子、评论、前台用户

角色名,管理员,权限:板块、帖子、评论、前台用户、后台用户

创建角色,同创建权限一样操作,下面只给出代码,执行步骤一致

<code>from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

from models.user import PermissionEnum, PermissionModel, RoleModel

import click

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

@app.route('/')

def hello_world():

return 'Hello World!'

# 测试命令

@app.cli.command("my-command")

def my_command():

click.echo("这是我的自定义命令!")

# 创建添加权限的命令create-permission

@app.cli.command("create-permission")

def create_permission():

for permission_name in dir(PermissionEnum):

if permission_name.startswith("__"):

continue

permission = PermissionModel(name=getattr(PermissionEnum, permission_name))

db.session.add(permission)

db.session.commit()

click.echo("权限添加成功")

# 创建添加角色的命令create-role

@app.cli.command("create-role")

def create_role():

# 稽查

inspector = RoleModel(name="稽查", desc="负责审核帖子和评论是否合法合规")code>

inspector.permissions = PermissionModel.query.filter(

PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all()

# 运营

operator = RoleModel(name="运营", desc="负责网站持续正常运营!")code>

operator.permissions = PermissionModel.query.filter(PermissionModel.name.in_(

[PermissionEnum.POST, PermissionEnum.COMMENT, PermissionEnum.BOARD, PermissionEnum.FRONT_USER,

PermissionEnum.CMS_USER])).all()

# 管理员

administrator = RoleModel(name="管理员", desc="负责整个网站所有工作!")code>

administrator.permissions = PermissionModel.query.all()

db.session.add_all([inspector,operator,administrator])

db.session.commit()

click.echo("角色创建成功!")

if __name__ == '__main__':

app.run()

# 在pycharm中的terminal中运行

flask create-role

在这里插入图片描述

对app.py进行瘦身,将command部分的代码放到commands.py里

<code># commands.py

from models.user import PermissionEnum, PermissionModel, RoleModel

import click

from exts import db

# 测试命令

# @app.cli.command("my-command")

def my_command():

click.echo("这是我的自定义命令!")

# 创建添加权限的命令create-permission

# @app.cli.command("create-permission")

def create_permission():

for permission_name in dir(PermissionEnum):

if permission_name.startswith("__"):

continue

permission = PermissionModel(name=getattr(PermissionEnum, permission_name))

db.session.add(permission)

db.session.commit()

click.echo("权限添加成功")

# 创建添加角色的命令create-role

# @app.cli.command("create-role")

def create_role():

# 稽查

inspector = RoleModel(name="稽查", desc="负责审核帖子和评论是否合法合规")code>

inspector.permissions = PermissionModel.query.filter(

PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all()

# 运营

operator = RoleModel(name="运营", desc="负责网站持续正常运营!")code>

operator.permissions = PermissionModel.query.filter(PermissionModel.name.in_(

[PermissionEnum.POST, PermissionEnum.COMMENT, PermissionEnum.BOARD, PermissionEnum.FRONT_USER,

PermissionEnum.CMS_USER])).all()

# 管理员

administrator = RoleModel(name="管理员", desc="负责整个网站所有工作!")code>

administrator.permissions = PermissionModel.query.all()

db.session.add_all([inspector,operator,administrator])

db.session.commit()

click.echo("角色创建成功!")

# app.py文件

from flask import Flask

import config

from exts import db

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

# 引入了命令

import commands

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

# 添加命令

app.cli.command("create-permission")(commands.create_permission)

app.cli.command("create-role")(commands.create_role)

app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

在这里插入图片描述

创建用户模型

UUID(universally unique identfier)

UUID的长度为32字符,再加上4个横线,总共有36个字符。

引入python第三方库,shortuuid

shortuuid 会对原始UUID进行base57 编码,然后删除相似的字符,如:I,1,L,o和0,最后生成默认22位长度的字符串。

<code># 安装shortuuid库

pip install shortuuid

重构UserModel

from exts import db

from datetime import datetime

from enum import Enum

# 引入shortuuid

from shortuuid import uuid

# 引入加密和验证

from werkzeug.security import generate_password_hash,check_password_hash

# PermissionEnum 枚举类型

# 存放权限类型的枚举

class PermissionEnum(Enum):

BOARD = "板块"

POST = "帖子"

COMMENT = "评论"

FRONT_USER = "前台用户"

CMS_USER = "后台用户"

# PermissionModel模型

# name 从PermissionEnum 枚举取

class PermissionModel(db.Model):

__tablename__ = "permission"

id = db.Column(db.Integer, primary_key=True, autoincrement=True)

name = db.Column(db.Enum(PermissionEnum), nullable=False, unique=True)

# 中间表

# role_id来引用role表

# permission_id来引用permission表

role_permission_table = db.Table(

"role_permission_table",

db.Column("role_id", db.Integer, db.ForeignKey("role.id")),

db.Column("permission_id", db.Integer, db.ForeignKey("permission.id"))

)

class RoleModel(db.Model):

__tablename__ = "role"

# 主键id

id = db.Column(db.Integer, primary_key=True, autoincrement=True)

# 角色名称

name = db.Column(db.String(100), nullable=False)

# 角色描述

desc = db.Column(db.String(100), nullable=False)

# 创建时间

create_time = db.Column(db.DateTime, default=datetime.now)

# 关系属性(RoleModel和PermissionModel属于多对多关系)

# role_permission_table 中间表

permissions = db.relationship("PermissionModel", secondary=role_permission_table, backref="roles")code>

class UserModel(db.Model):

__tablename__ = "user"

# 主键

id = db.Column(db.String(100), primary_key=True, default=uuid)

# 用户名,不能为空,且值唯一

username = db.Column(db.String(50), nullable=False, unique=True)

# 密码,最大长度200,应该先加密再存储

_password = db.Column(db.String(200), nullable=False)

# 邮箱,不能为空,且值唯一

email = db.Column(db.String(50), nullable=False, unique=True)

# 头像,存储图片在服务器中保存的路径,可以为空

avatar = db.Column(db.String(100))

# 签名,可以为空

signature = db.Column(db.String(100))

# 加入时间,第一次存储,默认当前时间

join_time = db.Column(db.DateTime, default=datetime.now)

# 是否员工,只有员工才能进入后台系统,默认为False

is_staff = db.Column(db.Boolean, default=False)

# 是否可用,默认情况下是可用

is_active = db.Column(db.Boolean, default=True)

is_admin = db.Column(db.Boolean,default=False)

# 外键

# 角色外键,引用role表的id字段

role_id = db.Column(db.Integer,db.ForeignKey("role.id"))

# 关系属性,引用RoleModel

role = db.relationship("RoleModel",backref="users")code>

def __init__(self,*args,**kwargs):

if "password" in kwargs:

self.password = kwargs.get("password")

kwargs.pop("password")

super(UserModel,self).__init__(*args,**kwargs)

@property

def password(self):

return self._password

@password.setter

def password(self,raw_password):

self._password=generate_password_hash(raw_password)

def check_password(self,raw_password):

result = check_password_hash(self.password,raw_password)

return result

def has_permission(self,permission):

return permission in [permission.name for permisson in self.role.permissions]

# 在terminal中运行

flask db migrate -m "create user model"

flask db upgrade

在这里插入图片描述

在这里插入图片描述

创建测试用户

为了方便后续开发,按照角色个数创建3个员工账户。

在commands.py文件中,添加以下代码

<code>

# 添加测试3个角色的测试用户

def create_test_user():

admin_role = RoleModel.query.filter_by(name="管理员").first()code>

zhangsan = UserModel(username ="张三",email="zhangsan@zlkt.net",password="111111",is_staff=True,role=admin_role)code>

operator_role = RoleModel.query.filter_by(name="运营").first()code>

lisi = UserModel(username ="李四",email="lisi@zlkt.net",password="111111",is_staff=True,role=operator_role)code>

inspector_role = RoleModel.query.filter_by(name="稽查").first()code>

wangwu = UserModel(username="王五", email="wangwu@zlkt.net", password="111111", is_staff=True, role=inspector_role)code>

db.session.add_all([zhangsan,lisi,wangwu])

db.session.commit()

click.echo("测试用户添加成功!")

在app.py中添加,以下代码:

# 添加命令(增加3个角色测试用户)

app.cli.command("create-test-user")(commands.create_test_user)

在pycharm的terminal中,运行命令

flask create-test-user

在这里插入图片描述

创建管理员

在commands.py中添加以下命令:

<code># 创建管理员

@click.option("--username",'-u')

@click.option("--email",'-e')

@click.option("--password",'-p')

def create_admin(username,email,password):

admin_role = RoleModel.query.filter_by(name="管理员").first()code>

admin_user = UserModel(username=username,email=email,password=password,is_staff=True,role = admin_role)

db.session.add(admin_user)

db.session.commit()

click.echo("管理员创建成功")

其中,通过@click.option装饰器添加了3个参数。

以后在命令行中即可使用–username,–email,–password将用户名、邮箱、密码当作参数传递到函数中。

注册

渲染注册模版

因,未直接下载书籍自带的源码,只能手动自行编辑。

1、在根目录的static文件下,创建一个文件夹,名为:front

2、在front文件夹下,创建一个文件夹,名为:css

3、在css文件夹下,创建2个文件,分别为:base.css、sign.css

1、再在根目录的templates文件夹下,创建一个文件夹,名为:front

2、在front文件夹下,创建2个文件,分别为:base.html、register.html

# base.html

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<scrip src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></scrip>code>

<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">code>

<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.css"></script>code>

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/base.css') }}">code>

<title>{ % block title %}{ % endblock %}</title>

{ % block head %}{ % endblock %}

</head>

<body>

<nav class="navbar navbar-expand-lg navbar-light bg-light">code>

<a href="#" class="navbar-brand">知了Python论坛</a>code>

<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"code>

aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">code>

<span class="navbar-toggle-icon"></span>code>

</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">code>

<ul class="navbar-nav mr-auto">code>

<li class="nav-item active">code>

<a href="/" class="nav-link">首页 <span class="sr-only">(current)</span></a>code>

</li>

</ul>

<form class="form-inline my-lg-0">code>

<input class="form-control mr-sm-2" type="search" placeholder="请输入关键字" aria-label="Search">code>

<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>code>

</form>

<ul class="navbar-nav ml-4">code>

<li class="nav-item">code>

<a href="#" class="nav-link">登录</a>code>

</li>

<li class="nav-link">code>

<a href="#" class="nav-link">注册</a>code>

</li>

</ul>

</div>

</nav>

<div class="main-container">code>

{ % block body %}{ % endblock %}

</div>

</body>

</html>

base.html文件是所有前台页面的父模版

jquery.min.js:3.6.0版本的JQuery文件。JQuery文件可以快速寻找元素,发送AJAX请求

bootstrap.min.css:4.6.0 版本的bootstrap样式文件。可以快速构建网页界面。

bootstrap.min.js:4.6.0 版本的bootstrap JavaScript文件。BootStrap中的一些组件运行需要通过bootstrap.min.js来实现。

# register.html

{ % extends 'front/base.html' %}

{ % block title %}

知了课堂注册

{ % endblock %}

{ % block head %}

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/sign.css') }}">code>

{ % endblock %}

{ % block body %}

<h1 class="page-title">注册</h1>code>

<div class="sign-box">code>

<form action="" id="register-form">code>

<div class="form-group">code>

<div class="input-group">code>

<input type="text" class="form-control" name="email" placeholder="邮箱">code>

<div class="input-group-append">code>

<button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>code>

</div>

</div>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">code>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="username" placeholder="用户名">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="password" placeholder="密码">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="confirm_password" placeholder="确认密码">code>

</div>

<div class="form-group">code>

<a href="#" class="signup-link">返回登录</a>code>

<a href="#" class="resetpwd-link" style="float: right">找回密码</a>code>

</div>

</form>

</div>

{ % endblock %}

在blueprints/user.py中,添加以下代码:

from flask import Blueprint,render_template

bp = Blueprint("user",__name__,url_prefix="/user")code>

@bp.route("/register/")

def register():

return render_template("front/register.html")

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

安装Flask-Mail

在Pycharm的Terminal,输入并执行:

pip install flask-mail

配置邮箱参数

# 以QQ邮箱为例,其QQ邮箱,POP3/IMAP/SMTP/Exchange/CardDAV授权,请查阅其他资料完成。

开启个人邮箱的SMTP服务后,在项目中,打开config.py文件,在DevelopmentConfig中添加以下代码。

# 开发环境

class DevelopmentConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' # 服务器地址

PORT = 3306 # 默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' # 数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

# 邮箱配置

MAIL_SERVER = "smtp.qq.com"

MAIL_USE_SSL = True

MAIL_USE_TLS = False

MAIL_PORT = 465

MAIL_USERNAME = "**@qq.com" # 发送者邮箱

MAIL_PASSWORD = "****" # SMTP授权码

MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱

发送邮件

在项目中,打开exts.py中,创建1个mail对象

from flask_sqlalchemy import SQLAlchemy

from flask_mail import Mail

db = SQLAlchemy()

# 创建1个mail对象

mail = Mail()

回到,app.py文件,从exts.py中导入mail变量,并进行初始化。

from flask import Flask

import config

from exts import db,mail

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

# 引入了命令

import commands

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 初始化mail

mail.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

# 添加命令

app.cli.command("create-permission")(commands.create_permission)

app.cli.command("create-role")(commands.create_role)

app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试

# 添加命令(增加3个角色测试用户)

app.cli.command("create-test-user")(commands.create_test_user)

# 创建管理员命令

app.cli.command("create-admin")(commands.create_admin)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

完成Flask-Mail对象的初始化,后续就可使用mail变量发送邮件了。

接着,在blueprints/user.py中输入以下代码:

from flask import Blueprint, render_template

# 发送邮箱,引入的2个包

from flask_mail import Message

from exts import mail

bp = Blueprint("user", __name__, url_prefix="/user")code>

@bp.route("/register/")

def register():

return render_template("front/register.html")

@bp.route('/mail/captcha/')

def mail_captcha():

# recipients,填写接收者的邮箱地址

# 可以写多个,用,逗号隔开。

message = Message(subject="我是邮件主题", recipients=['***@outlook.com'], body="我是邮件内容")code>

mail.send(message)

return "success"

运行,输入URL:http://127.0.0.1:5000/user/mail/captcha/

在这里插入图片描述

上面是测试,下面是针对项目验证码的代码:

<code>from flask import Blueprint, render_template, request

import random

# 发送邮箱,引入的2个包

from flask_mail import Message

from exts import mail

bp = Blueprint("user", __name__, url_prefix="/user")code>

@bp.route("/register/")

def register():

return render_template("front/register.html")

@bp.route('/mail/captcha/')

def mail_catpcha():

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)code>

mail.send(message)

return "Succes"

使用Flask-Caching和Redis缓存验证码

要在Python中使用Redis,首先需要安装Redis包。

打开pyCharm中的terminal,输入以下命令:

pip install redis

在Flask中使用Redis,可以借助第三方插件Flask-Caching实现。

打开Pycharm中的Terminal,输入以下命令:

pip install flask-caching

回到项目中,打开config.py文件

在DevelopmentConfig中添加Flask-Caching的配置信息,如下所示:

# 开发环境

class DevelopmentConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' # 服务器地址

PORT = 3306 # 默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' # 数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

# 邮箱配置

MAIL_SERVER = "smtp.qq.com"

MAIL_USE_SSL = True

MAIL_USE_TLS = False

MAIL_PORT = 465

MAIL_USERNAME = "**@qq.com" # 发送者邮箱

MAIL_PASSWORD = "****" # SMTP授权码

MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱

# 缓存配置

CACHE_TYPE = "RedisCache"

CACHE_REDIS_HOST = "127.0.0.1"

CACHE_REDIS_PORT = 6379

在项目中,打开exts.py文件,然后输入以下代码:

from flask_sqlalchemy import SQLAlchemy

from flask_mail import Mail

from flask_caching import Cache

db = SQLAlchemy()

# 创建1个mail对象

mail = Mail()

# 创建1个Flask-Caching对象

cache = Cache()

再回到app.py文件中,从exts.py文件中导入cache变量,并且进行初始化,代码如下:

from flask import Flask

import config

from exts import db,mail,cache

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

# 引入了命令

import commands

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 初始化mail

mail.init_app(app)

# 初始化cache

cache.init_app(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

# 添加命令

app.cli.command("create-permission")(commands.create_permission)

app.cli.command("create-role")(commands.create_role)

app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试

# 添加命令(增加3个角色测试用户)

app.cli.command("create-test-user")(commands.create_test_user)

# 创建管理员命令

app.cli.command("create-admin")(commands.create_admin)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

Flask-Caching初始化完成后,就可以用它来缓存数据了。

回到blueprints/user.py文件中

先从exts.py文件中导入cache对象,然后将email.captcha代码修改如下:

from flask import Blueprint, render_template, request

import random

# 发送邮箱,引入的2个包

from flask_mail import Message

from exts import mail, cache

bp = Blueprint("user", __name__, url_prefix="/user")code>

@bp.route("/register/")

def register():

return render_template("front/register.html")

# # 测试使用

# @bp.route('/mail/captcha/')

# def mail_captcha():

# message = Message(subject="我是邮件主题", recipients=['*****@outlook.com'], body="我是邮件内容")code>

# mail.send(message)

# return "success"

@bp.route('/mail/captcha/')

def mail_catpcha():

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)code>

mail.send(message)

cache.set(email, captcha, timeout=100)

return "Succes"

使用Celery 发送邮件

上述,可以实现发送邮件,但体验感不好

1、发送邮件需要发起网络请求,必须要等邮件发送成功后,浏览器才能收到响应,用户等待长。

2、发送邮件这种耗时操作,会导致线程被长时间占用,从而无法服务其他请求。

通过异步方式来解决。

Celery是一个任务调度框架,由纯python开发,有5大模块:

1、Task(任务)

2、Broker(中间人)

3、Celery Beat(调度器)

4、Worker(消费者)

5、Backend(存储)

Celery是一个第三方Python库,在Pycharm的terminal中输入以下命令来安装:

pip install celery

首先在config.py下的 DevelopmentConfig中添加Broker和Backend配置信息,代码如下:

# 开发环境

class DevelopmentConfig(BaseConfig):

# 配置连接数据库

HOSTNAME = '192.168.3.5' # 服务器地址

PORT = 3306 # 默认端口号

USERNAME = 'root'

PASSWORD = 'root'

DATABASE = 'pythonbbs' # 数据库名

SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{ USERNAME}:{ PASSWORD}@{ HOSTNAME}:{ PORT}/{ DATABASE}?charset=utf8mb4"

# 邮箱配置

MAIL_SERVER = "smtp.qq.com"

MAIL_USE_SSL = True

MAIL_USE_TLS = False

MAIL_PORT = 465

MAIL_USERNAME = "**@qq.com" # 发送者邮箱

MAIL_PASSWORD = "****" # SMTP授权码

MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱

# 缓存配置

CACHE_TYPE = "RedisCache"

CACHE_REDIS_HOST = "127.0.0.1"

CACHE_REDIS_PORT = 6379

# Celery配置

CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"

CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/0"

在根目录下,创建一个bbs_celery.py文件,然后输入以下代码:

from flask_mail import Message

from exts import mail

from celery import Celery

# 定义任务函数

def send_mail(recipient, subject, body):

message = Message(subject=subject, recipients=[recipient], body=body)

mail.send(message)

print("发送成功")

# 创建Celery对象

def make_celery(app):

celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],

broker=app.config['CELERY_BROKER_URL'])

TaskBase = celery.Task

class ContextTask(TaskBase):

abstract = True

def __call__(self, *args, **kwargs):

with app.app_context():

return TaskBase.__call__(self, *args, **kwargs)

celery.Task = ContextTask

app.celery = celery

# 添加任务

celery.task(name="send_mail")(send_mail)code>

return celery

在app.py中导入make_celery函数,并创建1个Celery对象

from flask import Flask

import config

from exts import db,mail,cache

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

# 引入了命令

import commands

# 导入make_celery函数,并创建1个Celery对象

from bbs_celery import make_celery

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 初始化mail

mail.init_app(app)

# 初始化cache

cache.init_app(app)

# 构建celery

celery = make_celery(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

# 添加命令

app.cli.command("create-permission")(commands.create_permission)

app.cli.command("create-role")(commands.create_role)

app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试

# 添加命令(增加3个角色测试用户)

app.cli.command("create-test-user")(commands.create_test_user)

# 创建管理员命令

app.cli.command("create-admin")(commands.create_admin)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

回到blueprints/user.py的email_captcha视图函数中,把之前的发送邮件代码删除,改成celery任务的方式发送。

from flask import Blueprint, render_template, request, current_app

import random

# 发送邮箱,引入的2个包

from flask_mail import Message

from exts import mail, cache

bp = Blueprint("user", __name__, url_prefix="/user")code>

@bp.route("/register/")

def register():

return render_template("front/register.html")

# @bp.route('/mail/captcha/')

# def mail_catpcha():

# 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)code>

# mail.send(message)

# cache.set(email, captcha, timeout=100)

# return "Succes"

@bp.route('/mail/captcha/')

def mail_catpcha():

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 "Succes"

运行Celery,需要安装另外一个第三方python库:

pip install gevent

在pycharm的terminal中,输入以下命令,启动cerely:

celery -A app.celery worker -l info

在这里插入图片描述

配置Redis

Mac电脑配置Redis参考:参考链接

<code>【记录下这里关闭Redis报错】Mac上使用Redis无法写入快照或者

Error trying to save the DB, can‘t exit.

Mac上使用Redis无法写入快照或者 Error trying to save the DB, can‘t exit.

参考:https://blog.csdn.net/m0_53370288/article/details/117416009

安装Redis Desktop Manager

下载地址:https://www.macyy.cn/archives/1343#J_DLIPPCont

安装后,打开,并连接;

在这里插入图片描述

RESTful API

RESTful 也叫做REST(representational state transfer,表现层状态转换)

在项目根目录下,创建1个名叫:utils的包,用于存放一些工具类模块。

接着,在utils包下,创建1个restful.py的文件,代码如下:

<code>from flask import jsonify

class HttpCode(object):

# 响应正常

ok = 200

# 登录错误

unloginerror = 401

# 权限错误

permissionerror = 403

# 客户端参数错误

paramserror = 400

# 服务器错误

servererror = 500

def _restful_result(code, message, data):

return jsonify(({ "message": message or "", "data": data or { }})), code

def ok(message=None, data=None):

return _restful_result(code=HttpCode.ok, message=message, data=data)

def unlogin_error(message="没有登录!"):code>

return _restful_result(code=HttpCode.unloginerror, message=message, data=None)

def permission_error(message="没有权限!"):code>

return _restful_result(code=HttpCode.permissionerror, message=message, data=None)

def params_error(message="参数错误!"):code>

return _restful_result(code=HttpCode.paramserror, message=message, data=None)

def server_error(message="服务器错误!"):code>

return _restful_result(code=HttpCode.servererror, message=message or "服务器内部错误", data=None)

将blueprints/user.py中的email_captcha的代码修改如下:

@bp.route('/mail/captcha/')

def mail_catpcha():

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()

CSRF保护

开启CSRF保护需要使用flask-wtf中的CSRFProtect

首先,在pycharm的Terminal下输入安装flask-wtf命令:

pip install flask-wtf

回到app.py中,添加以下代码:

from flask import Flask

import config

from exts import db,mail,cache

from blueprints.cms import bp as cms_bp

from blueprints.front import bp as front_bp

from blueprints.user import bp as user_bp

from flask_migrate import Migrate

# 引入了命令

import commands

# 导入make_celery函数,并创建1个Celery对象

from bbs_celery import make_celery

# 导入CSRF

from flask_wtf import CSRFProtect

app = Flask(__name__)

# 引入开发环境

app.config.from_object(config.DevelopmentConfig)

# 初始化db

db.init_app(app)

# 初始化mail

mail.init_app(app)

# 初始化cache

cache.init_app(app)

# 构建celery

celery = make_celery(app)

# CSRF保护

CSRFProtect(app)

# 注册蓝图

app.register_blueprint(cms_bp)

app.register_blueprint(front_bp)

app.register_blueprint(user_bp)

# 创建Migrate对象

migrate = Migrate(app, db)

# 添加命令

app.cli.command("create-permission")(commands.create_permission)

app.cli.command("create-role")(commands.create_role)

app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试

# 添加命令(增加3个角色测试用户)

app.cli.command("create-test-user")(commands.create_test_user)

# 创建管理员命令

app.cli.command("create-admin")(commands.create_admin)

@app.route('/')

def hello_world():

return 'Hello World!'

if __name__ == '__main__':

app.run()

---------------------------------------------------------------------------

ModuleNotFoundError Traceback (most recent call last)

Cell In[9], line 2

1 from flask import Flask

----> 2 import config

3 from exts import db,mail,cache

5 from blueprints.cms import bp as cms_bp

ModuleNotFoundError: No module named 'config'

在templates/front/register.html的form标签下添加以下代码:

<input type="hidden" name="crsf_token" value="{ { csrf_token() }}">code>

使用AJAX获取邮箱验证码

在static/common下,创建1个名为:zlajax.js文件,这样每次发送非GET请求都不需要手动设置csrf_token了。具体代码如下:

var zlajax = {

'get': function (args) {

args['method'] = "get"

return this.ajax(args);

},

'post': function (args) {

args['method'] = "post"

return this.ajax(args);

},

'put': function (args) {

args['method'] = "put"

return this.ajax(args);

},

'delete': function (args) {

args['method'] = "delete"

return this.ajax(args);

},

'ajax': function (args) {

this._ajaxSetup();

return $.ajax(args);

},

'_ajaxSetup': function () {

$.ajaxSetup({

'beforeSend': function (xhr, settings) {

if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {

var csrftoken = $('meta[name=csrf-token]').attr('content');

xhr.setRequestHeader("X-CSRFToken", csrftoken)

}

}

}

);

}

};

将zlajax.js文件放到templates/front/base.html的head标签里,这样后面所有的页面都能使用这个文件里。代码如下:

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<scrip src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></scrip>code>

<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">code>

<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.css"></script>code>

<script src="{ { url_for('static',filename='common/zlajax.js') }}"></script>code>

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/base.css') }}">code>

<title>{ % block title %}{ % endblock %}</title>

{ % block head %}{ % endblock %}

</head>

<body>

<nav class="navbar navbar-expand-lg navbar-light bg-light">code>

<a href="#" class="navbar-brand">知了Python论坛</a>code>

<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"code>

aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">code>

<span class="navbar-toggle-icon"></span>code>

</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">code>

<ul class="navbar-nav mr-auto">code>

<li class="nav-item active">code>

<a href="/" class="nav-link">首页 <span class="sr-only">(current)</span></a>code>

</li>

</ul>

<form class="form-inline my-lg-0">code>

<input class="form-control mr-sm-2" type="search" placeholder="请输入关键字" aria-label="Search">code>

<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>code>

</form>

<ul class="navbar-nav ml-4">code>

<li class="nav-item">code>

<a href="#" class="nav-link">登录</a>code>

</li>

<li class="nav-link">code>

<a href="#" class="nav-link">注册</a>code>

</li>

</ul>

</div>

</nav>

<div class="main-container">code>

{ % block body %}{ % endblock %}

</div>

</body>

</html>

因为zlajax.js依赖JQuery,所以必须把zlajax.js放到jQuery文件后面。</br>

在static/front/js下创建1个register.js文件,用于绑定“发送验证码”按钮的单击时间,并且自信AJAX请求。代码如下:

$(function () {

$('#captcha-btn').on("click", function (event) {

event.preventDefault();

// 获取邮箱

var email = $("input[name='email']").val();code>

zlajax.get({

url: "/user/mail/captcha?mail=" + email

}).done(function (result) {

alert("验证码发送成功!");code>

}).fail(function (error) {

alert(error.message);

})

});

});

上述代码:id为captcha-btn的按钮绑定了单击事件。

单击按钮后,先是获取用户输入的邮箱,然后通过zlajax.get方法发送请求,URL不需要带域名,向以/开头的URL发送请求,浏览器会自动使员工当前域名。

如果请求成功,会执行done函数,如果请求失败,会执行fail函数。

由于js文件必须要加载到模版中才能生效,所以,打开templates/front/register.html文件,将head这个block的中代码修改如下:

{ % extends 'front/base.html' %}

{ % block title %}

知了课堂注册

{ % endblock %}

{ % block head %}

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/sign.css') }}">code>

<script src="{ { url_for('static',filename='front/js/register.js') }}"></script>code>

{ % endblock %}

{ % block body %}

<h1 class="page-title">注册</h1>code>

<div class="sign-box">code>

<form action="" id="register-form">code>

<div class="form-group">code>

<div class="input-group">code>

<input type="text" class="form-control" name="email" placeholder="邮箱">code>

<div class="input-group-append">code>

<button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>code>

</div>

</div>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">code>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="username" placeholder="用户名">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="password" placeholder="密码">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="confirm_password" placeholder="确认密码">code>

</div>

<div class="form-group">code>

<a href="#" class="signup-link">返回登录</a>code>

<a href="#" class="resetpwd-link" style="float: right">找回密码</a>code>

</div>

</form>

</div>

{ % endblock %}

实现注册功能

首先,在templates/front/register.html中的form标签上添加action和method属性。代码如下:

{ % extends 'front/base.html' %}

{ % block title %}

知了课堂注册

{ % endblock %}

{ % block head %}

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/sign.css') }}">code>

<script src="{ { url_for('static',filename='front/js/register.js') }}"></script>code>

{ % endblock %}

{ % block body %}

<h1 class="page-title">注册</h1>code>

<div class="sign-box">code>

<form action="{ { url_for('user.register') }}" method="post" id="register-form">code>

<div class="form-group">code>

<div class="input-group">code>

<input type="text" class="form-control" name="email" placeholder="邮箱">code>

<div class="input-group-append">code>

<button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>code>

</div>

</div>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">code>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="username" placeholder="用户名">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="password" placeholder="密码">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="confirm_password" placeholder="确认密码">code>

</div>

<div class="form-group">code>

<a href="#" class="signup-link">返回登录</a>code>

<a href="#" class="resetpwd-link" style="float: right">找回密码</a>code>

</div>

</form>

</div>

{ % endblock %}

表单通常用POST方法,把表单数据提交到视图函数后,需要对先对表单数据做验证,在根目录下,插件一个名叫:forms的Python Package,然后插件user.py文件,代码如下:

from wtforms import Form, StringField, ValidationError

from wtforms.validators import Email, EqualTo, Length

from exts import cache

from models.user import UserModel

class RegisterForm(Form):

email = StringField(validators=[Email(message="请输入正确格式的邮箱!")])code>

captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")])code>

username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")])code>

password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")])code>

confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")])code>

def validate_email(self,field):

email = field.data

user = UserModel.query.filter_by(email=email).first()

if user:

raise ValidationError(message="邮箱已存在")code>

def validate_captcha(self,field):

captcha = field.data

email = self.email.data

cache_captcha = cache.get(email)

if not cache_captcha or captcha != cache_captcha:

raise ValidationError(message="验证码错误!")code>

创建了一个RegisterForm表单类,然后定义了email等5个字段,并分别指定了验证器,其中email验证器必须要安装第三方python库email_validator,在pycharm的terminal中,输入: pip install email_validator,进行安装。

考虑到以后在视图函数中的表单验证失败后,需要吧错误信息传递到模版中,定一个父类,用于从form.errors中提取所有字符串类型的错误信息。

在根目录下的forms文件下新建一个baseform.py文件。然后输入一下代码:

from wtforms import Form

class BaseForm(Form):

@property

def message(self):

message_list = []

if self.errors:

for errors in self.errors.values():

message_list.extend(errors)

return message_list

将RegisterFrom的继承关系修改如霞:

from wtforms import Form, StringField, ValidationError

from wtforms.validators import Email, EqualTo, Length

from exts import cache

from models.user import UserModel

from .baseform import BaseForm

class RegisterForm(BaseForm):

email = StringField(validators=[Email(message="请输入正确格式的邮箱!")])code>

captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")])code>

username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")])code>

password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")])code>

confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")])code>

def validate_email(self,field):

email = field.data

user = UserModel.query.filter_by(email=email).first()

if user:

raise ValidationError(message="邮箱已存在")code>

def validate_captcha(self,field):

captcha = field.data

email = self.email.data

cache_captcha = cache.get(email)

if not cache_captcha or captcha != cache_captcha:

raise ValidationError(message="验证码错误!")code>

下面,再把RegisterForm导入blueprints/user.py,完善register视图函数,代码如下:

from flask import Blueprint, render_template, request, current_app, redirect, url_for, flash

import random

# 发送邮箱,引入的2个包

from flask_mail import Message

from exts import mail, cache, db

# 导入工具类

from utils import restful

from forms.user import RegisterForm

from models.user import UserModel

bp = Blueprint("user", __name__, url_prefix="/user")code>

@bp.route("/register/", methods=['GET', 'POST'])

def register():

if request.method == 'GET':

return render_template("front/register.html")

else:

form = RegisterForm(request.form)

if form.validate():

email = form.email.data

username = form.username.data

password = form.password.data

user = UserModel(email=email, username=username, password=password)

db.session.add(user)

db.session.commit()

return redirect(url_for('user.login'))

else:

for message in form.messages:

flash(message)

return redirect(url_for("user.register"))

@bp.route('/login/')

def login():

return "login"

@bp.route('/mail/captcha/')

def mail_catpcha():

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()

表单验证失败的情况下,由于视图函数已经把错误消息添加到flash中,所以模版中可以通过get_flashed_messages获取所有的错误消息。

在templates/front/register.html中的“立即注册”按钮桑拿添加以下代码:

{ % extends 'front/base.html' %}

{ % block title %}

知了课堂注册

{ % endblock %}

{ % block head %}

<link rel="stylesheet" href="{ { url_for('static',filename='front/css/sign.css') }}">code>

<script src="{ { url_for('static',filename='front/js/register.js') }}"></script>code>

{ % endblock %}

{ % block body %}

<h1 class="page-title">注册</h1>code>

<div class="sign-box">code>

<form action="{ { url_for('user.register') }}" method="post" id="register-form">code>

<div class="form-group">code>

<div class="input-group">code>

<input type="text" class="form-control" name="email" placeholder="邮箱">code>

<div class="input-group-append">code>

<button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>code>

</div>

</div>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">code>

</div>

<div class="form-group">code>

<input type="text" class="form-control" name="username" placeholder="用户名">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="password" placeholder="密码">code>

</div>

<div class="form-group">code>

<input type="password" class="form-control" name="confirm_password" placeholder="确认密码">code>

</div>

{ % with messages=get_flashed_messages() %}

{ % if messahes %}

<div class="form-group">code>

<ul>

{ % for message in messages %}

<li class="text-danger">{ { message }}</li>code>

{ % endfor %}

</ul>

</div>

{ % endif %}

{ % endwith %}

<div class="form-group">code>

<button class="btn btn-warning btn-block" id="submit-btn">立即注册</button>code>

</div>

<div class="form-group">code>

<a href="#" class="signup-link">返回登录</a>code>

<a href="#" class="resetpwd-link" style="float: right">找回密码</a>code>

</div>

</form>

</div>

{ % endblock %}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。