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 the 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
        app.py
    setup.py
    static/
        __init__.py

These files are:

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

Register your wiki application

Change your current working directory to mywiki 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 app.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

def app():
    return 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 click_callback(self):
        self.title = '%s has been clicked' % self.title

@presentation.render_for(Page)
def render(self, h, binding, *args):
    with h.div:
        h << 'This is page *'
        h << h.a(self.title).action(self.click_callback)
        h << '*'

    return h.root

def app():
    return Page(u'FrontPage')

The a tag is generated by the h renderer wihch is by default the HTML namespace. As you can see a method can be hooked to this tag through the use of .action().

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

Use different views on the same page

Now, instead of changing the title when the link is clicked, we will display the wiki component with an other view.

So, let’s create a new view called “clicked”:

from nagare import presentation

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


@presentation.render_for(Page)
def render(self, h, comp, model):
    with h.div:
        h << 'This is page *'
        h << h.a(self.title).action(comp.becomes, model='clicked')
        h << '*'

    return h.root


@presentation.render_for(Page, model='clicked')
def render(self, h, comp, model):
    with h.div:
        h << 'Page *'
        h << h.a(self.title).action(comp.becomes, model=None),
        h << '* has been clicked'

    return h.root

def app():
    return Page(u'FrontPage')

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):
    page_name = Field(Unicode(40), primary_key=True)
    page_content = 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 its [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():
    PageData(
        page_name=u'FrontPage',
        page_content=u'Welcome on my *WikiWiki* !'
    )

    PageData(
        page_name=u'WikiWiki',
        page_content=u'A wiki allows users to create and edit web pages easily.'
    )

Open mywiki.cfg and modify its [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, comp, *args):
    page = PageData.get_by(page_name=self.title)

    with h.div:
        h << 'This is page *'
        h << h.a(self.title).action(comp.becomes, model='clicked')
        h << '*' << h.hr
        h << page.page_content

    return h.root
...

Handle wikiwords properly

In Frontage page there is the wiki word WikiWiki. This is the name of the second page inserted in our database. Let’s make this word a link to the WikiWiki page.

first install the docutils package with the <NAGARE_HOME>/bin/easy_install docutils command, then open app.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 __init__(self, title):
        self.title = title

        # If the page doesn't exist, create it
        page = PageData.get_by(page_name=self.title)
        if page is None:
            PageData(page_name=title, page_content='')

    def get_wiki_tagged_content(self):
        page = PageData.get_by(page_name=self.title)
        content = docutils.core.publish_parts(page.page_content,
                                              writer_name='html')['html_body']

        return wikiwords.sub(r'<wiki>\1</wiki>', content)
    ...
...
@presentation.render_for(Page)
def render(self, h, comp, model):
    content = h.parse_htmlstring(self.get_wiki_tagged_content(), fragment=True)[0]
    for node in content.getiterator():
        if node.tag == 'wiki':
            node.replace(h.a(node.text).action(
                    lambda title=unicode(node.text): comp.becomes(Page(title)))
                    )

    with h.div:
        h << 'This is page *'
        h <<  h.a(self.title).action(comp.becomes, model='clicked')
        h <<  '*' << h.hr
        h <<  content


    return h.root
...

Use form to modify pages

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

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

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

        if content is not None:
            PageData.get_by(page_name=self.title).page_content = content

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

    with h.div:
        with h.p:
            h << h.a('Edit *', self.title, '* page').action(self.edit, comp)

        h << h.hr
        h << content

    return h.root
...
class PageEditor(object):
    def __init__(self, page):
        self.page = page
        self.content = Var()


@presentation.render_for(PageEditor)
def render(self, h, comp, model):
    page = PageData.get_by(page_name=self.page.title)

    with h.form:
        h << h.textarea(page.page_content, rows=10, cols=60).action(self.content)
        h << h.br
        h << h.input(type='submit', value='Save').action(lambda: comp.answer(self.content()))
        h << ' '
        h << h.input(type='submit', value='Cancel').action(comp.answer)

    return h.root

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 the 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 app.py and replace the app() function by the following:

from nagare import component

...

class Wiki(object):
    pass


@presentation.render_for(Wiki)
def render(self, h, comp, model):
    page = component.Component(Page(u'FrontPage'))

    with h.div:
        h << h.div(page) << h.hr
        h << 'View the '
        h << h.a('complete list of pages').action(comp.becomes, model='all')

    return h.root


@presentation.render_for(Wiki, model='all')
def render(self, h, comp, model):
    with h.div:
        with h.ul:
            for page in PageData.query.order_by(PageData.page_name):
                with h.li:
                    h << h.a(page.page_name).action(lambda title=page.page_name: comp.becomes(Page(title)))
        h << h.hr
        h << h.a('Back').action(comp.becomes, model=None)

    return h.root

app = Wiki

Here, the navigation is done between the Wiki and the pages 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 app.py and replace the previously inserted code by:

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

    def navigate(self, title):
        self.current_page.becomes(Page(title))


@presentation.render_for(Wiki)
def render(self, h, comp, model):
    with h.div:
        h << h.div(self.current_page) << h.hr
        h << 'View the '
        h << h.a('complete list of pages').action(comp.becomes, model='all')

    return h.root

@presentation.render_for(Wiki, model='all')
def render(self, h, comp, model):
    with h.div:
        h << 'Select a page:'
        with h.ul:
            for page in PageData.query.order_by(PageData.page_name):
                with h.li:
                    h << h.a(page.page_name).action(self.navigate, page.page_name)

        h << h.hr
        h << h.a('View the selected page').action(comp.becomes, model=None)

    return h.root

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.