wiki:WikiTutorial

Writing your first Nagare application

This document explains you how to create a basic wiki app using the Nagare framework.

Prerequisites

You have Nagare installed as explained in QuickStart.

Creating your wiki app

Change your current working directory to a directory where you'd like to store your wiki code, then run the command <NAGARE_HOME>/bin/nagare-admin create-app mywiki on command line.

This will create a mywiki directory in your current directory with the following elements:

mywiki/
    conf/
        __init__.py
        mywiki.cfg
    data/
        __init__.py
    mywiki/
        __init__.py
        models.py
        mywiki.py
    setup.py
    static/
        __init__.py

These files are:

  • __init__.py: Empty files that are used by Python to treat the directories as packages.
  • models.py: File that contains database model description for wiki.
  • mywiki.py: File that contains wiki classes.
  • setup.py: File used by setuptools to package wiki application.
  • mywiki.cfg: wiki configuration file.

Register your wiki application

Change your current working directory to the wiki directory and run the command <NAGARE_HOME>/bin/python setup.py develop on command line.

Presenting your pages

Write a simple Wiki Page

Let's create a simple page and view it in a browser. Open mywiki.py and replace its content by the following:

from nagare import presentation

class Page(object):
    def __init__(self, title):
        self.title = title

@presentation.render_for(Page)
def render(self, h, binding, *args):
    return 'This is page *%s*' % self.title

app = lambda: Page(u'FrontPage')

The @presentation.render_for(Page) decoration associates the view with the python object. Let's check if it worked, run the command <NAGARE_HOME>/bin/nagare-admin serve mywiki --reload, you should see the following output:

Application 'app mywiki' registered as '/mywiki'
serving on http://127.0.0.1:8080

The lightweight web server is up and running, try to visit http://127.0.0.1:8080/mywiki with your browser. You'll see your first wiki page containing "This is page FrontPage".

Hence you launched the server with the option --reload, any modification made in source code will be automatically reloaded.

Write a page that actually does something

To generate HTML with Nagare there is no need for templates, it can be completely forged out of python code. The templating issue will be discussed later. A link is inserted on the page title that will append "clicked" to the pages title:

from nagare import presentation

class Page(object):
    def __init__(self, title):
        self.title = title

    def clickCallBack(self):
        self.title = "%s has been clicked" % self.title

@presentation.render_for(Page)
def render(self, h, binding, *args):
    return ('This is page *',
            h.a(self.title).action(self.clickCallBack),
            '*')

app = lambda: Page(u'FrontPage')

The a tag is generated by the h renderer, by default it uses html namespace. As you can see a method can be hooked to this tag through the use of action.

Once the action clickCallBack is done the current object is automatically rendered with its associated view.

Use different views on the same page

Actually, we did not intend to modify the title by a single but to change the informations shown on the page. To do it we just modify the callback associated with the link by one which change the model. A model, is a parameter passed to @presentation.render_for decoration to qualifiy it. Let's create a "clicked" view:

from nagare import presentation

class Page(object):
    def __init__(self, title):
        self.title = title


@presentation.render_for(Page)
def render(self, h, binding, *args):

    return ('This is page *',
            h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')),
            '*')


@presentation.render_for(Page, model = 'clicked')
def render(self, h, binding, *args):

    return ('Page *',
            h.a(self.title).action(lambda : binding.becomes(self)),
            '* has been clicked')

app = lambda: Page(u'FrontPage')

The model change is achieved by replacing the object planned for rendering binding through the use of becomes method which accepts an optional model keyword.

Using a database

Setup database model

Let's start with a wikipage with two properties:
  • its name
  • its content

Open models.py file and add the following lines:

class PageData(Entity):
    pagename = Field(Unicode(40), primary_key=True)
    pagecontent = Field(Unicode(10*1024))

Model declaration can use any of Elixir or SQLAlchemy syntax.

Setup database connection

We gonna use an SQLite database to store our wikipages.

Open mywiki.cfg and modify [database] section like this:

[database]
activated = on
uri = sqlite:///$here/../data/mywiki.db
metadata = mywiki.models:__metadata__
debug = off

Populate database

Let's prepare some data for our wiki

Open models.py file and add the following lines:

def populate():
    page = PageData()
    page.pagename = u'FrontPage'
    page.data = u'Welcome on my *WikiWiki* !'
    session.save(page)

    page = PageData()
    page.pagename = u'WikiWiki'
    page.data = u'A wiki allows users to create and edit web pages easily.'
    session.save(page)

Open mywiki.cfg and modify [database] section like this:

[database]
activated = on
uri = sqlite:///$here/../data/mywiki.db
metadata = mywiki.models:__metadata__
debug = off
populate = mywiki.models:populate

Create database

Run the command <NAGARE_HOME>/bin/nagare-admin create-db mywiki on command line. The SQLite database now contains our two pages Frontpage and WikiWiki.

Associate database object with our previous page

Now we use our previous Page object to render our page Frontpage data:

...
from models import PageData
...

@presentation.render_for(Page)
def render(self, h, binding, *args):
    page = PageData.get_by(pagename=self.title)
    return ('This is page *',
            h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')),
            '*',
            h.hr, page.pagecontent)
...

Handle wikiwords properly

In Frontage page there is the wiki word WikiWiki. This is the name of the second page previously inserted in our database, let's make the word a link to the WikiWiki page.

Open mywiki.py and replace its content by the following:

...
import docutils.core
import re
wikiwords = re.compile(r'\b([A-Z]\w+[A-Z]+\w+)')
...

class Page(object):
    ...
    def getWikiTaggedContent(self):
        page = PageData.get_by(pagename=self.title)
        if page is None:
            page = PageData(pagename=self.title, pagecontent='')
        content = docutils.core.publish_parts(page.pagecontent,
                                              writer_name='html')['html_body']
        return wikiwords.sub(r'<wiki>\1</wiki>', content)
    ...
...
@presentation.render_for(Page)
def render(self, h, binding, *args):
    pageContent = h.parse_htmlstring(self.getWikiTaggedContent(), fragment=True)[0]
    for node in pageContent.getiterator():
        if node.tag == 'wiki':
            node.replace(h.a(node.text).action(
                    lambda title=unicode(node.text): binding.becomes(Page(title)))
                    )

    return ('This is page *',
            h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')),
            '*',
            h.hr,
            pageContent)
...

Use form to modify pages

To modify our wiki page, we use another object that will handle page updates, let's call it PageEditor. Open mywiki.py and add the following:

...
from nagare.var import Var
...

class Page(object):
    ...
    def edit(self, binding):
        content = binding.call(PageEditor(self))

        if content is not None:
            page = PageData.get_by(pagename=self.title)
            if page is None:
                page = PageData(pagename=self.title, pagecontent='')
            page.pagecontent = content

...
@presentation.render_for(Page)
def render(self, h, binding, *args):
    pageContent = h.parse_htmlstring(self.getWikiTaggedContent(), fragment=True)[0]
    for node in pageContent.getiterator():
        if node.tag == 'wiki':
            node.replace(h.a(node.text).action(lambda title=unicode(node.text): binding.becomes(Page(title))))

    return (h.p(h.a('Edit *',
                    self.title,
                    '* page').action(lambda: self.edit(binding))),
            h.hr,
            pageContent)
...
...
class PageEditor(object):
    def __init__(self, page):
        self.page = page
        self.content = Var()


@presentation.render_for(PageEditor)
def render(self, h, binding, *args):
    page = PageData.get_by(pagename=self.page.title)
    if page is None:
        page = PageData(pagename=self.page.title, pagecontent='')

    return h.form(h.textarea(page.pagecontent, rows='10', cols='60').action(self.content),
                h.br,
                h.input(type='submit', value='Save').action(lambda: binding.answer(self.content())),
                ' ',
                h.input(type='submit', value='Cancel').action(binding.answer))
...

The call/answer mechanism is used to show up the form and handle the posted content. When the call method is used the form is displayed and the edit method is blocked awaiting an answer that should be send through the use of answer method. On every input of a form an action can be hooked. They are called in the input > submit order.

Refactoring

Merge pages into a real wiki app

Open mywiki.py and replace app = ... content by the following:

class Wiki(object):
    pass


@presentation.render_for(Wiki)
def render(self, h, binding, *args):
    page = component.ComponentBinding(Page(u'FrontPage'))
    return (h.div( page ),
            h.hr,
            'View the ',
            h.a('complete list of pages').action(lambda : binding.becomes(self,
                                                                          model = 'all')),)


@presentation.render_for(Wiki, model = 'all')
def render(self, h, binding, *args):
    return h.div(h.ul([h.li(h.a(page.pagename).action(lambda title=page.pagename: binding.becomes(Page(title),
                                                      model = 'all'))) for page in PageData.query.order_by(PageData.pagename)]),
                h.hr,
                h.a('Back').action(lambda : binding.becomes(self)))

app = Wiki

Here, the navigation is done between the wiki and the page, when we click on the link to a page the current component is replaced by the selected page. For example the bottom part with the link complete list of pages has completely disapeared from the HTML page.

Compose components and views

Open mywiki.py and replace the previously inserted code by:

class Wiki(object):
    def __init__(self):
        self.current_page = component.ComponentBinding(Page(u'FrontPage'))


@presentation.render_for(Wiki)
def render(self, h, binding, *args):
    return (h.div( self.current_page ),
            h.hr,
            'View the ',
            h.a('complete list of pages').action(lambda : binding.becomes(self, model = 'all')),)


@presentation.render_for(Wiki, model = 'all')
def render(self, h, binding, *args):
    return h.div(h.ul([h.li(h.a(page.pagename).action(lambda title=page.pagename: self.current_page.becomes(Page(title)))) for page in PageData.query.order_by(PageData.pagename)]),
                h.hr,
                h.a('Back').action(lambda : binding.becomes(self)))

app = Wiki

With this new version we only change the current_page attribute when we click a link to a page. The Wiki application context is preserved, e.g. the bottom part with the link complete list of pages is still there.