原贴:http://www.cnblogs.com/alima/p/5796298.html

简介:简单的集成flask,WTForms,包括跨站请求伪造(CSRF),文件上传和验证码。

一.安装(Install)

此文仍然是Windows操作系统下的教程,但是和linux操作系统下的运行环境相差甚微。

使用Python版本3.5.2.

上一篇文章提到Virtualenv环境运行Python,这次仍然建立Python虚拟运行环境以便实现不同数据包的隔离。

  • 创建wtfdemo虚拟运行环境

用控制台(管理员运行模式)进入(cd)到想要创建工程的路径下,创建wtfdemo文件夹。

mkdir wtfdemo

进入(cd)wtfdemo文件夹,创建Python虚拟运行环境。

virtualenv flaskr

出现如下字样,说明虚拟环境创建成功

PS:本次提供第二种创建Python虚拟运行环境的使用方法

virtualenv -p source_file\python.exe target_file

为什么提出第二种创建方法,你会发现,当你的Python Web程序开发多了以后,PC上难免安装了很多版本的Python运行环境。

举例:当PC上同时安装了Python2.7和Python3.5,运行virtualenv flaskr后,建立的Python虚拟运行环境是Python2.7版本的,但是我们的开发环境是Python3.5。

在控制台中输入一下指令,你就会发现问题。

virtualenv -h

出现下面图片显示,默认的virtualenv安装路径是Python2.7,也就是默认的安装的虚拟环境是Python2.7版本。

所以,在这种情况下,请指定你需要的Python版本,并建立虚拟运行环境。

  • 安装flask-wtf库文件

进入Python虚拟运行环境(在上一篇博文中写过),执行以下指令。

pip install flask
pip install flask-wtf
pip install flask-login
pip install flask-sqlalchemy

出现如下图所示,说明安装成功。

flask安装成功。

flask-wtf安装成功。

flask-login安装成功。(本次使用flask-wtf库时需要辅助以该运行库)

flask-sqlalchemy安装成功。

至此,环境配置阶段结束。

二.Flask-WTF

flask-wtf实现了简单集成WTForms,包括CSRF,文件上传以及Google内嵌的验证码。

特性:

①集成了WTForms。

②包含了带有CSRF的安全表单

③全局的CSRF保护

④验证码Recaptcha的支持

⑤支持Flask-Upload的文件上传

⑥多语言集成

但,成也集成败也集成,flask-wtf不包含WTForms中许多重要的API,在进行复杂作业时显得力不从心。

实际上,flask-wtf下整合的WTForm单元已经能基本满足一些小型网站的建设使用了。

三.在实例中应用

  • 文件结构

上一篇博文中,文件结构很是混乱,在实际维护中很不方便,会留下很多隐患,比如后期维护人员无法很快的切人项目中进行解读代码。

此处将各个模块进行分离,抽离出各个不同的模块。这样的有点是单个文件只操作自己的功能,代码可读性强,逻辑清晰。缺点就是大型项目时文件会有很多碎小的文件,需要经常使用from import语句从不同的文件中引用出不同的功能。

但是相对于优点,缺点所带来的风险是很小的。实际工作中,这样的分文件管理项目是极力推荐的,逻辑清晰,可维护性高是每一个优秀的项目应该具有的最基本属性。

本文采用以下文件结构。

wtfdemo→flaskr
    →templates →login.html
            →index.html
→uploads
→wtf.py
→model.py
→views.py
→run.py
→config.py
→form.py

本次应用实现一个简单的用户登录功能模块,文中涵盖的flask-login包将在下一篇博文中详细解释,如果想马上了解,请查看flask-login官方文档

  • 详细参数解析

config.py代码如下所示。

 1 import os
2
3 basedir = os.path.abspath(os.path.dirname(__file__))
4
5 SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'test.db')
6 SQLALCHEMY_TRACK_MODIFICATIONS = False
7
8 CSRF_ENABLED = True
9 SECRET_KEY = 'you will never guess'
10
11 RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J'
12 RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'

一句一句解释

①OS模块是Python标准库中的一个用于访问操作系统功能的模块,OS模块提供了一种可移植的方法使用操作系统的功能。

这里利用os.path.abspath()函数,取出该文件下的绝对路径。

②将取出的绝对路径下存放到basedir中,以便被其他文件引用。

③SQLALCHEMY_DATABASE_URI用于连接数据库,SQLALCHEMY_TRACK_MODIFICATIONS追踪对象的修改并且发送信号,这里在上一篇文章讲解过。

④CSRF_ENABLED为是否开启flask-wtf下的CSRF(跨站请求伪造)保护,True为开启保护状态。

⑤SECRET_KEY设置Flask的应用密匙,同时CSRF开启保护时,同时需要一个密匙,如果不在文件中明确指出,通常与你的 Flask 应用密钥一致。如果想要使用不同的密匙,请在文件中配置CSRF单独的密匙。

WTF_CSRF_ENABLED = False

⑥RECAPTCHA_PUBLIC_KEY与RECAPTCHA_PRIVATE_KEY分别为,必需公钥与必需私钥。

wtf.py代码如下。

 1 from flask import Flask
2 from flask_sqlalchemy import SQLAlchemy
3 from config import SQLALCHEMY_DATABASE_URI, SQLALCHEMY_TRACK_MODIFICATIONS
4 from flask_login import LoginManager, AnonymousUserMixin
5
6 app = Flask(__name__)
7 app.config.from_object('config')
8 db = SQLAlchemy(app)
9 lm = LoginManager()
10 lm.init_app(app)
11 lm.login_view = 'login'
12 lm.login_message = 'Please log in!'
13 lm.login_message_category = 'info'
14 lm.anonymous_user = AnonymousUserMixin
15 lm.session_protection = 'strong'
16 lm.refresh_view = 'login'
17 lm.needs_refresh_message = 'Please enter your info'
18 lm.needs_refresh_message_category = "refresh_info"
19
20 from model import User
21 import views

①引用包中的函数说明。

Flask函数为flask架构的主函数,一切flask架构的Web开发都是由此函数引申出来的。

SQLAlchemy函数为flask-sqlalchemy包的主函数,由此函数绑定相应的flask架构应用,就可以实现连接数据库的操作。

SQLALCHEMY_DATABASE_URI与SQLALCHEMY_TRACK_MODIFICATIONS为config.py中配置相应的参数,在此文件中被引用以便使用。

LoginManager函数是flask-login包的主函数,由此将flask架构和flask-login连接成一个整体。

②app.config.from_object函数将config文件下能配置到app的条目,绑定到app上。结果等同于直接绑定,等同结果如下。

app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = SQLALCHEMY_TRACK_MODIFICATIONS

③其他关于flask-login包下的函数,在下篇博文中将详细的讲解,本篇文章重点介绍flask-wtf包中的API使用。

form.py代码构成如下。

 1 from flask_wtf import Form
2 from wtforms import TextField, BooleanField
3 from wtforms.validators import DataRequired
4 from flask_wtf.recaptcha import RecaptchaField
5
6 from werkzeug import secure_filename
7 from flask_wtf.file import FileField
8
9 class LoginForm(Form):
10 username = TextField('username', validators=[DataRequired()])
11 password = TextField('password', validators=[DataRequired()])
12 remember_me = BooleanField('remember_me', default=False)
13 recaptcha = RecaptchaField()
14
15 class UploadForm(Form):
16 file = FileField()

①Form为flask-wtf的基础form类型,一切的网页表单都是继承Form父类的属性,并添加自己独有的处理

②TextField, BooleanField是WTForm构建的文字域和复选框域,对应HTML的<input type='text'/>和<input type='checkbox'/>标签。

③DataRequired验证TextField是否为空,为空返回一个错误信息(error message)

④在将文件保存在文件系统之前,要坚持使用secure_filename函数来确保文件名是安全的。

⑤flask-wtf整合了flask-upload中的部分函数,所以FileField不是从WTForm中引用出来的。FileField顾名思义,对应HTML标签<input type='file'/>标签。

⑥分析LoginForm类

1 class LoginForm(Form):
2 username = TextField('username', validators=[DataRequired()])
3 password = TextField('password', validators=[DataRequired()])
4 remember_me = BooleanField('remember_me', default=False)
5 recaptcha = RecaptchaField()

类成员构成: 变量名 = xxxField(),其中xxxField中可以选择传入<input/>标签的name,validators属性判断如果TextBox中为None则返回错误消息

变量名可以在相对应的HTML文件中使用对应的 form.变量名 使用。

解析:此处添加两个TextBox一个输入用户名,一个输入密码。一个CheckBox复选框,实现记住用户功能。最后一个为登陆时防止恶意登陆的验证码。

model.py代码构成。

 1 from wtf import db
2 from flask_login import UserMixin
3
4 class User(db.Model, UserMixin):
5 id = db.Column(db.Integer, primary_key=True)
6 username = db.Column(db.String(80), unique=True)
7 password = db.Column(db.String(80), unique=True)
8
9 def __init__(self, username, password):
10 self.username = username
11 self.password = password
12
13 def __repr__(self):
14 return '<User %r>' % self.username

①将wtf中模块中的db参数引用到model.py中,以创建ORM数据库映射关系

②其中UserMixin为flask-login中的用户类,其中包含了一些可以在实现用户登录时验证时需要的属性。

③User类中定义id,用户名及密码属性。__init__为User类的构造函数,__repr__方便在调试中输出数据时使用。

views.py代码构成。(下边的代码包含了Flask框架下的一些简单应用,请在有一定Flask架构基础的情况下,可以快速的切入下段代码。其中flask-login在本文只一笔带过,将在下篇博文中详细解析flask-login包的API使用实例教程)

 1 from flask import render_template, flash, redirect, session, url_for, request, g
2 from flask_login import login_user, logout_user, current_user, login_required, AnonymousUserMixin, fresh_login_required, login_fresh
3 from wtf import app, db, lm, User
4 from form import LoginForm, UploadForm
5
6 @app.before_request
7 def before_request():
8 g.user = current_user
9
10 @lm.user_loader
11 def load_user(id):
12 user = User.query.filter_by(id=id).first()
13 return user
14
15 @app.route('/login', methods=['GET', 'POST'])
16 def login():
17 if g.user is not None and g.user.is_authenticated:
18 login_fresh()
19 session['_fresh'] = False
20 return redirect(url_for('index'))
21 if g.user.is_active == True:
22 flash('active is True')
23 else:
24 flash('active is False')
25 form = LoginForm()
26 if form.validate_on_submit():
27 user = User.query.filter_by(username=form.username.data, password=form.password.data).first()
28 if(user is not None):
29 login_user(user,remember=form.remember_me.data)
30 return redirect(request.args.get("next") or url_for('index'))
31 return render_template('login.html', form=form)
32
33 @app.route('/', methods = ['GET', 'POST'])
34 @app.route('/index', methods=['GET', 'POST'])
35 @login_required
36 def index():
37 form = UploadForm()
38
39 if form.validate_on_submit():
40 filename = secure_filename(form.file.data.filename)
41 form.file.data.save('uploads/' + filename)
42 return redirect(url_for('upload'))
43
44 return render_template('upload.html', form=form)
45
46 @app.route('/logout')
47 @login_required
48 def logout():
49 logout_user()
50 return redirect(url_for('login'))

①from form import LoginForm, UploadForm, InfoForm

将form.py中定义的表单类引入到该文件中。

②before_request函数,定义为flask-login绑定用户时使用,在发起请求之前首先调用此函数。

③load_user函数,定义为根据ID取出用户实体。

④login函数,详细分析:

@app.route('/login', methods=['GET', 'POST'])         #接受前段POST和GET的表单信息,绑定到发布/login页面上
def login():
if g.user is not None and g.user.is_authenticated:#判断用户是否登录过系统,或者是否使用过记住用户这个功能
return redirect(url_for('index')) #如果满足条件,则跳转到/index界面
form = LoginForm() #实例化LoginForm()
if form.validate_on_submit(): #validate_on_submit函数判断form表单中是否有提交动作,如果有,则获取该动作
user = User.query.filter_by(username=form.username.data, password=form.password.data).first()#获取是否存在用户(这种未加密登录方法不推荐在实际项目中使用)
if(user is not None): #判断取出的数据库用户实体是否为空
login_user(user,remember=form.remember_me.data)#将用户实体,记住用户选项,绑定到flask-login中
return redirect(request.args.get("next") or url_for('index'))#重定向到next页面或是index页面
return render_template('login.html', form=form) #使用模板函数,将form绑定到login.html页面上

⑤index函数分析

@app.route('/', methods = ['GET', 'POST'])            #接受前段POST和GET的表单信息,绑定到发布跟目录上
@app.route('/index', methods=['GET', 'POST'])        #接受前段POST和GET的表单信息,绑定到发布/index页面上
@login_required #验证用户是否登录
def index():                                     
form = UploadForm() #实例化UploadForm()
if form.validate_on_submit():                  #判断表单中是否存在提交动作
filename = secure_filename(form.file.data.filename)#secure_filename函数确保文件名是安全,并赋值到filename中
form.file.data.save('uploads/' + filename) #将文件保存到工程目录的位置
return redirect(url_for('index')) #上传完文件后将页面重定向到index.html
return render_template('index.html', form=form) #使用模板函数

⑥logout函数分析

@app.route('/logout') #将函数绑定到/logout链接上
@login_required #验证用户是否登录
def logout():
logout_user() #清空session中的缓存
return redirect(url_for('login'))#重定向到login.html页面上

login.html代码构成

<html>
<head>
Login-Demo
</head>
<body>
<form method="POST" action="">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }} </li>
{% endfor %}
</ul>
<hr/>
{% endif %}
{% endwith %}
{{form.csrf_token}}
{{form.username(size=80)}}
{% for error in form.username.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
{{ form.password(size=80) }}
{% for error in form.password.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
<p>{{ form.remember_me }} Remember Me</p>
<p></p>
<p><input type="submit" value="Sign In"></p>
<a href="{{ url_for('logout') }}">Logout</a>
</form>
</body>
</html>

一下讲解表单中各个语句的含义。

{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }} </li>
{% endfor %}
</ul>
<hr/>
{% endif %}
{% endwith %}

①get_flashed_messages函数获取flask后端发动的flash消息。

②判断如果是消息类型的话循环输出

{{form.csrf_token}}
{{form.username(size=80)}}
{% for error in form.username.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
{{ form.password(size=80) }}
{% for error in form.password.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
<p>{{ form.remember_me }} Remember Me</p>
<p><input type="submit" value="Sign In"></p>
<a href="{{ url_for('logout') }}">Logout</a>

①form.csrf_token设置开启CSRF保护

②form.username(size=80),创建一个长度为80px的TextBox,对应前面提到的LoginForm创建的成员变量username

③for error in form.username.errors,此处抓取validators=[DataRequired()]返回的错误信息(error message)

④form.password(size=80),创建一个长度为80px的TextBox,对应前面提到的LoginForm创建的成员变量password

⑤form.remember_me,创建一个CheckBox,对应前面提到的LoginForm创建的成员变量remember_me

⑥<input type="submit" value="Sign In">添加一个按钮,点击后触发form.validate_on_submit()函数

⑦<a href="{{ url_for('logout') }}">前段调用后端函数的一种方法,调用后端函数view.py中的logout函数

index.html代码构成

<html>
<head>
{% if filedata %}
<h3>{{ filedata.filename }}</h3>
{% endif %}
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }} </li>
{% endfor %}
</ul>
<hr/>
{% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">Logout</a>
<form method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ form.file }}
<input type="submit">
</form>
</body>
</html>

将UploadForm类中的file呈现在页面上。

最后,我们为了运行程序,添加run.py文件。

from wtf import app
app.run(debug=True)

运行如下代码,执行此次实例程序。

四.运行实例程序

打开浏览器输入 http://127.0.0.1:5000/login,进入登录界面。

关于数据库的操作已经在上一篇博文里写的很清楚了,在数据库中已经添加了用户(username=john,password=1)。

①当直接点击Sign In按钮之后,会出现如下图显示的效果。

出现上面[This field is required.]是from.py文件中validators=[DataRequired()]的功劳,这段代码帮你验证了提交表单中TextBox是否为空。

②当输入用户名和密码之后,你会发现,点击Sign In按钮之后,并没有跳转到预期的index.html界面。

Q:为什么没有跳转。

A:这和我们之前的一个疏忽造成的,之前的代码我已经留下了一个位置修改这个问题。

下面我们来分析,问题是点击按钮无法执行代码中的跳转,说明后端代码绑定控件的时候出现问题。

那是什么问题呢,flask-wtf已经为你提供了一个可以查出问题的方法,使用form.errors检索出在提交表单之后发生的问题。

在views.py文件的login函数中添加如下代码(在实例化LoginForm之后添加)

flash(form.errors)

重新运行程序,输入用户名和密码,点击Sign In按钮。

看到{'recaptcha': ['The response parameter is missing.']}你发现问题了么,对的,就是因为笔者的一个不小心,没有在HTML文件实例化recaptcha,由于验证码和按钮有一个相互作用的关系,试想当你登录一个系统,不输入验证码直接点击登录,会发生一样的问题。

解决办法:

在login.html添加如下代码。

<p>{{ form.remember_me }} Remember Me</p>
<p>{{ form.recaptcha }}</p>#添加的代码
<p><input type="submit" value="Sign In"/><p>

再次重新启动程序,这回我们就会看到我们需要的样子。

输入用户名和密码,按验证码要求点击相应的图片,点击Sign In按钮,登录成功!

点击Choose File,让我们上传一个文件试试看,点击upload按钮之后,你会在项目根目录下的Uploads文件夹找到你上传的文件。

PS:本地运行并上传文件,不会出现问题。如果你是发布在网络中的时候,请注意一下几个问题。

①图片同名问题。

②上传文件夹权限问题。(笔者之前在项目中处理的办法是将读写权限赋予给everyone用户,但是这样会大大降低服务器的安全性)

关于flask-wtf中文件上传的一些解析,将在以后讲解flask-upload的时候一起讲解。

关于本文中的flask-login包,我们将在下一篇文章中解析。

以上就是基本的flask-wtf使用实例说明,欢迎探讨转载及引用参考,你们的回应是我的动力。

如果文中存在错误或是某个地方说的不够精确,劳烦批评指出。

以上,辛苦了。

[转帖]关于flask-login中各种API使用实例的更多相关文章

  1. [Python][flask][flask-login]关于flask-login中各种API使用实例

    本篇博文跟上一篇[Python][flask][flask-wtf]关于flask-wtf中API使用实例教程有莫大的关系. 简介:Flask-Login 为 Flask 提供了用户会话管理.它处理了 ...

  2. [Python][flask][flask-wtf]关于flask-wtf中API使用实例教程

    简介:简单的集成flask,WTForms,包括跨站请求伪造(CSRF),文件上传和验证码. 一.安装(Install) 此文仍然是Windows操作系统下的教程,但是和linux操作系统下的运行环境 ...

  3. 使用python的Flask实现一个RESTful API服务器端

    使用python的Flask实现一个RESTful API服务器端 最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了. 本文 ...

  4. python flask的request模块以及在flask编程中遇到的坑

    一.首先来讲讲遇到的坑: 1.linux下package的打包引用: """ 路径结构如下: ./project ./bin ./api ""&quo ...

  5. 转:使用python的Flask实现一个RESTful API服务器端

    提示:可以学习一下flask框架中对于密码进行校验的部分.封装了太多操作. 最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了 ...

  6. 在 Flask 应用中使用 gevent

    在 Flask 应用中使用 gevent 普通的 flask 应用 通常在用 python 开发 Flask web 应用时,使用 Flask 自带的调试模式能够给开发带来极大便利.Flask 自带的 ...

  7. 如何在项目中封装api

    一般在项目中,会有很多的api请求,无论在vue,angular,还是react中都应该把接口封装起来,方便后期的维护. 1.新建一个api文件 我们可以在项目的分目录下创建一个api文件夹,在这里面 ...

  8. HDFS中JAVA API的使用

    HDFS中JAVA API的使用   HDFS是一个分布式文件系统,既然是文件系统,就可以对其文件进行操作,比如说新建文件.删除文件.读取文件内容等操作.下面记录一下使用JAVA API对HDFS中的 ...

  9. Appium中部分api的使用方法

    使用的语言是java,appium的版本是1.3.4,java-client的版本是java-client-2.1.0,建议多参考java-client-2.1.0-javadoc. 1.使用Andr ...

随机推荐

  1. c++学习 - int 和 string 的相互转换

    在C++中会碰到int和string类型转换的. string -> int 首先我们先看两个函数: atoi 这个函数是把char * 转换成int的.应该是属于标准库函数.在想把string ...

  2. 如何以正确的顺序重新安装驱动程序 | Dell 中国

      购买 支持 社区 我的帐户     购买 支持 社区   如何以正确的顺序重新安装驱动程序 在戴尔笔记本电脑或台式机上手动重新安装Microsoft Windows操作系统后,您还必须以正确的顺序 ...

  3. socket 由浅入深系列------ 原理(一)

    来自:网络整理 个人觉得写一个网络应用程序没有是一件非常easy的事.其实,我们刚開始的时候总觉得的原则: 建立------>连接套接字------->接受一个连接---->发送数据 ...

  4. 菜鸟运维笔记:安装与配置Apacheserver

    前几天在在阿里花了49.5买了一个月的主机. 试着好用再续费吧. 地域:青岛 可用区:青岛可用区A CPU:1核 内存:512MB 带宽:1Mbps 操作系统:CentOS 6.5 64位 云盾:是 ...

  5. 消息驱动bean(MDB)实例

    到眼下为止前面介绍的有关JavaEE的东西都是同步的.也就是说调用者调用某个方法.那么这种方法必须马上运行并返回运行结果. 用官方一些的语言来说就是"client通过业务接口调用一个方法,在 ...

  6. Xcode中git的用法介绍与&quot;Please tell me who you are&quot;问题的解决方式

    我在之前多篇博客中解说了怎样使用命令行操作git,能够大大提高我们的工作效率.详细能够參考<Git学习札记><Git学习札记--进阶>等文章.事实上对于同一个工具,我们有不同的 ...

  7. kvm虚拟化网络管理

    Linux Bridge 网桥管理 VM2 的虚拟网卡 vnet1 也连接到了 br0 上. 现在 VM1 和 VM2 之间可以通信,同时 VM1 和 VM2 也都可以与外网通信 # Vlan LAN ...

  8. 《转》 Ceilometer项目源代码分析----ceilometer项目源代码结构分析

    感谢朋友支持本博客,欢迎共同探讨交流.因为能力和时间有限,错误之处在所难免,欢迎指正! 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/gaoxingnengjisua ...

  9. 自己定义ViewGroup控件(一)-----&gt;流式布局进阶(一)

    main.xml <? xml version="1.0" encoding="utf-8"?> <com.example.SimpleLay ...

  10. msyql索引详解

    一.mysql查询表索引命令两种形式 1.mysql>SHOW INDEX FROM 'biaoming' 2.mysql>SHOW keys FROM 'biaoming' 运行结果如下 ...