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

勿忘初心做自己  金牌会员 | 2024-7-31 18:23:18 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 956|帖子 956|积分 2868

说明

基于册本《Flask Web全栈开发实战》【黄勇·著】第9章项目实战
创建项目

代码环境:pycharm
File->New Project->Flask

安装相应的python 包
  1. # 用于flask在使用ORM模型操作数据库
  2. pip install flask-sqlalchemy
  3. # Python操作数据库的驱动程序
  4. pip install pymysql
  5. # 对密码加密和解密
  6. pip install cryptography
  7. # 用于将ORM模型的变更同步到数据库中
  8. pip install flask-migrate
复制代码
config.py文件

在根目录下,常见一个config.py的python文件,用来存放设置项。
  1. class BaseConfig:
  2.     SECRET_KEY = 'linql_test'
  3.     SQLALCHEMY_TRACK_MODIFICATIONS = False
  4. # 开发环境
  5. class DevelopmentConfig(BaseConfig):
  6.     # 配置连接数据库
  7.     HOSTNAME = '192.168.3.5' #服务器地址
  8.     PORT = 3306 #默认端口号
  9.     USERNAME = 'root'
  10.     PASSWORD = 'root'
  11.     DATABASE = 'pythonbbs' #数据库名
  12.     SQLALCHEMY_DATABASE_URI =  f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
  13.    
  14. #  测试环境
  15. class TestingConfig(BaseConfig):
  16.     # 配置连接数据库
  17.     HOSTNAME = '192.168.3.5'  # 服务器地址
  18.     PORT = 3306  # 默认端口号
  19.     USERNAME = 'root'
  20.     PASSWORD = 'root'
  21.     DATABASE = 'pythonbbs'  # 数据库名
  22.     SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
  23. # 生产部署环境
  24. class ProductionConfig(BaseConfig):
  25.     # 配置连接数据库
  26.     HOSTNAME = '192.168.3.5'  # 服务器地址
  27.     PORT = 3306  # 默认端口号
  28.     USERNAME = 'root'
  29.     PASSWORD = 'root'
  30.     DATABASE = 'pythonbbs'  # 数据库名
  31.     SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
复制代码
在app.py中,绑定设置
  1. from flask import Flask
  2. import config
  3. app = Flask(__name__)
  4. # 引入开发环境
  5. app.config.from_object(config.DevelopmentConfig)
  6. @app.route('/')
  7. def hello_world():
  8.     return 'Hello World!'
  9. if __name__ == '__main__':
  10.     app.run()
复制代码
exts.py文件

在根目录里创建exts.py文件,主要用来存放一些第三方插件的对象。
如:SQLAlchemy对象、Flask-Mail对象等。
目标,是为了防止循环引用。
  1. from flask_sqlalchemy import SQLAlchemy
  2. db = SQLAlchemy()
复制代码
回到app.py中,然后导入db变量,再通过db.init_app(app)完成初始化。
  1. from flask import Flask
  2. import config
  3. from exts import db
  4. app = Flask(__name__)
  5. # 引入开发环境
  6. app.config.from_object(config.DevelopmentConfig)
  7. # 初始化db
  8. db.init_app(app)
  9. @app.route('/')
  10. def hello_world():
  11.     return 'Hello World!'
  12. if __name__ == '__main__':
  13.     app.run()
复制代码
blueprints模块

通过蓝图来模块化,创建一个blueprints的包,用于存放蓝图模块。
在项目名称上右击,New->ython Package
并在,blueprints下,分别创建名为:cms、front和user的python文件

在cms.py中创建蓝图对象
  1. from flask import Blueprint
  2. bp = Blueprint("cms",__name__,url_prefix="/cms")
复制代码
在front.py中创建蓝图对象
  1. from flask import Blueprint
  2. bp = Blueprint("front",__name__,url_prefix="")
复制代码
在user.py中创建蓝图对象
  1. from flask import Blueprint
  2. bp = Blueprint("user",__name__,url_prefix="/user")
复制代码
创建了蓝图对象,并指定了url前缀,因front是面向前台的,以是url为空
创建蓝图对象后,还需要在app.py中完成注册。
  1. from flask import Flask
  2. import config
  3. from exts import db
  4. from blueprints.cms import bp as cms_bp
  5. from blueprints.front import bp as front_bp
  6. from blueprints.user import bp as user_bp
  7. app = Flask(__name__)
  8. # 引入开发环境
  9. app.config.from_object(config.DevelopmentConfig)
  10. # 初始化db
  11. db.init_app(app)
  12. # 注册蓝图
  13. app.register_blueprint(cms_bp)
  14. app.register_blueprint(front_bp)
  15. app.register_blueprint(user_bp)
  16. @app.route('/')
  17. def hello_world():
  18.     return 'Hello World!'
  19. if __name__ == '__main__':
  20.     app.run()
复制代码
models模块

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

创建用户相关模子

创建权限和角色模子

用户系统最核心的部门就是用户相关的ORM模子。
该系统的前台和后台用的是同一个用户系统,而后台系统中需要角色和权限管理。
首先,来添加权限ORM模子,在models/user.py中添加以下代码:
  1. from exts import db
  2. from datetime import datetime
  3. from enum import Enum
  4. # PermissionEnum 枚举类型
  5. # 存放权限类型的枚举
  6. class PermissionEnum(Enum):
  7.     BOARD = "板块"
  8.     POST = "帖子"
  9.     COMMENT = "评论"
  10.     FRONT_USER = "前台用户"
  11.     CMS_USER = "后台用户"
  12. # PermissionModel模型
  13. # name 从PermissionEnum 枚举取
  14. class PermissionModel(db.Model):
  15.     __tablename__="permission"
  16.     id = db.Column(db.Integer,primary_key=True,autoincrement=True)
  17.     name = db.Column(db.Enum(PermissionEnum),nullable=False,unique=True)
  18. # 中间表
  19. # role_id来引用role表
  20. # permission_id来引用permission表
  21. role_permission_table=db.Table(
  22.     "role_permission_table",
  23.     db.Column("role_id",db.Integer,db.ForeignKey("role.id")),
  24.     db.Column("permission_id",db.Integer,db.ForeignKey("permission.id"))
  25. )
  26. class RoleModel(db.Model):
  27.     __tablename__="role"
  28.     # 主键id
  29.     id = db.Column(db.Integer,primary_key=True,autoincrement=True)
  30.     # 角色名称
  31.     name = db.Column(db.String(100),nullable=False)
  32.     # 角色描述
  33.     desc = db.Column(db.String(100),nullable=False)
  34.     # 创建时间
  35.     create_time = db.Column(db.Datetime,default=datetime.now)
  36.     # 关系属性(RoleModel和PermissionModel属于多对多关系)
  37.     # role_permission_table 中间表
  38.     permissions = db.relationship("PermissionModel",secondary=role_permission_table,backref="roles")
复制代码
在PermissionModel和RoleModel创建完成后,将模子映射到数据库中,需要借助Flask-Migrate插件
在app.py中,创建Migrate对象
  1. from flask import Flask
  2. import config
  3. from exts import db
  4. from blueprints.cms import bp as cms_bp
  5. from blueprints.front import bp as front_bp
  6. from blueprints.user import bp as user_bp
  7. from flask_migrate import Migrate
  8. app = Flask(__name__)
  9. # 引入开发环境
  10. app.config.from_object(config.DevelopmentConfig)
  11. # 初始化db
  12. db.init_app(app)
  13. # 注册蓝图
  14. app.register_blueprint(cms_bp)
  15. app.register_blueprint(front_bp)
  16. app.register_blueprint(user_bp)
  17. # 创建Migrate对象
  18. migrate = Migrate(app,db)
  19. @app.route('/')
  20. def hello_world():
  21.     return 'Hello World!'
  22. if __name__ == '__main__':
  23.     app.run()
复制代码
为了迁移时能让步伐辨认到models/user.py中的ORM模子,需要导入
  1. from models import user
复制代码
  1. ---------------------------------------------------------------------------ModuleNotFoundError                       Traceback (most recent call last)Cell In[5], line 1----> 1 from models import user
  2. ModuleNotFoundError: No module named 'models'
复制代码
在pyCharm的Terminal中实行以下下令,以生成迁移脚本,并完成迁移脚本的实行。
  1. (pythonWeb) linql@localhost flaskDemo1 % flask db migrate -m "create permission and role model"
  2. Error: Path doesn't exist: '/Users/linql/Desktop/code_Deeplearn/flaskDemo1/migrations'.  Please use the 'init' command to create a new scripts folder.
复制代码
上面运行,会报错。提示需要先“init”
  1. # 先执行
  2. flask db init
  3. # 再执行
  4. flask db migrate -m "create permission and role model"
复制代码

实行以上下令后,已经为ORM模子生成了迁移脚本。
但,此时并没有真正同步到数据库中,还需要实行以下下令才会实现同步到数据库中。
  1. flask db upgrade
复制代码


创建权限和角色

Flask 项目 如何创建下令

在flask安装时,默认会安装click库
click库,主要作用是用来实现下令,Flask已经针对click库进行了集成,通过app.cli即可访问到click对象。
下面是一个简单的下令测试
  1. from flask import Flaskimport configfrom exts import dbfrom blueprints.cms import bp as cms_bpfrom blueprints.front import bp as front_bpfrom blueprints.user import bp as user_bpfrom flask_migrate import Migratefrom models import user
  2. import clickapp = Flask(__name__)# 引入开发环境app.config.from_object(config.DevelopmentConfig)# 初始化dbdb.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中输入以下下令。
  1. flask my-command
复制代码

在app.py中,实现一个名叫create-permission的下令。
针对每个模块分别添加一个权限。
  1. from flask import Flask  
  2. import config
  3. from exts import db
  4. from blueprints.cms import bp as cms_bp
  5. from blueprints.front import bp as front_bp
  6. from blueprints.user import bp as user_bp
  7. from flask_migrate import Migrate
  8. from models.user import PermissionEnum,PermissionModel,RoleModel
  9. import click
  10. app = Flask(__name__)
  11. # 引入开发环境
  12. app.config.from_object(config.DevelopmentConfig)
  13. # 初始化db
  14. db.init_app(app)
  15. # 注册蓝图
  16. app.register_blueprint(cms_bp)
  17. app.register_blueprint(front_bp)
  18. app.register_blueprint(user_bp)
  19. # 创建Migrate对象
  20. migrate = Migrate(app,db)
  21. @app.route('/')
  22. def hello_world():
  23.     return 'Hello World!'
  24. # 测试命令
  25. @app.cli.command("my-command")
  26. def my_command():
  27.     click.echo("这是我的自定义命令!")
  28. # 创建create-permission命令
  29. @app.cli.command("create-permission")
  30. def create_permission():
  31.     for permission_name in dir(PermissionEnum):
  32.         if permission_name.startswith("__"):
  33.             continue
  34.         permission = PermissionModel(name=getattr(PermissionEnum,permission_name))
  35.         db.session.add(permission)
  36.     db.session.commit()
  37.     click.echo("权限添加成功")
  38. if __name__ == '__main__':
  39.     app.run()
复制代码
然后,再在pycharm的terminal中输入以下下令。
  1. flask create-permission
复制代码


权限创建乐成后,再添加角色。
创建3个角色,分别为:稽查、运营、管理员。
角色名,稽查,权限:帖子、评论
角色名,运营,权限:板块、帖子、评论、前台用户
角色名,管理员,权限:板块、帖子、评论、前台用户、后台用户
创建角色,同创建权限一样操纵,下面只给出代码,实行步骤划一
  1. from flask import Flask
  2. import config
  3. from exts import db
  4. from blueprints.cms import bp as cms_bp
  5. from blueprints.front import bp as front_bp
  6. from blueprints.user import bp as user_bp
  7. from flask_migrate import Migrate
  8. from models.user import PermissionEnum, PermissionModel, RoleModel
  9. import click
  10. app = Flask(__name__)
  11. # 引入开发环境
  12. app.config.from_object(config.DevelopmentConfig)
  13. # 初始化db
  14. db.init_app(app)
  15. # 注册蓝图
  16. app.register_blueprint(cms_bp)
  17. app.register_blueprint(front_bp)
  18. app.register_blueprint(user_bp)
  19. # 创建Migrate对象
  20. migrate = Migrate(app, db)
  21. @app.route('/')
  22. def hello_world():
  23.     return 'Hello World!'
  24. # 测试命令
  25. @app.cli.command("my-command")
  26. def my_command():
  27.     click.echo("这是我的自定义命令!")
  28. # 创建添加权限的命令create-permission
  29. @app.cli.command("create-permission")
  30. def create_permission():
  31.     for permission_name in dir(PermissionEnum):
  32.         if permission_name.startswith("__"):
  33.             continue
  34.         permission = PermissionModel(name=getattr(PermissionEnum, permission_name))
  35.         db.session.add(permission)
  36.     db.session.commit()
  37.     click.echo("权限添加成功")
  38. # 创建添加角色的命令create-role
  39. @app.cli.command("create-role")
  40. def create_role():
  41.     # 稽查
  42.     inspector = RoleModel(name="稽查", desc="负责审核帖子和评论是否合法合规")
  43.     inspector.permissions = PermissionModel.query.filter(
  44.         PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all()
  45.     # 运营
  46.     operator = RoleModel(name="运营", desc="负责网站持续正常运营!")
  47.     operator.permissions = PermissionModel.query.filter(PermissionModel.name.in_(
  48.         [PermissionEnum.POST, PermissionEnum.COMMENT, PermissionEnum.BOARD, PermissionEnum.FRONT_USER,
  49.          PermissionEnum.CMS_USER])).all()
  50.     # 管理员
  51.     administrator = RoleModel(name="管理员", desc="负责整个网站所有工作!")
  52.     administrator.permissions = PermissionModel.query.all()
  53.     db.session.add_all([inspector,operator,administrator])
  54.     db.session.commit()
  55.     click.echo("角色创建成功!")
  56. if __name__ == '__main__':
  57.     app.run()
复制代码
  1. # 在pycharm中的terminal中运行
  2. flask create-role
复制代码

对app.py进行瘦身,将command部门的代码放到commands.py里
  1. # commands.py
  2. from models.user import PermissionEnum, PermissionModel, RoleModel
  3. import click
  4. from exts import db
  5. # 测试命令
  6. # @app.cli.command("my-command")
  7. def my_command():
  8.     click.echo("这是我的自定义命令!")
  9. # 创建添加权限的命令create-permission
  10. # @app.cli.command("create-permission")
  11. def create_permission():
  12.     for permission_name in dir(PermissionEnum):
  13.         if permission_name.startswith("__"):
  14.             continue
  15.         permission = PermissionModel(name=getattr(PermissionEnum, permission_name))
  16.         db.session.add(permission)
  17.     db.session.commit()
  18.     click.echo("权限添加成功")
  19. # 创建添加角色的命令create-role
  20. # @app.cli.command("create-role")
  21. def create_role():
  22.     # 稽查
  23.     inspector = RoleModel(name="稽查", desc="负责审核帖子和评论是否合法合规")
  24.     inspector.permissions = PermissionModel.query.filter(
  25.         PermissionModel.name.in_([PermissionEnum.POST, PermissionEnum.COMMENT])).all()
  26.     # 运营
  27.     operator = RoleModel(name="运营", desc="负责网站持续正常运营!")
  28.     operator.permissions = PermissionModel.query.filter(PermissionModel.name.in_(
  29.         [PermissionEnum.POST, PermissionEnum.COMMENT, PermissionEnum.BOARD, PermissionEnum.FRONT_USER,
  30.          PermissionEnum.CMS_USER])).all()
  31.     # 管理员
  32.     administrator = RoleModel(name="管理员", desc="负责整个网站所有工作!")
  33.     administrator.permissions = PermissionModel.query.all()
  34.     db.session.add_all([inspector,operator,administrator])
  35.     db.session.commit()
  36.     click.echo("角色创建成功!")
复制代码
  1. # app.py文件
  2. from flask import Flask
  3. import config
  4. from exts import db
  5. from blueprints.cms import bp as cms_bp
  6. from blueprints.front import bp as front_bp
  7. from blueprints.user import bp as user_bp
  8. from flask_migrate import Migrate
  9. # 引入了命令
  10. import commands
  11. app = Flask(__name__)
  12. # 引入开发环境
  13. app.config.from_object(config.DevelopmentConfig)
  14. # 初始化db
  15. db.init_app(app)
  16. # 注册蓝图
  17. app.register_blueprint(cms_bp)
  18. app.register_blueprint(front_bp)
  19. app.register_blueprint(user_bp)
  20. # 创建Migrate对象
  21. migrate = Migrate(app, db)
  22. # 添加命令
  23. app.cli.command("create-permission")(commands.create_permission)
  24. app.cli.command("create-role")(commands.create_role)
  25. app.cli.command("my-command")(commands.my_command) #瘦身后,再次测试
  26. @app.route('/')
  27. def hello_world():
  28.     return 'Hello World!'
  29. if __name__ == '__main__':
  30.     app.run()
复制代码

创建用户模子

UUID(universally unique identfier)
UUID的长度为32字符,再加上4个横线,总共有36个字符。
引入python第三方库,shortuuid
shortuuid 会对原始UUID进行base57 编码,然后删除相似的字符,如:I,1,L,o和0,末了生成默认22位长度的字符串。
  1. # 安装shortuuid库
  2. pip install shortuuid
复制代码
重构UserModel
  1. from exts import db
  2. from datetime import datetime
  3. from enum import Enum
  4. # 引入shortuuid
  5. from shortuuid import uuid
  6. # 引入加密和验证
  7. from werkzeug.security import generate_password_hash,check_password_hash
  8. # PermissionEnum 枚举类型
  9. # 存放权限类型的枚举
  10. class PermissionEnum(Enum):
  11.     BOARD = "板块"
  12.     POST = "帖子"
  13.     COMMENT = "评论"
  14.     FRONT_USER = "前台用户"
  15.     CMS_USER = "后台用户"
  16. # PermissionModel模型
  17. # name 从PermissionEnum 枚举取
  18. class PermissionModel(db.Model):
  19.     __tablename__ = "permission"
  20.     id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  21.     name = db.Column(db.Enum(PermissionEnum), nullable=False, unique=True)
  22. # 中间表
  23. # role_id来引用role表
  24. # permission_id来引用permission表
  25. role_permission_table = db.Table(
  26.     "role_permission_table",
  27.     db.Column("role_id", db.Integer, db.ForeignKey("role.id")),
  28.     db.Column("permission_id", db.Integer, db.ForeignKey("permission.id"))
  29. )
  30. class RoleModel(db.Model):
  31.     __tablename__ = "role"
  32.     # 主键id
  33.     id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  34.     # 角色名称
  35.     name = db.Column(db.String(100), nullable=False)
  36.     # 角色描述
  37.     desc = db.Column(db.String(100), nullable=False)
  38.     # 创建时间
  39.     create_time = db.Column(db.DateTime, default=datetime.now)
  40.     # 关系属性(RoleModel和PermissionModel属于多对多关系)
  41.     # role_permission_table 中间表
  42.     permissions = db.relationship("PermissionModel", secondary=role_permission_table, backref="roles")
  43. class UserModel(db.Model):
  44.     __tablename__ = "user"
  45.     # 主键
  46.     id = db.Column(db.String(100), primary_key=True, default=uuid)
  47.     # 用户名,不能为空,且值唯一
  48.     username = db.Column(db.String(50), nullable=False, unique=True)
  49.     # 密码,最大长度200,应该先加密再存储
  50.     _password = db.Column(db.String(200), nullable=False)
  51.     # 邮箱,不能为空,且值唯一
  52.     email = db.Column(db.String(50), nullable=False, unique=True)
  53.     # 头像,存储图片在服务器中保存的路径,可以为空
  54.     avatar = db.Column(db.String(100))
  55.     # 签名,可以为空
  56.     signature = db.Column(db.String(100))
  57.     # 加入时间,第一次存储,默认当前时间
  58.     join_time = db.Column(db.DateTime, default=datetime.now)
  59.     # 是否员工,只有员工才能进入后台系统,默认为False
  60.     is_staff = db.Column(db.Boolean, default=False)
  61.     # 是否可用,默认情况下是可用
  62.     is_active = db.Column(db.Boolean, default=True)
  63.     is_admin = db.Column(db.Boolean,default=False)
  64.     # 外键
  65.     # 角色外键,引用role表的id字段
  66.     role_id = db.Column(db.Integer,db.ForeignKey("role.id"))
  67.     # 关系属性,引用RoleModel
  68.     role = db.relationship("RoleModel",backref="users")
  69.     def __init__(self,*args,**kwargs):
  70.         if "password" in kwargs:
  71.             self.password = kwargs.get("password")
  72.             kwargs.pop("password")
  73.         super(UserModel,self).__init__(*args,**kwargs)
  74.     @property
  75.     def password(self):
  76.         return self._password
  77.     @password.setter
  78.     def password(self,raw_password):
  79.         self._password=generate_password_hash(raw_password)
  80.     def check_password(self,raw_password):
  81.         result = check_password_hash(self.password,raw_password)
  82.         return result
  83.     def has_permission(self,permission):
  84.         return permission in [permission.name for permisson in self.role.permissions]
复制代码
  1. # 在terminal中运行
  2. flask db migrate -m "create user model"
  3. flask db upgrade
复制代码


创建测试用户

为了方便后续开发,按照角色个数创建3个员工账户。
在commands.py文件中,添加以下代码
  1. #     添加测试3个角色的测试用户
  2. def create_test_user():
  3.     admin_role = RoleModel.query.filter_by(name="管理员").first()
  4.     zhangsan = UserModel(username ="张三",email="zhangsan@zlkt.net",password="111111",is_staff=True,role=admin_role)
  5.     operator_role = RoleModel.query.filter_by(name="运营").first()
  6.     lisi = UserModel(username ="李四",email="lisi@zlkt.net",password="111111",is_staff=True,role=operator_role)
  7.     inspector_role = RoleModel.query.filter_by(name="稽查").first()
  8.     wangwu = UserModel(username="王五", email="wangwu@zlkt.net", password="111111", is_staff=True, role=inspector_role)
  9.     db.session.add_all([zhangsan,lisi,wangwu])
  10.     db.session.commit()
  11.     click.echo("测试用户添加成功!")
复制代码
在app.py中添加,以下代码:
  1. # 添加命令(增加3个角色测试用户)
  2. app.cli.command("create-test-user")(commands.create_test_user)
复制代码
在pycharm的terminal中,运行下令
  1. flask create-test-user
复制代码

创建管理员

在commands.py中添加以下下令:
  1. # 创建管理员
  2. @click.option("--username",'-u')
  3. @click.option("--email",'-e')
  4. @click.option("--password",'-p')
  5. def create_admin(username,email,password):     
  6.     admin_role = RoleModel.query.filter_by(name="管理员").first()
  7.     admin_user = UserModel(username=username,email=email,password=password,is_staff=True,role = admin_role)
  8.     db.session.add(admin_user)
  9.     db.session.commit()
  10.     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
  1. # base.html
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5.     <meta charset="UTF-8">
  6.     <scrip src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></scrip>
  7.     <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">
  8.     <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.css"></script>
  9.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/base.css') }}">
  10.     <title>{% block title %}{% endblock %}</title>
  11.     {% block head %}{% endblock %}
  12. </head>
  13. <body>
  14. <nav class="navbar navbar-expand-lg navbar-light bg-light">
  15.     <a href="#" class="navbar-brand">知了Python论坛</a>
  16.     <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
  17.             aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  18.         <span class="navbar-toggle-icon"></span>
  19.     </button>
  20.     <div class="collapse navbar-collapse" id="navbarSupportedContent">
  21.         <ul class="navbar-nav mr-auto">
  22.             <li class="nav-item active">
  23.                 <a href="/" class="nav-link">首页 <span class="sr-only">(current)</span></a>
  24.             </li>
  25.         </ul>
  26.         <form class="form-inline my-lg-0">
  27.             <input class="form-control mr-sm-2" type="search" placeholder="请输入关键字" aria-label="Search">
  28.             <button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
  29.         </form>
  30.         <ul class="navbar-nav ml-4">
  31.             <li class="nav-item">
  32.                 <a href="#" class="nav-link">登录</a>
  33.             </li>
  34.             <li class="nav-link">
  35.                 <a href="#" class="nav-link">注册</a>
  36.             </li>
  37.         </ul>
  38.     </div>
  39. </nav>
  40. <div class="main-container">
  41.     {% block body %}{% endblock %}
  42. </div>
  43. </body>
  44. </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来实现。
  1. # register.html
  2. {% extends 'front/base.html' %}
  3. {% block title %}
  4.     知了课堂注册
  5. {% endblock %}
  6. {% block head %}
  7.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/sign.css') }}">
  8. {% endblock %}
  9. {% block body %}
  10.     <h1 class="page-title">注册</h1>
  11.     <div class="sign-box">
  12.         <form action="" id="register-form">
  13.             <div class="form-group">
  14.                 <div class="input-group">
  15.                     <input type="text" class="form-control" name="email" placeholder="邮箱">
  16.                     <div class="input-group-append">
  17.                         <button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>
  18.                     </div>
  19.                 </div>
  20.             </div>
  21.             <div class="form-group">
  22.                 <input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
  23.             </div>
  24.             <div class="form-group">
  25.                 <input type="text" class="form-control" name="username" placeholder="用户名">
  26.             </div>
  27.             <div class="form-group">
  28.                 <input type="password" class="form-control" name="password" placeholder="密码">
  29.             </div>
  30.             <div class="form-group">
  31.                 <input type="password" class="form-control" name="confirm_password" placeholder="确认密码">
  32.             </div>
  33.             <div class="form-group">
  34.                 <a href="#" class="signup-link">返回登录</a>
  35.                 <a href="#" class="resetpwd-link" style="float: right">找回密码</a>
  36.             </div>
  37.         </form>
  38.     </div>
  39. {% endblock %}
复制代码
在blueprints/user.py中,添加以下代码:
  1. from flask import Blueprint,render_template
  2. bp = Blueprint("user",__name__,url_prefix="/user")
  3. @bp.route("/register/")
  4. def register():
  5.     return render_template("front/register.html")
复制代码
使用Flask-Mail发送邮箱验证码

安装Flask-Mail

在Pycharm的Terminal,输入并实行:
pip install flask-mail
设置邮箱参数

  1. # 以QQ邮箱为例,其QQ邮箱,POP3/IMAP/SMTP/Exchange/CardDAV授权,请查阅其他资料完成。
复制代码
开启个人邮箱的SMTP服务后,在项目中,打开config.py文件,在DevelopmentConfig中添加以下代码。
  1. # 开发环境
  2. class DevelopmentConfig(BaseConfig):
  3.     # 配置连接数据库
  4.     HOSTNAME = '192.168.3.5'  # 服务器地址
  5.     PORT = 3306  # 默认端口号
  6.     USERNAME = 'root'
  7.     PASSWORD = 'root'
  8.     DATABASE = 'pythonbbs'  # 数据库名
  9.     SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
  10.     # 邮箱配置
  11.     MAIL_SERVER = "smtp.qq.com"
  12.     MAIL_USE_SSL = True
  13.     MAIL_USE_TLS = False
  14.     MAIL_PORT = 465
  15.     MAIL_USERNAME = "**@qq.com" # 发送者邮箱
  16.     MAIL_PASSWORD = "****" # SMTP授权码
  17.     MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱
复制代码
发送邮件

在项目中,打开exts.py中,创建1个mail对象
  1. from flask_sqlalchemy import SQLAlchemy
  2. from flask_mail import Mail
  3. db = SQLAlchemy()
  4. # 创建1个mail对象
  5. mail = Mail()
复制代码
回到,app.py文件,从exts.py中导入mail变量,并进行初始化。
  1. from flask import Flaskimport configfrom exts import db,mailfrom blueprints.cms import bp as cms_bpfrom blueprints.front import bp as front_bpfrom blueprints.user import bp as user_bpfrom flask_migrate import Migrate# 引入了下令import commandsapp = Flask(__name__)# 引入开发环境app.config.from_object(config.DevelopmentConfig)# 初始化dbdb.init_app(app)# 初始化mailmail.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个角色测试用户)
  2. app.cli.command("create-test-user")(commands.create_test_user)
  3. # 创建管理员下令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中输入以下代码:
  1. from flask import Blueprint, render_template
  2. # 发送邮箱,引入的2个包
  3. from flask_mail import Message
  4. from exts import mail
  5. bp = Blueprint("user", __name__, url_prefix="/user")
  6. @bp.route("/register/")
  7. def register():
  8.     return render_template("front/register.html")
  9. @bp.route('/mail/captcha/')
  10. def mail_captcha():
  11. #     recipients,填写接收者的邮箱地址
  12. # 可以写多个,用,逗号隔开。
  13.     message = Message(subject="我是邮件主题", recipients=['***@outlook.com'], body="我是邮件内容")
  14.     mail.send(message)
  15.     return "success"
复制代码
运行,输入URL:http://127.0.0.1:5000/user/mail/captcha/

上面是测试,下面是针对项目验证码的代码:
  1. from flask import Blueprint, render_template, request
  2. import random
  3. # 发送邮箱,引入的2个包
  4. from flask_mail import Message
  5. from exts import mail
  6. bp = Blueprint("user", __name__, url_prefix="/user")
  7. @bp.route("/register/")
  8. def register():
  9.     return render_template("front/register.html")
  10. @bp.route('/mail/captcha/')
  11. def mail_catpcha():
  12.     email = request.args.get("mail")
  13.     digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  14.     captcha = "".join(random.sample(digits, 4))
  15.     body=f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  16.     message = Message(subject="我是邮箱主题",recipients=[email],body=body)
  17.     mail.send(message)
  18.     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的设置信息,如下所示:
  1. # 开发环境
  2. class DevelopmentConfig(BaseConfig):
  3.     # 配置连接数据库
  4.     HOSTNAME = '192.168.3.5'  # 服务器地址
  5.     PORT = 3306  # 默认端口号
  6.     USERNAME = 'root'
  7.     PASSWORD = 'root'
  8.     DATABASE = 'pythonbbs'  # 数据库名
  9.     SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
  10.     # 邮箱配置
  11.     MAIL_SERVER = "smtp.qq.com"
  12.     MAIL_USE_SSL = True
  13.     MAIL_USE_TLS = False
  14.     MAIL_PORT = 465
  15.     MAIL_USERNAME = "**@qq.com" # 发送者邮箱
  16.     MAIL_PASSWORD = "****" # SMTP授权码
  17.     MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱
  18.         # 缓存设置    CACHE_TYPE = "RedisCache"    CACHE_REDIS_HOST = "127.0.0.1"    CACHE_REDIS_PORT = 6379
复制代码
在项目中,打开exts.py文件,然后输入以下代码:
  1. from flask_sqlalchemy import SQLAlchemy
  2. from flask_mail import Mail
  3. from flask_caching import Cache
  4. db = SQLAlchemy()
  5. # 创建1个mail对象
  6. mail = Mail()
  7. # 创建1个Flask-Caching对象
  8. cache = Cache()
复制代码
再回到app.py文件中,从exts.py文件中导入cache变量,而且进行初始化,代码如下:
  1. from flask import Flaskimport configfrom exts import db,mail,cachefrom blueprints.cms import bp as cms_bpfrom blueprints.front import bp as front_bpfrom blueprints.user import bp as user_bpfrom flask_migrate import Migrate# 引入了下令import commandsapp = Flask(__name__)# 引入开发环境app.config.from_object(config.DevelopmentConfig)# 初始化dbdb.init_app(app)# 初始化mailmail.init_app(app)# 初始化cachecache.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个角色测试用户)
  2. app.cli.command("create-test-user")(commands.create_test_user)
  3. # 创建管理员下令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代码修改如下:
  1. from flask import Blueprint, render_template, request
  2. import random
  3. # 发送邮箱,引入的2个包
  4. from flask_mail import Message
  5. from exts import mail, cache
  6. bp = Blueprint("user", __name__, url_prefix="/user")
  7. @bp.route("/register/")
  8. def register():
  9.     return render_template("front/register.html")
  10. # # 测试使用
  11. # @bp.route('/mail/captcha/')
  12. # def mail_captcha():
  13. #     message = Message(subject="我是邮件主题", recipients=['*****@outlook.com'], body="我是邮件内容")
  14. #     mail.send(message)
  15. #     return "success"
  16. @bp.route('/mail/captcha/')
  17. def mail_catpcha():
  18.     email = request.args.get("mail")
  19.     digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  20.     captcha = "".join(random.sample(digits, 4))
  21.     body = f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  22.     message = Message(subject="我是邮箱主题", recipients=[email], body=body)
  23.     mail.send(message)
  24.     cache.set(email, captcha, timeout=100)
  25.     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设置信息,代码如下:
  1. # 开发环境
  2. class DevelopmentConfig(BaseConfig):
  3.     # 配置连接数据库
  4.     HOSTNAME = '192.168.3.5'  # 服务器地址
  5.     PORT = 3306  # 默认端口号
  6.     USERNAME = 'root'
  7.     PASSWORD = 'root'
  8.     DATABASE = 'pythonbbs'  # 数据库名
  9.     SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
  10.     # 邮箱配置
  11.     MAIL_SERVER = "smtp.qq.com"
  12.     MAIL_USE_SSL = True
  13.     MAIL_USE_TLS = False
  14.     MAIL_PORT = 465
  15.     MAIL_USERNAME = "**@qq.com" # 发送者邮箱
  16.     MAIL_PASSWORD = "****" # SMTP授权码
  17.     MAIL_DEFAULT_SENDER = "**@qq.com" #默认发送邮箱
  18.         # 缓存设置    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文件,然后输入以下代码:
  1. from flask_mail import Message
  2. from exts import mail
  3. from celery import Celery
  4. # 定义任务函数
  5. def send_mail(recipient, subject, body):
  6.     message = Message(subject=subject, recipients=[recipient], body=body)
  7.     mail.send(message)
  8.     print("发送成功")
  9. # 创建Celery对象
  10. def make_celery(app):
  11.     celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],
  12.                     broker=app.config['CELERY_BROKER_URL'])
  13.     TaskBase = celery.Task
  14.     class ContextTask(TaskBase):
  15.         abstract = True
  16.         def __call__(self, *args, **kwargs):
  17.             with app.app_context():
  18.                 return TaskBase.__call__(self, *args, **kwargs)
  19.     celery.Task = ContextTask
  20.     app.celery = celery
  21.     # 添加任务
  22.     celery.task(name="send_mail")(send_mail)
  23.     return celery
复制代码
在app.py中导入make_celery函数,并创建1个Celery对象
  1. from flask import Flaskimport configfrom exts import db,mail,cachefrom blueprints.cms import bp as cms_bpfrom blueprints.front import bp as front_bpfrom blueprints.user import bp as user_bpfrom flask_migrate import Migrate# 引入了下令import commands# 导入make_celery函数,并创建1个Celery对象from bbs_celery import make_celeryapp = Flask(__name__)# 引入开发环境app.config.from_object(config.DevelopmentConfig)# 初始化dbdb.init_app(app)# 初始化mailmail.init_app(app)# 初始化cachecache.init_app(app)# 构建celerycelery = 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个角色测试用户)
  2. app.cli.command("create-test-user")(commands.create_test_user)
  3. # 创建管理员下令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任务的方式发送。
  1. from flask import Blueprint, render_template, request, current_app
  2. import random
  3. # 发送邮箱,引入的2个包
  4. from flask_mail import Message
  5. from exts import mail, cache
  6. bp = Blueprint("user", __name__, url_prefix="/user")
  7. @bp.route("/register/")
  8. def register():
  9.     return render_template("front/register.html")
  10. # @bp.route('/mail/captcha/')
  11. # def mail_catpcha():
  12. #     email = request.args.get("mail")
  13. #     digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  14. #     captcha = "".join(random.sample(digits, 4))
  15. #     body = f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  16. #     message = Message(subject="我是邮箱主题", recipients=[email], body=body)
  17. #     mail.send(message)
  18. #     cache.set(email, captcha, timeout=100)
  19. #     return "Succes"
  20. @bp.route('/mail/captcha/')
  21. def mail_catpcha():
  22.     email = request.args.get("mail")
  23.     digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  24.     captcha = "".join(random.sample(digits, 4))
  25.     subject = "[知了Python论坛]注册验证码"
  26.     body = f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  27.     current_app.celery.send_task("send_mail", (email, subject, body))
  28.     cache.set(email, captcha, timeout=100)
  29.     return "Succes"
复制代码
运行Celery,需要安装别的一个第三方python库:
pip install gevent
在pycharm的terminal中,输入以下下令,启动cerely:
celery -A app.celery worker -l info


设置Redis

Mac电脑设置Redis参考:参考链接
  1. 【记录下这里关闭Redis报错】Mac上使用Redis无法写入快照或者
  2. 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的文件,代码如下:
  1. from flask import jsonify
  2. class HttpCode(object):
  3.     # 响应正常
  4.     ok = 200
  5.     # 登录错误
  6.     unloginerror = 401
  7.     # 权限错误
  8.     permissionerror = 403
  9.     # 客户端参数错误
  10.     paramserror = 400
  11.     # 服务器错误
  12.     servererror = 500
  13. def _restful_result(code, message, data):
  14.     return jsonify(({"message": message or "", "data": data or {}})), code
  15. def ok(message=None, data=None):
  16.     return _restful_result(code=HttpCode.ok, message=message, data=data)
  17. def unlogin_error(message="没有登录!"):
  18.     return _restful_result(code=HttpCode.unloginerror, message=message, data=None)
  19. def permission_error(message="没有权限!"):
  20.     return _restful_result(code=HttpCode.permissionerror, message=message, data=None)
  21. def params_error(message="参数错误!"):
  22.     return _restful_result(code=HttpCode.paramserror, message=message, data=None)
  23. def server_error(message="服务器错误!"):
  24.     return _restful_result(code=HttpCode.servererror, message=message or "服务器内部错误", data=None)
复制代码
将blueprints/user.py中的email_captcha的代码修改如下:
  1. @bp.route('/mail/captcha/')
  2. def mail_catpcha():
  3.     try:
  4.         email = request.args.get("mail")
  5.         digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  6.         captcha = "".join(random.sample(digits, 4))
  7.         subject = "[知了Python论坛]注册验证码"
  8.         body = f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  9.         current_app.celery.send_task("send_mail", (email, subject, body))
  10.         cache.set(email, captcha, timeout=100)
  11.         return restful.ok()
  12.     except Exception as e:
  13.         print(e)
  14.         return restful.server_error()
复制代码
CSRF保护

开启CSRF保护需要使用flask-wtf中的CSRFProtect
首先,在pycharm的Terminal下输入安装flask-wtf下令:
  1. pip install flask-wtf
复制代码
回到app.py中,添加以下代码:
  1. from flask import Flask import configfrom exts import db,mail,cachefrom blueprints.cms import bp as cms_bpfrom blueprints.front import bp as front_bpfrom blueprints.user import bp as user_bpfrom flask_migrate import Migrate# 引入了下令import commands# 导入make_celery函数,并创建1个Celery对象from bbs_celery import make_celery# 导入CSRFfrom flask_wtf import CSRFProtectapp = Flask(__name__)# 引入开发环境app.config.from_object(config.DevelopmentConfig)# 初始化dbdb.init_app(app)# 初始化mailmail.init_app(app)# 初始化cachecache.init_app(app)# 构建celerycelery = 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个角色测试用户)
  2. app.cli.command("create-test-user")(commands.create_test_user)
  3. # 创建管理员下令app.cli.command("create-admin")(commands.create_admin)@app.route('/')def hello_world():    return 'Hello World!'if __name__ == '__main__':    app.run()
复制代码
  1. ---------------------------------------------------------------------------
  2. ModuleNotFoundError                       Traceback (most recent call last)
  3. Cell In[9], line 2
  4.       1 from flask import Flask
  5. ----> 2 import config
  6.       3 from exts import db,mail,cache
  7.       5 from blueprints.cms import bp as cms_bp
  8. ModuleNotFoundError: No module named 'config'
复制代码
在templates/front/register.html的form标签下添加以下代码:
  1. <input type="hidden" name="crsf_token" value="{{ csrf_token() }}">
复制代码
使用AJAX获取邮箱验证码

在static/common下,创建1个名为:zlajax.js文件,如许每次发送非GET请求都不需要手动设置csrf_token了。具体代码如下:
  1. var zlajax = {
  2.     'get': function (args) {
  3.         args['method'] = "get"
  4.         return this.ajax(args);
  5.     },
  6.     'post': function (args) {
  7.         args['method'] = "post"
  8.         return this.ajax(args);
  9.     },
  10.     'put': function (args) {
  11.         args['method'] = "put"
  12.         return this.ajax(args);
  13.     },
  14.     'delete': function (args) {
  15.         args['method'] = "delete"
  16.         return this.ajax(args);
  17.     },
  18.     'ajax': function (args) {
  19.         this._ajaxSetup();
  20.         return $.ajax(args);
  21.     },
  22.     '_ajaxSetup': function () {
  23.         $.ajaxSetup({
  24.                 'beforeSend': function (xhr, settings) {
  25.                     if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
  26.                         var csrftoken = $('meta[name=csrf-token]').attr('content');
  27.                         xhr.setRequestHeader("X-CSRFToken", csrftoken)
  28.                     }
  29.                 }
  30.             }
  31.         );
  32.     }
  33. };
复制代码
将zlajax.js文件放到templates/front/base.html的head标签里,如许后面全部的页面都能使用这个文件里。代码如下:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <scrip src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></scrip>
  6.     <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">
  7.     <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.css"></script>
  8.     <script src="{{ url_for('static',filename='common/zlajax.js') }}"></script>
  9.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/base.css') }}">
  10.     <title>{% block title %}{% endblock %}</title>
  11.     {% block head %}{% endblock %}
  12. </head>
  13. <body>
  14. <nav class="navbar navbar-expand-lg navbar-light bg-light">
  15.     <a href="#" class="navbar-brand">知了Python论坛</a>
  16.     <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
  17.             aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  18.         <span class="navbar-toggle-icon"></span>
  19.     </button>
  20.     <div class="collapse navbar-collapse" id="navbarSupportedContent">
  21.         <ul class="navbar-nav mr-auto">
  22.             <li class="nav-item active">
  23.                 <a href="/" class="nav-link">首页 <span class="sr-only">(current)</span></a>
  24.             </li>
  25.         </ul>
  26.         <form class="form-inline my-lg-0">
  27.             <input class="form-control mr-sm-2" type="search" placeholder="请输入关键字" aria-label="Search">
  28.             <button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
  29.         </form>
  30.         <ul class="navbar-nav ml-4">
  31.             <li class="nav-item">
  32.                 <a href="#" class="nav-link">登录</a>
  33.             </li>
  34.             <li class="nav-link">
  35.                 <a href="#" class="nav-link">注册</a>
  36.             </li>
  37.         </ul>
  38.     </div>
  39. </nav>
  40. <div class="main-container">
  41.     {% block body %}{% endblock %}
  42. </div>
  43. </body>
  44. </html>
复制代码
  1. 因为zlajax.js依赖JQuery,所以必须把zlajax.js放到jQuery文件后面。</br>
  2. 在static/front/js下创建1个register.js文件,用于绑定“发送验证码”按钮的单击时间,并且自信AJAX请求。代码如下:
复制代码
  1. $(function () {
  2.     $('#captcha-btn').on("click", function (event) {
  3.         event.preventDefault();
  4.         //   获取邮箱
  5.         var email = $("input[name='email']").val();
  6.         zlajax.get({
  7.             url: "/user/mail/captcha?mail=" + email
  8.         }).done(function (result) {
  9.             alert("验证码发送成功!");
  10.         }).fail(function (error) {
  11.             alert(error.message);
  12.         })
  13.     });
  14. });
复制代码
上述代码:id为captcha-btn的按钮绑定了单击变乱。
单击按钮后,先是获取用户输入的邮箱,然后通过zlajax.get方法发送请求,URL不需要带域名,向以/开头的URL发送请求,欣赏器会自动使员工当前域名。
如果请求乐成,会实行done函数,如果请求失败,会实行fail函数。
由于js文件必须要加载到模版中才能生效,以是,打开templates/front/register.html文件,将head这个block的中代码修改如下:
  1. {% extends 'front/base.html' %}
  2. {% block title %}
  3.     知了课堂注册
  4. {% endblock %}
  5. {% block head %}
  6.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/sign.css') }}">
  7.     <script src="{{ url_for('static',filename='front/js/register.js') }}"></script>
  8. {% endblock %}
  9. {% block body %}
  10.     <h1 class="page-title">注册</h1>
  11.     <div class="sign-box">
  12.         <form action="" id="register-form">
  13.             <div class="form-group">
  14.                 <div class="input-group">
  15.                     <input type="text" class="form-control" name="email" placeholder="邮箱">
  16.                     <div class="input-group-append">
  17.                         <button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>
  18.                     </div>
  19.                 </div>
  20.             </div>
  21.             <div class="form-group">
  22.                 <input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
  23.             </div>
  24.             <div class="form-group">
  25.                 <input type="text" class="form-control" name="username" placeholder="用户名">
  26.             </div>
  27.             <div class="form-group">
  28.                 <input type="password" class="form-control" name="password" placeholder="密码">
  29.             </div>
  30.             <div class="form-group">
  31.                 <input type="password" class="form-control" name="confirm_password" placeholder="确认密码">
  32.             </div>
  33.             <div class="form-group">
  34.                 <a href="#" class="signup-link">返回登录</a>
  35.                 <a href="#" class="resetpwd-link" style="float: right">找回密码</a>
  36.             </div>
  37.         </form>
  38.     </div>
  39. {% endblock %}
复制代码
实现注册功能

首先,在templates/front/register.html中的form标签上添加action和method属性。代码如下:
  1. {% extends 'front/base.html' %}
  2. {% block title %}
  3.     知了课堂注册
  4. {% endblock %}
  5. {% block head %}
  6.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/sign.css') }}">
  7.     <script src="{{ url_for('static',filename='front/js/register.js') }}"></script>
  8. {% endblock %}
  9. {% block body %}
  10.     <h1 class="page-title">注册</h1>
  11.     <div class="sign-box">
  12.         <form action="{{ url_for('user.register') }}" method="post" id="register-form">
  13.             <div class="form-group">
  14.                 <div class="input-group">
  15.                     <input type="text" class="form-control" name="email" placeholder="邮箱">
  16.                     <div class="input-group-append">
  17.                         <button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>
  18.                     </div>
  19.                 </div>
  20.             </div>
  21.             <div class="form-group">
  22.                 <input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
  23.             </div>
  24.             <div class="form-group">
  25.                 <input type="text" class="form-control" name="username" placeholder="用户名">
  26.             </div>
  27.             <div class="form-group">
  28.                 <input type="password" class="form-control" name="password" placeholder="密码">
  29.             </div>
  30.             <div class="form-group">
  31.                 <input type="password" class="form-control" name="confirm_password" placeholder="确认密码">
  32.             </div>
  33.             <div class="form-group">
  34.                 <a href="#" class="signup-link">返回登录</a>
  35.                 <a href="#" class="resetpwd-link" style="float: right">找回密码</a>
  36.             </div>
  37.         </form>
  38.     </div>
  39. {% endblock %}
复制代码
表单通常用POST方法,把表单数据提交到视图函数后,需要对先对表单数据做验证,在根目录下,插件一个名叫:forms的Python Package,然后插件user.py文件,代码如下:
  1. from wtforms import Form, StringField, ValidationError
  2. from wtforms.validators import Email, EqualTo, Length
  3. from exts import cache
  4. from models.user import UserModel
  5. class RegisterForm(Form):
  6.     email = StringField(validators=[Email(message="请输入正确格式的邮箱!")])
  7.     captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")])
  8.     username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")])
  9.     password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")])
  10.     confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")])
  11.     def validate_email(self,field):
  12.         email = field.data
  13.         user = UserModel.query.filter_by(email=email).first()
  14.         if user:
  15.             raise ValidationError(message="邮箱已存在")
  16.     def validate_captcha(self,field):
  17.         captcha = field.data
  18.         email = self.email.data
  19.         cache_captcha = cache.get(email)
  20.         if not cache_captcha or captcha != cache_captcha:
  21.             raise ValidationError(message="验证码错误!")
复制代码
创建了一个RegisterForm表单类,然后定义了email等5个字段,并分别指定了验证器,其中email验证器必须要安装第三方python库email_validator,在pycharm的terminal中,输入: pip install email_validator,进行安装。
思量到以后在视图函数中的表单验证失败后,需要吧错误信息传递到模版中,定一个父类,用于从form.errors中提取全部字符串类型的错误信息。
在根目录下的forms文件下新建一个baseform.py文件。然后输入一下代码:
  1. from wtforms import Form
  2. class BaseForm(Form):
  3.     @property
  4.     def message(self):
  5.         message_list = []
  6.         if self.errors:
  7.             for errors in self.errors.values():
  8.                 message_list.extend(errors)
  9.         return message_list
复制代码
将RegisterFrom的继承关系修改如霞:
  1. from wtforms import Form, StringField, ValidationError
  2. from wtforms.validators import Email, EqualTo, Length
  3. from exts import cache
  4. from models.user import UserModel
  5. from .baseform import BaseForm
  6. class RegisterForm(BaseForm):
  7.     email = StringField(validators=[Email(message="请输入正确格式的邮箱!")])
  8.     captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确格式的验证码!")])
  9.     username = StringField(validators=[Length(min=2, max=20, message="请输入正确格式的用户名!")])
  10.     password = StringField(validators=[Length(min=6, max=20, message="请输入正确长度的密码!")])
  11.     confirm_password = StringField(validators=[EqualTo("password", message="两次密码不一致!")])
  12.     def validate_email(self,field):
  13.         email = field.data
  14.         user = UserModel.query.filter_by(email=email).first()
  15.         if user:
  16.             raise ValidationError(message="邮箱已存在")
  17.     def validate_captcha(self,field):
  18.         captcha = field.data
  19.         email = self.email.data
  20.         cache_captcha = cache.get(email)
  21.         if not cache_captcha or captcha != cache_captcha:
  22.             raise ValidationError(message="验证码错误!")
复制代码
下面,再把RegisterForm导入blueprints/user.py,美满register视图函数,代码如下:
  1. from flask import Blueprint, render_template, request, current_app, redirect, url_for, flash
  2. import random
  3. # 发送邮箱,引入的2个包
  4. from flask_mail import Message
  5. from exts import mail, cache, db
  6. # 导入工具类
  7. from utils import restful
  8. from forms.user import RegisterForm
  9. from models.user import UserModel
  10. bp = Blueprint("user", __name__, url_prefix="/user")
  11. @bp.route("/register/", methods=['GET', 'POST'])
  12. def register():
  13.     if request.method == 'GET':
  14.         return render_template("front/register.html")
  15.     else:
  16.         form = RegisterForm(request.form)
  17.         if form.validate():
  18.             email = form.email.data
  19.             username = form.username.data
  20.             password = form.password.data
  21.             user = UserModel(email=email, username=username, password=password)
  22.             db.session.add(user)
  23.             db.session.commit()
  24.             return redirect(url_for('user.login'))
  25.         else:
  26.             for message in form.messages:
  27.                 flash(message)
  28.             return redirect(url_for("user.register"))
  29. @bp.route('/login/')
  30. def login():
  31.     return "login"
  32. @bp.route('/mail/captcha/')
  33. def mail_catpcha():
  34.     try:
  35.         email = request.args.get("mail")
  36.         digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
  37.         captcha = "".join(random.sample(digits, 4))
  38.         subject = "[知了Python论坛]注册验证码"
  39.         body = f"[知了Python论坛]您的注册验证码是:{captcha},请勿告诉别人!"
  40.         current_app.celery.send_task("send_mail", (email, subject, body))
  41.         cache.set(email, captcha, timeout=100)
  42.         return restful.ok()
  43.     except Exception as e:
  44.         print(e)
  45.         return restful.server_error()
复制代码
表单验证失败的环境下,由于视图函数已经把错误消息添加到flash中,以是模版中可以通过get_flashed_messages获取全部的错误消息。
在templates/front/register.html中的“立刻注册”按钮桑拿添加以下代码:
  1. {% extends 'front/base.html' %}
  2. {% block title %}
  3.     知了课堂注册
  4. {% endblock %}
  5. {% block head %}
  6.     <link rel="stylesheet" href="{{ url_for('static',filename='front/css/sign.css') }}">
  7.     <script src="{{ url_for('static',filename='front/js/register.js') }}"></script>
  8. {% endblock %}
  9. {% block body %}
  10.     <h1 class="page-title">注册</h1>
  11.     <div class="sign-box">
  12.         <form action="{{ url_for('user.register') }}" method="post" id="register-form">
  13.             <div class="form-group">
  14.                 <div class="input-group">
  15.                     <input type="text" class="form-control" name="email" placeholder="邮箱">
  16.                     <div class="input-group-append">
  17.                         <button id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>
  18.                     </div>
  19.                 </div>
  20.             </div>
  21.             <div class="form-group">
  22.                 <input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
  23.             </div>
  24.             <div class="form-group">
  25.                 <input type="text" class="form-control" name="username" placeholder="用户名">
  26.             </div>
  27.             <div class="form-group">
  28.                 <input type="password" class="form-control" name="password" placeholder="密码">
  29.             </div>
  30.             <div class="form-group">
  31.                 <input type="password" class="form-control" name="confirm_password" placeholder="确认密码">
  32.             </div>
  33.             {% with messages=get_flashed_messages() %}
  34.                 {% if messahes %}
  35.                     <div class="form-group">
  36.                         <ul>
  37.                             {% for message in messages %}
  38.                                 <li class="text-danger">{{ message }}</li>
  39.                             {% endfor %}
  40.                         </ul>
  41.                     </div>
  42.                 {% endif %}
  43.             {% endwith %}
  44.             <div class="form-group">
  45.                 <button class="btn btn-warning btn-block" id="submit-btn">立即注册</button>
  46.             </div>
  47.             <div class="form-group">
  48.                 <a href="#" class="signup-link">返回登录</a>
  49.                 <a href="#" class="resetpwd-link" style="float: right">找回密码</a>
  50.             </div>
  51.         </form>
  52.     </div>
  53. {% endblock %}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表