Version 0.8.1, Webware for Python
We present a tutorial on making a Webware script, and some guidance on turning that into a web application.
This document does not cover the basic installation. See the Install Guide -- you should be able to open up the Examples context in Webware before you continue.
We'll first set up a directory dedicated to your application. Run this command:
$ cd ~ $ python /path/to/Webware/bin/MakeAppWorkDir.py -l --cvsignore \ -c context WebwareTest
You'll now have a directory WebwareTest in your home directory (or of course you can put it in some other location). Inside this directory will be several subdirectories and a couple files. The only file we'll worry about is AppServer (or AppServer.bat if you're on Windows). The directories of interest are context (that you specified with -c context) where you'll be putting your servlets; Configs that holds some configuration files; and lib where you can put your non-servlet code.
For more information about the working directory and setting up the file structure for your application, see Application Development.
For the most part the configuration is fine, but we'll make a couple changes to make it easier to develop. For more information on configuration see the Configuration Guide.
In the file AppServer.config, change this line:
# Original (default setting): 'AutoReload': 0, # To: 'AutoReload': 1,
This will cause the AppServer to restart if any loaded files are changed -- without this you may edit a file and your application won't see the updated version until you manually restart the AppServer.
The other change you may want to make to allow you to use more interesting URLs. In Application.config:
# Original (default setting): 'ExtraPathInfo': 0, # To: 'ExtraPathInfo': 1,
Otherwise the settings should be appropriate for development. (There are several you would want to change before deploying the application in a production environment).
Webware's core concept for serving pages is the servlet. This is a class that creates a response given a request.
The core classes to understanding the servlet are Servlet, HTTPServlet, and Page. Also of interest would be the request (Request and HTTPRequest) and the response (Response and HTTPResponse) -- the HTTP- versions of these classes are more interesting. There is also a Transaction object, which is solely a container for the request and response.
While there are several levels you can work on while creating your servlet, in this tutorial we will work solely with subclassing the Page class. This class defines a more high-level interface, appropriate for generating HTML (though it can be used with any content type). It also provides a number of convenience methods.
Each servlet is a plain Python class. There is no Webware magic (except perhaps for the level one import module based on URL spell). PSP has more magic, but that's a topic for another day. (@@ ib: link to PSP doc) An extremely simple servlet might look like:
from WebKit.Page import Page class MyServlet(Page): def title(self): return 'My Sample Servlet' def writeContent(self): self.write('Hello world!')
This would be placed in MyServlet.py. Webware will create a pool of MyServlet instances, which will be reused. Servlets "write" the text of the response (like self.write("some text")). Webware calls the servlet like this:
You only have to override the portions that you want to. It is not uncommon to only override the writeContent() method in a servlet, for instance.
You'll notice a file context/Main.py in your working directory. You can look at it to get a feel for what a servlet might look like. (As an aside, a servlet called Main or index will be used analogous to the index.html file). You can look at it for a place to start experimenting, but here we'll work on developing an entire (small) application, introducing the other concepts as we go along.
If you look online, you'll see a huge number of web applications available for an online photo album. The world needs one more!
You will need the Python Imaging Library (@@ ib: link) installed for this example. First we'll use it to find the sizes of the images, and later we will use it to create thumbnails.
We'll develop the application in iterations -- the iterations are numbered in WebKit/Examples/PhotoAlbum/VersionX.
For the first iteration, we'll display files that you upload by hand. We do this with two servlets -- one to show the entire album, and another for individual pictures. First, the entire album:
from WebKit.Page import Page import os import Image from urllib import quote as urlEncode dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pics") class Main(Page): def title(self): # It's nice to give a real title, otherwise "Main" would # be used. return 'Photo Album' def writeContent(self): # We'll format these simpy, one thumbnail per line. for filename in os.listdir(dir): im = Image.open(os.path.join(dir, filename)) x, y = im.size # Here we figure out the scaled-down size of the image, # so that we preserve the aspect ratio. We'll use fake # thumbnails, where the image is scaled down by the browser. x = int(x * (100.0 / y)) y = 100 # note that we are just using % substitution to generate # the HTML. There's other ways, but this works well enough. # We're linking to the View servlet which we'll show later. # Notice we use urlEncode -- otherwise we'll encounter bugs # if there's a file with an embedded space or other character # in it. self.write('<p><a href="View?filename=%s">' % urlEncode(filename)) self.write('<img src="../pics/%s" width="%i" height="%i">' % (urlEncode(filename), x, y)) self.write('</a></p>\n')
The servlet View takes one URL parameter of filename -- you can get the value of a URL parameter like self.request().field('filename') -- or, if you want a default value, you can use self.request().field('filename', defaultValue). In the likely case you don't want to write self.request() before retrieving each value, do:
req = self.request() self.write(req.field('username')) # Even more compactly: field = self.request().field self.write(field('username'))
The individual images are viewed with this servlet:
from WebKit.Page import Page import os import Image from urllib import quote as urlEncode dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pics") class View(Page): def title(self): return 'View: %s' \ % self.htmlEncode(self.request().field('filename')) def writeContent(self): filename = self.request().field('filename') im = Image.open(os.path.join(dir, filename)) self.write('<center>') self.write('<img src="../pics/%s" width="%i" height="%i">' % (self.urlEncode(filename), im.size[0], im.size[1])) self.write('<br>\n') self.write(filename) self.write('<p>\n') self.write('<a href="./">Return to Index</a>') self.write('</center>')
That was fairly simple -- but usually you want to upload files, potentially through a web interface. Along the way we'll add thumbnail generation using PIL, and slighly improve the image index.
We'll generate thumbnails kind of on demand, so you can still upload files manually -- thumbnails will be put in pics/thumbs/ and have -tn appended just to avoid confusion:
from WebKit.Page import Page import os import Image from urllib import quote as urlEncode dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pics") class Main(Page): def title(self): # It's nice to give a real title, otherwise "Main" would # be used. return 'Photo Album' def writeContent(self): # We'll format these in a table, two columns wide self.write('<table width="100%">\n') # col will be 0 or 1, depending if we're on the left or # right column. col = 0 filenames = os.listdir(dir) # We'll sort the files, case-insensitive filenames.sort(lambda a, b: cmp(a.lower(), b.lower())) for filename in filenames: if not col: # right column self.write('<tr>') thumbFilename = os.path.splitext(filename)[0] + "-tn.jp" if not os.path.exists(os.path.join(dir, "thumbs", thumbFilename)): # No thumnail exists -- we have to generate one im = Image.open(os.path.join(dir, filename)) im.thumbnail((250, 100)) # @@ ib: handle GIFs im.save(os.path.join(dir, "thumbs", thumbFilename)) else: im = Image.open(os.path.join(dir, "thumbs", thumbFilename)) # note that we are just using % substitution to generate # the HTML. There's other ways, but this works well enough. # We're linking to the View servlet which we'll show later. # Notice we use urlEncode -- otherwise we'll encounter bugs # if there's a file with an embedded space or other character # in it. self.write('<td><a href="View?filename=%s">' % urlEncode(filename)) self.write('<img src="../pics/%s" width="%i" height="%i">' % (urlEncode(filename), im.size[0], im.size[1])) self.write('<br>Filename: %s<br>Size: %i' % (self.htmlEncode(filename), os.stat(os.path.join(dir, filename)).st_size)) self.write('</a></td>\n') if col: self.write('</tr>\n') col = not col self.write('</table><p>\n') self.write('<center><a href="Upload">Upload an image</a></center>')
The View servlet we'll leave just like it was. We'll add an Upload servlet. Notice we use enctype="form/multi-part" in the <form> tag -- this is an HTMLism for file uploading (otherwise you'll just get the filename and not the file contents). We also add a hidden field doUpload which tells us that the form has been submitted. Finally when the form is finished we redirect them to the viewing page (self.response().sendRedirect(url)):
from WebKit.Page import Page from urllib import quote as urlEncode dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pics") class Upload(Page): def writeContent(self): if self.request().field('doUpload', 0): self.doUpload() return self.write(''' Upload your image:<br> <form action="Upload" method="POST" enctype="form/multi-part"> <input type="hidden" name="doUpload" value="yes"> <input type="file" name="imageFile"> <input type="submit" value="Upload"> </form> ''') def doUpload(self): file = self.request().field('imageFile') # Because it's a file upload, we don't get a string back. # So to get the value we do this: contents = file.value filename = file.filename f = open(os.path.join(dir, filename), 'w') f.write(contents) f.close() self.response().sendRedirect('View?filename=%s' % urlEncode(filename))