[ login ]

Chapter Table of Contents

The Views

views
template language

web2py 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.

The purpose of a view is to embed code (Python) in an HTML document. In general, this poses some problems:

  • How should embedded code be escaped?
  • Should indenting be based on Python or HTML rules?

web2py uses {{ ... }} to escape Python code embedded in HTML. The advantage of using curly 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 web2py 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 web2py 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 keyword pass is not necessary when the end of the block is obvious from the context.
Here is an example:
1.
2.
3.
4.
5.
6.
7.
{{
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 web2py 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 web2py template language does exactly the same. When it finds something like:

1.
2.
3.
<html><body>
{{for x in range(10):}}{{=x}}hello<br />{{pass}}
</body></html>

it translates it into a program:

1.
2.
3.
4.
5.
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 web2py 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:

1.
{{=x}}

generates

response.write
escape
1.
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:

1.
{{=H1(i)}}

which is translated to:

1.
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 web2py 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

for

In templates you can loop over any iterable object:

1.
2.
3.
4.
{{items = ['a', 'b', 'c']}}
<ul>
{{for item in items:}}<li>{{=item}}</li>{{pass}}
</ul>

which produces:

1.
2.
3.
4.
5.
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>

Here item 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

while

You can create a loop using the while keyword:

1.
2.
3.
4.
{{k = 3}}
<ul>
{{while k > 0:}}<li>{{=k}}{{k = k - 1}}</li>{{pass}}
</ul>

which produces:

1.
2.
3.
4.
5.
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>

if...elif...else

if
elif
else

You can use conditional clauses:

1.
2.
3.
4.
5.
6.
7.
8.
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 2:}}is odd{{else:}}is even{{pass}}
</h2>

which produces:

1.
2.
3.
<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:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
{{
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:

1.
2.
3.
<h2>
64 is divisible by 4
</h2>

try...except...else...finally

try
except
else
finally

It is also possible to use try...except statements in views with one caveat. Consider the following example:

1.
2.
3.
4.
5.
6.
7.
8.
9.
{{try:}}
Hello {{= 1 / 0}}
{{except:}}
division by zero
{{else:}}
no division by zero
{{finally}}
<br />
{{pass}}

It will produce the following output:

1.
2.
3.
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

def
return

The web2py 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:

1.
2.
3.
4.
{{def itemize1(link): return LI(A(link, _href="http://" + link))}}
<ul>
{{=itemize1('www.google.com')}}
</ul>

produces the following output:

1.
2.
3.
<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:

1.
2.
3.
4.
5.
6.
{{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 web2py 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.

HTML Helpers

helpers

Consider the following code in a view:

1.
{{=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')}}

it is rendered as:

1.
<div id="123" class="myclass">thisisatest</div>
DIV is a helper class, i.e., something that can be used to build HTML programmatically. It corresponds to the HTML <div> tag.

Positional arguments are interpreted as objects contained between the open and close tags. Named arguments that start with an underscore are interpreted as HTML tag attributes (without the underscore). Some helpers also have named arguments that do not start with underscore; these arguments are tag-specific.

The following set of helpers:

A, B, BEAUTIFY, BODY, BR, CENTER, CODE, DIV, EM, EMBED, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, IMG, INPUT, LABEL, LEGEND, LI, LINK, OL, UL, MARKMIN, MENU, META, OBJECT, ON, OPTION, P, PRE, SCRIPT, OPTGROUP, SELECT, SPAN, STYLE, TABLE, TAG, TD, TEXTAREA, TH, THEAD, TBODY, TFOOT, TITLE, TR, TT, URL, XHTML, XML, xmlescape, embed64

can be used to build complex expressions that can then be serialized to XML49 50. For example:

1.
{{=DIV(B(I("hello ", "<world>"))), _class="myclass")}}

is rendered:

1.
<div class="myclass"><b><i>hello <world></i></b></div>

Document Object Model (DOM)
The helpers mechanism in web2py is more than a system to generate HTML without concatenating strings. It provides a server-side representation of the Document Object Model (DOM).

Components' objects can be referenced via their position, and helpers act as lists with respect to their components:

1.
2.
3.
4.
5.
6.
7.
8.
>>> a = DIV(SPAN('a', 'b'), 'c')
>>>
print a
<div><span>ab</span>c</div>
>>>
del a[1]
>>>
a.append(B('x'))
>>>
a[0][0] = 'y'
>>> print a
<div><span>yb</span><b>x</b></div>

Attributes of helpers can be referenced by name, and helpers act as dictionaries with respect to their attributes:

1.
2.
3.
4.
5.
>>> a = DIV(SPAN('a', 'b'), 'c')
>>>
a['_class'] = 's'
>>> a[0]['_class'] = 't'
>>> print a
<div class="s"><span class="t">ab</span>c</div>

XML

XML
XML is an object used to encapsulate text that should not be escaped. The text may or may not contain valid XML. For example, it could contain JavaScript.

The text in this example is escaped:

1.
2.
>>> print DIV("<b>hello</b>")
&
lt;b&gt;hello&lt;/b&gt;

by using XML you can prevent escaping:

1.
2.
>>> print DIV(XML("<b>hello</b>"))
<
b>hello</b>

Sometimes you want to render HTML stored in a variable, but the HTML may contain unsafe tags such as scripts:

1.
2.
>>> print XML('<script>alert("unsafe!")</script>')
<
script>alert("unsafe!")</script>

Un-escaped executable input such as this (for example, entered in the body of a comment in a blog) is unsafe, because it can be used to generate Cross Site Scripting (XSS) attacks against other visitors to the page.

sanitize
The web2py XML helper can sanitize our text to prevent injections and escape all tags except those that you explicitly allow. Here is an example:
1.
2.
>>> print XML('<script>alert("unsafe!")</script>', sanitize=True)
&
lt;script&gt;alert(&quot;unsafe!&quot;)&lt;/script&gt;

The XML constructors, by default, consider the content of some tags and some of their attributes safe. You can override the defaults using the optional permitted_tags and allowed_attributes arguments. Here are the default values of the optional arguments of the XML helper.

1.
2.
3.
4.
5.
XML(text, sanitize=False,
permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li',
'ol', 'ul', 'p', 'cite', 'code', 'pre', 'img/'],
allowed_attributes={'a':['href', 'title'],
'img':['src', 'alt'], 'blockquote':['type']})

Built-in Helpers

A

This helper is used to build links.

A
1.
2.
3.
>>> print A('<click>', XML('<b>me</b>'),
_href='http://www.web2py.com')
<
a href='http://www.web2py.com'>&lt;click&gt;<b>me/b></a>

This helper takes a special argument called cid. It works as follows:

1.
2.
<div id="myid"></div>
{{=A('linked page',_href='http://example.com',cid='myid')}}

and a click on the link cases the content to be loaded in the div. This requires {{include 'web2py_ajax.html'}} in the layout head.

We discuss applications of cid in more detail in Chapter 13, in the context of components.

B

B

This helper makes its contents bold.

1.
2.
>>> print B('<hello>', XML('<i>world</i>'), _class='test', _id=0)
<
b id="0" class="test">&lt;hello&gt;<i>world</i></b>

BODY

BODY
This helper makes the body of a page.
1.
2.
>>> print BODY('<hello>', XML('<b>world</b>'), _bgcolor='red')
<
body bgcolor="red">&lt;hello&gt;<b>world</b></body>

CENTER

CENTER

This helper centers its content.

1.
2.
3.
>>> print CENTER('<hello>', XML('<b>world</b>'),
>>>
_class='test', _id=0)
<
center id="0" class="test">&lt;hello&gt;<b>world</b></center>

CODE

CODE

This helper performs syntax highlighting for Python, C, C++, HTML and web2py code, and is preferable to PRE for code listings. CODE also has the ability to create links to the web2py API documentation.

Here is an example of highlighting sections of Python code.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
>>> print CODE('print "hello"', language='python').xml()
<
table><tr valign="top"><td style="width:40px; text-align: right;"><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
background-color: #E0E0E0;
color: #A0A0A0;
">1.</pre></td><td><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
overflow: auto;
"><span style="color:#185369; font-weight: bold">print </span>
<span style="color: #FF9966">"hello"</span></pre></td></tr>
</
table>

Here is a similar example for HTML

1.
2.
3.
>>> print CODE(
>>>
'<html><body>{{=request.env.remote_add}}</body></html>',
>>>
language='html')
1.
2.
3.
<table>...<code>...
<html><body>{{=request.env.remote_add}}</body></html>
...</code>...</table>

These are the default arguments for the CODE helper:

1.
CODE("print 'hello world'", language='python', link=None, counter=1, styles={})

Supported values for the language argument are "python", "html_plain", "c", "cpp", "web2py", and "html". The "html" language interprets {{ and }} tags as "web2py" code, while "html_plain" doesn't.

If a link value is specified, for example "/examples/global/vars/", web2py API references in the code are linked to documentation at the link URL. For example "request" would be linked to "/examples/global/vars/request". In the above example, the link URL is handled by the "var" action in the "global.py" controller that is distributed as part of the web2py "examples" application.

The counter argument is used for line numbering. It can be set to any of three different values. It can be None for no line numbers, a numerical value specifying the start number, or a string. If the counter is set to a string, it is interpreted as a prompt, and there are no line numbers.

DIV

All helpers apart from XML are derived from DIV and inherit its basic methods.

DIV
1.
2.
>>> print DIV('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
div id="0" class="test">&lt;hello&gt;<b>world</b></div>

EM

Emphasizes its content.

EM
1.
2.
>>> print EM('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
em id="0" class="test">&lt;hello&gt;<b>world</b></em>

FIELDSET

FIELDSET

This is used to create an input field together with its label.

1.
2.
>>> print FIELDSET('Height:', INPUT(_name='height'), _class='test')
<
fieldset class="test">Height:<input name="height" /></fieldset>

FORM

FORM

This is one of the most important helpers. In its simple form, it just makes a <form>...</form> tag, but because helpers are objects and have knowledge of what they contain, they can process submitted forms (for example, perform validation of the fields). This will be discussed in detail in Chapter 7.

1.
2.
3.
>>> print FORM(INPUT(_type='submit'), _action=", _method='post')
<
form enctype="multipart/form-data" action="" method="post">
<
input type="submit" /></form>

The "enctype" is "multipart/form-data" by default.

hidden
The constructor of a FORM, and of SQLFORM, can also take a special argument called hidden. When a dictionary is passed as hidden, its items are translated into "hidden" INPUT fields. For example:
1.
2.
3.
>>> print FORM(hidden=dict(a='b'))
<
form enctype="multipart/form-data" action="" method="post">
<
input value="b" type="hidden" name="a" /></form>

H1, H2, H3, H4, H5, H6

H1

These helpers are for paragraph headings and subheadings:

1.
2.
>>> print H1('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
h1 id="0" class="test">&lt;hello&gt;<b>world</b></h1>

HEAD

For tagging the HEAD of an HTML page.

HEAD
1.
2.
>>> print HEAD(TITLE('<hello>', XML('<b>world</b>')))
<
head><title>&lt;hello&gt;<b>world</b></title></head>

HTML

HTML
XHTML

This helper is a little different. In addition to making the <html> tags, it prepends the tag with a doctype string .

1.
2.
3.
4.
>>> print HTML(BODY('<hello>', XML('<b>world</b>')))
<!
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"
>
<
html><body>&lt;hello&gt;<b>world</b></body></html>

The HTML helper also takes some additional optional arguments that have the following default:

1.
HTML(..., lang='en', doctype='transitional')

where doctype can be 'strict', 'transitional', 'frameset', 'html5', or a full doctype string.

XHTML

XHTML

XHTML is similar to HTML but it creates an XHTML doctype instead.

1.
XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml')

where doctype can be 'strict', 'transitional', 'frameset', or a full doctype string.

INPUT

INPUT

Creates an <input.../> tag. An input tag may not contain other tags, and is closed by /> instead of >. The input tag has an optional attribute _type that can be set to "text" (the default), "submit", "checkbox", or "radio".

1.
2.
>>> print INPUT(_name='test', _value='a')
<
input value="a" name="test" />

It also takes an optional special argument called "value", distinct from "_value". The latter sets the default value for the input field; the former sets its current value. For an input of type "text", the former overrides the latter:

1.
2.
>>> print INPUT(_name='test', _value='a', value='b')
<
input value="b" name="test" />

For radio buttons INPUT selectively sets the "checked" attribute:

radio
1.
2.
3.
4.
5.
>>> for v in ['a', 'b', 'c']:
>>>
print INPUT(_type='radio', _name='test', _value=v, value='b'), v
<input value="a" type="radio" name="test" /> a
<input value="b" type="radio" checked="checked" name="test" /> b
<input value="c" type="radio" name="test" /> c

and similarly for checkboxes:

checkbox
1.
2.
3.
4.
>>> print INPUT(_type='checkbox', _name='test', _value='a', value=True)
<
input value="a" type="checkbox" checked="checked" name="test" />
>>>
print INPUT(_type='checkbox', _name='test', _value='a', value=False)
<
input value="a" type="checkbox" name="test" />

IFRAME

This helper includes another web page in the current page. The url of the other page is specified via the "_src" attribute.

IFRAME
1.
2.
>>> print IFRAME(_src='http://www.web2py.com')
<
iframe src="http://www.web2py.com"></iframe>

IMG

IMG

It can be used to embed images into HTML:

1.
2.
>>> IMG(_src='http://example.com/image.png',_alt='test')
<
img src="http://example.com/image.ong" alt="rest" />

Here is a combination of A, IMG, and URL helpers for including a static image with a link:

1.
2.
3.
4.
5.
>>> A(IMG(_src=URL('static','logo.png'), _alt="My Logo")
_href=URL('default','index'))
<
a href="/myapp/default/index">
<
img src="/myapp/static/logo.png" alt="My Logo" />
</
a>

LABEL

It is used to create a LABEL tag for an INPUT field.

LABEL
1.
2.
>>> print LABEL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
label id="0" class="test">&lt;hello&gt;<b>world</b></label>

LI

It makes a list item and should be contained in a UL or OL tag.

LI
1.
2.
>>> print LI('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
li id="0" class="test">&lt;hello&gt;<b>world</b></li>

LEGEND

It is used to create a legend tag for a field in a form.

LEGEND
1.
2.
>>> print LEGEND('Name', _for='myfield')
<
legend for="myfield">Name</legend>

META

To be used for building META tags in the HTML head. For example:

META
1.
2.
>>> print META(_name='security', _content='high')
<
meta name="security" content="high" />

MARKMIN

Implements the markmin wiki syntax. It converts the input text into output html according to the markmin rules described in the example below:

MARKMIN
1.
2.
3.
>>> print MARKMIN("'this is **bold** or ''italic'' and this [[a link http://web2py.com]]"')
<
p>this is <b>bold</b> or <i>italic</i> and
this <a href="http://web2py.com">a link</a></p>

The markmin syntax is described in this file that ships with web2py:

http://127.0.0.1:8000/examples/static/markmin.html

and some examples are shown in chapter 13 in the context of plugin_wiki which uses MARKMIN extensively.

OBJECT

Used to embed objects (for example, a flash player) in the HTML.

OBJECT
1.
2.
3.
>>> print OBJECT('<hello>', XML('<b>world</b>'),
>>>
_src='http://www.web2py.com')
<
object src="http://www.web2py.com">&lt;hello&gt;<b>world</b></object>

OL

It stands for Ordered List. The list should contain LI tags. OL arguments that are not LI objects are automatically enclosed in <li>...</li> tags.

OL
1.
2.
>>> print OL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
ol id="0" class="test"><li>&lt;hello&gt;</li><li><b>world</b></li></ol>

ON

This is here for backward compatibility and it is simply an alias for True. It is used exclusively for checkboxes and deprecated since True is more Pythonic.

ON
1.
2.
>>> print INPUT(_type='checkbox', _name='test', _checked=ON)
<
input checked="checked" type="checkbox" name="test" />

OPTGROUP

Allows you tro group multiple options in a SELECT and it is useful to customize the fields using CSS.

OPTGROUP
1.
2.
3.
4.
5.
6.
7.
8.
>>> print SELECT('a', OPTGROUP('b', 'c'))
<
select>
<
option value="a">a</option>
<
optgroup>
<
option value="b">b</option>
<
option value="c">c</option>
</
optgroup>
</
select>

OPTION

This should only be used as part of a SELECT/OPTION combination.

OPTION
1.
2.
>>> print OPTION('<hello>', XML('<b>world</b>'), _value='a')
<
option value="a">&lt;hello&gt;<b>world</b></option>

As in the case of INPUT, web2py make a distinction between "_value" (the value of the OPTION), and "value" (the current value of the enclosing select). If they are equal, the option is "selected".

selected
1.
2.
3.
4.
5.
>>> print SELECT('a', 'b', value='b'):
<
select>
<
option value="a">a</option>
<
option value="b" selected="selected">b</option>
</
select>

P

P

This is for tagging a paragraph.

1.
2.
>>> print P('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
p id="0" class="test">&lt;hello&gt;<b>world</b></p>

PRE

PRE

Generates a <pre>...</pre> tag for displaying pre-formatted text. The CODE helper is generally preferable for code listings.

1.
2.
>>> print PRE('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
pre id="0" class="test">&lt;hello&gt;<b>world</b></pre>

SCRIPT

SCRIPT

This is include or link a script, such as JavaScript. The content between the tags is rendered as an HTML comment, for the benefit of really old browsers.

1.
2.
3.
4.
>>> print SCRIPT('alert("hello world");', _language='javascript')
<
script language="javascript"><!--
alert("hello world");
//--></script>

SELECT

SELECT

Makes a <select>...</select> tag. This is used with the OPTION helper. Those SELECT arguments that are not OPTION objects are automatically converted to options.

1.
2.
3.
4.
5.
>>> print SELECT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
select id="0" class="test">
<
option value="&lt;hello&gt;">&lt;hello&gt;</option>
<
option value="&lt;b&gt;world&lt;/b&gt;"><b>world</b></option>
</
select>

SPAN

SPAN

Similar to DIV but used to tag inline (rather than block) content.

1.
2.
>>> print SPAN('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
span id="0" class="test">&lt;hello&gt;<b>world</b></span>

STYLE

STYLE

Similar to script, but used to either include or link CSS code. Here the CSS is included:

1.
2.
3.
4.
>>> print STYLE(XML('body {color: white}'))
<
style><!--
body { color: white }
//--></
style>

and here it is linked:

1.
2.
3.
>>> print STYLE(_src='style.css')
<
style src="style.css"><!--
//--></
style>

TABLE, TR, TD

TABLE
TR
TD

These tags (along with the optional THEAD, TBODY and TFOOTER helpers) are used to build HTML tables.

1.
2.
>>> print TABLE(TR(TD('a'), TD('b')), TR(TD('c'), TD('d')))
<
table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
TR expects TD content; arguments that are not TD objects are converted automatically.
1.
2.
>>> print TABLE(TR('a', 'b'), TR('c', 'd'))
<
table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

It is easy to convert a Python array into an HTML table using Python's * function arguments notation, which maps list elements to positional function arguments.

Here, we will do it line by line:

1.
2.
3.
>>> table = [['a', 'b'], ['c', 'd']]
>>>
print TABLE(TR(*table[0]), TR(*table[1]))
<
table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

Here we do all lines at once:

1.
2.
3.
>>> table = [['a', 'b'], ['c', 'd']]
>>>
print TABLE(*[TR(*rows) for rows in table])
<
table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

TBODY

TBODY

This is used to tag rows contained in the table body, as opposed to header or footer rows. It is optional.

1.
2.
>>> print TBODY(TR('<hello>'), _class='test', _id=0)
<
tbody id="0" class="test"><tr><td>&lt;hello&gt;</td></tr></tbody>

TEXTAREA

TEXTAREA

This helper makes a <textarea>...</textarea> tag.

1.
2.
>>> print TEXTAREA('<hello>', XML('<b>world</b>'), _class='test')
<
textarea class="test" cols="40" rows="10">&lt;hello&gt;<b>world</b></textarea>

The only caveat is that its optional "value" overrides its content (inner HTML)

1.
2.
>>> print TEXTAREA(value="<hello world>", _class="test")
<
textarea class="test" cols="40" rows="10">&lt;hello world&gt;</textarea>

TFOOT

TFOOT

This is used to tag table footer rows.

1.
2.
>>> print TFOOT(TR(TD('<hello>')), _class='test', _id=0)
<
tfoot id="0" class="test"><tr><td>&lt;hello&gt;</td></tr></tfoot>

TH

TH

This is used instead of TD in table headers.

1.
2.
>>> print TH('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
th id="0" class="test">&lt;hello&gt;<b>world</b></th>

THEAD

THEAD

This is used to tag table header rows.

1.
2.
>>> print THEAD(TR(TD('<hello>')), _class='test', _id=0)
<
thead id="0" class="test"><tr><td>&lt;hello&gt;</td></tr></thead>

TITLE

TITLE

This is used to tag the title of a page in an HTML header.

1.
2.
>>> print TITLE('<hello>', XML('<b>world</b>'))
<
title>&lt;hello&gt;<b>world</b></title>

TR

TR

Tags a table row. It should be rendered inside a table and contain <td>...</td> tags. TR arguments that are not TD objects will be automatically converted.

1.
2.
>>> print TR('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
tr id="0" class="test"><td>&lt;hello&gt;</td><td><b>world</b></td></tr>

TT

TT

Tags text as typewriter (monospaced) text.

1.
2.
>>> print TT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
tt id="0" class="test">&lt;hello&gt;<b>world</b></tt>

UL

Signifies an Unordered List and should contain LI items. If its content is not tagged as LI, UL does it automatically.

UL
1.
2.
>>> print UL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<
ul id="0" class="test"><li>&lt;hello&gt;</li><li><b>world</b></li></ul>

Custom Helpers

TAG

Sometimes you need to generate custom XML tags. web2py provides TAG, a universal tag generator.

1.
{{=TAG.name('a', 'b', _c='d')}}

generates the following XML

1.
<name c="d">ab</name>

Arguments "a" and "b" and "d" are automatically escaped; use the XML helper to suppress this behavior. Using TAG you can generate HTML/XML tags not already provided by the API. TAGs can be nested, and are serialized with str(). An equivalent syntax is:

1.
{{=TAG['name']('a', 'b', c='d')}}

Notice that TAG is an object, and TAG.name or TAG['name'] is a function that returns a temporary helper class.

MENU

MENU

The MENU helper takes a list of lists of the form of response.menu (as described in Chapter 4) and generates a tree-like structure using unordered lists representing the menu. For example:

1.
2.
3.
4.
5.
>>> print MENU([['One', False, 'link1'], ['Two', False, 'link2']])
<
ul class="web2py-menu web2py-menu-vertical">
<
li><a href="link1">One</a></li>
<
li><a href="link2">Two</a></li>
</
ul>

Each menu item can have a fourth argument that is a nested submenu (and so on recursively):

1.
2.
3.
4.
5.
6.
7.
8.
9.
>>> print MENU([['One', False, 'link1', [['Two', False, 'link2']]]])
<
ul class="web2py-menu web2py-menu-vertical">
<
li class="web2py-menu-expand">
<
a href="link1">One</a>
<
ul class="web2py-menu-vertical">
<
li><a href="link2">Two</a></li>
</
ul>
</
li>
</
ul>

The MENU helper takes the following optional arguments:

  • _class: defaults to "web2py-menu web2py-menu-vertical" and sets the class of the outer UL elements.
  • ul_class: defaults to "web2py-menu-vertical" and sets the class of the inner UL elements.
  • li_class: defaults to "web2py-menu-expand" and sets the class of the inner LI elements.

The "base.css" of the scaffolding application understands the following basic types of menus: "web2py-menu web2py-menu-vertical" and "web2py-menu web2py-menu-horizontal".

BEAUTIFY

BEAUTIFY is used to build HTML representations of compound objects, including lists, tuples and dictionaries:

1.
{{=BEAUTIFY({"a":["hello", XML("world")], "b":(1, 2)})}}
BEAUTIFY returns an XML-like object serializable to XML, with a nice looking representation of its constructor argument. In this case, the XML representation of:
1.
{"a":["hello", XML("world")], "b":(1, 2)}

will render as:

1.
2.
3.
4.
<table>
<tr><td>a</td><td>:</td><td>hello<br />world</td></tr>
<tr><td>b</td><td>:</td><td>1<br />2</td></tr>
</table>

Server-side DOM and Parsing

element
elements

elements

The DIV helper and all derived helpers provide an three search methods: element and elements. element returns the first child element matching a specified condition (or None if no match). elements returns a list of all matching children.

element and elements use the same syntax to specify the matching condition which allows for three possibilities that can be mixed and matched: jQuery-like expressions, match by exact attribute value, match using regular expressions.

Here is a simple example:

1.
2.
3.
4.
5.
>>> a = DIV(DIV(DIV('a', _id='target',_class='abc')))
>>>
d = a.elements('div#target')
>>>
d[0] = 'changed'
>>> print a
<div><div><div id="target" class="abc">changed</div></div></div>

The un-named argument of elements is a string which may contain: the name of a tag, the id of a tag preceded by a pound symbol, the class preceded by a dot, the explicit value of an attribute in square brackets.

Here are 4 equivalent ways to search the previous tag by id:

1.
2.
3.
4.
>>> d = a.elements('#target')
>>>
d = a.elements('div#target')
>>>
d = a.elements('div[id=target]')
>>>
d = a.elements('div',_id='target')

Here are 4 equivalent ways to search the previous tag by class:

1.
2.
3.
4.
>>> d = a.elements('.abc')
>>>
d = a.elements('div.abc')
>>>
d = a.elements('div[class=abc]')
>>>
d = a.elements('div',_class='abc')

Any attribute can be used to locate an element (not just id and class), including multiple attributes (the function element can take multiple named arguments) but only the first matching element will be returned.

Using the jQuery syntax "div#target" it is possible to specify multiple search criteria separated by a space:

1.
2.
>>> a = DIV(SPAN('a', _id='t1'),div('b',_class='c2'))
>>>
d = a.elements('span#t1, div#c2')

or equivalently

1.
2.
>>> a = DIV(SPAN('a', _id='t1'),div('b',_class='c2'))
>>>
d = a.elements('span#t1','div#c2')

If the value of search attribute is specified using a name argument, it can be a string or a regular expression:

1.
2.
>>> a = DIV(SPAN('a', _id='test123'),div('b',_class='c2'))
>>>
d = a.elements('span',_id=re.compile('test\d{3}')

A special named argument of the DIV (and derived) helpers is find. It can be used to specify a search value or a search regular expression in the text content of the tag. For example:

1.
2.
3.
4.
>>> a = DIV(SPAN('abcde'),div('fghij'))
>>>
d = a.elements(find='bcd')
>>>
print d[0]
<
span>abcde</span>

or

1.
2.
3.
4.
>>> a = DIV(SPAN('abcde'),div('fghij'))
>>>
d = a.elements(find=re.compile('fg\w{3}'))
>>>
print d[0]
<
div>fghij</div>

components

Here's an example of listing all elements in an html string:

1.
2.
html = TAG('<a>xxx</a><b>yyy</b>')
for item in html.components: print item

parent

parent returns the parent of the current element.

1.
2.
3.
4.
5.
>>> a = DIV(SPAN('a'),DIV('b'))
>>>
d = a.element('a').parent()
>>>
d['_class']='abc'
>>> print a
<div class="abc"><span>a</span><div>b</div></div>

flatten

The flatten method recursive serializes the content of the children of a given element into regular text (without tags):

1.
2.
3.
>>> a = DIV(SPAN('this',DIV('is',B('a'))),SPAN('test'))
>>>
print a.flatten()
thisisatest

Flatten can be passed an optional argument, render, i.e. a function that renders/flattens the content using a different protocol. Here is an example to serialize some tags into Markmin wiki syntax:

1.
2.
3.
4.
5.
6.
>>> a = DIV(H1('title'),P('example of a ',A('link',_href='#test')))
>>>
from gluon.html import markmin_serializer
>>> print a.flatten(render=markmin_serializer)
# titles

example of [[a link #test]]

At the time of writing we provide markmin_serializer and markdown_serializer.

Parsing

The TAG object is also an XML/HTML parser. It can read text and convert into a tree structure of helpers. This allows manipulation using the API above:

1.
2.
3.
4.
5.
>>> html = '<h1>Title</h1><p>this is a <span>test</span></p>'
>>> parsed_html = TAG(html)
>>>
parsed_html.element('span')[0]='TEST'
>>> print parsed_html
<h1>Title</h1><p>this is a <span>TEST</span></p>

Page Layout

page layout
layout.html
extent
include

Views can extend and include other views in a tree-like structure.

For example we can think of a view "index.html" extends "layout.html" and includes "body.html". At the same time "layout.html" may include "header.html" and "footer.html".

The root of the tree is what we call a layout view. Just like any other HTML template file, you can edit it using the web2py administrative interface. The file name "layout.html" is just a convention.

Here is a minimalist page that extends the "layout.html" view and includes the "page.html" view:

1.
2.
3.
{{extend 'layout.html'}}
<h1>Hello World</h1>
{{include 'page.html'}}

The extended layout file must contain an {{include}} directive, something like:

1.
2.
3.
4.
5.
<html><head><title>Page Title</title></head>
<body>
{{include}}
</body>
</head>

When the view is called, the extended (layout) view is loaded, and the calling view replaces the {{include}} directive inside the layout. Processing continues recursively until all extend and include directives have been processed. The resulting template is then translated into Python code.

extend and include are special template directives, not Python commands.
response.menu
menu
response.meta
meta

Layouts are used to encapsulate page commonality (headers, footers, menus), and though they are not mandatory, they will make your application easier to write and maintain. In particular, we suggest writing layouts that take advantage of the following variables that can be set in the controller. Using these well known variables will help make your layouts interchangeable:

1.
2.
3.
4.
5.
6.
7.
8.
response.title
response.subtitle
response.meta.author
response.meta.keywords
response.meta.description
response.flash
response.menu
response.files

Except for menu and files these are all strings and their meaning should be obvious.

response.menu menu is a list of 3-tuples or 4-tuples. The three elements are: the link name, a boolean representing whether the link is active (is the current link), and the URL of the linked page. For example:

1.
2.
response.menu = [('Google', False, 'http://www.google.com',[]),
(
'Index', True, URL('index'), [])]

sub-menu
the fourth tuple element is an optional sub-menu.

response.files is a list of CSS and JS files that are needed by your page.

We also recommend that you use:

1.
{{include 'web2py_ajax.html'}}

in the HTML head, since this will include the jQuery libraries and define some backward-compatible JavaScript functions for special effects and Ajax. "web2py_ajax.html" includes the response.meta tags in the view, jQuery base, the jQuery calendar and includes all required CSS and JS response.files.

Default Page Layout

superfish
ez.css

Here is a minimal "views/layout.html" that ships with the web2py scaffolding application welcome, and any new application will have a similar default layout:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{{=T.accepted_language or 'en'}}">
<head>
<title>{{=response.title or request.application}}</title>
<link rel="shortcut icon"
href="{{=URL(request.application,'static','favicon.ico')}}"
type="image/vnd.microsoft.icon">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

{{###### require CSS and JS files for this page (read info in base.css) }}
{{response.files.append(URL(request.application,'static','base.css'))}}
{{response.files.append(URL(request.application,'static','superfish.js'))}}

{{###### include web2py specific js code (jquery, calendar, form stuff) }}
{{include 'web2py_ajax.html'}}
</head>
<body>
<div class="flash">{{=response.flash or ''}}</div>
<div class="ez-mr wrapper" id="layout">

{{###### Layout 3 from http://www.ez-css.org/layouts }}
<div class="ez-wr">
<div class="ez-box" id="header">

{{try:}}{{=auth.navbar(action=URL(request.application,'default','user'))}}{{except:pass}}
<h1>
<a href="">{{=response.title or 'response.title'}}</a>
</h1>
<h2>
{{=response.subtitle or 'response.subtitle'}}
</h2>
</div>
<div class="ez-box" id="statusbar">

{{###### superfish menu }}
{{=MENU(response.menu,_class='sf-menu')}}

<script>
jQuery(document).ready(function(){
jQuery('ul.sf-menu').superfish({delay:400});});
</script>
</div>
<div class="ez-wr">
<div class="ez-fl ez-negmx">
<div class="ez-box" id="left_sidebar">{{###### unused space}}</div>
</div>
<div class="ez-fl ez-negmr">
<
div class="ez-box" id="content">{{include}}</div>
</div>
<div class="ez-last ez-oh">
<div class="ez-box" id="right_sidebar">{{###### unused space}}</div>
</div>
</div>
<div class="ez-box" id="footer">
{{=
T('Copyright')}} © 2010 -
{{=T('Powered by')}} <a href="http://www.web2py.com">web2py</a>
</div>
</div>
</div>
</body>
</html>

There are a few features of this default layout that make it very easy to use and customize:

  • {{#...}} are special comments that will not appear in the HTML of the page.
  • It displays both response.title and response.subtitle which can be set in a model. If they are not set, it adopts the application name as title
  • It include the web2py_ajax.html file in the header
  • It requires two files explicitly: "base.css" and "superfish.js". The former contains the complete CSS for the page and it is very well documented and customizable. The latter contains the JS for the default cascading menu.
  • The {{=auth.navbar(...)}} displays a welcome to the current user and links to auth functions like login, logout, register, change password, etc. depending on context. It is placed in a {{try:}}...{{except:pass}} in case auth is undefined.
  • The {{=MENU(response.menu) displays the menu structure as <ul>...</ul>.
  • There is an explicit script to activate the superfish cascading menu and it can be removed if not necessary.
  • {{include}} is replaced by the content of the extending view when the page is rendered.
  • By default it uses a three column page layout and the uses the following DIV ids: "header", "left_sidebar", "content", and "right_sidebar", "footer" although the provided "base.css" sets the widths of the sidebars to zero.
  • It uses the ez.css convention for layout CSS naming defined in ref 51. In particular it uses Layout number 3. The minimized ez.css is included in "base.css".

Customizing the Default Layout

Customizing the default layout without editing is very easy because of the "static/base.css" file is very well documented.

In particular it is organized in the following sub-sections:

  • ez.css
  • reset common tags
  • choose default fonts
  • choose link style
  • add bottom line to table rows
  • labels bold and occasionally centered
  • make all input fields the same size
  • add proper separation between h1-h6 and text
  • always indent the first line and add space below paragraphs
  • bullets and numbers style and indent
  • form and table padding
  • code blocks
  • left and right padding to quoted text
  • page layout alignment, width and padding (change this for spaces)
  • column widths (change this to use left_sidebar and right_sidebar)
  • background images and colors (change this for colors)
  • menu style (for superfish.js)
  • web2py specific (.flash, .error)

To change the left_sidebar, content, right_sidebar widths, simply edit part of "base.css":

1.
2.
3.
4.
/*********** column widths ***********/
#left_sidebar { width: 0px; }
#content { width: 840px; }
#right_sidebar { width: 0px; }

To change colors and background images, simply edit the following part:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
/*********** backrgound images and colors ***********/
body { background: url('/book/static/book_images_png/background.png') repeat-x #3A3A3A; }

a { color: #349C01; }
.auth_navbar {
top: 0px;
float: right;
padding: 3px 10px 3px 10px;
font-size: 0.9em;
}
code { color: green; background: black; }
input:focus, textarea:focus { background: #ccffcc; }
#layout { background: white; }
#header, #footer { color: white; background: url('/book/static/book_images_png/header.png') repeat #111111;}
#header h1 { color: #349C01; }
#header h2 { color: white; font-style: italic; font-size: 14px;}
#statusbar { background: #333333; border-bottom: 5px #349C01 solid; }
#statusbar a { color: white; }
#footer { border-top: 5px #349C01 solid; }

The menu is built in a color-neutral way but you can change that too.

Of course you can also completely replace the "layout.html" and "base.css" files with your own.

Functions in Views

Consider this "layout.html":

1.
2.
3.
4.
5.
6.
7.
8.
9.
<html>
<body>
{{include}} <!-- must come before the two blocks below -->
<div class="sidebar">
{{if 'mysidebar' in globals():}}{{mysidebar()}}{{else:}}
my default sidebar
{{pass}}
</body>
</html>

and this extending view

1.
2.
3.
4.
5.
{{def mysidebar():}}
my new sidebar!!!
{{return}}
{{extend 'layout.html'}}
Hello World!!!

Notice the function defined before the {{extend...}} statement. Also notice the function is included in the extended view without the = prefix.

The code generates the following output:

1.
2.
3.
4.
5.
6.
7.
8.
9.
<html>
<body>
Hello World!!!
<div class="sidebar">
{{block mysidebar}}
my new sidebar!!!
{{end}}
</body>
</html>

Notice that the functions are defined in HTML (although they can also contain Python code) so that response.write is used to write their content (the functions do not return the content). This is why the layout calls the view function using {{mysidebar()}} rather than {{=mysidebar()}}. Functions defined in this way can take arguments.

Blocks in Views

block

Another way to make a view more modular is by using {{block...}}s and this mechanism is an alternative to the mechanism discussed in the previous section.

Consider this "layout.html":

1.
2.
3.
4.
5.
6.
7.
8.
9.
<html>
<body>
{{include}} <!-- must come before the two blocks below -->
<div class="sidebar">
{{block mysidebar}}
my default sidebar
{{end}}
</body>
</html>

and this extending view

1.
2.
3.
4.
5.
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
my new sidebar!!!
{{end}}

It generates the following output:

1.
2.
3.
4.
5.
6.
7.
8.
9.
<html>
<body>
Hello World!!!
<div class="sidebar">
{{block mysidebar}}
my new sidebar!!!
{{end}}
</body>
</html>

You can have many blocks and if the a block is present the extended view but not in the extending view, the content of the extended view is used.

Using the Template System to Generate Emails

emails

It is possible to use the template system to generate emails. For example, consider the database table

1.
db.define_table('person', Field('name'))

where you want to send to every person in the database the following message, stored in a view file "message.html":

1.
2.
Dear {{=person.name}},
You have won the second prize, a set of steak knives.

You can achieve this in the following way

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
>>> from gluon.tool import Mail
>>> mail = Mail(globals())
>>>
mail.settings.server = 'smtp.gmail.com:587'
>>> mail.settings.sender = '...@somewhere.com'
>>> mail.settings.login = None or 'username:password'
>>> for person in db(db.person.id>0).select():
>>>
context = dict(person=person)
>>>
message = response.render('message.html', context)
>>>
mail.send(to=['who@example.com'],
>>>
subject='None',
>>>
message=message)

Most of the work is done in the statement

1.
response.render('message.html', context)

It renders the view "file.html" with the variables defined in the dictionary "context", and it returns a string with the rendered email text. The context is a dictionary that contains variables that will be visible to the template file.

If the message starts with <html> and ends with </html> the email will be a HTML email.

The same mechanism that is used to generate email text can also be used to generate SMS or any other type of message based on a template.

© 2008-2010 by Massimo Di Pierro - All rights reserved - Powered by web2py - design derived from a theme by the earlybird
The content of this book is released under the Artistic License 2.0 - Modified content cannot be reproduced.