Flask基础
目录
对于一个web框架而言,一定要有的就是路由,视图,模板语法
对于一个没有见到的框架,从路由入口,然后走视图
1.谈谈你对django和flask的认识?
1.django是一个大而全的框架,内置了很多的组件,比如分页,缓存,orm...
2.flask轻量级的,可扩展性强,可定制性强
3.两个框架你选择哪个?因人而异
2.flask和django最大的不同点:
1.request/session(django中的session是依附在request对象中传递进来的,但是flask是导入的)
3.flask知识点
- 可以设置静态文件和模板(实例化flask对象的时候配置的...)
- 路由装饰器,对象点route @app.route('/index',methods=['GET','POST'])
- 请求相关的
request.form
request.args
request.method
- 响应
render
redirect
-session
# 放值
session['xx'] = 123
# 取值
session.get('xx')
内容详细
知识点:
给你一个路径,如'xx.xx.ClassFoo',找到这个类获取里面的静态字段
import importlib
# 如何找到这个类
path = 'settings.Foo'
# 先拿到这个路径,然后利用importlib
p, c = path.rsplit('.',maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m,c) # 路径, 类名
# print(cls)
print(dir(cls)) # 拿到的是这个类所有的方法
for k in dir(cls):
if k.isupper():
print(k) # DEBUG
v = getattr(cls,k) # 第一个参数是你要对谁操作,第二个是你要拿谁的值
print(v) # True
配置文件
print(app.config) # 配置文件的查看
# 修改配置文件的方法一
app.config['DEBUG'] = True
app.config['DEBUG'] = True
app.config['DEBUG'] = True
app.config['DEBUG'] = True
# 修改配置文件的方法二
app.config.from_object('settings.Foo')
# 去看settings中的Foo这个类
class Foo:
DEBUG = True
# 方法二修改配置文件的源码
def from_object(self, obj):
if isinstance(obj, string_types): # obj就是'settings.Foo'
obj = import_string(obj) # 根据字符串的形式去导入他
# obj 就是那个传进来的类,在这里就是我们的那个Foo
for key in dir(obj):
if key.isupper():
# self就是配置文件对象,封装了所有的配置文件
self[key] = getattr(obj, key) # 在这一步进行了修改
def import_string(import_name, silent=False): # import_name='settings.Foo'
import_name = str(import_name).replace(":", ".")
try:
try:
__import__(import_name)
except ImportError:
if "." not in import_name:
raise
else:
return sys.modules[import_name]
module_name, obj_name = import_name.rsplit(".", 1) # 获取路径和类
module = __import__(module_name, globals(), locals(), [obj_name])
# __import__类似于importlib.import_moudle(module)
try:
# 在这一步将获取到的类传了出去,拿到的是路径和类名
return getattr(module, obj_name)
except AttributeError as e:
raise ImportError(e)
except ImportError as e:
if not silent:
reraise(
ImportStringError, ImportStringError(import_name, e), sys.exc_info()[2]
)
# 配置分为开发环境和线上环境
class Base(object):
xxx = '123'
class Pro(Base): # 线上环境
DEBUG = False
class Dev(Base): # 开发环境
DEBUG = True
# 后面我们上线了,只需要更改app.config.from_object('settings.Dev')就好,后面如果还有公用的,只需要来一个继承就好
路由系统
- endpoint # 反向生成url,默认是函数名
- url_for('endpoint') # 就是反向解析,类似django的reverse
# 如果没有参数,反向生成路由
url_for('endpoint')/url_for('index',nid=999)
# 动态路由,得有参数接收一下
@app.route('/index/<int:nid>', methods=['GET', 'POST'])
def index(nid):
print(nid)
return 'Index'
# 注意
'''
endpoint就是反向解析的name,不定义就是函数名
路由中可以跟参数,这个参数中间不要留空格,这个值是一个动态的,int表示的是什么类型
什么都不写就是字符串,但是不允许正则表达式
'''
视图
FBV和CBV
FBV
@app.route('/index/<int:nid>', methods=['GET', 'POST'])
def index(nid):
print(nid)
# 反向生成url
print(url_for('/index',nid=999))
return 'Index'
请求相关的数据
'''
flask是直接引用,django是作为一个参数,内部处理机制完全不同
django请求数据是一个个的传递(门口有个人给了一塌子钱,同学一个个的传递过来)
flask(直接扔到桌子上,自己导入去取)
'''
@app.route('/index/<int:nid>', methods=['GET', 'POST'])
def index(nid):
print(nid)
# 请求相关信息
'''
request.method
request.args
request.form
request.value
request.cookies
request.headers
request.path
request.full_path
request.script_root
request.url
request.base_url
request.host
rrequest.files # 上传文件
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filenaem(f.filename)) # 将上传的文件保存起来
'''
return 'Index'
响应
# 响应相关的数据
# Json格式也可以返回
# dic = {'k': v1, 'k2': v2}
# 返回json格式的有这几种
# 1.json.dumps(dic)
# 2.jsonify(dic)
'''
return 'Index'
return render_template()
return redict()
return json.dumps(dic)
return jsonify(dic) 和django的JsonResponse类似
'''
定制响应头/cookie
# 响应头在哪写
# return 'Index'
# 对于上面的这种数据,我们如果想要设置响应头,那么我们可以导入make_response,然后进行封装
obj = make_response('Index')
obj.headers['xxx'] = '123'
return obj
# 对于剩下的几种格式都一样,我们都可以设置响应头
obj = make_response(jsonify(dic))
obj.headers['xxx'] = '123'
return obj
# 我们可以设置响应头,那么也可以设置cookie
obj = make_response('Index')
obj.headers['xxx'] = '123'
obj.set_cookie('key','value')
return obj
示例程序一
from flask import Flask,render_template,redirect,session,url_for
app = Flask(__name__)
app.config.from_object('settings.DevelopmentConfig')
STUDENT_DICT = {
1 : {'name': 'mm', 'age': 2, 'gender': 'male'},
2 : {'name': 'cc', 'age': 18, 'gender': 'female'},
3 : {'name': 'yy', 'age': 18, 'gender': 'female'},
}
@app.route('/index')
def index():
return render_template('index.html',stu_dic=STUDENT_DICT)
@app.route('/info/<int:nid>')
def detail(nid):
info = STUDENT_DICT[nid]
return render_template('detail.html',info=info)
@app.route('/delete/<int:nid>') # 传过来是字符串,int还有个作用转成int
def remove(nid): # 只需要保证路由一致就好,具体函数名可以不用管
STUDENT_DICT.pop(nid)
print(STUDENT_DICT)
return redirect(url_for('index')) # 直接跟函数名就好
if __name__ == '__main__':
app.run()
html页面
<!--index页面-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>学生列表</h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>选项</th>
</tr>
</thead>
<tbody>
{% for key,value in stu_dic.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value['name'] }}</td>
<td>{{ value.get('ae','默认') }}</td>
<td>{{ value.gender }}</td>
<td>
<a href="/info/{{key}}">详细信息</a>
<a href="/add/{{key}}">添加</a>
<a href="/delete/{{key}}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
<!--详细信息页面-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>学生详细</h1>
<ul>
<!--和python语法一模一样-->
{% for item in info.values() %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body>
</html>
<!--和python的使用一模一样,python怎么用他就怎么用-->
安全登录版本一
@app.route('/login',methods=['GET', 'POST']) # method默认是GET
def login():
if request.method == 'GET':
return render_template('login.html')
user = request.form.get('user')
pwd = request.form.get('password')
if user == 'mcc' and pwd == '123':
session['user'] = user # 将东西存到了cookie中
return redirect('/index')
return render_template('login.html', **{'error': '用户名或者密码错误'})
@app.route('/index')
def index():
if session.get('user'):
return render_template('index.html',stu_dic=STUDENT_DICT)
return redirect(url_for('login'))
# 方式一:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做
知识点
# 知识点一
import functools
def auth(func):
# @functools.wraps(func) # django是wraps(func)
@functools.wraps(func)
def inner(*args, **kwargs): # 其实最后执行的是inner
ret = func(*args, **kwargs)
return ret
return inner
@auth
def login():
print('login')
@auth
def detail():
print('detail')
print(login.__name__) # 不加 @functools.wraps(func)的时候是inner
print(detail.__name__) # 加 @functools.wraps(func)的时候是detail
# 知识点二
endpoint默认是函数名,那么如果同名了,怎么解决
def auth(func):
def inner(*args, **kwargs): # 其实最后执行的是inner
ret = func(*args, **kwargs)
return ret
return inner
@auth
def login():
print('login')
@auth
def detail():
print('detail')
# 如果我们不加@functools.wraps(func)程序报错了
AssertionError: View function mapping is overwriting an existing endpoint function: inner
# 这时候我们打印这个的函数发现我们的名字都是inner,没有指定endpoint,此时所有的都是inner
print(login.__name__) # inner
print(detail.__name__) # inner
# 看看源码如何实现的?
def route(self, rule, **options): # 入口 rule='/index'
def decorator(f): # f就是我们传进来的func
endpoint = options.pop("endpoint", None) # 别名
self.add_url_rule(rule, endpoint, f, **options) # 走这一步添加url
return f
return decorator
@setupmethod
def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options
):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # 不写默认是函数名
options["endpoint"] = endpoint
methods = options.pop("methods", None) # GET/POST
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, string_types):
raise TypeError(
"Allowed methods have to be iterables of strings, "
'for example: @app.route(..., methods=["POST"])'
)
methods = set(item.upper() for item in methods)
# Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ()))
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)
if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False
# Add the required methods now.
methods |= required_methods
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
'''
{
endpoint:函数
‘endpoint':inner函数 }
'''
self.url_map.add(rule)
if view_func is not None: # view_func我们自己的函数,就是inner
# view_functions认为他就是一个字典,上面看
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func: # 不加functools.wrap(func)的报错信息
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)
self.view_functions[endpoint] = view_func # 第一次进来‘函数名’=inner
# 装饰器的先后顺序
# 装饰器需要放在路由的下面,这样子一进来先做的是路由的匹配,之后才会走装饰器,如果装饰器放在路由的上面,那么一进来就会连同下面的函数一起去进行装饰,不可理,所以会将装饰器放在视图函数的下面
装饰器适用--版本二
# 方式二:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs): # 其实最后执行的是inner
if session.get('user'):
ret = func(*args, **kwargs)
return ret
return redict(url_for('login'))
return inner
@app.route('/index')
@auth
def index():
return render_template('index.html', stu_dic=STUDENT_DICT)
# 应用场景:比较少的函数中需要额外添加功能
版本三****实现权限管理
基于before_request
# 记住,当before_request返回的是None,是正常往下走,返回其他都是阻拦程序的运行
@app.before_request
def mmmmrrrr():
# print('before_request')
if request.path == '/login':
return None
return 'gong' # 当请求不是login的时候就会直接返回
# 所以我们直接使用这种方式,给函数批量的加入登录认证
@app.before_request
def mmmmrrrr():
# print('before_request')
if request.path == '/login':
return None
if session.get('user'):
return None
return redirect(url_for('login'))
模板的渲染
--基本数据类型:可以执行python的语法,如:dict.get() list['xx']
--传入函数
-django,自动执行
-flask,不自动执行
--全局定义函数,如下
--模板继承
同django一模一样,{% block %}
--静态模板的导入
include
--宏定义,就是自定义一个函数,后面可以多次使用
--安全方式的展示
前端: {{ u|safe }}
后端: Makeup()
<!--模板支持类型-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--两种方式都可以支持-->
{{ users.0 }}
{{ users[0] }}
{{ txt }}
{{ func() }} # 和django的区别是django会自动加括号调用,而flask不会
{{ sb(6,3) }} # 模板全局设置函数的方式一
{{ 1|aa(2, 4) }} # 模板全局设置函数的方式二
{% if 1|aa(2, 4) %} # 这个可以放在if的后面作为条件
<div>999</div>
{% else %}
<div>7777</div>
{% endif %}
</body>
</html>
<!--模板继承-->
<!--母版-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>模板</h1>
{% block content %}
{% endblock %}
</body>
</html>
<!--继承母版-->
{% extends "base.html"%}
<!--两种方式都可以支持-->
{% block content %}
{{ users.0 }}
{{ users[0] }}
{{ txt }}
{{ sb(6,3) }}
{{ 1|aa(2, 4) }}
{% endblock %}
<!--宏-->
{% extends "base.html"%}
<!--两种方式都可以支持-->
{% block content %}
{% macro aaa(name, type='text', value='') %}
<h1>宏</h1>
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
<input type="submit" value="提交">
{% endmacro %}
{{aaa('n1')}} 第一次调用
<!--可以将这种函数放在一个单独的页面中,在使用的时候直接加括号调用就好,类似django的inclution_tag-->
{{aaa('n2')}} 第二次调用
{% endblock %}
<!--静态文件的导入-->
{% include 'form.html %}
模板后端的书写
from flask import Markup # 这个就是django中的mark_safe
@app.template_global()
def sb(a, c):
# {{sb(6, 3)}}
return a+c
@app.template_filter()
def aa(a, b, c):
# {{1 | aa(2, 4)}} 区别就是这个放在if后面做条件
return a+b+c
@app.route('/tpl')
def tpl():
contex = {
'users': ['longya', 'yyy', 'mcc'],
'txt': Markup('<input type="text" />')
}
return render_template('tpl.html', **contex)
session
flask是将这个session对数据进行加密,保存到用户的浏览器的cookie中,其实session就是一个字典
'''
当请求刚进来的时候,flask会帮我们读取cookie中session对应的值,将该值解密并反序列成为一个字典,放入内存,以便视图函数使用。
当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户的cookie中
'''
# 使用
@app.route('/sess')
def sess():
session['k1'] = '123'
session['k2'] = '234'
del session['k1']
return 'session'
闪现
# 在sesson种存储一个数据,读取时通过通过pop将数据移除,只能存储取值一次
# 普通的session设置值,然后发现通过这种方式,session中的值会永久存在,如果我们设置了好多的session的话,那么就会一直存储下来
@app.route('/page1')
def page1():
session['k1'] = '123'
return 'session'
@app.route('/page2')
def page2():
print(session.get('k1'))
return 'ok'
# 基于上面的这种机制,flask引入了闪现,也就是flash,他会将session设置的值变成一个临时存储的值
from flask import session,get_flashed_messages
@app.route('/page1')
def page1():
flash('临时存储的数据','error') # 分类是error
return 'session'
@app.route('/page2')
def page2():
print(get_flashed_messages(category_filter=['error'])) # 取分类是category_filter
return 'ok'
# flash内部源码实现原理
def flash(message, category="message"):
# 从session中取值,取不到就给一个默认的空列表[]
flashes = session.get("_flashes", []) [1,2,3]
# 支持分类,所以将分类和分类信息加入到列表中
flashes.append((category, message))
# 通过session设置从session中取到的值,所以设置的就是一个列表的值
session["_flashes"] = flashes # {'_flashes':[1,2,3]}
# 信号暂时不用看
message_flashed.send(
current_app._get_current_object(), message=message, category=category
)
# get_flashed_messages内部源码实现原理
def get_flashed_messages(with_categories=False, category_filter=()):
#
flashes = _request_ctx_stack.top.flashes
if flashes is None:
_request_ctx_stack.top.flashes = flashes = (
session.pop("_flashes") if "_flashes" in session else []
) # 通过session给pop出来了,出来的要么是个空,要么是一个空列表
if category_filter:
flashes = list(filter(lambda f: f[0] in category_filter, flashes))
if not with_categories:
return [x[1] for x in flashes]
return flashes
中间件
# 需求,想在程序启动之前做一些操作,启动之后做一些操作,通过中间件的形式
'''
我们知道的是,当一个请求开始执行的时候,基于wsgi,走的是run_simple(localhost,port,执行的函数),
那么flask内部是怎么实现?
'''
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run() # 开始启动flask,开始看执行流程
# 内部源码开始啦
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
......# 感兴趣的可以看看,我们只看这下面的这一行
from werkzeug.serving import run_simple
try:
# 走的是run_simple方法,第三个参数就是当前对象,就是app,传的是对象的时候,会自动加括号调用,那么对象加括号调用走的是类的__call__方法。
run_simple(host, port, self, **options)
finally:
self._got_first_request = False
# 直接搜索当前的__call__方法
def __call__(self, environ, start_response):
# 执行的就是对象自己的wsgi_app方法
return self.wsgi_app(environ, start_response)
# 所以我们想要实现在请求开始之前就做一些操作,我们可以考虑从这几个方面下手
'''
1.直接修改源码 ---不好点就是同事也会拉取到你的代码,所以淘汰
2.重写__call__方法 --可以尝试
3.提供一种新的方法,具体请看下方
'''
# 第三种方法书写
class MiddleWare(object):
def __init__(self, old):
self.old = old
def __call__(self, *args, **kwargs):
print('来人了')
return self.old(*args, **kwargs)
if __name__ == '__main__':
# 将对象的wsgi_app变成了自定义类的对象,此时的old就是原来的wsgi_app
app.wsgi_app = MiddleWare(app.wsgi_app)
# 那么后面在执行wsgi_app的时候,发现这个方法已经被我们给覆盖了,就会走我们书写的这个方法,这个时候他已经是一个对象了,对象+()走的就是call方法,那么此时我们在call方法里面做的操作就达到了在不修改源码的情况下增加了新功能。
app.run()
# 这就是flask的中间件的执行,但是用的不多,主要用的还是before_request这些
特殊的装饰器****
before_request ***
after_request ***
template_global
template_filter
before_first_request # 第一次请求的时候执行的,之后的时候都不会执行这个装饰器
errorhandler(404) # 用来返回错误日志的
# django1.9版本之前,中间件的执行顺序也是和flask一样,都是在request的时候返回一个return,就会在返回的时候从最后一个response返回这个请求,在1.9版本之后变成了从当前返回
@app.before_request
def re2():
print('re2')
@app.before_request
def re3():
print('re3')
@app.after_request
def res1(response):
print('res1')
return response
@app.after_request
def res3(response):
print('res3')
# TypeError: after_request() takes 0 positional arguments but 1 was given 是因为没有接收response
# TypeError: 'NoneType' object is not callable 是因为没有返回response
return response
# 定制错误页面
@app.errorhandler(404) # 用来捕获404错误的,返回一个自定义的错误页面
def error(response):
print('303')
return 'not found'
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run()
# 如果有多个request和response,那么执行的顺序分为request和response
# request,按照书写顺序从上到下依次执行
# response,按照书写顺序从下到上依次执行,进行了一个反转