赞
踩
新建一个项目,在项目中添加一个config.py文件,用来进行邮箱验证码,cookie,和session和一些加密等的配置
这样看来config.py是项目的一部分了,但是还要在app.py中进行导入
import config
app.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)
当一个项目很小的时候视图函数可以全写在app.py中,很简单,但是如果项目大了的话有很多视图函数,如果都写在app.py中的话,就会显得十分的臃肿。不利于后期维护。那么这就要使用到蓝图这个东西,他就是用来模块化的。
开始处理新项目或现有项目后,PyCharm 将扫描路由并将其列在 Endpoints(端点)工具窗口中,您可以在该窗口中对 URL 进行代码补全、导航和重构。 此工具窗口还提供了对端点的更好概览和对文档的快速访问
新建一个软件包(注意不是目录,软件包是带一个——init——.py文件的),一般都是命名为blueprints。然后在包内开发模块即可。
在保重我们新建一个蓝图就是一个python文件例如auth.py和qa.py,前者负责权限方面的蓝图,后者负责问答方面的蓝图
# auth.py from 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")
#引入蓝图对象
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
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模块包含以下重要类的定义。
它管理电子邮件消息传递需求。类构造函数采用以下形式:
flask-mail.Mail(app = None)
构造函数将Flask应用程序对象作为参数。
序号 | 方法与描述 |
---|---|
1 | **send()**发送Message类对象的内容 |
2 | **connect()**打开与邮件主机的连接 |
3 | **send_message()**发送消息对象 |
它封装了一封电子邮件。Message类构造函数有几个参数:
flask-mail.Message(subject, recipients, body, html, sender, cc, bcc,
reply-to, date, charset, extra_headers, mail_options, rcpt_options)
attach() - 为邮件添加附件。此方法采用以下参数:
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, Message app =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 Mail
mail = Mail()
在app.py中引入mail
from exts import mail
# 邮箱插件绑定app
mail.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即可将验证码法送至对应邮箱
我们如何将前端输入的验证码与后端生成的进行比对
这个就要用到缓存
这里就姑且使用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
@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() # 统一返回格式 Restful return 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) { // 阻止触发默认事件 例如将表单提交给action event.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 -= 1 if (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) { // 阻止触发默认事件 例如将表单提交给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 -= 1 if (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 db class 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.data user = UserModel.query.filter_by(email=self.email).first() if user: raise wtforms.ValidationError(message="不要用人家的邮箱注册!用你自己的!") def validate_captcha(self, field): captcha = field.data email = self.email iscaptcha = 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中的验证器 返回值类型为 Boolean
if 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() # 统一返回格式 Restful return 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中的验证器 返回值类型为 Boolean if form.validate(): # 将用户数据存储到数据库 username = form.username.data password_source = form.password1.data telephone = form.telephone.data email = 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 的作用就是将视图函数转换位url return 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
步骤是
@bp.route('/login/', methods=['GET', 'POST']) def login(): data = request.form if 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() 第一个参数是加密之后的密码 第二参数是明文密码 返回值类型为bool if 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已经被存到浏览器中了
第一个钩子函数 见面知意,就是在发送请求之前做一些改动
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.py
class 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.py
class 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.data content = form.content.data question = 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_for
def 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.data content = form.content.data question = 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 }}
评论显示功能到这里就完成了,这里用到了许多外键的反向引用查询可能会有点绕,总之就是当使用到反向引用的模型时,对应的模型会根据外键进行查询到所有记录。 例如: ```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源码地址(看着视频手敲修改了一些地方):源代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。