Django своими руками часть 1: Собираем шаблоны для jinja2

в 15:48, , рубрики: framework, jinja2, python, метки: , ,

Введение

В этом посте хотелось бы описать создание небольшого фреймворка с системой плагинов как django. Только с использованием внешних компонентов. Jinja2 для шаблонов, bottle для получения переменых среды, вместо ORM будет использоваться pymongo, а сессиями будет заниматься beaker.
В первой части хочу рассказать как удобно подсоединить Jinja2 чтоб шаблоны можно было собирать из разных папок (читай плагинов) и кешировать их.
Также в следующей части хотелось бы рассказать как подключить к шаблонам gettext и автоматизировать их перевод.

Структура фреймворка

Предполагается что наш фреймворк как библиотека может лежать в любом каталоге скорее всего для ubuntu '/usr/local/lib/python2.6/dist-packages/', а проект где ни будь, ну скажем, например, '/home/user/worckspace/'. В проекте имеется index.wsgi для mod_wsgi или для uwsgi index_wsgi.py в котором указан путь к нашей библиотеке, если она вручную куда то копировалась.
Проект имеет примерно такую структуру:

/project/
	/__init__.py
	/app/
		/__init__.py
		/app1/
			/__init__.py
			/templ/
			/static/
			/routes.py
			/models.py
			/views.py
	/teml/
	/static/
	/index.py
	/routes.py
	/views.py
	/models.py
	/settings.py

Соответственно в подкаталогах /templ будут лежать шаблоны, в /static статика, в /app любое количество приложений (или компонентов, кому как больше нравится).
Соответствено предполагается что в нашей библиотеке также есть папка app аналог джанговского contrib в которой тоже будут лежать компоненты со своими шаблонами.
Также в проекте по молчанию еще будет создаваться папка например caсhe в нее jinja2 будет сохранять кеш шаблонов.

Подключение шаблонов

Итак все наше подключение будет лежать в файлике core.py в пакете core который в свою очередь лежит в корне библиотеки. Импортируем необходимые классы.

from jinja2 import Environment, PackageLoader, FunctionLoader, FileSystemBytecodeCache

Далее определяем путь для шаблонов, эту функцию мы буем вызывать при загрузке шаблонов.
Шаблоны на выбор могут иметь несколько расширений.

templ_exts = ['', '.tpl', '.html', '.htm'] 
def split_templ_name (t):
	""" определение пути для загрузки шаблонов. Двоеточие выступает символом разделителя для указания в каком модуле будет лежать шаблон. Чтоб не было путаницы с одинаковыми именами. """
	if ':' in t:
		# тут составляем путь до шаблона конкретного модуля
		i = t.index(':')
		module = t[:i]
		module_path = '/app/' + module +'/templ/'
		# получаем подстроку после двоеточия
		file = t[(i+1):]
	else:
		# путь к шаблонам которые лежат в папке проекта.
		module = ' '
		module_path = '/templ/'
		file = t
	return (module, module_path, file)

Собственно сама загрузка шаблонов.
Здесь можно реализовать альтернативные места хранения шаблонов, например реализовав загрузку из базы

def union_templ(t, **p):

	(module, module_path, file) = split_templ_name (t)
	def load_template (base, module_path, file):

		path = base + module_path + file
		template = ' '
		for ext in templ_exts:
			filename = path+ext
			if os.path.exists(filename):
				with open(filename, "rb") as f:
					template = f.read()
				break;
		return template
	template = load_template (os.getcwd(), module_path, file);
	if not template:
		template = load_template( settings.lib_path, module_path, file);
	if not template:
		return 'Template not found %s' % t
	return template.decode('UTF-8')

Автоматическое создание папки для кеширования шаблонов.

jcp = os.path.join(os.getcwd(), 'jinja_cache')
if not os.path.isdir(jcp): os.mkdir(jcp)

Создание объекта, управляющего кешем.

bcc = FileSystemBytecodeCache(jcp, '%s.cache')

Вешаем хук на jinja2, где указываем на необходимость кеширования и передаем функцию, которую нужно вызывать для загрузки шаблонов.

jinja = Environment(auto_reload=True, loader=FunctionLoader(union_templ), bytecode_cache  = bcc ) 

Функция которая непосредственно выполняет отрисовку шаблона. Сейчас она ничего особенного не делает, напрямую передавая управление jinja2, но мы сюда еще вернемся.

def render_templ(t, **p):
	template = jinja.get_template(t)
	return template.render(**p)

Импорт этой функции на глобальный уровень.

__builtin__.templ = render_templ

Теперь благодаря этой последней строчке будет достаточно в любом файле вызвать функцию templ() и передать ей в аргументах название шаблона и что в нем вывести. Например:
return templ('base:base', param = param) или return templ('base', param = param), ':' значит что шаблон лежит не в проекте, а в соответствующем компоненте в данном случае 'base'.

Резюме

Итак для начала у нас есть небольшой фреймворк с модульной структурой. На базе которого мы будем двигаться дальше.
Это пока первая статья и многое, наверно, можно было бы написать лучше, поэтому буду рад всем замечаниям и вопросам.
На этом пока все, продолжение следует.

Автор: Alex10


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js