YATL Template Language
py4web uses Python for its models, controllers, and views, although it uses a slightly modified Python syntax in the views to allow more readable code without imposing any restrictions on proper Python usage.
py4web uses [[ ... ]]
to escape Python code embedded in HTML. The
advantage of using square brackets instead of angle brackets is that
it’s transparent to all common HTML editors. This allows the developer
to use those editors to create py4web views.
Since the developer is embedding Python code into HTML, the document
should be indented according to HTML rules, and not Python rules.
Therefore, we allow unindented Python inside the [[ ... ]]
tags.
Since Python normally uses indentation to delimit blocks of code, we
need a different way to delimit them; this is why the py4web template
language makes use of the Python keyword pass
.
A code block starts with a line ending with a colon and ends with a line beginning with
pass
. The keywordpass
is not necessary when the end of the block is obvious from the context.
Here is an example:
[[
if i == 0:
response.write('i is 0')
else:
response.write('i is not 0')
pass
]]
Note that pass
is a Python keyword, not a py4web keyword. Some
Python editors, such as Emacs, use the keyword pass
to signify the
division of blocks and use it to re-indent code automatically.
The py4web template language does exactly the same. When it finds something like:
<html><body>
[[for x in range(10):]][[=x]]hello<br />[[pass]]
</body></html>
it translates it into a program:
response.write("""<html><body>""", escape=False)
for x in range(10):
response.write(x)
response.write("""hello<br />""", escape=False)
response.write("""</body></html>""", escape=False)
response.write
writes to the response.body
.
When there is an error in a py4web view, the error report shows the generated view code, not the actual view as written by the developer. This helps the developer debug the code by highlighting the actual code that is executed (which is something that can be debugged with an HTML editor or the DOM inspector of the browser).
Also note that:
[[=x]]
generates
response.write(x)
Variables injected into the HTML in this way are escaped by default. The
escaping is ignored if x
is an XML
object, even if escape is set
to True
.
Here is an example that introduces the H1
helper:
[[=H1(i)]]
which is translated to:
response.write(H1(i))
upon evaluation, the H1
object and its components are recursively
serialized, escaped and written to the response body. The tags generated
by H1
and inner HTML are not escaped. This mechanism guarantees that
all text — and only text — displayed on the web page is always escaped,
thus preventing XSS vulnerabilities. At the same time, the code is
simple and easy to debug.
The method response.write(obj, escape=True)
takes two arguments, the
object to be written and whether it has to be escaped (set to True
by default). If obj
has an .xml()
method, it is called and the
result written to the response body (the escape
argument is
ignored). Otherwise it uses the object’s __str__
method to serialize
it and, if the escape argument is True
, escapes it. All built-in
helper objects (H1
in the example) are objects that know how to
serialize themselves via the .xml()
method.
This is all done transparently. You never need to (and never should)
call the response.write
method explicitly.
Basic syntax
The py4web template language supports all Python control structures. Here we provide some examples of each of them. They can be nested according to usual programming practice.
for...in
In templates you can loop over any iterable object:
[[items = ['a', 'b', 'c']]]
<ul>
[[for item in items:]]<li>[[=item]]</li>[[pass]]
</ul>
which produces:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
Here items
is any iterable object such as a Python list, Python
tuple, or Rows object, or any object that is implemented as an iterator.
The elements displayed are first serialized and escaped.
while
You can create a loop using the while keyword:
[[k = 3]]
<ul>
[[while k > 0:]]<li>[[=k]][[k = k - 1]]</li>[[pass]]
</ul>
which produces:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>
if...elif...else
You can use conditional clauses:
[[
import random
k = random.randint(0, 100)
]]
<h2>
[[=k]]
[[if k % 2:]]is odd[[else:]]is even[[pass]]
</h2>
which produces:
<h2>
45 is odd
</h2>
Since it is obvious that else
closes the first if
block, there
is no need for a pass
statement, and using one would be incorrect.
However, you must explicitly close the else
block with a pass
.
Recall that in Python “else if” is written elif
as in the following
example:
[[
import random
k = random.randint(0, 100)
]]
<h2>
[[=k]]
[[if k % 4 == 0:]]is divisible by 4
[[elif k % 2 == 0:]]is even
[[else:]]is odd
[[pass]]
</h2>
It produces:
<h2>
64 is divisible by 4
</h2>
try...except...else...finally
It is also possible to use try...except
statements in views with one
caveat. Consider the following example:
[[try:]]
Hello [[= 1 / 0]]
[[except:]]
division by zero
[[else:]]
no division by zero
[[finally:]]
<br />
[[pass]]
It will produce the following output:
Hello division by zero
<br />
This example illustrates that all output generated before an exception occurs is rendered (including output that preceded the exception) inside the try block. “Hello” is written because it precedes the exception.
def...return
The py4web template language allows the developer to define and implement functions that can return any Python object or a text/html string. Here we consider two examples:
[[def itemize1(link): return LI(A(link, _href="http://" + link))]]
<ul>
[[=itemize1('www.google.com')]]
</ul>
produces the following output:
<ul>
<li><a href="http:/www.google.com">www.google.com</a></li>
</ul>
The function itemize1
returns a helper object that is inserted at
the location where the function is called.
Consider now the following code:
[[def itemize2(link):]]
<li><a href="http://[[=link]]">[[=link]]</a></li>
[[return]]
<ul>
[[itemize2('www.google.com')]]
</ul>
It produces exactly the same output as above. In this case, the function
itemize2
represents a piece of HTML that is going to replace the
py4web tag where the function is called. Notice that there is no ‘=’ in
front of the call to itemize2
, since the function does not return
the text, but it writes it directly into the response.
There is one caveat: functions defined inside a view must terminate with
a return
statement, or the automatic indentation will fail.