a simple wiki with web.py

Tweet

Ran into web.py here, while it was still unreleased and got hooked by the API design and the comment Aaron made here

The third principle is that web.py should, by default, do the right thing by the Web. This means distinguishing between GET and POST properly. It means simple, canonical URLs which synonyms redirect to. It means readable HTML with the proper HTTP headers.

Since then web.py has been released with an initial reaction here – agree with those remarks so don’t need to repeat.

More interesting was hacking something together with it – a very simple wiki which took about 2 hours to get to where it is (below), while reading the docs and tutorial. Note this isn’t pretty code – the HTML in embedded directly, violating Aaron’s principle (didn’t want to have to mess with Cheetah as well), plus my Python skills are not the greatest but perhaps it makes a useful beginners example.

To install and run (Linux) to a file like wiki.py then do the following;


$ wget http://webpy.org/web.py
$ wget http://webpy.org/markdown.py
$ mkdir pages
$ chmod +x wiki.py
$ ./wiki.py

Then point your browser at http://localhost:8080/page/somepage to get started

The code;


#!/usr/bin/python

import web
from markdown import Markdown
import os, time, re, cgi

# For debugging use only
web.internalerror = web.debugerror

urls = (
    '/', 'WikiPages',
    '/page/([a-zA-Z_]+)', 'WikiPage',
    '/editor/([a-zA-Z_]+)', 'WikiEditor'
)

wikidir = os.path.realpath('./pages')

class WikiPages:
	
	def GET(self):
		web.header("Content-Type","text/html; charset=utf-8")
		t = re.compile('^[a-zA-Z_]+$')
		wikipages = os.listdir(wikidir)
		print "<html><head><title>wiki pages</title></head><body>"
		print "<h1>Wiki Pages:</h1><ul>"
		for wikipage in wikipages:
			if os.path.isfile(os.path.join(wikidir, wikipage)) and t.match(wikipage):
				print "<li><a href="%(path)s/page/%(page)s">%(page)s</a></li>" 
					% {'path':web.ctx.home+web.ctx.path[1:],'page':wikipage}
		print "</ul></body></html>"

class WikiPage:
	
	def GET(self, name):
		page = os.path.join(wikidir,name)
		web.header("Content-Type","text/html; charset=utf-8")
		if os.path.exists(page):
			print "<html><head><title>%s</title></head><body>" % name
			print "<h1>%s</h1>" % name
			print "<p>"
			print "[<a href="%s">Pages</a>] " 
					% (web.ctx.home+"/")
			print "[<a href="%s">Edit</a>] " 
					% (web.ctx.home+'/editor/'+name)
			print "</p>"
			print Markdown(open(page).read())
			print "</body></html>"
		else:
			web.ctx.status = '404 Not Found'
			print "<html><head><title>Does not exist: %s</title></head><body>" % name
			print "<p>Page %s does not yet exist - " % name
			print "<a href="%s">Create</a>" % (web.ctx.home+'/editor/'+name)
	
	def POST(self,name):
		page = os.path.join(wikidir,name)
		if os.path.exists(page):
			newpage = page+'.'+time.strftime("%Y%m%d%H%M%S", time.gmtime())
			os.rename(page,newpage)
		f = open(page, "w")
		f.write(web.input(page='').page)
		f.close()
		web.redirect(web.ctx.home+'/page/'+name)

class WikiEditor:

	def GET(self,name):
		web.header("Content-Type","text/html; charset=utf-8")
		print "<html><head><title>Editing %s</title></head><body>" % name;
		print "<h1>Editing: %s</h1>" % name
		print "<form method="POST" accept-charset="utf-8" action="%s">" 
			% (web.ctx.home+'/page/'+name)
		print "<textarea name="page" cols="100" rows="20">"

		page = os.path.join(wikidir,name)
		if os.path.exists(page):
			print cgi.escape(open(page).read())
		print "</textarea><br><input type="submit" value="Update"></form>"
		print "<p><a href="http://daringfireball.net/projects/markdown/syntax">Markdown Syntax</a></p>"

		print "</body></html>"
	
if __name__ == "__main__": web.run(urls)

Thoughts on the experience some other time, suffice to say I think Aaron is headed in the right direction.

Note I dumped this under PHP as Sitepoint doesn’t (yet) have a Python category and so as this follows on somewhere from here, figured I stick with the PHP theme.

Update: it needs pointing out there are two issues with the code below.

First, the Markdown parser is not designed to escape HTML special chars, hence the web.safemarkdown() function in web.py. In otherwords the code below exposes the risk of XSS exploits.

Second it should do some kind of file locking while writing to wiki pages.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • k4ml

    interesting … I’ve been waiting for their release actually. now if I can I find something like phphtmllib in python. you have any idea harry ?

  • Bertrand Mansion

    Hi Harry,

    Good to see you are also doing things with Python. I am also learning it at the moment and was interested in web.py as well. Now that it’s been released I don’t find it very impressive, especially the 404 embedded error pages… But I guess it’s just a start.

    I’d be interested to know which way you use to deploy/test your Python web applications : lighttpd and fastcgi ? Do you use a Python library ?

    Thanks for the article.

  • http://www.phppatterns.com HarryF

    interesting … I’ve been waiting for their release actually. now if I can I find something like phphtmllib in python. you have any idea harry ?

    To be frank, I haven’t really done much of anything with Python regarding web apps, aside from some stuff with the XML-RPC server, so I don’t really know much of what’s what in Python for web apps. Python’s had me more interested due to wxPython and the outstanding Win 32 extensions.

    There was template engine I ran into (now lost the name / link) via the daily Python URL which was something like WACT’s template engine – template tags become objects you can interact with but that’s not quite phphtmllib. For anyone who knows Python better, phphtmllib is a library for building HTML (or otherwise) explicitly with objects (no templates involved) – something like DOM for HTML.

    am also learning it at the moment and was interested in web.py as well. Now that it’s been released I don’t find it very impressive,

    This may sound perverse but that’s actually why I think it’s looking good: it’s unimpressive meaning it’s very simple (at least it is right now) but it already “works” and I think the API design is right for putting HTTP resources first – re earlier discussion, the “controller” has taken a less important role in the equation.

    Otherwise think it’s cool that the framework doesn’t “dwarf” the application using the framework. Think that provides a greater incentive to build distributable apps on top of it, with web.py included. A more extreme point of view is web.py should go into the standard Python distribution one day.

    especially the 404 embedded error pages…

    OK they’re not pretty but I think it’s definately the right thing to be doing by having 404 behaviour in there by default. You can probably (just a guess right now – think think this is valid Python) override the default function with your own something like;

    def notfound():
    web.ctx.status = ‘404 Not Found’
    header(‘Content-Type’, ‘text/html’)
    # add stuff here for your own 404 page

    web.notfound = notfound

    I’d be interested to know which way you use to deploy/test your Python web applications : lighttpd and fastcgi ? Do you use a Python library ?

    As said, haven’t done anything serious with Python on the web – Python has it’s own SimpleHTTPServer (and versions of that like a Threaded one for concurrent request handling). web.py seems to be “aware” of lighthttpd and fastcgi via this stuff. More I can’t say there.

    Testing-wise, there’s a bunch of Python unit testers – this is a good place to start it seems and a bunch more “web testers” of which I think all can be found here.

    Regarding distibution, what Python does have is distutils, for making Python modules easy to install (you write a script called setup.py) and I have used py2exe, which is an awesome tool for Python on Windows – converts your Python script(s) into an Windows executable.

    At the moment I’m just in a phase of looking at what different people are doing in different languages, and not sure I’d do anything real with web.py yet. I could argue that in a way, web.py is rediscovering what PHP already does, the URL to class mapping being like PHP under Apache, mapping to PHP scripts on the filesystem, perhaps with some mod_rewrite / mod_alias / mod_actions magic. Of course not many people are writing PHP scripts like this;

    Perhaps we should be?

  • http://www.phppatterns.com HarryF

    More on the FCGI / lightHttpd front: this in an excellent read FastCGI, SCGI, and Apache: Background and Future

  • k4ml

    one thing I like about webpy is it let me learn python the fun way. no complicated setup just to get your database row displayed on the browser. kind of reminds me to the first day I wrote my php script. btw, I’ve created a simple blog in web.py. thanks to aaron for helping me sorting out few problems that I have while on my way doing it.

  • Sam

    You use the url “localhost:8080/pages/somepage” in your example, but your code implies that the url “localhost:8080/page/somepage” should be used. notice the difference (pages vs. page). In the former, you get the default web.py notfound page, in the latter, you get your behavior.

    Thanks for the wonderful example,

    -Sam

  • Olila

    I find Zope 3 to be really really cool..

  • http://www.phppatterns.com HarryF

    You use the url “localhost:8080/pages/somepage” in your example, but your code implies that the url “localhost:8080/page/somepage” should be used. notice the difference (pages vs. page). In the former, you get the default web.py notfound page, in the latter, you get your behavior.

    Thanks – fixed.

    I find Zope 3 to be really really cool..

    Sitepoint really needs a Python blogger – personally have zero experience of Zope but have read other good things about Zope3

  • Pat Kohler

    I have not personally taken the time to go about playing with Python, but there is much noise about Django at http://www.djangoproject.com/ and I have a post-it not telling me about http://www.diveintopython.org that I have not yet looked at either.

    I have though, looked at a video made by a user of Django, Tom Dyson. The video can be found at http://www.throwingbeans.org/django_screencasts.html.

  • Kevin Dangoor

    Regarding templates and phphtmllib: you’d probably like STAN which sounds similar to your description. There’s an example on this page:
    http://www.develix.com/software/

    Regarding distribution: there’s a new package called setuptools that produces “Python Eggs”. Eggs provide good metadata about a package including dependencies. So, users on any platform can run “easy_install

  • Kevin Dangoor

    Ugh. Half of my comment was eaten by angle brackets.

    I was saying that users can easy_install *Package* and get that package and all of its dependencies. This makes it much easier to use other code in your projects without worrying about difficulty installing. setuptools also makes sure that the correct version of a Python package is installed. Linux users get that from their distributions, but Mac and Windows users don’t and setuptools works on all.

    I am the creator of TurboGears, which has gotten very popular since its release (more than 1,000 people on the high-traffic googlegroup). Part of the reason that TurboGears has been successful is that it helps out with many parts of building a web app and uses pre-existing components for the major parts (which is where easy_install has been exceedingly helpful!). It happens that a wiki example is the most popular demo I’ve done for TurboGears:
    http://www.turbogears.org/docs/wiki20/

    This is a great time to be doing web programming in Python, because there are many people focused on making development easier.

  • Pingback: import this. » Blog Archive » Building a simple wiki using web.py

  • Pingback: SitePoint Blogs » More PHP frameworks

  • Pingback: The Unkaizened Life » Blog Archive » links for 2006-03-01

  • Damjan

    wikidir = os.path.realpath(‘./pages’) will not work if you run you application through mod_python/WSGI.. You need something like this:
    realpath = os.path.realpath(os.path.dirname(__file__))
    wikidir = os.path.join(realpath, “pages”)
    This will work if you run it either standalone, or through mod_python.

  • kaktusz

    http://webpy.org/markdown.py is retreiveng quasi empty file with
    markdown moved to
    http://www.freewisdom.org/projects/python-markdown/

  • Kelvin

    You can do this when you host with DreamHost and get $30 off any hosting package with this promo code RFX30.