重定向邮箱修改的url,绑定url和html、样式文件
添加修改邮箱的html页面文件:
修改密码文件:templates/cms/cms_resetemail.html
{% extends 'cms/cms_base.html' %}
{% block title %}
修改邮箱
{% endblock %}
{% block page_title %}
{{self.title()}}
{% endblock %}
{% block head %}
<style>
.form-container{
width:300px;
}
style>
<script src="{{ url_for('static', filename='cms/js/resetemail.js') }}">script>
{% endblock %}
{% block content %}
<form action="" method="post">
<div class="form-container">
<div class="form-group">
<div class="input-group">
<input type="email" class="form-control" name="email" placeholder="新邮箱">
<span class="input-group-addon" id="captcha-btn" style="cursor:pointer;">获取验证码span>
div>
div>
<div class="form-group">
<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
div>
<div class="form-group">
<button class="btn btn-primary" id="submit">立即修改button>
div>
div>
form>
{% endblock %}
修改密码的样式文件:static/cms/js/resetemail.js
var lgajax = {
'get':function(args) {
args['method'] = 'get';
this.ajax(args);
},
'post':function(args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax':function(args) {
// 设置csrftoken
this._ajaxSetup();
$.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)
}
}
});
}
};
$(function () {
$("#captcha-btn").click(function (event) {
event.preventDefault();
var email = $("input[name='email']").val();
if(!email){
lgalert.alertInfoToast('请输入邮箱');
return;
}
var lgajax = {
'get':function(args) {
args['method'] = 'get';
this.ajax(args);
},
'post':function(args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax':function(args) {
// 设置csrftoken
this._ajaxSetup();
$.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)
}
}
});
}
};
lgajax.get({ // 这里ajax的方法是get方法
'url': '/cms/email_captcha/', // 该url需要与视图函数中的路由相同
'data': {
'email': email
},
'success': function (data) {
if(data['code'] == 200){
lgalert.alertSuccessToast('邮件发送成功!请注意查收!');
}else{
lgalert.alertInfo(data['message']);
}
},
'fail': function (error) {
lgalert.alertNetworkError();
}
});
});
});
$(function () {
$("#submit").click(function (event) {
event.preventDefault();
var emailE = $("input[name='email']");
var captchaE = $("input[name='captcha']");
var email = emailE.val();
var captcha = captchaE.val();
lgajax.post({
'url': '/cms/resetemail/',
'data': {
'email': email,
'captcha': captcha
},
'success': function (data) {
if(data['code'] == 200){
emailE.val("");
captchaE.val("");
lgalert.alertSuccessToast('恭喜!邮箱修改成功!');
}else{
lgalert.alertInfo(data['message']);
}
},
'fail': function (error) {
lgalert.alertNetworkError();
}
});
});
});
修改模板文件中的url_for重定向到修改密码的url:
基础模板文件cms_base.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{% block title %}
{% endblock %}title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js">script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js">script>
<link rel="stylesheet" href="{{ url_for('static', filename='cms/css/cms_base.css') }}">
<script src="{{ url_for('static', filename='cms/js/cms_base.js') }}">script>
<link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
<script src="{{ url_for('static', filename='common/sweetalert/lgalert.js') }}">script>
<script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}">script>
{% block head %}
{% endblock %}
head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a class="navbar-brand" href="#">论坛CMS管理系统a>
div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ g.cms_user.username }}<span>[超级管理员]span>a>li>
<li><a href="{{ url_for('cms.logout') }}">注销a>li>
ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="查找...">
form>
div>
div>
nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav-sidebar">
<li class="unfold"><a href="#">首页a>li>
<li class="profile-li">
<a href="#">个人中心<span>span>a>
<ul class="subnav">
<li><a href="{{ url_for('cms.profile') }}">个人信息a>li>
<li><a href="{{ url_for('cms.resetpwd') }}">修改密码a>li>
<li><a href="{{ url_for('cms.resetemail') }}">修改邮箱a>li>
ul>
li>
<li class="nav-group post-manage"><a href="#">帖子管理a>li>
<li class="comments-manage"><a href="#">评论管理a>li>
<li class="board-manage"><a href="#">板块管理a>li>
<li class="nav-group user-manage"><a href="#">用户管理a>li>
<li class="role-manage"><a href="#">组管理a>li>
<li class="nav-group cmsuser-manage"><a href="#">CMS用户管理a>li>
<li class="cmsrole-manage"><a href="#">CMS组管理a>li>
ul>
div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1>{% block page_title %}
{% endblock %}h1>
<div class="main_content">
{% block content %}
{% endblock %}
div>
div>
div>
div>
body>
html>
通过视图文件apps/cms/views.py文件定义修改邮箱的类视图函数进行验证
视图文件apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
"""
# 蓝图文件:实现模块化应用,应用可以分解成一系列的蓝图 后端的类视图函数写在这个文件
from flask import (
request, redirect, url_for, # 页面跳转redirect request请求收集
Blueprint, render_template, views, session, # 定义类视图,显示模板文件
jsonify, g # jsonify强制转换成json数据
)
from exts import db # 数据库中更新密码、邮箱等使用
# 导入form表单 .forms代表同级目录下的forms.py ResetPwdForm修改密码的form信息
from .forms import LoginForm, ResetPwdForm
# 导入模型 .models代表同级目录下的models.py
from .models import CMS_User
# 导入装饰器:判断当前界面是否是登录界面,不是就将url重定向到登录界面
from .decorators import login_required
# 导入restful.py中的访问网页状态码的函数
from utils import restful
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前缀url_prefix
# 钩子函数是在cms_bp创建之后才创建的,顺序在cms_bp创建之后
from .hooks import before_request
@cms_bp.route("/") # 后台界面
# @login_required # 装饰器判定当前界面是否是登录界面,但是需要每个路由函数都要加该装饰器,比较麻烦,推荐使用钩子函数
def index():
# return "cms index:后端类视图文件"
return render_template('cms/cms_index.html') # 登陆之后进入CMS后台管理界面,路径写全cms/cms_index.html
# 用户注销登录
@cms_bp.route("/logout/") # 需要关联到cms/cms_index.html中的注销属性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登录界面
return redirect(url_for('cms.login')) # 重定向(redirec)为把url变为重定向的url
# 定义个人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)则不会改变url,模板渲染是用模板来渲染请求的url
# 定义类视图,显示模板文件 用户登录功能实现
class LoginView(views.MethodView):
def get(self, message=None): # message=None时候不传输信息到cms_login.html页面
return render_template("cms/cms_login.html", message=message) # 针对post方法中同样要返回到cms_login.html页面进行代码简化
# 用户登录操作验证
def post(self):
# 收集表单信息
login_form = LoginForm(request.form)
if login_form.validate():
# 数据库验证
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
# 查询数据库中的用户信息
user = CMS_User.query.filter_by(email=email).first() # 邮箱唯一,用于查询验证用户
if user and user.check_password(password): # 验证用户和密码是否都正确
session['user_id'] = user.id # 查询到用户数据时,保存session的id到浏览器
# session['user_name'] = user.username # 将数据库中的user.username保存到session中,在hooks.py中判断
# session['user_email'] = user.email # 将数据库中的email保存到session中,方便html调用信息
# session['user_join_time'] = user.join_time # 将数据库中的join_time保存到session中,方便html调用信息
if remember: # 如果用户点击了remember选择,在浏览器中进行数据持久化
session.permanent = True # 数据持久化,默认31天,需要设置session_key在config.py中
# 登录成功,跳转到后台首页
return redirect(url_for('cms.index')) # 在蓝图中必须加cms 跳转到index方法
else:
# return "邮箱或密码错误" # 登录出错,返回结果
# return render_template("cms/cms_login.html", message="邮箱或密码错误") # 登录出错,返回结果渲染到cms_login.html页面
return self.get(message="邮箱或密码错误") # 传参到get方法中,多加一个传输错误信息的参数到方法中
else:
# print(login_form.errors) # forms.py中的错误信息 字典类型数据
# print(login_form.errors.popitem()) # forms.py中的错误信息 元祖类型数据
# return "表单验证错误" # 错误信息需要渲染到cms_login.html页面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典类型数据信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表单信息,信息提取放置到forms.py的父类中实现
# 修改密码的类视图验证
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密码修改
def post(self):
# 先审查旧密码是否与数据库中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 对象
user = g.cms_user
# 将用户输入的密码进行加密检测是否与数据库中的相同
if user.check_password(oldpwd):
# 更新我的密码 将新密码赋值,此时的新密码已经经过验证二次密码是否一致
user.password = newpwd # user.password已经调用了models.py中的 @property装饰器进行密码加密
# 数据库更新
db.session.commit()
# return jsonify({"code": 400, "message": "密码修改成功"}) # 代码改写为下面
return restful.success("密码修改成功") # 调用restful.py中定义的访问网页成功的函数
else:
# 当前用户输入的旧密码与数据库中的不符
# return jsonify({"code": 400, "message": "旧密码输入错误"})
return restful.params_error(message="旧密码输入错误") # 参数错误
else:
# ajax 需要返回一个json类型的数据
# message = form.errors.popitem()[1][0] # 收集错误信息
# return jsonify({"code": 400, "message": message}) # 将数据转换成json类型
return restful.params_error(message=form.get_error()) # 参数错误,信息的收集在forms.py的父类函数中实现
# 定义修改邮箱的类视图 验证
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改邮箱页面url
def post(self):
pass
# 添加登录路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 类视图函数添加绑定路由 注意类视图需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改邮箱的类视图路由绑定,路由的命名和cms_base.js中的命名要相同,否则不关联,url=/resetemail/必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
Flask虚拟环境安装 Flask-Mail
pip install Flask-Mail
根据:Flask-Mail网页介绍
添加配置文件中的配置:config.py
# -*- encoding: utf-8 -*-
"""
@File : config.py
@Time : 2020/5/11 10:08
@Author : chen
"""
import os # 导入随机字符串用于加密session
# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo_bbs"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
'''
# 创建引擎并生成Base类
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
SQLALCHEMY_DATABASE_URI = DB_URL # 数据库连接成功
# FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.
# Set it to True or False to suppress this warning.'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
# 这里是为了解决上面的警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.urandom(15) # 产生随机15位字符串
# flask-mail配置信息
MAIL_SERVER = 'smtp.qq.com' # 发送邮箱的服务地址 这里设置为QQ邮箱服务器地址
MAIL_PORT = '587' # 发送端口为465或者587
MAIL_USE_TLS = True # 端口为587对应的服务
# MAIL_USE_SSL = True # 端口为465对应的服务 二选一即可
MAIL_USERNAME = '[email protected]' # 使用者的邮箱
MAIL_PASSWORD = 'lzpihkhrsbhqbajd' # 不是QQ邮箱登录密码,是QQ邮箱授权码获取,用于第三方登录验证
MAIL_DEFAULT_SENDER = '[email protected]' # 默认发送者,暂时先设置为自己
QQ邮箱授权码获取方式
生成QQ邮箱授权码
修改主程序入口文件:bbs.py
# -*- encoding: utf-8 -*-
"""
@File : bbs.py
@Time : 2020/5/11 9:46
@Author : chen
"""
# 项目主文件,启动入口
# 前台 front 管理前端界面的逻辑
# 后台 cms 管理后端的操作
# 公有的文件 common
from flask import Flask
import config # 配置文件库
from exts import db, mail # 第三方库导入db,mail
from apps.cms.views import cms_bp # 导入后端蓝图文件
from apps.front.views import front_bp # 导入前端蓝图文件
from flask_wtf import CSRFProtect # CSRF表单保护验证
app = Flask(__name__)
CSRFProtect(app) # CSRF保护app
app.config.from_object(config) # 添加配置
db.init_app(app) # 绑定app
mail.init_app(app) # mail绑定app
app.register_blueprint(cms_bp) # 后端蓝图文件注册
app.register_blueprint(front_bp) # 前端蓝图文件注册
if __name__ == '__main__':
app.run(debug=True, port=9999)
修改视图文件apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
"""
# 蓝图文件:实现模块化应用,应用可以分解成一系列的蓝图 后端的类视图函数写在这个文件
from flask import (
request, redirect, url_for, # 页面跳转redirect request请求收集
Blueprint, render_template, views, session, # 定义类视图,显示模板文件
jsonify, g # jsonify强制转换成json数据
)
from exts import db, mail # 数据库中更新密码、邮箱等使用
# 导入form表单 .forms代表同级目录下的forms.py ResetPwdForm修改密码的form信息
from .forms import LoginForm, ResetPwdForm
# 导入模型 .models代表同级目录下的models.py
from .models import CMS_User
# 导入装饰器:判断当前界面是否是登录界面,不是就将url重定向到登录界面,一般不用,使用的主要是钩子函数
from .decorators import login_required
# 导入restful.py中的访问网页状态码的函数
from utils import restful
# 导入flask-mail中的Message
from flask_mail import Message
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前缀url_prefix
# 钩子函数是在cms_bp创建之后才创建的,顺序在cms_bp创建之后
from .hooks import before_request
@cms_bp.route("/") # 后台界面
# @login_required # 装饰器判定当前界面是否是登录界面,但是需要每个路由函数都要加该装饰器,比较麻烦,推荐使用钩子函数
def index():
# return "cms index:后端类视图文件"
return render_template('cms/cms_index.html') # 登陆之后进入CMS后台管理界面,路径写全cms/cms_index.html
# 用户注销登录
@cms_bp.route("/logout/") # 需要关联到cms/cms_index.html中的注销属性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登录界面
return redirect(url_for('cms.login')) # 重定向(redirec)为把url变为重定向的url
# 定义个人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)则不会改变url,模板渲染是用模板来渲染请求的url
# 定义类视图,显示模板文件 用户登录功能实现
class LoginView(views.MethodView):
def get(self, message=None): # message=None时候不传输信息到cms_login.html页面
return render_template("cms/cms_login.html", message=message) # 针对post方法中同样要返回到cms_login.html页面进行代码简化
# 用户登录操作验证
def post(self):
# 收集表单信息
login_form = LoginForm(request.form)
if login_form.validate():
# 数据库验证
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
# 查询数据库中的用户信息
user = CMS_User.query.filter_by(email=email).first() # 邮箱唯一,用于查询验证用户
if user and user.check_password(password): # 验证用户和密码是否都正确
session['user_id'] = user.id # 查询到用户数据时,保存session的id到浏览器
# session['user_name'] = user.username # 将数据库中的user.username保存到session中,在hooks.py中判断
# session['user_email'] = user.email # 将数据库中的email保存到session中,方便html调用信息
# session['user_join_time'] = user.join_time # 将数据库中的join_time保存到session中,方便html调用信息
if remember: # 如果用户点击了remember选择,在浏览器中进行数据持久化
session.permanent = True # 数据持久化,默认31天,需要设置session_key在config.py中
# 登录成功,跳转到后台首页
return redirect(url_for('cms.index')) # 在蓝图中必须加cms 跳转到index方法
else:
# return "邮箱或密码错误" # 登录出错,返回结果
# return render_template("cms/cms_login.html", message="邮箱或密码错误") # 登录出错,返回结果渲染到cms_login.html页面
return self.get(message="邮箱或密码错误") # 传参到get方法中,多加一个传输错误信息的参数到方法中
else:
# print(login_form.errors) # forms.py中的错误信息 字典类型数据
# print(login_form.errors.popitem()) # forms.py中的错误信息 元祖类型数据
# return "表单验证错误" # 错误信息需要渲染到cms_login.html页面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典类型数据信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表单信息,信息提取放置到forms.py的父类中实现
# 修改密码的类视图验证
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密码修改
def post(self):
# 先审查旧密码是否与数据库中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 对象
user = g.cms_user
# 将用户输入的密码进行加密检测是否与数据库中的相同
if user.check_password(oldpwd):
# 更新我的密码 将新密码赋值,此时的新密码已经经过验证二次密码是否一致
user.password = newpwd # user.password已经调用了models.py中的 @property装饰器进行密码加密
# 数据库更新
db.session.commit()
# return jsonify({"code": 400, "message": "密码修改成功"}) # 代码改写为下面
return restful.success("密码修改成功") # 调用restful.py中定义的访问网页成功的函数
else:
# 当前用户输入的旧密码与数据库中的不符
# return jsonify({"code": 400, "message": "旧密码输入错误"})
return restful.params_error(message="旧密码输入错误") # 参数错误
else:
# ajax 需要返回一个json类型的数据
# message = form.errors.popitem()[1][0] # 收集错误信息
# return jsonify({"code": 400, "message": message}) # 将数据转换成json类型
return restful.params_error(message=form.get_error()) # 参数错误,信息的收集在forms.py的父类函数中实现
# 定义修改邮箱的类视图 验证
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改邮箱页面url
def post(self):
pass
# 发送测试邮件进行验证
@cms_bp.route("/send_email/")
def send_mail():
message = Message('邮件发送', recipients=['[email protected]'], body='测试邮件发送') # 主题:邮件发送;收件人:recipients;邮件内容:测试邮件发送
mail.send(message) # 发送邮件
return "邮件已发送"
# 添加登录路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 类视图函数添加绑定路由 注意类视图需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改邮箱的类视图路由绑定,路由的命名和cms_base.js中的命名要相同,否则不关联,url=/resetemail/必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
第三方引用文件 exts.py
导入flask-mail模块
# -*- encoding: utf-8 -*-
"""
@File : exts.py
@Time : 2020/5/11 9:51
@Author : chen
"""
# 第三方引用文件,防止互相引用报错
from flask_sqlalchemy import SQLAlchemy
# 引用flask-mail
from flask_mail import Mail
db = SQLAlchemy()
# 引入Mail()
mail = Mail()
创建随机生成验证码的函数文件:utils/random_captcha.py
# -*- encoding: utf-8 -*-
"""
@File : random_captcha.py
@Time : 2020/5/17 21:05
@Author : chen
"""
"""随机生成验证码(字母数字随机组合,包含大小写)"""
import random
def get_random_captcha(num):
code_list = []
# 每一位验证码都有三种可能(大写字母,小写字母,数字)
for i in range(num):
statu = random.randint(1, 3)
if statu == 1:
a = random.randint(65, 90)
random_uppercase = chr(a)
code_list.append(random_uppercase)
elif statu == 2:
b = random.randint(97, 122)
random_lowercase = chr(b)
code_list.append(random_lowercase)
elif statu == 3:
random_num = random.randint(0, 9)
code_list.append(str(random_num))
verification_code = "".join(code_list) # 生成随机验证码
return verification_code
配置文件configs.py
# -*- encoding: utf-8 -*-
"""
@File : config.py
@Time : 2020/5/11 10:08
@Author : chen
"""
import os # 导入随机字符串用于加密session
# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo_bbs"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
'''
# 创建引擎并生成Base类
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
SQLALCHEMY_DATABASE_URI = DB_URL # 数据库连接成功
# FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.
# Set it to True or False to suppress this warning.'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
# 这里是为了解决上面的警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.urandom(15) # 产生随机15位字符串
# flask-mail配置信息
MAIL_SERVER = 'smtp.qq.com' # 发送邮箱的服务地址 这里设置为QQ邮箱服务器地址
MAIL_PORT = '587' # 发送端口为465或者587
MAIL_USE_TLS = True # 端口为587对应的服务
# MAIL_USE_SSL = True # 端口为465对应的服务 二选一即可
MAIL_USERNAME = '[email protected]' # 使用者的邮箱
MAIL_PASSWORD = 'lzpihkhrsbhqbajd' # 不是QQ邮箱登录密码,是QQ邮箱授权码获取,用于第三方登录验证
MAIL_DEFAULT_SENDER = '[email protected]' # 默认发送者,暂时先设置为自己
TEMPLATES_AUTO_RELOAD = True # 修改模板,ajax自动启动
视图文件apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
"""
# 蓝图文件:实现模块化应用,应用可以分解成一系列的蓝图 后端的类视图函数写在这个文件
from flask import (
request, redirect, url_for, # 页面跳转redirect request请求收集
Blueprint, render_template, views, session, # 定义类视图,显示模板文件
jsonify, g # jsonify强制转换成json数据
)
from exts import db, mail # 数据库中更新密码、邮箱等使用
# 导入form表单 .forms代表同级目录下的forms.py ResetPwdForm修改密码的form信息
from .forms import LoginForm, ResetPwdForm
# 导入模型 .models代表同级目录下的models.py
from .models import CMS_User
# 导入装饰器:判断当前界面是否是登录界面,不是就将url重定向到登录界面,一般不用,使用的主要是钩子函数
from .decorators import login_required
# 导入restful.py中的访问网页状态码的函数
from utils import restful, random_captcha # 随机生成验证码函数random_captcha()
# 导入flask-mail中的Message
from flask_mail import Message
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前缀url_prefix
# 钩子函数是在cms_bp创建之后才创建的,顺序在cms_bp创建之后
from .hooks import before_request
@cms_bp.route("/") # 后台界面
# @login_required # 装饰器判定当前界面是否是登录界面,但是需要每个路由函数都要加该装饰器,比较麻烦,推荐使用钩子函数
def index():
# return "cms index:后端类视图文件"
return render_template('cms/cms_index.html') # 登陆之后进入CMS后台管理界面,路径写全cms/cms_index.html
# 用户注销登录
@cms_bp.route("/logout/") # 需要关联到cms/cms_index.html中的注销属性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登录界面
return redirect(url_for('cms.login')) # 重定向(redirec)为把url变为重定向的url
# 定义个人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)则不会改变url,模板渲染是用模板来渲染请求的url
# 定义类视图,显示模板文件 用户登录功能实现
class LoginView(views.MethodView):
def get(self, message=None): # message=None时候不传输信息到cms_login.html页面
return render_template("cms/cms_login.html", message=message) # 针对post方法中同样要返回到cms_login.html页面进行代码简化
# 用户登录操作验证
def post(self):
# 收集表单信息
login_form = LoginForm(request.form)
if login_form.validate():
# 数据库验证
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
# 查询数据库中的用户信息
user = CMS_User.query.filter_by(email=email).first() # 邮箱唯一,用于查询验证用户
if user and user.check_password(password): # 验证用户和密码是否都正确
session['user_id'] = user.id # 查询到用户数据时,保存session的id到浏览器
# session['user_name'] = user.username # 将数据库中的user.username保存到session中,在hooks.py中判断
# session['user_email'] = user.email # 将数据库中的email保存到session中,方便html调用信息
# session['user_join_time'] = user.join_time # 将数据库中的join_time保存到session中,方便html调用信息
if remember: # 如果用户点击了remember选择,在浏览器中进行数据持久化
session.permanent = True # 数据持久化,默认31天,需要设置session_key在config.py中
# 登录成功,跳转到后台首页
return redirect(url_for('cms.index')) # 在蓝图中必须加cms 跳转到index方法
else:
# return "邮箱或密码错误" # 登录出错,返回结果
# return render_template("cms/cms_login.html", message="邮箱或密码错误") # 登录出错,返回结果渲染到cms_login.html页面
return self.get(message="邮箱或密码错误") # 传参到get方法中,多加一个传输错误信息的参数到方法中
else:
# print(login_form.errors) # forms.py中的错误信息 字典类型数据
# print(login_form.errors.popitem()) # forms.py中的错误信息 元祖类型数据
# return "表单验证错误" # 错误信息需要渲染到cms_login.html页面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典类型数据信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表单信息,信息提取放置到forms.py的父类中实现
# 修改密码的类视图验证
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密码修改
def post(self):
# 先审查旧密码是否与数据库中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 对象
user = g.cms_user
# 将用户输入的密码进行加密检测是否与数据库中的相同
if user.check_password(oldpwd):
# 更新我的密码 将新密码赋值,此时的新密码已经经过验证二次密码是否一致
user.password = newpwd # user.password已经调用了models.py中的 @property装饰器进行密码加密
# 数据库更新
db.session.commit()
# return jsonify({"code": 400, "message": "密码修改成功"}) # 代码改写为下面
return restful.success("密码修改成功") # 调用restful.py中定义的访问网页成功的函数
else:
# 当前用户输入的旧密码与数据库中的不符
# return jsonify({"code": 400, "message": "旧密码输入错误"})
return restful.params_error(message="旧密码输入错误") # 参数错误
else:
# ajax 需要返回一个json类型的数据
# message = form.errors.popitem()[1][0] # 收集错误信息
# return jsonify({"code": 400, "message": message}) # 将数据转换成json类型
return restful.params_error(message=form.get_error()) # 参数错误,信息的收集在forms.py的父类函数中实现 form是收集到的信息
# 定义修改邮箱的类视图 验证
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改邮箱页面url
def post(self):
pass
# 发送测试邮件进行验证
@cms_bp.route("/send_email/")
def send_mail():
message = Message('邮件发送', recipients=['[email protected]'], body='测试邮件发送') # 主题:邮件发送;收件人:recipients;邮件内容:测试邮件发送
mail.send(message) # 发送邮件
return "邮件已发送"
# 邮件发送
class EmailCaptcha(views.MethodView):
def get(self): # 根据resetemail.js中的ajax方法来写函数,不需要post请求
email = request.args.get('email') # 查询email参数是否存在
if not email:
return restful.params_error('请传递邮箱参数')
# 发送邮件,内容为一个验证码:4、6位数字英文组合
captcha = random_captcha.get_random_captcha(4) # 生成4位验证码
message = Message('BBS论坛邮箱验证码', recipients=[email], body='您的验证码是:%s' % captcha)
# 异常处理
try:
mail.send(message)
except:
return restful.server_error(message="服务器错误,邮件验证码未发送!") # 发送异常,服务器错误
# 验证码保存,一般有时效性,且频繁请求变化,所以保存在Redis中
return restful.success("邮件验证码发送成功!")
# 添加登录路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 类视图函数添加绑定路由 注意类视图需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改邮箱的类视图路由绑定,路由的命名和cms_base.js中的命名要相同,否则不关联,url=/resetemail/必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
# 绑定路由,路由的命名和cms_base.js中的命名要相同,必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))
开启Redis服务端和客户端,并清除客户端所有数据:
虚拟环境中导入redis库
创建redis存储、获取、删除验证码:
工具文件utils/redis_captcha.py
# -*- encoding: utf-8 -*-
"""
@File : redis_captcha.py
@Time : 2020/5/17 21:46
@Author : chen
"""
# Redis中保存验证码,并读取进行验证,再删除验证码
import redis
# 连接Redis,相当于redis.Redis() 暂时是本地连接,decode_responses=True是将redis读取出来的验证码转换成字符串类型,原本是二进制字节
r = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True)
# 存储验证码
def redis_set(key, value, timeout=60): # timeout=60过期时间60s
return r.set(key, value, timeout)
# 提取验证码
def redis_get(key):
return r.get(key) # 这里输出的原本是二进制字节类型数据,decode_responses=True自动转换成字符串
# 删除验证码
def redis_delete(key):
return r.delete(key)
修改视图文件通过Redis保存验证码:
视图文件apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
"""
# 蓝图文件:实现模块化应用,应用可以分解成一系列的蓝图 后端的类视图函数写在这个文件
from flask import (
request, redirect, url_for, # 页面跳转redirect request请求收集
Blueprint, render_template, views, session, # 定义类视图,显示模板文件
jsonify, g # jsonify强制转换成json数据
)
from exts import db, mail # 数据库中更新密码、邮箱等使用
# 导入form表单 .forms代表同级目录下的forms.py ResetPwdForm修改密码的form信息
from .forms import LoginForm, ResetPwdForm
# 导入模型 .models代表同级目录下的models.py
from .models import CMS_User
# 导入装饰器:判断当前界面是否是登录界面,不是就将url重定向到登录界面,一般不用,使用的主要是钩子函数
from .decorators import login_required
# 导入restful.py中的访问网页状态码的函数 redis_captcha:redis存储、提取、删除验证码功能
from utils import restful, random_captcha, redis_captcha # 随机生成验证码函数random_captcha()
# 导入flask-mail中的Message
from flask_mail import Message
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前缀url_prefix
# 钩子函数是在cms_bp创建之后才创建的,顺序在cms_bp创建之后
from .hooks import before_request
@cms_bp.route("/") # 后台界面
# @login_required # 装饰器判定当前界面是否是登录界面,但是需要每个路由函数都要加该装饰器,比较麻烦,推荐使用钩子函数
def index():
# return "cms index:后端类视图文件"
return render_template('cms/cms_index.html') # 登陆之后进入CMS后台管理界面,路径写全cms/cms_index.html
# 用户注销登录
@cms_bp.route("/logout/") # 需要关联到cms/cms_index.html中的注销属性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登录界面
return redirect(url_for('cms.login')) # 重定向(redirec)为把url变为重定向的url
# 定义个人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)则不会改变url,模板渲染是用模板来渲染请求的url
# 定义类视图,显示模板文件 用户登录功能实现
class LoginView(views.MethodView):
def get(self, message=None): # message=None时候不传输信息到cms_login.html页面
return render_template("cms/cms_login.html", message=message) # 针对post方法中同样要返回到cms_login.html页面进行代码简化
# 用户登录操作验证
def post(self):
# 收集表单信息
login_form = LoginForm(request.form)
if login_form.validate():
# 数据库验证
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
# 查询数据库中的用户信息
user = CMS_User.query.filter_by(email=email).first() # 邮箱唯一,用于查询验证用户
if user and user.check_password(password): # 验证用户和密码是否都正确
session['user_id'] = user.id # 查询到用户数据时,保存session的id到浏览器
# session['user_name'] = user.username # 将数据库中的user.username保存到session中,在hooks.py中判断
# session['user_email'] = user.email # 将数据库中的email保存到session中,方便html调用信息
# session['user_join_time'] = user.join_time # 将数据库中的join_time保存到session中,方便html调用信息
if remember: # 如果用户点击了remember选择,在浏览器中进行数据持久化
session.permanent = True # 数据持久化,默认31天,需要设置session_key在config.py中
# 登录成功,跳转到后台首页
return redirect(url_for('cms.index')) # 在蓝图中必须加cms 跳转到index方法
else:
# return "邮箱或密码错误" # 登录出错,返回结果
# return render_template("cms/cms_login.html", message="邮箱或密码错误") # 登录出错,返回结果渲染到cms_login.html页面
return self.get(message="邮箱或密码错误") # 传参到get方法中,多加一个传输错误信息的参数到方法中
else:
# print(login_form.errors) # forms.py中的错误信息 字典类型数据
# print(login_form.errors.popitem()) # forms.py中的错误信息 元祖类型数据
# return "表单验证错误" # 错误信息需要渲染到cms_login.html页面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典类型数据信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表单信息,信息提取放置到forms.py的父类中实现
# 修改密码的类视图验证
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密码修改
def post(self):
# 先审查旧密码是否与数据库中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 对象
user = g.cms_user
# 将用户输入的密码进行加密检测是否与数据库中的相同
if user.check_password(oldpwd):
# 更新我的密码 将新密码赋值,此时的新密码已经经过验证二次密码是否一致
user.password = newpwd # user.password已经调用了models.py中的 @property装饰器进行密码加密
# 数据库更新
db.session.commit()
# return jsonify({"code": 400, "message": "密码修改成功"}) # 代码改写为下面
return restful.success("密码修改成功") # 调用restful.py中定义的访问网页成功的函数
else:
# 当前用户输入的旧密码与数据库中的不符
# return jsonify({"code": 400, "message": "旧密码输入错误"})
return restful.params_error(message="旧密码输入错误") # 参数错误
else:
# ajax 需要返回一个json类型的数据
# message = form.errors.popitem()[1][0] # 收集错误信息
# return jsonify({"code": 400, "message": message}) # 将数据转换成json类型
return restful.params_error(message=form.get_error()) # 参数错误,信息的收集在forms.py的父类函数中实现 form是收集到的信息
# 定义修改邮箱的类视图 验证
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改邮箱页面url
def post(self):
pass
# 发送测试邮件进行验证
@cms_bp.route("/send_email/")
def send_mail():
message = Message('邮件发送', recipients=['[email protected]'], body='测试邮件发送') # 主题:邮件发送;收件人:recipients;邮件内容:测试邮件发送
mail.send(message) # 发送邮件
return "邮件已发送"
# 邮件发送
class EmailCaptcha(views.MethodView):
def get(self): # 根据resetemail.js中的ajax方法来写函数,不需要post请求
email = request.args.get('email') # 查询email参数是否存在
if not email:
return restful.params_error('请传递邮箱参数')
# 发送邮件,内容为一个验证码:4、6位数字英文组合
captcha = random_captcha.get_random_captcha(4) # 生成4位验证码
message = Message('BBS论坛邮箱验证码', recipients=[email], body='您的验证码是:%s' % captcha)
# 异常处理
try:
mail.send(message)
except:
return restful.server_error(message="服务器错误,邮件验证码未发送!") # 发送异常,服务器错误
# 验证码保存,一般有时效性,且频繁请求变化,所以保存在Redis中
redis_captcha.redis_set(key=email, value=captcha) # redis中都是键值对类型,存储验证码
return restful.success("邮件验证码发送成功!")
# 添加登录路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 类视图函数添加绑定路由 注意类视图需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改邮箱的类视图路由绑定,路由的命名和cms_base.js中的命名要相同,否则不关联,url=/resetemail/必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
# 绑定路由,路由的命名和cms_base.js中的命名要相同,必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))
需要定义设置邮箱时用户输入的表单信息,进行提交验证时候使用
修改Form表单文件:apps/cms/forms.py文件
# -*- encoding: utf-8 -*-
"""
@File : forms.py
@Time : 2020/5/11 10:00
@Author : chen
"""
# forms表单信息
from wtforms import Form, StringField, IntegerField, ValidationError
from wtforms.validators import Email, InputRequired, Length, EqualTo # EqualTo验证新密码是否相同
from utils.redis_captcha import redis_get # 导入验证码模块
# 创父类form表单,用于输出错误信息
class BaseForm(Form):
def get_error(self):
message = self.errors.popitem()[1][0] # 错误信息的收集,字典类型数据信息提取
return message
# 登录页面中的Form表单 继承父类form
class LoginForm(BaseForm):
email = StringField(validators=[Email(message="请输入正确的邮箱"), InputRequired(message="请输入邮箱")])
password = StringField(validators=[Length(3, 15, message='请输入正确长度的密码')]) # 长度可以先设置短的,方便项目测试
remember = IntegerField() # 记住cookie操作 赋值为0或1
# 修改密码页面中的form表单信息 继承父类form
class ResetPwdForm(BaseForm):
oldpwd = StringField(validators=[Length(3, 15, message="密码长度有误")])
newpwd = StringField(validators=[Length(3, 15, message="密码长度有误")])
newpwd2 = StringField(validators=[EqualTo("newpwd", message="两次输入密码不一致")])
# 定义设置邮箱的表单信息,进行提交时候使用
class ResetEmailForm(BaseForm):
email = StringField(validators=[Email(message="请输入正确格式的邮箱")]) # 名称email与cms_resetemail.html中的要相同
captcha = StringField(validators=[Length(min=4, max=4, message="请输入正确长度的验证码")]) # 名称captcha与cms_resetemail.html中的要相同
# 验证redis中的字段与数据库中的字段是否相同
def validate_captcha(self, field): # 方法命名规则是:validate_字段名()
# 表单提交上来的验证码
email = self.email.data
captcha = self.captcha.data
# 取redis中保存的验证码 第一个redis_captcha是新对象,第二个redis_captcha是redis_captcha.py文件
redis_captcha = redis_get(email)
if not redis_captcha or captcha.lower() != redis_captcha.lower(): # 不区分大小写
raise ValidationError('邮箱验证码错误')
修改视图文件进行验证邮箱验证码和数据库信息:
视图文件apps/cms/views.py文件
# -*- encoding: utf-8 -*-
"""
@File : views.py
@Time : 2020/5/11 9:59
@Author : chen
"""
# 蓝图文件:实现模块化应用,应用可以分解成一系列的蓝图 后端的类视图函数写在这个文件
from flask import (
request, redirect, url_for, # 页面跳转redirect request请求收集
Blueprint, render_template, views, session, # 定义类视图,显示模板文件
jsonify, g # jsonify强制转换成json数据
)
from exts import db, mail # 数据库中更新密码、邮箱等使用
# 导入form表单 .forms代表同级目录下的forms.py ResetPwdForm修改密码的form信息
from .forms import LoginForm, ResetPwdForm
# 导入forms.py文件中的邮箱验证的表单信息类
from apps.cms.forms import ResetEmailForm
# 导入模型 .models代表同级目录下的models.py
from .models import CMS_User
# 导入装饰器:判断当前界面是否是登录界面,不是就将url重定向到登录界面,一般不用,使用的主要是钩子函数
from .decorators import login_required
# 导入restful.py中的访问网页状态码的函数 redis_captcha:redis存储、提取、删除验证码功能
from utils import restful, random_captcha, redis_captcha # 随机生成验证码函数random_captcha()
# 导入flask-mail中的Message
from flask_mail import Message
cms_bp = Blueprint("cms", __name__, url_prefix='/cms/') # URL前缀url_prefix
# 钩子函数是在cms_bp创建之后才创建的,顺序在cms_bp创建之后
from .hooks import before_request
@cms_bp.route("/") # 后台界面
# @login_required # 装饰器判定当前界面是否是登录界面,但是需要每个路由函数都要加该装饰器,比较麻烦,推荐使用钩子函数
def index():
# return "cms index:后端类视图文件"
return render_template('cms/cms_index.html') # 登陆之后进入CMS后台管理界面,路径写全cms/cms_index.html
# 用户注销登录
@cms_bp.route("/logout/") # 需要关联到cms/cms_index.html中的注销属性
def logout():
# session清除user_id
del session['user_id']
# 重定向到登录界面
return redirect(url_for('cms.login')) # 重定向(redirec)为把url变为重定向的url
# 定义个人中心的路由
@cms_bp.route("/profile/")
def profile():
return render_template("cms/cms_profile.html") # 模板渲染(render_template)则不会改变url,模板渲染是用模板来渲染请求的url
# 定义类视图,显示模板文件 用户登录功能实现
class LoginView(views.MethodView):
def get(self, message=None): # message=None时候不传输信息到cms_login.html页面
return render_template("cms/cms_login.html", message=message) # 针对post方法中同样要返回到cms_login.html页面进行代码简化
# 用户登录操作验证
def post(self):
# 收集表单信息
login_form = LoginForm(request.form)
if login_form.validate():
# 数据库验证
email = login_form.email.data
password = login_form.password.data
remember = login_form.remember.data
# 查询数据库中的用户信息
user = CMS_User.query.filter_by(email=email).first() # 邮箱唯一,用于查询验证用户
if user and user.check_password(password): # 验证用户和密码是否都正确
session['user_id'] = user.id # 查询到用户数据时,保存session的id到浏览器
# session['user_name'] = user.username # 将数据库中的user.username保存到session中,在hooks.py中判断
# session['user_email'] = user.email # 将数据库中的email保存到session中,方便html调用信息
# session['user_join_time'] = user.join_time # 将数据库中的join_time保存到session中,方便html调用信息
if remember: # 如果用户点击了remember选择,在浏览器中进行数据持久化
session.permanent = True # 数据持久化,默认31天,需要设置session_key在config.py中
# 登录成功,跳转到后台首页
return redirect(url_for('cms.index')) # 在蓝图中必须加cms 跳转到index方法
else:
# return "邮箱或密码错误" # 登录出错,返回结果
# return render_template("cms/cms_login.html", message="邮箱或密码错误") # 登录出错,返回结果渲染到cms_login.html页面
return self.get(message="邮箱或密码错误") # 传参到get方法中,多加一个传输错误信息的参数到方法中
else:
# print(login_form.errors) # forms.py中的错误信息 字典类型数据
# print(login_form.errors.popitem()) # forms.py中的错误信息 元祖类型数据
# return "表单验证错误" # 错误信息需要渲染到cms_login.html页面
# return self.get(message=login_form.errors.popitem()[1][0]) # 字典类型数据信息提取
return self.get(message=login_form.get_error()) # login_form是收集到的表单信息,信息提取放置到forms.py的父类中实现
# 修改密码的类视图验证
class ResetPwd(views.MethodView):
def get(self):
return render_template('cms/cms_resetpwd.html') # 模板渲染到cms_resetpwd.html
# post提交密码修改
def post(self):
# 先审查旧密码是否与数据库中的信息相同
form = ResetPwdForm(request.form)
if form.validate():
oldpwd = form.oldpwd.data
newpwd = form.newpwd.data
# 对象
user = g.cms_user
# 将用户输入的密码进行加密检测是否与数据库中的相同
if user.check_password(oldpwd):
# 更新我的密码 将新密码赋值,此时的新密码已经经过验证二次密码是否一致
user.password = newpwd # user.password已经调用了models.py中的 @property装饰器进行密码加密
# 数据库更新
db.session.commit()
# return jsonify({"code": 400, "message": "密码修改成功"}) # 代码改写为下面
return restful.success("密码修改成功") # 调用restful.py中定义的访问网页成功的函数
else:
# 当前用户输入的旧密码与数据库中的不符
# return jsonify({"code": 400, "message": "旧密码输入错误"})
return restful.params_error(message="旧密码输入错误") # 参数错误
else:
# ajax 需要返回一个json类型的数据
# message = form.errors.popitem()[1][0] # 收集错误信息
# return jsonify({"code": 400, "message": message}) # 将数据转换成json类型
return restful.params_error(message=form.get_error()) # 参数错误,信息的收集在forms.py的父类函数中实现 form是收集到的信息
# 定义修改邮箱的类视图 验证
class ResetEmail(views.MethodView):
def get(self):
return render_template("cms/cms_resetemail.html") # 返回到修改邮箱页面url
def post(self):
form = ResetEmailForm(request.form) # 接收邮箱验证的form表单信息
if form.validate(): # 验证表单信息是否通过
email = form.email.data # 获取form表单中填写的邮箱地址
# 查询数据库
# CMS_User.query.filter_by(email=email).first()
# CMS_User.query.filter(CMS_User.email == email).first()
g.cms_user.email = email # 数据库中的查询在apps/cms/hooks.py文件中确定了该用户的数据库信息,用全局对象g.cms_user修改邮箱
db.session.commit()
return restful.success() # 邮箱修改成功
else:
return restful.params_error(form.get_error()) # form是这个类中的所有表单信息
# 发送测试邮件进行验证
@cms_bp.route("/send_email/")
def send_mail():
message = Message('邮件发送', recipients=['[email protected]'], body='测试邮件发送') # 主题:邮件发送;收件人:recipients;邮件内容:测试邮件发送
mail.send(message) # 发送邮件
return "邮件已发送"
# 邮件发送
class EmailCaptcha(views.MethodView):
def get(self): # 根据resetemail.js中的ajax方法来写函数,不需要post请求
email = request.args.get('email') # 查询email参数是否存在
if not email:
return restful.params_error('请传递邮箱参数')
# 发送邮件,内容为一个验证码:4、6位数字英文组合
captcha = random_captcha.get_random_captcha(4) # 生成4位验证码
message = Message('BBS论坛邮箱验证码', recipients=[email], body='您的验证码是:%s' % captcha)
# 异常处理
try:
mail.send(message)
except:
return restful.server_error(message="服务器错误,邮件验证码未发送!") # 发送异常,服务器错误
# 验证码保存,一般有时效性,且频繁请求变化,所以保存在Redis中
redis_captcha.redis_set(key=email, value=captcha) # redis中都是键值对类型,存储验证码
return restful.success("邮件验证码发送成功!")
# 添加登录路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login')) # view_func 命名操作名字,"/login/"路由地址
# 类视图函数添加绑定路由 注意类视图需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd')) # view_func 命名操作名字,/resetpwd/路由地址
# 添加修改邮箱的类视图路由绑定,路由的命名和cms_base.js中的命名要相同,否则不关联,url=/resetemail/必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))
# 绑定路由,路由的命名和cms_base.js中的命名要相同,必须要和resetemail.js中的ajax绑定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))
权限管理功能是使用二进制的与、或运算来实现
用户、角色和权限的关系:
创建角色模型定义:
模型文件models.py
# -*- encoding: utf-8 -*-
"""
@File : models.py
@Time : 2020/5/11 10:00
@Author : chen
"""
# 定义后端用户模型
from exts import db # 数据库
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash # 导入密码加密,解密方法的库
# 权限定义,不是模型,没有继承db.Model
class CMSPersmission(object):
# 255 二进制表示所有的权限
ALL_PERMISSION = 0b11111111 # 每一位数代表一个权限,共7个权限,8位1个字节
# 访问权限
VISITOR = 0b00000001
# 管理帖子
POSTER = 0b00000010
# 管理评论
COMMENTER = 0b00000100
# 管理板块
BOARDER = 0b00001000
# 管理后台用户
CMSUSER = 0b00010000
# 管理前台用户
FRONTUSER = 0b00100000
# 管理管理员用户
ADMINER = 0b01000000
# 权限与角色是多对多的关系,创建他们的中间表
cms_role_user = db.Table(
"cms_role_user",
db.Column("cms_role_id", db.Integer, db.ForeignKey('cms_role.id'), primary_key=True),
db.Column("cms_user_id", db.Integer, db.ForeignKey('cms_user.id'), primary_key=True),
)
# 角色模型定义 继承了db.Model
class CMSRole(db.Model):
__tablename__ = 'cms_role'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主键 自增
name = db.Column(db.String(50), nullable=False) # 非空
desc = db.Column(db.String(250), nullable=False) # 非空
creat_time = db.Column(db.DateTime, default=datetime.now)
permission = db.Column(db.Integer, default=CMSPersmission.VISITOR) # 默认先给游客权限
# 反向查询属性,关联中间表secondary=cms_role_user,对应了CMS_User模型,建立模型联系,不映射到数据库中
users = db.relationship('CMS_User', secondary=cms_role_user, backref="roles")
# 后台用户模型定义
class CMS_User(db.Model):
__tablename__ = 'cms_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主键 自增
username = db.Column(db.String(150), nullable=False) # 非空
# password = db.Column(db.String(150), nullable=False)
_password = db.Column(db.String(150), nullable=False) # 密码加密操作修改字段
email = db.Column(db.String(50), nullable=False, unique=True) # 非空、唯一
join_time = db.Column(db.DateTime, default=datetime.now) # 默认当前时间
# 修改密码加密操作中的字段,在manage.py映射数据库时候,使用字段还是保持相同
def __init__(self, username, password, email):
self.username = username
self.password = password # 调用该方法 返回下面的self._password数值,
self.email = email
# 密码加密操作
@property
def password(self): # 密码取值
return self._password
@password.setter # 密码加密
def password(self, raw_password):
self._password = generate_password_hash(raw_password)
# 用于验证后台登录密码是否和数据库一致,raw_password是后台登录输入的密码
def check_password(self, raw_password):
result = check_password_hash(self.password, raw_password) # 相当于用相同的hash加密算法加密raw_password,检测与数据库中是否一致
return result
映射数据库信息文件manage.py
# -*- encoding: utf-8 -*-
"""
@File : manage.py
@Time : 2020/5/10 17:36
@Author : chen
"""
from flask_script import Manager
from bbs import app # 需要将当前文件夹设置为当前根目录,才不会报错
from flask_migrate import Migrate, MigrateCommand
from exts import db
# 导入模型 才能映射到数据库 导入后端的模型
from apps.cms.models import CMS_User
# 导入角色模型,映射到数据库
from apps.cms.models import CMSRole
manage = Manager(app)
Migrate(app, db)
manage.add_command('db', MigrateCommand)
# 命令行添加后端用户
@manage.option('-u', '--username', dest='username')
@manage.option('-p', '--password', dest='password')
@manage.option('-e', '--email', dest='email')
def create_cms_user(username, password, email):
user = CMS_User(username=username, password=password, email=email)
# 添加映射到数据库,提交至数据库
db.session.add(user)
db.session.commit()
print("cms后端用户添加成功")
if __name__ == '__main__':
manage.run()
命令行映射到数据库
映射到数据库中
python manage.py db migrate
python manage.py db upgrade