博主在使用Flask过程,预在项目中使用中Jinja2模板。本文为博主Jinja2的学习笔记,主要为了形成对Jinja2模板的相对系统的认识和为日后复习备忘。
为什么使用模板
本节部分参考转载自廖雪峰大大的相关教学
主要从web实质出发,从开发者角度,一步步向高层抽象,说明为什么要使用模板。
Web与Web静态服务器
一个Web应用的本质就是:
- 浏览器发送一个HTTP请求
- 服务器收到请求,生成一个HTML文档
- 服务器把HTML文档作为HTTP响应的Body发送给浏览器
- 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示
所以,最简单的Web应用就是:
- 先把HTML用文件保存好
- 用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML
- 返回
Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
动态HTML
WSGI
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
WSGI接口定义
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
- environ:一个包含所有HTTP请求信息的
dict
对象; - start_response:一个发送HTTP响应的函数。
WSGI接口调用
在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下,都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。
然后,函数的返回值'<h1>Hello, web!</h1>'
将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
WSGI过程分析
整个application()
函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。
所以application()
函数必须由WSGI服务器来调用。
框架
了解了WSGI框架,我们发现:其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。
但是如何处理HTTP请求不是问题,问题是如何处理100个不同的URL。
每一个URL可以对应GET和POST请求,当然还有PUT、DELETE等请求,但是我们通常只考虑最常见的GET和POST请求。
一个最简单的想法是从environ
变量里取出HTTP请求的信息,然后逐个判断:
def application(environ, start_response):
method = environ['REQUEST_METHOD']
path = environ['PATH_INFO']
if method=='GET' and path=='/':
return handle_home(environ, start_response)
if method=='POST' and path='/signin':
return handle_signin(environ, start_response)
...
只是这么写下去代码是肯定没法维护了。
代码这么写没法维护的原因是因为WSGI提供的接口虽然比HTTP接口高级了不少,但和Web App的处理逻辑比,还是比较低级,我们需要在WSGI接口之上能进一步抽象
Web框架:让我们专注于用一个函数处理一个URL,至于URL到函数的映射,就交给Web框架来做。
模板
Web框架的不足
Web框架把我们从WSGI中拯救出来了。现在,我们只需要不断地编写函数,带上URL,就可以继续Web App的开发了。
但是,Web App不仅仅是处理逻辑,展示给用户的页面也非常重要。在函数中返回一个包含HTML的字符串,简单的页面还可以,但是,多行的HTML极难做到。
生成HTML页面的难度很大。在Python代码里拼字符串是不现实的,所以,模板技术出现了。
使用模板,我们需要预先准备一个HTML文档,这个HTML文档不是普通的HTML,而是嵌入了一些变量和指令,然后,根据我们传入的数据,替换后,得到最终的HTML,发送给用户:
mvc-seqMVC
这就是MVC:Model-View-Controller,中文名“模型-视图-控制器”。
Controller
- Controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等等
- Python处理URL的函数就是Controller
View
- View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML
- 包含变量
{{ name }}
的模板就是View
Model
- Model是用来传给View的,这样View在替换变量的时候,就可以从Model中取出相应的数据。
上面的例子中,Model就是一个dict
:
{ 'name': 'Michael' }
只是因为Python支持关键字参数,很多Web框架允许传入关键字参数,然后,在框架内部组装出一个dict
作为Model。
MVC优点
通过MVC,我们在Python代码中处理Model和Controller,而View是通过模板处理的,这样我们就成功地把Python代码和HTML代码最大限度地分离了。
使用模板的另一大好处是,模板改起来很方便,而且,改完保存后,刷新浏览器就能看到最新的效果,这对于调试HTML、CSS和JavaScript的前端工程师来说实在是太重要了。
在Jinja2模板中,我们用{{ name }}
表示一个需要替换的变量。很多时候,还需要循环、条件判断等指令语句,在Jinja2中,用{% ... %}
表示指令。
除了Jinja2,常见的模板还有:
-
Mako:用
<% ... %>
和${xxx}
的一个模板; -
Cheetah:也是用
<% ... %>
和${xxx}
的一个模板; -
Django:Django是一站式框架,内置一个用
{% ... %}
和{{ xxx }}
的模板。
小结
有了MVC,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率
Jinja2模板简介
- 组成
模板语言本质上包括一些变量和编程逻辑,并在被解释(或渲染成HTML)时被真实值替换 - 扩展名
模板仅仅是文本文件。它可以生成任何基于文本的格式(HTML、XML、CSV、LaTex 等等)。 它并没有特定的扩展名, .html 或 .xml 都是可以的。 - 位置
一般放在Flask项目的/templates
目录里
一. 语句与表达式
控制语句--{%控制语句%}
- 控制语句都是包含在分隔符
{% %}
内 - 模板中无法像代码中一样靠缩进来判断代码块的结束,故:
注意if(for同理)控制语句要用{% endif %}
来结束
{% if name and name == 'admin' %}
<h1>This is admin console</h1>
{% elif name %}
<h1>Welcome {{ name }}!</h1>
{% else %}
<h1>Please login</h1>
{% endif %}
例子:
Flask Python代码:
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/hello')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
模板代码:
<!doctype html>
<title>Hello Sample</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
表达式--{{表达式}}
模板的表达式都是包含在分隔符{{ }}
内的
表达式类型 | 例子 |
---|---|
变量 | 由Flask渲染模板时传过来 |
任意一种Python基础类型 | 直接显示基础类型没意义,一般配合其他表达式用 |
运算 | 包括算数运算,如{{ 2 + 3 }};比较运算,如{{ 2 > 1 }};逻辑运算,如{{ False and True }} |
过滤器“|”和测试器“is” | 这个在后面会介绍 |
函数调用 | 如{{ current_time() }};数组下标操作,如{{ arr[1] }} |
“in”操作符 | 如{{ 1 in [1,2,3] }} |
字符串连接符”~” | 作用同Python中的”+”一样,如{{ “Hello ” ~ name ~ “!” }} |
“if”关键字,如{{ ‘Hi, %s’ % name if name }} | 这里的”if”不是条件控制语句。 |
例子:
<a class="navbar-brand" href={{ url_for('index') }}>Flask小例子 </a>
注释块--{{# 注释 #}}
要把模板中一行的部分注释掉,默认使用 {# ... #} 注释语法。
行语句--#这是行语句
如果应用启用了行语句,就可以把一个行标记为一个语句。例如如果行语句前缀配置为 #
,下面的两个例子是等价的:
<ul>
# for item in seq
<li>{{ item }}</li>
# endfor
</ul>
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
继承--block
Jinja 中最强大的部分就是模板继承。
定义
模板继承允许你构建一个包含你站点共同元素的基本模板“骨架”,并定义子模板可以覆盖的 块。
可以将整个布局设置为父模板,让其子模板继承。这样可以非常方便的控制整个网站的外观。
对比一下面向对象编程的继承概念,容易理解。
做法
子模板用内容填充空的块。
例子
父模板
这个 base.html
模板,定义了一个简单的 HTML 骨架文档,你可能使用一个简单的两栏页面。用内容填充空的块是子模板的工作:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
在本例中, {% block %}
标签定义了四个字幕版可以填充的块。
所有的 block 标签 告诉模板引擎子模板可以覆盖模板中的这些部分。
子模块--{% extends "父模板" %}
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}
- 语法
{% extends %}
标签是这里的关键。它告诉模板引擎这个模板“继承”另一个模板。 当模板系统对这个模板求值时,首先定位父模板。 - 位置
extends 标签应该是模板中的第一个 标签。它前面的所有东西都会按照普通情况打印出来
网友评论