Differences between revisions 1 and 2
Revision 1 as of 2006-08-22 02:03:36
Size: 35289
Editor: HuangYi
Comment: pylons : QuickWiki Tutorial
Revision 2 as of 2006-08-22 02:33:43
Size: 37254
Editor: HuangYi
Comment:
Deletions are marked like this. Additions are marked like this.
Line 4: Line 4:
 * author: James Gardner  * 作者: James Gardner
Line 9: Line 9:
请先阅读 [http://www.pylonshq.com/docs/0.9.1/install.html installation instructions] 和 [http://www.pylonshq.com/docs/0.9.1/getting_started.html getting started] 两份指南 , 如果你还没读过的话 :)
Line 11: Line 13:
在这个教程中我们要使用 Pylons0.9 和 SQLAlchemy 从头创建一个可以工作的 wiki. 我们的 wiki 可以允许用户添加,修改,删除有格式的wiki页面.
Line 15: Line 19:
Pylons 的设计目标是要方便所有人, 而不仅仅是开发者, 所有让我们从以 QuickWiki 最终用户的身份下载并安装一个 QuickWiki 的成品开始. 等我们发掘完它的特性后, 我们就开始从头开始编写它.
Line 16: Line 22:

先安装 [http://peak.telecommunity.com/DevCenter/EasyInstall Easy Install] , 然后运行以下命令来安装 QuickWiki 并创建一个配置文件:
Line 22: Line 30:
下面编辑该配置文件, 在 [app:main] 节中指定 dsn 变量, 让数据源的名称指向你希望使用的数据库.
Line 24: Line 34:
注意: 默认的数据源名称 dsn = postgres://@localhost/quickwiki_test 使用一个叫 quickwiki_test 的在本地运行的 PostgreSQL 数据库. 你可以使用 PostgreSQL 的命令 createdb quickwiki_test 来创建这个数据库, 你也可以使用 MySQL, Oracle 或者 SQLite. Firebird 和 MS-SQL 应该也能行. 连接不同数据库的详细信息请查看 [http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing SQLAlchemy documentation]. 比如 SQLite 的 dsn 里需要有4个反斜线. 你还得确定你已经有了你要使用的数据库的相应的 python驱动. 比如用 PostgreSQL 的话你就需要 [http://www.initd.org/projects/psycopg1 psycopg]
Line 25: Line 37:

最后, 创建数据库表并启动该成品:
Line 31: Line 45:
这样就OK了 ! 现在你可以访问 http://127.0.0.1:5000 并实验这个 Wiki 成品. 注意, 在标题列表页, 你将页面标题拖到垃圾区域, 就可以通过 Ajax 调用删除它.
Line 32: Line 48:

弄成这些以后, 使用 CTRL+C 停止服务器, 因为我们要开始开发我们自己的版本了.

TableOfContents

QuickWiki Tutorial

简介

请先阅读 [http://www.pylonshq.com/docs/0.9.1/install.html installation instructions] 和 [http://www.pylonshq.com/docs/0.9.1/getting_started.html getting started] 两份指南 , 如果你还没读过的话 :)

If you haven't done so already read the installation instructions <install.html>_ and getting started <getting_started.html>_ guide.

在这个教程中我们要使用 Pylons0.9 和 SQLAlchemy 从头创建一个可以工作的 wiki. 我们的 wiki 可以允许用户添加,修改,删除有格式的wiki页面.

In this tutorial we are going to create a working wiki from scratch using Pylons 0.9 and SQLAlchemy. Our wiki will allow visitors to add, edit or delete formatted wiki pages.

从成品开始 Starting at the End

Pylons 的设计目标是要方便所有人, 而不仅仅是开发者, 所有让我们从以 QuickWiki 最终用户的身份下载并安装一个 QuickWiki 的成品开始. 等我们发掘完它的特性后, 我们就开始从头开始编写它.

Pylons is designed to be easy for everyone, not just developers, so lets start by downloading and installing the finished QuickWiki in exactly the way end users of QuickWiki might do. Once we have explored its features we will set about writing it from scratch.

先安装 [http://peak.telecommunity.com/DevCenter/EasyInstall Easy Install] , 然后运行以下命令来安装 QuickWiki 并创建一个配置文件:

After you have installed Easy Install <http://peak.telecommunity.com/DevCenter/EasyInstall>_ run these commands to install QuickWiki and create a config file::

下面编辑该配置文件, 在 [app:main] 节中指定 dsn 变量, 让数据源的名称指向你希望使用的数据库.

Next edit the configuration file by specifying the dsn variable in [app:main] section so that the data source name points to the database you wish to use.

注意: 默认的数据源名称 dsn = postgres://@localhost/quickwiki_test 使用一个叫 quickwiki_test 的在本地运行的 PostgreSQL 数据库. 你可以使用 PostgreSQL 的命令 createdb quickwiki_test 来创建这个数据库, 你也可以使用 MySQL, Oracle 或者 SQLite. Firebird 和 MS-SQL 应该也能行. 连接不同数据库的详细信息请查看 [http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing SQLAlchemy documentation]. 比如 SQLite 的 dsn 里需要有4个反斜线. 你还得确定你已经有了你要使用的数据库的相应的 python驱动. 比如用 PostgreSQL 的话你就需要 [http://www.initd.org/projects/psycopg1 psycopg]

.. Note:: The default data source name dsn = postgres://@localhost/quickwiki_test uses a PostgreSQL database named quickwiki_test running on the local machine. You can create this database with the PostgreSQL command createdb quickwiki_test but you could also use MySQL, Oracle or SQLite. Firebird and MS-SQL may also work. See the SQLAlchemy documentation <http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing>_ for more information on how to connect to different databases. SQLite for example requires four forward slashes in its DSN. You will also need to make sure you have the appropriate Python driver for the database you wish to use. For PostgreSQL this would be psycopg <http://www.initd.org/projects/psycopg1>_.

最后, 创建数据库表并启动该成品:

Finally create the database tables and serve the finished application::

  • > paster setup-app test.ini > paster serve test.ini

这样就OK了 ! 现在你可以访问 http://127.0.0.1:5000 并实验这个 Wiki 成品. 注意, 在标题列表页, 你将页面标题拖到垃圾区域, 就可以通过 Ajax 调用删除它.

That's it! Now you can visit http://127.0.0.1:5000 and experiment with the finished Wiki. Note that in the title list screen you can drag page titles to the trash area to delete them via AJAX calls.

弄成这些以后, 使用 CTRL+C 停止服务器, 因为我们要开始开发我们自己的版本了.

When you've finished, stop the server with CTRL+C because we will start developing our own version.

开发 QuickWiki

If you skipped the section above you will need to install Pylons 0.9::

  • > easy_install -U Pylons[full]==0.9

Then create your project::

Now lets start the server and see what we have::

  • > cd QuickWiki > paster serve --reload development.ini

.. Note:: We have started the server with the --reload switch. This means any changes we make to code will cause the server to restart (if necessary); your changes are immediately reflected on the live site.

Open a new console and cd QuickWiki/quickwiki. Visit http://127.0.0.1:5000 where you will see the introduction page. Delete the file public/index.html because we want to see the front page of the wiki instead of this welcome page. If you now refresh the page, the Pylons built-in error document support will kick in and display an Error 404 page to tell you the file could not be found. We'll setup a controller to handle this location later.

Model

Pylons uses a Model View Controller architecture; we'll start by creating the model. We could use any system we like for the model including SQLObject <http://www.sqlobject.org>_ or SQLAlchemy <http://www.sqlalchemy.org>_. We'll use SQLAlchemy for QuickWiki.

.. Note:: SQLAlchemy is a Python SQL toolkit and Object Relational Mapper that is fast becoming the default choice for many Pylons programmers.

  • It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performance database access, adapted into a simple and Pythonic domain language. There is full and detailed documentation available on the SQLAlchemy website at http://sqlalchemy.org/docs/ and you should really read this before you get heavily into SQLAlchemy.

Edit your models/init.py so that it looks like:

.. code-block:: Python

  • import sqlalchemy.mods.threadlocal from sqlalchemy import *

    meta = DynamicMetaData() pages_table = Table('pages', meta,

    • Column('title', String(40), primary_key=True),

      Column('content', String(), default=)

    )

The first two lines setup SQLAlchemy to support threads and import some useful objects such as the Table and Column classes. The second line meta = DynamicMetaData() is so that we can bind the tables to a database later on. We then define a table called pages which has two columns, title (the primary key) and content.

.. Note:: SQLAlchemy also supports reflecting this information directly from a database table so if we had already created the database table SQLAlchemy could have constructed the Table object for us.

A core philosophy of SQLAlchemy is that tables and domain classes are different beasts. So next, we'll create the Python class that will represent the pages of our wiki and map these domain objects to rows in the pages table using a mapper. Add this to the bottom of models/init.py:

.. code-block:: Python

  • class Page(object):
    • def str(self):

      • return self.title
    page_mapper = mapper(Page, pages_table)

Looking ahead, our wiki will need some formatting so we will need to turn the content field into HTML. Any WikiWords (which are words made by joining together two or more lowercase words with the first letter capitalized) will also need to be converted into hyperlinks.

It would be nice if we could add a method to our Page object to retrieve the formatted HTML with the WikiWords already converted to hyperlinks. Add the following at the top of the models/init.py file:

.. code-block:: Python

  • from pylons import h import re wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") from docutils.core import publish_parts

and then add a get_wiki_content() method to the Page object so it looks like this:

.. code-block:: Python

  • class Page(object):
    • def str(self):

      • return self.title
      content = None def get_wiki_content(self):
      • content = publish_parts(self.content, writer_name="html")["html_body"] titles = wikiwords.findall(content) if titles:
        • for title in titles:
          • content = content.replace(
            • title, h.link_to(
              • title, h.url_for(
                • controller='page', action='index', title=title
                ),
              ),
            )
        return content

This code deserves a bit of explaining. The content = None line is so that the content attribute is initialized to None when a new Page object is created. If the Page object represents a row in the pages table then self.content will be the value of the content field. h.link_to() and h.url_for() are standard Pylons helpers which create links to specific controller actions. In this case we have decided that all WikiWords should link to the index action of the page controller which we will create later.

One final change, since we have used docutils and SQLAlchemy, both third party packages, we need to edit our setup.py file so that anyone installing QuickWiki with Easy Install <http://peak.telecommunity.com/DevCenter/EasyInstall>_ will automatically also have these dependencies installed for them too. Edit your setup.py in your project root directory so that the install_requires line looks like this::

  • install_requires=["Pylons>=0.9", "docutils==0.4", "SQLAlchemy>=0.2.6"],

While we are we are making changes to setup.py we might want to complete some of the other sections too. Set the version number to 0.1.1 and add a description and URL which will be used on the Python Cheeseshop when we release it::

We might also want to make a full release rather than a development release in which case we would remove the following lines from setup.cfg::

  • [egg_info] tag_build = dev tag_svn_revision = true

To test the automatic installation of the dependencies, run the following command which will also install docutils and SQLAlchemy if you don't already have them::

  • > python setup.py develop

.. Note:: The command python setup.py develop installs your application in a special mode so that it behaves exactly as if it had been installed as an egg file by an end user. This is really useful when you are developing an application because it saves you having to create an egg and install it every time you want to test a change.

连接数据库

All of your project's controllers are derived from the BaseController in lib/base.py by default. We will make use of this fact to enable all controllers to with SQLAlchemy support. Add the following code underneath the import statements in lib/base.py to replace the existing BaseController class:

.. code-block:: Python

  • import sqlalchemy.mods.threadlocal from sqlalchemy import *

    class BaseController(WSGIController):

    • def call(self, environ, start_response):

      • model.meta.connect(
        • request.environ['paste.config']['app_conf']['dsn']
        ) objectstore.clear()

        response = WSGIController.call(self, environ, start_response) objectstore.flush() return response

On each request the SQLAlchemy objects will now be connected to the database.

配置 安装

Now lets make the changes necessary to enable QuickWiki to be set up by an end user. Edit websetup.py used by the paster setup-app command to look like this:

.. code-block:: Python

  • import sqlalchemy.mods.threadlocal from sqlalchemy import * from quickwiki.models import * from paste.deploy import appconfig def setup_config(command, filename, section, vars):
    • app_conf = appconfig('config:'+filename) print "Connecting to database %s"%app_conf['dsn'] meta.connect(app_conf['dsn']) print "Creating tables" meta.create_all() print "Adding front page data" page = Page()

      page.title = 'FrontPage' page.content = 'Welcome to the QuickWiki front page.' objectstore.flush() print "Successfully setup."

The line from quickwiki.models import * imports our page table, the line meta.connect(app_conf['dsn']) connects to the database with the DSN the user specifies in the config file and finally meta.create_all() creates the table. After the tables are created the code adds some data for the simple front page to out wiki.

To test this functionality run you first need to install your QuickWiki if you haven't already done so in order for paster to find the version we are developing instead of the version we installed at the very start::

  • > python setup.py develop

You can then edit your development.ini to add your database DSN:

.. code-block:: PasteIni

  • [app:main] use = egg:quickwiki dsn = postgres://@localhost/quickwiki_test

You should also edit QuickWiki.egg_info/paste_deploy_config.ini_tmpl so that when users run paster make-config the configuration file will already have a section telling them to enter their own database DSN as we did when we installed the finished QuickWiki at the start of the tutorial:

.. code-block:: PasteIni

  • [app:main] use = egg:quickwiki # Specify your own database connection here dsn = postgres://@localhost/quickwiki_test

You can should then run the paster setup-app command to setup your tables in the same way and end user would, remembering to drop and recreate the database if the version tested earlier has already created the tables::

  • > paster setup-app development.ini

.. Note:: See the SQLAlchemy note in the Starting at the End_ section for information on supported DSNs and a link to the SQLAlchemy documentation about the various options that can be included in them.

Templates

.. Note:: Pylons uses the Myghty templating language by default, although as is the case with most aspects of Pylons you are free to deviate from the default if you prefer. Pylons also supports Kid and Cheetah out of the box.

We will make use of a feature of the Myghty templating language called inheritance for our project. Add the main page template in templates/autohandler:

.. code-block:: HyperText

  • <html>

    • <head>

      • <title>QuickWiki</title> <% h.stylesheet_link_tag('/quick.css') %>

      </head> <body>

      • <div class="content">

    % m.call_next()
    • <p class="footer">

      • Return to the

        <% h.link_to('FrontPage', h.url_for(action="index", title="FrontPage")) %> | <% h.link_to('Edit '+c.title, h.url_for(title=c.title, action='edit')) %>

      </p> </div>

    • </body>

    </html>

All our other templates will be automatically inserted into the % m.call_next() line and the whole page returned when we call the render_response() global from our controller so that we can easily apply a consistent theme to all our templates.

If you are interested in learning some of the features of Myghty templates have a look at the comprehensive Myghty Documentation <http://www.myghty.org/docs/>_. For now we just need to understand that is replaced with the child template and that anything in <% and %> brackets is executed and replaced with the result. Having the % as the first character on a line is shorthand for putting the whole line in <% and %> brackets.

This autohandler also makes use of various helper functions attached to the h object. These are described in the WebHelpers documentation <http://pylonshq.com/WebHelpers/module-index.html>_. You can add more helpers to the h object by adding them to lib/helpers.py although for this project we don't need to do so.

.. Warning:: Your templates won't work if there is any whitespace before lines starting with a % character.

Routing (url dispatcher)

Before we can add the actions we want to be able to route the requests to them correctly. Edit config/routing.py so that the route map looks like this:

.. code-block:: Python

  • map.connect('error/:action/:id', controller='error')

    map.connect(':controller/:action/:title', controller='page', action='index', title='FrontPage') map.connect(':title', controller='page', action='index', title='FrontPage') map.connect('*url', controller='template', action='view')

Full information on the powerful things you can do to route requests to controllers and actions can be found in the Routes documentation <http://routes.groovie.org/docs/>_.

Controllers

Quick Recap: We've setup the model, configured the application, added the routes and setup the base template in autohandler, now we need to write the application! In your project's root directory add a controller called page to your project with this command::

  • > paster controller page

We are going to need the following actions::

  • index(self, title)

    • displays a page based on the title

    edit(self, title)

    • displays a from for editing the page title

    save(self, title)

    • save the page title and show it with a saved message

    list(self)

    • gives a list of all pages

    delete(self)

    • deletes a page based on an AJAX drag and drop call

Let's get cracking! Add the following import statements to the top of the page controller controllers/page.py after the existing import statement:

.. code-block:: Python

  • import sqlalchemy.mods.threadlocal from sqlalchemy import objectstore

Add the this method to the PageController class to create a query object for the Page on each request:

.. code-block:: Python

  • def before(self):

    • self.query = objectstore.query(model.Page)

.. Note:: before() is a special action which is always called on each request before the main action is called. Likewise there is also an after() action called after your main action is called.

index()

Replace the existing index() action with this:

.. code-block:: Python

  • def index(self, title):
    • page = self.query.get_by(title=title) if page:
      • c.content = page.get_wiki_content() return render_response('/page.myt')
      elif model.wikiwords.match(title):
      • return render_response('/new_page.myt')
      abort(404)

Add a template called templates/page.myt that looks like this:

.. code-block:: HyperText

  • <h1 class="main"><% c.title %></h1> <% c.content %>

This template simply displays the page title and content.

.. Note:: Pylons automatically assigns all the action parameters to the context object c so that you don't have to assign them yourself. In this case, the value of title will be automatically assigned to c.title so that it can be used in the templates. We assign c.content manually in the controller.

We also need a template for pages that don't already exist. They need to display a message and link to the edit action so that they can be created. Add a template called templates/new_page.myt that looks like this:

.. code-block:: HyperText

  • <h1 class="main"><% c.title %></h1> <p>This page doesn't exist yet.

    • <a href="<% h.url_for(action='edit', title=c.title) %>">Create the page</a>.</p>

At this point we can test our QuickWiki to see how it looks. If you don't already have a the server running start it now with::

  • > paster serve --reload development.ini

Visit http://127.0.0.1:5000/ and you will see the front page of the wiki. We can spruce it up a little by adding the stylesheet we linked to in the templates/autohandler file earlier. Add this the file public/quick.css with the following content:

.. code-block:: CSS

  • body {
    • background-color: #888; margin: 25px;
    } div.content{
    • margin: 0; margin-bottom: 10px; background-color: #d3e0ea; border: 5px solid #333; padding: 5px 25px 25px 25px;
    } h1.main{
    • width: 100%; border-bottom: 1px solid #000;
    } p.footer{
    • width: 100%; padding-top: 3px; border-top: 1px solid #000;
    }

Refresh the page again and we have a better looking wiki. You will notice that the word QuickWiki has been turned into a hyperlink by the get_wiki_content() method we added to our Page domain object earlier. You can click the link and will see an example of the new page screen from the new_page.myt template. If you follow the Create the page link you will see the Pylons automatic error handler kick in to tell you Action edit is not implemented. Well, we better write it next but before we do, have a play with the interactive debugger <interactive_debugger.html>_, try clicking on the + or >> arrows and you will be able to interactively debug your application it is a tremendously useful tool.

edit()

To edit the wiki page we need to get the content from the database without changing it to HTML to display it in a simple form for editing. Add the edit() action:

.. code-block:: Python

  • def edit(self, title):
    • page = self.query.get_by(title=title) if page:
      • c.content = page.content
      return render_response('/edit.myt')

and the create the templates/edit.myt file:

.. code-block:: HyperText

  • <h1 class="main">Editing <% c.title %></h1>

    <% h.start_form(h.url_for(action='save', title=c.title), method="get") %>

    • <% h.text_area(name='content', rows=7, cols=40, content=c.content)%> <br /> <% h.submit(value="Save changes", name='commit') %>

    <% h.end_form() %>

.. Note:: You might have noticed that we only set c.content if the page exists but that it is accessed in h.text_area even for pages that don't exist and doesn't raise an AttributeError. We are making use of the fact that the c object returns an empty string "" for any attribute that is accessed. This is a very useful feature of the c object but can catch you out on occasions where you don't expect this behavior.

We are making use of the h object to create our form and field objects. This saves a bit of manual HTML writing. The form submits to the save() action to save the new or updated content so let's write that next.

save()

The first thing the save() action has to do is to see if the page being saved already exists. If not it creates it with page = model.Page(). Next it needs the updated content. In Pylons you can get request parameters from form submissions, GET or POST requests from the appropriately named request.params object.

Add the save() action:

.. code-block:: Python

  • def save(self, title):
    • page = self.query.get_by(title=title) if not page:
      • page = model.Page() page.title = title
      page.content = request.params['content'] c.title = page.title c.content = page.get_wiki_content() c.message = 'Successfully saved' return render_response('/page.myt')

.. Note::

  • request.params is a MultiDict object; an ordered dictionary that may contain multiple values for each key. The MultiDict will always return one value for any existing key via the normal dict accessors (params[key], params.get() and params.setdefault()). When multiple values are expected, use the getall() method to return all values in a list.

In order for the page.myt template to display the Successfully saved message after the page is saved we need to update the templates/page.myt file. After <h1 class="main"><% c.title %></h1> add these lines making sure there is no whitespace before the lines that start with a % character:

.. code-block:: HyperText

  • % if c.message:

    <p><% c.message %> </p> % #end if

At this point we have a fully functioning wiki that lets you create and edit pages and can be installed and deployed by an end user with just a few simple commands.

Visit http://127.0.0.1:5000 and have a play.

It would be nice to get a title list and to be able to delete pages, so that's what we'll do next!

list()

Add the list() action:

.. code-block:: Python

  • def list(self):
    • c.titles = [page.title for page in self.query.select()] return render_response('/titles.myt')

The list() action simply gets all the pages from the database. Create the templates/titles.myt file to display the list:

.. code-block:: HyperText

  • <h1 class="main">Title List</h1>

    <ul id="titles"> % for title in c.titles: <li>

    • <% title %> [<% h.link_to('visit', h.url_for(title=title, action="index")) %>]

    </li> % #end for </ul>

Finally edit templates/autohandler to add a link to the title list so that the footer looks like this:

.. code-block:: HyperText

  • <p class="footer">

    % if h.url_for() != '/page/list':
    • | <% h.link_to('Edit '+c.title, h.url_for(title=c.title, action='edit')) %> | <% h.link_to('Title List', h.url_for(action='list', title=None)) %>

    % # end if

    </p>

If you visit http://127.0.0.1:5000/page/list you should see the full titles list and you should be able to visit each page.

delete()

Since this tutorial is designed to get you familiar with as much of Pylons core functionality as possible we will use some AJAX to allow the user to drag a title from the title list into a trash area that will automatically delete the page.

Add this line to templates/autohandler before </head>:

.. code-block:: HyperText

  • <% h.javascript_include_tag('/javascripts/effects.js', builtins=True) %>

.. Note:: The h.javascript_include_tag() helper will create links to all the built-in JavaScripts we need and also add /javascripts/effects.js creating HTML that looks like this when you access it from a browser:

.. code-block:: HyperText

  • <script src="/javascripts/prototype.js" type="text/javascript"></script> <script src="/javascripts/scriptaculous.js" type="text/javascript"></script> <script src="/javascripts/effects.js" type="text/javascript"></script>

If you look at config/middleware.py you will see these lines:

.. code-block:: Python

  • javascripts_app = StaticJavascripts()

  • .. app = Cascade([static_app, javascripts_app, app])

The javascripts_app WSGI application maps any requests to /javascripts/ straight to the relevant JavaScript in the WebHelpers package. This means you don't have to manually copy the Pylons JavaScript files to your project and that if you upgrade Pylons, you will automatically be using the latest scripts.

Now for the AJAX! We want all the titles in the titles list to be draggable so we enclose each of them with a <span> element with a unique ID. Edit templates/titles.myt to replace this line:

.. code-block:: HyperText

  • <% title %> [<% h.link_to('visit', h.url_for(title=title, action="index")) %>]

with this:

.. code-block:: HyperText

  • <span id="page-<% title %>"><% title %></span>  [<% h.link_to('visit', h.url_for(title=title, action="index")) %>] <% h.draggable_element("page-"+ str(title), revert=True) %>

This marks each of the titles as a draggable element that reverts to its original position if it isn't dropped over a drop target. If we want to be able to delete the pages we better add a drop target. Try it out at http://127.0.0.1:5000/page/list by dragging the titles themselves around the screen. Notice how much functionality we get with just the one helper h.draggable_element().

We better have somewhere to drop the titles to delete them so add this before the <ul id="titles"> line in templates/titles.myt :

.. code-block:: HyperText

  • <div id="trash">

    • Delete a page by dragging its title here

    </div> <% h.drop_receiving_element("trash", update="titles", url=h.url_for(action="delete")) %>

We will also need to add the style for the trash box to the end of public/quick.css:

.. code-block:: CSS

  • div#trash{
    • float: right; margin: 0px 20px 20px 20px; background: #eee; border: 1px solid #000; padding: 15px;
    }

.. Tip:: It can sometimes be very hard to debug AJAX applications. Pylons can help. if an error occurs in debug mode (the default in development.ini) a debug URL where you can use an interactive debugger will be printed to the error stream, even in an AJAX request. If you copy and paste that address into a bowser address bar you will be able to debug the request.

When a title is dropped on the trash box an AJAX request will be made to the delete() action posting an id parameter with the id of the element that was dropped. The element with id titles will be replaced with whatever is returned from the action so we better add a delete() action that returns the new list of titles excluding the one that has been deleted:

.. code-block:: Python

  • def delete(self):
    • title = request.params['id'][5:] page = self.query.get_by(title=title) objectstore.delete(page) objectstore.flush() c.titles = self.query.select() return render_response('/list.myt', fragment=True)

The title of the page is obtained from the id element and the object is loaded and then deleted. The change is saved objectstore.flush() before the list of remaining titles is rendered by the template templates/list.myt which you should create with the following content:

.. code-block:: HyperText

  • % for title in c.titles:

    <li>

    • <span id="page-<% title %>"><% title %></span> [<% h.link_to('visit', h.url_for(title=title, action="index")) %>] <% h.draggable_element("page-"+ str(title), revert=True) %>

    </li> % #end for

.. Note:: We passed fragment=True to the render_response() global. This treats the template as a fragment and does not try to insert its output into the autohandler. If we hadn't used this an entire page would have been returned and put in place of the titles list which clearly isn't what we wanted.

Visit http://127.0.0.1:5000/page/list and have a go at deleting some pages. You may need to go back to the FrontPage and create some more if you get carried away!

One last tidy up we can make before we finish. You might have noticed that the list.myt and titles.myt use the same code to produce the lists. In the interests of keeping code duplication to a minimum edit templates/titles.myt to replace this code:

.. code-block:: HyperText

  • % for title in c.titles:

    <li>

    • <span id="page-<% title %>"><% title %></span> [<% h.link_to('visit', h.url_for(title=title, action="index")) %>] <% h.draggable_element("page-"+ str(title), revert=True) %>

    </li> % #end for

With this code:

.. code-block:: HyperText

  • <% render('/list.myt', fragment=True) %>

That's it! A working, production-ready wiki in 20 mins. You can visit http://127.0.0.1:5000/ once more to admire your work.

发布该产品

After all that hard work it would be good to distribute the finished package wouldn't it? Luckily this is really easy in Pylons too. In the project root directory run this command::

  • > python setup.py bdist_egg

This will create an egg file in dist which contains everything anyone needs to run your program. They can install it with::

You should probably make eggs for each version of Python your users might require by running the above command with both Python 2.3 and 2.4.

If you want to register your project with the Cheeseshop at http://www.python.org/pypi you can run the command below. *Please only do this with your own projects though because QuickWiki has already been registered!*

::

  • > python setup.py register

.. Warning:: The CheeseShop authentication is very weak and passwords are transmitted in plain text. Don't use any sign in details that you use for important applications as they could be easily intercepted.

You will be asked a number of questions and then the information you entered in setup.py will be used as a basis for the page that is created.

Now visit http://www.python.org/pypi to see the new index with your new package listed.

.. Note:: A CheeseShop Tutorial <http://wiki.python.org/moin/CheeseShopTutorial>_ has been written and full documentation on setup.py <http://docs.python.org/dist/dist.html>_ is available from the Python website. You can even use reStructuredText <http://docutils.sourceforge.net/rst.html>_ in the description and long_description areas of setup.py to add formatting to the pages produced on the CheeseShop. There is also another tutorial here <http://www.python.org/~jeremy/weblog/030924.html>_.

Finally you can sign in to the CheeseShop with the account details you used when you registered your application and upload the eggs you've created. If that seems too difficult you can even use this command which should be run for each version of Python supported to upload the eggs for you::

  • > python setup.py bdist_egg upload

Before this will work you will need to create a .pypirc file in your home directory containing your username and password so that the upload command knows who to sign in as. It should look similar to this::

  • [server-login] username:james password:password

.. Tip:: This works on windows too but you will need to set your HOME environment variable first. If your home directory is C:\Documents and Settings\James you would put your .pypirc file in that directory and set your HOME environment variable with this command::

  • > SET HOME=C:\Documents and Settings\James

  • You can now use the python setup.py bdist_egg upload as normal.

Now that the application is on CheeseShop anyone can install it with the easy_install command exactly as we did right at the very start of this tutorial.

安全

A final word about security.

.. Danger:: Always set debug = false in configuration files for production sites and make sure your users do to.

You should NEVER run a production site accessible to the public with debug mode on. If there was a problem with your application and an interactive error page was shown, the visitor would be able to run any Python commands they liked in the same way you can when you are debugging. This would obviously allow them to do all sorts of malicious things so it is very important you turn off interactive debugging for production sites by setting debug = false in configuration files and also that you make users of your software do the same.

总结

We've gone through the whole cycle of creating and distributing a Pylons application looking at setup and configuration, routing, models, controllers and templates. Hopefully you have an idea of how powerful Pylons is and, once you get used to the concepts introduced in this tutorial, how easy it is to create sophisticated, distributable applications with Pylons.

That's it, I hope you found the tutorial useful. You are encouraged to email any comments to the Pylons mailing list <http://groups.google.co.uk/group/pylons-discuss>_ where they will be gratefully received.

James Gardner

PyLons/QuickWiki (last edited 2009-12-25 07:08:56 by localhost)