Programming - - By Harry Fuecks

a simple wiki with web.py

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.

Sponsors