Changes between Initial Version and Version 1 of WikiTutorial


Ignore:
Timestamp:
09/12/2008 06:03:54 PM (9 years ago)
Author:
trac
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • WikiTutorial

    v1 v1  
     1{{{ 
     2#!rst 
     3===================================== 
     4Writing your first Nagare application 
     5===================================== 
     6 
     7This document explains you how to create a basic wiki app using the Nagare framework. 
     8 
     9Prerequisites 
     10============== 
     11 
     12You have Nagare installed as explained in :wiki:`QuickStart`. 
     13 
     14Creating your wiki app 
     15====================== 
     16 
     17Change your current working directory to a directory where you'd like to store 
     18your wiki code, then run the command ``<NAGARE_HOME>/bin/nagare-admin create-app mywiki`` 
     19on command line. 
     20 
     21This will create a ``mywiki`` directory in your current directory with the following elements:: 
     22 
     23    mywiki/ 
     24        conf/ 
     25            __init__.py 
     26            mywiki.cfg 
     27        data/ 
     28            __init__.py 
     29        mywiki/ 
     30            __init__.py 
     31            models.py 
     32            mywiki.py 
     33        setup.py 
     34        static/ 
     35            __init__.py 
     36 
     37These files are: 
     38 
     39    * ``__init__.py``: Empty files that are used by Python to treat the directories as packages. 
     40    * ``models.py``: File that contains database model description for wiki. 
     41    * ``mywiki.py``: File that contains wiki classes. 
     42    * ``setup.py``: File used by setuptools to package wiki application. 
     43    * ``mywiki.cfg``: wiki configuration file. 
     44 
     45Register your wiki application 
     46------------------------------ 
     47 
     48Change your current working directory to the wiki directory and run the command 
     49``<NAGARE_HOME>/bin/python setup.py develop`` on command line. 
     50 
     51Presenting your pages 
     52===================== 
     53 
     54Write a simple Wiki Page 
     55------------------------- 
     56 
     57Let's create a simple page and view it in a browser.  
     58Open ``mywiki.py`` and replace its content by the following: 
     59 
     60.. code-block:: python 
     61 
     62    from nagare import presentation 
     63 
     64    class Page(object): 
     65        def __init__(self, title): 
     66            self.title = title 
     67     
     68    @presentation.render_for(Page) 
     69    def render(self, h, binding, *args): 
     70        return 'This is page *%s*' % self.title 
     71     
     72    app = lambda: Page(u'FrontPage') 
     73 
     74The ``@presentation.render_for(Page)`` decoration associates the view with the 
     75python object. Let's check if it worked, run the command 
     76``<NAGARE_HOME>/bin/nagare-admin serve mywiki --reload``, you should 
     77see the following output:: 
     78 
     79    Application 'app mywiki' registered as '/mywiki' 
     80    serving on http://127.0.0.1:8080 
     81 
     82The lightweight web server is up and running, try to visit http://127.0.0.1:8080/mywiki 
     83with your browser. You'll see your first wiki page containing 
     84"This is page *FrontPage*". 
     85 
     86Hence you launched the server with the option ``--reload``, any modification 
     87made in source code will be automatically reloaded. 
     88 
     89Write a page that actually does something 
     90----------------------------------------- 
     91 
     92To generate HTML with Nagare there is no need for templates, it can be completely 
     93forged out of python code. The templating issue will be discussed later. 
     94A link is inserted on the page title that will append "clicked" to the pages title: 
     95 
     96.. code-block:: python 
     97 
     98    from nagare import presentation 
     99     
     100    class Page(object): 
     101        def __init__(self, title): 
     102            self.title = title 
     103     
     104        def clickCallBack(self): 
     105            self.title = "%s has been clicked" % self.title 
     106     
     107    @presentation.render_for(Page) 
     108    def render(self, h, binding, *args): 
     109        return ('This is page *', 
     110                h.a(self.title).action(self.clickCallBack), 
     111                '*') 
     112     
     113    app = lambda: Page(u'FrontPage') 
     114 
     115The `a` tag is generated by the h renderer, by default it uses html namespace. 
     116As you can see a method can be hooked to this tag through the use of `action`. 
     117 
     118Once the action `clickCallBack` is done the current object is automatically 
     119rendered with its associated view. 
     120 
     121Use different views on the same page 
     122------------------------------------ 
     123 
     124Actually, we did not intend to modify the title by a single but to change the 
     125informations shown on the page. To do it we just modify the callback associated 
     126with the link by one which change the model. A model, is a parameter passed to 
     127`@presentation.render_for` decoration to qualifiy it. Let's create a "clicked" 
     128view: 
     129 
     130.. code-block:: python 
     131 
     132    from nagare import presentation 
     133     
     134    class Page(object): 
     135        def __init__(self, title): 
     136            self.title = title 
     137 
     138     
     139    @presentation.render_for(Page) 
     140    def render(self, h, binding, *args): 
     141     
     142        return ('This is page *', 
     143                h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')), 
     144                '*') 
     145     
     146     
     147    @presentation.render_for(Page, model = 'clicked') 
     148    def render(self, h, binding, *args): 
     149     
     150        return ('Page *', 
     151                h.a(self.title).action(lambda : binding.becomes(self)), 
     152                '* has been clicked') 
     153     
     154    app = lambda: Page(u'FrontPage') 
     155 
     156The model change is achieved by replacing the object planned for rendering 
     157`binding` through the use of `becomes` method which accepts an optional `model` 
     158keyword. 
     159 
     160Using a database 
     161================ 
     162 
     163Setup database model 
     164--------------------- 
     165 
     166Let's start with a wikipage with two properties: 
     167    * its name 
     168    * its content 
     169 
     170Open ``models.py`` file and add the following lines: 
     171 
     172.. code-block:: python 
     173 
     174    class PageData(Entity): 
     175        pagename = Field(Unicode(40), primary_key=True) 
     176        pagecontent = Field(Unicode(10*1024)) 
     177 
     178Model declaration can use any of `Elixir`_ or `SQLAlchemy`_ syntax. 
     179 
     180.. _Elixir: http://elixir.ematia.de 
     181.. _SQLAlchemy: http://www.sqlalchemy.org 
     182 
     183Setup database connection 
     184-------------------------- 
     185 
     186We gonna use an SQLite database to store our wikipages. 
     187 
     188Open ``mywiki.cfg`` and modify `[database]` section like this:: 
     189 
     190    [database] 
     191    activated = on 
     192    uri = sqlite:///$here/../data/mywiki.db 
     193    metadata = mywiki.models:__metadata__ 
     194    debug = off 
     195 
     196Populate database 
     197------------------ 
     198 
     199Let's prepare some data for our wiki 
     200 
     201Open ``models.py`` file and add the following lines: 
     202 
     203.. code-block:: python 
     204 
     205    def populate(): 
     206        page = PageData() 
     207        page.pagename = u'FrontPage' 
     208        page.data = u'Welcome on my *WikiWiki* !' 
     209        session.save(page) 
     210 
     211        page = PageData() 
     212        page.pagename = u'WikiWiki' 
     213        page.data = u'A wiki allows users to create and edit web pages easily.' 
     214        session.save(page) 
     215 
     216Open ``mywiki.cfg`` and modify `[database]` section like this: 
     217 
     218:: 
     219 
     220    [database] 
     221    activated = on 
     222    uri = sqlite:///$here/../data/mywiki.db 
     223    metadata = mywiki.models:__metadata__ 
     224    debug = off 
     225    populate = mywiki.models:populate 
     226 
     227Create database 
     228---------------- 
     229 
     230Run the command ``<NAGARE_HOME>/bin/nagare-admin create-db mywiki`` 
     231on command line. The SQLite database now contains our two pages `Frontpage` 
     232and `WikiWiki`. 
     233 
     234Associate database object with our previous page 
     235------------------------------------------------ 
     236 
     237Now we use our previous `Page` object to render our page `Frontpage` data: 
     238 
     239.. code-block:: python 
     240 
     241    ... 
     242    from models import PageData 
     243    ... 
     244     
     245    @presentation.render_for(Page) 
     246    def render(self, h, binding, *args): 
     247        page = PageData.get_by(pagename=self.title) 
     248        return ('This is page *', 
     249                h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')), 
     250                '*', 
     251                h.hr, page.pagecontent) 
     252    ... 
     253 
     254Handle wikiwords properly 
     255------------------------- 
     256 
     257In `Frontage` page there is the `wiki word` WikiWiki. This is the name of the 
     258second page previously inserted in our database, let's make the word a link 
     259to the `WikiWiki` page. 
     260 
     261Open ``mywiki.py`` and replace its content by the following: 
     262     
     263.. code-block:: python 
     264 
     265    ... 
     266    import docutils.core     
     267    import re 
     268    wikiwords = re.compile(r'\b([A-Z]\w+[A-Z]+\w+)') 
     269    ... 
     270 
     271    class Page(object): 
     272        ... 
     273        def getWikiTaggedContent(self): 
     274            page = PageData.get_by(pagename=self.title) 
     275            if page is None: 
     276                page = PageData(pagename=self.title, pagecontent='') 
     277            content = docutils.core.publish_parts(page.pagecontent, 
     278                                                  writer_name='html')['html_body'] 
     279            return wikiwords.sub(r'<wiki>\1</wiki>', content) 
     280        ... 
     281    ... 
     282    @presentation.render_for(Page) 
     283    def render(self, h, binding, *args): 
     284        pageContent = h.parse_htmlstring(self.getWikiTaggedContent(), fragment=True)[0] 
     285        for node in pageContent.getiterator(): 
     286            if node.tag == 'wiki': 
     287                node.replace(h.a(node.text).action( 
     288                        lambda title=unicode(node.text): binding.becomes(Page(title))) 
     289                        ) 
     290                 
     291        return ('This is page *', 
     292                h.a(self.title).action(lambda : binding.becomes(self, model = 'clicked')), 
     293                '*', 
     294                h.hr, 
     295                pageContent) 
     296    ... 
     297 
     298Use form to modify pages 
     299------------------------ 
     300 
     301To modify our wiki page, we use another object that will handle page updates, 
     302let's call it `PageEditor`. Open ``mywiki.py`` and add the following: 
     303 
     304.. code-block:: python 
     305 
     306    ... 
     307    from nagare.var import Var 
     308    ... 
     309     
     310    class Page(object): 
     311        ... 
     312        def edit(self, binding): 
     313            content = binding.call(PageEditor(self)) 
     314     
     315            if content is not None: 
     316                page = PageData.get_by(pagename=self.title) 
     317                if page is None: 
     318                    page = PageData(pagename=self.title, pagecontent='') 
     319                page.pagecontent = content 
     320     
     321    ... 
     322    @presentation.render_for(Page) 
     323    def render(self, h, binding, *args): 
     324        pageContent = h.parse_htmlstring(self.getWikiTaggedContent(), fragment=True)[0] 
     325        for node in pageContent.getiterator(): 
     326            if node.tag == 'wiki': 
     327                node.replace(h.a(node.text).action(lambda title=unicode(node.text): binding.becomes(Page(title)))) 
     328                 
     329        return (h.p(h.a('Edit *',  
     330                        self.title, 
     331                        '* page').action(lambda: self.edit(binding))), 
     332                h.hr, 
     333                pageContent) 
     334    ... 
     335    ... 
     336    class PageEditor(object): 
     337        def __init__(self, page): 
     338            self.page = page 
     339            self.content = Var() 
     340     
     341     
     342    @presentation.render_for(PageEditor) 
     343    def render(self, h, binding, *args): 
     344        page = PageData.get_by(pagename=self.page.title) 
     345        if page is None: 
     346            page = PageData(pagename=self.page.title, pagecontent='') 
     347     
     348        return h.form(h.textarea(page.pagecontent, rows='10', cols='60').action(self.content), 
     349                    h.br, 
     350                    h.input(type='submit', value='Save').action(lambda: binding.answer(self.content())), 
     351                    ' ', 
     352                    h.input(type='submit', value='Cancel').action(binding.answer)) 
     353    ... 
     354 
     355The `call/answer` mechanism is used to show up the form and handle the posted 
     356content. When the `call` method is used the form is displayed and the `edit` 
     357method is blocked awaiting an answer that should be send through the use of 
     358`answer` method. On every input of a form an action can be hooked. They are 
     359called in the `input > submit` order. 
     360 
     361Refactoring 
     362=========== 
     363 
     364Merge pages into a real wiki app 
     365-------------------------------- 
     366 
     367Open ``mywiki.py`` and replace ``app = ...`` content by the following: 
     368 
     369.. code-block:: python 
     370 
     371    class Wiki(object): 
     372        pass 
     373     
     374     
     375    @presentation.render_for(Wiki) 
     376    def render(self, h, binding, *args): 
     377        page = component.ComponentBinding(Page(u'FrontPage')) 
     378        return (h.div( page ), 
     379                h.hr, 
     380                'View the ', 
     381                h.a('complete list of pages').action(lambda : binding.becomes(self, 
     382                                                                              model = 'all')),) 
     383     
     384     
     385    @presentation.render_for(Wiki, model = 'all') 
     386    def render(self, h, binding, *args): 
     387        return h.div(h.ul([h.li(h.a(page.pagename).action(lambda title=page.pagename: binding.becomes(Page(title), 
     388                                                          model = 'all'))) for page in PageData.query.order_by(PageData.pagename)]), 
     389                    h.hr, 
     390                    h.a('Back').action(lambda : binding.becomes(self))) 
     391     
     392    app = Wiki 
     393 
     394Here, the navigation is done between the wiki and the page, when we click on 
     395the link to a page the current component is replaced by the selected page. 
     396For example the bottom part with the link `complete list of pages` has 
     397completely disapeared from the HTML page. 
     398 
     399Compose components and views 
     400---------------------------- 
     401 
     402Open ``mywiki.py`` and replace the previously inserted code by: 
     403 
     404.. code-block:: python 
     405     
     406    class Wiki(object): 
     407        def __init__(self): 
     408            self.current_page = component.ComponentBinding(Page(u'FrontPage')) 
     409     
     410     
     411    @presentation.render_for(Wiki) 
     412    def render(self, h, binding, *args): 
     413        return (h.div( self.current_page ), 
     414                h.hr, 
     415                'View the ', 
     416                h.a('complete list of pages').action(lambda : binding.becomes(self, model = 'all')),) 
     417     
     418     
     419    @presentation.render_for(Wiki, model = 'all') 
     420    def render(self, h, binding, *args): 
     421        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)]), 
     422                    h.hr, 
     423                    h.a('Back').action(lambda : binding.becomes(self))) 
     424     
     425    app = Wiki 
     426 
     427With this new version we only change the `current_page` attribute when we 
     428click a link to a page. The Wiki application context is preserved, e.g. the 
     429bottom part with the link `complete list of pages` is still there. 
     430 
     431.. wikiname: WikiTutorial 
     432}}}