PAGE LAYOUT 145 3 <head> 4 <! define the meta tags > 5 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 6 <meta name="keywords" content="{{=response.keywords}}" /> 7 <meta name="description" content="{{=response.description}}" /> 8 <meta name="author" content="{{=response.author}}" /> 9 10 <! choose a title or use the application name > 11 <title>{{=response.title or request.application)}}</title> 12 13 <! include jQuery and other ajax functions > 14 {{include 'web2py_ajax.html'}} 15 16 <! include a style.css file and optional js files > 17 <link href="{{=URL(r=request, c='static', f='style.css')}}" 18 rel="stylesheet" type="text/css"/> 19 </head> 20 <body> 21 22 <! build your header > 23 <div class="header">[Here goes the header]</div> 24 25 <! here is the menu > 26 {{if response.menu:}} 27 <div id="menu"> 28 <ul> 29 <! loop over menu items > 30 {{=for _name, _active, _link in response.menu:}} 31 <li><a href="{{=_link}}" class="{{='active' if _active else ' inactive'}}">{{=_name}}</a></li> 32 {{pass}} 33 </ul> 34 </div> 35 {{pass}} 36 37 <! here is the flash message > 38 <div id="flash">{{=response.flash or ''}}</div> 39 40 <! here the extending view is included > 41 {{include}} 42 43 <! here is the footer > 44 <div class="footer">[created by {{=response.author}} with web2py]</ div> 45 </body> 46 </html> In the layout, it may sometimes be necessary to display variables that are defined in the extending view. This will not be a problem as long as the variables are defined before the "extend" directive. This behavior can be used to extend a layout in more than one place (a standard layout is extended at the point where the {{include}} directive occurs). The idea is to define view functions that generate separate portions of the page (for example: sidebar, 146 THE VIEWS maincontent) and render them in different parts of the layout. The view functions are called in the layout at the points we want them rendered. For example in the following layout: 1 <html><body> 2 {{include}} <! must come before the two blocks below > 3 whatever html 4 {{maincontent()}} 5 whatever html 6 {{if 'sidebar' in globals(): sidebar()}} 7 whatever html 8 </body></html> The functions "maincontent" and "sidebar" are defined in the extending view, although in this example we allowed for the possibility that view does not define "sidebar" function. Here is the corresponding view: 1 {{def sidebar():}} 2 <h1>This is the sidebar</h1> 3 {{return}} 4 {{def maincontent():}} 5 <h1>This is the maincontent</h1> 6 {{return}} 7 {{extend 'layout.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 {{maincontent()}} rather than {{=maincontent()}}. 5.5 Using the Template System to Generate 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 Dear {{=person.name}}, 2 You have won the second prize, a set of steak knives. You can achieve this in the following way 1 >>> from gluon.tool import Mail 2 >>> mail = Mail(globals()) 3 >>> mail.settings.server = 'smtp.gmail.com:587' 4 >>> mail.settings.sender = ' @somewhere.com' 5 >>> mail.settings.login = None or 'username:password' 6 >>> for person in db(db.person.id>0).select(): 7 >>> context = dict(person=person) LAYOUT BUILDER 147 8 >>> message = response.render('message.html', context) 9 >>> mail.send(to=['who@example.com'], 10 >>> subject='None', 11 >>> 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. 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. 5.6 Layout Builder The web2py web site provides a layout builder to help us design new layout pages. Here is a screenshot: This service is in a beta stage and has limited functionality. It is based on the work of Johannes Itten, an exponent of the Bauhaus, and creator of the modern "theory of color". The website lets you select a base color and a few parameters of your layout, such as the height of the header, and it generates a sample layout (in 148 THE VIEWS HTML with embedded CSS) with matching colors and a coherent look and feel. To use the layout, simply download it, and save it over the existing layout.html of your application. CHAPTER 6 THE DATABASE ABSTRACTION LAYER 6.1 Dependencies web2py comes with a Database Abstraction Layer (DAL), an API that maps Python objects into database objects such as queries, tables, and records. The DAL dynamically generates the SQL in real time using the specified dialect for the database back end, so that you do not have to write SQL code or learn different SQL dialects (the term SQL is used generically), and the application will be portable among different types of databases. At the time of this writing, the supported databases are SQLite (which comes with Python and thus web2py), PostgreSQL, MySQL, Oracle, MSSQL, FireBird, DB2, Informix and (partially) the Google App Engine (GAE). GAE is treated as a particular case in Chapter 11. The Windows binary distribution works out of the box with SQLite and MySQL. The Mac binary distribution works out of the box with SQLite. To WEB2PY: Enterprise Web Framework / 2nd Ed By Massimo Di Pierro Copyright © 2009 149 150 THE DATABASE ABSTRACTION LAYER use any other database back-end, run from the source distribution and install the appropriate driver for the required back end. Once the proper driver is installed, start web2py from source, and it will find the driver. Here is a list of drivers: database driver (source) SQLite sqlite3 or pysqlite2 or zxJDBC [53] (on Jython) PostgreSQL psycopg2 [54] or zxJDBC [53] (on Jython) MySQL MySQLdb [55] Oracle cx Oracle [56] MSSQL pyodbc [57] FireBird kinterbasdb [58] DB2 pyodbc [57] Informix informixdb [59] web2py defines the following classes that make up the DAL: • DAL represents a database connection. For example: 1 db = DAL('sqlite://storage.db') • Tablerepresents a database table. You do not directly instantiate Table; instead, DAL.define table instantiates it. 1 db.define_table('mytable', Field('myfield')) The most important methods of a Table are insert, truncate, drop, and import from csv file. • DAL Field represents a database field. It can be instantiated and passed as an argument to DAL.define table. • DAL Rows is the object returned by a database select. It can be thought of as a list of DALStorage rows: 1 rows = db(db.mytable.myfield!=None).select() • DAL Storage contains field values. 1 for row in rows: 2 print row.myfield • DAL Query is an object that represents an SQL "where" clause: 1 myquery = (db.mytable.myfield != None) & (db.mytable.myfield > ' A') • DAL Set is an object that represents a set of records. Its most important methods are count, select, update, and delete. CONNECTION STRINGS 151 1 myset = db(myquery) 2 rows = myset.select() 3 myset.update(myfield='somevalue') 4 myset.delete() • DAL Expression is something that can be ORed, for example in orderby and groupby expressions. The Field class is derived from Expression. Here is an example. 1 myorder = db.mytable.myfield.upper() | db.mytable.id 2 db().select(db.table.ALL, orderby=myorder) 6.2 Connection Strings A connection with the database is established by creating an instance of the DAL object: 1 >>> db = DAL('sqlite://storage.db', pool_size=0) db is not a keyword; it is a local variable that stores the connection object DAL. You are free to give it a different name. The constructor of DAL requires a single argument, the connection string. The connection string is the only web2py code that depends on a specific back-end database. Here are exam- ples of connection strings for specific types of supported back-end databases (in all cases, we assume the database is running from localhost on its default port and is named "test"): • SQLite 1 'sqlite://storage.db' • MySQL 1 'mysql://username:password@localhost/test' • PostgreSQL 1 'postgres://username:password@localhost/test' • MSSQL 1 'mssql://username:password@localhost/test' • FireBird 152 THE DATABASE ABSTRACTION LAYER 1 'firebird://username:password@localhost/test' • Oracle 1 'oracle://username:password@test' • DB2 1 'db2://username:password@test' • Informix 1 'informix://username:password@test' • Google BigTable on Google App Engine 1 'gae' Notice that in SQLite the database consists of a single file. If it does not exist, it is created. This file is locked every time it is accessed. In the case of MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Informix the database "test" must be created outside web2py. Once the connection is established, web2py will create, alter, and drop tables appropriately. It is also possible to set the connection string to None. In this case DAL will not connect to any back-end database, but the API can still be accessed for testing. Examples of this will be discussed in Chapter 7. Connection Pooling The second argument of the DAL constructor is the pool size; it defaults to 0. For databases other than SQLite and GAE, it is slow to establish a new database connection for each request. To avoid this, web2py implements a mechanism of connection pooling. When a connection is established, after the page has been served and the transaction completed, the connection is not closed, but it goes into a pool. When the next http request arrives, web2py tries to pick a connection from the pool and use that one for a new transaction. If there are no available connections from the pool, a new connection is established. Connections in the pools are shared sequentially among threads, in the sense that they may be used by two different but not simultaneous threads. There is only one pool for each web2py process. When web2py starts, the pool is always empty. The pool grows up to the minimum between the value of pool size and the max number of concurrent DAL, TABLE, FIELD 153 requests. This means that if pool size=10 but our server never receives more than 5 concurrent requests, then the actual pool size will only grow to 5. If pool size=0 then connection pooling is not used. Connection pooling is ignored for SQLite, since it would not yield any benefit. 6.3 DAL, Table, Field The best way to understand the DAL API is to try each function yourself. This can be done interactively via the web2py shell, although ultimately, DAL code goes in the models and controllers. Start by creating a connection. For the sake of example, you can use SQLite. Nothing in this discussion changes when you change the back-end engine. 1 >>> db = DAL('sqlite://storage.db') The database is now connected and the connection is stored in the global variable db. At any time you can retrieve the connection string. 1 >>> print db._uri 2 sqlite://storage.db and the database name 1 >>> print db._dbname 2 sqlite The connection string is called a uri because it is an instance of a Uniform Resource Identifier. The DAL allows multiple connections with the same database or with different databases, even databases of different types. For now, we will assume the presence of a single database since this is the most common situation. The most important method of a DAL is define table: 1 >>> db.define_table('person', Field('name')) It defines, stores and returns a Table object called "person" containing a field (column) "name". This object can also be accessed via db.person, so you do not need to catch the return value. define table checks whether or not the corresponding table exists. If it does not, it generates the SQL to create it and executes the SQL. If the table does exist but differs from the one being defined, it generates the SQL to alter the table and executes it. If a field has 154 THE DATABASE ABSTRACTION LAYER changed type but not name, it will try to convert the data 4 . If the table exists and matches the current definition, it will leave it alone. In all cases it will create the db.person object that represents the table. 6.4 Migrations We refer to this behavior as a "migration". web2py logs all migrations and migration attempts in the file "databases/sql.log". The first argument of define table is always the table name. The other unnamed arguments are the fields (Field). The function also takes an optional last argument called "migrate" which must be referred to explicitly by name as in: 1 >>> db.define_table('person', Field('name'), migrate='person.table') The value of migrate is the filename (in the "databases" folder for the application) where web2py stores internal migration information for this table. These files are very important and should never be removed except when the entire database is dropped. In this case, the ".table" files have to be removed manually. By default, migrate is set to True. This causes web2py to generate the filename from a hash of the connection string. If migrate is set to False, the migration is not performed, and web2py assumes that the table exists in the datastore and it contains (at least) the fields listed in define table. The best practice is to give an explicit name to the migrate table. There may not be two tables in the same application with the same migrate filename. These are the default values of a Field constructor: 1 Field(name, 'string', length=None, default=None, 2 required=False, requires='<default>', 3 ondelete='CASCADE', notnull=False, unique=False, 4 uploadfield=True, widget=None, label=None, comment=None, 5 writable=True, readable=True, update=None, authorize=None, 6 autodelete=False, represent=None) Not all of them are relevant for every field. "length" is relevant only for fields of type "string". "uploadfield" and "authorize" are relevant only for fields of type "upload". "ondelete" is relevant only for fields of type "reference" and "upload". • length sets the maximum length of a "string", "password" or "upload" field. If length is not specified a default value is used but the default 4 If you do not want this, you need to redefine the table twice, the first time, letting web2py drop the field by removing it, and the second time adding the newly defined field so that web2py can create it. . connection string is the only web2 py code that depends on a specific back-end database. Here are exam- ples of connection strings for specific types of supported back-end databases (in all cases,. the box with SQLite. To WEB2 PY: Enterprise Web Framework / 2nd Ed By Massimo Di Pierro Copyright © 2009 149 150 THE DATABASE ABSTRACTION LAYER use any other database back-end, run from the source. existing layout.html of your application. CHAPTER 6 THE DATABASE ABSTRACTION LAYER 6.1 Dependencies web2 py comes with a Database Abstraction Layer (DAL), an API that maps Python objects into database objects