Copyright © 2002 by Jason D. Hildebrand
Thanks to the Webware developers. Much of the information in this paper was drawn from
There are several additional kits which this paper does not address. The best way to become familiar with them is to visit the Webware Home Page.
The PSP kit allows the user to write HTML pages with embedded Python code in a fashion similar to PHP, but with some key advantages.
UserKit supports users stored in files or using MiddleKit, and operations such as login, logout, listing logged-in users and check user roles/permissions. This kit is still in alpha, but may be used by early adopters.
TaskKit provides a framework for the scheduling and management of tasks which can be triggered periodically or at specific times.
FunFormKit is a package for generating and validating HTML forms
FormKit is a newly released framework which offers some improvements over the FunFormKit
Cheetah is a python-powered template engine which integrates tightly with Webware
Servlets stay resident in memory and may be re-used for many requests
Database connections may be maintained, eliminating connection setup/teardown times
The web server and application server may be run on different machines to increase performance
The following diagram illustrates the relationship between the webserver and the application server:
Note: If your site contains a lot of static content (i.e. images), the web server may be used to serve such content directly, since it can do this more efficiently than the application server.
The latest release can be downloaded from http://webware.sourceforge.net or you can use CVS to get the latest development version.
python bin/MakeAppWorkDir.py /path/to/workdirwhich creates the following directory structure:
workdir/ Cache/ used by Webware Cans/ ??? Configs/ Application.config edit these to alter your configuration AppServer.config ErrorMsgs/ Webware stores error messages here Logs/ Webware stores logs here MyContext/ Sample context data (i.e. Servlets) is placed here; you can modify it to create your own application Sessions/ Session data is stored here AppServer Starts the appserver on Unix AppServer.bat Starts the appserver on Windows Launch.py Used by AppServer[.bat] NTService.py Win NT/2000 Service version of AppServer WebKit.cgi install in your cgi-bin dir OneShot.cgi install in your cgi-bin dir to use One-shot mode
The application server's namespace is divided into contexts. For example, a request for
http://www.mydomain.org/cgi-bin/WebKit.cgi/MyContext/Hellowill cause the application server to look in the MyContext context for a servlet called Hello. The current implementation of a context is a directory, so the application server will look for the file MyContext/Hello relative to your working directory.
It is possible to define a default context in the Application.config file. If this default is defined and the request does not include a context, the application server will check for the servlet in the default context.
http://www.mydomain.org/cgi-bin/WebKit.cgi/MyContext/Main
Important: Don't include the ".py" extension in URLs. The application server knows to add the extension when looking for servlets. Additional Webware kits may be installed to handle other types of files, too, so you could later switch to some templating system which uses a different extension without having to change all your URLs. In general, it is better to avoid exposing any particular technology in your URLs, since this means that switching to a different technology will force you to change them.
Note: See the Webware documentation for more detailed installation instructions.
We'll start with a simple servlet as an example:
from WebKit.Page import Page class HelloWorld(Page): def writeContent(self): self.writeln('Hello, world!')
Note: This assumes that the person doing the site design is familiar with programming, or can at least work closely with the programmer to divide sections of the HTML page into logical units (menu, content, etc.). If this is not the case, you may want to investigate templating technologies such as PSP and/or Cheetah, possibly in combination with servlets.
Here is a simple example which demonstrates the use of the request fields and actions:
from WebKit.Page import Page ValidationError = 'ValidationError' class Test(Page): def writeContent(self,msg=''): self.writeln(''' %s<BR> <form method="Post" action="Test"> <input type="text" name="value1"> <input type="text" name="value2"> <input type="submit" name="_action_add" value="Add"><input type="submit" name="_action_multiply" value="Multiply">
''' % msg ) def actions(self): return Page.actions(self) + ["add", "multiply"]
def validate(self): req = self.request() if not req.field('value1') or not req.field('value1'):
raise ValidationError, "Please enter two numbers." try: value1 = float(req.field('value1'))
value2 = float(req.field('value2')) except ValueError: raise ValidationError, "Only numbers may be entered." return ( value1, value2 ) def add(self):
try: value1, value2 = self.validate() self.write( "<body>The sum is %f<BR>" % ( value1 + value2 ))
self.write( '<a href="Test">Play again</a></body>') except ValidationError, e: self.writeContent(e) def multiply(self):
try: value1, value2 = self.validate() self.write( "<body>The product is %f<BR>" % ( value1 + value2 )) self.write( '<a href="Test">Play again</a></body>') except ValidationError, e: self.writeContent(e)
Note: Both GET and POST variables are accessed through the same functions.
self.response().setCookie(name,value)to set the value of a named cookie,
self.request().hasCookie(name)to check if a cookie is available, and
self.request().cookie(name)to retrieve its value. Check the Webware Wiki for examples on how to set permanent cookies.
Warning |
There is a security consideration if you enable UseAutomaticPathSessions. If you provide external links from your website to other websites, these websites will be able to see your session ids in the HTTP_REFERER field (this field is passed by most browsers and contains the URL from which the link was activated). One way around this is to avoid using any direct external links. Replace external links with links to a redirect servlet, passing the target URL in the query string. The redirect servlet should then use an HTTP redirect to cause the user's browser to go to the external URL. |
self.session().setValue(name,value)to set a value,
self.session().hasValue(name)to test if a value has been set, and
self.session().value(name)to retrieve the value of a session variable.
Tip: Session variables are not limited to simple variables. You can store sequences and dictionaries, too, or complicated nested data structures. In general, using the current session implementation, you can store any data which can be pickled by the standard Python module cPickle.
Middle/ BookStore.mkmodel/ contains model information Classes.csv class definitions Samples.csv sample data to be inserted into database Settings.config config settings (see MiddleKit docs) __init__.py create this empty file so that the directory can be used as a Python package
We start with a few classes to represent employees and customers:
Table 1. Part of Classes.csv
Class | Attribute | Type | Default | isRequired | Min | Max | Extras |
---|---|---|---|---|---|---|---|
Person | isAbstract=1 | ||||||
firstName | string | 1 | 100 | ||||
lastName | string | 1 | 100 | ||||
Employee(Person) | |||||||
id | int | 1 | |||||
Customer(Person) | |||||||
purchased | list of Bought |
Table 2. Another part of Classes.csv
Class | Attribute | Type | Default | isRequired | Min | Max | Extras |
---|---|---|---|---|---|---|---|
Bought | |||||||
customer | Customer | onDeleteOther='cascade' | |||||
item | Item | ||||||
date | Date |
Warning |
MiddleKit does not create back reference attributes automatically; it is up to you to add them. |
Table 3. The last part of Classes.csv
Class | Attribute | Type | Default | isRequired | Min | Max | Extras |
---|---|---|---|---|---|---|---|
Shelf | |||||||
name | string | 1 | 100 | ||||
description | string | 500 | |||||
items | list of Item | ||||||
Item | isAbstract=1 | ||||||
title | string | 100 | |||||
shelf | Shelf | ||||||
purchasedBy | list of Bought | ||||||
price | float | 1 | |||||
inStock | int | 0 | 1 | ||||
Book(Item) | |||||||
author | string | 100 | |||||
numPages | int | ||||||
CompactDisc(Item) | |||||||
artist | string | 100 | |||||
length | int |
Item is an abstract class; it provides a base class for the Book and CompactDisc classes.
To generate the SQL, base classes and skeleton class files from the Model, enter the command
python /path/to/Webware/MiddleKit/Design/Generate.py --db MySQL --model BookStorefrom the Middle directory. MiddleKit will generate the file GeneratedSQL/Create.sql which contains the SQL statements for creating the database schema. With MySQL you can execute this script with the command:
mysql <GeneratedSQL/Create.sql
For each (non abstract) class in the model, MiddleKit will create a base class in the Middle/GeneratedPy directory which contains the attribute accessors, and a subclass in the Middle which you can customize. For example, for the Customer class, MiddleKit generates Middle/GeneratedPy/GenCustomer.py, which you should not edit, and Middle/Customer.py which you are free to customize. If you re-generate the classes, MiddleKit will only overwrite files under Middle/GeneratedPy.
Table 4. Obligatory Python-related Sample Data
Customer objects | |||||
---|---|---|---|---|---|
firstName | lastName | ||||
Terry | Jones | ||||
Employee objects | |||||
firstName | lastName | id | |||
John | Cleese | 1 | |||
Book objects | |||||
author | title | price | numPages | inStock | shelf |
A.E.J. Elliot O.V.E. | 30 Days in the Samarkand Desert | 7.9 | 120 | 0 | Shelf.2 |
I. Gentleman | 101 Ways to Start a Fight | 4.95 | 57 | 0 | Shelf.4 |
Charles Dikkens | Rarnaby Budge | 12.99 | 230 | 0 | Shelf.3 |
Edmund Wells | David Coperfield | 15.79 | 234 | 0 | Shelf.3 |
Olsen | Olsen's Standard Book of British Birds | 10.5 | 157 | 1 | Shelf.1 |
A. Git | Ethel the Aardvark goes Quantity Surveying | 7.5 | 37 | 1 | Shelf.3 |
CompactDisc objects | |||||
artist | title | price | inStock | shelf | |
Mary Queen of Scots | Soothing Sounds of Nature | 12.95 | 1 | Shelf.5 | |
Burma | I panicked | 11.7 | 1 | Shelf.5 | |
Shelf objects | |||||
name | description | ||||
Nature | Books about Nature | ||||
Travel | Travel Literature | ||||
Classics | Famous Works | ||||
Do it Yourself | Try this at home | ||||
Music | |||||
Bought objects | |||||
item | customer | date | |||
Book.6 | Customer.1 | 1967-04-1 |
Note: Objects are assigned ids in order starting from 1. Use these numbers if you need to refer to another object. If you need to refer to a subclass, use the form classname.nn, as in the example.
Normally there is one global object store instance for the entire application. You can create a module called Store.py which creates the object store instance when the module is imported. Since Python will only execute this initialization code once, it will be safe in a multithreaded environment such as WebKit. All other modules (i.e. servlets) which require access to the store simply import Store.py and use the global instance.
import sys sys.path.insert(1, '/path/to/my/app/Middle') from MiddleKit.Run.MySQLObjectStore import MySQLObjectStore store = MySQLObjectStore(user='jdhildeb') store.readModelFileNamed('/path/to/my/app/Middle/BookStore')
store.saveChanges()is called.
Here are some examples of how to fetch Middle Objects.
Fetch all Book and CompactDisc objects and print their titles:
items = store.fetchObjectsOfClass( "Item" ) for item in items: print items.title()
Fetch books whose titles start with 'D':
books = store.fetchObjectsOfClass( "Book" ) books = [ book for book in books if book.title()[0] == 'D' ]
Note: Note that this is quite inefficient if the list of books is large. It is possible to optimize this operation by passing a where clause to the SQL database.
The following example passes an extra "where clause" to the database. This is more efficient; since only relevant objects are returned by the database there is less processing in Python-land.
books = store.fetchObjectsOfClass( "Book", clauses="where title like 'D%'" )
Print a list of all shelves and the items on each shelf:
shelves = store.fetchObjectsOfClass('Shelf') for shelf in shelves: print shelf.name() for item in shelf.items(): print "\t" + item.title()
num = book.serialNum()
Important: Objects are only assigned permanent serial numbers after they have been added to the store. Newly created objects have negative (temporary) serial numbers.
Fetch the book corresponding to a specific serial number:
book = store.fetchObject( "Book", 4 )
When using inheritance it is often convenient to have an object reference which is unique among all objects in the store. MiddleKit has such an id, which is a 64-bit long integer (the high 32 bits are the class id, and the low 32 bits are the serial number):
print book.sqlObjRef()
Note: These 64-bit object references are also used internally to represent object references. If you are debugging a MiddleKit application you'll find it difficult to figure out which object is being referenced until you decode the number into its components (class id and serial number).
If you have a sqlObjRef, it is easy to fetch the corresponding object:
obj = store.fetchObjRef( objref )
Tip: It is possible to pass each of these fetch methods a default value to return if the specified object is not found. Where class names are passed, it is also possible to pass the Python class itself, instead of the class name as a string. You'll find more shortcuts and niceties if you browse through Webware/Middle/Run/SQLObjectStore.py.
In this example, we add a new shelf, and then create a new CD to put on the shelf:
import sys,os sys.path.insert(1, '/path/to/my/app/Middle') from CompactDisc import CompactDisc from Shelf import Shelf shelf = Shelf() shelf.setName('Comedy') store.addObject(shelf) cd = CompactDisc() cd.setTitle('And Now for Something Completely Different') cd.setArtist('Monty Python') cd.setPrice(10.78) shelf.addToItems(cd) store.saveChanges()
Note: It is not necessary to call store.addObject(cd). The new shelf was added to the store, and so anything added to the shelf will be automatically added to the store, too. However, it doesn't hurt if you add it explicitly.
Important: Don't forget to call store.saveChanges() to make your changes persistent.
Warning |
it is important to have your Middle directory (the one containing your Middle classes) in your Python path, and not to access the modules as (for example) Middle.Shelf. If a module is imported via two different paths, comparisons for classes may not produce the correct results, which can lead to runtime exceptions. |
To modify an objects, simply fetch it and change its attributes. Don't forget to save the changes.
# inflation items = store.fetchObjectsOfClass("Item") for item in items: item.setPrice( item.price() * 1.07 ) store.saveChanges()
For objects which are not referenced by any other objects in the store, deletion is straightforward:
book = store.fetchObject('Book',1) store.deleteObject( book ) store.saveChanges()
As a developer, you have a few ways to delete such objects. You can
customer = store.fetchObject('Customer', 1 ) store.deleteObject( customer ) store.saveChanges()
The Webware for Python home page is at http://webware.sourceforge.net
The webware Wiki page contains many useful tips and examples which have not yet been included in the documentation. It can be found at http://webware.colorstudy.net/twiki/bin/view/Webware
Join the webware-discuss mailing list. Instructions are on the Webware home page.