Divmod : Mantissa : Wiki Tutorial

This is more a quick walkthrough, than a real tutorial. If you feel like it, please add some more explanations.

Please let me know if you know how to solve some of the steps more elegantly. Other comments are also welcome, of course.

-- Karl Bartel <karlb@gmx.net>

Note: this walkthrough relies on axiomatic project. However, axiomatic project is currently broken. See #911 for progress on this issue.

-- Jean-Paul Calderone <exarkun@divmod.com>

Things to learn in this Tutorial:

  • Setting up a Mantissa server
  • Serving simple dynamic pages
  • Using public pages
  • Manipulating your database from the python shell
  • Reading parameters passed from the browser

Part 1: Creating an Offering and Running it in a Mantissa Server

Create Project Skeletion

$ axiomatic project -n wiki

Set up Database and Mantissa Server

$ cd Wiki
$ axiomatic -d wikitut.axiom mantissa

Add a public page

The project skeleton's start page is designed to be viewed only by users who have a login for our mantissa server. We don't want to deal with the login system in this tutorial, so let's change the WikiStart object to a PublicPage in wiki/wiki_model.py.

  • Change ixmantissa.INavigableElement to ixmantissa.IPublicPage both in the "implements" call and in the "installOn" method
  • Remove the "getTabs" and "explode" methods and the registerAdapter line below the class
  • add the following method:
            def getResource(self):
                return WikiView(self)
    

Because WikiStart is defined as powerup in our Offering(xmantissa/wikioff.py), an instance of this class will be added to our application store, when our app is added to the mantissa server.

Add Project to Mantissa Server

  • Run the mantissa server
    $ axiomatic -d wikitut.axiom start -n
    
  • Go to http://localhost:8080/login and log in as admin
  • Navigate to Home->Offerings and click on the "Wiki" offering to install it. If you can't see an offering named "Wiki", your probably need to get your project dir onto your PYTHONPATH.

From now on our WikiStart object can be viewed publicly at http://localhost:8080/Wiki. Unfortunately, we haven't written a nice view for our object, yet. This makes the Mantissa server display a nice backtrace.

Part 2: Adding a View using a Template

Add a View

Replace your WikiView in wiki/wiki_view.py with

from nevow import loaders
from nevow.rend import Page

class WikiView(Page):

    docFactory = loaders.xmlfile('wiki/themes/base/wiki-start.html')

    def __init__(self, original):
        Page.__init__(self)
        self.original = original

    def customizeFor(self, avatar):
        return self

    def render_page(self, context, date):
        return 'test'

Now you can view the rendered version of the wiki/themes/base/wiki-start.html template when you visit http://localhost:8080/Wiki. Well, at least if you restart your mantissa server, so that it can have a look at the changed python files.

Write a useful Template

Replace wiki-start.html with a template which let's our view do something. For instance

<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:nevow="http://nevow.com/ns/nevow/0.1">
	<body>
	    Welcome to the Wiki Application
		<hr/>
		<nevow:invisible nevow:render="page" />	
	</body>
</html>

This template call out view's render_page function. Currently, it only returns 'test', but that will change soon.

Part 3: Storing and Retrieving Wiki Pages

Create a New Item Subclass

Add this to your wiki_model.py

class WikiPage(Item):
    implements(ixmantissa.INavigableElement)

    schemaVersion = 1            # First version of this object.
    typeName = 'wiki_page'       # Database table name.

    page_name = text()
    page_content = text()

Change our Application Store from the Python Shell

  • Open application store and do some imports
    >>> from axiom.store import Store
    >>> s = Store('wikitut.axiom/files/app/Wiki.axiom')
    >>> from wiki.wiki_model import WikiPage 
    
  • Create FrontPage and PageTemplate
    >>> WikiPage(store = s, page_name=u'FrontPage', page_content=u'This is the FrontPage.\nMantissa is *cool*. There might also be OtherPages and MoreStuff.')
    >>> WikiPage(store = s, page_name=u'PageTemplate', page_content=u'This page does not exist, yet. Use the edit link below to create it.')
    

Retrieve a Wiki Page and Display its Content

Replace the render_page method of our WikiView class with somthing more advanced:

        from wiki.wiki_model import WikiPage
        args = inevow.IRequest(context).args
        page_name = unicode(args.get('page', ['FrontPage'])[0])
        page = self.original.store.findFirst(WikiPage, WikiPage.page_name==page_name)
        
        if page == None:
            page = self.original.store.findFirst(WikiPage, WikiPage.page_name==u"PageTemplate")
        
        return page.page_content

For this you'll need to import inevow from nevow. Now have a look at our application again:

Part 4: Rendering the WikiPage as a Fragment

Create a New View

Add the following to your wiki_view.py (NOTE: You may need to install python-docutils module)

import re
re_wiki_word = re.compile(r'\b([A-Z]\w+[A-Z]+\w+)')
from nevow import tags
from nevow.rend import Fragment
base_url = 'Wiki?page='

class WikiPageView(Fragment):
    implements(ixmantissa.INavigableFragment)
    docFactory = loaders.xmlfile('wiki/themes/base/wiki-page.html')

    def wikified(self):
        # add some nice formatting
        from docutils.core import publish_parts
        text = publish_parts(self.original.page_content, writer_name="html")["html_body"]
        # place links on all WikiWords
        text = re_wiki_word.sub(r'<a href="%s\1">\1</a>' % base_url, text)
        return tags.xml(text)

    def render_page_content(self, context, data):
        return self.wikified()

    def render_page_name(self, context, data):
        return self.original.page_name

    def render_page_link(self, context, data):
        args = inevow.IRequest(context).args
        return base_url + args.get('page', ['FrontPage'])[0]

Add a New Template

In the new WikiPageView, we are using the template 'wiki/themes/base/wiki-page.html'. Create the file with the following content:

<div xmlns="http://www.w3.org/1999/xhtml"
     xmlns:nevow="http://nevow.com/ns/nevow/0.1">
	<h1><span nevow:render="page_name" /></h1>
	<hr/>
	<p><span nevow:render="page_content" /></p>
	<hr/>
	<a>
		<nevow:attr name="href">
			<nevow:invisible nevow:render="page_link" />&amp;edit=1
		</nevow:attr>
		Edit this page
	</a>
</div>

Putting the Fragment in the WikiView

  • Register WikiPageView as an adapter for WikiPage by adding the following lines to the end of your wiki_model.py:
    from wiki.wiki_view import WikiPageView
    registerAdapter(WikiPageView, WikiPage, ixmantissa.INavigableFragment)
    
  • Replace the return statement in your WikiView?'s render_page method with
            return ixmantissa.INavigableFragment(page)
    

This adapts the WikiPage to the INavigableFragment interface by creating a WikiPageView instance, which implements this interface. This new instance will have a reference to the WikiPage in it's "original" attribute.

And again, it's time to see what our app can do now. It has some nice formatting possibilities and it automatically links WikiWords. Great!

Part 5: Editing

Change WikiPageView?'s render_page method to this:

    def render_page_content(self, context, data):
        args = inevow.IRequest(context).args
        if args.get('page_content', [0])[0]:
            if self.original.page_name != args['page'][0]:
                from wiki.wiki_model import WikiPage
                self.original = WikiPage(
                    store = self.original.store,
                    page_name = unicode(args['page'][0]),
                    page_content = unicode(args['page_content'][0]),
                )
            else:
                self.original.page_content = unicode(args['page_content'][0])
        
        if args.get('edit', [0])[0]:
            link = base_url + args['page'][0]
            return [
                tags.form(action=link, method='post')[
                    tags.textarea(name='page_content', cols='80', rows=50)[
                        self.original.page_content
                    ],
                    tags.input(type='submit', value='save changed page')
                ]
            ]
        else:
            return self.wikified()

Voila!

jethro@divmod.org