web2py comes in binary packages for Windows and Mac OS X. There is also a source code version that runs on Windows, Mac, Linux, and other Unix systems. The Windows and OS X binary versions include the necessary Python interpreter. The source code package assumes that Python is already installed on the computer.
web2py requires no installation. To get started, unzip the downloaded zip file for your specific operating system and execute the corresponding web2py file.
On Windows, run:
1. | web2py.exe |
On OS X, run:
1. | open web2py.app |
On Unix and Linux, run from source by typing:
1. | python2.5 web2py.py |
The web2py program accepts various command line options which are discussed later.
By default, at startup, web2py displays a startup window:

and then displays a GUI widget that asks you to choose a one-time administrator password, the IP address of the network interface to be used for the web server, and a port number from which to serve requests. By default, web2py runs its web server on 127.0.0.1:8000 (port 8000 on localhost), but you can run it on any available IP address and port. You can query the IP address of your network interface by opening a command line and typing ipconfig on Windows or ifconfig on OS X and Linux. From now on we assume web2py is running on localhost (127.0.0.1:8000). Use 0.0.0.0:80 to run web2py publicly on any of your network interfaces.

If you do not provide an administrator password, the administration interface is disabled. This is a security measure to prevent publicly exposing the admin interface.
The administrative interface, admin, is only accessible from localhost unless you run web2py behind Apache with mod_proxy. If admin detects a proxy, the session cookie is set to secure and admin login does not work unless the communication between the client and the proxy goes over HTTPS; this is a security measure. All communications between the client and admin must always be local or encrypted; otherwise an attacker would be able to perform a man-in-the middle attack or a replay attack and execute arbitrary code on the server.
After the administration password has been set, web2py starts up the web browser at the page:
If the computer does not have a default browser, open a web browser and enter the URL.

Clicking on "administrative interface" takes you to the login page for the administration interface.

The administrator password is the password you chose at startup. Notice that there is only one administrator, and therefore only one administrator password. For security reasons, the developer is asked to choose a new password every time web2py starts unless the <recycle> option is specified. This is distinct from the authentication mechanism in web2py applications.
After the administrator logs into web2py, the browser is redirected to the "site" page.

This page lists all installed web2py applications and allows the administrator to manage them. web2py comes with three applications:
From the admin application's site page, you can perform the following operations:
When you create a new application using admin, it starts as a clone of the "welcome" scaffolding app with a "models/db.py" that creates a SQLite database, connects to it, instantiates Auth, Crud and Service, configures them. It also provides a "controller/default.py" which exposes actions "index", "download", "user" for user management, and "call" for services. In the following, we assume that these files have been removed; we will be creating apps from scratch.
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.

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

To run the new application, visit:
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 five categories:
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 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(): |
Here is what the online editor looks like:

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

Now, edit the "index" function as follows:
1. | def index(): |
Also from the edit page, edit the view default/index (the new file associated with the action) and, in this file, write:
1. | <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.xml, and generic.rss. These generic views can be modified for each application individually, and additional views can be added easily.Read more on this topic in Chapter 9.
If you go back to "EDIT" and click on index, you will now see the following HTML 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(): |
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(): |
Now modify the view to add a line that displays the value of the counter:
1. | <html> |
When you visit the index page again (and again) you should get the following HTML page:

The counter is associated with each visitor, and is incremented each time the visitor reloads the page. Different visitors see different counters.
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.

Write the corresponding actions in the default controller:
1. | def first(): |
Then create a view "default/first.html" for the first action:

and enter:
1. | {{extend 'layout.html'}} |
Finally, create a view "default/second.html" for the second action:
1. | {{extend 'layout.html'}} |
If you now visit the first page, type your name:

and submit the form, you will receive a greeting:

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.

Modify the default controller to implement self-submission:
1. | def first(): |
Then modify the "default/first.html" view:
1. | {{extend 'layout.html'}} |
and the "default/second.html" view needs to retrieve the data from the session instead of from the request.vars:
1. | {{extend 'layout.html'}} |
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 applications 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 not request.function=='first' and not session.visitor_name: |
This is a general mechanism that you can use to enforce authorization on controllers, though see Chapter 8 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(): |
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.
The form object can be easily serialized in HTML by embedding it in the "default/first.html" view.
1. | {{extend 'layout.html'}} |
The form.accepts method applies the validators. 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:

In the next section we will show how forms can be generated automatically from a model.
Here, as another example, we wish to create a web application that allows the administrator to post images and give them a name, and allows the visitors of the web site to view the named images and submit comments.
As before, create the new application from the site page in admin and navigate to the edit page:

We start by creating a model, a representation of the persistent data in the application (the images to upload, their names, and the comments). First, you need to create/edit a model file which, for lack of imagination, we call "db.py". We assume the code below will replace any existing code in "db.py". Models and controllers must have a .py extension since they are Python code. If the extension is not provided, it is appended by web2py. Views instead have a .html extension since they mainly contain HTML code.
Edit the "db.py" file by clicking the corresponding "edit" button:

and enter the following:
1. | db = DAL("sqlite://storage.sqlite") |
Let's analyze this line by line.
Line 1 defines a global variable called db that represents the database connection. In this case it is a connection to a SQLite database stored in the file "applications/images/databases/storage.sqlite". In the SQLite case, if the database does not exist, it is created. You can change the name of the file, as well as the name of the global variable db, but it is convenient to give them the same name, to make it easy to remember.
Lines 3-5 define a table "image". define_table is a method of the db object. The first argument, "image", is the name of the table we are defining. The other arguments are the fields belonging to that table. This table has a field called "title", a field called "file", and a field called "id" that serves as the table primary key ("id" is not explicitly declared because all tables have an id field by default). The field "title" is a string, and the field "file" is of type "upload". "upload" is a special type of field used by the web2py Data Abstraction Layer (DAL) to store the names of uploaded files. web2py knows how to upload files (via streaming if they are large), rename them safely, and store them.
When a table is defined, web2py takes one of several possible actions: a) if the table does not exist, the table is created; b) if the table exists and does not correspond to the definition, the table is altered accordingly, and if a field has a different type, web2py tries to convert its contents; c) if the table exists and corresponds to the definition, web2py does nothing.
This behavior is called "migration". In web2py migrations are automatic, but can be disabled for each table by passing migrate=False as the last argument of define_table.
Lines 7-11 define another table called "comment".
A comment has an "author", an "email" (we intend to store the email address of the author of the comment), a "body" of type "text" (we intend to use it to store the actual comment posted by the author), and an "image_id" field of type reference that points to db.image via the "id" field.
In lines 13
db.image.title represents the field "title" of table "image". The attribute requires allows you to set requirements/constraints that will be enforced by web2py forms. Here we require that the "title" is unique
1. | IS_NOT_IN_DB(db, db.image.title) |
The objects representing these constraints are called validators. Multiple validators can be grouped in a list. Validators are executed in the order they appear.
IS_NOT_IN_DB(a, b) is a special validator that checks that the value of a field b for a new record is not already in a.
Line 14 requires that the field "image_id" of table "comment" is in db.image.id. As far as the database is concerned, we had already declared this when we defined the table "comment".
Now we are explicitly telling the model that this condition should be enforced by web2py, too, at the form processing level when a new comment is posted, so that invalid values do not propagate from input forms to the database. We also require that the "image_id" be represented by the "title", '%(title)s', of the corresponding record.
Line 18 indicates that the field "image_id" of table "comment" should not be shown in forms, writable=False and not even in readonly forms, readable=False.
The meaning of the validators in lines 15-17 should be obvious.
1. | db.comment.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s') |
could be omitted (and would be automatic) if we were to specify a format for representing an image:
1. | db.define_table('image',....,format='%(title)s') |
where the format can be a string or a function that takes a record and returns a string.
http://127.0.0.1:8000/images/appadmin
Here is a screenshot of the appadmin interface:

This interface is coded in the controller called "appadmin.py" and the corresponding view "appadmin.html". From now on, we will refer to this interface simply as appadmin. It allows the administrator to insert new database records, edit and delete existing records, browse tables, and perform database joins.
The first time appadmin is accessed, the model is executed and the tables are created. The web2py DAL translates Python code into SQL statements that are specific to the selected database back-end (SQLite in this example). You can see the generated SQL from the edit page by clicking on the "sql.log" link under "models". Notice that the link is not present until the tables have been created.

If you were to edit the model and access appadmin again, web2py would generate SQL to alter the existing tables. The generated SQL is logged into "sql.log".
Now go back to appadmin and try to insert a new image record:

web2py has translated the db.image.file "upload" field into an upload form for the file. When the form is submitted and an image file is uploaded, the file is renamed in a secure way that preserves the extension, it is saved with the new name under the application "uploads" folder, and the new name is stored in the db.image.file field. This process is designed to prevent directory traversal attacks.
Notice that each field type is rendered by a widget. Default widgets can be overridden.
When you click on a table name in appadmin, web2py performs a select of all records on the current table, identified by the DAL query
1. | db.image.id > 0 |
and renders the result.

You can select a different set of records by editing the SQL query and pressing "apply".
To edit or delete a single record, click on the record id number.

Because of the IS_IN_DB validator, the reference field "image_id" is rendered by a drop-down menu. The items in the drop-down are stored as keys (db.image.id), but are represented by their db.image.title, as specified by the validator.
Validators are powerful objects that know how to represent fields, filter field values, generate errors, and format values extracted from the field.
The following figure shows what happens when you submit a form that does not pass validation:

The same forms that are automatically generated by appadmin can also be generated programmatically via the SQLFORM helper and embedded in user applications. These forms are CSS-friendly, and can be customized.
Every application has its own appadmin; therefore, appadmin itself can be modified without affecting other applications.
So far, the application knows how to store data, and we have seen how to access the database via appadmin. Access to appadmin is restricted to the administrator, and it is not intended as a production web interface for the application; hence the next part of this walk-through. Specifically we want to create:
This is represented schematically here:

Go back to the edit page and edit the "default.py" controller, replacing its contents with the following:
1. | def index(): |
This action returns a dictionary. The keys of the items in the dictionary are interpreted as variables passed to the view associated to the action. If there is no view, the action is rendered by the "generic.html" view that is provided with every web2py application.
The index action performs a select of all fields (db.image.ALL) from table image, ordered by db.image.title. The result of the select is a Rows object containing the records. Assign it to a local variable called images returned by the action to the view. images is iterable and its elements are the selected rows. For each row the columns can be accessed as dictionaries:
images[0]['title'] or equivalently as images[0].title.
If you do not write a view, the dictionary is rendered by "views/generic.html" and a call to the index action would look like this:

You have not created a view for this action yet, so web2py renders the set of records in plain tabular form.
Proceed to create a view for the index action. Return to admin, edit "default/index.html" and replace its content with the following:
1. | {{extend 'layout.html'}} |
The first thing to notice is that a view is pure HTML with special {{...}} tags. The code embedded in {{...}} is pure Python code with one caveat: indentation is irrelevant. Blocks of code start with lines ending in colon (:) and end in lines beginning with the keyword pass. In some cases the end of a block is obvious from context and the use of pass is not required.
Lines 5-7 loop over the image rows and for each row image display:
1. | LI(A(image.title, _href=URL('show', args=image.id)) |
This is a <li>...</li> tag that contains an <a href="...">...</a> tag which contains the image.title. The value of the hypertext reference (href attribute) is:
1. | URL('show', args=image.id) |
i.e., the URL within the same application and controller as the current request that calls the function called "show", passing a single argument to the function, args=image.id.
LI, A, etc. are web2py helpers that map to the corresponding HTML tags. Their unnamed arguments are interpreted as objects to be serialized and inserted in the tag's innerHTML. Named arguments starting with an underscore (for example _href) are interpreted as tag attributes but without the underscore. For example _href is the href attribute, _class is the class attribute, etc.
As an example, the following statement:
1. | {{=LI(A('something', _href=URL('show', args=123))}} |
is rendered as:
1. | <li><a href="/images/default/show/123">something</a></li> |
A handful of helpers (INPUT, TEXTAREA, OPTION and SELECT) also support some special named attributes not starting with underscore (value, and requires). They are important for building custom forms and will be discussed later.
Go back to the edit page. It now indicates that "default.py exposes index". By clicking on "index", you can visit the newly created page:
http://127.0.0.1:8000/images/default/index
which looks like:

If you click on the image name link, you are directed to:
http://127.0.0.1:8000/images/default/show/1
and this results in an error, since you have not yet created an action called "show" in controller "default.py".
Let's edit the "default.py" controller and replace its content with:
1. | def index(): |
The controller contains two actions: "show" and "download".
The "show" action selects the image with the id parsed from the request args and all comments related to the image. "show" then passes everything to the view "default/show.html".
The image id referenced by:
1. | URL('show', args=image.id) |
in "default/index.html", can be accessed as:
request.args(0)
from the "show" action.
The "download" action expects a filename in request.args(0), builds a path to the location where that file is supposed to be, and sends it back to the client. If the file is too large, it streams the file without incurring any memory overhead.
Notice the following statements:
db.comment table using only the specified fields.request.vars) within the current session (the session is used to prevent double submissions, and to enforce navigation). If the submitted form variables are validated, the new comment is inserted in the db.comment table; otherwise the form is modified to include error messages (for example, if the author's email address is invalid). This is all done in line 9!.response.flash is a web2py variable that is displayed in the views and used to notify the visitor that something happened.The "download" action is already defined in the "default.py" controller of the scaffolding application.The "download" action does not return a dictionary, so it does not need a view. The "show" action, though, should have a view, so return to admin and create a new view called "default/show.html".
Edit this new file and replace its content with the following:
1. | {{extend 'layout.html'}} |
This view displays the image.file by calling the "download" action inside an <img ... /> tag.
If there are comments, it loops over them and displays each one.
Here is how everything will appear to a visitor.

When a visitor submits a comment via this page, the comment is stored in the database and appended at the bottom of the page.
web2py also provides a CRUD (Create/Read/Update/Delete) API that simplifies forms even more. To use CRUD it is necessary to define it somewhere, such as in module "db.py":
1. | from gluon.tools import Crud |
These two lines are already in the scaffolding application.The
crud object provides high-level methods, for example:
1. | form = crud.create(table) |
that can be used to replace the programming pattern:
1. | form = SQLFORM(table) |
Here, we rewrite the previous "show" action using crud and making some more improvements:
1. | def show(): |
First of all notice we have used the syntax
1. | db.image(request.args(0)) or redirect(...) |
to fetch the required record. Since `table(id) returns None if the record is not found, we can use or redirect(...) in this case in one line.
The next argument of crud.create is the URL to redirect to after the form is accepted. The message argument is the one to be displayed upon acceptance. You can read more about CRUD in Chapter 7.
The web2py API for Role-Based Access Control is quite sophisticated, but for now we will limit ourselves to restricting access to the show action to authenticated users, deferring a more detailed discussion to Chapter 8.
To limit access to authenticated users, we need to complete three steps. In a model, for example "db.py", we need to add:
1. | from gluon.tools import Auth |
In our controller, we need to add one action:
1. | def user(): |
This is sufficient to enable login, register, logout, etc. pages. The default layout will also show options to the corresponding pages in the top right corner.

We can now decorate the functions that we want to restrict, for example:
1. | @auth.requires_login() |
Any attempt to access
http://127.0.0.1:8000/images/default/show/[image_id]
will require login. If the user is not logged it, the user will be redirected to
http://127.0.0.1:8000/images/default/user/login

The user function also exposes, among others, the following actions:
http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized
Now, a first-time user needs to register in order to be able to log in and read or post comments.
Both theauthobject and theuserfunction are already defined in the scaffolding application. Theauthobject is highly customizable and can deal with email verification, registration approvals, CAPTCHA, and alternate login methods via plugins. Both theauthobject and theuserfunction are already defined in the scaffolding application. Theauthobject is highly customizable and can deal with email verification, registration approvals, CAPTCHA, and alternate login methods.
You can configure the defaul layout by editing "views/layout.html" but you can also configure it without editing the HTML. In fact, the "static/base.css" stylesheet is well documented and described in Chapter 5. You can change color, columns, size, borders and background without editing the HTML. If you want to edit the menu, the title or the subtitle, you can do so in any model file. The scaffolding app, sets default values of these parameters in the file "models/menu.py":
1. | response.title = request.application |
The following diagram lists the actions that we need to implement and the links we intend to build among them.

Start by creating a new scaffolding app, naming it "mywiki".
The model must contain three tables: page, comment, and document. Both comment and document reference page because they belong to page. A document contains a file field of type upload as in the previous images application.
Here is the complete model:
1. | db = DAL('sqlite://storage.sqlite') |
Edit the controller "default.py" and create the following actions:
Here is the "default.py" controller:
1. | def index(): |
Lines 2-6 provide a comment for the index action. Lines 4-5 inside the comment are interpreted by python as test code (doctest). Tests can be run via the admin interface. In this case the tests verify that the index action runs without errors.
Lines 18, 27, and 35 try fetch a page record with the id in request.args(0).
Line 13, 20 and 37 define and process create forms, for a new page and a new comment and a new document respectively.
Line 28 defines and process an update form for a wiki page.
Some magic happens in line 51. The onkeyup attribute of the INPUT tag "keyword" is set. Every time the visitor releases a key, the JavaScript code inside the onkeyup attribute is executed, client-side. Here is the JavaScript code:
1. | ajax('bg_find', ['keyword'], 'target'); |
ajax is a JavaScript function defined in the file "web2py_ajax.html" which is included by the default "layout.html". It takes three parameters: the URL of the action that performs the synchronous callback ("bg_find"), a list of the IDs of variables to be sent to the callback (["keyword"]), and the ID where the response has to be inserted ("target").As soon as you type something in the search box and release a key, the client calls the server and sends the content of the 'keyword' field, and, when the sever responds, the response is embedded in the page itself as the innerHTML of the 'target' tag.
The 'target' tag is a DIV defined in line 52. It could have been defined in the view as well.
Here is the code for the view "default/create.html":
1. | {{extend 'layout.html'}} |
If you visit the create page, you see the following:

Here is the code for the view "default/index.html":
1. | {{extend 'layout.html'}} |
It generates the following page:

Here is the code for the view "default/show.html":
1. | {{extend 'layout.html'}} |
If you wish to use markdown syntax instead of markmin syntax:
1. | from gluon.contrib.markdown import WIKI |
and use WIKI it instead of the MARKMIN helper.
Alternatively, you can choose to accept raw HTML instead of markmin syntax. In this case you would replace:
1. | {{=MARKMIN(page.body)}} |
with:
1. | {{=XML(page.body)}} |
This can be done better with:
1. | {{=XML(page.body, sanitize=True)}} |
By setting sanitize=True, you tell web2py to escape unsafe XML tags such as "<script>", and thus prevent XSS vulnerabilities.
Now if, from the index page, you click on a page title, you can see the page that you have created:

Here is the code for the view "default/edit.html":
1. | {{extend 'layout.html'}} |
It generates a page that looks almost identical to the create page.
Here is the code for the view "default/documents.html":
1. | {{extend 'layout.html'}} |
If, from the "show" page, you click on documents, you can now manage the documents attached to the page.

Finally here is the code for the view "default/search.html":
1. | {{extend 'layout.html'}} |
which generates the following Ajax search form:

You can also try to call the callback action directly by visiting, for example, the following URL:
http://127.0.0.1:8000/mywiki/default/bg_find?keyword=wiki
If you look at the page source you see the HTML returned by the callback:
1. | <ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul> |
gluon.contrib.rss2. Just append the following action to the default controller:
1. | def news(): |
and when you visit the page
http://127.0.0.1:8000/mywiki/default/news.rss
you see the feed (the exact output depends on the feed reader). Notice that the dict is automatically converted to RSS, thanks to the .rss extension in the URL.

web2py also includes feedparser to read third-party feeds.
1. | service=Service(globals()) |
Here, the handler action simply publishes (via XML-RPC), the functions specified in the list. In this case, find_by. find_by is not an action (because it takes an argument). It queries the database with .select() and then extracts the records as a list with .response and returns the list.
Here is an example of how to access the XML-RPC handler from an external Python program.
1. | >>> import xmlrpclib |
The handler can be accessed from many other programming languages that understand XML-RPC, including C, C++, C# and Java.
The administrative interface provides additional functionality that we briefly review here.
This page lists all installed applications. There are two forms at the bottom.
The first of them allows creating a new application by specifying its name.
http://web2py.com/appliances/default/download/app.source.221663266939....
Uploaded applications can be .tar files (old convention) and .w2p files (new convention). The latter ones are gzipped tar files. They can be uncompressed manually with tar zxvf [filename] although this is never necessary.

Upon successful upload, web2py displays the MD5 checksum of the uploaded file. You can use it to verify that the file was not corrupted during upload. The KPAX name will appear in the list of installed applications.
Click on the KPAX name on admin to get it up and running.

Application files are stored as w2p files (tar gzipped), but you are not intended to tar or untar them manually; web2py does it for you.For each application the site page allows you to:
All the functionality available from the web2py admin site page is also accessible programmatically via the API defined in the module gluon/admin.py. Simply open a python shell and import this module.
The about tab allows editing the description of the application and its license. These are written respectively in the ABOUT and LICENSE files in the application folder.

You can use MARKMIN, or gluon.contrib.markdown.WIKI syntax for these files as described in ref.29 .
The image below shows the output of the test page for the welcome application.

The image below show the languages tab for the welcome application.

The image below shows how to edit a language file, in this case the "it" (Italian) language for the welcome application.

If you click on the "shell" link under the controllers tab in edit, web2py will open a web based Python shell and will execute the models for the current application. This allows you to interactively talk to your application.

Also under the controllers tab in edit there is a "crontab" link. By clicking on this link you will be able to edit the web2py crontab file. This follows the same syntax as the unix crontab but does not rely on unix. In fact, it only requires web2py, and it works on Windows. It allows you to register actions that need to be executed in background at scheduled times. For more information about this, see the next chapter.
Purposely introduce an error in the images application as shown below:
1. | def index(): |
When you access the index action, you get the following ticket:

Only the administrator can access the ticket:

The ticket shows the traceback, and the content of the file that caused the problem. If the error occurs in a view, web2py shows the view converted from HTML into Python code. This allows to easily identify the logical structure of the file.
Notice that everywhere admin shows syntax-highlighted code (for example, in error reports, web2py keywords are shown in orange). If you click on a web2py keyword, you are redirected to a documentation page about the keyword.
If you fix the divide-by-zero bug in the index action and introduce one in the index view:
1. | {{extend 'layout.html'}} |
you get the following ticket:

Note that web2py has converted the view from HTML into a Python file, and the error described in the ticket refers to the generated Python code and NOT to the original view file.
you get the following ticket:

This may seem confusing at first, but in practice it makes debugging easier, because the Python indentation highlights the logical structure of the code that you embedded in the views.
The code is shown at the bottom of the same page.
All tickets are listed under admin in the errors page for each application:

If you are running from source and you have the Mercurial version control libraries installed:
1. | easy_install mercurial |
then the administrative interface shows one more menu item called "mercurial". It automatically creates a local Mercurial repository for the application. Pressing the "commit" button in the page will commit the current application.
This feature is experimental and will be improved in the future.
appadmin is not intended to be exposed to the public. It is designed to help you by providing an easy access to the database. It consists of only two files: a controller "appadmin.py" and a view "appadmin.html" which are used by all actions in the controller.
The appadmin controller is relatively small and readable; it provides an example on designing a database interface.
appadmin shows which databases are available and which tables exist in each database. You can insert records and list all records for each table individually. appadmin paginates output 100 records at a time.
Once a set of records is selected, the header of the pages changes, allowing you to update or delete the selected records.
To update the records, enter an SQL assignment in the Query string field:
1. | title = 'test' |
where string values must be enclosed in single quotes. Multiple fields can be separated by commas.
To delete a record, click the corresponding checkbox to confirm that you are sure.
appadmin can also perform joins if the SQL FILTER contains a SQL condition that involves two or more tables. For example, try:
1. | db.image.id == db.comment.image_id |
web2py passes this along to the DAL, and it understands that the query links two tables; hence, both tables are selected with an INNER JOIN. Here is the output:

If you click on the number of an id field, you get an edit page for the record with the corresponding id.
If you click on the number of a reference field, you get an edit page for the referenced record.
You cannot update or delete rows selected by a join, because they involve records from multiple tables and this would be ambiguous.