flask框架搭的web项目
html应当写在templates文件夹里面
css文件还有静态资源(例如图片)应该放在static文件夹里面,直接访问 localhost:80/static/a.jpg
应用程序根目录是根据初始化app=Flask(__name__)的时候的代码在哪就决定了哪里是根目录
更改flask的默认设置静态资源位置:
关键词: static_folder
app = Flask(__name__, static_folder='hehe') # http://127.0.0.1:5000/hehe/haha/a.png app = Flask(__name__, static_folder="hehe/haha") # http://127.0.0.1:5000/haha/a.png
关键词: static_url_path:
前端访问资源文件的前缀目录。默认是/static,就是前端必须这样访问:<img src="/static/img/mylogo.jpg" />
我们改成 '',就可以这样访问了:<img src="/img/mylogo.jpg" />。就达到前端从根目录访问的目的了。
app = Flask(__name__, static_folder="hehe",static_url_path="") # http://127.0.0.1:5000/haha/a.png
关键词: url_for()
这个函数有很多的功能,可根据你定义的函数名获取到@app.route()这个路由语句传进来的url参数(动态的带尖括号<>的无效)
@app.route('/test') def tt(): return url_for("haha") # 页面会打出 /someurl @app.route("/someurl") def haha(): return url_for("tt") # 页面会打出 /test
关键词: redirect
功能就是跳转到指定的url,大部分情况下,我们都是用return关键字阻止下文的继续执行, 也可以和url_for方法一起使用,例如:
@app.route('/') def hello_world(): return 'Hello World' @app.route('/<name>/') def hello(name): if name == 'Harp': return 'Hello %s' % name else: return redirect(url_for('hello_world'))
在hello这个视图函数中,如果url传入的参数是Harp(即请求的网址是http://127.0.0.1:5000/Harp/),则返回'Hello Harp',其他情况则重定向到hello_world这个视图函数对应的网址'/'。
如何获取input标签的用户名和密码?
方式一:request.form['username']
<head> <meta charset="UTF-8"> <title>‹/title> </head> <body> <h1>HTTP 方法:{{method }J</h1> <form method="post"> <div> <input type="text" name=iusername" placeholder="User name"/> </div> <div> <input type="password" name="password" placeholder="Password" /> </div> <input type="submit"/> </form> </body> </html>
下面指定的username和password是和上面的form中的name是一致的
Capp.route('/login', methods=['GET', 'POST']) def login(): if request.method=='POST': username=request.form['username'] password=request.form['password'] return render_template('login.html', method=request.method)
方式二:request.args['username']
如果要通过url来传送数据 , https://localhost:5000/login?username=jenrey 就要用下面这种方式获取参数值
@app.route('/login', methods=['GET', 'POST') def login(): if request.method=='POST': username=request.form['username'] password=request. form['password'] else: username = request. args['username'] return render_template('login.html', method=...
关键字: render_template
重要, 作用:render_template不仅能渲染静态的html文件,也能传递参数给html。在templates文件夹建立一个html文件,内容随便写:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <h1>This is index page</h1> </body> </html>
需要在项目最开始的代码中找到Flask的主变量, 在里面多传一个template_folder参数, 而且要import关键字来引入render_template, 例如:
from flask import Flask, url_for, redirect, render_template app = Flask(__name__, template_folder="template")
这段代码中的template对应项目根目录中的template, 然后把你想要渲染的视图文件塞到template文件夹里。
谈如何写入cookie:
from flask import Flask,render_template,request,redirect,url_for,make_response
... 中间代码省略
@app.route('/') def index(): response = make_response(render_template(index.html, title='Welcome')) response.set_cookie('username', '') return response
DIY异常页面, 关键字: @app.errorhandler(400)
不需要指定路由地址,只需指定状态码,这样出现错误的时候,他就会自己响应到所对应的页面了。
@app.errorhandler(404) def page_not_found(error): return render_template('404.html'), 404
接下来, 自己编写一个名为404.html的报错页面
一条命令生成依赖引用文档:
C:\Users\Desktop\testflask>pip freeze > requirements.txt
这个cmd命令执行完毕会产生一个文档,他会把我们用到的包列入到这个叫requirements.txt的文本文档。
requirements这个文件名其实是python约定俗成的用法,当时我们使用这个文件名,pycharm会自动帮我们识别我们装了哪些包没装哪些包
安装依赖引用文档的包的命令
pip install -r requirement.txt
关键字: flask_script
先安装模块
pip install flask_script
flask_script 作用:可以通过命令行的形式来操作Flask,例如通过命令跑一个开发版本的服务器、设置数据库、定时任务等
在项目代码中导入包:
from flask.ext.script import Manager
或
from flask_script import Manager
直接把flask对象app传入进去。整体代码如下:
from flask import Flask from flask_script import Manager app = Flask(__name__) manager = Manager(app) @app.route('/<a>') def hello(a): return "hello" + str(a) if __name__ == '__main__': # app.run(debug=True) manager.run()
用命令启动网站:
python manage.py runserver
然后在浏览器里输入
localhost:5000/a
可以访问到自己写的网站
flask-script官网:https://flask-script.readthedocs.org/en/latest/
具体的@manager.command用法看下面
关键字: livereload:
先安装模块:
pip install livereload
代码中引入的方式:
from livereload import Server
代码案例:
@manager.command Idef dev(): from livereload import Server live_server= Server (app.wsgi_app) live_server.watch('**/*.*') live_server. serve (open_url=True) if __name__== '__main__': manager.run()
用@manager.command来,上面就传入了dev的参数
使用@app.template_filter() 把filter注册到模板
我们只要在函数上声明这个装饰器,就可以把这个filter注册到模板
先说说怎么自定义Markdown过滤器, 上文提到过requirements.txt这个文本文档, 假设你已经用过requirements这个包也实现了这个依赖包配置文件的导出, 你需要找到这个文档所在的路径, 然后在同路径里面输入下方的命令来定制Markdown过滤器的版本:
pip install -r requirements.txt
如果你对windows的路径什么的云里雾里? 举个例子, 如果你知道requirements.txt是在C:\somefolders\balabala\下面, 那它的路径全称应该是:
C:\somefolders\balabala\requirements.txt
举一反三, 那么上述的命令就变成了
pip install -r C:\somefolders\balabala\requirements.txt
发现有前端的哥们对这个特别迷糊,一定要把文件路径这个套路搞清楚了。在mac系统里其实套路也一样, 只不过反斜杠变成了正斜杠, 捺变成了撇。
代码里这样写:
@app.route('/') def index(): return render_template('index.html', title="<h1>Hello world</hi>", body="## Header2") @app.template_filter('md") def markdown_to_html(txt) : from markdown import markdown return markdown(txt)
模板里这样写(部门html代码省略)
<body> {{ title|safe }} {{ body|md|safe }} </body>
关键字: @app.context_processor上下文处理器
直接把方法注册到模板中。上下文处理器本身会返回一个字典的函数。其装饰的函数返回的内容对所有html模板都起作用。
@app.context_processor def inject_methods(): return dict(read_md=read_md)
在代码中的实现, 省略部分html代码:
<body> {{ title|safe }} {{ body|md|safe }} {{ read_md('http_methods.md')|md|safe }} </body>
如果你想看看这里面都是啥意思, 请你访问一下这个路径看看具体打印出啥来了?
关键字: @app.template_test 自定义测试函数
@app.template_test('current_link') def is_current_link(link) return link['href'] == request.path
在视图模板里这么写:
{% for link in links %} {% if_not loop.first %} | {% endif %} {% if link is current_link %} <a href="{{ link.href }}">{{ link.label }}</a> {% endif %} {% endfor %}
关键字: flash 消息闪现
(如果你会前端, 建议你还是用layer.js在模板里自己搞)
写入代码导入flash包
from flask import Jsonify, request, Current_app, url_for, render_template, flash
代码里直接call函数
flash('嗨你好啊')
在视图模板文件里找个地方写以下代码看效果:
{% set message = get_flashed_messages() %} {% messages %}
需要在配置文件中设置 SECRET_KEY 字符串, 可以随便写这个秘钥,能保证数据的安全,因为flask的session是写在客户端的,所以设置完SECRET_KEY后就可以通过系统自己的算法来进行验证数据安全的问题了。
SECRET_KEY = 'xxxxxxxxxxxx'
这里看起来是个列表的形式,所以我们可以多次使用flash()
flash('xxxxxxxxxxxx') flash('yyyyyyyyyyyy')
消息闪现的消息分类:
flash('我要报错', category='error') flash('我要警告', category='warning')
然后视图里你可以这么写
{% with errors = get_flashed_messages(category_filter=["error"]) %} {% if errors %} <!-- 中间省略代码 --> {% endif %} {% endwith %}
with和endwith是限制变量作用域的,errors只会在其之间有效果。原来我们不加with,变量的作用域是在block中的,加了之后就变成with中了。
知识点: 开启DEBUG调试模式
有多种方法来开启debug模式:
1. 在app.run()中添加参数,变为app.run(debug=True);
2. 在run之前增加app.debug = True;
3. 新建config.py文件,在config文件中添加DEBUG = True,然后在程序中引入app.config.from_object(config);
4. 在run之前增加app.config['DEBUG'] = True;
建议使用第3种方式,其中还可以写入以下信息
SECRET_KEY
SQLALCHEMY_DB
APP_ROOT
send_static_file:
我们可以使用Flask对象app的send_static_file方法,使视图函数返回一个静态的html文件,但现在我们不使用这种方法,而是使用flask的render_template函数,它功能更强大。
从flask中导入render_template,整体代码如下:
from flask import Flask, render_template import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run()
render_template函数会自动在templates文件夹中找到对应的html,因此我们不用写完整的html文件路径。用浏览器访问'localhost:5000/'这个地址,显示index.html的内容: This is index page
使一个html模板根据参数的不同显示不同的内容,这是因为flask使用了jinja2这个模板引擎。要使用模板,在render_template参数中以key=value形式传入变量,在html中使用{{key}}来显示传入的变量,例如:
python视图处理代码:
# 视图函数 @app.route('/') def index(): return render_template('index.html', contents='This is index page')
html代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <h1>{{ contents }}</h1> </body> </html>
浏览器显示的结果与上文是一样的。我们还可以直接把一个类的实例传递过去,并在模板中访问类的属性,例如假设一个类对象obj有a和b属性,关键部分的代码如下:
视图函数中
return render_template('index.html', object=obj) # ...
html模板中
<p>a: {{ object.a }}</p> <p>b: {{ object.b }}</p>
传入一个字典也可以,并且在模板中既可以用dict[key],也可以用dict.key。
关键字: ORM 和 SQLAlchemy
重头戏来了, 以MySQL为例,平时我们会用mysqldb(python 2)或者pymysql(python 3)去操作MySQL数据库,但这种方法也是需要自己编写SQL语句的。现在我们有了ORM模型,简单来说,ORM是把数据库中的表抽象成模型,表的列名对应模型的属性,这样我们可以调用类的属性或方法去获得数据库中的数据。例如假设MySQL数据库中有一张表名为table1,使用SQL语句例如:
SELECT * FROM table1 WHERE id=1
来获取id为1的数据,如果将表table1映射成ORM模型Table,那么可以直接使用Table.query.filter(id=1),这样操作简单了很多,也很利于理解。SQLAlchemy就是一个这样的ORM,我们可以直接导入一个flask_sqlalchemy包来使用
在配置文件config.py中填写好数据库的连接信息:
HOST = "127.0.0.1" PORT = "3306" DB = "harp" USER = "root" PASS = "Your Password" CHARSET = "utf8" DB_URI = "mysql+pymysql://{}:{}@{}:{}/{}?charset={}".format(USER, PASS, HOST, PORT, DB, CHARSET) SQLALCHEMY_DATABASE_URI = DB_URI
SQLAlchemy依赖mysqldb或者pymysql去连接数据库和执行SQL语句,因为我们用的是python 3,所以需要在配置信息中指明使用pymysql,如果是python 2可以省略,默认是使用mysqldb。
数据库建表
直接上代码
from flask_sqlalchemy import SQLAlchemy from datetime import datetime import config app = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app) class Users(db.Model): __tablename__ = 'users_info' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(32), nullable=False) password = db.Column(db.String(100), nullable=False) register_time = db.Column(db.DateTime, nullable=False, default=datetime.now()) db.create_all()
__tablename__这个属性就是建表后,数据库生成的表名。primary_key=True说明该字段为主键,autoincrement=True代表自增长,nullable决定是否可为空,default代表默认值。最后用db.create_all()来实现创建。
进入数据库命令行,输入desc user_info;这个指令, 我们发现表已经建立好了,其结构图如下:
mysql> desc users_info;
Field | Type | Null | Key | Default | Extra |
id | int(11) | NO | PRI | NULL | auto_increment |
username | varchar(32) | NO | NULL | ||
password | varchar(100) | NO | NULL | ||
register_time | datetime | NO | NULL |
在数据库里插入数据
上代码
@app.route('/') def index(): user = Users(username='Sharp', password='123456') db.session.add(user) db.session.commit() return render_template('home.html')
代码实例化一个Users的对象user,传入username和password,使用db.session.add(user)将其加入到数据库的session(可以理解为事务)中,然后使用db.session.commit()提交。我们运行程序,然后用浏览器访问,浏览器正常显示了结果,这时再看一眼数据库,发现这条数据已经写入到了数据库:
mysql> select * from users_info;
id | username | password | register_time |
1 | Sharp | 123456 | 2022-10-13 10:08:22 |
数据库的查询、修改数据
代码如下:
@app.route('/')
def index(): user = Users.query.filter(Users.id == 1).first() #查找 print(user.username) user.username = 'Harp1207' #修改 db.session.commit() #修改后需提交 print(user.username) return render_template('home.html')
外键关联
users_info表(Users模型)代码如下:
class Users(db.Model): __tablename__ = 'users_info' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(32), nullable=False) password = db.Column(db.String(100), nullable=False) register_time = db.Column(db.DateTime, nullable=False, default=datetime.now()) # 我们新增了一个avatar_path字段来存用户头像图片文件的路径 avatar_path = db.Column(db.String(256), nullable=False, default='images/doraemon.jpg')
questions_info表(Questions模型)代码如下:
class Questions(db.Model): __tablename__ = 'questions_info' id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(100), nullable=False) content = db.Column(db.TEXT, nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('users_info.id')) create_time = db.Column(db.DateTime, nullable=False, default=datetime.now()) author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))
这个表存储所有问题的标题、内容、创建时间、作者ID,作者ID通过外键与用户表的ID关联,方式也很简单,在db.Column中用db.ForeignKey('users_info.id')作为参数即可。
再看最后一条语句:
author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))
db.relationship会自动找到两个表的外键,建立Questions和Users的关系,此时对于任意一个Questions对象question,通过question.author就可获得这个question的作者对应的Users对象,例如获取id为1的问题的作者姓名:
question = Questions.query.filter(Questions.id == 1).first() author_name = question.author.username
db.relationship的第二个参数backref=db.backref('questions', order_by=create_time.desc())则建立了一个反向引用,这样我们不仅可以使用question.author,还可以使用author.questions获得一个作者所有的问题,并通过order_by=create_time.desc()按创建时间倒序排列(网页的内容按时间倒序排列),返回的是一个Questions对象的列表,可以遍历它获取每个对象,如获取作者Harp的所有问题的title:
author = Users.query.filter(Users.username == 'Harp').first() for question in author.questions: print(question.title)
flask-migrate 数据库迁移(用途:更新表结构)
我们增加了两个模型Questions和Comments,并为Users增加了avatar_path这个字段,然后通过这段代码更新到数据库:
with app.test_request_context(): db.drop_all() db.create_all()
因为当使用过db.create_all()之后,再次直接使用db.create_all(),对模型的修改并不会更新到数据库,我们要使用db.drop_all()先把数据库中所有的表先删除掉,然后再db.create_all()一次。听上去是不是很麻烦?更糟糕的是,原先数据库的的数据也就没有了。所以我们不用这种简单粗暴的方式去更新数据库结构,而是借助flask-migrate这个专门用于迁移数据库的工具,它可以在保留数据库原始数据的情况下,完成模型的更新。此外,我们还将结合flask-script一起使用,简单来说flask-script让我们可以使用命令行去完成数据库迁移的操作。
在项目主文件夹下新建一个manage.py,代码如下:
from flask_script import Manager from flask_migrate import Migrate, MigrateCommand from HarpQA import app, db from models import Users, Questions, Comments manager = Manager(app) migrate = Migrate(app, db) manager.add_command('db', MigrateCommand) if __name__ == '__main__': manager.run()
首先导入相关的类,注意模型要全部导入过来,即使代码中并没有显式地使用它们。然后传入app或db来构建Manager和Migrate两个类的实例,最后将MigrateCommand的命令加入到manager中。
此时我们假设要更新模型的结构,在models.py的User模型结尾添加一行代码test = db.Column(db.Integer),然后点击PyCharm下方的Terminal,自动进入到了虚拟环境的命令行中,输入python manage.py db init来初始化,这一步主要是建立数据库迁移相关的文件和文件夹,只是在第一次需要使用。接着依次使用python manage.py db migrate和python manage.py db upgrade,待运行完成,查看users_infor表的结构,结果如下:
Field | Type | Null | Key | Default | Extra |
id | int(11) | NO | PRI | NULL | auto_increment |
username | varchar(32) | NO | NULL | ||
password | varchar(100) | NO | NULL | ||
register_time | datetime | NO | NULL | ||
avatar_path | varchar(256) | NO | NULL | ||
test | int(11) | YES | NULL |
可以看到test字段已经添加到表中了。
werkzeug.security 哈希与字符串的加密与解密
werkzeug.security中的generate_password_hash这个函数,功能是将字符串变成hash值。
password=generate_password_hash(password1)
werkzeug.security的check_password_hash方法,它能验证哈希值是否与原始的密码是匹配的
check_password_hash(加密后的哈希, 需要对比的非哈希密码)
常用判断语句 request.method == 'GET'
为了方便说明, 直接上代码
@app.route('/question/', methods=['GET', 'POST'])
def question(): if request.method == 'GET': return render_template('question.html') else: question_title = request.form.get('question_title') question_desc = request.form.get('question_desc') author_id = Users.query.filter(Users.username == session.get('username')).first().id new_question = Questions(title=question_title, content=question_desc, author_id=author_id) db.session.add(new_question) db.session.commit() return redirect(url_for('home'))
常用钩子函数 @app.before_request
函数里面有before_request,看其名字就很好理解,是在request之前会自动运行的,我们在每次请求之前(或者说每次运行视图函数之前)。
例如通过钩子函数来得到当期登录用户的User对象(而不是仅仅是session中的username),然后在需要的地方使用它,代码如下:
@app.before_request def my_before_request(): username = session.get('username') if username: g.user = Users.query.filter(Users.username == username).first()
这个钩子函数,从session中获取当前登陆的username,如果获取到了,再去检索Users模型,把返回的user对象存入到g对象中,在视图函数中我们就可以直接使用这个user对象的id/register_time等字段了。此时前面的视图函数中的
author_id = Users.query.filter(Users.username == session.get('username')).first().id
可以修改成
author_id = g.user.id
g对象不能跨请求使用,因此在上下文管理器中用的是session,为什么这里又用了g对象呢?原因是现在有了钩子函数,每次请求都会执行钩子函数,向g对象中写入user,所以上下文管理器一直都能从g对象中取到user,不管这个g对象是属于哪次请求的。
获取上传的文件的方法 request.files[name]
与获取POST数据类似(上传文件其实也是使用POST方法),在flask中使用request.files[name]获取上传的文件,其中name为对应input控件的name值(name="avatar_upload"),然后使用文件的save方法即可保存。例如:
@app.route('/user/avatar/', methods=['GET', 'POST']) def avatar(): if request.method == 'GET': return render_template('avatar.html') else: file = request.files['avatar_upload'] path = "D:\\Flask\\HarpQA\\static\\" file.save(path + file.filename) return 'Saved'
注意save方法要加上具体的路径,默认不会保存到py文件所在的路径,而是系统的根目录,此时会提示Permission denied。
set_cookie 设置cookie
make_response方法生成一个response对象(这个对象有set_cookie方法,这也是Flask设置cookie的常规方法),并为其设置cookie,set_cookie第一个参数'sid'是key,第二个参数是value(session id),之后返回response对象。当请求是GET时候,首先就会使用request.cookies.get('sid')去获取cookie中的session id
make_response 方法
make_response(),相当于Django框架中的HttpResponse。
返回内容的写法
from flask import make_response @app.route('/makeresponse/') def make_response_function(): response = make_response('<h2>羞羞哒</h2>') return response, 404
返回页面的写法
from flask import make_response @app.route('/makeresponse/') def make_response_function(): temp = render_template('hello.html') response = make_response(temp) return response
注意:make_response 想要返回页面,不能直接写做:make_response('hello.html'),必须用render_template('hello.html')形式。
返回状态码
方式一:在make_response()中传入状态码
from flask import make_response @app.route('/makeresponse/') def make_response_function(): temp = render_template('hello.html') response = make_response(temp, 200) return response
方式二:直接return状态码
from flask import make_response @app.route('/makeresponse/') def make_response_function(): temp = render_template('hello.html') response = make_response(temp) return response, 200
redirect 跳转
flask中的 redirect 相当于 Django框架的 HttpResponseRedirect。
1. 参数是url形式
from flask import redirect @app.route('/redirect/') def make_redirect(): return redirect('/hello/index/')
2. 参数是 name.name 形式
url_for 相当于reverse,name.name 相当于Django框架的namespace:name,第一个name是初始化蓝图时的参数名,第二个name是函数名
blue = Blueprint('first', __name__)
@blue.route('/index/')
def index():
return render_template('hello.html')
from flask import redirect @blue.route('/redirect/') def make_redirect(): return redirect(url_for('first.index'))
request的属性
#代码示例,仅仅是为了测试request的属性值
@app.route('/login', methods = ['GET','POST']) def login(): if request.method == 'POST': if request.form['username'] == request.form['password']: return 'TRUE' else: # 当form中的两个字段内容不一致时,返回我们所需要的测试信息 return str(request.headers) # 需要替换的部分 else: return render_template('login.html')
1、method:请求的方法
return request.method #POST
2、form:返回form的内容
return json.dumps(request.form) #{"username": "123", "password": "1234"}
3、args和values:args返回请求中的参数,values返回请求中的参数和form
return json.dumps(request.args) #url:http://192.168.1.183:5000/login?a=1&b=2、返回值:{"a": "1", "b": "2"}
return str(request.values) #CombinedMultiDict([ImmutableMultiDict([('a', '1'), ('b', '2')]), ImmutableMultiDict([('username', '123'), ('password', '1234')])])
4、cookies:cookies信息
return json.dumps(request.cookies) #cookies信息
5、headers:请求headers信息,返回的结果是个list
return str(request.headers) #headers信息
request.headers.get('User-Agent') #获取User-Agent信息
6、url、path、script_root、base_url、url_root:看结果比较直观
return 'url: %s , script_root: %s , path: %s , base_url: %s , url_root : %s' % (request.url,request.script_root, request.path,request.base_url,request.url_root)
#url: http://192.168.1.183:5000/testrequest?a&b , script_root: , path: /testrequest , base_url: http://192.168.1.183:5000/testrequest , url_root : http://192.168.1.183:5000/
7、date、files:date是请求的数据,files随请求上传的文件
@app.route('/upload',methods=['GET','POST']) def upload(): if request.method == 'POST': f = request.files['file'] filename = secure_filename(f.filename) #f.save(os.path.join('app/static',filename)) f.save('app/static/'+str(filename)) return 'ok' else: return render_template('upload.html')
html部分
<!DOCTYPE html> <html> <body> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" /><br /> <input type="submit" value="Upload" /> </form> </body> </html>
jsonify flask提供的json格式数据处理方法
python的flask框架为用户提供了直接返回包含json格式数据响应的方法,即jsonify
在flask中使用jsonify和json.dumps的区别: https://blog.csdn.net/JENREY/article/details/86509593
g对象
1. 在flask中,有一个专门用来存储用户信息的g对象,g的全称的为global。
2. g对象在一次请求中的所有的代码的地方,都是可以使用的。
flask之g对象: https://www.wkwkk.com/articles/d9d1e3ea47c6f398.html
Flask-HTTPAuth 扩展 HTTP认证
终端下运行命令来安装:
pip install flask-httpauth
Flask-HTTPAuth提供了几种不同的Auth方法,比如HTTPBasicAuth,HTTPTokenAuth,MultiAuth和HTTPDigestAuth。
HTTPBasicAuth:基础认证
创建扩展对象实例
from flask import Flask from flask_httpauth import HTTPBasicAuth app = Flask(__name__) auth = HTTPBasicAuth()
注意,初始化实例时不需要传入app对象,也不需要调用”auth.init_app(app)”注入应用对象。
案例:用户名及密码验证
我们所要做的,就是实现一个根据用户名获取密码的回调函数:
@auth.get_password 的使用(明文密码有效)
users = [ {'username': 'Tom', 'password': '111111'}, {'username': 'Michael', 'password': '123456'} ] @auth.get_password def get_password(username): for user in users: if user['username'] == username: return user['password'] return None
回调函数”get_password()”由装饰器”@auth.get_password”修饰。在函数里,我们根据传入的用户名,返回其密码;如果用户不存在,则返回空。
@auth.login_required 的使用
接下来,我们就可以在任一视图函数上,加上”@auth.login_required”装饰器,来表示该视图需要认证:
@app.route('/') @auth.login_required def index(): return "Hello, %s!" % auth.username()
启动该应用,当你在浏览器里打开”http://localhost:5000/”,你会发现浏览器跳出了浏览器原生味道的登录框,输入正确的用户名密码(比如上例中的Tom:111111)后,”Hello Tom!”的字样才会显示出来。
进入浏览器调试,发现认证并没有启用Cookie,而是在请求头中加上了加密后的认证字段:
Authorization: Basic TWljaGFlbDoxMjM0NTY=
这就是”HTTPBasicAuth”认证的功能,你也可以用Curl命令来测试:
curl -u Tom:111111 -i -X GET http://localhost:5000/
@auth.verify_password 的使用(非明文密码有效)
上例中”@auth.get_password”回调只对明文的密码有效,但是大部分情况,我们的密码都是经过加密后才保存的,这时候,我们要使用另一个回调函数”@auth.verify_password”。在演示代码之前,先要介绍Werkzeug库里提供的两个方法:
generate_password_hash: 对于给定的字符串,生成其加盐的哈希值
check_password_hash: 验证传入的哈希值及明文字符串是否相符
这两个方法都在”werkzeug.security”包下。现在,我们要利用这两个方法,来实现加密后的用户名密码验证:
from werkzeug.security import generate_password_hash, check_password_hash users = [ {'username': 'Tom', 'password': generate_password_hash('111111')}, {'username': 'Michael', 'password': generate_password_hash('123456')} ] @auth.verify_password def verify_password(username, password): for user in users: if user['username'] == username: if check_password_hash(user['password'], password): return True return False
在”@auth.verify_password”所修饰的回调函数里,我们验证传入的用户名密码,如果正确的话返回True,否则就返回False。
错误处理
在之前的例子中,如果未认证成功,服务端会返回401状态码及”Unauthorized Access”文本信息。你可以重写错误处理方法,并用”@auth.error_handler”装饰器来修饰它:
from flask import make_response, jsonify @auth.error_handler def unauthorized(): return make_response(jsonify({'error': 'Unauthorized access'}), 401)
有了上面的”unauthorized()”方法后,如果认证未成功,服务端返回401状态码,并返回JSON信息”{‘error’: ‘Unauthorized access’}”。
HTTPTokenAuth:Token认证
在对HTTP形式的API发请求时,大部分情况我们不是通过用户名密码做验证,而是通过一个令牌,也就是Token来做验证。此时,我们就要请出Flask-HTTPAuth扩展中的HTTPTokenAuth对象。
同HTTPBasicAuth类似,它也提供”login_required”装饰器来认证视图函数,”error_handler”装饰器来处理错误。
@auth.verify_token 的使用
区别是,它没有”verify_password”装饰器,相应的,它提供了”verify_token”装饰器来验证令牌。我们来看下代码,为了简化,我们将Token与用户的关系保存在一个字典中:
from flask import Flask, g from flask_httpauth import HTTPTokenAuth app = Flask(__name__) auth = HTTPTokenAuth(scheme='Bearer') tokens = { "secret-token-1": "John", "secret-token-2": "Susan" } @auth.verify_token def verify_token(token): g.user = None if token in tokens: g.user = tokens[token] return True return False @app.route('/') @auth.login_required def index(): return "Hello, %s!" % g.user
可以看到,在”verify_token()”方法里,我们验证传入的Token是否合法,是的话返回True,否则返回False。另外,我们通过Token获取了用户信息,并保存在全局变量g中,这样视图中可以获取它。注意,在第一节的例子中,我们使用了”auth.username()”来获取用户名,但这里不支持。
初始化HTTPTokenAuth对象时,我们传入了”scheme=’Bearer'”。这个scheme,就是我们在发送请求时,在HTTP头”Authorization”中要用的scheme字段。
启动上面的代码,并用Curl命令来测试它:
curl -X GET -H "Authorization: Bearer secret-token-1" http://localhost:5000/
HTTP头信息”Authorization: Bearer secret-token-1″,”Bearer”就是指定的scheme,”secret-token-1″就是待验证的Token。在上例中,”secret-token-1″对应着用户名”John”,所以Token验证成功,Curl命令会返回响应内容”Hello, John!”。
使用itsdangerous库来管理令牌
itsdangerous库提供了对信息加签名(Signature)的功能,我们可以通过它来生成并验证令牌。使用前,先记得安装”pip install itsdangerous”。现在,让我们先来产生令牌,并打印出来看看:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer app = Flask(__name__) app.config['SECRET_KEY'] = 'secret key here' serializer = Serializer(app.config['SECRET_KEY'], expires_in=1800) users = ['John', 'Susan'] for user in users: token = serializer.dumps({'username': user}) print('Token for {}: {}\n'.format(user, token))
这里实例化了一个针对JSON的签名序列化对象serializer,它是有时效性的,30分钟后序列化后的签名即会失效。让我们运行下程序,在控制台上,会看到类似下面的内容:
Token for John: eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IkpvaG4ifQ.ox-64Jbd2ngjQMV198nHYUsJ639KIZS6RJl48tC7-DU
Token for Susan: eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IlN1c2FuIn0.lRx6Z4YZMmjCmga7gs84KB44UIadHYRnhOr7b4AAKwo
接下来,改写”verify_token()”方法:
@auth.verify_token def verify_token(token): g.user = None try: data = serializer.loads(token) except: return False if 'username' in data: g.user = data['username'] return True return False
我们通过序列化对象的”load()”方法,将签名反序列化为JSON对象,也就是Python里的字典。然后获取字典中的用户名,如果成功则返回True,否则返回False。这样,就实现了加密后的令牌认证了,让我们用Curl测试一下,还记得刚才控制台上打印出的令牌吗?
curl -X GET -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2MzUzMzY4MCwiaWF0IjoxNDYzNTMxODgwfQ.eyJ1c2VybmFtZSI6IkpvaG4ifQ.ox-64Jbd2ngjQMV198nHYUsJ639KIZS6RJl48tC7-DU" http://localhost:5000/
MultiAuth: 多重认证
Flask-HTTPAuth扩展还支持几种不同认证的组合,比如上面我们介绍了HTTPBasicAuth和HTTPTokenAuth,我们可以将两者组合在一起,其中任意一个认证通过,即可以访问应用视图。实现起来也很简单,只需将不同的认证实例化为不同的对象,并将其传入MultiAuth对象即可。大体代码如下:
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth ... basic_auth = HTTPBasicAuth() token_auth = HTTPTokenAuth(scheme='Bearer') multi_auth = MultiAuth(basic_auth, token_auth) ... @basic_auth.verify_password ... @token_auth.verify_token ... @basic_auth.error_handler ... @token_auth.error_handler ... @app.route('/') @multi_auth.login_required def index(): return 'Hello, %s!' % g.user
这里,每个认证都有自己的验证和错误处理函数,不过在视图上,我们使用”@multi_auth.login_required”来实现多重认证。大家可以使用Curl命令试验下。
HTTPDigestAuth:
暂无举例
详情请点击Flask 扩展 HTTP认证--flask-httpAuth
return 返回问题
例如下面的代码返回的是空的,因为浏览器会解析html标签,但是我们标签里面没有内容所以无任何显示
@app.route('/hello') def hello(): # status code 200,404, 301 # contert-type http headers # content-type = text/html # Response return '<html></html>'
如果想当做普通字符串打印出来就需要返回的content-type=text/plain,如下图所示,需要导入flask的make_response
@app.route('/hello') def hello(): # status code 200, 404, 301 # content-type http headers # content-type = text/html # Response headers = { 'content-type": 'text/plain' } response = make_response('<html></html>', 404) response.headers = headers return response
现在我们在做一个有意思的测试(转发)
@app.route('/hello') def hello(): # status code 200, 404, 301 # content-type http headers # content-type = text/html # Response headers = { 'content-type': 'text/plain', 'location': 'http://www.bing.com' } response= make_response('<html></html> ', 301) response.headers = headers return response
测试发现直接转发到了bing的网站
上面还有一种简单的写法就是如下图所示:
@app.route("/hello") def hello(): # status code 200, 404, 301 # content-type http headers # content-type = text/html # Response headers = { 'content-type': 'application/json', 'location': 'http://www.bing.com' } # response = make_response (" <html> </html>", 301) # response . headers = headers return '<html></html>', 301, headers
上面我们用逗号分隔的形式其实是我们的flask的元组,当你返回为一个元组的时候flask内部还是会把它自动变成一个response对象的。在返回回去。
改成下面这样,就是返回json格式的数据了,其实这就是web返回的本质,返回的本质都是都是字符串,只不过控制的因素在这个context-type,他控制了我们的客户端在接收到我们的返回的时候要怎么样的去解释我们的返回内容。
headers = { 'content-type': 'application/json', location': 'http://www.bing.com' }
flask-login 的使用
当用户登录成功之后我们要产生一个票据,并且把这个票据写入cookie中,我们不仅负责写入票据还要负责读取票据,并且要管理这个票据,整个的登陆机制是非常繁琐的,所以我们自己去用cookie实现这一整套的管理机制是非常不明智的,很幸运的是flask给我们提供了插件flask-login,可以完全用这个插件来管理登录信息。
导入插件
from flask_ login import LoginManage