20190118 Jinja2模板详解

为什么需要模板?

  1. 让HTML设计者和后端Python开发工作分离
  2. 减少使用PYthon的复杂程度,页面逻辑应该独立业务逻辑,这样才能开发出易于维护的程序
  3. 模板非常灵活、快速和安全,对设计者和开发者会更友好

Python语言自带的模板

In : from string import Template
In : s = Template('$who likes $what')
In : s.substitute(who='tim', what='kung pao')
Out: 'tim likes kung pao'
In : s = Template("$var is here but $missing is not provided")
In : s.safe_substitute(var='tim')Out: 'timis here but $missing isnot provided'
In : class MyTemplate(Template):
...: delimiter = '@' # 使用`@`为分隔符
...: idpattern = '[a-z]+\.[a-z]+' # 符合的模式才会被替换
...:
In : t = MyTemplate('@with.dot @notdoted')
In : t.safe_substitute({'with.dot': 'replaced', 'notdoted': 'not replaced'})
Out: 'replaced @notdoted'

Jinja2特点

  1. 沙箱中执行
  2. 强大的HTML自动转移系统保护系统免受XSS攻击
  3. 模板继承
  4. 即时编译最优的Python代码
  5. 易于调式
  6. 可配置的语法

安装Jinja2

❯ pip install Jinja2❯ 
python -c 'import jinja2; print(jinja2.__version__)'2.10

基本API使用

In : from jinja2 import Template
In : template = Template('Hello {{ name }}!')
In : template.render(name='Xiao Ming')
Out: u'Hello Xiao Ming!'

In : from jinja2 import EnvironmentIn : env = Environment()
In : template = env.from_string('Hello {{ name }}!')
In : template.render(name='Xiao Ming')
Out: u'Hello Xiao Ming!'
❯ echo "Hello {{ name }}" > hello.html❯ 
touch app.py
❯ ipython
In : from jinja2 import Environment, PackageLoader
In : env = Environment(loader=PackageLoader('app', 'templates'))
In : template = env.get_template('hello.html')
In : template
Out: <Template 'hello.html'>
In : template.render(name='Geng WenHao')
Out: u'Hello Geng WenHao'

Jinja2支持多种loader

  1. FileSystemLoader
In : from jinja2 import FileSystemLoader
In : loader = FileSystemLoader('templates')
In : template = Environment(loader=loader).get_template('hello.html')
In : loader = FileSystemLoader(['/path/to/templates', '/other/path'])
  1. DictLoader
In: loader = DictLoader({'hello.html': 'Hello {{ name }}'})
  1. 自定义Loader
❯ cat my_loader.py
# coding=utf-8
from os.path import join, exists, getmtime
from jinja2 import BaseLoader, TemplateNotFound

classMyLoader(BaseLoader):
def__init__(self, path):
self.path = path


def get_source(self, environment, template):
    path = join(self.path, template)
    if not exists(path):
        raise TemplateNotFound(template)
    mtime = getmtime(path)
    with file(path) as f:
        source = f.read().decode('utf-8')
    return source, path, lambda: mtime == getmtime(path)

字节码缓存 - FileSystemBytecodeCache

from jinja2 import FileSystemBytecodeCache
bcc = FileSystemBytecodeCache('/tmp/jinja2_cache', '%s.cache')

字节码缓存 - MemcachedBytecodeCache

from jinja2 import MemcachedBytecodeCache
import memcache

mc = memcache.Client(['127.0.0.1:11211'], debug=0)
bxx = MemcachedBytecodeCache(mc, prefix='jinja2/bytecode/')

自定义字节码缓存

❯ mkdir tmp # 创建一个存放字节码缓存的目录, 之后的临时文件也都放在这里面
❯ cat bytecode_cache.py
# coding=utf-8
import shutil
from os import path
from jinja2 import Environment, DictLoader, BytecodeCache


class MyCache(BytecodeCache):
    def __init__(self, directory):
        self.directory = directory


def load_bytecode(self, bucket):
    filename = path.join(self.directory, bucket.key)
    if path.exists(filename):
        with open(filename, 'rb') as f:
            bucket.load_bytecode(f)


def dump_bytecode(self, bucket):
    filename = path.join(self.directory, bucket.key)
    with open(filename, 'wb') as f:
        bucket.write_bytecode(f)


def clear(self):
    shutil.rmtree(self.directory)


if __name__ == '__main__':
    loader = DictLoader({'hello.html': 'Hello {{ name }}'})
    env = Environment(loader=loader, bytecode_cache=MyCache('tmp'))
    template = env.get_template('hello.html')
    print(template.render(name='Geng WenHao'))

❯ python bytecode_cache.pyHello Xiao Ming
❯ ll tmptotal 4.0K-rw-r--r-- 1 dongwm staff 870 Sep 1516:349a60b6907b5908143cfea0aa8f6f88cd6b9138df

自定分隔符

In : template = Template('Hello $ name $!', variable_start_string='$', variable_end_string='$') # 现在使用`$`作为分隔符
In : template.render(name='Geng WenHao')
Out: u'Hello Geng WenHao!

''''
* block_start_string:块开始标记。默认是{%。
* block_end_string:块结束标记。默认是%}。
* comment_start_string:注释开始标记。默认是{#。
* comment_end_string:注释结束标记。默认是#}。
'''

沙箱

In : from jinja2.sandbox import SandboxedEnvironment
In : env = SandboxedEnvironment()
In : env.from_string("{{ func.func_code }}").render(func=lambda:None)
Out: u''
In : env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None)
--------------------------------------------------------------------SecurityError

JinJa2的基本语法

> cat simple.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Simple Page</title>
    </head>
    <body>{# This is a Comment #}
        <ul id="navigation">
            {% for item in items %}
                <li><a href="{{ item.href }}">{{ item['caption'] }}</a></li>
            {% endfor %}
        </ul>
        <h1>{{ title | trim }}</h1>
        <p>{{ content }}</p>
  </body>
</html>

空白控制

{% for item in seq -%}
    {{ item }}
{%- endfor %}

{%- if foo -%}...{% endif %} # 有效的
{% - if foo - %}...{% endif %} # 无效的

行语句

<ul>
# for item in seq:
    <li>{{ item }}</li>
# endfor
</ul>

<ul>
{% for item in seq %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

<ul># for href, caption in [('index.html', 'Index'), ('about.html', 'About')]:
    <li><a href="{{ href }}">{{ caption }}</a></li>
# endfor
</ul>

# for item in seq:
    <li>{{ item }}</li> ## this comment is ignored
# endfor

模板继承

❯ cat base.html
<!DOCTYPE html>
<html lang="en">
    <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 %}
            {% endblock %}
        </div>
    </body>
</html>

❯ cat index.html
{% 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 content %}

控制结构 - for

{% for item in items %}
...
{% endfor %}

<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no users found</em></li>
{% endfor %}
</ul>

# 特殊变量
1.loop.index:当前循环迭代的次数(从1开始)
2.loop.index0:当前循环迭代的次数(从0开始)
3.loop.first:如果是第一次迭代值为True
4.loop.last:如果是最后一次迭代值为True
5.loop.length:序列中的项目数

控制结构 - 条件语句

{% if xiaoming.is_admin %}    
    GengWenHao is a Administrator
{% elif xiaoming.is_editor %}    
    GengWenHao is a Editor
{% else %}    
    Unknown User
{% endif %}

{% extends layout_template if layout_template is defined else 'master.html' %}

In : Template('''
...: {% macro hello(name) %}
...: Hello {{ name }}
...: {% endmacro %}
...: <p>{{ hello('world') }}</p>
...: ''').render()
Out: u'\n\n<p>\n Hello world\n</p>'

赋值

In : print Template('''
...: {% set a = 1 %}
...: {% set b, c = range(2) %}
...: <p>{{ a }} {{ b }} {{ c }}</p>
...: ''').render()
Out: u'\n\n\n<p>1 0 1</p>'

include

{% include 'header.html' %}    
    Body
{% include 'footer.html' %}

{% include "sidebar.html" ignore missing %}

import

❯ cat macro.html
{% macrohello(name) %}    
Hello {{ name }}
{% endmacro %}

{% macro strftime(time, fmt='%Y-%m-%d %H:%M:%S') %}    
    {{ time.strftime(fmt) }}
{% endmacro %}❯ 

> cat hello_macro.html
{% import 'macro.html'asmacro%}
{% from 'macro.html' import hello as _hello, strftime %}

<p>{{ macro.hello('world') }}</p>
<p>{{ strftime(time) }}</p>

In : from jinja2 import FileSystemLoader, Environment
In : from datetime import datetime
In : loader = FileSystemLoader('templates')
In : template = Environment(loader=loader).get_template('hello_macro.html')
In : template.render(time=datetime.now())
Out:'\n\n\n<p>\n Hello world\n</p>\n<p>\n 2018-09-15 23:05:19\n</p>'

延伸阅读

  1. http://jinja.pocoo.org/docs/2.10/templates/#list-of-global-functions
  2. http://jinja.pocoo.org/docs/2.10/
posted @ 2019-04-13 02:06  耿文浩  阅读(2516)  评论(0编辑  收藏  举报