PageMaker » History » Version 3
Version 2 (Elmer de Looff, 2012-05-03 14:47) → Version 3/17 (Elmer de Looff, 2012-05-07 17:30)
PageMaker is the _Controller_ of the MVC approach in µWeb. After a request is received by the web server (either [[Standalone]] or [[Apache]]) and wrapped inside a [[Request]] object, it is [[Request Router|routed]] here to be answered.
In the PageMaker, there might be database lookups done through the [[Model|data abstraction layer (model)]] and likely output is sent back making use of the [[TemplateParser]].
{{toc}}
h1. A very minimal Your own PageMaker
In the simplest form, a PageMaker for a project subclasses from µWeb's default @PageMaker@ class and provides its own methods to handle requests. The full source for this would look something like this:
<pre><code class="python">
#!/usr/bin/python
"""PageMaker demonstration module"""
# uWeb framework
import uweb
class PageMaker(uweb.PageMaker):
def Index(self):
return 'Welcome to our website, it is still very much under construction.'
def Catchall(self, path):
return 'The requested page %r does not exist yet' % path
</code></pre>
h1. DebuggingPageMaker
Before we do anything else, during development you are _strongly advised_ to use µWeb's @DebuggingPageMaker@. This has a lot of additional features for when something goes wrong on the server side. When the regular PageMaker runs into a server side error, it returns a very plain HTTP 500 response:
<pre>
INTERNAL SERVER ERROR (HTTP 500) DURING PROCESSING OF '/'
</pre>
Where @'/'@ is obviously dependent on the path requested by the client. When running @DebuggingPageMaker@ there is a significantly more helpful (for the developer at least) page whenever an internal server error is encountered. It will show a full stack trace, the local variables on each stack level (typically at the point of calling another function), which helps to arrive to the point of failure more quickly.
In all cases, an internal server error will cause a full stacktrace to be logged in the log file database.
h1. Templateparser
The µWeb *[[TemplateParser]]* is available on the standard PageMaker instance. When using PageMaker, an instantiated TemplateParser instance is available through the @parser@ member of PageMaker. Basic usage looks like this:
<pre><code class="python">
import uweb
import time
class PageMaker(uweb.PageMaker):
def VersionPage(self):
return self.parser.Parse(
'version.utp', year=time.strftime('%Y'), version=uweb.__version__)
</code></pre>
The example template for the above file could look something like this:
<pre><code class="html">
<!DOCTYPE html>
<html>
<head>
<title>µWeb version info</title>
</head>
<body>
<p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
</body>
</html>
</code></pre>
And would result in the following output:
<pre><code class="html">
<!DOCTYPE html>
<html>
<head>
<title>µWeb version info</title>
</head>
<body>
<p>µWeb version 0.12 - Copyright 2010-2012 Underdark</p>
</body>
</html>
</code></pre>
Full documentation, with plenty of example template uses can be found on the [[TemplateParser|TemplateParser wiki-entry]].
h2. Template directory configuration
By default, template are loaded from the 'templates' directory that is expected to be on the same path as the pagemaker module. If your pagemaker is located on @/var/www/uweb_project/project.py@, then templates will be automatically loaded from @/var/www/uweb_project/templates/@.
To change the default template loading path, define a new path in the class variable @TEMPLATE_DIR@. This should be a relative path (and defaults to @'templates'@).
h1. Static files
µWeb can handle Static files from disk by itself, you just need to point the routes to the Static method, and give it the correct path where it can find the resource.
The mime-type will be discovered automatically by using the Magic libraries avialable on your server.
h2. Static directory configuration
h1. OpenID routers
How to setup routes if you want to use the openID module:
* ('/OpenIDLogin/?(\w+)?', '_OpenIdInitiate')
* ('/OpenIDValidate', '_OpenIdValidate')
h1. Persistent storage between requests
µWeb allows you to store objects in a process-persistent storage. This means that the storage will be properly persistent and available when µWeb is in [[standalone]] mode. When running on top of [[apache]], this persistence is only as good as the apache process, which is typically a couple hundred to a few thousand requests.
h2. Default users of the persistent storage
By default, the [[TemplateParser]] and the various database connectors are stored in the persistent storage. This has the benefit that pre-parsed templates will not need to be read from disk on subsequent requests. For databases the benefit is that connections need not be made on-the-fly, but can mostly be retrieved from the storage.
h2. Storing persistent values
Storing persistent values is done with the @Set@ method, as follows:
<pre><code class="python">
def _PostInit(self):
if 'connection' not in self.persistent:
self.persistent.Set('connection', self._MakeConnection())
</code></pre>
In the example above, the database connection is only created, and added to the persistent storage, if it's not already present. This way expensive but reusable actions can be optimized by performing them only once (or once every few so many requests, if running on Apache).
h2. Retrieving persistent values
Retrieving stored values works much like this, but uses the @Get@ method:
<pre><code class="python">
def DatabaseAccess(self):
with self.persistent.Get('connection') as cursor:
cursor.Execute('INSERT INTO `message` SET `text` = "success!"')
</code></pre>
This uses the connection we created (or still had) during @_PostInit@, and uses it to update the database.
In case a key has is not present in the persistent storage (because it wasn't set in the process' lifetime or because it was exlicitly dropped), the @Get@ method has an optional second argument, that is returned when the key is not present:
<pre><code class="python">
def FirstVisit(self):
when = self.persistent.Get('first_visit_time', 'just now')
return 'Your first visit was %s.' % when
</code></pre>
This will return the stored date and time when there was a previously recorded visit, or the text _just now_ if there was no previous time logged.
Finally, the persistent storage has a @SetDefault@ method, that acts much like the similarly named dictionary method. It returns the value for the given key, but if it's not present, it will set the key to the provided value, and return it as well. With this, we can improve on our first-visit tracker, and in one call retrieve or store the first time someone visited:
<pre><code class="python">
def FirstVisit(self):
when = self.persistent.SetDefault('first_visit_time', datetime.datetime.now())
return 'Your first visit was %s.' % when
</code></pre>
h2. Deleting persistent values
If for any reason you need to delete a value from the persistent storage, this can be done using the @Del@ method. The given key name is removed from the storage. *N.B.:* If the key was already removed from the storage (this can happen if the delete code runs more than once, or the key was not defined in the process' lifetime), no error is raised. It is assumed that removing the key is the only desired action.
<pre><code class="python">
def DeletePersistentKey(self, key):
self.persistent.Del(key)
</code></pre>
In the PageMaker, there might be database lookups done through the [[Model|data abstraction layer (model)]] and likely output is sent back making use of the [[TemplateParser]].
{{toc}}
h1. A very minimal Your own PageMaker
In the simplest form, a PageMaker for a project subclasses from µWeb's default @PageMaker@ class and provides its own methods to handle requests. The full source for this would look something like this:
<pre><code class="python">
#!/usr/bin/python
"""PageMaker demonstration module"""
# uWeb framework
import uweb
class PageMaker(uweb.PageMaker):
def Index(self):
return 'Welcome to our website, it is still very much under construction.'
def Catchall(self, path):
return 'The requested page %r does not exist yet' % path
</code></pre>
h1. DebuggingPageMaker
Before we do anything else, during development you are _strongly advised_ to use µWeb's @DebuggingPageMaker@. This has a lot of additional features for when something goes wrong on the server side. When the regular PageMaker runs into a server side error, it returns a very plain HTTP 500 response:
<pre>
INTERNAL SERVER ERROR (HTTP 500) DURING PROCESSING OF '/'
</pre>
Where @'/'@ is obviously dependent on the path requested by the client. When running @DebuggingPageMaker@ there is a significantly more helpful (for the developer at least) page whenever an internal server error is encountered. It will show a full stack trace, the local variables on each stack level (typically at the point of calling another function), which helps to arrive to the point of failure more quickly.
In all cases, an internal server error will cause a full stacktrace to be logged in the log file database.
h1. Templateparser
The µWeb *[[TemplateParser]]* is available on the standard PageMaker instance. When using PageMaker, an instantiated TemplateParser instance is available through the @parser@ member of PageMaker. Basic usage looks like this:
<pre><code class="python">
import uweb
import time
class PageMaker(uweb.PageMaker):
def VersionPage(self):
return self.parser.Parse(
'version.utp', year=time.strftime('%Y'), version=uweb.__version__)
</code></pre>
The example template for the above file could look something like this:
<pre><code class="html">
<!DOCTYPE html>
<html>
<head>
<title>µWeb version info</title>
</head>
<body>
<p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
</body>
</html>
</code></pre>
And would result in the following output:
<pre><code class="html">
<!DOCTYPE html>
<html>
<head>
<title>µWeb version info</title>
</head>
<body>
<p>µWeb version 0.12 - Copyright 2010-2012 Underdark</p>
</body>
</html>
</code></pre>
Full documentation, with plenty of example template uses can be found on the [[TemplateParser|TemplateParser wiki-entry]].
h2. Template directory configuration
By default, template are loaded from the 'templates' directory that is expected to be on the same path as the pagemaker module. If your pagemaker is located on @/var/www/uweb_project/project.py@, then templates will be automatically loaded from @/var/www/uweb_project/templates/@.
To change the default template loading path, define a new path in the class variable @TEMPLATE_DIR@. This should be a relative path (and defaults to @'templates'@).
h1. Static files
µWeb can handle Static files from disk by itself, you just need to point the routes to the Static method, and give it the correct path where it can find the resource.
The mime-type will be discovered automatically by using the Magic libraries avialable on your server.
h2. Static directory configuration
h1. OpenID routers
How to setup routes if you want to use the openID module:
* ('/OpenIDLogin/?(\w+)?', '_OpenIdInitiate')
* ('/OpenIDValidate', '_OpenIdValidate')
h1. Persistent storage between requests
µWeb allows you to store objects in a process-persistent storage. This means that the storage will be properly persistent and available when µWeb is in [[standalone]] mode. When running on top of [[apache]], this persistence is only as good as the apache process, which is typically a couple hundred to a few thousand requests.
h2. Default users of the persistent storage
By default, the [[TemplateParser]] and the various database connectors are stored in the persistent storage. This has the benefit that pre-parsed templates will not need to be read from disk on subsequent requests. For databases the benefit is that connections need not be made on-the-fly, but can mostly be retrieved from the storage.
h2. Storing persistent values
Storing persistent values is done with the @Set@ method, as follows:
<pre><code class="python">
def _PostInit(self):
if 'connection' not in self.persistent:
self.persistent.Set('connection', self._MakeConnection())
</code></pre>
In the example above, the database connection is only created, and added to the persistent storage, if it's not already present. This way expensive but reusable actions can be optimized by performing them only once (or once every few so many requests, if running on Apache).
h2. Retrieving persistent values
Retrieving stored values works much like this, but uses the @Get@ method:
<pre><code class="python">
def DatabaseAccess(self):
with self.persistent.Get('connection') as cursor:
cursor.Execute('INSERT INTO `message` SET `text` = "success!"')
</code></pre>
This uses the connection we created (or still had) during @_PostInit@, and uses it to update the database.
In case a key has is not present in the persistent storage (because it wasn't set in the process' lifetime or because it was exlicitly dropped), the @Get@ method has an optional second argument, that is returned when the key is not present:
<pre><code class="python">
def FirstVisit(self):
when = self.persistent.Get('first_visit_time', 'just now')
return 'Your first visit was %s.' % when
</code></pre>
This will return the stored date and time when there was a previously recorded visit, or the text _just now_ if there was no previous time logged.
Finally, the persistent storage has a @SetDefault@ method, that acts much like the similarly named dictionary method. It returns the value for the given key, but if it's not present, it will set the key to the provided value, and return it as well. With this, we can improve on our first-visit tracker, and in one call retrieve or store the first time someone visited:
<pre><code class="python">
def FirstVisit(self):
when = self.persistent.SetDefault('first_visit_time', datetime.datetime.now())
return 'Your first visit was %s.' % when
</code></pre>
h2. Deleting persistent values
If for any reason you need to delete a value from the persistent storage, this can be done using the @Del@ method. The given key name is removed from the storage. *N.B.:* If the key was already removed from the storage (this can happen if the delete code runs more than once, or the key was not defined in the process' lifetime), no error is raised. It is assumed that removing the key is the only desired action.
<pre><code class="python">
def DeletePersistentKey(self, key):
self.persistent.Del(key)
</code></pre>