说明
基于书籍《Flask Web全栈开发实战》【黄勇·著】第9章项目实战
创建项目
代码环境:pycharm
File->New Project->Flask
安装相应的python 包
# 用于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中创建蓝图对象
from flask import Blueprint bp = Blueprint("cms",__name__,url_prefix="/cms")
在front.py中创建蓝图对象
from flask import Blueprint bp = Blueprint("front",__name__,url_prefix="")
在user.py中创建蓝图对象
from flask import Blueprint bp = Blueprint("user",__name__,url_prefix="/user")
创建了蓝图对象,并指定了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中添加以下代码:
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" 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")
在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模型生成了迁移脚本。
但,此时并没有真正同步到数据库中,还需要执行以下命令才会实现同步到数据库中。
flask db upgrade
创建权限和角色
Flask 项目 如何创建命令
在flask安装时,默认会安装click库
click库,主要作用是用来实现命令,Flask已经针对click库进行了集成,通过app.cli即可访问到click对象。
下面是一个简单的命令测试
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的命令。
针对每个模块分别添加一个权限。
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个角色,分别为:稽查、运营、管理员。
角色名,稽查,权限:帖子、评论
角色名,运营,权限:板块、帖子、评论、前台用户
角色名,管理员,权限:板块、帖子、评论、前台用户、后台用户
创建角色,同创建权限一样操作,下面只给出代码,执行步骤一致
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="负责审核帖子和评论是否合法合规") inspector.permissions = PermissionModel.query.filter( PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all() # 运营 operator = RoleModel(name="运营", desc="负责网站持续正常运营!") 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="负责整个网站所有工作!") 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里
# 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="负责审核帖子和评论是否合法合规") inspector.permissions = PermissionModel.query.filter( PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all() # 运营 operator = RoleModel(name="运营", desc="负责网站持续正常运营!") 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="负责整个网站所有工作!") 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位长度的字符串。
# 安装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") 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") 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文件中,添加以下代码
# 添加测试3个角色的测试用户 def create_test_user(): admin_role = RoleModel.query.filter_by(name="管理员").first() zhangsan = UserModel(username ="张三",email="zhangsan@zlkt.net",password="111111",is_staff=True,role=admin_role) operator_role = RoleModel.query.filter_by(name="运营").first() lisi = UserModel(username ="李四",email="lisi@zlkt.net",password="111111",is_staff=True,role=operator_role) inspector_role = RoleModel.query.filter_by(name="稽查").first() wangwu = UserModel(username="王五", email="wangwu@zlkt.net", password="111111", is_staff=True, role=inspector_role) 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中添加以下命令:
# 创建管理员 @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() 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{% block title %}{% endblock %} {% block head %}{% endblock %}{% block body %}{% endblock %}
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 %} {% endblock %} {% block body %}注册
{% endblock %}
在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")
使用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") @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="我是邮件内容") mail.send(message) return "success"
运行,输入URL:http://127.0.0.1:5000/user/mail/captcha/
上面是测试,下面是针对项目验证码的代码:
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") @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) 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") @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="我是邮件内容") # 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) 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) 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") @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) # 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参考:参考链接
【记录下这里关闭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的文件,代码如下:
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="没有登录!"): return _restful_result(code=HttpCode.unloginerror, message=message, data=None) def permission_error(message="没有权限!"): return _restful_result(code=HttpCode.permissionerror, message=message, data=None) def params_error(message="参数错误!"): return _restful_result(code=HttpCode.paramserror, message=message, data=None) def server_error(message="服务器错误!"): 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标签下添加以下代码:
使用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标签里,这样后面所有的页面都能使用这个文件里。代码如下:
{% block title %}{% endblock %} {% block head %}{% endblock %}{% block body %}{% endblock %}
因为zlajax.js依赖JQuery,所以必须把zlajax.js放到jQuery文件后面。 在static/front/js下创建1个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函数。
由于js文件必须要加载到模版中才能生效,所以,打开templates/front/register.html文件,将head这个block的中代码修改如下:
{% extends 'front/base.html' %} {% block title %} 知了课堂注册 {% endblock %} {% block head %} {% endblock %} {% block body %}注册
{% endblock %}
实现注册功能
首先,在templates/front/register.html中的form标签上添加action和method属性。代码如下:
{% extends 'front/base.html' %} {% block title %} 知了课堂注册 {% endblock %} {% block head %} {% endblock %} {% block body %}注册
{% 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="请输入正确格式的邮箱!")]) captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")]) username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")]) password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")]) confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")]) def validate_email(self,field): email = field.data user = UserModel.query.filter_by(email=email).first() if user: raise ValidationError(message="邮箱已存在") 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="验证码错误!")
创建了一个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="请输入正确格式的邮箱!")]) captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")]) username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")]) password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")]) confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")]) def validate_email(self,field): email = field.data user = UserModel.query.filter_by(email=email).first() if user: raise ValidationError(message="邮箱已存在") 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="验证码错误!")
下面,再把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") @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 %} {% endblock %} {% block body %}注册
{% endblock %}
还没有评论,来说两句吧...