准备

新建一个项目,在项目中添加一个config.py文件,用来进行邮箱验证码,cookie,和session和一些加密等的配置

这样看来config.py是项目的一部分了,但是还要在app.py中进行导入

import configapp.config.from_object(config)

另外还要新建一个exts.py文件,用来存放扩展的插件

新建一个models.py来定义数据库映射表

循环引用

这样呢就会形成一种循环引用,什么意思呢?就是在app.py中的app对象会被exts.py的db = SQLAlchemy(app)语句使用然后呢db又要被models.py在创建模型类的继承中使用,而models.py中定义的ORM模型映射数据库的表又要在app.py中执行数据库操作的时候使用。这样就形成了一个循环引用。

那么如何解决呢?就是如上图在exts.py文件中就只定义一个空的SQLAlchemy对象。然后在app.py中进行引入然后进行设置绑定app。这样就构不成一个循环了。

在app.py中绑定app

db.init_app(app)

蓝图(blueprints)

引入原因

当一个项目很小的时候视图函数可以全写在app.py中,很简单,但是如果项目大了的话有很多视图函数,如果都写在app.py中的话,就会显得十分的臃肿。不利于后期维护。那么这就要使用到蓝图这个东西,他就是用来模块化的。

使用

FastAPI 和 Flask 的新端点工具窗口

开始处理新项目或现有项目后,PyCharm 将扫描路由并将其列在 Endpoints(端点)工具窗口中,您可以在该窗口中对 URL 进行代码补全、导航和重构。 此工具窗口还提供了对端点的更好概览和对文档的快速访问

新建一个软件包(注意不是目录,软件包是带一个——init——.py文件的),一般都是命名为blueprints。然后在包内开发模块即可。

在保重我们新建一个蓝图就是一个python文件例如auth.py和qa.py,前者负责权限方面的蓝图,后者负责问答方面的蓝图

编写蓝图文件

# auth.pyfrom flask import Blueprint, render_template# 第一个参数是蓝图的名字,第二个参数是指蓝图文件的__name__ 第三个参数是本文件路由的目录前缀 "/" 表示根目录
bp = Blueprint("auth", __name__, url_prefix="/")@bp.route('/')
def login():return render_template("login.html")# qa.py
from flask import Blueprint, render_template# 第一个参数是蓝图的名字,第二个参数是指蓝图文件的__name__ 第三个参数是本文件路由的目录前缀 "/" 表示根目录
bp = Blueprint("auth", __name__, url_prefix="/")@bp.route('/')
def login():return render_template("login.html")

在app.py中注册蓝图

#引入蓝图对象
from blueprints.qa import bp as qa_bp
from blueprints.auth import bp as auth_bp#注册蓝图对象
app.register_blueprint(auth_bp)
app.register_blueprint(qa_bp)

到目前为止我们的目录结构是

连接数据库

新建

新建一个数据库为questionandanswer

为数据库连接添加配置文件

HOSTNAME = '127.0.0.1'
# 监听端口 相当于一个房间号 mysql默认3306
PORT = '3306'
# 数据库名字
DATABASE = 'questionandanswer'
# 数据库用户名
USERNAME = 'root'
# 密码
PASSWORD = 'huang12

添加模型映射类

# 这里是定义ORM模型映射到数据库的
from exts import db
from selfmodules.SHUJUKU import DataBaseOperations
from datetime import datetime
op = DataBaseOperations()class UserModel(db.Model):__tablename__="user"id = op.InsertColumn(db, db.Integer, iskey=True, autoincrement=True)username = op.InsertColumn(db, db.String(100), nullable=False)password = op.InsertColumn(db, db.String(100), nullable=False)email = op.InsertColumn(db, db.String(50), unique=True)join_time = db.Column(db.DateTime,default=datetime.now)

在对应蓝图文件中引入模型类

# 如果不引入则表就不会同步from models import UserModel

将模型在app.py中映射到数据库

import pymysql
from flask_migrate import Migrate# 设置默认数据库连接工具
pymysql.install_as_MySQLdb()
# 注册迁移对像
migrate = Migrate(app,db)#控制台三部曲
1.flask db init
2.flask db migrate
3.flask db upgrade# 以上三部曲 运行时 会在app.py中寻找注册的蓝图 然后分析蓝图引入的模型类是否有改变 然后进行后续操作

注册页面渲染

注册页面是通过jinja2语法继承基础页面来实现的所以先设置基础页面:base.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"><script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script><link rel="stylesheet" href="{{ url_for('static',filename='css/base.css') }}">{% block head %}{% endblock %}<title>{% block title %}{% endblock %}-知了课堂问答平台</title>
</head>
<body>
<nav class="navbar navbar-default"><div class="container"><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#"><img class="zhiliaologo" src="{{ url_for('static',filename='images/zhiliaologo.png') }}" alt=""></a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="/">首页<span class="sr-only">(current)</span></a></li><li><a href="{{ url_for('qa.question') }}">发布问答</a></li></ul><form class="navbar-form navbar-left" action="#"><div class="form-group"><input name="q" type="text" class="form-control" placeholder="请输入关键字"></div><button type="submit" class="btn btn-default">查找</button></form><ul class="nav navbar-nav navbar-right">{% if user %}<li><a href="{{ url_for('auth.login') }}">{{ user.username }}</a></li><li><a href="{{ url_for('auth.login') }}">注销</a></li>{% else %}<li><a href="{{ url_for('auth.login') }}">登录</a></li><li><a href="{{ url_for('auth.register') }}">注册</a></li>{% endif %}</ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid -->
</nav>
<div class="main">{% block main %}{% endblock %}
</div>
</body>
</html>

然后设置样式base.css

a, abbr, acronym, address, applet, article, aside, audio, b, big, blockquote, body, canvas, caption, center, cite, code, dd, del, details, dfn, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, html, i, iframe, img, ins, kbd, label, legend, li, mark, menu, nav, object, ol, output, p, pre, q, ruby, s, samp, section, small, span, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, time, tr, tt, u, ul, li, var, video {margin: 0;padding: 0;border: 0;vertical-align: baseline;list-style: none;
}main {border-radius: 10%;border: 1px solid #9b9b9b;
}.zhiliaologo {width: 100px;
}.main {background: #fff;width: 730px;overflow: hidden;margin: 0 auto;
}body {background-image: url(../images/login.png);background-repeat: no-repeat;background-size: 100% 300%;
}.page-title {text-align: center;padding: 20px;
}

接下来就可以在base.html的基础上实现regist.html页面的注册了

{% extends 'base.html' %}{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/login_regist.css') }}">
{% endblock %}{% block title %}注册{% endblock %}{% block main %}<h3 class="page-title">注册</h3><form action="" method="post"><div class="form-container"><div class="form-group"><input type="text" name="telephone" placeholder="手机号码" class="form-control"></div><div class="form-group"><input type="text" name="username" placeholder="用户名" class="form-control"></div><div class="form-group"><input type="password" name="password1" placeholder="密码" class="form-control"></div><div class="form-group"><input type="password" name="password2" placeholder="重复密码" class="form-control"></div><div class="form-group email_container"><input type="text" name="email" placeholder="邮箱" class="form-control email"><button  οnclick="" class="send_btn">发送验证码</button></div><div class="form-group"><input type="text" name="captcha" placeholder="请填写验证码" class="form-control"></div><div class="form-group"><button class="btn btn-primary btn-block">立即注册</button></div></div></form>
{% endblock %}

在base.html中会有

 <li><a href="{{ url_for('auth.login') }}">{{ user.username }}</a></li>

这样的对url_for()的使用。这里就要使用到我们之前定义的蓝图了,它会使用相关蓝图的对应视图函数

注册页面邮件的发送

安装插件

这就要用到Flask-Mail这个插件了 同样的先使用pip install Flask-Mail 进行安装

相关配置参数

序号 参数与描述
1 MAIL_SERVER电子邮件服务器的名称/IP地址
2 MAIL_PORT使用的服务器的端口号
3 MAIL_USE_TLS启用/禁用传输安全层加密
4 MAIL_USE_SSL启用/禁用安全套接字层加密
5 MAIL_DEBUG调试支持。默认值是Flask应用程序的调试状态
6 MAIL_USERNAME发件人的用户名
7 MAIL_PASSWORD发件人的密码
8 MAIL_DEFAULT_SENDER设置默认发件人
9 MAIL_MAX_EMAILS设置要发送的最大邮件数
10 MAIL_SUPPRESS_SEND如果app.testing设置为true,则发送被抑制
11 MAIL_ASCII_ATTACHMENTS如果设置为true,则附加的文件名将转换为ASCII

flask-mail模块包含以下重要类的定义。

Mail类

它管理电子邮件消息传递需求。类构造函数采用以下形式:

flask-mail.Mail(app = None)

构造函数将Flask应用程序对象作为参数。

Mail类的方法

序号 方法与描述
1 **send()**发送Message类对象的内容
2 **connect()**打开与邮件主机的连接
3 **send_message()**发送消息对象

Message类

它封装了一封电子邮件。Message类构造函数有几个参数:

flask-mail.Message(subject, recipients, body, html, sender, cc, bcc, reply-to, date, charset, extra_headers, mail_options, rcpt_options)

Message类方法

attach() - 为邮件添加附件。此方法采用以下参数:

  • filename - 要附加的文件的名称
  • content_type - MIME类型的文件
  • data - 原始文件数据
  • 处置 - 内容处置(如果有的话)。

add_recipient() - 向邮件添加另一个收件人

在下面的示例中,Google gmail服务的SMTP服务器用作Flask-Mail配置的MAIL_SERVER。

步骤1 - 在代码中从flask-mail模块导入Mail和Message类。

from flask_mail import Mail, Message

步骤2 - 然后按照以下设置配置Flask-Mail。

app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'yourId@gmail.com'
app.config['MAIL_PASSWORD'] = '*****'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True

步骤3 - 创建Mail类的实例。

mail = Mail(app)

步骤4 - 在由URL规则**(‘/’)**映射的Python函数中设置Message对象。

@app.route("/")
def index():msg = Message('Hello', sender = 'yourId@gmail.com', recipients = ['id1@gmail.com'])msg.body = "This is the email body"mail.send(msg)return "Sent"

步骤5 - 整个代码如下。

在Python Shell中运行以下脚本并访问http://localhost:5000/。

from flask import Flask
from flask_mail import Mail, Messageapp =Flask(__name__)app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'yourId@gmail.com'
app.config['MAIL_PASSWORD'] = '*****'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app)@app.route("/")
def index():msg = Message('Hello', sender = 'yourId@gmail.com', recipients = ['id1@gmail.com'])msg.body = "Hello Flask message sent from Flask-Mail"mail.send(msg)return "Sent"if __name__ == '__main__':app.run(debug = True)

请注意,Gmail服务中的内置不安全功能可能会阻止此次登录尝试。您可能必须降低安全级别。请登录您的Gmail帐户并访问此链接以降低安全性。

设置邮箱服务配置

准备工作

我们要使用的话,首先是要有一个邮箱服务器。这边呢我们选择QQ邮箱进行设置。进入网页版QQ邮箱:

https://mail.qq.com/cgi-bin/frame_html?sid=4CD2J2Pkky9oNmwI&r=6f5646e6c0f5f5b4497c1a421d16c11f&lang=zh

找到设置-》账户-》如果没有打开如图所示的POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务就打开服务,打开了就点击管理服务

显示如下页面

然后点击生成授权码

发送成功后就可以看到

**这里一定要记得赋值授权码不然以后是看不到的。**这里是授权码管理页面,如果察觉授权码已经泄露,则可以点击停用。

代码配置

接下来就可以在config.py中进行配置了

MAIL_SERVER='smtp.qq.com'
MAIL_USE_SSL=True
MAIL_POR=465
MAIL_USERNAME='你的邮箱'
MAIL_PASSWORD='刚刚开通的授权码'
MAIL_DEFAULT_SENDER='你的邮箱'

现在的Flask现在就相当于是第三方软件。可以使用授权码来登录邮箱了。

然后就是在插件部分exts.py中使用配置

from flask_mail import Mailmail = Mail()

在app.py中引入mail

from exts import mail# 邮箱插件绑定appmail.init_app(app)

运行测试

在auth.py蓝图中测试一下

from exts import mail
from flask_mail import Message@bp.route('/mail/test')
def mail_test():# 创建一个信息对象 添加几个必填参数message = Message(subject="测试", recipients=['你要发送的吗目标邮箱'],body="这是一封测试邮件" )# 发送邮件mail.send(message)

大坑

这里有个大问题这是我们去启动这个路由的时候,会报错

UnicodeEncodeError: 'ascii' codec can't encode characters in position 49-52: ordinal not in range(128)

填坑

这是需要根据
https://blog.csdn.net/qq_46983701/article/details/121206698
这篇文章改一下代码。然后重新运行就可以了。

实例编写

首先明确发验证码首先需要什么

  • 目标邮箱
  • 验证码

目标邮箱来自注册用户的表单中,可以选择在路由路径的后面加"/"的方式来接受参数

验证码的话是可以通过各种随机的方式来生成,这里呢我们选择string.digists,因为它是’0123456789’我们可以从中随机取4位来当作验证码,因为太短了当用户量大的时候会有几率产生重复,所以我们可以使用字符串的*语法来将其变为原来字符串的四倍的样子。

初步实现

import string@bp.route('/email_captcha/<email>')
def getEmailCaptcha(email):# 这里的 digits 是 '0123456789'  str*4 就是将str复制4份然后拼接起来source = string.digits * 4# 现在的captcha是一个有四个元素的列表captchalist = random.sample(source, 4)# 将数组转化为字符串captcha = "".join(captchalist)message = Message(subject="这是您的注册验证码", recipients=[email], body="您的验证码是:" + captcha)mail.send(message)return "success"

这里呢项目运行之后,使用127.0.0.1/emai_captcha/xxxx@example.com即可将验证码法送至对应邮箱

新问题

我们如何将前端输入的验证码与后端生成的进行比对

这个就要用到缓存

  • memcached :这个是缓存到内存中 当服务器断电了 或者什么故障数据很容易就没了
  • redis :这个是存储到本地硬盘中
  • 用数据库的方式去存储:速度慢

这里就姑且使用MySQL暂时解决这个问题

#在models.py 编写验证码模型类class EmailModel(db.Model):__tablename__ = "email_captcha"id = op.InsertColumn(db, db.Integer, iskey=True, autoincrement=True)email_box = op.InsertColumn(db, db.String(100), nullable=False)captcha = op.InsertColumn(db, db.String(100), nullable=False)
# 在auth.py蓝图中引入from models import UserModel, EmailModel
from app import db#添加调用@bp.route('/email_captcha/<email>')
def getEmailCaptcha(email):# 这里的 digits 是 '0123456789'  str*4 就是将str复制4份然后拼接起来source = string.digits * 4# 现在的captcha是一个有四个元素的列表captchalist = random.sample(source, 4)# 将数组转化为字符串captcha = "".join(captchalist)message = Message(subject="这是您的注册验证码", recipients=[email], body="您的验证码是:" + captcha)mail.send(message)# 用数据库存储验证码来验证前后端验证码是否一致emailcaptcha = EmailModel(email_box=email, captcha=captcha)db.session.add(emailcaptcha)db.session.commit()return "success"然后使用三步曲去进行数据库同步只需要后两步flask db migrate
flask db upgrade

统一返回格式Restful

@bp.route('/email_captcha/<email>')
def getEmailCaptcha(email):# 这里的 digits 是 '0123456789'  str*4 就是将str复制4份然后拼接起来source = string.digits * 4# 现在的captcha是一个有四个元素的列表captchalist = random.sample(source, 4)# 将数组转化为字符串captcha = "".join(captchalist)message = Message(subject="这是您的注册验证码", recipients=[email], body="您的验证码是:" + captcha)mail.send(message)# 用数据库存储验证码来验证前后端验证码是否一致emailcaptcha = EmailModel(email_box=email, captcha=captcha)db.session.add(emailcaptcha)db.session.commit()# 统一返回格式 Restfulreturn jsonify({"code": 200, "message": "captcha has send successfully!验证码发送成功!", "data": None})

前端调用后端接口

这就不得不提一下Jquery

jQuery是一个JavaScript库,旨在使Web开发更加便捷。它封装了一系列通用的JavaScript功能,如DOM操作和事件处理,使得JavaScript代码编写和书写更加容易。使用jQuery可以有效地缩短Web应用程序的开发周期并提高开发效率。除此之外,jQuery还有丰富的插件供用户选择使用,例如日期选择器、轮播等常见组件,这些插件可以帮助开发人员快速实现常见的UI功能。

所以在这里我们就要使用到jQuery的一个传参功能

这里我们从http://xiazai.jb51.net/jslib/jquery/jquery-3.6.0.rar这里来下载一下jQuery,然后复制到项目中即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJ1y7gzb-1682600291121)(C:\Users\huang\AppData\Roaming\Typora\typora-user-images\image-20230426105050747.png)]

接下来就是在regist.html中引入jquery-3.6.0.min.js文件 以及调用jQuery的register.js文件

{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/login_regist.css') }}"><script src="{{ url_for('static',filename='jquery-3.6.0/jquery-3.6.0.min.js') }}"></script><script src="{{ url_for('static',filename='js/register.js') }}"></script>
{% endblock %}

这里呢又有一个问题就是,网站的html代码是从上往下执行的,但是button是在js的下面,当js执行的时候button还没有加载。所以在regist.js中要这么写

//整个网页加载完毕后再执行 基础语法: $(selector).action()
$(document).ready(function () {$("#captcha_btn").click(function (event) {// 阻止触发默认事件 例如将表单提交给actionevent.preventDefault();// 获取输入的邮箱email_value = $("#email_box").val()// alert(email_value)// 发送Ajax请求 默认是get请求$.ajax({// 接口的相对路径url: "http://127.0.0.1:5000/email_captcha/" + email_value,success: function (res) {if(res["code"]===200){alert(res["message"])}else alert("出现了错误")},fail: function () {alert('发生了错误')}})})}
)

按钮倒计时的实现

注意这个button的type应该是button一定要指定

function BindEmailCaptcha() {$("#captcha_btn").click(function (event) {// 阻止触发默认事件 例如将表单提交给action// event.preventDefault();// button的jQuery对象 方便后面动态定义按钮的显示文字var $this = $(this)// 获取输入的邮箱email_value = $("#email_box").val()// alert(email_value)// 发送Ajax请求 默认是get请求$.ajax({// 接口的相对路径url: "http://127.0.0.1:5000/email_captcha/" + email_value,success: function (res) {if (res["code"] === 200) {alert(res["message"])var countdown = 10;// 开始倒计时之前取消绑定事件 当倒计时结束要重新绑定事件$this.off("click")// 设置一个定时器,每秒执行一次var timer = setInterval(function () {$this.text("(" + countdown + ")")countdown -= 1if (countdown <= 0) {// 清理到定时器clearInterval(timer)$this.text('获取验证码')$this.click(BindEmailCaptcha())  //递归调用}}, 1000)} else alert("出现了错误")},fail: function () {alert('发生了错误')}})})
}//整个网页加载完毕后再执行 基础语法: $(selector).action()
$(document).ready(function () {$("#captcha_btn").click(function (event) {// 阻止触发默认事件 例如将表单提交给actionevent.preventDefault();// button的jQuery对象 方便后面动态定义按钮的显示文字var $this = $(this)// 获取输入的邮箱email_value = $("#email_box").val()// alert(email_value)// 发送Ajax请求 默认是get请求$.ajax({// 接口的相对路径url: "http://127.0.0.1:5000/email_captcha/" + email_value,success: function (res) {if (res["code"] === 200) {alert(res["message"])var countdown = 10;// 开始倒计时之前取消绑定事件 当倒计时结束要重新绑定事件$this.off("click")// 设置一个定时器,每秒执行一次var timer = setInterval(function () {$this.text("(" + countdown + ")")countdown -= 1if (countdown <= 0) {// 清理到定时器clearInterval(timer)$this.text('获取验证码')$this.click(BindEmailCaptcha())}}, 1000)} else alert("出现了错误")},fail: function () {alert('发生了错误')}})})}
)

注册功能的收尾

表单验证

准备工作

要使用flask-wtf 插件 使用命令 pip install flask-wtf 安装即可,wtforms会自动安装

安装完成后 新建一个forms.py文件再blueprints中 使用validators.Email 要安装 pip install email_validator

# 这里是表单验证
import wtforms
# 这里的是验证器
from wtforms import validators
from models import UserModel, EmailModel
from app import dbclass RegistForm(wtforms.Form):email = wtforms.StringField(validators=[validators.Email(message="邮箱格式不正确")])captcha = wtforms.StringField(validators=[validators.length(min=4, max=4, message="验证码位数不对")])telephone = wtforms.StringField(validators=[validators.length(min=11, max=11)])username = wtforms.StringField(validators=[validators.InputRequired(message=u'请输入用户名')])password1 = wtforms.StringField(validators=[validators.InputRequired()])password2 = wtforms.StringField(validators=[validators.InputRequired(), validators.EqualTo('password1')])# 自定义验证 1.邮箱是否已经被注册 2.验证码是否正确def validate_email(self, field):self.email = field.datauser = UserModel.query.filter_by(email=self.email).first()if user:raise wtforms.ValidationError(message="不要用人家的邮箱注册!用你自己的!")def validate_captcha(self, field):captcha = field.dataemail = self.emailiscaptcha = EmailModel.query.filter_by(captcha=captcha, email_box=email).first()if not iscaptcha:raise wtforms.ValidationError(message="验证码不正确看准了再输入")# else:  #使用起来太浪费时间  可以写一个脚本定期清理#     db.session.delete(iscaptcha)#     db.session.commit()

注册视图函数的编写:

# 这里做一下处理 当method是get类型的时候触发跳转注册页面 当method 是post请求时执行注册过程
@bp.route('/register', methods=['GET', 'POST'])
def register():if request.method == 'GET':return render_template("regist.html")else:# 进行表单验证 执行注册操作 使用request.form 获取前端数据form = RegistForm(request.form)# 这里会自动的调用 forms.py中的验证器 返回值类型为 Booleanif form.validate():return "success"else:print(form.errors)return "failure"

将用户数据存储到数据库

import random
from flask import Blueprint, render_template, jsonify, redirect, url_for
from exts import mail
from flask_mail import Message
import string
from models import UserModel, EmailModel
from app import db
from flask import request
from .forms import RegistForm
from werkzeug.security import generate_password_hash# 第一个参数是蓝图的名字,第二个参数是指蓝图文件的__name__ 第三个参数是本文件路由的目录前缀 "/" 表示根目录
bp = Blueprint("auth", __name__, url_prefix="/")@bp.route('/')
def index():return render_template("index.html")@bp.route('/login')
def login():return render_template("login.html")# @bp.route('/mail/test')
# def mail_test():
#     # 创建一个信息对象 添加几个必填参数
#     message = Message(subject="邮件测试", recipients=['huangshiwei15222@outlook.com'], body="别学习了,歇会吧,摸会鱼")
#     # 发送邮件
#     mail.send(message)
#     return "邮件发送成功"
@bp.route('/email_captcha/<email>')
def getEmailCaptcha(email):# 这里的 digits 是 '0123456789'  str*4 就是将str复制4份然后拼接起来source = string.digits * 4# 现在的captcha是一个有四个元素的列表captchalist = random.sample(source, 4)# 将数组转化为字符串captcha = "".join(captchalist)message = Message(subject="这是您的注册验证码", recipients=[email], body="您的验证码是:" + captcha)mail.send(message)# 用数据库存储验证码来验证前后端验证码是否一致emailcaptcha = EmailModel(email_box=email, captcha=captcha)db.session.add(emailcaptcha)db.session.commit()# 统一返回格式 Restfulreturn jsonify({"code": 200, "message": "captcha has send successfully!验证码发送成功!", "data": None})# 这里做一下处理 当method是get类型的时候触发跳转注册页面 当method 是post请求时执行注册过程
@bp.route('/register', methods=['GET', 'POST'])
def register():if request.method == 'GET':return render_template("regist.html")else:# 进行表单验证 执行注册操作 使用request.form 获取前端数据form = RegistForm(request.form)# 这里会自动的调用 forms.py中的验证器 返回值类型为 Booleanif form.validate():# 将用户数据存储到数据库username = form.username.datapassword_source = form.password1.datatelephone = form.telephone.dataemail = form.email# 这里对密码进行加密处理password = generate_password_hash(password_source)user = UserModel(username=username, password=password, telephone=telephone, email=email)db.session.add(user)db.session.commit()# 注册成功后 重定向到登录页面   url_for 的作用就是将视图函数转换位urlreturn redirect(url_for("auth.login"))else:print(form.errors)return redirect(url_for("auth.register"))

可能会报错密码太长了可以再models中把密码字段的位数改长一点。

登录页面

登录页面和注册界面类似

需要编写一个js文件,来绑定登录按钮的click事件

// login.js$(document).ready(function () {$("#login_btn").click(function () {telephone = $("#tele_box").val()password = $("#pwd_box").val()$.ajax({url: "http://127.0.0.1:5000/login",method: "POST",})})
})

还要在login.html中引入

{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/login_regist.css') }}"><script src="{{ url_for('static',filename="js/login.js") }}"></script>
{% endblock %}

然你在服务端配置auth.py

步骤是

  • 点击登录发送请求
  • 通过表单数据和数据库数据对比(先判断手机号是否存在,如果存在就将那一条记录的对象取出备用),判断是否登录成功
  • 如果登录成功就保存登录状态session
@bp.route('/login/', methods=['GET', 'POST'])
def login():data = request.formif request.method == 'POST':form = LoginForm(data)if form.validate():user = UserModel.query.filter_by(telephone=data['telephone']).first()if user:# print(data["telephone"])# check_password_hash() 第一个参数是加密之后的密码 第二参数是明文密码 返回值类型为boolif check_password_hash(user.password, data['password']):# cookie 不适合存储太多的数据,一般用来存放登录授权的东西存储再浏览器中# 用session(flask引入)存储登录状态  flask 中的 session 是经过加密后存储到cookie中的# 使用session必须要在 config.py中添加一个 SECRET_KEY 相当于MD5的盐 只不过 MD5的盐不是必须的session['user_id']=user.id  # 和字典的存储格式类似  --- 键值对return redirect(url_for("qa.index"))else:return "密码错误"else:return "查无此人"else:print(form.errors)return redirect(url_for("auth.login"))else:return render_template("login.html")

当我们登录成功时可以看到cookie已经被存到浏览器中了

用钩子函数(HOOK)定义全局对象user

第一个钩子函数 见面知意,就是在发送请求之前做一些改动

from flask import session,g
@app.before_request
def my_before_request():# 这里的解密工作flask已经帮我们做好user_id = session.get('user_id')if user_id:user = UserModel.query.get(user_id)# 这里的g是一个全局变量 可以在项目的任何地方都可以使用user对象setattr(g, "user", user)else:#  这里如果不设置的话在下面用g.user 时会报错setattr(g, "user", None)

第二个钩子函数 :

注册模板上下文处理器函数。这些函数在呈现模板之前运行。返回的字典的键将作为模板中可用的变量添加。

@app.context_processor
def my_context_processor():# 将g.user 注册为上下文变量 让每个模板文件中都有这么一个变量return {"user": g.user}

显示登录还是用户名

使用jinja2语法来控制哪一块代码生效

{% if user %}<li><span>{{ user.username }}</span></li><li><a href="{{ url_for('auth.login') }}">注销</a></li>
{% else %}<li><a href="{{ url_for('auth.login') }}">登录</a></li><li><a href="{{ url_for('auth.register') }}">注册</a></li>
{% endif %}

现在是已经有显示效果了

注销功能的实现

注销就是清除浏览器中的cookie的session的值

在auth.py中新建一个路由

@bp.route("/logout")
def logout():# 清除浏览器中的cookie的session即可session.clear()return redirect(url_for("auth.login"))

这样当点击注销的时候就可以清除浏览器cookie并跳转到登录页面了

发布问答功能的实现

创建表

发布功能首先要解决的是要创建一个ORM映射类,用来建立存放问答的内容,然后更新到数据库(二步曲)

# models.pyclass QaModel(db.Model):__tablename__ = "questions"id = op.InsertColumn(db, db.Integer, iskey=True, autoincrement=True)title = op.InsertColumn(db, db.String(100), nullable=False)text = op.InsertColumn(db, db.String(10000), nullable=False)publish_time = db.Column(db.DateTime, default=datetime.now)# 外键user_id = db.Column(db.Integer, db.ForeignKey("user.id"))# 设置反向引用  使用用户的id就可以查到 该用户的所有文章了author = db.relationship(UserModel, backref="articles")

创建表单验证

数据库的表建立了,那么就可以验证数据然后将数据存到数据库了。

这里呢,也是需要表单验证的,防止数据库中存放乱七八糟的东西。

# forms.pyclass PublishForm(wtforms.Form):title = wtforms.StringField(validators=[validators.length(min=2, max=100, message="标题长度不合适哟")])content = wtforms.StringField(validators=[validators.length(min=3, message="问的太少了")])

编写服务端接口

# qa.py# 发布问答路由
@bp.route('/question', methods=['GET', 'POST'])
def question():if request.method == "GET":return render_template("question.html")else:form = PublishForm(request.form)if form.validate():if g.user != None:print(g.user)title = form.title.datacontent = form.content.dataquestion = QaModel(title=title, text=content, author=g.user)op.update_add(db=db, record_object=question)return redirect(url_for("qa.index"))else:return redirect(url_for('auth.login'))else:return {'code': 404, "error": form.errors}

使用装饰器(拦路虎)过滤用户

新建一个文件.py

from functools import wraps
from flask import g,redirect,url_fordef login_required(func):# 保留传进来的func的信息 就相当于 func = example()@wraps(func)def inner(*args, **kwargs):if g.user:return func(*args, **kwargs)else:return redirect(url_for("auth.login"))return inner

在要使用装饰器的蓝图中引入 并以注解的方式使用

from decorators import login_required# 发布问答路由
@bp.route('/question', methods=['GET', 'POST'])
# 将下面一整个函数传入到装饰器中
@login_required
def question():if request.method == "GET":return render_template("question.html")else:form = PublishForm(request.form)if form.validate():print(g.user)title = form.title.datacontent = form.content.dataquestion = QaModel(title=title, text=content, author=g.user)op.update_add(db=db, record_object=question)return redirect(url_for("qa.index"))else:return {'code': 404, "error": form.errors}

此时我们在未登录的状态下再去点击发布问答,就会自动跳转到登录页面

首页的渲染

首页渲染的话需要首先要有数据

# qa.py@bp.route('/index')
def index():# 获取到全部问答记录并且安装时间排序从新到旧进行展示questions = QaModel.query.order_by(QaModel.publish_time.desc()).all()# 这里将记录对象传到页面中以便渲染return render_template("index.html", questions=questions)

然后是首页模板的编写

{% extends 'base.html' %}
{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}">
{% endblock %}
{% block title %}首页
{% endblock %}{% block main %}<ul class="question-ul">{#遍历收到的记录对象#}{% for question in questions %}<li><div class="side-question"><img class="side-question-avatar" src="{{ url_for('static',filename="images/zhiliao.png") }}"></div><div class="question-main"><p class="question-title"><a href="#">{{ question.title }}</a></p><p class="question-content">{{ question.text }}</p><p class="question-detail"><span class="question-author">{{ question.author.username }}</span><span class="question-time">{{ question.publish_time }}</span></p></div></li>{% endfor %}</ul>
{% endblock %}

到这里首页就已经显示正常了

详情页的渲染

帖子内容的渲染

# qa.py@bp.route('/detail/<qa_id>')
def detail(qa_id):question = QaModel.query.get(qa_id)return render_template("detail.html",question=question)
{# detail.html#}
{% extends 'base.html' %}{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/detail.css') }}">
{% endblock %}{% block title %}详情{% endblock %}{% block main %}<h3 class="page-title">{{ question.title }}</h3><p class="question-info"><span>作者:{{ question.author.username }}</span><span>时间:{{ question.publish_time }}</span></p><hr><p class="question-content">{{ question.text }}</p><hr><h4 class="comment-group-title">评论({{ question.answers|length }}):</h4>
{% endblock %}

评论的渲染

评论也是需要一个ORM映射表的

# 这里是评论的映射表
class CommentsModel(db.Model):__tablename__ = "comments"id = op.InsertColumn(db, db.Integer, iskey=True, autoincrement=True)content = op.InsertColumn(db, db.Text, nullable=False)create_time = db.Column(db.DateTime, default=datetime.now)# 外键author_id = db.Column(db.Integer, db.ForeignKey("user.id"))question_id = db.Column(db.Integer, db.ForeignKey("questions.id"))# 关系anthor = db.relationship(UserModel, backref="answers")question = db.relationship(QaModel, backref=db.backref("answers", order_by=create_time.desc()

发布评论API

@bp.route("/publish_answer", methods=['POST'])
@login_required
def publish_answer():form = CommentForm(request.form)if form.validate():comment_text = request.form.get("content")question_id = request.form.get("question_id")comment = CommentsModel(content=comment_text, question_id=question_id, author_id=g.user.id)op.update_add(db, comment)return redirect(url_for("qa.detail", qa_id=question_id))else:return {"error": form.errors}

评论显示前端页面代码

{% extends 'base.html' %}{% block head %}<link rel="stylesheet" href="{{ url_for('static',filename='css/detail.css') }}">
{% endblock %}{% block title %}{{ question.title }}{% endblock %}{% block main %}<h3 class="page-title">{{ question.title }}</h3><p class="question-info"><span>作者:{{ question.author.username }}</span><span>时间:{{ question.publish_time }}</span></p><hr><p class="question-content">{{ question.text }}</p><hr><h4 class="comment-group-title">评论({{ question.answers|length }}):</h4><form action="{{ url_for('qa.publish_answer') }}" method="post"><input type="hidden" name="question_id" value="{{ question.id }}"><div class="form-container"><div class="form-group"><input type="text" placeholder="请填写评论" name="content" class="form-control"></div><div class="form-group"><button class="btn btn-primary" id="comment_btn" type="submit">评论</button></div></div></form><ul class="comment-group">    {% for answer in question.answers %}<li><div class="user-info"><img class="avatar" src="{{ url_for('static',filename='images/zhiliao.png') }}" alt=""><span class="username">{{ answer.author.username }}</span><span class="create-time">{{ answer.create_time }}</span></div><p class="comment-content">{{ answer.content }}</p></li>{% endfor %}</ul>
{% endblock %}

评论显示功能到这里就完成了,这里用到了许多外键的反向引用查询可能会有点绕,总之就是当使用到反向引用的模型时,对应的模型会根据外键进行查询到所有记录。

例如:

questions = QaModel.query.order_by(QaModel.publish_time.desc()).all()
获得questions对象
然后:在前端  这里的answers是 CommentsModel 定义的一个反向引用关系,此时就已经出发了查询,通过外键question_id在comments表中查到所有符合条件的评论,然后使用for循环遍历每个评论记录。这里又看到qu.author.username,这里的author是CommentsModel中定义的一个反向引用关系这就启动了在user表中按照author_id,查询记录,然后.username获取到用户名{%for qu in  questions.answers %}<div>{{qu.author.username}}</div>{%endfor%}

搜索功能

首先获取数据

@bp.route('/search', methods=['POST'])
def search():keyword=request.form.get("q")questions = QaModel.query.order_by(QaModel.publish_time.desc()).filter(QaModel.title.contains(keyword))print(questions)return render_template("index.html", questions=questions)

设置搜索按钮点击事件


<form class="navbar-form navbar-left" action="{{ url_for("qa.search") }}" method="post"><div class="form-group"><input name="q" type="text" class="form-control" placeholder="请输入关键字"></div><button type="submit" class="btn btn-default">查找</button></form>

到这里问答功能就全部完成了
e }}

{{ answer.content }}

{% endfor %}

{% endblock %}


评论显示功能到这里就完成了,这里用到了许多外键的反向引用查询可能会有点绕,总之就是当使用到反向引用的模型时,对应的模型会根据外键进行查询到所有记录。例如:```jinja2
questions = QaModel.query.order_by(QaModel.publish_time.desc()).all()
获得questions对象
然后:在前端  这里的answers是 CommentsModel 定义的一个反向引用关系,此时就已经出发了查询,通过外键question_id在comments表中查到所有符合条件的评论,然后使用for循环遍历每个评论记录。这里又看到qu.author.username,这里的author是CommentsModel中定义的一个反向引用关系这就启动了在user表中按照author_id,查询记录,然后.username获取到用户名{%for qu in  questions.answers %}<div>{{qu.author.username}}</div>{%endfor%}

搜索功能

首先获取数据

@bp.route('/search', methods=['POST'])
def search():keyword=request.form.get("q")questions = QaModel.query.order_by(QaModel.publish_time.desc()).filter(QaModel.title.contains(keyword))print(questions)return render_template("index.html", questions=questions)

设置搜索按钮点击事件


<form class="navbar-form navbar-left" action="{{ url_for("qa.search") }}" method="post"><div class="form-group"><input name="q" type="text" class="form-control" placeholder="请输入关键字"></div><button type="submit" class="btn btn-default">查找</button></form>

到这里问答功能就全部完成了

声明:本篇笔记是参考b站项目和w3cschool编写的入门笔记,仅供个人参考使用
b站视频链接:视频教程
w3cschool-Flask教程:w3cschool教程地址
gitee源码地址(看着视频手敲修改了一些地方):源代码

flask实战之问答平台(参考b站视频)相关推荐

  1. Flask实战2问答平台-登录限制(装饰器)

    我们来解决上一文章说到的,为登录不能点击发布问答功能 (一)先写一个装饰器 装饰器教程参考廖雪峰教程 http://t.cn/RK0SaGl from functools import wraps # ...

  2. Flask实战2问答平台-发布问答界面完成

    当我们登录进来后,我们便可以发布问答了,注意一点再未登录之前,我们是不能点击发布问答 的,这一点我们在下一篇文章中实现. (1)新建question.html,还是继承base.html {% ext ...

  3. Flask实战2问答平台-父模板抽离(登录注册界面)

    我们写一个基模板,然后让其他的继承. (1)新建基模板base.html 注意5,13,53行的{%%}块 <!DOCTYPE html> <html lang="en&q ...

  4. Flask实战2问答平台-问答详情完成

    首页文章已经布局完成,这次要完成的功能是点击文章,跳转到文章详情页. 新建detail.html {% extends 'base.html' %} {% block title %}详情{% end ...

  5. Flask实战2问答平台-首页布局,功能完成

    首页需要用来显示文章 index.html如下 {% extends 'base.html' %}{% block title %} 首页 {% endblock %}{% block head %} ...

  6. Flask实战2问答平台-发布问答功能完成

    我们需要新建一个表,可以采用两种方式: (1)手动建表 (2)使用flask_migrate. 说一下使用第二种方法建表的方式 (1)在models.py文件中添加如下代码 class Questio ...

  7. Flask实战2问答平台-完成登录注册功能

    本来可以提前完成这篇的,结果测试时发现了一些问题,稍后将会提到. 上篇中我们已经完成了登录注册的界面,现在具体完成其具体功能. 1.注册功能 因为注册成功后,才能登录,我们在主py文件中添加如下 @a ...

  8. Flask实战2问答平台--导航条

    项目总览 1.新建index.html 2.引入css,js .链接http://v3.bootcss.com/getting-started/ 3.导入当行条代码,代码链接http://v3.boo ...

  9. python flask框架发布问答平台注册页面_Python|Flask框架实现QQ账号登录

    本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章. 前期准备 因为注册QQ互联需要已备案的网站,所以需要准备一个已备案的网站与域名. 首先访问QQ互联平台 ...

最新文章

  1. 搜索引擎solr和elasticsearch
  2. 2019年5月21号总结
  3. wxWidgets:wxSplashScreen类用法
  4. 【钉钉PC】PC端钉钉清除缓存
  5. 在Axure中通过全局变量实现两个文本框与中继器联动
  6. y700支持m2硬盘_两块硬盘一起读写?奥睿科M.2 NVMe双盘位固态硬盘盒使用
  7. html单元格竖着排列,html表格,表头竖向固定,横向滚动的例子
  8. js实现图片加载中效果 loading
  9. 已知圆心及半径,通过MATLAB画圆
  10. 网络模块打线步骤及技巧解读
  11. Codeforces 1419B. Stairs
  12. 每日必读DZone News—对DevOps的关注
  13. 知乎:有哪些让你相见恨晚的 PPT 制作技术或知识?
  14. IE不能上网浏览的原因和解决办法
  15. 战火与秩序迁城显示服务器忙,战火与秩序怎么迁城 迁城方法和迁城技巧分享[图]...
  16. 微信分享解决wx not defined
  17. c# 开发文字识别软件
  18. LeetCode 迷宫系列(0490, 0499, 0505)
  19. 探究:光伏电站三大并网模式哪个更适合你?
  20. 三星复印机载体初始化步骤_夏普AR4020D复印机怎样载体初始化的步骤(希望从维修模式到载体初始化)...

热门文章

  1. Unity调试Android
  2. 在Mac电脑使用时间机器备份时如何加密?如何给照片加密?
  3. list redis 怎样做排行_redis实现排行榜效果
  4. 李宏毅机器学习笔记13:Why Deep
  5. 2年Android开发面经分享:跳槽网易个人创业失败后,拿到快手,字节,百度,美团的Offer之旅
  6. php MD5加密解密
  7. AppDomain加载与释放dll
  8. IOIO OTG开发板
  9. python支持面向对象设计_python 面向对象设计
  10. win2003 X64下32位程序兼容问题