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))

Models are SQLAlchemy entities.

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.