整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

5分钟学会用Python Jinja2模板引擎渲染HTML网页

深入Python Web开发的过程中,HTML模板渲染是构建动态Web应用的重要环节。今天将详细探讨如何使用Python中最流行的模板引擎之一——Jinja2来进行高效且灵活的模板渲染。通过具体的代码示例,将了解如何结合Flask框架与Jinja2实现数据与视图的完美分离。

Jinja2简介

Jinja2是一个强大的现代模板引擎,设计用于Python web开发项目,它支持变量替换、控制结构、过滤器和宏等丰富的功能。Flask框架默认集成并推荐使用Jinja2进行模板渲染,使其成为Python Web开发者手中不可或缺的工具。

安装与配置Jinja2(适用于未安装Flask环境)

如果已经安装过Flask,Jinja2通常已随Flask一同安装。若需要单独安装:

pip install Jinja2

基本使用示例

1.创建模板文件

在项目中创建一个名为templates的文件夹,并在其内放置我们的HTML模板文件,例如index.html:

<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Html渲染示例</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>Welcome to {{ site_name }}!</p>
    <ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
</body>
</html>

在这个模板中,我们用{{ variable }}表示变量占位符,{% %}表示控制结构

2.Flask中加载和渲染模板

在Flask应用中,我们需要导入render_template函数来加载并渲染模板:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    # 定义传递给模板的数据
    title = '主页'
    site_name = 'Html模板渲染示例'
    items = ['Item 1', 'Item 2', 'Item 3']

    # 渲染模板并将数据传递给模板
    return render_template('index.html', title=title, site_name=site_name, items=items)

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

当用户访问主页时,Flask会调用home函数,其中render_template函数会查找templates目录下的index.html模板,并将定义好的变量替换到相应的位置。

Jinja2高级特性

1. 控制结构

除了简单的循环外,Jinja2还支持条件判断和其他逻辑操作:

<!-- 在模板中添加条件判断 -->
{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}!</p>
{% else %}
    <p>Please log in.</p>
{% endif %}

2. 过滤器

Jinja2内置了众多过滤器,可以对变量进行处理,如格式化日期、转换大小写等:

<!-- 使用过滤器格式化日期 -->
<p>The date is: {{ current_date|date("Y-m-d") }}</p>

3. 宏

宏允许复用或封装常见的HTML片段,提高代码可读性和维护性:

{# 在一个单独的macros.html模板中定义宏 #}
{% macro render_item(item) %}
    <div class="item">
        <h3>{{ item.title }}</h3>
        <p>{{ item.description }}</p>
    </div>
{% endmacro %}

然后在其他模板中引入并使用该宏:

{% from 'macros.html' import render_item %}

<ul>
{% for item in items %}
    {{ render_item(item) }}
{% endfor %}
</ul>

结语

通过本文,我们已经深入了解了Jinja2模板引擎的基础使用方法以及其高级特性。掌握好Jinja2能够显著提升你的Web应用开发效率,实现更复杂、更美观的动态页面布局。

关注我,手把手带你快速入门Python Web编程!

、前言

模板语言由HTML代码和逻辑控制代码组成,此处 @PHP 。通过模板语言可以快速的生成预想的HTML页面。应该算是后端渲染不可缺少的组成部分。

二、功能介绍

通过使用学习 tornadobottle 的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自 tornadobottle 的语法。可以用来做一些简单的事情 网页渲染邮件内容生成 等HTML显示方面。以下就是简单的语法使用介绍。

1. 变量。使用 {{ }} 包裹起来,里面的变量为Python传入。模板渲染时会将传入的变量转换成字符串并填入对应位置。

# 模板文件内容
<title>{{my_title}}</title>
<label>{{ session.name }}</label>
# py代码调用  t_html 为上面的内容
Template(t_html).render(my_title="标题", session = some_obj) 

2. 转义。默认传入的数据都会进行HTML转义,可以使用 {% raw value %} 来将value的内容按原始字符串输出。

# 模板文件内容
<p>{% raw value %} </p>
# Py调用内容
Template(t_html).render(my_title="<label>显示标签</label>")

3. 条件控制。支持Python的 if,elif,else 。条件代码需要放在 {% %} 内部,并且在条件结束后需要额外增加 {% end %} ,用于标识条件控制语句块范围。

# 模板文件内容
{% if a > 1%}
<label>A大于1</label>
{% else %}
<label>A小于或等于1</label>
{% end %}
# py调用
Template(t_html).render(a=1)

4. 循环控制。支持Python的 forwhile 。与条件控制一样也需要放在 {% %} 内部,并且结束处需要额外增加 {% end %} ,用于标识循环控制语句块的范围。

# 模板文件内容
{% for i in range(10) %}
  <label>当前序号:{{i+1}}</label>
{% end %}
# py调用
Template(t_html).render()  

5. 变量声明。如果需要在模板文件内声明一个变量,方便使用时,可以通过 set 来实现。具体格式为 {% set v = xx %} 。通过 set 声明的变量在整个模板文件中都可以使用,包括在条件控制和循环控制中作为条件判断也可以。

# 模板文件内容
{% set a = 1 %}
<label>a的值:{{a}}</label>

三、源码

这个模板语言模块是在 Python2.7 上面开发使用的,如果要在 Python3+ 上使用需要对 strbytes 进行一些处理即可,由于没有引用任何其他模块,可以很好地独立使用。

  1 # -*- coding:utf-8 -*-
  2 
  3 """ 模板语言"""
  4 
  5 # TOKEN相关的定义
  6 TOKEN_S_BRACE = "{"
  7 TOKEN_S_BLOCK = "%"
  8 TOKEN_EXPRESSION_L = "{{"  
  9 TOKEN_EXPRESSION_R = "}}"
 10 TOKEN_BLOCK_L = "{%"
 11 TOKEN_BLOCK_R = "%}"
 12 TOKEN_KEY_SET = "set"
 13 TOKEN_KEY_RAW = "raw"
 14 TOKEN_KEY_IF = "if"
 15 TOKEN_KEY_ELIF = "elif"
 16 TOKEN_KEY_ELSE = "else"
 17 TOKEN_KEY_FOR = "for"
 18 TOKEN_KEY_WHILE = "while"
 19 TOKEN_KEY_END = "end"
 20 TOKEN_KEY_BREAK = "break"
 21 TOKEN_KEY_CONTINUE = "continue"
 22 TOKEN_SPACE = " "
 23 TOKEN_COLON = ":"
 24 # Token标记 {{}} {% %}
 25 TOKEN_FLAG_SET = {TOKEN_S_BRACE, TOKEN_S_BLOCK}
 26 # 简单的语句
 27 TOKEN_KEY_SET_SIMPLE_EXPRESSION = {TOKEN_KEY_SET, TOKEN_KEY_RAW}
 28 # 前置条件
 29 TOKEN_KEY_PRE_CONDITION = {
 30     # end 必须在if/elif/else/for/while 后面
 31     TOKEN_KEY_END: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_ELSE, 
 32                     TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
 33     # elif 必须在if 后面
 34     TOKEN_KEY_ELIF: {TOKEN_KEY_IF},
 35     # else 必须在if/elif 后面
 36     TOKEN_KEY_ELSE: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
 37 }
 38 # 循环语句
 39 TOKEN_KEY_LOOP = {TOKEN_KEY_WHILE, TOKEN_KEY_FOR}
 40 # 循环的控制break continue
 41 TOKEN_KEY_LOOP_CTRL = {TOKEN_KEY_BREAK, TOKEN_KEY_CONTINUE}
 42 
 43 class ParseException(Exception):
 44     pass
 45 
 46 class TemplateCode(object):
 47     def __init__(self):
 48         self.codeTrees = {"parent": None, "nodes": []}
 49         self.cursor = self.codeTrees
 50         self.compiled_code = None
 51 
 52     def create_code(self):
 53         """创建一个代码子块"""
 54         child_codes = {"parent": self.cursor, "nodes": []}
 55         self.cursor["nodes"].append(child_codes)
 56         self.cursor = child_codes
 57 
 58     def close_code(self):
 59         """ 关闭一个代码子块 """
 60         assert self.cursor["parent"] is not None, "overflow"
 61         self.cursor = self.cursor["parent"]
 62 
 63     def append_text(self, text):
 64         """ 添加文本 """
 65         # 排除空行
 66         self.cursor["nodes"].append("_add(%r)" % text)
 67 
 68     def append_express(self, express, raw=False):
 69         """ 表达式 """
 70         if raw:
 71             temp_exp = "_t_exp = _str_(%s)" % express
 72         else:
 73             temp_exp = "_t_exp = _esc_(%s)" % express
 74         self.cursor["nodes"].append(temp_exp)
 75         self.cursor["nodes"].append("_add(_t_exp)")
 76 
 77     def append_statement(self, statement):
 78         """ 语句 """
 79         temp_statement = "%s" % statement
 80         self.cursor["nodes"].append(temp_statement)
 81 
 82     def reset(self):
 83         self.codeTrees = {"parent": None, "nodes": []}
 84         self.cursor = self.codeTrees
 85         self.compiled_code = None
 86 
 87     def build_code(self, filename):
 88         temp_code_buff = []
 89         self.write_buff_with_indent(temp_code_buff, "def _template_render():", 0)
 90         self.write_buff_with_indent(temp_code_buff, "_codes = []", 4)
 91         self.write_buff_with_indent(temp_code_buff, "_add = _codes.append", 4)
 92         self.write_codes(temp_code_buff, self.codeTrees, 4)
 93         self.write_buff_with_indent(temp_code_buff, "return ''.join(_codes)", 4)
 94         temp_code = "".join(temp_code_buff)
 95         self.compiled_code = compile(temp_code,filename, "exec", dont_inherit=True)
 96 
 97     def write_codes(self, code_buff, codes, indent):
 98         for node in codes.get("nodes", []):
 99             if isinstance(node, dict):
100                 self.write_codes(code_buff, node, indent+4)
101             else:
102                 self.write_buff_with_indent(code_buff, node, indent)
103 
104     def generate(self, **kwargs):
105         temp_namespace = {}
106         temp_namespace['_str_'] = self.to_utf8
107         temp_namespace['_esc_'] = self.to_safe_utf8
108         temp_namespace.update(kwargs)
109         exec(self.compiled_code, temp_namespace)
110         return temp_namespace['_template_render']()
111 
112     @staticmethod
113     def write_buff_with_indent(code_buff, raw_str, indent):
114         """"""
115         temp = (" " * indent) + raw_str + "\n"
116         code_buff.append(temp)
117 
118     @staticmethod
119     def to_utf8(raw_str):
120         """ 转换 """
121         if isinstance(raw_str, str):
122             return raw_str
123         elif isinstance(raw_str, bytes):
124             return raw_str.decode()
125         return str(raw_str)
126 
127     @staticmethod
128     def to_safe_utf8(raw_str):
129         """ 过滤html转义 """
130         text = TemplateCode.to_utf8(raw_str)
131         return text.replace("&", "&").replace("<", "<").replace(">", ">")
132 class Template(object):
133     """模板类"""
134     def __init__(self, input_obj,filename="<string>", **namespace):
135         """模板初始化"""
136         self.namespace = {}
137         self.namespace.update(namespace)
138         # 将数据丢进去解析生成编译代码
139         self.lexer = TemplateLexer(input_obj, filename)
140 
141     def render(self, **kwargs):
142         """渲染模板 """
143         temp_name_space = {}
144         temp_name_space.update(self.namespace)
145         temp_name_space.update(kwargs)
146         # 执行渲染
147         return self.lexer.render(**kwargs)
148 
149 class TemplateLexer(object):
150     """模板语法分析器 """
151     def __init__(self, input_obb, filename="<string>"):
152         if hasattr(input_obb, "read"):
153             self.raw_string = input_obb.read()
154         else:
155             self.raw_string = input_obb
156         self.filename = filename
157         # 记录当前的位置
158         self.pos = 0
159         # 记录原始数据的总长度
160         self.raw_str_len = len(self.raw_string)
161         # 记录解析的数据
162         self.code_data = TemplateCode()
163         # 开始解析
164         self.parse_template()
165 
166     def match(self, keyword, pos=None):
167         return self.raw_string.find(keyword, pos if pos is not None else self.pos)
168 
169     def cut(self, size=-1):
170         """剪取数据 size切割数据的大小,-1表示全部"""
171         if size == -1:
172             new_pos = self.raw_str_len
173         else:
174             new_pos = self.pos + size
175         s = self.raw_string[self.pos: new_pos]
176         self.pos = new_pos
177         return s
178 
179     def remaining(self):
180         """获取剩余大小 """
181         return self.raw_str_len - self.pos
182 
183     def function_brace(self):
184         """ 获取{{  / {% """
185         skip_index = self.pos
186         while True:
187             index = self.match(TOKEN_S_BRACE, skip_index)  # {% {{
188             # 没找到
189             if index == -1:
190                 return None, -1
191             # 末尾
192             if index >= self.raw_str_len:
193                 return None, -1
194             # 匹配类型
195             next_value = self.raw_string[index + 1:index + 2]
196             if next_value not in TOKEN_FLAG_SET:
197                 skip_index = index + 1
198                 # 说明不是关键类型
199                 continue
200             brace = self.raw_string[index: index + 2]
201             return brace, index
202         return None, -1
203 
204     def read_content_with_token(self, index, begin_token, end_token):
205         """
206         读取匹配token的内容
207         """
208         end_index = self.match(end_token)
209         if end_index == -1:
210             return ParseException("{0} missing end token {1}".format(begin_token, end_token))
211         # 过滤 begin_token
212         self.pos = index + len(begin_token)
213         content = self.cut(end_index - self.pos)
214         # 去除末尾 end_token
215         self.cut(len(end_token))
216         return content
217 
218     def add_simple_block_statement(self, operator, suffix):
219         if not suffix:
220             raise ParseException("{0} missing content".format(operator))
221         if operator == TOKEN_KEY_SET:
222             self.code_data.append_statement(suffix)
223         elif operator == TOKEN_KEY_RAW:
224             self.code_data.append_express(suffix, True)
225         else:
226             raise ParseException("{0} is undefined".format(operator))
227 
228     def parse_template(self):
229         """解析模板 """
230         # TODO 检查模板文件是否更改过,如果没有则不需要重新解析
231         self.code_data.reset()
232         # 解析模板原文件
233         self.__parse()
234         # 生成编译code
235         self.__compiled_code()
236 
237     def render(self, **kwargs):
238         return self.code_data.generate(**kwargs)
239 
240     def __parse(self, control_operator=None, in_loop=False):
241         """开始解析"""
242         while True:
243             if self.remaining() <= 0:
244                 if control_operator or in_loop:
245                     raise ParseException("%s missing {%% end %%}" % control_operator)
246                 break
247             # 读取 {{ {%
248             brace, index = self.function_brace()
249             # 说明没有找到
250             if not brace:
251                 text = self.cut(index)
252                 self.code_data.append_text(text)
253                 continue
254             else:
255                 text = self.cut(index - self.pos)
256                 if text:
257                     self.code_data.append_text(text)
258 
259             if brace == TOKEN_EXPRESSION_L:
260                 content = self.read_content_with_token(index, TOKEN_EXPRESSION_L, TOKEN_EXPRESSION_R).strip()
261                 if not content:
262                     raise ParseException("Empty Express")
263                 self.code_data.append_express(content)
264                 continue
265             elif brace == TOKEN_BLOCK_L:
266                 content = self.read_content_with_token(index, TOKEN_BLOCK_L, TOKEN_BLOCK_R).strip()
267                 if not content:
268                     raise ParseException("Empty block")
269 
270                 # 得到表达式 for x in x ;  if x ;  elif x ;  else ;  end ;  set ;  while x ;
271                 operator, _, suffix = content.partition(TOKEN_SPACE)
272                 if not operator:
273                     raise ParseException("block missing operator")
274 
275                 suffix = suffix.strip()
276                 # 简单语句,set / raw
277                 if operator in TOKEN_KEY_SET_SIMPLE_EXPRESSION:
278                     self.add_simple_block_statement(operator, suffix)
279                 elif operator in TOKEN_KEY_LOOP_CTRL:
280                     if not in_loop:
281                         raise ParseException("{0} must in loop block".format(operator))
282                     self.code_data.append_statement(operator)
283                 else:
284                     # 控制语句 检查匹配if 后面可以跟elif/else
285                     pre_condition = TOKEN_KEY_PRE_CONDITION.get(operator, None)
286                     if pre_condition:
287                         # 里面就是elif/else/end
288                         if control_operator not in pre_condition:
289                             raise ParseException("{0} must behind with {1}".format(operator, pre_condition))
290                         elif operator == TOKEN_KEY_END:
291                             # 遇到{% end %}则结束
292                             self.code_data.close_code()
293                             return
294                         else:
295                             # 由于是依据if 进入 来计算elif ,因此elif与if是同级的
296                             self.code_data.close_code()
297                             self.code_data.append_statement(content + TOKEN_COLON)
298                             self.code_data.create_code()
299                             self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
300                             break
301                     # 添加控制语句及内部语句体 if for while
302                     self.code_data.append_statement(content + TOKEN_COLON)
303                     self.code_data.create_code()
304                     self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
305             else:
306                 raise ParseException("Unkown brace")
307         return
308 
309     def __compiled_code(self):
310         """生成 编译code """
311         self.code_data.build_code(self.filename)
312 if __name__ == "__main__":
313         t = Template("<html>{{hello}}</html>")
314         t.render(hello="你好"

原文链接:
http://www.cnblogs.com/jeffxun/p/15585073.html