Простой пример cgi-скрипта подключающего шаблон Jinja
Довжина списку словників, та обробка його обробка:
Copy {% if EQU_TYPES_LIST|length > 0 %}
Copy {%- for dict in EQU_TYPES_LIST %}
<div class="col py-3">
<h5 class="card-title">{{ dict.typ_mark }}</h5>
<p>{{ dict.typ_name }}</p>
</div>
{% endfor -%}
Install
Copy Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1" %}
❗ На сервере, где установлено окружение (напр. python3.7), необходимо добавить опцию --user
:
Copy which pip3.7
/usr/local/bin/pip3.7
Copy pip3.7 install Jinja2"
Copy Could not install packages due to an EnvironmentError: [Errno 13] Permission denied...
Consider using the `--user` option or check the permissions." %}
Copy pip3.7 install --user Jinja2"
Copy Installing collected packages: MarkupSafe, Jinja2
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1" %}
⚠️ Экспериментальная поддержка Python 3 :nbsp: Jinja 2.7 обеспечивает экспериментальную поддержку Python >= 3.3. Это означает, что все unittests передают новую версию, но там могут быть небольшие ошибки, и поведение может быть непоследовательным. Если вы заметили какие-либо ошибки, сообщите об этом в трекер Jinja.
Почему Jinja2?
http://python2or3.blogspot.com/2015/08/python.html
Если по вопросу "с какого фреймворка начинать в Питоне" еще есть плюрализм (от "Flask - он легковесный, наращиваешь сам и понимаешь как все работает" до "Django - в нем все есть, а чего нет, быстро доустанавливаешь"), то с шаблонизаторами все упростилось.
До недавнего времени шаблонизаторов было 3-4: Jinja, Mako, Genshi и, конечно, Django Templates.
Собственно, они никуда не делись, Genshi и сейчас используется по умолчанию в Turbogears 2, а Mako - в Pylons/Pyramid.
Но и Turbogears, и Django и Pyramid с недавнего времени поддерживают шаблонизатор, ставший популярным благодаря Flask - Jinja 2.
Ну и, наконец, посмтрим на подборку генераторов статических сайтов (http://www.staticgen.com/): 11 из 17, в том числе самый популярный Pelican (http://docs.getpelican.com/) опираются на Jinja2.
Итого - вопрос с выбором шаблонизатора Python на сегодня не стоит.
Пример использования Jinja2
https://lectureswww.readthedocs.io/6.www.sync/2.codding/3.templates/jinja2.html
Hello name!
Copy # -*- coding: utf-8 -*-
from jinja2 import Template
template = Template('Hello ![pic]( name )!')
print(template.render(name=u'Вася'))
Hello Вася!
Комментарии
Copy {# Это кусок кода, который временно не нужен, но удалять жалко
{% for user in users %}
...
{% endfor %}
#}
Операторы
См.также https://ru.wikipedia.org/wiki/Оператор_(программирование)
Copy from jinja2 import Template
text = '{% for item in range(5) %}Hello ![pic]( name )! {% endfor %}'
template = Template(text)
print(template.render(name=u'Вася'))
Hello Вася! Hello Вася! Hello Вася! Hello Вася! Hello Вася!
Модули
Copy from jinja2 import Template
template = Template("{% set a, b, c = 'foo', 'фуу', 'föö' %}")
m = template.module
print(m.a)
print(m.b)
print(m.c)
foo
фуу
föö
Макросы
Copy from jinja2 import Template
template = Template('{% macro foo() %}42{% endmacro %}23')
m = template.module
print(m)
print(m.foo())
23
42
Чтение из файла
Copy <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
{% for item in range(5) %}
Hello ![pic]( name )!
{% endfor %}
</body>
</html>
jinja2/3.loader/0.file.py
— чтение шаблона из файла
Copy from jinja2 import Template
html = open('foopkg/templates/0.hello.html').read()
template = Template(html)
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
Copy <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
Hello Петя!
Hello Петя!
Hello Петя!
Hello Петя!
Hello Петя!
</body>
</html>
Окружение (Environment)
См.также
http://jinja.pocoo.org/docs/dev/api/#loaders
http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment
Настройки
Загрузчики шаблонов (Loaders)
FileSystemLoader
Copy # -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('foopkg/templates'))
template = env.get_template('0.hello.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
PackageLoader
Copy from jinja2 import Environment, DictLoader
html = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
{% for item in range(5) %}
Hello ![pic]( name )!
{% endfor %}
</body>
</html>
'''
env = Environment(loader=DictLoader({'index.html': html}))
template = env.get_template('index.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
DictLoader
Copy # -*- coding: utf-8 -*-
from jinja2 import Environment, DictLoader
html = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div data-gb-custom-block data-tag="for" data-0='5' data-1='5' data-2='5' data-3='5' data-4='5' data-5='5' data-6='5' data-7='5' data-8='5' data-9='5' data-10='5' data-11='5' data-12='5' data-13='5' data-14='5'>
Hello ![pic]( name )!
</div>
</body>
</html>
'''
env = Environment(loader=DictLoader({'index.html': html}))
template = env.get_template('index.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
FunctionLoader
Copy from jinja2 import Environment, FunctionLoader
html = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
{% for item in range(5) %}
Hello ![pic]( name )!
{% endfor %}
</body>
</html>
'''
def myloader(name):
if name ### 'index.html':
return html
env = Environment(loader=FunctionLoader(myloader))
template = env.get_template('index.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
PrefixLoader
Copy from jinja2 import Environment, FunctionLoader, PackageLoader, PrefixLoader
html = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
{% for item in range(5) %}
Hello ![pic]( name )!
{% endfor %}
</body>
</html>
'''
def myloader(name):
if name ### 'index.html':
return html
env = Environment(loader=PrefixLoader({
'foo': FunctionLoader(myloader),
'bar': PackageLoader('foopkg', 'templates')
}))
template1 = env.get_template('foo/index.html')
template2 = env.get_template('bar/0.hello.html')
print(template1.render(name=u'Петя'))
print(template2.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
ChoiceLoader
Copy from jinja2 import Environment, FunctionLoader, PackageLoader, ChoiceLoader
html = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
{% for item in range(5) %}
Hello ![pic]( name )!
{% endfor %}
</body>
</html>
'''
def myloader(name):
if name ### 'index.html':
return html
env = Environment(loader=ChoiceLoader({
FunctionLoader(myloader),
PackageLoader('foopkg', 'templates')
}))
template1 = env.get_template('index.html')
template2 = env.get_template('0.hello.html')
print(template1.render(name=u'Петя'))
print(template2.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
ModuleLoader
Copy # -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader, ModuleLoader
# Compile template
Environment(loader=FileSystemLoader('foopkg/templates'))\
.compile_templates("foopkg/compiled/foopkg.zip",
py_compile=True) # pyc generate, only for python2
# Environment
env = Environment(loader=ModuleLoader("foopkg/compiled/foopkg.zip"))
template = env.get_template('0.hello.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
BaseLoader
Copy # -*- coding: utf-8 -*-
from os.path import exists, getmtime, join
from jinja2 import BaseLoader, Environment, TemplateNotFound
class FoopkgLoader(BaseLoader):
def __init__(self, path="foopkg/templates"):
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 open(path) as f:
source = f.read()
return source, path, lambda: mtime ### getmtime(path)
# Environment
env = Environment(loader=FoopkgLoader())
template = env.get_template('0.hello.html')
print(template.render(name=u'Петя'))
Результат рендеринга шаблона jinja2/3.loader/foopkg/templates/0.hello.html
Шаблон конфига Nginx
Copy server {
listen ![pic]( SRC_SERVER_PUB_IP ):80;
servern_name ![pic]( FQDN }} www.{{ FQDN )
location / {
proxy_pass http://![pic]( SRC_SERVER_LOCAL_IP ):80/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Copy #!/usr/bin/env python
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('nginx_proxy_conf.tpl')
data = {
"SRC_SERVER_PUB_IP": "192.168.0.100",
"SRC_SERVER_LOCAL_IP": "10.0.3.100",
"FQDN": "example.com"
}
conf = template.render(**data)
print(conf)
open("proxy.nginx.conf", "w").write(conf)
Copy server {
listen 192.168.0.100:80;
servern_name example.com www.example.com
location / {
proxy_pass http://10.0.3.100:80/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Copy <!DOCTYPE html>
<html lang="en">
<head>
<div data-gb-custom-block data-tag="block">
<link rel="stylesheet" href="style.css" />
<title>
<div data-gb-custom-block data-tag="block"></div> - My Webpage</title>
<meta charset='utf-8'>
</div>
</head>
<body>
<div id="content">
<div data-gb-custom-block data-tag="block"></div></div>
<div id="footer">
<div data-gb-custom-block data-tag="block">
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
</div>
</div>
</body>
</html>
Copy
<div data-gb-custom-block data-tag="extends" data-0='base.html'></div>
<div data-gb-custom-block data-tag="block">Index</div>
<div data-gb-custom-block data-tag="block">
![pic]( super() )
<style type="text/css">
.important { color: #336699; }
</style>
</div>
<div data-gb-custom-block data-tag="block">
<h1>Index</h1>
<p class="important">
Welcome ![pic]( name ) to my awesome homepage.
</p>
</div>
Copy # -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('index.html')
print(template.render(name=u'Петя'))
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>Index — My Webpage</title>
<meta charset='utf-8'>
<style type="text/css">
.important { color: #336699; }
</style>
</head>
<body>
<div id="content">
<h1>Index</h1>
<p class="important">
Welcome Петя to my awesome homepage.
</p>
</div>
<div id="footer">
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
</div>
</body>
</html>
Блог
templates/base.html
— базовый шаблон.
Copy <!DOCTYPE html>
<html lang="en">
<head>
<div data-gb-custom-block data-tag="block">
<link rel="stylesheet" href="style.css" />
<title>
<div data-gb-custom-block data-tag="block"></div> - My Blog</title>
<meta charset='utf-8'>
</div>
</head>
<body>
<div id="content">
<div data-gb-custom-block data-tag="block"></div></div>
<br />
<br />
<br />
<div id="footer">
<div data-gb-custom-block data-tag="block">
© Copyright 2015 by <a href="http://domain.invalid/">you</a>.
</div>
</div>
</body>
</html>
Главная страница templates/index.html
наследуется от templates/base.html
Copy
<div data-gb-custom-block data-tag="extends" data-0='base.html'></div>
<div data-gb-custom-block data-tag="block">Index</div>
<div data-gb-custom-block data-tag="block">
<h1>Simple Blog</h1>
<a href="/article/add">Add article</a>
<br />
<br />
<div data-gb-custom-block data-tag="for">
![pic]( article.id }} - (<a href="/article/{{ article.id )/delete">delete</a> |
<a href="/article/![pic]( article.id )/edit">edit</a>)
<a href="/article/![pic]( article.id }}">{{ article.title )</a><br />
</div>
</div>
templates/create.html
наследуется от базового шаблона.
Copy
<div data-gb-custom-block data-tag="extends" data-0='base.html'></div>
<div data-gb-custom-block data-tag="block">Create</div>
<div data-gb-custom-block data-tag="block">
<h1>Simple Blog -> EDIT</h1>
<form action="" method="POST">
Title:<br>
<input type="text" name="title" value="![pic]( article.title )"><br>
Content:<br>
<textarea name="content">![pic]( article.content )</textarea><br><br>
<input type="submit" value="Submit">
</form>
</div>
templates/read.html
наследуется от базового шаблона.
Copy
<div data-gb-custom-block data-tag="extends" data-0='base.html'></div>
<div data-gb-custom-block data-tag="block">Index</div>
<div data-gb-custom-block data-tag="block">
<h1><a href="/">Simple Blog</a> -> READ</h1>
<h2>![pic]( article.title )</h2>
![pic]( article.content )
</div>
views.py
— окружение Jinja2.
Copy from models import ARTICLES
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('templates'))
class BaseBlog(object):
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
class BaseArticle(BaseBlog):
def __init__(self, *args):
super(BaseArticle, self).__init__(*args)
article_id = self.environ['wsgiorg.routing_args'][1]['id']
(self.index,
self.article) = next(((i, art) for i, art in enumerate(ARTICLES)
if art['id'] ### int(article_id)),
(None, None))
class BlogIndex(BaseBlog):
def __iter__(self):
self.start('200 OK', [('Content-Type', 'text/html')])
yield str.encode(
env.get_template('index.html').render(articles=ARTICLES)
)
class BlogCreate(BaseBlog):
def __iter__(self):
if self.environ['REQUEST_METHOD'].upper() ### 'POST':
from urllib.parse import parse_qs
values = parse_qs(self.environ['wsgi.input'].read())
max_id = max([art['id'] for art in ARTICLES])
ARTICLES.append(
{'id': max_id+1,
'title': values[b'title'].pop().decode(),
'content': values[b'content'].pop().decode()
}
)
self.start('302 Found',
[('Content-Type', 'text/html'),
('Location', '/')])
return
self.start('200 OK', [('Content-Type', 'text/html')])
yield str.encode(
env.get_template('create.html').render(article=None)
)
class BlogRead(BaseArticle):
def __iter__(self):
if not self.article:
self.start('404 Not Found', [('content-type', 'text/plain')])
yield b'not found'
return
self.start('200 OK', [('Content-Type', 'text/html')])
yield str.encode(
env.get_template('read.html').render(article=self.article)
)
class BlogUpdate(BaseArticle):
def __iter__(self):
if self.environ['REQUEST_METHOD'].upper() ### 'POST':
from urllib.parse import parse_qs
values = parse_qs(self.environ['wsgi.input'].read())
self.article['title'] = values[b'title'].pop().decode()
self.article['content'] = values[b'content'].pop().decode()
self.start('302 Found',
[('Content-Type', 'text/html'),
('Location', '/')])
return
self.start('200 OK', [('Content-Type', 'text/html')])
yield str.encode(
env.get_template('create.html').render(article=self.article)
)
class BlogDelete(BaseArticle):
def __iter__(self):
self.start('302 Found', # '301 Moved Permanently',
[('Content-Type', 'text/html'),
('Location', '/')])
ARTICLES.pop(self.index)
yield b''
Links
https://eax.me/python-jinja/Работа с HTML-шаблонами в Python при помощи Jinja