Wiki Models with SQLAlchemy
I was working on an example application for the upcoming Werkzeug release and decided to make a wiki for that purpose. (The main reason is that I found the Creoleparser on the pypi index and thought that it would integrate into a genshi powered wiki well)
Now wikis have an interesting data structure. Basically you have pages and revisions, where a revision is bound to exactly one page. Simple to model and my database definition looks like that:
page_table = Table('pages', metadata,
Column('page_id', Integer, primary_key=True),
Column('name', String(60), unique=True)
)
revision_table = Table('revisions', metadata,
Column('revision_id', Integer, primary_key=True),
Column('page_id', Integer, ForeignKey('pages.page_id')),
Column('timestamp', DateTime),
Column('text', String),
Column('change_note', String(200))
)
Very simple schema and you can easily bind it to two classes:
class Revision(object):
def __init__(self, page, text, change_note='', timestamp=None):
if isinstance(page, (int, long)):
self.page_id = page
else:
self.page = page
self.text = text
self.change_note = change_note
self.timestamp = timestamp or datetime.utcnow()
def render(self, request=None):
"""Render the page text into a genshi stream."""
if request is None:
request = get_request()
if request is None:
raise RuntimeError('rendering requires request context')
return parse_creole(request, self.text)
class Page(object):
def __init__(self, name):
self.name = name
@property
def title(self):
return self.name.replace('_', ' ')
Session.mapper(Revision, revision_table)
Session.mapper(Page, page_table, properties=dict(
revisions=relation(Revision, backref='page', order_by=[desc(Revision.revision_id)])
))
Works pretty well but as soon as you start putting your stuff into the template it feels unpythonic :-) You basically have to provide always two objects, the revision and the page. Why not combine that into one model? First I removed my database tables again and combined that into one table but then I found out that you can map joins to classes in SQLAlchemy. That and Python’s ability to do multiple inheritance gives me the ability to combine both tables into one class:
class RevisionedPage(Page, Revision):
pass
Session.mapper(RevisionedPage, join(page_table, revision_table), properties=dict(
page_id=[page_table.c.page_id, revision_table.c.page_id],
))
Very nice for the templates and also easy to query. It looks like a normal python class. I haven’t tried if that also works for write access, but I doubt it. Because of that I have added an exception to the __init__ method to avoid creating pages and revisions via the RevisionedPage object.
The full example can be found in the simplewiki sources.