Flask视图、内容、模板

Flask获取请求相关信息

from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=["GET", "POST"])
def hello_world():
    # - 1. request.data: 获取的是以post提交的, 非表单数据
    print(request.data)

    # - 2. request.form: 获取的是以post提交的, 表单数据
    print(request.form)

    # - 3. request.args: 获取的是查询参数, 一般是GET请求, 获取的是问号后面拼接的参数
    print(request.args)

    # - 4. request.method: 获取的是请求方式
    print(request.method)

    # - 5. request.url: 获取的是浏览器的请求地址
    print(request.url)

    return "helloworld"


if __name__ == '__main__':
    app.run(debug=True)

其中,request.args获取到的是链接问号后的参数,此参数一般都是键值对形式存在的。要想获取键值对的值,一般有两种方式:

  • request.args[“key”]
  • request.args.get(“key”)

一般,我们采用第二种方式,因为第一种方式若key不存在,则会报错,第二种方式若key不存在,则返回None


请求钩子(request-hook)

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:

  • before_first_request:在处理第一个请求前执行
  • before_request:在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行
  • after_request:如果没有抛出错误,在每次请求后执行
    • 接受一个参数:视图函数作出的响应
    • 在此函数中可以对响应值,在返回之前做最后一步处理,再返回
  • teardown_request:在每次请求后执行
    • 接受一个参数:用来接收错误信息
from flask import Flask

app = Flask(__name__)


# 只有第一次请求会调用此方法,因此可以在此方法内部做一些初始化操作
@app.before_first_request
def bfr():
    print('This is the first request')


# 每次请求都会调用此方法,可以在此方法里面做类似验证的语句
# 若请求不成功,可直接再次方法中进行响应,可以直接return
@app.before_request
def br():
    print('This is the normal request')


# 在每次执行完视图函数后会调用,并会把视图函数所生成的响应传入-->[<Response 6 bytes [200 OK]>]
# 可在此方法中对响应做最后一步统一的处理
@app.after_request
def ar(a):
    print(a,'****************')
    print('This is the end of request')
    return a


# 每次请求之后都会调用,会接收到一个参数,参数是服务器可能出现的错误信息,若无错误信息,返回None
@app.teardown_request
def tr(e):
    print(e,'****************')
    print('This is why I cry')


@app.route('/')
def func():
    return '123456'


if __name__ == '__main__':
    app.run()

保持状态

状态相关概念

  • http是一种无状态协议,浏览器请求服务器是无状态的
  • 无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求
  • 无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象
  • 有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
  • 实现状态保持主要有两种方式:
    • 在客户端存储信息使用Cookie
    • 在服务器端存储信息使用Session

cookie

什么是cookie

  • 指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密),复数形式Cookies
  • Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器
  • Cookie中的key/value可以由服务器端自己定义

设置,获取cookie

from flask import Flask, make_response, request

app = Flask(__name__)


# 设置cookie值
@app.route('/set_cookie')
def set_cookie():
    response = make_response("wahaha")
    response.set_cookie("name", "zhangsan")
    response.set_cookie("age", "13", 60)  # 60秒有效期

    return response


# 获取cookie
@app.route('/get_cookie')
def get_cookie():
    # 获取cookie,可以根据cookie的内容来推荐商品信息
    name1 = request.cookies
    print(name1)
    name = request.cookies.get('name')
    age = request.cookies.get('age')

    return "获取cookie,name is %s, age is %s" % (name, age)


if __name__ == '__main__':
    app.run(debug=True)

session

什么是session

cookie是保存在客户端浏览器中的信息,因此会存在一定安全隐患。为此对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息,所以可以使用session进行保存。

设置,获取session

from flask import Flask,session

app = Flask(__name__)

#设置任意一个字符串当作secret_key
app.config["SECRET_KEY"] = "fhdk^fk#djefkj&*&*&"

#设置session
@app.route('/set_session/<path:name>')
def set_session(name):

    session["name"] = name
    session["age"] = "13"

    return "set session"

#获取session内容
@app.route('/get_session')
def get_session():

    name = session.get('name')
    age = session.get('age')

    return "name is %s, age is %s"%(name,age)

if __name__ == '__main__':
    app.run(debug=True)

上下文

上下文:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
Flask中有两种上下文,请求上下文和应用上下文

请求上下文

在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session

request

封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。

session

用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。

应用上下文

它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g

current_app

应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连了哪个数据库
  • 有哪些public的工具类、常量
  • 应用跑再哪个机器上,IP多少,内存多大

g

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
二者区别:
请求上下文:保存了客户端和服务器交互的数据
应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等


Flask-Script扩展

Flask-Script属于flask的扩展包,通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数,而不仅仅通过app.run()方法中传参。
需要先安装flask-script扩展
pip install flask-script

python hello.py runserver -host ip地址

用代码实现代理

from flask import Flask
#1.从flask_script中导入Manager类
from flask_script import Manager

app = Flask(__name__)

# 2.使用Manager管理app对象
manager = Manager(app)

@app.route('/')
def hello_world():
    return "helloworld"

if __name__ == '__main__':
    manager.run()

该方法只能在终端启动。若想要在代码页面直接右键运行,需在Edit Configuration处添加参数runserver。


Jinja2

Jinja2模板概述

Jinja2是用来展示数据的html页面,这个过程也通常称为渲染,属于Jinja2的功能使用模板的好处:

  • 视图函数只负责业务逻辑和数据处理(业务逻辑方面)
  • 而模板则取到视图函数的数据结果进行展示(视图展示方面)
  • 代码结构清晰,耦合度低

Jinja2特点

  • Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
  • 模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
  • 使用render_template函数封装模板引擎

render_template函数模板语法

Jinja2模板语法

获取变量
    <h1>整数:{ {number} }</h1>
    <h1>元祖:{ {tuple[0]} }</h1>
    <h1>列表:{ { list[0] } }</h1>
    <h1>字典:{ { dict['key'] } }</h1>
分支语句if
{ % if 条件 % }
    语句1
 { % else % }    
    语句2
{ % endif % }
for循环
{% for 变量  in 容器 %}
    语句
{% endfor%}

代码展示

-使用函数:render_template(‘模板文件名’,key=value)
-将数据携带到,文件中进行展示
-创建文件demo01.py,代码如下:

from flask import Flask,render_template
app = Flask(__name__) #默认省略了三个参数,static_url_path, static_folder, template_folders

@app.route('/')
def hello_world():
    #定义数据,整数,字符串,元祖,列表,字典
    num = 10
    str = "hello"
    tuple = (1,2,3,4)
    list = [5,6,7,8]
    dict = {
        "name":"张三",
        "age":13
    }

    return render_template('file01.html',my_num=num,my_str=str,my_tuple=tuple,my_list=list,my_dict=dict)

if __name__ == '__main__':
    app.run(debug=True)

在templates文件夹下,创建文件file01.html文件,代码如下:

<h2>1.获取各种变量的值</h2>
    <h3>整数: {{ my_num + 20}}</h3>
    <h3>字符串: {{ my_str + " python" }}</h3>
    <h3>元组: {{ my_tuple }}, 分开获取:{{ my_tuple[0] }}, {{ my_tuple[1] }}</h3>
    <h3>列表: {{ my_list }}, 分开获取:{{ my_list[0] }}, {{ my_list[1] }}</h3>
    <h3>字典: {{ my_dict }},分开获取:{{ my_dict.name }}, {{ my_dict[age] }}</h3>
    <h2>2.遍历元祖中所有的元素</h2>
    {% for item in my_tuple %}
        <li>{{ item }}</li>
    {% endfor %}

    <h2>3.取出列表中所有偶数</h2>
    {% for item in my_list %}
        {% if item %2 == 0 %}
            {{ item }}
        {% endif %}
    {% endfor %}

    <h2>4.遍历字典内容</h2>
    {% for key in my_dict %}
        {# 如果直接是mydict.key ,那么这个key是一个字符串, 如果是 mydict[key], 那么key当成变量 #}
        <li>{{ key }} = {{ my_dict[key] }}</li>
    {% endfor %}

扩展

变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。

Jinja2过滤器

过滤器概述

过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。

Jinja2自带过滤器

字符串

使用格式:{{字符串 | 字符串过滤器 }}

  • safe:禁用转义
    <p>{{ '<em>hello</em>' | safe }}</p>
  • capitalize:把变量值的首字母转成大写,其余字母转小写
    <p>{{ 'hello' | capitalize }}</p>
  • lower:把值转成小写
    <p>{{ 'HELLO' | lower }}</p>
  • upper:把值转成大写
    <p>{{ 'hello' | upper }}</p>
  • title:把值中的每个单词的首字母都转成大写
    <p>{{ 'hello' | title }}</p>
  • reverse:字符串反转
    <p>{{ 'olleh' | reverse }}</p>
  • format:格式化输出
    <p>{{ '%s is %d' | format('name',17) }}</p>
  • striptags:渲染之前把值中所有的HTML标签都删掉
    <p>{{ '<em>hello</em>' | striptags }}</p>

列表

使用格式:{{ 列表 | 列表过滤器 }}

  • first:取第一个元素
    <p>{{ [1,2,3,4,5,6] | first }}</p>
  • last:取最后一个元素
    <p>{{ [1,2,3,4,5,6] | last }}</p>
  • length:获取列表长度
    <p>{{ [1,2,3,4,5,6] | length }}</p>
  • sum:列表求和
    <p>{{ [1,2,3,4,5,6] | sum }}</p>
  • sort:列表排序
    <p>{{ [6,2,3,1,5,4] | sort }}</p>

语句块操作

{% filter upper %}
#假装这里有一大堆文字#
{% endfilter %}

链式调用

{{ "hello world" | reverse | upper }}

自定义过滤器

方法一

先定义函数,后添加到过滤器列表
app.add_template_filter(‘函数名’,‘过滤器名称’)

def do_listreverse(li):
    # 通过原列表创建一个新列表
    temp_li = list(li)
    # 将新列表进行返转
    temp_li.reverse()
    return temp_li

app.add_template_filter(do_listreverse,'lireverse')

方法二

定义函数,直接使用@app.template_filter(‘过滤器名称’)装饰

@app.template_filter('lireverse')
def do_listreverse(li):
    # 通过原列表创建一个新列表
    temp_li = list(li)
    # 将新列表进行返转
    temp_li.reverse()
    return temp_li

在html中使用过滤器

在 html 中使用该自定义过滤器

<h2>my_array 原内容:{{ my_array }}</h2>
<h2> my_array 反转:{{ my_array | lireverse }}</h2>

模板复用

当有部分代码会重复利用的时候,我们就可以利用代码复用的方式。代码复用一般有三种方式:宏、继承和包含。

解释

宏和python中的函数类似,在需要的时候调用即可

定义格式

{%macro 宏名(参数)%}内容{%endmacro%}

{% macro 宏名(参数) %}
{# 内容 #}
{% endmacro %}

使用

在当前文件中
直接{{ 宏名(参数) }}
在其他文件中
先导入
{{ import ‘拥有宏代码的文件名’ as ‘别名’}}
再引用
{{ 宏名(参数) }}

继承

目的

共性抽取,代码复用

格式

{% extends"父模板文件名" %}

父模板

  1. 多个子类完全相同的部分可以直接写死
  2. 各个子类直接不同的地方使用block模块定义出来
    {%block 块名%}可修改内容{%endblock%}
    {% block 块名 %}
    {# 可修改内容 #}
    {% endblock %}
    

子模版

  1. 子类继承父类
    子类继承父类时,继承的代码最好写在所有代码的最上边
  2. 子类根据自己的需求,重写父类block模块中的代码
    • 完全重写
      {%block 块名%}新内容{%endblock%}
    {% block 块名%}
    {# 新内容 #}
    {% endblock%}
    
    • 继承并增加
      {%block 块名%}{{super()}}新内容{%endblock%}
    {% block 块名%}
    {{super()}}
    {# 新内容 #}
    {% endblock%}
    

包含

包含指的是在一个文件中完全拷贝另一个文件的所有代码.这种方式无法对代码进行扩展,因此不够灵活.

格式

{%include "文件名" ignore missing%}
其中,ignore missing是可选参数,选此参数,当包含的文件找不到时,系统不会报错.因此,推荐选择该参数.

模板中的特有变量

特有变量指的是不需要从python中传递到模板,就可以直接使用的变量

常见特有变量

request
就是python中的请求上下文对象
g
是一个全局的应用上下文对象
url_for(函数名)
是一个反解析方法,根据函数名找到对应的路径
config
config指的就是app.config的配置对象
get_flashed_messages()
是消耗消息,消耗的是在python中使用flash(“消息”)方法储存的消息.
flash 储存消息时需要依赖 session ,所以用 flash 应该先设置SECRET_KEY

CSRF攻击

CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。指攻击者盗用了你的身份,以你的名义发送恶意请求。
CSRF攻击示意图:
在这里插入图片描述

如何防止CSRF攻击

防止CSRF攻击的思想:

  • 在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
  • 在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token
  • 在用户点击提交的时候,会带上这两个值向后台发起请求
  • 后端接受到请求,以会以下几件事件:
    • 从 cookie中取出 csrf_token
    • 从 表单数据中取出来隐藏的 csrf_token 的值
    • 进行对比
  • 如果比较之后两值一样,那么代表是正常的请求,如果没取到或者比较不一样,代表不是正常的请求,不执行下一步操作
  • 提示:代码展示:见<< webA >>, << webB >>文件

代码构思

  • flask_wtf模块提供了csrf攻击的保护
  • 使用流程:
    • from flask_wtf.csrf import CSRFProtect
    • CSRFProtect(app)
  • CSRFProtect(app)保护原理:
    • 对应用程序app中的post,put,dispatch,delete, 4种类型的请求做保护,因为这些类型的请求是用于更改服务器的资源
    • 当以上面4种类型的请求,操作服务器资源的时候,会校验cookie中的csrf_token, 表单中的csrf_token信息
    • 只有上面二者的值相等的时候,那么校验则通过,可以操作服务器资源

提示 : csrf_token值的生成需要加密,所以要设置SECRET_KEY

代码如下

  • 后端代码
from flask import Flask,render_template
from flask_wtf import CSRFProtect

app = Flask(__name__)

#设置SECRET_KEY
app.config["SECRET_KEY"] = "fjkdjfkdfjdk"

#保护应用程序
CSRFProtect(app)

@app.route('/')
def show_page():

    return render_template('file01csrf.html')

@app.route('/add_data',methods=["POST"])
def add_data():

    return "登陆成功"

if __name__ == '__main__':
    app.run(debug=True)
  • 前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/add_data" method="post">
    {#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

    <label>用户名:</label> <input type="text" name="username"><br>
    <label>密码:</label> <input type="text" name="username"><br>
    <input type="submit" value="登陆">
</form>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/washing1127/article/details/84330436