## page was renamed from PyLons/QuickWiki ##language:zh <> = QuickWiki Tutorial [ for PyLons ] = * [[http://www.PyLonshq.com/docs/0.9.1/quick_wiki.html|原文]] * 作者: James Gardner * 翻译: [[HuangYi]] * [[PyWebFrameList]], [[PyLonsQuickIn]], [[PasteQuickIn]] == 简介 == 请先阅读 [[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]] 两份指南 , 如果你还没读过的话 :) 在这个教程中我们要使用 PyLons0.9 和 SQLAlchemy 从头创建一个可以工作的 wiki. 我们的 wiki 可以允许用户添加,修改,删除带有格式的wiki页面. == 从成品开始 Starting at the End == PyLons 的设计目标是要方便所有人, 而不仅仅是开发者, 所有让我们以 QuickWiki 最终用户的身份下载并安装一个 QuickWiki 的成品先. 等我们发掘完它的特性后, 我们就从头开始编写它. 先安装 [[http://peak.telecommunity.com/DevCenter/EasyInstall|Easy Install]] , 然后运行以下命令来安装 QuickWiki 并创建一个配置文件: {{{ > easy_install QuickWiki > paster make-config QuickWiki test.ini }}} 下面编辑该配置文件, 在 {{{[app:main]}}} 节中指定 {{{dsn}}} 变量, 让数据源的名称指向你希望使用的数据库. * 注意: 默认的数据源名称 {{{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]] 最后, 创建数据表并启动该成品: {{{ > paster setup-app test.ini > paster serve test.ini }}} 这样就OK了 ! 现在你可以访问 http://127.0.0.1:5000 并实验这个 Wiki 成品. 注意, 在标题列表页, 只要将页面标题拖到垃圾区域, 就可以通过 Ajax 调用删除它. 弄完这些以后, 使用 {{{CTRL+C}}} 停止服务器, 因为我们要开始开发我们自己的版本了. == 开发 QuickWiki == 如果你跳过了上面这一节那你需要安装 PyLons 0.9 先: {{{ > easy_install -U PyLons[full]==0.9 }}} 然后创建你的 project : {{{ > paster create --template=PyLons QuickWiki }}} 我们启动服务器看看现在有了点啥: {{{ > cd QuickWiki > paster serve --reload development.ini }}} * 注意: 我们使用了{{{--reload}}}选项启动服务器. 这意味着这以后我们对代码做的任何更改都会导致服务器重启(如果有必要重启的话); 你的改变可以实时得在当前站点上反映出来. 开一个新的控制台并执行{{{ cd QuickWiki/quickwiki }}}. 访问 http://127.0.0.1:5000 你就可以看到介绍页面. 删除文件 {{{public/index.html}}} ,因为我们想看到 wiki 的首页而不是欢迎页面. 如果你现在就刷新页面的话, PyLons 内置的错误文档会起作用并显示一个 {{{ Error 404 }}} 的页面, 告诉你该文件找不到. 待会我们会建立一个 controller 来处理对这个地址的访问. == Model == PyLons 使用的是一个 Model View Controller 的架构; 我们从创建 model 开始. 我们可以使用任何我们喜欢的系统来作为我们的 model 层, 包括 [[http://www.sqlobject.org|SQLObject]] 或者 [[http://www.sqlalchemy.org|SQLAlchemy]]. 下面我们将使用 SQLAlchemy 来开发 QuickWiki. * 注意: SQLAlchemy 是一个 Python SQL工具包 和 Object Relational Mapper , 它正迅速成为许多 PyLons 开发者的默认选择. 它提供了一套完整的众所周知的企业级持久层模式的实现, 设计目标是高效和高性能的数据库访问, 并向外提供一个简单的Pythonic的领域语言 (adapted into a simple and Pythonic domain language). 在 SQLAlchemy 的网站 http://sqlalchemy.org/docs/ 上有完整的详细的文档, 强烈建议在你大量使用 SQLAlchemy 之前看看这些文档先. 将 {{{models/__init__.py}}} 编辑成下面这样: {{{#!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='') ) }}} 前面两行将 SQLAlchemy 设置为支持线程, 并导入一些有用的对象, 比如 {{{Table}}} 和 {{{Column}}}. 第二行 {{{meta = DynamicMetaData()}}} 是让我们可以在以后将数据表绑定到特定的数据库上去. 然后我们定义了一个叫做 {{{pages}}} 的表, 它有两列: {{{title}}} 和 {{{content}}}. * 注意: SQLAlchemy 也支持直接从数据库表中反射这些信息, 如果我们已经创建好了数据库的话,SQLAlchemy 可以自动构造 {{{Table}}} 对象. SQLAlchemy 一个核心的哲学就是 数据表 和 领域类 是两个不同的东西. 下面, 我们就要创建一个 Python 类, 它表示 wiki 中的页面,并且会通过一个 mapper 将一个领域对象映射为 {{{pages}}} 表中的一行. 将下面这些代码添加到 {{{models/__init__.py}}} 的底部: {{{#!python class Page(object): def __str__(self): return self.title page_mapper = mapper(Page, pages_table) }}} 稍微想远一点, 我们的 wiki 将会需要一些格式, 所以我们需要将 {{{content}}} 字段转换成 HTML. 任何 WikiWords (就是那些由一个大写字母和几个小写字母组成的单词连接起来组成的 which are words made by joining together two or more lowercase words with the first letter capitalized) 也需要转换成超链接. 要是我们可以给我们的 {{{Page}}} 对象添加一个方法,返回格式化好的 HTML 该多好啊. 将以下代码加到 {{{models/__init__.py}}} 的头部: {{{#!python from PyLons import h import re wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") from docutils.core import publish_parts }}} 然后给{{{Page}}}对象添加一个 {{{get_wiki_content()}}} 方法, 像这样: {{{#!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 }}} 这些代码值得解释一下.{{{content = None}}}这一行是在创建一个新的{{{Page}}}对象的时候将属性{{{content}}}初始化为{{{None}}}. 如果{{{Page}}}对象表示{{{pages}}}表中的一行,那么{{{self.content}}}就是字段{{{content}}}的值. {{{h.link_to()}}} 和 {{{h.url_for()}}} 是标准的 PyLons helpers, 这两个函数创建指向特定controller actions的链接. 现在我们已经决定所有 WikiWords 应该链接到 {{{page}}} controller 的 {{{index}}} action , 这个 controller 我们会在后面创建. 最后再做一点修改, 既然我们使用到了 docutils 和 SQLAlchemy 两个第三方的库, 那么我们还需要修改下我们的 {{{setup.py}}} 文件, 这样当别人通过[[http://peak.telecommunity.com/DevCenter/EasyInstall|Easy Install]] 安装 QuickWiki 的时候就会自动安装依赖的这些库. 修改项目根目录下的{{{setup.py}}}, 将其中 {{{install_requires}}} 修改为如下: {{{#!python install_requires=["PyLons>=0.9", "docutils==0.4", "SQLAlchemy>=0.2.6"], }}} 既然我们正在修改 {{{setup.py}}} ,干脆把其他一些修改也一起做了吧. 将版本号设置为 0.1.1, 并添加一个描述和URL, 当我们把它发布在Python Cheeseshop 上的时候会用得上: {{{#!python version="0.1.1", description="Result of following the PyLons 0.9 Tutorial", url="http://www.PyLonshq.com/docs/0.9/quick_wiki.html", }}} 我们可能也希望制作一个完整的发布版而不是一个开发版, 这样的话我们就把 {{{setup.cfg}}} 里的下面几行移除: {{{ [egg_info] tag_build = dev tag_svn_revision = true }}} 要想测试下依赖库的自动安装的话, 运行下面的命令, 他们会自动安装 docutils 和 SQLAlchemy ,如果你还没有装过的话: {{{ > python setup.py develop }}} * 注意: 使用命令 {{{python setup.py develop}}} 安装该应用程序, 和最终用户通过egg文件安装的情形是一样的. 当你开发应用程序的时候这是很有用的,因为你不用每次修改了程序后想测试一下的时候都必须另外创建一个egg并安装才行. == 连接数据库 == 你的项目中所有的 controllers 默认都继承自 {{{lib/base.py}}} 文件中的 {{{BaseController}}}. 我们要利用它来给所有 controllers 加上对 SQLAlchemy 的支持. 将下面这些代码加在 {{{lib/base.py}}} 的 import 语句后面,覆盖已有的 {{{BaseController}}} 类: {{{#!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 }}} 现在每次用户请求, SQLAlchemy的对象都会连接到数据库了. == 配置 安装 == 现在让我们再做一点修改, 目的是让最终用户能够将 QuickWiki 安装起来. 将 {{{websetup.py}}} ( 命令 {{{paster setup-app}}} 将会使用该函数 ) 修改为下面这样: {{{#!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." }}} {{{from quickwiki.models import *}}} 这一行导入我们的 {{{page}}} 表, {{{meta.connect(app_conf['dsn'])}}} 这一行使用 用户在配置文件中指定的 DSN 连接数据库. 最后 {{{meta.create_all()}}} 创建数据表. 数据表创建完成后,代码还为简单的 wiki 首页添加了点数据. 要测试上面这个功能的话, 你首先需要安装你的 QuickWiki (如果你还没这么做过的话), 目的是让 {{{paster}}} 找到我们正在开发的版本而不是我们在教程最开始的时候安装的版本: {{{ > python setup.py develop }}} 然后你就可以编辑你的 {{{development.ini}}} , 并添加你的数据库 DSN: {{{ [app:main] use = egg:quickwiki dsn = postgres://@localhost/quickwiki_test }}} 你还需要修改 {{{QuickWiki.egg_info/paste_deploy_config.ini_tmpl}}} 如下, 这样当用户运行命令 {{{paster make-config}}}时 , 生成的配置文件总是会有一个节提示他们输入他们自己的数据库的DSN , 就像我们在一开始安装 QuickWiki 成品时所做的一样: {{{ [app:main] use = egg:quickwiki # Specify your own database connection here dsn = postgres://@localhost/quickwiki_test }}} 然后你就可以运行命令 {{{paster setup-app}}} 来建立你的数据表,就和最终用户将会做的一样. 如果以前版本的程序已经创建过数据表的话, 记得要删除并重建数据库. (译注:如果没有修改过model的话,就不需要) {{{ > paster setup-app development.ini }}} * 注意: 关于具体支持哪些 DSN 的信息请查看第一段中关于 SQLAlchemy 的 "注意" , 和包含在其中的指向 SQLAlchemy文档中关于DSN不同选项部分的链接. == Templates == * 注意: PyLons 默认使用 Myghty 模板语言, 但是只要你愿意你可以方便地使用其他的模板语言, 同时不影响大部分 PyLons 的功能. PyLons另外还支持 Kid 和 Cheetah * 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. 我们会在我们的项目中用到 Myghty 模板语言 的一个叫做继承的特性. 首先在 {{{templates/autohandler}}} 中添加基页面模板: {{{ QuickWiki <% h.stylesheet_link_tag('/quick.css') %>
% m.call_next()
}}} 所有其他的模板会自动地被插入到 {{{% m.call_next()}}} 这行, 并且当我们在控制器中调用 {{{render_response()}}} 的时候会返回整个页面, 这样我们就可以方便地给我们所有的模板应用统一的主题了. 如果你有兴趣学习 Myghty模板 的其他一些特性的话可以去看看易懂的 [[http://www.myghty.org/docs/|Myghty 文档]]. 现在我们只需要理解那一行会被子模板替换 和 任何在 {{{<%}}} 和 {{{%>}}} 之间的部分会被当作python程序执行然后被替换成执行结果. 在一行的第一个字符处写 {{{%}}} 等于是用 {{{<%}}} 和 {{{%>}}} 把整行扩起来. 这个 autohandler 用到几个被附在 {{{h}}} 对象上的 helper 函数. 这些函数在[[http://PyLonshq.com/WebHelpers/module-index.html|WebHelpers 文档]]中有详细描述. 你还可以向 {{{h}}} 上附加更多的helpers, 只需把他们写到 {{{lib/helpers.py}}} 中即可, 虽然这个项目中我们还不需要. * 警告:如果在 {{{%}}} 字符前有空格的话, 你的模板将不起作用. == Routing (url dispatcher) == 在我们添加任何 action 之前我们希望能够将请求正确的路由( route )到这些 action . 编辑 {{{config/routing.py}}} 文件, 把 route map 改成这样: {{{#!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') }}} 关于强大的将请求路由(route) 到 controllers 和 actions 的功能的完整文档在这里: [[http://routes.groovie.org/docs/|Routes文档]]. == Controllers == 简单回顾: 我们已经建立了 model, 配置好了应用程序, 添加了 routes 并在 autohandler 中设置了基模板, 现在我们需要编写项目了! 在你的项目的根目录下使用下面这个命令添加一个叫做{{{page}}}的 controller : {{{ > paster controller page }}} 我们需要下面几个 actions: {{{#!python 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! 在 page controller {{{controller/page.py}}} 的顶端紧靠现有 import 语句下面的地方添加以下几个 import 语句: {{{#!python import sqlalchemy.mods.threadlocal from sqlalchemy import objectstore }}} 将这个方法添加到 {{{PageController}}} 类, 在每个请求到来之时为 {{{Page}}} 创建一个 {{{query}}} 对象: {{{#!python def __before__(self): self.query = objectstore.query(model.Page) }}} * 注意: {{{__before__()}}} 是一个特殊的 action, 每次请求到来地时候, 在调用实际 action 之前都会先调用 {{{__defore__}}, 还有一个 {{{__after__}}} 则是在调用实际 action 之后调用. === index() === 用下面代码替换 {{{index()}}} action: {{{#!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) }}} 添加一个叫 {{{templates/page.myt}}} 的模板,内容如下: {{{

<% c.title %>

<% c.content %> }}} 这个模板只是简单地显示页面的 title 和 content. * 注意: PyLons 自动把所有 action 参数赋给 context object {{{c}}}, 这样你就不用亲自动手了. 这样, {{{title}}} 的值自动赋给 {{{c.title}}} , 可以直接在模板中使用了. 我们还得在 controller 中手动给 {{{c.content}}} 赋值 (译注: 对象 {{{c}}} 可以直接在模板中使用, 用来向模板中传递数据) 我们还需要为页面不存在的情况编写一个模板. 需要在上面显示一条信息和一个到 edit action 的链接, 这样该页面才可以被创建. 添加一个叫 {{{templates/new_page.myt}}} 的模板, 内容如下: {{{

<% c.title %>

This page doesn't exist yet. Create the page.

}}} 现在我们可以测试我们的 QuickWiki 了 , 看看现在怎么样了. 如果你还没有把服务器运行起来的话,使用下面命令运行它: {{{ > paster serve --reload development.ini }}} 访问 http://127.0.0.1:5000/ 你就可以看到 wiki 的首页了. 我们可以添加一个已经在 {{{templates/autohandler}}} 中链进来了的 css 把它变漂亮一点点. 把以下内容加到文件 {{{public/quick.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; } }}} 刷新页面, 这时我们便有了一个更漂亮的 wiki 了. 你会注意到单词 {{{QuickWiki}}} 已经被 {{{Page}}} 领域对象的 {{{get_wiki_content()}}} 方法转换成一个超链接了. 你可以点击该链接然后会看到一个来自 {{{new_page.myt}}} 模板的新建页面的页面. 如果你点击 {{{Create the page}}} 链接你会看到 PyLons 的错误处理起作用并提示你 {{{Action edit is not implemented}}}. Well, 下面我们最好把它加上, 不过加它之前, 我们先玩一玩这个 [[http://www.PyLonshq.com/docs/0.9.1/interactive_debugger.html|interactive debugger]], 试着点击 {{{+}}} 或者 {{{>>}}} 箭头 , 你就可以交互式地调试你的应用程序, 这是一个相当有用的工具. === edit() === 要编辑 wiki 页面, 我们需要先从数据库中取得没被转成HTML的内容, 并把它显示在一个简单的 form 里面等候编辑. 添加 {{{edit()}}} action: {{{#!python def edit(self, title): page = self.query.get_by(title=title) if page: c.content = page.content return render_response('/edit.myt') }}} 并创建 {{{templates/edit.myt}}} 文件: {{{

Editing <% c.title %>

<% 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)%>
<% h.submit(value="Save changes", name='commit') %> <% h.end_form() %> }}} * 注意: 你可能已经注意到我们只在页面存在的情况下设置 {{{c.content}}}, 但是在页面不存在的情况下 {{{c.content}}} 被 {{{h.text_area}}} 访问也没有抛出 {{{AttributeError}}} 异常. 我们其实是利用了这么一个规定: 对象 {{{c}}} 任何被访问的属性总是默认返回空字符串 {{{""}}} . 这是对象 {{{c}}} 一个很有用的特性, 不过如果你期望的不是这种行为的话可能会让你很郁闷( catch you out ) 我们使用到 {{{h}}} 对象来创建我们的 form 和 field 对象. 这个节省了一点点手工编写 HTML 的时间. 该 form 提交到 {{{save()}}} action 以保存刚刚新建的或更新过的内容, 下面我们来编写 {{{save()}}} . === save() === {{{save()}}} action 需要做的第一件事就是要检查要保存的页面是否已经存在. 如果不存在则使用 {{{page = model.Page()}}} 创建新页面. 然后再更新其内容. 在 PyLons 中你可以从 form 提交 中获取请求参数, 从相应名字的 {{{request.params}}} 对象中可以获取 GET 或者 POST 请求. 添加 {{{save()}}} action: {{{#!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') }}} * 注意: {{{request.params}}} 是一个 MultiDict 对象; 一个排好序了的字典, 其中一个 key 可以对应多个值. 使用正常的 dict 访问方法 ({{{params[key]}}}, {{{params.get()}}} 和 {{{params.setdefault()}}}) 访问 MultiDict 可以获取已存在的 key 对应的多个 value 中的一个, 如果想得到多个 value, 使用 {{{getall()}}} 方法, 它会通过一个列表中返回所有 value. 为了让 {{{page.myt}}} 模板在页面保存以后显示 {{{Successfully saved}}} 信息, 我们需要修改 {{{templates/page.myt}}} 文件. 在 {{{

<% c.title %>

}}} 后面添加下面几行, 确保字符 {{{%}}} 前面没有空白: {{{ % if c.message:

<% c.message %>

% #end if }}} 现在我们有了一个功能完整的 wiki , 你可以创建并编辑页面, 最终用户还可以通过简单的命令进行安装部署. 访问 http://127.0.0.1:5000 试试看. 要是有一个标题列表, 可以在里面删除页面该多好啊, 下面我们就来增加这个功能 ! === list() === 添加 {{{list()}}} action: {{{#!python def list(self): c.titles = [page.title for page in self.query.select()] return render_response('/titles.myt') }}} {{{list()}}} action 简单地从数据库中获取所有页面. 我们创建 {{{templates/titles.myt}}} 文件来显示该列表: {{{

Title List

}}} 最后编辑 {{{templates/autohandler}}} , 添加一个到标题列表页的链接, 这样页面底部就像这样: {{{ }}} 如果你访问 http://127.0.0.1:5000/page/list 你应该可以看到完整的标题列表并且你应该可以访问到每个页面. === delete() === 既然设计这个教程的目的是让你熟悉尽可能多的 PyLons核心功能, 那我们要使用一些 AJAX 来允许用户从标题列表拖动一个标题到垃圾区, 并自动删除该 wiki 页. 把下面这行添加到 {{{templates/autohandler}}} 中 {{{}}} 的前面: {{{ <% h.javascript_include_tag('/javascripts/effects.js', builtins=True) %> }}} * 注意: {{{h.javascript_include_tag()}}} helper 会创建指向所有我们需要的内置 JavaScript 的 link, 另外再添加 {{{/javascripts/effects.js}}} , 生成的 HTML 是这个样子: {{{ }}} 如果你查看 {{{config/middleware.py}}} , 你会看到这么几行: {{{#!python javascripts_app = StaticJavascripts() ... app = Cascade([static_app, javascripts_app, app]) }}} WSGI 应用程序 {{{javascripts_app}}} 将所有对 {{{/javascripts/}}} 的请求直接映射为 WebHelpers 包中对应的 JavaScript.这意味这你不需要手动将 PyLons JavaScript 文件复制到你的项目, 并且如果你升级 PyLons , 你自动便可以使用最新的 scripts. 现在我们来编写 AJAX ! 我们想要标题列表中所有的标题是可拖动的, 我们把它们每一个都用一个 id 唯一 {{{}}} 元素包起来. 编辑 {{{templates/titles.myt}}} 把这一行: {{{ <% title %> [<% h.link_to('visit', h.url_for(title=title, action="index")) %>] }}} 换成 {{{ <% title %>  [<% h.link_to('visit', h.url_for(title=title, action="index")) %>] <% h.draggable_element("page-"+ str(title), revert=True) %> }}} 该代码将每一个标题变成可拖动的元素, 如果没有拖到拖曳目标的话, 会自动回到原始位置. 如果想要删除页面的话, 我们最好还是添加一个拖曳目标. 现在你已经可以访问 http://127.0.0.1:5000/page/list 拖页面标题玩. 你应该能感受到这一个 helper 函数: {{{h.draggable_element()}}}, 提供了多少功能 ! 我们最好准备个拖曳目标, 把标题拖到那里就自动删掉相应页面, 在 {{{templates/titles.myt}}} 中 {{{