当前位置 博文首页 > 云崖先生:Python Tornado系列(甩锅版)
tornado
是Python
界中非常出名的一款Web
框架,和Flask
一样它也属于轻量级的Web
框架。
但是从性能而言tornado
由于其支持异步非阻塞的特性所以对于一些高并发的场景显得更为适用。
tornado
简洁,高效,能够支持WebSocket
,其I/O
多路复用采用epoll
模式来实现异步,并且还有Future
期程对象来实现非阻塞。
tornado
与Django
和Flask
等基于WSGI
的框架有一个根本的区别,就是它实现socket
的模块是自己写的,并不是用其他模块。
A : socket部分 | B: 路由与视图函数对应关系(路由匹配) | C: 模版语法 | |
---|---|---|---|
django | 别人的wsgiref模块 | 自己写 | 自己的(没有jinja2好用 但是也很方便) |
flask | 别人的werkzeug(内部还是wsgiref模块) | 自己写 | 别人的(jinja2) |
tornado | 自己写的 | 自己写 | 自己写 |
如何编写一个最简单的tornado
:
import os
import tornado.ioloop
import tornado.web
BASE_DIR = os.path.dirname(__file__)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
settings = {
"debug": True,
"template_path": os.path.join(BASE_DIR, "views"), # 存放模板的文件夹
"static_path": os.path.join(BASE_DIR, "static"), # 存放静态文件的文件夹
}
application = tornado.web.Application(
[
(r"/index", IndexHandler), # 正则匹配,路由规则
],
**settings) # 配置项
if __name__ == '__main__':
# 1.新增socket Server端,并将fp描述符添加至select或者epoll中
application.listen(8888)
# 2.循环epoll进行监听
tornado.ioloop.IOLoop.instance().start()
模板文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>INDEX</title>
<link rel="stylesheet" href="{{static_url('common.css')}}">
<!-- <link rel="stylesheet" href="/static/common.css">-->
</head>
<body>
<p>INDEX</p>
</body>
</html>
下面将来探究Django/Flask/tornado
如何处理一次HTTP
请求:
Django
中处理一次HTTP
请求默认是以多线程模式完成。
在DJnago1.x版本之后,默认启动都是多线程形式,如果要启用单线程模式:
python manage.py runserver --nothreading
from django.shortcuts import HttpResponse
from threading import get_ident
def api1(request):
print(get_ident()) # 13246
return HttpResponse("api1")
def api2(request):
print(get_ident()) # 13824
return HttpResponse("api2")
Flask
的底层其实也是wsgiref
模块实现,所以处理一次HTTP
请求也是以多线程。
import flask
from threading import get_ident
app = flask.Flask(__name__)
@app.route('/api1')
def api1():
print(get_ident()) # 15952
return "api1"
@app.route('/api2')
def api2():
print(get_ident()) # 15236
return "api2"
if __name__ == '__main__':
app.run()
tornado
的处理方式是单线程+I/O
多路复用,这意味着必须挨个挨个排队处理每一个HTTP
请求:
import tornado.ioloop
import tornado.web
from threading import get_ident
class Api1Handler(tornado.web.RequestHandler):
def get(self):
print(get_ident()) # 10168
self.write("api1")
class Api2Handler(tornado.web.RequestHandler):
def get(self):
print(get_ident()) # 10168
self.write("api2")
application = tornado.web.Application([
(r"/api1",Api1Handler),
(r"/api2",Api2Handler),
])
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
要想了解tornado
的异步,就结合前面请求处理方式来看。
同步Web
框架与异步Web
框架的区别,这里有一篇文章写的不错:
同步与异步 Python 有何不同?
上面文章的一句话来概括就是说同步大多数都是监听一个socket
对象(服务器),当服务器对象的描述符状态(可读)发生改变后,就会创建一个新的线程来处理本次请求,Django/Flask
内部其实都是通过wsgiref
模块实现,并且wsgiref
依赖于socketserver
模块。如果想了解他们如何启动多线程进行服务监听,可参照早期文章(调用方式一模一样):
socketserver使用及源码分析
而对于tornado
来说,它不会创建多线程,而是将conn
双向连接对象放入事件循环中予以监听。
得益于epoll
的主动性,tornado
的速度非常快,而在处理完conn
(本次会话后),则会将conn
(Socket
)进行断开。 (HTTP
短链接)
拿Django
的单线程举例,当一个HTTP
请求到来并未完成时,下一个HTTP
请求将会被阻塞。
python manage.py runserver --nothreading
# 尝试以单线程的方式运行...对比tornado的单线程
代码如下:
from django.shortcuts import HttpResponse
import time
def api1(request):
time.sleep(5)
return HttpResponse("api1")
def api2(request):
return HttpResponse("api2")
而如果是tornado
的非阻塞方式,单线程模式下即使第一个视图阻塞了,第二个视图依旧能够进行访问.
import time
import tornado.ioloop
import tornado.web
from tornado import gen
from tornado.concurrent import Future
class Api1Handler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
future = Future()
# 方式一:添加回调 五秒后执行该异步任务
tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.done)
# 方式二:添加future
# tornado.ioloop.IOLoop.current().add_future(future,self.done)
# 方式三:添加回调
# future.add_done_callback(self.doing)
yield future
def done(self, *args, **kwargs):
self.write('api1')
self.finish() # 完成本次HTTP请求,将future的result状态改变
class Api2Handler(tornado.web.RequestHandler):
def get(self):
self.write("api2")
application = tornado.web.Application([
(r"/api1", Api1Handler),
(r"/api2", Api2Handler),
])
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
有关于Future
对象如何实现异步,下面会进行详细的探讨。
tornado
实现异步的根本技术点:I/O
多路复用的epoll
模式
tornado
实现非阻塞的根本技术点:Future
期程(未来)对象
仔细看起步介绍中,tornado
的配置,它是作为关键字传参传入Application
这个类中。
所以我们可以使用**{k1:v1}
的方式来设定配置项,下面举例一些常见的配置项。
常规配置项:
设置项 | 描述 |
---|---|
autoreload | 如果True,任何源文件更改时服务器进程将重新启动,如调试模式和自动重新加载中所述。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制 |
debug | 几种调试模式设置的简写,在调试模式和自动重新加载中描述。设置debug=True相当于autoreload=True,compiled_template_cache=False,static_hash_cache=False,serve_traceback=True |
default_handler_class|default_handler_args | 如果没有找到其他匹配项,将使用此处理程序; 使用它来实现自定义404页面(Tornado 3.2中的新增功能) |
compress_response | 如果True,文本格式的响应将自动压缩。Tornado 4.0的新功能 |
gzip | compress_response自Tornado 4.0以来已弃用的别名 |
log_function | 此函数将在每个记录结果的请求结束时调用(使用一个参数,即RequestHandler 对象)。默认实现将写入logging模块的根记录器。也可以通过覆盖来定制Application.log_request。 |
serve_traceback | 如果True,默认错误页面将包含错误的回溯。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制 |
ui_modules | ui_methods | 可以设置为UIModule可用于模板的映射或UI方法。可以设置为模块,字典或模块和/或dicts列表。有关详细信息,请参阅UI模块。 |
websocket_ping_interval | 如果设置为数字,则每n秒钟将对所有websockets进行ping操作。这有助于通过关闭空闲连接的某些代理服务器保持连接活动,并且它可以检测websocket是否在未正确关闭的情况下发生故障。 |
websocket_ping_timeout | 如果设置了ping间隔,并且服务器在这么多秒内没有收到“pong”,它将关闭websocket。默认值是ping间隔的三倍,最少30秒。如果未设置ping间隔,则忽略。 |
说点人话,debug
或者autoreload
为True
时,修改源文件代码将会自动重启服务,相当于Django
的重启功能。
而log_function
则可以自定制日志的输出格式,如下所示:
def log_func(handler):
if handler.get_status() < 400:
log_method = access_log.info
elif handler.get_status() < 500:
log_method = access_log.warning
else:
log_method = access_log.error
request_time = 1000.0 * handler.request.request_time()
log_method("%d %s %s (%s) %s %s %.2fms",
handler.get_status(), handler.request.method,
handler.request.uri, handler.request.remote_ip,
handler.request.headers["User-Agent"],
handler.request.arguments,
settings = {"log_function":log_func}
关于身份、验证、安全的配置项:
设置项 | 描述 |
---|---|
cookie_secret | 用于RequestHandler.get_secure_cookie 和set_secure_cookie签署cookie |
key_version | set_secure_cooki 当cookie_secret是密钥字典时,requestHandler 使用特定密钥对cookie进行签名 |
login_url | authenticated如果用户未登录,装饰器将重定向到此URL。可以通过覆盖进一步自定义RequestHandler.get_login_url |
xsrf_cookies | 如果True ,将启用跨站点请求伪造保护 |
xsrf_cookie_version | 控制此服务器生成的新XSRF cookie的版本。通常应该保留默认值(它始终是支持的最高版本),但可以在版本转换期间临时设置为较低的值。Tornado 3.2.2中的新功能,它引入了XSRF cookie版本2 |
xsrf_cookie_kwargs | 可以设置为要传递给RequestHandler.set_cookie XSRF cookie 的其他参数的字典 |
twitter_consumer_key | 所用的 tornado.auth模块来验证各种API,如检测这些种类账号是否登录等... |
twitter_consumer_secret | 同上.. |
friendfeed_consumer_key | 同上.. |
friendfeed_consumer_secret | 同上.. |
google_consumer_key | 同上.. |
google_consumer_secret | 同上.. |
facebook_api_key | 同上.. |
facebook_secret | 同上.. |
模板设置项:
设置项 | 描述 |
---|---|
autoescape | 控制模板的自动转义。可以设置为None禁用转义,或者设置 应该传递所有输出的函数的名称。默认为"xhtml_escape"。可以使用该指令在每个模板的基础上进行更改。{% autoescape %} |
compiled_template_cache | 默认是True; 如果False每个请求都会重新编译模板。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制 |
template_path | 包含模板文件的目录。可以通过覆盖进一步定制RequestHandler.get_template_path |
template_loader | 分配给tornado.template.BaseLoader自定义模板加载的实例 。如果使用此 设置,则忽略template_path和autoescape设置。可以通过覆盖进一步定制RequestHandler.create_template_loader |
template_whitespace | 控制模板中空格的处理; 查看tornado.template.filter_whitespace允许的值。Tornado 4.3中的新功能 |
静态文件相关设置:
设置项 | 描述 |
---|---|
static_hash_cache | 默认是True; 如果False 每次请求都会重新计算静态网址。此选项是Tornado 3.2中的新选项; 以前此功能由debug设置控制 |
static_path | 将从中提供静态文件的目录 |
static_url_prefix | 静态文件的Url前缀,默认为/static/ |
static_handler_class | static_handler_args | 可以设置为静态文件而不是默认文件使用不同的处理程序 tornado.web.StaticFileHandler。 static_handler_args如果设置,则应该是要传递给处理程序initialize方法的关键字参数的字典。 |
在tornado
中,一个url
对应一个类。
匹配方式为正则匹配,因此要注意使用^
与$
的使用。
由于匹配行为是从上至下,所以在定义时一定要注意顺序。
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/admin
class APIHandler(tornado.web.RequestHandler):
def get(self):
self.write("...")
settings = {"debug": True}
application = tornado.web.Application([
(r"^/a.{4}$", APIHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
使用正则分组()
解析出资源请求地址的一些参数。
匹配到的参数会通过位置传参的形式传递给控制器处理函数,所以接收参数可以任意命名,因此你可以通过*args
接收到所有匹配的参数:
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register/yunya
class RegisterHandler(tornado.web.RequestHandler):
def get(self,*args):
self.write(str(args)) # ('yunya',)
settings = {"debug": True}
application = tornado.web.Application([
(r"^/register/(\w+)", RegisterHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
如果确定这一被捕捉参数将被使用,则可指定形参进行接收:
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register/yunya
class RegisterHandler(tornado.web.RequestHandler):
def get(self,params):
self.write(params) # 'yunya'
settings = {"debug": True}
application = tornado.web.Application([
(r"^/register/(\w+)", RegisterHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
使用正则的有命分组(?P<组名>规则)
解析出资源请求地址的一些参数。
匹配到的参数会通过关键字传参的形式传递给控制器处理函数,所以接收参数必须与组名相同,因此你可以通过**kwargs
接收到所有匹配的参数:
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register/yunya
class RegisterHandler(tornado.web.RequestHandler):
def get(self,**kwargs):
self.write(str(kwargs)) # {'username': 'yunya'}
settings = {"debug": True}
application = tornado.web.Application([
(r"^/register/(?P<username>\w+)", RegisterHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
如果确定这一被捕捉参数将被使用,则可指定形参进行接收(形参命名必须与正则匹配的组名相同):
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register/yunya
class RegisterHandler(tornado.web.RequestHandler):
def get(self,username):
self.write(username) # 'yunya'
settings = {"debug": True}
application = tornado.web.Application([
(r"^/register/(?P<username>\w+)", RegisterHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
在tornado
中,路由匹配的参数捕捉不允许无名分组和有名分组的混合使用,这会引发一个异常:
application = tornado.web.Application([
(r"^/register/(/d+)/(?P<username>\w+)", RegisterHandler),
], **settings)
抛出的错误:
AssertionError: groups in url regexes must either be all named or all positional: '^/register/(/d+)/(?P<username>\\w+)$'
分组必须全部使用位置、或者使用命名。
反向解析要与路由命名同时使用:
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register
class RegisterHandler(tornado.web.RequestHandler):
def get(self):
print(self.reverse_url("reg")) # /register
self.write("注册页面")
settings = {"debug": True}
# 使用tornado.web.url来进行添加路由规则
application = tornado.web.Application([
tornado.web.url(r'/register', RegisterHandler, name="reg")
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
前端模板中的反向解析(必须注册名字):
{{reverse_url('reg')}}
在MCV
模型中,C
代表Controller
即为控制器,类似于Django
中的views
。
我们可以看见在tornado
中,控制器处理函数都毫不意外的继承了一个叫做tornado.web.RequestHandler
的对象,所有的方法都是从self
中进行调用,所以你可以查看它的源码获取所有方法,或者使用help()
函数获得它的DOC
。
下面我将例举出一些常用的方法。
以下是常用的获取相关属性以及方法,基本是RequestHandler
中的属性、方法与self.request
对象中封装的方法和属性:
属性/方法 | 描述 |
---|---|
self.request | 获取用户请求相关信息 |
self._headers | 获取请求头信息,基本请求头被过滤 |
self.request.headers | 获取请求头信息,包含基本请求头 |
slef.request.body | 获取请求体信息,bytes格式 |
self.request.remote_ip | 获取客户端IP |
self.request.method | 获取请求方式 |
self.request.version | 获取所使用的HTTP版本 |
self.get_query_argument() | 获取单个GET中传递过来的参数,如果多个参数同名,获取最后一个 |
slef.get_query_arguments() | 获取所有GET中传递过来的参数,返回列表的形式 |
self.get_body_argument() | 获取单个POST中传递过来的参数,如果多个参数同名,获取最后一个 |
self.get_body_arguments() | 获取所有POST中传递过来的参数,返回列表的形式 |
self.get_argument() | 获取单个GET/POST中传递过来的参数,如果多个参数同名,获取最后一个 |
self.get_arguments() | 获取所有GET/POST中传递过来的参数,返回列表的形式 |
self.request.files | 获取所有通过 multipart/form-data POST 请求上传的文件 |
self.request.host | 获取主机名 |
self.request.uri | 获取请求的完整资源标识,包括路径和查询字符串 |
self.request.query | 获取查询字符串的部分 |
self.request.path | 获取请求的路径( ?之前的所有内容) |
示例演示:
import tornado.ioloop
import tornado.web
# http://127.0.0.1:8888/register?name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
class RegisterHandler(tornado.web.RequestHandler):
def get(self):
# 获取客户端IP
print(self.request.remote_ip) # 127.0.0.1
# 查看请求方式
print(self.request.method) # GET
# 获取单个GET/POST传递的参数
print(self.get_query_argument("name")) # yunya
# 获取多个GET/POST传递的参数、list形式
print(self.get_query_arguments("hobby")) # ['篮球', '足球']
print(self.request.host) # 127.0.0.1:8888
print(self.request.uri) # register?name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
print(self.request.path) # /register
print(self.request.query) # name=yunya&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83
self.write("OK")
settings = {"debug":True}
application = tornado.web.Application([
tornado.web.url(r'/register', RegisterHandler, name="reg")
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
文件上传案例:
import tornado.ioloop
import tornado.web
class APIHandler(tornado.web.RequestHandler):
def post(self):
# step01:获取所有名为img的文件对象,返回一个列表 [img,img,img]
file_obj_list = self.request.files.get("img")
# step02:获取第一个对象
file_obj = file_obj_list[0]
# step03:获取文件名称
file_name = file_obj.filename
# step04:获取文件数据
file_body = file_obj.body
# step05:获取文件类型
file_type = file_obj.content_type
with open(f"./{file_name}",mode="wb") as f:
f.write(file_body)
self.write("OK")
settings = {"debug":True}
application = tornado.web.Application([
tornado.web.url(r'/api', APIHandler)
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
响应一般就分为以下几种,返回单纯的字符串,返回一个模板页面,返回JSON
格式数据,返回一个错误,以及重定向:
返回单纯字符串:
self.write("OK")
返回一个模板页面:
self.render("templatePath",**kwargs) # 传递给模板的数据
返回JSON
格式数据(手动JSON
):
import json
json_data = json.dumps({"k1":"v1"})
self.write(json_data)
返回一个错误(直接raise
引发异常即可):
raise tornado.web.HTTPError(403)
重定向:
self.redirect("/",status=301)
响应头相关的操作:
self.set_header("k1", 1)
self.add_header("k2", 2)
self.clear_header("k1")
我们可以在控制器中定义一个钩子函数initialize()
,并且可以在url
中对他进行一些参数传递:
import tornado.ioloop
import tornado.web
class APIHandler(tornado.web.RequestHandler):
def initialize(self, *args, **kwargs) -> None:
print(kwargs) # {k1:v1}
self.data = "某个数据"
def post(self):
print(self.data) # 某个数据
self.write("ok")
settings = {"debug": True}
application = tornado.web.Application([
tornado.web.url(r'/api', APIHandler, {"k1": "v1"}), # dict -> **kwargs
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
所有的钩子函数:
class APIHandler(tornado.web.RequestHandler):
def set_default_headers(self):
print("first--设置headers")
def initialize(self):
print("second--初始化")
def prepare(self):
print("third--准备工作")
def get(self):
print("fourth--处理get请求")
def post(self):
print('fourth--处理post请求')
def write_error(self, status_code, **kwargs):
print("fifth--处理错误")
def on_finish(self):
print("sixth--处理结束,释放资源--")
在settings
中指定模板所在目录,如不指定,默认在当前文件夹下:
import tornado.ioloop
import tornado.web
class APIHandler(tornado.web.RequestHandler):
def get(self):
# 找当前目录下的views文件夹,到views下去找api.html模板文件
self.render("api.html")
settings = {
"debug": True,
"template_path": "views", # 指定模板目录
"static_path": "static", # 指定静态文件目录
}
application = tornado.web.Application([
tornado.web.url(r'/api', APIHandler),
], **settings)
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
tornado
中的模板传参与Flask
相同。
模板传参可以通过k=v
的方式传递,也可以通过**dict
的方式进行解包传递:
class APIHandler(tornado.web.RequestHandler):
def get(self):
context = {
"name": "云崖",
"age": 18,
"hobby": ["篮球", "足球"]
}
self.render("api.html",**context)
# self.render("api.html",name="云崖",age=18,hobby=["篮球", "足球"])
渲染,通过{{}}
进行,注意:trdtmp
中不支持.
的深度查询访问,这点与DTL
和JinJa2
不同:
<body>
<p>{{name}}</p>
<p>{{age}}</p>
<p>{{hobby[0]}}-{{hobby[1]}}</p>
</body>
模板中有两种表现形式,一种是表达式形式以{{}}
进行包裹,另一种是命令形式以{% 命令 %}
包裹。
注意:tornado的模板语法的结束标识是{% end %},不是Django或jinja2的{% endblock %}
举例表达式形式:
# 渲染控制器函数传入的变量
<body>
欢迎{{ username }}登录
</body>
# 进行Python表达式
{{ 1 + 1 }}
# 导入模块并使用
{{ time.time() }}
举例命令形式:
{% if 1 %}
this is if
{% end %}
如果想对命令进行注释,则可以使用{# #}
,如果不想执行内容,则可以使用{{! {%! {#
为前缀,如下示例:
{{! 1 + 1}}
{%! if 1 %}
this is if
{%! end %}
{#! time.time() #}}
tornado
中的模板语言允许导入Python
包、模块:
{% import time %}
{{ time.time() }}
{% from util.modify import mul %}
{{mul(6,6)}}
模板中提供一些功能,可以在{{}}
或者{% %}
中进行使用:
模板调用的方法/功能/模块 | 描述 |
---|---|
escape | tornado.escape.xhtml_escape的别名 |
xhtml_escape | tornado.escape.xhtml_escape的别名 |
url_escape | tornado.escape.url_escape的别名 |
json_encode | tornado.escape.json_encode的别名 |
squeeze | tornado.escape.squeeze的别名 |
linkify | tornado.escape.linkify的别名 |
datetime | Python 的 datetime模组 |
handler | 当前的 RequestHandler对象 |
request | handler.request的別名 |
current_user | handler.current_user的别名 |
locale | handler.locale`的別名 |
_ | handler.locale.translate 的別名 |
static_url | for handler.static_url 的別名 |
xsrf_form_html | handler.xsrf_form_html 的別名 |
reverse_url | Application.reverse_url 的別名 |
Application | 设置中ui_methods和 ui_modules下面的所有项目 |
模板中的if
判断:
{% if username != 'no' %}
欢迎{{ username }}登录
{% else %}
请登录
{% end %}
for
循环:
<body>
{% for item in range(10) %}
{% if item == 0%}
<p>start</p>
{% elif item == len(range(10))-1 %}
<p>end</p>
{% else %}
<p>{{item}}</p>
{% end %}
{% end %}
</body>
while
循环:
{% set a = 0 %}
{% while a<5 %}
{{ a }}<br>
{% set a += 1 %}
{% end %}
默认的模板在渲染时都会将<
与>
以及空格等特殊字符替换为HTML
内容,如<
,>
等。
关于模板转义的方式有以下几种。
1.单变量去除转义:
{{'<b>你好</b>'}} # <b>你好</b>
{% raw '<b>你好</b>' %} # <b>你好</b>
2.当前模板全局去除转义:
{% autoescape None %} # 模板首行加入
3.整个项目去掉转义,为当前的application
进行配置:
settings = {
"autoescape":None, # 禁用转义
}
使用{% extends %}
引入一个定义好的模板。
使用{% blocak %}
和{% end %}
定义并替换块。
定义主模板:
# views/base.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}Document{% end %}</title>
{% block css %}
{% end %}
</head>
<body>
{% block main %}
{% end %}
</body>
{% block js %}
{% end %}
</html>
继承与使用模板:
{% extends 'base.html'%}
{% block title %}
API
{% end %}
{% block css %}
<style>
body{
background-color: red;
}
</style>
{% end %}
{% block main %}
<p>HELLO</p>
{% end %}
{% block js %}
<script>
alert("HELLO")
</script>
{% end %}
如果一个地方需要一块完整的模板文件,则使用模板引入即可:
{include 'templateName'}
定义公用模板:
# views/common.html
<h1>公用内容</h1>