На сервере, где установлено окружение (напр. python3.7), необходимо добавить опцию --user:
which pip3.7
/usr/local/bin/pip3.7
pip3.7 install Jinja2"
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied...
Consider using the `--user` option or check the permissions." %}
Если по вопросу "с какого фреймворка начинать в Питоне" еще есть плюрализм (от "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 на сегодня не стоит.
from jinja2 import Template
text = '{% for item in range(5) %}Hello ! {% endfor %}'
template = Template(text)
print(template.render(name=u'Вася'))
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
Экспериментальная поддержка Python 3 :nbsp: Jinja 2.7 обеспечивает экспериментальную поддержку Python >= 3.3. Это означает, что все unittests передают новую версию, но там могут быть небольшие ошибки, и поведение может быть непоследовательным. Если вы заметили какие-либо ошибки, сообщите об этом в трекер Jinja.