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.