pyWeb

Python Active HTML Framework

User and Programmer Manual

Copyright (C) 2003 by David McNab
david at freenet dot org dot nz
Released under the GNU General Public License

(pyWeb homepage is http://www.freenet.org.nz/python/pyweb)

Introduction | Installation | DOM | Examples | Class Reference

PyHP - a lovely PHP-like preprocessor for embedding python code in HTML

pywebserver - your own http server, using pyWeb and PyHP



0. Introduction

The whole concept of pyWeb programming is based on a highly Pythonic "Document Object Model".

To create a web page, you instantiate an http object, populate it with content, then invoke its send() method to send it back to the client browser.

Within the document object model, you'll find a simple and intuitive interface for handling HTTP variables, form variables and cookies, as well as generating and updating the elements of your page.

In addition to this is a PHP-like module called pyhp, which preprocesses HTML with python code embedded as comments, executes the python code (which may call an include() function to nest other HTML files (which may themselves contain python code, and include calls, etc). For more info on pyhp, click here.

For a really quick start, you can refer to the Examples section at the end of this document.


1. Installation and Setup

Refer to the INSTALL file within the pyWeb distribution for detailed info on setting up pyWeb and getting it working.


2. Document Object Model

2.1 Overview

pyWeb pages are built from a class called http, which you instantiate, then add content to.

Here are the basic principles of pyWeb's DOM:
  1. You're best off doing:
    from pyweb import *
    instead of:
    import pyweb
    Otherwise, your code will be full of pyweb dot this and pyweb dot that and a bunch of lexical pollution.

  2. For (nearly) all HTML tags, there is a corresponding pyWeb class, with the same name as the tag but in lowercase.
    Look in the main pyweb.py module to see the classes http, html, head, body, p, center, table, div, span, etc etc.
    For example, the class to generate a paragraph tag ("<P>") is p. To generate an empty paragraph, you'd call 'p()'.

  3. The base pyWeb tag class is called 'webwidget', from which all the other content classes are derived. You may never use webwidget directly, although you might at some times want to subclass it.

  4. To add content to a tag object, you simply invoke its add() method and pass in one or more strings and/or tag objects.

  5. To set attributes of a tag, you can either:

    1. Pass the attributes in when you instantiate the tag object. For example:
      page.intro = div(style="font-family:monospace")
      page.body.add(page.intro)
    2. Set the attributes as actual Python attributes, for example:
      page.intro = div()
      page.body.add(page.intro)
      page.intro.style = "font-family: monospace"
    3. Pass an attr() object to the tag's add() method
      page.intro = div()
      page.body.add(page.intro)
      page.intro.add(attr(style="font-family: monospace"))

  6. When adding a tag object, you can 'save' it in the page under a name. This allows you to access the page's content in any order you like
  7. Within the basic http class, there are some magic attributes which allow you to address the HTTP variables

2.2 Document Object Tree

2.2.1 Tag Objects: Common Attributes and Methods

The table below lists all the methods and attributes common to all pyWeb tag objects:

Attributes

Type

Description

tagopen,
tagclose
string
The text name of the tag. Eg the 'table' tag has a value of 'table', so this attribute is used to generate the '<table...>' tag. If you're subclassing a tag, you need to explicitly set this attribute to that of the tag you're subclassing. If the tag requires no closing tag (eg <br>), then the attribute notagclose is set.
content
list
This lists the content which will be generated between the <tagname...> and </tagname> tags.
Items in this list can be strings or nested tag objects.
attr
dict
The attributes which will be output as tag attributes
(<tagname attr1="value1" attr2="value2"...>)

No need to access this directly. If you set any attribute of the tag object, the attribute will be added to this dict. So "mytag.someattribute = somevalue" causes "mytag.attr[someattribute] = somevalue".
There is an exception with the html tag class. Here, if you set an attribute, an attempt is made to decide whether the attribute belongs to the <head>...</head> part or the <body>...</body> part, and it is forwarded on to the head or body attributes (which are actually head and body tag objects. (Sorry if this confuses).
add
method
Call this to add one or more items to the tag object's content.
Each item you add should normally be a string or another tag object.
But in addition to passing strings and tag objects, there are two other magickal entities:
  • attr - a class which holds attribute settings for the tag. You can pass this at any time during any add() call to modify a tag object's attributes. For example:
    tagobject.add("One string",
    p("Some paragraph"),
    attr(align='center'),
    "Last string")
    The add() method will see that you've passed in an attr object and extract its keyword args and add them to the tag object's own attr dictionary.
  • save - a class which you instantiate with the args tagobject, name.
    You call this to 'mark' a tag object, by placing a ref to it in another tag object, so you can access it later at your convenience. For example:
    page = http()
    page.title = "My page title"
    page.body.add(h1("Big heading"),
    p(save(page, "firstpara"),
    "This is the first sentence of firstpara."),
    p(save(page, "secondpara"),
    "This is the first sentence of secondpara."),
    )
    page.secondpara.add(" This is the second sentence of secondpara.")
    page.firstpara.add(" This is another sentence for firstpara.")
    page.send()
  • Note that this lets you get handy references to tags that are otherwise deeply nested within the page. This opens up a lot of possibilities with subclassing and templating.

Note also that the .add() method returns a ref to its tag object , which can offer some syntactic convenience for doing stuff on the fly.
set
method
Call this to totally replace the content of the tag object. Anything previously stored in the content dict will be discarded, and the arguments to this set() call added in its place.
setattr
method
A bit ancient, but some people may want this to stay available. Pass it a bunch of keyword args to add to the existing set of attributes.
attrdump
method
You don't need to call this directly. This method is used as part of the rendering process (see method render() below) to convert the attr dict to a string in the form attr1="val1" attr2="val2"...

render
method
Recursively formats the tag, its attributes, and its (tag and string) contents into real live HTML code.

2.2.2 Tag Object Hierarchy

I've aimed for an intuitive layout of the object/attribute hierarchy for creating pages.

In the table below is a list of the entities you can address within an http object. It is assumed that you've instantiated your http object to the variable 'page' (though you can call it what you like).

Entity

Type

Description

page class pyweb.http
The top-level document object.
Typically, you won't instantiate more than one of these per web hit (unless you're into smart stuff like pickling page templates into disk files or databases).
page.head class
pyweb.head
A tag object comprising the <head...> ... </head> part of the document.
page.head.title
class pyweb.head.title
The 'title' attribute of the <head> tag. Defaults to "None so far"
page.head.style class pyweb.style
Builds and renders the embedded stylesheet. (Sorry, but external stylesheets aren't supported yet.)
A page.head.style object is created and added automatically when the pyweb.head object is created (and therefore, when any pyweb.html or pyweb.http objects are created).
You can build up your stylesheet in several different ways:
  1. Call page.head.style.add(), passing one or more strings and/or dicts.
    • If you pass strings, they'll be broken up into lines and stored in the style object.
    • If you pass any dicts, their keys and values will be added to the style object. Note that the values in these dicts must not contain the enclosing braces, because these will be added automatically on rendering.
  2. Treat page.head.style as a dict - set 'keys' to different values.

Note that, for convenience, 'page.style' is available as shorthand for page.head.style.

Examples:
  • # adding stuff, dict style
    page.style['table'] = 'background: #ffcc00; text-weight: bold'
    page.style['p'] = 'background: red'
  • # adding raw lines of the stylesheet
    page.style.add("table {background:#ffcc00}",
    "p {background: red}")
  • # you can pass in dicts of stylesheet settings
    page.style.add({'table': 'background: #ffcc00',
    'text-weight: bold'},
    "p {background: red}")
  • # or you can pass in the whole stylesheet content as a big string
    page.style.add("""table {background: #ffcc00; text-weight: bold}
    p {background: red}""")
page.body
class pyweb.body
A tag object comprising the <body...> ... </body> part of the document
page.send method Invokes page.render() (and the render() methods of any nested tag objects) to create a string comprising the full HTML page, including the HTTP reply headers.

Call this method when you've finished constructing the page, and exit the script immediately after the call.
page.session
class pyweb.httpenv
This doesn't get rendered in the actual HTML, but gives easy convenient access to most aspects of the whole http request/response cycle. For instance, HTTP environment variables, cookies and form/url variables.
You normally should never need to instantiate this yourself, as it gets automatically created within an http object on creation.
Read on - there's lots of magickal stuff in this object.
page.session.env dict
This contains all the HTTP environment headers, including:

DOCUMENT_ROOT, HTTP_HOST, HTTP_USER_AGENT, PATH, QUERY_STRING, REDIRECT_QUERY_STRING, REMOTE_ADDR', 'REMOTE_PORT', REQUEST_METHOD, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME, SERVER_ADDR, SERVER_NAME, SERVER_PORT, SERVER_PROTOCOL, HTTP_X_FORWARDED_FOR

Obviously you can't set these (or it wouldn't have any effect if you did), but these headers contain all kinds of useful info about the client and his/her request.
page.session.fields
class cgi.FieldStorage
(acts like a dict)
The fields which are passed in the request, either within the URL (with 'http://site.com?var1=val1&var2=val2...'), and/or as fields in a POSTed form.
Again, this is readonly, because nothing here will get sent to the client.
page.session.cookies
class Cookie.SimpleCookie
(acts somewhat like a dict)
This is a read/write object. Contains the cookies which are received from the client browser. Any values remaining in this object at time of rendering will get sent to the client.
Note that in addition to setting values, as in 'page.session.cookies['cookie1'] = value1', you can also set cookie attributes such as 'expires', 'path', 'comment', 'domain', 'max-age', 'secure' and 'version'. For example:
# Create a new cookie
page.session.cookies['viagra_usage'] = num_pills_taken

# Magic - set cookie to expire in one week
page.session.cookies['viagra_usage'].expires = 7*24*3600

As per the http protocol, the cookies get sent back to the browser immediately before the usual Content-type: text/html header.

Important - to retrieve the value of a cookie, you need to use the form:
page.session.cookies['cookiename'].value
If you forget the '.value', you will get the cookie with the 'Cookie:' header string.

Please don't interfere with any cookie with a name beginning with '__data'. These cookies are special (see page.session.data below).
Security note - do not assume that cookies will come back in the form in which you set them. There are people who'll try to subvert the security of your server-side code through malformed cookies. So treat all incoming cookie values as if they might be deliberately tampered with.
page.session.data
class pyweb._datastore
This is one of the most 'automagickal' features of pyWeb.
It's used for persistent per-client data storage, and offers much more flexibility and security than normal cookies.

You add data to this object by simply setting attributes. When the page gets rendered, this data gets pickled, zlib-compressed, converted to base 64, cryptographically signed then written to one or more cookies of the name '__data[_nnn]' (but you don't even have to know about that).
For example:
page.session.data.timed_visited += 1
Notes:
  1. Since cookies are not supposed to be more than 2k each, this data may get broken up into several cookies, and reassembled when next received from the client. The result is that (with compression) you can get 10-40k of data into this store, and send/receive it to/from the client successfully, before the web server barfs that the incoming Cookie header has gotten too large (Apache has an 8k limit).
  2. Yes (I hear the Python boffins scream!), Python pickles are horribly insecure. It's said that a malformed pickle can cause the python interpreter to execute any arbitrary code. So for your protection against 5cr1p7 X1dd1e5, pyWeb creates a signed hash (hmac/sha1) and adds it to the header cookie. Therefore you can trust that any data which comes back is exactly what you sent to the client (as long as you haven't maxed out the size of the incoming Cookie header, that is). If some 14m3 h4X0r tries something on, this datastore will be empty upon page creation, with page.session.error set to an appropriate error value.
page.session.error
string
If the client request and all the fields and cookies were received successfully,  and if the page.session.data was successfully recreated, then this attribute will be set to an empty string.
Otherwise, it will be set to one of the following values:
  • keynotset - You need to set the variable 'pickleSignatureKey' in pyweb.py to something secure
  • nocookie - The datastore cookies are not present in the client browser
  • badformat - The raw datastore cookie came back in an invalid format
  • badsignature - The cookie signature is invalid - most likely the result of client tampering
  • unpackerror - The cookie pickle has an invalid format - tampering is a possibility
page.session.setDataExpiry
method
Call this with the number of seconds you want the datastore cookies (the persistent data in self.session.data) to remain.

Note that this doesn't guarantee you the cookies will stay intact. The client (and/or client browser) may reject your cookies, or have a strict quota on cookie storage space (older cookies get deleted), or (vague possibility) some smart-assed website somewhere else might try tampering with your *own* cookies.
page.style
instance of pyweb.style
Shorthand for page.head.style (see above)
page.contentType
string
Defaults to 'text/html'. Determines the value of the Content-Type: header which is transmitted when the page is rendered out.
You can set this to anything you like, which will prove necessary if your code is generating something like a tarball or graphic image on the fly,




2.2.3 Tag Objects, and various Magick

2.2.3.1 Overview

In pyWeb, there are tag classes named after most of the corresponding HTML tags. For example, you can freely use html, body, ul, li, table, ..., to create a tree of html objects on the fly, then render out that tree into a raw string with the .render() method.

This section discusses some of the 'black magick' involved in instantiating tag objects.

Nearly all of the tag classes are constructed with a list of content items, and keywords:

mytag = tagclassname(arg1, arg2, ..., attr1=val1, attr2=val2,...)

or, for a specific example,

mytable = table(align='center', cellspacing=0, cellpadding=3, border=0)
mytable.add(tr(td("first row"), td("second row")))

Some keywords (such as content, parent, etc) are 'magickal', and have special meanings which are discussed below.

But for all other keywords, these will simply be written to the tag as tag attributes. For example, when the object mytable is rendered out (by calling mytable.render()), the resulting string should look something like:
<table cellpadding="3" border="0" align="center" cellspacing="0">
<tr>
<td>first row</td><td>second row</td></tr>
</table>

2.2.3.2 Constructing a Tag Object

With all tag objects, you can instantiate them without any arguments or keywords, and later on add the content and set attributes.

Any non-keyword arguments you pass in will be immediately added as content (with the exception of attr objects - see the examples below).

With the exception of a few 'magickal keywords', all keyword arguments will be stored in the tag as tag attributes (as per the above example).

2.2.3.3 Magickal Keywords

The following constructor keywords are not added to the tag object as HTML attributes, but instead take some special action:
The above keywords apply to all tag classes.

In addition to these, the http and httpEmpty tag classes also support the following magickal keywords:

2.2.3.4 The html Tag Object

This is one tag object whose behaviour differs significantly from the rest.

Basically, an html object has the following special attributes:
The .add() method of html objects simply passes its args and keywords to the html object's body object, so you can freely add stuff to an html object and trust it'll end up in the body. If you want it to explicitly appear in the head, call .head.add() instead.

2.2.3.5 The http Tag Object

This is a subclass of html, with the httpMixin class thrown in.
http objects work like html objects, but also support:
For information on special attributes and methods in http objects, refer to Section 2.2.2 above.

2.2.3.5 The httpEmpty Tag Object

This is a subclass of notag and httpMixin.

httpEmpty objects work like http objects, except that their content is initially empty (as opposed to http, whose initial content includes the dtd string, head and body objects.

There are at least two important uses of httpEmpty objects:
If you're sending back anything other than HTML, don't forget to set the .contentType attribute.





3. Examples and Commentaries

These quick and simple examples should be enough to get you started quickly with pyweb

3.1 Minimal Page

Generating an html page simply requires you to create an http object, populate it with data, then invoke its send() method.
The example below is the minimal possible pyWeb CGI program, but it doesn't offer anything really meaningful.
#!/usr/bin/env python
from pyweb import *
http().send()


3.2 Hello, World

This example shows very simply how basic page attributes can be set, and content added.
#!/usr/bin/env python
from pyweb import *

page = http()
page.title = "My first pyWeb page"
page.add("Hello, this is your first pyWeb-generated page")
page.send()


3.3 With a bit of formatting

Here, we instantiate one of the page's tag objects and pass in tag attributes on the fly. Note that we are also creating nested tag objects on the fly:
#!/usr/bin/env python
from pyweb import *

page = http()
page.title="My next pyWeb page"
page.add(h1("My next pyWeb page"))
page.add(h2("A subheading"))
page.add(p("A normal body text paragraph"),
p(attr(style="color:red"),
"Another paragraph in red text"))
page.send()


3.4 Create a Derived Tag Class

Here, we show how you can subclass
#!/usr/bin/env python
from pyweb import *

class colourtext(span):
def __init__(self, colour, *args):
# you need the special keywords 'tagopen' and 'tagclose' to
# make your custom tag object render properly.
span.__init__(self, tagopen="span", tagclose="span")
self.tagopen = 'span'
self.tagclose = 'span'
self.style = "color: %s" % colour
for item in args:
self.add(item)

page = http()
page.title = "My next pyWeb page"
page.add(h1("My next pyWeb page"))
page.add(h2("A subheading"))
page.add(p("A normal body text paragraph"),
p("Second paragraph with different colours: ",
colourtext("red", "red "),
colourtext("#0000C0", "blue "),
br(),
colourtext("#008000", "dark green")))
page.send()


3.5 Access Form Fields, Set/Retrieve Cookies

Without cookies, the capabilities of active server-side programming are dramatically reduced.
Yes, you can embed fields within the page, and munge all the local http hyperlinks to contain state data, but (in my opinion) this is goddam ugly. Even if you just have a 'sessionID' field which is used to access per-user session data in a backend database, you'll still be carrying needless complexity in your page generation code.

pyWeb makes it easy and comfortable to write to, and read from, cookies that are stored in the client browser.
You can safely assume that most users allow cookies that are targetted at the site being visited.

Note that pyWeb does not support 'offsite' cookies (storing cookies against other domains, which get sent during hits to those other domains, as in the networks of 'affiliate' advertising/spamming companies). This non-support is a deliberate decision on my part, because I consider this practice to be a highly unethical abuse of visitors' privacy which I do not want to facilitate.
#!/usr/bin/env python

from pyweb import *

page = http()
page.head.title = "pyWeb - Cookies demo"
page.add(h1("This page uses cookies"))
page.add(h2("A subheading"))

# Get username from form fields, if present.
if page.session.fields.has_key('username'):
# retrieve the form field, and stick it in a cookie
username = page.session.fields['username']
page.session.cookies['username'] = username # set the cookie
elif page.session.cookies.has_key('username'):
# not in form fields, but present in cookie
# Never forget to do the '.value' attribute fetch
username = page.session.cookies['username'].value
else:
# not present at all new visit - prompt user to enter their name
username = None

if username:
page.add(p("Welcome back, %s!" % username))
else:
page.add(form(attr(method="POST"),
"Please enter your name: ",
input(attr(type="text", name="username", size=40)),
input(attr(type="submit", value="Send")),
)
)
page.send()

3.6 Manipulate the Persistent Data Store

The persistent data store is superior to cookies in a lot of ways, because:
#!/usr/bin/env python

from pyweb import *

page = http()
page.title = "pyWeb - Using the persistent datastore"
page.add(h1("Persistent Client-Side Datastore Demo"))

# Get username from form fields, if present.
hits = page.session.data.get('hits', 0)

page.add(p("You have visited this page %d times" % hits))
page.session.data.hits = hits + 1

page.send() # the updated data store gets sent back to client browser

3.7 Making Page Objects Addressible

Recall from the description of the add() method above, that you can make tag objects addressible via calls to the save() object constructor.
You pass save() two arguments - a parent object in which to create a ref, and the name of the attribute which serves as the ref.

This way, you can create your own object/attribute hierarchy, which can make code easier to work with by presenting a logical as opposed to physical document object model.

The ability to address page elements at random, together with the ability to build the page on the fly, lend themselves to a lot of flexibility, and the freedom to write intuitive, readable code (or, obscure twisted code full of brain-mangling side-effects, if you hate the world). This freedom, for better and worse, is one of the main underpinnings of satisfaction in coding.

So here's an example of making page elements addressible:
#!/usr/bin/env python

from pyweb import *
page = http()
page.title = "pyWeb - logical object addressing"
page.body.add(h1("Example 7 - addressing tag objects logically"),
# makes this tag addressible as 'page.firstpara'
p(save(page, "firstpara"),
"This is the first sentence of firstpara."),
# make this next tag addressible as 'page.secondpara'
p(save(page, "secondpara"),
"This is the first sentence of secondpara."),
)

# with the objects thus 'tagged', you can address them willy-nilly
page.secondpara.add(" This is the second sentence of secondpara.")
page.firstpara.add(" This is another sentence for firstpara.")

page.send()

3.8 A More 'Physical' Way to Mark and Locate Tag Objects

As  you can see in the previous example, when you instantiate tag objects 'on the fly', you can make them 'addressible' by including save(parentobj, name) calls in the constructor, or in add() calls, which causes a ref to the object to be saved in any ancestor as a named attribute.

There's an alternative way to make tag objects addressible. You can do it via 'tagid()' object creator calls.

Advantages:
Disadvantages:
As you can see, the 'tagid()' approach promotes a very physical view of the code, while the 'save()' approach promotes a more logical view.
So here's an example of physical tag object addressing. Just for fun and learning, we wrap the whole thing in a class, as a kind of 'template':
#!/usr/bin/env python

from pyweb import *

class swallowDataPage(http):
def __init__(self, coconuts):
http.__init__(self)
self.title = "pyWeb - addressing physically"
self.body.add(h1("Physical object addressing"),
# outer paragraph
p(tagid('para1'),
"An African Swallow can carry ",
span(tagid('num_coconuts')),
" coconuts"))

# go down the ancestry tree to add content
self.body.para1.num_coconuts.add("0.715")

page = swallowDataPage("0.715")
page.send()


3.9 Using a Back-End Database

pyWeb doesn't directly support database back-ends, but they're not unduly difficult to add and weave in to your code.
This example shows pyWeb being used to run a database table editor page. Very basic, just editing a table with the fields "First Name", "Last Name" and "City". You have the option to create new rows, edit and delete existing rows.

Note that this example uses the Metakit Plus database API (which itself uses the Metakit Python API).
If you haven't heard of Metakit before, do please check it out - it's a very Pythonic DBMS with relational and OO aspects. After trying it ou, you might well give up SQL.

Also, I've chosen Metakit for my own server-side databases because it is easy to install and deploy on budget commercial web hosts that only have Python 1.5.2 with no SQL interface module.

Conversion of this example program to one which uses SQL is an exercise left to the (determined or masochistic) reader.

One last comment - note that the mainline code below is enclosed in a try... except block. As code complexity grows, so does the possibility of exceptions.
#!/usr/bin/env python

"""
example9a.cgir

Demonstrates pyWeb using a database back-end.

This version uses the 'Metakit' database (http://www.equi4.com/metakit),
an increasingly popular alternative to traditional SQL-based databases.

The 'metakitplus' module is my own enhancement wrapper, which you can
get from http://www.freenet.org.nz/python/metakit
"""

from pyweb import *
import metakitplus

# function to create an edit/create form object
def editform(row, first, last, city):
# set row to -1 for creating a new row
return form(attr(method="POST"),
table(attr(cellspacing=0, cellpadding=5, align='center'),
tr(td(attr(align="right"), "First Name:"),
td(attr(align="left"),
input(attr(name='first', type='text',
value=first, size=64, maxlen=64)))),
tr(td(attr(align="right"), "Last Name:"),
td(attr(align="left"),
input(attr(name='last', type='text',
value=last, size=64, maxlen=64)))),
tr(td(attr(align="right"), "City:"),
td(attr(align="left"),
input(attr(name='city', type='text',
value=city, size=64, maxlen=64))))),
center(input(attr(type="submit", name="op", value="Save")),
input(attr(type="submit", name="op", value="Cancel"))),
input(attr(type="hidden", name="row", value=row)))

# function to display the contents of our db table in an html table
def displaytable(view):
t = table(attr(cellspacing=0, cellpadding=5, align="center", border=1),
tr(td(attr(colspan=4, align="center"), h3("Table contents"))),
tr(td(b("First Name")),
td(b("Last Name")),
td(attr(colspan=2), b("City"))))
nitems = len(view)
for idx in range(nitems):
item = view[idx]
t.add(tr(td(item.first), td(item.last), td(item.city),
td(form(attr(method="POST"),
input(attr(type="submit", name="op", value="Edit")),
input(attr(type="submit", name="op", value="Delete")),
input(attr(type="hidden", name="row", value=idx))
))))
t.add(tr(td(attr(colspan=4, align="center"),
form(attr(method="POST"),
input(attr(type="submit", name="op", value="Create New"))))))
return t

# declare table structure
dbfile = "example9.db"
tablename = "people"
format = "first:S,last:S,city:S"
schema = "%s[%s]" % (tablename, format)

# it's always wise to put your entire script in a try..except block so you
# can catch anything that goes wrong
try:
# open database
try:
db = metakitplus.storage(dbfile, 1)
except:
page = http()
page.title = "pyWeb example - error"
page.add(h1("pyWeb Example 9 - error"),
p("We can't open/create the metakit database file '%s'" % dbfile),
p("Please make the necessary changes to your directory and try again"))
page.send()
sys.exit(0)

# Open table if it exists, otherwise create it
if tablename in db.tables:
view = db.view(tablename)
else:
view = db.getas(schema)

# Create page object and fill in the basics
page = http()
page.title = "pyWeb database access"
page.bgcolor = "black"

# format it as a table within a table.
# if you don't like it, track me down and shoot me :)
page.add(table(attr(bgcolor="white", width="100%", height="100%",
cellspacing=0, cellpadding=5),
tr(td(attr(align="center", valign="middle"),
h1("Example 9a - using Metakit database"),
table(attr(bgcolor="#ffffe0", width="90%", height="90%",
cellspacing=0, cellpadding=5),
tr(td(save(page, 'table'),
attr(align="center", valign="middle"),
)))))))

# Decide action based on form variables
fields = page.session.fields # easier shorthand
op = fields.get('op', 'none')
if op in ['Edit', 'Delete', 'Save']:
try:
row = int(fields.row)
except:
row = -1

# Take required action
if op in ['new', "Create New"]:
# creating a new record
page.table.add(editform('', '', '', ''))

elif op == 'Edit':
# select a row for editing
page.table.add(editform(row,
view[row].first,
view[row].last,
view[row].city))

elif op == 'Save':
# saving data from a new or edit form
if row == -1:
# Save a whole new record
view.append(first=fields.get('first', ''),
last=fields.get('last', ''),
city=fields.get('city', ''))
page.table.add("New record added successfully",
displaytable(view))
elif row >= 0 and row < len(view):
# saving previously edited data
thisrow = view[row]
thisrow.first = fields.get('first', '')
thisrow.last = fields.get('last', '')
thisrow.city = fields.get('city', '')
page.table.add("Changes saved successfully",
displaytable(view))
else:
page.table.add("Invalid row: %d" % row,
displaytable(view))

elif op == 'Delete':
if row >= 0 and row <= len(view):
view.delete(row)
status = "Item deleted successfully"
else:
status = "Invalid row '%s'" % row
page.table.add(status,
displaytable(view))

# if table is empty, prompt to add content
else:
if len(view) == 0:
page.table.add(p("The database table is presently empty"),
form(attr(method='POST'),
input(attr(type='hidden', name='op', value='new')),
input(attr(type='submit', name='button',
value="Create New Record"))))
else:
page.table.add(displaytable(view))

# All done (hopefully)
db.commit(1)
page.send()

except:
# Log the exception to a log file
print_exception("example9.err")


Introduction | Installation | DOM | Examples | Class Reference