The views

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. These delimiters can be changed for example with

  1. response.delimiters = ('<?', '?>')

If this line is in a model it will be applied everywhere, if in a controller only to views for the controller actions, if inside an action only to the view for that action.

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. if i == 0:
  3. response.write('i is 0')
  4. else:
  5. response.write('i is not 0')
  6. pass
  7. }}

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. <html><body>
  2. {{for x in range(10):}}{{=x}}hello<br />{{pass}}
  3. </body></html>

it translates it into a program:

  1. response.write("""<html><body>""", escape=False)
  2. for x in range(10):
  3. response.write(x)
  4. response.write("""hello<br />""", escape=False)
  5. 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

  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.