Simple examples

Say hello

Here, as an example, we create a simple web app that displays the message “Hello from MyApp” to the user. We will call this application “myapp”. We will also add a counter that counts how many times the same user visits the page.

You can create a new application simply by typing its name in the form on the top right of the site page in admin.

image

After you press [create], the application is created as a copy of the built-in welcome application.

image

To run the new application, visit:

  1. http://127.0.0.1:8000/myapp

Now you have a copy of the welcome application.

To edit an application, click on the edit button for the newly created application.

The edit page tells you what is inside the application. Every web2py application consists of certain files, most of which fall into one of six categories:

  • models: describe the data representation.
  • controllers: describe the application logic and workflow.
  • views: describe the data presentation.
  • languages: describe how to translate the application presentation to other languages.
  • modules: Python modules that belong to the application.
  • static files: static images, CSS files[css-w,css-o,css-school] , JavaScript files[js-w,js-b] , etc.
  • plugins: groups of files designed to work together.

Everything is neatly organized following the Model-View-Controller design pattern. Each section in the edit page corresponds to a subfolder in the application folder.

Notice that clicking on section headings will toggle their content. Folder names under static files are also collapsible.

Each file listed in the section corresponds to a file physically located in the subfolder. Any operation performed on a file via the admin interface (create, edit, delete) can be performed directly from the shell using your favorite editor.

The application contains other types of files (database, session files, error files, etc.), but they are not listed on the edit page because they are not created or modified by the administrator; they are created and modified by the application itself.

The controllers contain the logic and workflow of the application. Every URL gets mapped into a call to one of the functions in the controllers (actions). There are two default controllers: “appadmin.py” and “default.py”. appadmin provides the database administrative interface; we do not need it now. “default.py” is the controller that you need to edit, the one that is called by default when no controller is specified in the URL. Edit the “index” function as follows:

  1. def index():
  2. return "Hello from MyApp"

Here is what the online editor looks like:

image

Save it and go back to the edit page. Click on the index link to visit the newly created page.

When you visit the URL

  1. http://127.0.0.1:8000/myapp/default/index

the index action in the default controller of the myapp application is called. It returns a string that the browser displays for us. It should look like this:

image

Now, edit the “index” function as follows:

  1. def index():
  2. return dict(message="Hello from MyApp")

Also from the edit page, edit the view “default/index.html” (the view file associated with the action) and completely replace the existing contents of that file with the following:

  1. <html>
  2. <head></head>
  3. <body>
  4. <h1>{{=message}}</h1>
  5. </body>
  6. </html>

Now the action returns a dictionary defining a message. When an action returns a dictionary, web2py looks for a view with the name

  1. [controller]/[function].[extension]

and executes it. Here [extension] is the requested extension. If no extension is specified, it defaults to “html”, and that is what we will assume here. Under this assumption, the view is an HTML file that embeds Python code using special {{ }} tags. In particular, in the example, the {{=message}} instructs web2py to replace the tagged code with the value of the message returned by the action. Notice that message here is not a web2py keyword but is defined in the action. So far we have not used any web2py keywords.

If web2py does not find the requested view, it uses the “generic.html” view that comes with every application.

If an extension other than “html” is specified (“json” for example), and the view file “[controller]/[function].json” is not found, web2py looks for the view “generic.json”. web2py comes with generic.html, generic.json, generic.jsonp, generic.xml, generic.rss, generic.ics (for Mac Mail Calendar), generic.map (for embedding Google Maps), and generic.pdf (based on fpdf). These generic views can be modified for each application individually, and additional views can be added easily.

Generic views are a development tool. In production every action should have its own view. In fact, by default, generic views are only enabled from localhost.

You can also specify a view with response.view = 'default/something.html'

Read more on this topic in Chapter 10.

If you go back to “EDIT” and click on index, you will now see the following HTML page:

image

Debugging toolbar

For debugging purposes you can insert

  1. {{=response.toolbar()}}

to the code in a view and it will show you some useful information, including the request, response and session objects, and list all db queries with their timing.

Let’s count

Let’s now add a counter to this page that will count how many times the same visitor displays the page.

web2py automatically and transparently tracks visitors using sessions and cookies. For each new visitor, it creates a session and assigns a unique “session_id”. The session is a container for variables that are stored server-side. The unique id is sent to the browser via a cookie. When the visitor requests another page from the same application, the browser sends the cookie back, it is retrieved by web2py, and the corresponding session is restored.

To use the session, modify the default controller:

  1. def index():
  2. if not session.counter:
  3. session.counter = 1
  4. else:
  5. session.counter += 1
  6. return dict(message="Hello from MyApp", counter=session.counter)

Notice that counter is not a web2py keyword but session is. We are asking web2py to check whether there is a counter variable in the session and, if not, to create one and set it to 1. If the counter is there, we ask web2py to increase the counter by 1. Finally we pass the value of the counter to the view.

A more compact way to code the same function is this:

  1. def index():
  2. session.counter = (session.counter or 0) + 1
  3. return dict(message="Hello from MyApp", counter=session.counter)

Now modify the view to add a line that displays the value of the counter:

  1. <html>
  2. <head></head>
  3. <body>
  4. <h1>{{=message}}</h1>
  5. <h2>Number of visits: {{=counter}}</h2>
  6. </body>
  7. </html>

When you visit the index page again (and again) you should get the following HTML page:

image

The counter is associated with each visitor, and is incremented each time the visitor reloads the page. Different visitors see different counters.

Say my name

Now create two pages (first and second), where the first page creates a form, asks the visitor’s name, and redirects to the second page, which greets the visitor by name.

yUML diagram

Write the corresponding actions in the default controller:

  1. def first():
  2. return dict()
  3. def second():
  4. return dict()

Then create a view “default/first.html” for the first action, and enter:

  1. {{extend 'layout.html'}}
  2. <h1>What is your name?</h1>
  3. <form action="{{=URL('second')}}">
  4. <input name="visitor_name" />
  5. <input type="submit" />
  6. </form>

Finally, create a view “default/second.html” for the second action:

  1. {{extend 'layout.html'}}
  2. <h1>Hello {{=request.vars.visitor_name}}</h1>

In both views we have extended the basic “layout.html” view that comes with web2py. The layout view keeps the look and feel of the two pages consistent. The layout file can be edited and replaced easily, since it mainly contains HTML code.

If you now visit the first page, type your name:

image

and submit the form, you will receive a greeting:

image

Postbacks

The mechanism for form submission that we used before is very common, but it is not good programming practice. All input should be validated and, in the above example, the burden of validation would fall on the second action. Thus the action that performs the validation is different from the action that generated the form. This tends to cause redundancy in the code.

A better pattern for form submission is to submit forms to the same action that generated them, in our example the “first”. The “first” action should receive the variables, process them, store them server-side, and redirect the visitor to the “second” page, which retrieves the variables. This mechanism is called a postback.

yUML diagram

Modify the default controller to implement self-submission:

  1. def first():
  2. if request.vars.visitor_name:
  3. session.visitor_name = request.vars.visitor_name
  4. redirect(URL('second'))
  5. return dict()
  6. def second():
  7. return dict()

Then modify the “default/first.html” view:

  1. {{extend 'layout.html'}}
  2. What is your name?
  3. <form>
  4. <input name="visitor_name" />
  5. <input type="submit" />
  6. </form>

and the “default/second.html” view needs to retrieve the data from the session instead of from the request.vars:

  1. {{extend 'layout.html'}}
  2. <h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

From the point of view of the visitor, the self-submission behaves exactly the same as the previous implementation. We have not added validation yet, but it is now clear that validation should be performed by the first action.

This approach is better also because the name of the visitor stays in the session, and can be accessed by all actions and views in the application without having to be passed around explicitly.

Note that if the “second” action is ever called before a visitor name is set, it will display “Hello anonymous” because session.visitor_name returns None. Alternatively we could have added the following code in the controller (inside the second function):

  1. if request.function != 'first' and not session.visitor_name:
  2. redirect(URL('first'))

This is an ad hoc mechanism that you can use to enforce authorization on controllers, though see Chapter 9 for a more powerful method.

With web2py we can move one step further and ask web2py to generate the form for us, including validation. web2py provides helpers (FORM, INPUT, TEXTAREA, and SELECT/OPTION) with the same names as the equivalent HTML tags. They can be used to build forms either in the controller or in the view.

For example, here is one possible way to rewrite the first action:

  1. def first():
  2. form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
  3. INPUT(_type='submit'))
  4. if form.process().accepted:
  5. session.visitor_name = form.vars.visitor_name
  6. redirect(URL('second'))
  7. return dict(form=form)

where we are saying that the FORM tag contains two INPUT tags. The attributes of the input tags are specified by the named arguments starting with underscore. The requires argument is not a tag attribute (because it does not start by underscore) but it sets a validator for the value of visitor_name.

Here is yet another better way to create the same form:

  1. def first():
  2. form = SQLFORM.factory(Field('visitor_name',
  3. label='what is your name?',
  4. requires=IS_NOT_EMPTY()))
  5. if form.process().accepted:
  6. session.visitor_name = form.vars.visitor_name
  7. redirect(URL('second'))
  8. return dict(form=form)

The form object can be easily serialized in HTML by embedding it in the “default/first.html” view.

  1. {{extend 'layout.html'}}
  2. {{=form}}

The form.process() method applies the validators and returns the form itself. The form.accepted variable is set to True if the form was processed and passed validation. If the self-submitted form passes validation, it stores the variables in the session and redirects as before. If the form does not pass validation, error messages are inserted into the form and shown to the user, as below:

image

In the next section we will show how forms can be generated automatically from a model.

In all our examples we have used the session to pass the user name from the first action to the second. We could have used a different mechanism and passed data as part of a redirect URL:

  1. def first():
  2. form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
  3. if form.process().accepted:
  4. name = form.vars.visitor_name
  5. redirect(URL('second', vars=dict(name=name)))
  6. return dict(form=form)
  7. def second():
  8. name = request.vars.name or redirect(URL('first'))
  9. return dict(name=name)

Then modify the “default/second.html” view:

  1. {{extend 'layout.html'}}
  2. <h1>Hello {{=name}}</h1>

Mind that in general it is not a good idea to pass data from one action to another using the URL. It makes it harder to secure the application. It is safer to store the data in a session.

Internationalization

Your code is likely to include hardcoded strings such as “What is your name?”. You should be able to customize strings without editing the code and in particular insert translations for these strings in different languages. In this way if a visitor has the language preference of the browser set to “Italian”, web2py will use the Italian translation for the strings, if available. This feature of web2py is called “internationalization” and it is described in more detail in the next chapter.

Here we just observe that in order to use this feature you should markup strings that needs translation. This is done by wrapping a quoted string in code such as

  1. "What is your name?"

with the T operator:

  1. T("What is your name?")

You can also mark for translations strings hardcoded in views. For example

  1. <h1>What is your name?</h1>

becomes

  1. <h1>{{=T("What is your name?")}}</h1>

It is good practice to do this for every string in the code (field labels, flash messages, etc.) except for tables and field names.

Once the strings are identified and marked up, web2py takes care of almost everything else. The admin interface also provides a page where you can translate each string in the languages you desire to support.

web2py includes a powerful pluralization engine which is described in the next chapter. It is integrated with both the internationalization engine and the markmin renderer.