文章目录
- 一、Flask的CBV
- 1.CBV的写法
- 2.CBV的执行流程
- 3.endpoint 的使用
- 4.CBV中得methods作用
- 5.CBV加装饰器
- 二、模版语法
- 1.渲染变量
- 2.变量的循环
- 3.逻辑判断
- 三、请求和响应
- 四、session执行流程分析
- 1.基本使用
- 2.执行流程
- 3.Django中session的执行流程
- 五、Flask闪现
- 1.作用
- 2.案例
- 3.Django中的闪现(消息框架)
- 六、请求扩展
- 七、g对象
一、Flask的CBV
1.CBV的写法
from flask import Flask
app = Flask(__name__)
app.debug = True
# FBV的写法
@app.route('/')
def index():
return 'hello'
# CBV的写法
from flask.views import MethodView
class TestView(MethodView):
def get(self):
return '我是get'
def post(self):
return '我是post'
# 注册路由
# as_view必须加一个字符串---》是路由的别名
# endpoint 和 as_view(name= 路由别名),以谁为准?
# 读源码后,知道了,以 endpoint 为准
# 如果endpoint 没传--》endpoint是 view_func.__name__ 视图函数的名字 ,别名就是函数名
# 如果endpoint 没传,as_view(name= 路由别名)也没传---》去视图函数名字-->视图函数都是:view
# as_view(name= 路由别名) 有什么用? 把view的名字改为了,你传入的字符串名
app.add_url_rule('/test', 'test', TestView.as_view('test'))
if __name__ == '__main__':
app.run()
2.CBV的执行流程
'从上面CBV的写法来查看执行流程'
1.app.add_url_rule('/test', 'test', TestView.as_view('test'))
-这个注册路由的第三个参数,放视图函数的内存地址,也就是TestView.as_view('test') 是函数内存地址
2.所以我们需要在TestView中找类的方法as_view
-已知我们自己写的TestView中没有写这个方法,所以得去继承的MethodView中寻找,最后在MethodView继承的View中找到了
@classmethod '把源码精简一下'
def as_view(cls, name,*class_args, **class_kwargs):
def view(**kwargs):
'这里是把它放到异步函数中操作,本质就是return了self.dispatch_request(**kwargs)'
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
return view
3.app.add_url_rule的第三个位置,也就是as_view,放的就是View的内层函数
4.当请求来了的时候,路由匹配成功,就会执行view(参数),本质上就是执行self.dispatch_request(**kwargs)
5.所以我们要去找dispatch_request方法,我们视图类中没有,那么去父类MethodView中寻找,结果找到了
'这个就跟我们Django中的CBV一样了'
'然后看到self.dispatch_request中方法 ,在当前视图类中反射,请求方式的小写字符串(get,post),如果我们写了这些方法 就会去执行。'
def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
# request.method.lower() 请求方式变小写
# self是视图类的对象
# 去视图类的对象中反射跟请求方式同名的方法 假设是get方法
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
# get(传入参数)
return meth(**kwargs)
3.endpoint 的使用
1.如果写了endpoint,别名以它为准,如果不写以as_view的参数为准
app.add_url_rule('/test', endpoint='xxx',view_func=TestView.as_view('test'))
'逻辑:'
1 app.add_url_rule('/test',endpoint='xxx', view_func=TestView.as_view('test'))
2 endpoint = _endpoint_from_view_func(view_func)
如果endpoint没传也就是None,就会走这句 view_func是TestView.as_view('test') 它就是函数名
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # view_func.__name__
options["endpoint"] = endpoint # 如果都是endpoint就会冲突
3 _endpoint_from_view_func(view_func)---》返回了传入的函数的名字
return view_func.__name__
# 如果没有其他更改--->所有cbv的函数内存地址都是view
4 如果上面传入了TestView.as_view('test'),它的函数名就是view---》endpoint就是view
-view.__name__ = name
-view的名字 是咱么 TestView.as_view('名字') 传入的名字,不再是view了
'总结:'
endpoint如果不传,会以视图函数的函数名作为endpoint
-fbv:如果不写endpoint,会以函数名作为endpoint,但是如果多个视图函数加了同一个装饰器,又没有指定endpoint,就会出错了
-cbv:调用as_view一定要传入一个字符串---》如果endpoint没写,endpoint就是传入的这个字符串,如果写了,这个字符串没用
如果传了,直接以endpoint传入的作为endpoint
TestView.as_view(name='index'),name到底有啥用
app.add_url_rule('/test', view_func=TestView.as_view('test'))
没有传endpoint,Login.as_view('login')是view函数的内存地址
endpoint会以函数名作为endpoint的值,现在所有函数都是view,必须传入name,来修改调view函数的名字
如果传了endpoint,别名以endpoint为主,如果不传endpoint,别名以name为主
app.add_url_rule('/test', view_func=TestView.as_view(name='test'),endpoint='xxx')
4.CBV中得methods作用
视图类中有个属性就是methods = ['GET', 'POST']
用来控制允许的请求方式 写了什么方法 就允许什么请求 若没有写则不能使用该方法
5.CBV加装饰器
1 使用步骤:在类中加入类属性:
class UserView(MethodView):
decorators = [装饰器1,装饰器2] # 先写的装饰器放在最内部(最左边),是最后执行的装饰器
def get(self):
return 'get'
def post(self):
return 'post'
2.我们在as_view源码中可以看到
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
# 这就是装饰器的本质原理
'循环拿出,然后使用装饰器来执行被装饰函数,然后在把执行的赋值给被装饰函数'
for decorator in cls.decorators:
view = decorator(view)
二、模版语法
1.渲染变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}">查看详细</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
2.变量的循环
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}">查看详细</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
3.逻辑判断
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
</table>
</body>
</html>
比django中多可以加括号,执行函数,传参数
from flask import Flask,render_template,jsonify,make_response
from markupsafe import Markup
app = Flask(__name__, template_folder='temp')
def func1(arg):
return Markup("<input type='text' value='%s' />" %(arg,))
@app.route('/')
def index():
return render_template('index.html',ff = func1)
if __name__ == '__main__':
app.run()
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ff('六五')}}
{{ff('六五')|safe}}
</body>
</html>
注意:
1.Markup等价django的mark_safe ,
2.extends,include一模一样
三、请求和响应
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
app = Flask(__name__)
@app.route('/login.html', methods=['GET', "POST"])
def login():
'请求(request,http请求中的东西,都能从request中取出来)'
1.请求相关信息
# request.method 提交的方法
# request.args get请求提及的数据
# request.form post请求提交的数据
# request.values post和get提交的数据总和
# request.cookies 客户端所带的cookie
# request.headers 请求头
# request.path 不带域名,请求路径
# request.full_path 不带域名,带参数的请求路径
# request.script_root
# request.url 带域名带参数的请求路径
# request.base_url 带域名请求路径
# request.url_root 域名
# request.host_url 域名
# request.host 127.0.0.1:500
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(f.filename))
'响应'
2.响应相关信息
# return "字符串"
# return render_template('html模板路径',**{})
# return redirect('/index.html')
#return jsonify({'k1':'v1'})
# response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.delete_cookie('key')
# response.set_cookie('key', 'value')
# response.headers['X-Something'] = 'A value'
# 向响应头中添加
res = make_response('hello')
res.headers['xx'] = 'll'
# return response
retrun res
return "内容"
if __name__ == '__main__':
app.run()
四、session执行流程分析
1.基本使用
'cookie 是客户端浏览器的键值对,session是存在于服务端的键值对'
flask的session存在哪了?它加密后存放到了cookie中
from flask import Flask, request,session
from flask.views import MethodView
app = Flask(__name__)
app.debug = True
app.secret_key='xuoikjfdlsakjfs'
@app.route('/login', methods=['GET'])
def login():
name = request.args.get('name') # 获取路径后面的?name=xxx
'存入全局的Session,session需要匹配secret_key才能使用'
session['name'] = name
return 'Hello World!,%s' % name
class UserView(MethodView):
def get(self):
取值全局Session
name = session.get('name')
if name:
return '欢迎你:%s' % name
else:
return '没有登录无法查看'
def post(self):
return 'post'
app.add_url_rule('/user', 'user', UserView.as_view('user'))
if __name__ == '__main__':
app.run(debug=True)
2.执行流程
'flask中通过SecureCookieSessionInterface对象来控制整个session的使用流程'
app.session_interface=SecureCookieSessionInterface()
1.登录成功后,会放到session中数据,最终它把session中的数据取出来,并且加密(dumps),放到cookie中,
然后返回给前端-->save_session
2.请求来了,请求中携带了cookie,然后falsk框架会取出cookie,然后会进行解密(loads)得到数据,
把数据放到session中,然后进入到视图函数,这样在后续的视图函数中直接使用session.get()
就能取出当时放入的值-->open_session
'注意:不同浏览器,放的不一样,取出来也不一样'
'SecureCookieSessionInterface 俩方法来支撑上面的流程'
1.open_session: 请求来的时候--》解析出cookie,放到session中
def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None:
# 拿到解密或加密的对象
s = self.get_signing_serializer(app) # 获取签名的序列化
if s is None:
return None
# request.cookie 取出客户端携带的cookie
# self.get_cookie_name(app) ---》 app.config["SESSION_COOKIE_NAME"]--》session
# eyJuYW1lIjoieHh4In0.ZeVMAw.2iCEYHAW7rDaH6Z5ntmKonErTbw
# val 就是取出的三段 头 荷载 签名
val = request.cookies.get(self.get_cookie_name(app))
if not val:
# 如果是空,就会走这里,这里做成空session后,返回了
return self.session_class()
# 如果不为空,继续执行下面代码,cookie在过期时间内
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
# 解密--》把eyJuYW1lIjoieHh4In0.ZeVMAw.2iCEYHAW7rDaH6Z5ntmKonErTbw解成字典
data = s.loads(val, max_age=max_age)
# 组装成有 数据的session
return self.session_class(data)
except BadSignature:
return self.session_class()
2.save_session:请求走的时候---》放到cookie中
def save_session(self, app: Flask, session: SessionMixin, response: Response) -> None:
# 拿到 session 字符串 拿到配置文件中对应的配置
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
httponly = self.get_cookie_httponly(app)
if session.accessed:
response.vary.add("Cookie")
# 如果session不为空,就继续往下走---》视图函数中放入了值 session[name]=jack
if not session:
# 判断session是否被修改过
if session.modified:
# 如果session被修改过,删除cookie
response.delete_cookie(
name,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
httponly=httponly,
)
response.vary.add("Cookie")
return
if not self.should_set_cookie(app, session):
return
# 获取过期时间
expires = self.get_expiration_time(app, session)
# 获得加密对象,使用dumps对session进行加密---》asdfas.asdfas.asdfasd
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
# 放到cookie中
response.set_cookie(
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
response.vary.add("Cookie")
3.Django中session的执行流程
# django 在中间件中做的
session不是直接加密放在cookie 中得, 加密放在 django-session表中--》有个随机字符串对应
1 app中要注册 session ---》生成django-session表
'django.contrib.sessions',
2 中间件要注册--》真正支撑session流程的
'django.contrib.sessions.middleware.SessionMiddleware',
3 支撑session流程的
请求走了---》把session--》写入到cookie中
-def process_response(self, request, response):---》等价于---》save_session
请求来了---》从cookie中取出来--》查数据库--》放到session中
-def process_request(self, request)---》等价于---》open_session
def process_request(self, request):
# 取出 前端在cookie中携带的 随机字符串
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
# SessionStore 实例化得到对象---》session_key是随机字符串
# 内部 根据随机字符串去django-session表中--》查出 session_data--》解密--》包装成对象
# 赋值给request.session -->后续所有视图函数中,都能使用request.session 放值或取值
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
return response
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
patch_vary_headers(response, ('Cookie',))
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SessionInterrupted(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
# 往cookie中放入 key是:session value值是随机字符串: request.session.session_key
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response
五、Flask闪现
1.作用
'访问A页面出现错误 重定向到B页面,要在B页面上展示A页面报错的信息'
-在某个请求中放入值,另一个请求中取出,取出来后就没了(只能使用一次)
-谁(浏览器)放入的,谁(浏览器)才能取出来
-所以它实际上是放在session中了,但是flask中session是通过加密放在cookie中
-所以闪现必须配合session(secret_key)使用
使用方式1:
设置值:
flash('用户名或密码错误') # 可以用多次
取值:
get_flashed_messages() # 取出列表
使用方式2(高级使用,设置flash时,设置category分类):
设置值:
flash('用户名或密码错误',category='login')
flash('register的错误',category='register') # 可以使用多次
取值:
res = get_flashed_messages(category_filter=['login']) # 可迭代对象
res = res[0][1] # 取出来的格式为,列表套元组
2.案例
from flask import Flask,request,session,render_template,redirect,flash,get_flashed_messages
app=Flask(__name__)
app.secret_key='kjfldaoiuozflka'
app.debug=True
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method=='GET':
return render_template('login.html')
else:
username = request.form.get('username')
password = request.form.get('password')
if username == 'jack' and password == '123':
# 存入session
session['name'] = username
return redirect('/')
else:
# 往flash中放入值
# flash('用户名或密码错误')
# 高级使用,设置分类
flash('用户名或密码错误',category='login')
flash('register的错误',category='register')
return redirect('/errors')
# return '用户名或密码错误!'
@app.route('/',methods=['GET'])
def index():
name = session.get('name')
if name:
return '欢迎您:%s'%name
else:
return '您还没有登录,请登录后尝试'
@app.route('/errors')
def errors():
# 从flash中取出值
# res = get_flashed_messages()[0] # 是一个列表
# 高级使用,通过分类取出
res = get_flashed_messages(True,category_filter=['login']) # 只拿登录相关的
res = res[0][1] # 取出来的格式是列表套元组
# flash闪现,主要取出来就没有了
return '请求出错!,错误是:%s' % res
if __name__ == '__main__':
app.run()
3.Django中的闪现(消息框架)
1 注册app
'django.contrib.messages',
2 注册中间件
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
3 配置 templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
'django.contrib.messages.context_processors.messages',
],
},
},
]
4 以后再视图函数中 放入
from django.contrib import messages
messages.debug
messages.info
messages.success
messages.warning
messages.error
messages.warning(request,'登陆失败,用户名或密码无效')
5 在 视图函数中取
get_messages(request)
# 4 在模板中
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} fade in">
{{ message }}
</div>
{% endfor %}
{% endif %}
六、请求扩展
在请求进入视图函数之前执行一些代码,请求出了视图函数以后执行一些代码,类似于django的中间件完成的功能6个装饰器(原来有7个,新版本去掉一个)
1. before_request:在请求进视图函数之前执行
多个的话会从上往下依次执行 django:process_request,如果返回四件套之一就直接返回了,在这里面正常使用request对象
2. after_request:在请求从视图函数走之后执行
多个的话会从下往上依次执行 django:process_response一样,要有参数和返回值参数就是response对象 返回值也必须是resposne对象
session、request照常使用
3. before_first_request:(这个新版本去掉了)项目启动后第一次访问会执行以后再也不执行了
可以做一些初始化的操作
4. teardown_request:每一个请求之后绑定一个函数即使遇到了异常,每个请求走都会执行记录错误日志
@app.teardown_request
def tear_down(e):
print(e) # 如果有异常,这是异常对象
print('我执行了')
5. errorhandler路径不存在时404,服务器内部错误500
@app.errorhandler(404)
def error_404(arg):
print('404会执行我')
return "404错误了"
return render_template('404.html')
@app.errorhandler(500) # debug为False请情况下才能看到
def error_500(arg):
print('500会执行我')
return "服务器内部错误"
6. template_global 标签 在模板中用 {{sb(1,2)}}
@app.template_global()
def sb(a1, a2):
return a1 + a2
7. template_filter过滤器 在模板中用 {{10|db(1,2)}}
@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
展示案例
from flask import Flask,request,session,render_template,redirect,flash,get_flashed_messages
app=Flask(__name__)
app.secret_key='kjfldaoiuozflka'
app.debug=True
@app.before_request # 任意一次请求来的都会执行,被装饰函数
def before_request():
# 判断如果没有登录,重定向到登录
if session.get('name') or 'login' in request.path:
print('请求来了')
else:
return redirect('/login')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method=='GET':
return render_template('login.html')
else:
username = request.form.get('username')
password = request.form.get('password')
if username == 'jack' and password == '123':
session['name'] = username
return redirect('/')
else:
flash('用户名或密码错误',category='login')
return redirect('/errors')
@app.route('/',methods=['GET'])
def index():
name = session.get('name')
if name:
return '欢迎您:%s'%name
else:
return '您还没有登录,请登录后尝试'
@app.route('/errors')
def errors():
res = get_flashed_messages(True,category_filter=['login'])
res = res[0][1] # 取出来的格式是列表套元组
return '请求出错!,错误是:%s' % res
@app.teardown_request # 每一个请求之后绑定一个函数,即使遇到了异常
def teardown_request(error):
# 可以在这里记录错误日志
print(error)
@app.errorhandler(404) # 监听http响应码:只要响应码对应,就会执行对应的函数
def errorhandler(args): # 会和debug模式冲突,因为debug模式是要在页面展示错误,会冲突
# 也可以写一个页面来展示404页面
return '路径不存在,404'
@app.after_request # 任意一次请求走了的都会执行,被装饰函数
def after_reqeust(response):
print('请求走了')
# 在响应头中加入一些数据
response.headers['hello'] = 'world'
return response
# 标签
@app.template_global() # {{sb(1,2)}}
def sb(a1, a2):
return a1 + a2
# 过滤器
@app.template_filter() # {{10|db(1,2)}}
def db(a1, a2, a3):
return a1 + a2 + a3
if __name__ == '__main__':
app.run()
七、g对象
专门用来存储用户信息的g对象,
g的全称的为global
g对象在一次请求中的所有的代码的地方,都是可以使用的
from flask import Flask,request,session,g
# g global 全局的意思,每次请求,这个g一直存在,可以设置值和取值
app = Flask(__name__)
app.secret_key = 'kjfldaoiuozflka'
app.debug = True
@app.before_request
def before():
# 在g中设置值
g.name = 'jack' # 放入g中
def ShowName():
print('ShowName',g.name)
@app.route('/', methods=['GET'])
def index():
# 取出g中得值
print('index',g.name)
ShowName()
return 'hello world'
@app.after_request
def after(response):
print(g.name)
return response
if __name__ == '__main__':
app.run()