Python-Flask框架
一、web框架的本质
1、自定义web框架
web应用本质上就是一个socket服务端,浏览器是socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
准备一个html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="https://iproute.cn/images/logo.png" alt="头像">
</body>
</html>
编写python的socket服务端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
conn.send(b'hello')
print(str_msg)
conn.close()
sk.close()
运行服务端之后,浏览器访问http://127.0.0.1:8080
,浏览器传给socket的内容如下
GET / HTTP/1.1 # 请求行,其中的/是路径
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8
Cookie: smid=sys4OxpO9GZqFqSAA37ZAWOnLB54KsilDWWuipaewHYE9ggajIeWtlHRrIBuSZyR038Q7r4c2XNUXdfdWR-I4A; APP_HOST=http%3A//127.0.0.1%3A49153/; HOST=http%3A//127.0.0.1%3A49153/; kodUserLanguage=zh-CN; kodUserID=1; X-CSRF-TOKEN=i9YQRqWXnS4Iy3uRt3vW; p_h5_u=057F0CAB-3644-4539-A2BC-249B82EA9934
修改socket服务端,让其返回网页内容
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
print(str_msg)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
with open('test.html','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
sk.close()
这样就可以将网页内容返回给浏览器了
如果想要在网页中携带本地路径的图片,那么修改html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="logo.png" alt="头像">
</body>
</html>
然后修改socket服务端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while 1: # 浏览器多次访问,所以需要while
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
with open('test.html','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close() # HTTP协议是短链接的,一次请求对应一次响应,这个请求就结束了,所以我们需要写上close,不然浏览器自己断了
elif path == '/logo.png':
with open('logo.png','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
sk.close()
可以改成使用函数的版本
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
func1(conn)
elif path == '/logo.png':
func2(conn)
sk.close()
现在还不支持高并发的情况,可以加上多线程
import socket
from threading import Thread
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
t = Thread(target=func1,args=(conn,))
t.start()
elif path == '/logo.png':
t = Thread(target=func2,args=(conn,))
t.start()
sk.close()
替换字符串,实现不同的时间访问返回时间戳模拟动态内容
在网页中,用特殊的符号@@666@@
表示需要被替换掉的地方,修改html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="logo.png" alt="头像">
<h2>@@666@@</h2>
</body>
</html>
修改socket服务端
import socket
from threading import Thread
import time
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'r',encoding="utf-8") as f:
f_data = f.read()
now = str(time.time())
f_data = f_data.replace("@@666@@",now).encode('utf-8')
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
t = Thread(target=func1,args=(conn,))
t.start()
elif path == '/logo.png':
t = Thread(target=func2,args=(conn,))
t.start()
sk.close()
二、URL与视图
1、Flask简介
flask
是一款非常流行的Python Web
框架,出生于2010年,作者是Armin Ronacher
,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。
flask
自2010年发布第一个版本以来,大受欢迎,深得开发者的喜爱,目前在Github
上的Star数已经超过55.5k
了,有超Django
之趋势。flask
能如此流行的原因,可以分为以下几点:
- 微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。
- Flask和相应的插件写得很好,用起来很爽。
- 开发效率非常高,比如使用
SQLAlchemy
的ORM
操作数据库可以节省开发者大量书写sql
的时间。
Flask
的灵活度非常之高,他不会帮你做太多的决策,一些你都可以按照自己的意愿进行更改。比如:
- 使用
Flask
开发数据库的时候,具体是使用SQLAlchemy
还是MongoEngine
,选择权完全掌握在你自己的手中。区别于Django
,Django
内置了非常完善和丰富的功能,并且如果你想替换成你自己想要的,要么不支持,要么非常麻烦。 - 把默认的
Jinija2
模板引擎替换成其他模板引擎都是非常容易的。
2、安装flask
在pycharm中可以在如下位置安装
在命令行中可以输入如下命令安装
pip install flask==2.0.3
3、Flask项目
3.1、运行helloworld
import flask
# 导入flask框架
app = flask.Flask(__name__)
# 传入__name__初始化一个Flask实例
@app.route('/')
# app.route装饰器映射URL和执行的函数。这个设置将根URL映射到了hello_world函数上
def hello_world():
return 'Hello'
# 返回的内容就会呈现到浏览器上
if __name__ == '__main__':
# 运行本项目,host=0.0.0.0可以让其他电脑也能访问到该网站,port指定访问的端口。默认的host是127.0.0.1,port为5000
app.run(debug=True,host='0.0.0.0',port=8080)
# 在pycharm中,debug,host,port等代码中的设置无效
如果在pycharm中想要修改调试的地址和端口可以进行如下设置
4、URL与视图
4.1、URL与函数的映射
一个URL
要与执行函数进行映射,使用的是@app.route
装饰器。@app.route
装饰器中,可以指定URL
的规则来进行更加详细的映射,比如现在要映射一个文章详情的URL
,文章详情的URL
是/article/id/
,id有可能为1、2、3…,那么可以通过以下方式:
@app.route('/article/<id>/')
# 可以用<xxx>来读取对应的内容
def article(id):
return f'{id}article detail'
其中<id>
,尖括号是固定写法,语法为<variable>
,variable
默认的数据类型是字符串。如果需要指定类型,则要写成<converter:variable>
,其中converter
就是类型名称,可以有以下几种:
- string: 默认的数据类型,接受没有任何斜杠
/
的字符串。 - int: 整形
- float: 浮点型。
- path: 和
string
类似,但是可以传递斜杠/
。 - uuid:
uuid
类型的字符串。 - any:可以指定多种路径
举例说明
@app.route('/guess/<int:num>')
# 由于指定了int类型,所以num可以直接和整数型比较大小
def guess(num):
if num < 66:
return "你猜的数字小了"
elif num > 66:
return "你猜的数字大了"
else:
return "你猜对了"
访问结果
如果传入一个不是int类型的,就无法触发这个路由,就会返回404
关于any类型可以看下面这个案例
@app.route('/user/<any(article,blog):url_path>')
# 在/user/后面只能是article和blog二选一,并且会被传递到url_path变量
def url_path(url_path):
return f"你输入的是{url_path},此处只能是article或者blog"
如果输入了别的字符串,就会返回404
4.2、构造URL
一般我们通过一个URL
就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个URL
呢?url_for
函数就可以帮我们实现这个功能。url_for()
函数接收两个及以上的参数,他接收函数名作为第一个参数,接收对应URL规则的命名参数,如果还出现其他的参数,则会添加到URL
的后面作为查询参数。
通过构建URL
的方式而选择直接在代码中拼URL
的原因有两点:
- 将来如果修改了
URL
,但没有修改该URL
对应的函数名,就不用到处去替换URL
了。 url_for()
函数会转义一些特殊字符和unicode
字符串,这些事情url_for
会自动的帮我们搞定。
下面用一个例子来进行解释:
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/article/<id>/')
def article(id):
return '%s article detail' % id
@app.route('/')
def index():
return url_for("article",id=1)
# 可以使用url_for来帮我们构造一个url,会帮助我们检查此url的合法性
在访问的时候,会打印出/article/1/
。
4.3、指定URL末尾的斜杠
当访问一个结尾不带斜线的URL
:/article
,会被重定向到带斜线的URL
:/article/
上去。但是当我们在定义article
的url
的时候,如果在末尾没有加上斜杠,但是在访问的时候又加上了斜杠,这时候就会抛出一个404
错误页面了:
@app.route('/test')
def haha():
return '你好'
4.4、指定HTTP方法
在@app.route()
中可以传入一个关键字参数methods
来指定本方法支持的HTTP
方法,默认情况下,只能使用GET
请求,看以下例子:
@app.route('/search/')
def search():
# return f"你输入的要搜索的东西是{request.args['kw']}"
return f"你输入的要搜索的东西是{request.args.get('kw')}"
# 可以和字典一样去取对应的值
# http://xxxx:5000/search?kw=xxx
如果使用post提交就会触发错误
如果想要使用post请求,可以看如下例子
from flask import Flask,request
app = Flask(__name__)
@app.route('/login/',methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if username == 'admin' and password == '123456':
return 'Logged in successfully'
else:
return 'Invalid username or password'
尝试登陆一下
4.5、页面跳转和重定向
重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
- 永久性重定向:
http
的状态码是301
,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入www.jingdong.com
的时候,会被重定向到www.jd.com
,因为jingdong.com
这个网址已经被废弃了,被改成jd.com
,所以这种情况下应该用永久重定向。 - 暂时性重定向:
http
的状态码是302
,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
在flask
中,重定向是通过flask.redirect(location,code=302)
这个函数来实现的,location
表示需要重定向到的URL
,应该配合之前讲的url_for()
函数来使用,code
表示采用哪个重定向,默认是302
也即暂时性重定向
,可以修改成301
来实现永久性重定向。
以下来看一个例子,关于在flask
中怎么使用重定向:
from flask import Flask, request, session, redirect, url_for
app = Flask(__name__)
app.secret_key = '啦啦啦,我是买糕的小行家'
# 使用session的话,需要配置一个secret_key,内容随意,最好别人想不到
@app.route('/login/',methods=['POST','GET'])
def login():
if 'username' not in request.form and 'password' not in request.form:
# 如果没有携带用户名和密码,就显示请登陆
return "请登陆"
username = request.form['username']
password = request.form['password']
if username == 'admin' and password == '123456':
session['username'] = 'admin'
# 登陆成功之后,服务器存储session值
return 'Logged in successfully'
else:
return 'Invalid username or password'
@app.route("/profile/",methods=['GET'])
def profile():
if 'username' in session:
return f"欢迎用户{session['username']}"
# 如果已经登陆,就欢迎
else:
return redirect(url_for('login'))
# 如果cookie中没有合法的session值,就重定向去登陆
三、模版简介
模板是一个web
开发必备的模块。因为我们在渲染一个网页的时候,并不是只渲染一个纯文本字符串,而是需要渲染一个有富文本标签的页面。这时候我们就需要使用模板了。在Flask
中,配套的模板是Jinja2
,Jinja2
的作者也是Flask
的作者。这个模板非常的强大,并且执行效率高。以下对Jinja2
做一个简单介绍!
1、渲染Jinja
模板
要渲染一个模板,通过render_template
方法即可,以下将用一个简单的例子进行讲解:
创建about.html
<h1>welcome back!</h1>
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/about/')
def about():
return render_template('about.html')
# 渲染 about.html 注意:必须要在 templates 文件夹下
if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0',port=5000)
当访问/about/
的时候,about()
函数会在当前目录下的templates
文件夹下寻找about.html
模板文件。如果想更改模板文件地址,应该在创建app
的时候,给Flask
传递一个关键字参数template_folder
,指定具体的路径,再看以下例子:
from flask import Flask,render_template
app = Flask(__name__,template_folder=r'C:\templates')
@app.route('/about/')
def about():
return render_template('about.html')
以上例子将会在C盘的templates
文件夹中寻找模板文件。还有最后一点是,如果模板文件中有参数需要传递,应该怎么传呢,我们再来看一个例子:
about.html 修改为如下
<h1>welcome {{ user }} </h1>
在渲染模版的时候传递一下参数
@app.route('/about/')
def about():
return render_template('about.html', user='user01')
如果有多个参数要传递的话可以放在字典中
@app.route('/about/')
def about():
return render_template('about.html', **{'user':'alice','name':'bob'})
# about.html 中需要有 user 和 name
以上例子介绍了两种传递参数的方式,因为render_template
需要传递的是一个关键字参数,所以第一种方式是顺其自然的。但是当你的模板中要传递的参数过多的时候,把所有参数放在一个函数中显然不是一个好的选择,因此我们使用字典进行包装,并且加两个*
号,来转换成关键字参数。
2、Jinja2模版概述
2.1、概要
先看一个简单例子:
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
{{ a_variable }}
{{ user.name }}
{{ user['name'] }}
{# a comment #}
</body>
</html>
以上示例有需要进行解释:
- 第12~14行的
{{ ... }}
:用来装载一个变量,模板渲染的时候,会把这个变量代表的值替换掉。并且可以间接访问一个变量的属性或者一个字典的key
。关于点.
号访问和[]
中括号访问,没有任何区别,都可以访问属性和字典的值。 - 第7~9行的
{% ... %}
:用来装载一个控制语句,以上装载的是for
循环,以后只要是要用到控制语句的,就用{% ... %}
。 - 第16行的
{# ... #}
:用来装载一个注释,模板渲染的时候会忽视这中间的值。
对应的 Python 代码如下
@app.route('/demo1/')
def demo1():
navigation = [
{'href':'http://www.baidu.com','caption':'百度'},
{'href':'http://www.qq.com','caption':'腾讯'}
]
a = 'hello world'
user = {'name':'alice'}
return render_template('demo1.html', navigation=navigation, a_variable=a, user=user)
3、Jinja2 模版过滤器
过滤器是通过管道符号(|
)进行使用的,例如:{{ name|length }}
,将返回name的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。Jinja2
中内置了许多过滤器,在这里可以看到所有的过滤器,现对一些常用的过滤器进行讲解:
abs(value)
:返回一个数值的绝对值。 例如:-1|abs
。default(value,default_value,boolean=false)
:如果当前变量没有值,则会使用参数中的值来代替。name|default('alice')
——如果name不存在,则会使用alice
来替代。boolean=False
默认是在只有这个变量为undefined
的时候才会使用default
中的值,如果想使用python
的形式判断是否为false
,则可以传递boolean=true
。也可以使用or
来替换。escape(value)或e
:转义字符,会将<
、>
等符号转义成HTML中的符号。例如:content|escape
或content|e
。first(value)
:返回一个序列的第一个元素。names|first
。format(value,*arags,**kwargs)
:格式化字符串。例如以下代码:
{{ "%s - %s"|format("hello","alice") }}
将输出:Hello - alice
last(value)
:返回一个序列的最后一个元素。示例:names|last
。length(value)
:返回一个序列或者字典的长度。示例:names|length
。join(value,d=u'')
:将一个序列用d
这个参数的值拼接成字符串。safe(value)
:如果开启了全局转义,那么safe
过滤器会将变量关掉转义。示例:content_html|safe
。int(value)
:将值转换为int
类型。float(value)
:将值转换为float
类型。lower(value)
:将字符串转换为小写。upper(value)
:将字符串转换为小写。replace(value,old,new)
: 替换将old
替换为new
的字符串。truncate(value,length=255,killwords=False)
:截取length
长度的字符串。striptags(value)
:删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。trim
:截取字符串前面和后面的空白字符。string(value)
:将变量转换成字符串。wordcount(s)
:计算一个长字符串中单词的个数。
4、控制语句
所有的控制语句都是放在{% ... %}
中,并且有一个语句{% endxxx %}
来进行结束,Jinja
中常用的控制语句有if/for..in..
,现对他们进行讲解:
if
:if语句和python
中的类似,可以使用>,<,<=,>=,==,!=
来进行判断,也可以通过and,or,not,()
来进行逻辑合并操作,以下看例子:
{% if num < 60 %}
你猜的数字小了
{% elif num > 60 %}
你猜的数字大了
{% else %}
你猜对了
{% endif %}
-
for...in...
:for
循环可以遍历任何一个序列包括列表、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释: -
普通的遍历:
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
- 遍历字典:
<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
- 如果序列中没有值的时候,进入
else
:
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no users found</em></li>
{% endfor %}
</ul>
并且Jinja
中的for
循环还包含以下变量,可以用来获取当前的遍历状态:
变量 | 描述 |
---|---|
loop.index | 当前迭代的索引(从1开始) |
loop.index0 | 当前迭代的索引(从0开始) |
loop.first | 是否是第一次迭代,返回True或False |
loop.last | 是否是最后一次迭代,返回True或False |
loop.length | 序列的长度 |
另外,不可以使用continue
和break
表达式来控制循环的执行。
5、测试器
测试器主要用来判断一个值是否满足某种类型,并且这种类型一般通过普通的if
判断是有很大的挑战的。语法是:if...is...
,先来简单的看个例子:
{% if variable is upper %}
value of variable: {{ variable }}
{% else %}
variable is not upper
{% endif %}
以上判断variable
这个变量是否全大写,Jinja
中内置了许多的测试器,看以下列表:
测试器 | 说明 |
---|---|
callable(object) |
是否可调用 |
defined(object) |
是否已经被定义了。 |
escaped(object) |
是否已经被转义了。 |
upper(object) |
是否全是大写。 |
lower(object) |
是否全是小写。 |
string(object) |
是否是一个字符串。 |
sequence(object) |
是否是一个序列。 |
number(object) |
是否是一个数字。 |
odd(object) |
是否是奇数。 |
even(object) |
是否是偶数。 |
6、宏和import语句
6.1、宏
模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">
{% endmacro %}
以上例子可以抽取出了一个input标签,指定了一些默认参数。那么我们以后创建input
标签的时候,可以通过他快速的创建:
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
6.2、import语句:
在真实的开发中,会将一些常用的宏单独放在一个文件中,在需要使用的时候,再从这个文件中进行导入。import
语句的用法跟python
中的import
类似,可以直接import...as...
,也可以from...import...
或者from...import...as...
,假设现在有一个文件,叫做forms.html
,里面有两个宏分别为input
和textarea
,如下:
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{% endmacro %}
{% macro textarea(name, value='', rows=10, cols=40) %}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
}}">{{ value|e }}</textarea>
{% endmacro %}
6.3、导入宏的例子:
import...as...
形式:
{% import 'forms.html' as forms %}
<dl>
<dt>Username</dt>
<dd>{{ forms.input('username') }}</dd>
<dt>Password</dt>
<dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>
from...import...as.../from...import...
形式:
{% from 'forms.html' import input as input_field, textarea %}
<dl>
<dt>Username</dt>
<dd>{{ input_field('username') }}</dd>
<dt>Password</dt>
<dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ textarea('comment') }}</p>
7、include和set语句
7.1、include语句
include
语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码copy到另外一个模板的指定位置,看以下例子:
header.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="box">
我是头部~~
</div>
footer.html
<div class="copy">
©copyright eagleslab 1988
</div>
</body>
</html>
demo1.html
{% include 'header.html' %}
<h1>正文内容</h1>
{% include 'footer.html' %}
flask中
@app.route("/demo1/")
def demo1():
return render_template('demo1.html')
最终效果
7.3、赋值(set)语句
有时候我们想在在模板中添加变量,这时候赋值语句(set)就派上用场了,先看以下例子:
{% set name='eagle' %}
那么以后就可以使用name
来代替eagle
这个值了,同时,也可以给他赋值为列表和元组:
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
赋值语句创建的变量在其之后都是有效的,如果不想让一个变量污染全局环境,可以使用with
语句来创建一个内部的作用域,将set
语句放在其中,这样创建的变量只在with
代码块中才有效,看以下示例:
{% with %}
{% set foo = 42 %}
{{ foo }} foo is 42 here
{% endwith %}
也可以在with
的后面直接添加变量,比如以上的写法可以修改成这样:
{% with foo = 42 %}
{{ foo }}
{% endwith %}xxxxxxxxxx {% with foo = 42 %} {{ foo }}{% endwith %}{% endwith %}
这两种方式都是等价的,一旦超出with
代码块,就不能再使用foo
这个变量了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
{% set name="alice" %}
<h1>欢迎{{ name }}</h1>
{% set link=[('http://www.baidu.com','百度'),('http://www.qq.com','腾讯')] %}
<ul>
{% for i in link %}
<li><a href="{{ i[0] }}">{{ i[1] }}</a></li>
{% endfor %}
</ul>
{% set foo=24 %}
{% with foo=88 %}
<p>在局部作用域中的值:{{ foo }}</p>
{% endwith %}
<p>在全局作用域中的值:{{ foo }}</p>
{# with中的修改不会影响全局的内容 #}
</body>
</html>
8、模版继承
Flask
中的模板可以继承,通过继承可以把模板中许多重复出现的元素抽取出来,放在父模板中,并且父模板通过定义block
给子模板开一个口,子模板根据需要,再实现这个block
,假设现在有一个base.html
这个父模板,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="base.css" />
<title>{% block title %}{% endblock %}</title>
{% block head %}
<meta charset="UTF-8">
{% endblock %}
</head>
<body>
<div id="body">{% block body %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>
{% endblock %}
</div>
</body>
</html>
以上父模板中,抽取了所有模板都需要用到的元素html
、body
等,并且对于一些所有模板都要用到的样式文件style.css
也进行了抽取,同时对于一些子模板需要重写的地方,比如title
、head
、body
都定义成了block
,然后子模板可以根据自己的需要,再具体的实现。以下再来看子模板的代码:
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block head %}
{{ super() }}
{# super()在原本head里面内容后面加上下面的内容 #}
<style type="text/css">
.detail{
color: red;
}
</style>
{% endblock %}
{% block body %}
<h1>这里是首页</h1>
<p class="detail">
首页的内容
</p>
{% endblock %}
首先第一行就定义了子模板继承的父模板,并且可以看到子模板实现了title
这个block
,并填充了自己的内容,再看head
这个block
,里面调用了super()
这个函数,这个函数的目的是执行父模板中的代码,把父模板中的内容添加到子模板中,如果没有这一句,则父模板中处在head
这个block
中的代码将会被子模板中的代码给覆盖掉。
另外,模板中不能出现重名的block
,如果一个地方需要用到另外一个block
中的内容,可以使用self.blockname
的方式进行引用,比如以下示例:
<title>
{% block title %}
这是标题
{% endblock %}
</title>
<h1>{{ self.title() }}</h1>
以上示例中h1
标签重用了title
这个block
中的内容,子模板实现了title
这个block
,h1
标签也能拥有这个值。
另外,在子模板中,所有的文本标签和代码都要添加到从父模板中继承的block
中。否则,这些文本和标签将不会被渲染。
9、转义
转义的概念是,在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如<
、>
等,这些字符会破坏掉原来HTML
标签的结构,更严重的可能会发生XSS
跨域脚本攻击,因此如果碰到<
、>
这些字符的时候,应该转义成HTML
能正确表示这些字符的写法,比如>
在HTML
中应该用<
来表示等。
但是Flask
中默认没有开启全局自动转义,针对那些以.html
、.htm
、.xml
和.xhtml
结尾的文件,如果采用render_template
函数进行渲染的,则会开启自动转义。并且当用render_template_string
函数的时候,会将所有的字符串进行转义后再渲染。而对于Jinja2
默认没有开启全局自动转义,作者有自己的原因:
- 渲染到模板中的字符串并不是所有都是危险的,大部分还是没有问题的,如果开启自动转义,那么将会带来大量的不必要的开销。
Jinja2
很难获取当前的字符串是否已经被转义过了,因此如果开启自动转义,将对一些已经被转义过的字符串发生二次转义,在渲染后会破坏原来的字符串。
在没有开启自动转义的模式下(比如以.conf
结尾的文件),对于一些不信任的字符串,可以通过{{ content_html|e }}
或者是{{ content_html|escape }}
的方式进行转义。在开启了自动转义的模式下,如果想关闭自动转义,可以通过{{ content_html|safe }}
的方式关闭自动转义。而{%autoescape true/false%}...{%endautoescape%}
可以将一段代码块放在中间,来关闭或开启自动转义,例如以下代码关闭了自动转义:
<h1>{{ name }}</h1>
<!-- 正常的变量值是会被转义的 -->
{% autoescape false %}
<p>autoescaping is disabled here
<p>{{ name }}
<!-- 关闭转义以后,就不转义了 -->
{% endautoescape %}
10、数据类型和运算符
10.1、数据类型
Jinja
支持许多数据类型,包括:字符串、整型、浮点型、列表、元组、字典、True/False。
10.2、运算符
+
号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用+
运算符来操作字符串,字符串相加应该使用~
运算符。-
号运算符:只能针对两个数字相减。/
号运算符:对两个数进行相除。%
号运算符:取余运算。*
号运算符:乘号运算符,并且可以对字符进行相乘。**
号运算符:次幂运算符,比如2**3=8。in
操作符:跟python中的in
一样使用,比如{{1 in [1,2,3]}}
返回true
。~
号运算符:拼接多个字符串,比如{{"Hello" ~ "World"}}
将返回HelloWorld
。
11、静态文件的配置
Web
应用中会出现大量的静态文件来使得网页更加生动美观。类似于CSS
样式文件、JavaScript
脚本文件、图片文件、字体文件等静态资源。在Jinja
中加载静态文件非常简单,只需要通过url_for
全局函数就可以实现,看以下代码:
<link href="{{ url_for('static',filename='about.css') }}">
url_for
函数默认会在项目根目录下的static
文件夹中寻找about.css
文件,如果找到了,会生成一个相对于项目根目录下的/static/about.css
路径。当然我们也可以把静态文件不放在static
文件夹中,此时就需要具体指定了,看以下代码:
app = Flask(__name__,static_folder='C:\static')
那么访问静态文件的时候,将会到/static
这个文件夹下寻找。
评论区