Z ciekawości spojrzałem, co się pisze o szablonach Mako w internetach po polsku, i... no właśnie. Nic. Raptem kilka wpisów wspominających, że jest, a jak jest, to do dupy. Hmm.. Szczerze mówiąc, po ponad roku pracy z tymi szablonami przy różnych projektach, jestem dokładnie odwrotnego zdania. Jeśli szukacie pythonowych systemu szablonów elastycznego jak guma w gaciach, który nie będzie was ograniczał, a jednocześnie będzie szybki, to Mako jest właśnie tym, czego szukacie.
WIELKI CZERWONY NAPIS! Ten wpis nie zastąpi lektury dokumentacji. Jest to zbiór tanich chwytów do wykorzystania w kodzie
Tips & tricks
-
Wprawdzie Mako jest inkorporowane w Pylonsach, ale nic nie przeszkadza użyć tego gdzie indziej, tym bardziej, że integracja z dowolną aplikacją pythonową jest prosta (nie licząc GAE). W naszym przykładzie zakładamy, że używamy dziedziczenia i inkludowania zawartości, więc Mako wymaga zainicjowania obiektu
TemplateLookup, który będzie wewnętrznie rozwiązywał zależności między szablonami.
W momencie, kiedy chcemy przywołać szablon i wyrenderować go, należy wykonać paręTemplateLookup.get_template(uri)orazTemplate.render().Przykład:
szablon.mako:
## -*- coding: utf-8 -*-
<%inherit file="/base.mako"/>
<%def name="content()">
${ x }
</%def>
${ content()}
template.py:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from mako.template import Template
from mako.lookup import TemplateLookup
lookup = TemplateLookup(directories=[ '.'], output_encoding = 'utf-8', default_filters=['decode.utf-8'])
t = lookup.get_template('szablon.mako')
print t.render(x='x')
bądź
t.render_unicode(), aby uzyskać obiekt unikodowy. -
Przydatne parametry TemplateLookup:
- directories=None
- lista katalogów, w których mają być szukane pliki szablonów.
- module_directory=None
- katalog, w którym przechowywane są skompilowane szablony
- filesystem_checks=True
- sprawdzanie, czy skompilowany szablon jest aktualny. można wyłączyć dla zaoszczędzenia na IO ustawiają na False
- collection_size=-1
- wielkość kolekcji skompilowanych szablonów, które trzymane są w pamięci.
- modulename_callable=None
- Callable, które zwraca nazwę modułu szablonu.
pozostałe parametry trafiają do
Template:- format_exceptions=False
- włącza/wyłącza formatowanie wyjątków
- error_handler=None
- callable przyjmujące
contexti wyjątek, wywoływane w przypadku wyjątku - disable_unicode=False
- Flaga wyłączająca wewnętrzne używanie unikodu. Traktuje wszystkie podstawienia (
${}) jako czyste stringi. Odradzana w użyciu ze względu na implikacje (WSZYSTKIE podstawienia muszą być typu<str>, szablon nie będzie działał z Py3k), choć daje pewne zyski w wydajności - output_encoding=None
- Kodowanie wykonanej zawartości szablonu. Wewnętrznie utworzony unikod zostanie zakodowany do tego kodowania
- encoding_errors='strict'
- Sposób traktowania błędów kodowania - więcej o tym można przeczytać w dokumentacji Pythona.
- cache_type=None
- Typ kontenera keszu - patrz:
Beaker - cache_dir=None
- Katalog z keszem, jeśli cache_type został określony jako
file - cache_url=None,
- URL dla keszowania z użyciem memcache
- default_filters=None
- Lista filtrów, które będą aplikowane do każdego podstawienia
- buffer_filters=[]
- ???
- imports=None
- Lista stringów z importami modułów, które zostaną dodane do skompilowanego szablonu
- input_encoding=None
- domyślne kodowanie wejściowe szablonu - w tym kodowaniu oczekiwane są stringi (mniej więcej równoważne nagłówkowi
coding, ale to ostatnie, jeśli jest zdefiniowane w szablonie, nadpisuje input_encoding) - preprocessor=None
- Funkcja, która przetwarza tekst szablonu przed jego kompilacją.
- Wywołując
TemplateLookup.get_templatepodajemyuri, czyli ścieżkę do szablonu liczoną od katalogów zdefiniowanych wdirectories. Ma to pewne implikacje, które opisałem trochę dalej. - Metoda
renderprzyjmuje argumenty nazwane, które są przekazywane do szablonu jako zmienne globalne. Czyli wywołanietemplate.render(test='dupa')spowoduje w renderowanym szablonie
${ test }
wyrenderowanie
dupa
Oczywiście ilość parametrów przekazanych w ten sposób jest dowolna.
- Jeśli chcesz wykorzystać te same szablony w różnych procesach, np. klastrując aplikację, pamiętaj, żeby dla każdego procesu ustawić oddzielny katalog
module_dirw instancjiTemplateLookup - Można podać listę katalogów z szablonami w parametrze
directories. Jeśli występuje w nich kilka szablonów o takich samych ścieżkach URI, to pierwszeństwo będzie miał ten, z pierwszej ścieżki podanej wdirectories - Jeśli ustawisz parametr
filesystem_checksnaTruei chcesz używać tego w środowisku produkcyjnym, to polecam utworzyć ramdysk do trzymania skompilowanych szablonów (parametrmodule_directory) - Parametr
collection_sizewarto ustawić na trochę większą wartość, aby skompilowane szablony były pobierane z pamięci. Default_filtersto lista domyślnych filtrów (podawana jako stringi, które odpowiadają obiektom callable), które będą aplikowane do każdego podstawienia w kolejności od lewej do prawej.Jeśli nie ustawiałeś flagi
disable_unicode=False(99% przypadków), i używasz jednego kodowania, to domyślnym filtrem, jeśli tego nie zmienisz, będzie['unicode']. Możesz jednak zaaplikować filtr'decode.<nazwa_kodowania_którego_używasz>', np.'decode.utf-8', który będzie konwertował nieunikodową zawartość do unikodu, zakładając, że jest to string w kodowaniu, które podałeś.
Decode, to sprytna klasa, dekoduje do unikodu z zadanego kodowania. Jeśli chesz użyć własnych filtrów, to należy je zaimportować w liście podanej przezimports, np:
TemplateLookup(imports = ['from mojmodul import funkcja_filtra'], default_filters =['funkcja_filtra'])
- Jeśli używasz dziedziczenia lub inkludowania, a szablony są rozsiane w podkatalogach, używaj bezwzględnych ścieżek (względem zerowego poziomu katalogu) w dyrektywach
inheritiinclude, inaczej plik, do którego chcesz się odwołać, będzie szukany w podkatalogu szablonu, który renderujesz. -
Mako posiada jedną ciekawą właściwość: pozwala na dynamiczne określanie parametrów dyrektywy
includeorazpagepoprzez podstawienie, np:
<%include file="${ c['zmienna'] }"/>
-
Stringi generowane w blokach kodu <% %> są filtrowane - znaki diakrytyczne są zamieniane do formy \uXXX. Wyprintowanie takiego znaku brzydko wygląda w html'u, dlatego lepiej deklarować stringi jako obiekty unikodowe:
<%
napis = 'Zażółć gęślą jaźń' # zostanie skompilowany do 'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144' i tak wyświetlony
%>
kontra
<%
napis = u'Zażółć gęślą jaźń' # zostanie skompilowany do 'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144', ale wyświetlony z poprawnymi znakami
%>
Sytuacja ta dotyczy tylko stringów utworzonych w szablonie. Jeśli przekazujesz string np. z kontrolera, to będzie on nienaruszony.
- Jeśli chcesz porównywać stringi z diakrytykami, przekazany z kontrolera i zdeklarowany w szablonie, to, że kodowanie stringu to utf-8, możesz użyć konstrukcji
u'Zaźółć gęślą jaźń'.encode('utf-8') == napis_z_kontrolera -
Jeśli importujesz funkcje za pomocą <%namespace/>, które nie są definiowane w innym szablonie Mako, to używając takiej funkcji w podstawieniu, musisz pamiętać, że pierwszym argumentem zawsze będzie obiekt
context. - Nie mogłem się powstrzymać przed tym punktem: wsparcie społeczności (a przede wszystkim deweloperów) Mako jest rewelacyjne. Jeśli masz jakiś problem z Mako, uderzaj na listę dyskusyjną, zadaj dobre pytanie, a na pewno dostaniesz odpowiedź.

