[转载]Tutorial: Todo-List 应用

本文转载自: https://github.com/myzhan/bottle-doc-zh-cn/blob/master/docs/tutorial_app.rst

Note

这份教程是 noisefloor 编写的,并在不断完善中。

这份教程简单介绍了Bottle框架,目的是让你看完后能在项目中使用Bottle。它没有涵盖所有东西,但介绍了URL映射,模板,处理GET/POST请求等基础知识。

读懂这份教程,你不需要事先了解WSGI标准,Bottle也一直避免用户直接接触WSGI标准。但你需要了解 Python 这门语言。更进一步,这份教程中的例子需要从SQL数据库中读写数据,所以事先了解一点SQL知识是很有帮助的。例子中使用了 SQLite来保存数据。因为是网页应用,所以事先了解一点HTML的知识也很有帮助。

作为一份教程,我们的代码尽可能做到了简明扼要。尽管教程中的代码能够工作,但是我们还是不建议你在公共服务器中使用教程中的代码。如果你想要这样做,你应该添加足够的错误处理,并且加密你的数据库,处理用户的输入。

目录

目标

在这份教程结束的时候,我们将完成一个简单的,基于Web的ToDo list(待办事项列表)。列表中的每一个待办事项都包含一条文本(最长100个字符)和一个状态(0表示关闭,1表示开启)。通过网页,已开启的待办事项可以被查看和编辑,可添加待办事项到列表中。

在开发过程中,所有的页面都只可以通过 localhost 来访问,完了会介绍如何将应用部署到"真实"服务器的服务器上面,包括使用mod_wsgi来部署到Apache服务器上面。

Bottle会负责URL映射,通过模板来输出页面。待办事项列表被存储在一个SQLite数据库中,通过Python代码来读写数据库。

我们会完成以下页面和功能:

  • 首页 http://localhost:8080/todo
  • 添加待办事项: http://localhost:8080/new
  • 编辑待办事项: http://localhost:8080/edit/:no
  • 通过 @validate 修饰器来验证数据合法性
  • 捕获错误

开始之前...

安装

假设你已经安装好了Python (2.5或更改版本),接下来你只需要下载Bottle就行了。除了Python标准库,Bottle没有其他依赖。

你可通过Python的esay_install命令来安装Bottle: easy_install bottle

其它软件

因为我们使用SQLite3来做数据库,请确保它已安装。如果是Linux系统,大多数的发行版已经默认安装了SQLite3。SQLite同时可工作在Windows系统和MacOS X系统上面。Pyhton标准库中,已经包含了 sqlite3 模块。

创建一个SQL数据库

首先,我们需要先创建一个数据库,稍后会用到。在你的项目文件夹执行以下脚本即可,你也可以在Python解释器逐条执行。

import sqlite3
con = sqlite3.connect(‘todo.db‘) # Warning: This file is created in the current directory
con.execute("CREATE TABLE todo (id INTEGER PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL)")
con.execute("INSERT INTO todo (task,status) VALUES (‘Read A-byte-of-python‘,0)")
con.execute("INSERT INTO todo (task,status) VALUES (‘Visit the Python website‘,1)")
con.execute("INSERT INTO todo (task,status) VALUES (‘Test various editors for syntax highlighting‘,1)")
con.execute("INSERT INTO todo (task,status) VALUES (‘Choose your favorite WSGI-Framework‘,0)")
con.commit()

现在,我们已经创建了一个名字为 todo.db 的数据库文件,数据库中有一张名为 todo 的表,表中有 id , task , 及 status这三列。每一行的 id 都是唯一的,稍后会根据id来获取数据。 task 用于保存待办事项的文本,最大长度为100个字符。最后 status 用于标明待办事项的状态,0为开启,1为关闭。

基于Bottle的待办事项列表

为了创建我们的Web应用,我们先来介绍一下Bottle框架。首先,我们需要了解Bottle中的route,即URL映射。

route URL映射

基本上,浏览器访问的每一页面都是动态生成的。Bottle通过route,将浏览器访问的URL映射到具体的Python函数。例如,在我们访问 http://localhost:8080/todo 的时候,Bottle会查找 todo 这个route映射到了哪个函数上面,接着调用该函数来响应浏览器请求。

第一步 - 显示所有已开启的待办事项

在我们了解什么是route后,让我们来试着写一个。访问它即可查看所有已开启的待办事项。

import sqlite3
from bottle import route, run

@route(‘/todo‘)
def todo_list():
    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE ‘1‘")
    result = c.fetchall()
    return str(result)

run()

将上面的代码保存为 todo.py ,放到 todo.db 文件所在的目录。如果你想将它们分开放,则需要在 sqlite3.connect() 函数中写上 todo.db 文件的路径。

来看看我们写的代码。 1. 导入了必须的 sqlite3 模块,从Bottle中导入 route 和 run 。 2. run() 函数启动了Bottle的内置开发服务器,默认情况下,开发服务器在监听本地的8080端口。 3. route 是Bottle实现URL映射功能的修饰器。你可以看到,我们定义了一个 todo_list() 函数,读取了数据库中的数据。然后我们使用 @route(‘/todo‘) 来将 todo_list() 函数和``todo`` 这个route绑定在一起。每一次浏览器访问 http://localhost:8080/todo 的时候,Bottle都会调用 todo_list() 函数来响应请求,并返回页面,这就是route的工作方式了。

事实上,你可以给一个函数添加多个route。

@route(‘/todo‘)
@route(‘/my_todo_list‘)
def todo_list():
    ...

这样是正确的。但是反过来,你不能将一个route和多个函数绑定在一起。

你在浏览器中看到的即是你在 todo_list() 函数中返回的页面。在这个例子中,我们通过 str() 函数将结果转换成字符串,因为Bottle期望函数的返回值是一个字符串或一个字符串的列表。但 Python DB API 中规定了,数据库查询的返回值是一个元组的列表。

现在,我们已经了解上面的代码是如何工作的,是时候运行它来看看效果了。记得在Linux或Unix系统中, todo.py 文件需要标记为可执行(译者注:没有必要)。然后,通过 python todo.py 命令来执行该脚本,接着用浏览器访问http://localhost:8080/todo 来看看效果。如果代码没有写错,你应该会在页面看到以下输出。

[(2, u‘Visit the Python website‘), (3, u‘Test various editors for and check the syntax highlighting‘)]

如果是这样,那么恭喜你!如果出现错误,那么你需要检查代码时候写错,修改完后记得重启HTTP服务器,要不新的版本不会生效。

实际上,这个输出很难看,只是SQL查询的结果。所以,下一步我们会把它变得更好看。

调试和自动加载

或许你已经注意到了,如果代码出错的话,Bottle会在页面上显示一个简短的错误信息。例如,连接数据库失败。为了方便调试, 我们希望错误信息更加具体,可加上以下语句。

from bottle import run, route, debug
...
#add this at the very end:
debug(True)
run()

开启调试模式后,出错时页面会打印出完整的Python运行栈。另外,在调试模式下,模板也不会被缓存,任何对模板的修改会马上生效,而不用重启服务器。

Warning

debug(True) 是为开发时的调试服务的, 不应 在生产环境中开启调试模式。

另外一个十分有用的功能是自动加载,可修改 run() 语句来开启。

run(reloader=True)

这样会自动检测对脚本的修改,并自动重启服务器来使其生效。同上,这个功能并不建议在生产环境中使用。

使用模板来格式化输出

现在我们试着格式化脚本的输出,使其更适合查看。

实际上Bottle期望route的回调函数返回一个字符串或一个字符串列表,通过内置的HTTP服务器将其返回给浏览器。Bottle不关心字符串的内容,所以我们可以将其格式化成HTML格式。

Bottle内置了独创的模板引擎。模板是后缀名为 .tpl 的文本文件。模板的内容混合着HTML标签和Python语句,模板也可以接受参数。例如数据库的查询结果,我们可以在模板内将其漂亮地格式化。

接下来,我们要将数据库的查询结果格式化为一个两列的表格。表格的第一列为待办事项的ID,第二列为待办事项的内容。查询结果是一个元组的列表,列表中的每个元组后包含一个结果。

在例子中使用模板,只需要添加以下代码。

from bottle import route, run, debug, template
...
result = c.fetchall()
c.close()
output = template(‘make_table‘, rows=result)
return output
...

我们添加了两样东西。首先我们从Bottle中导入了 template 函数以使用模板功能,接着,我们渲染 make_table 这个模板(参数是rows=result),把模板函数的返回值赋予 output 变量,并返回 output 。如有必要,我们可添加更多的参数。

模板总是返回一个字符串的列表,所以我们无须转换任何东西。当然,我们可以将返回写为一行以减少代码量。

return template(‘make_table‘, rows=result)

对应的模板文件。

%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>所有已开启的待办事项:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>

将上面的代码保存为 make_table.tpl 文件,和 todo.py 放在同一个目录。

看看上面的代码,以%开头的行被当作Python代码来执行。请注意,只有正确的Python语句才能通过编译,要不模板就会抛出一个异常。除了Python语句,其它都是普通的HTML标记。

如你所见,为了遍历 rows ,我们两次使用了Python的 for 语句。 rows``是持有查询结果的变量,一个元组的列表。第一个 ``for 语句遍历了列表中所有的元组,第二个 for 语句遍历了元组中的元素,将其放进表格中。 for , if , while 语句都需要通过 %end 来关闭,要不会得到不正确的结果。

如果想要在不以%开头的行中访问变量,则需要把它放在两个大括号中间。这告诉模板,需要用变量的实际值将其替换掉。

再次运行这个脚本,页面输出依旧不是很好看,但是更具可读性了。当然,你可给模板中的HTML标签加上CSS样式,使其更好看。

使用GET和POST

能够查看所有代码事项后,让我们进入到下一步,添加新的待办事项到列表中。新的待办事项应该在一个常规的HTML表单中,通过GET方式提交。

让我们先来添加一个接受GET请求的route。

from bottle import route, run, debug, template, request
...
return template(‘make_table‘, rows=result)
...

@route(‘/new‘, method=‘GET‘)
def new_item():

    new = request.GET.get(‘task‘, ‘‘).strip()

    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()

    c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
    new_id = c.lastrowid

    conn.commit()
    c.close()

    return ‘<p>新的待办事项已添加到数据库中,ID为 %s</p>‘ % new_id

为了访问GET(或POST)中的数据,我们需要从Bottle中导入 request ,通过 request.GET.get(‘task‘, ‘‘).strip() 来获取表单中 task 字段的数据。可多次使用 request.GET.get() 来获取表单中所有字段的数据。

接下来是对数据的操作:写入数据库,获取返回的ID,生成页面。

因为我们是从HTML表单中获取数据,所以现在让我们来创建这个表单吧。我们通过 /new 这个URL来添加待办事项。

...
@route(‘/new‘, method=‘GET‘)
def new_item():

if request.GET.get(‘save‘,‘‘).strip():

    new = request.GET.get(‘task‘, ‘‘).strip()
    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()

    c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
    new_id = c.lastrowid

    conn.commit()
    c.close()

    return ‘<p>新的待办事项已添加到数据库中,ID为 %s</p>‘ % new_id
else:
    return template(‘new_task.tpl‘)

对应的 new_task.tpl 模板如下。

<p>添加一个待办事项:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>

如你所见,这个模板只是纯HTML的,不包含Python代码。这样,我们就完成了添加待办事项这个功能。如果你想通过POST来获取数据,那么用 request.POST.get() 来代替 request.GET.get() 就行了。

修改已有待办事项

最后,我们需要做的是修改已有待办事项。

仅使用我们当前了解到的route类型,是可以完成这个任务的,但太取巧了。Bottle还提供了一种 动态route ,可以更简单地实现。

基本的动态route声明如下:

@route(‘/myroute/:something‘)

关键的区别在于那个冒号。它告诉了Bottle,在下一个 / 之前, :something 可以匹配任何字符串。 :something 匹配到的字符串会传递给回调函数,进一步地处理。

在我们的待办事项应用里,我们创建一个route( @route(‘edit/:no‘) ), no 是待办事项在数据库里面的ID。

对应的代码如下。

@route(‘/edit/:no‘, method=‘GET‘)
def edit_item(no):

    if request.GET.get(‘save‘,‘‘).strip():
        edit = request.GET.get(‘task‘,‘‘).strip()
        status = request.GET.get(‘status‘,‘‘).strip()

        if status == ‘open‘:
            status = 1
        else:
            status = 0

        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit, status, no))
        conn.commit()

        return ‘<p>ID为%s的待办事项已更新</p>‘ % no
    else:
        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template(‘edit_task‘, old=cur_data, no=no)

这和之前的添加待办事项类似,主要的不同点在于使用了动态的route( :no ),它可将ID传给route对应的回调函数。如你所见,我们在 edit_item 函数中使用了 no ,从数据库中获取数据。

对应的 edit_task.tpl 模板如下。

%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>

再一次,模板中混合了HTML代码和Python代码,之前已解释过。

你也可在动态route中使用正则表达式,稍后会提及。

验证动态route

在某些场景下,需要验证route中的可变部分。例如,在上面的例子中,我们的 no 需要是一个整形数,如果我们的输入是一个浮点数,或字符串,Python解释器将会抛出一个异常,这并不是我们想要的结果。

对应上述情况,Bottle提供了一个 @validate 修饰器,可在用户输入被传递给回调函数之前,检验用户数据的合法性。代码例子如下。

from bottle import route, run, debug, template, request, validate
...
@route(‘/edit/:no‘, method=‘GET‘)
@validate(no=int)
def edit_item(no):
...

首先,我们从Bottle中导入了 validate ,然后在route中使用了。在这里,我们验证 no 是否是一个整形数。基本上,validate 可用于其它类型,例如浮点数,列表等等。

保存更改,如果用户提供的 :no 不是一个整形数,而是一个浮点数或其他类型,将返回一个"403 forbidden"页面,而不是抛出异常。

在动态route中使用正则表达式

Bottle允许在动态route中使用正则表达式。

我们假设需要通过 item1 这样的形式来访问数据库中id为1的待办事项。显然,我们不想为每个待办事项都创建一个route。鉴于route中的"item"部分是固定的,简单的route就无法满足需求了,我们需要在route中使用正则表达式。

使用正则表达式的解决方法如下。

@route(‘/item:item#[1-9]+#‘)
def show_item(item):
    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
    result = c.fetchall()
    c.close()
    if not result:
        return ‘该待办事项不存在!‘
    else:
        return ‘待办事项: %s‘ %result[0]

当然,这个例子是我们想象出来的,去掉"item1"中的"item",直接使用"1"会更简单。虽然如此,我们还是想为你展示在route的正则表达式: @route(/item:item_#[1-9]+#) 和一个普通的route差不多,但是在两个"#"中的字符就是一个正则表达式,匹配从0到9的数字。在处理"/item9"这样的请求的时候,正则表达式会匹配到"9",然后将"9"做为item参数传递给show_item函数,而不是"item9"。

返回静态文件

有时候,我们只是想返回已有的静态文件。例如我们的应用中有个静态的帮助页面help.html,我们不希望每次访问帮助页面的时候都动态生成。

from bottle import route, run, debug, template, request, validate, static_file

@route(‘/help‘)
def help():
    return static_file(‘help.html‘, root=‘/path/to/file‘)

首先,我们需要从Bottle中导入 static_file 函数。它接受至少两个参数,一个是需要返回的文件的文件名,一个是该文件的路径。即使该文件和你的应用在同一个目录下,还是要指定文件路径(可以使用".")。Bottle会猜测文件的MIME类型,并自动设置。如果你想显式指定MIME类型,可以在static_file函数里面加上例如 mimetype=‘text/html‘ 这样的参数。 static_file 函数可和任何route配合使用,包括动态route。

返回JSON数据

有时我们希望返回JSON,以便在客户端使用JavaScript来生成页面,Bottle直接支持返回JSON数据了。

我们假设现在需要返回JSON数据。

@route(‘/json:json#[1-9]+#‘)
def show_json(json):
    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {‘task‘:‘对应的待办事项不存在‘}
    else:
        return {‘Task‘: result[0]}

很简单,只需要返回一个Python中的字典就可以了,Bottle会自动将其转换为JSON,再传输到客户端。如果你访问"http://localhost/json1",你应能得到 {"Task": ["Read A-byte-of-python to get a good introduction into Python"]}类型的JSON数据。

捕获错误

为了避免用户看到出错信息,我们需要捕获应用运行时出现的错误,以提供更友好的错误提示。Bottle提供了专门用于捕获错误的route。

例如,我们想捕获403错误。

from bottle import error

@error(403)
def mistake(code):
    return ‘参数格式错误!‘

首先,我们需要从Bottle中导入 error ,然后通过 error(403) 来定义创建一个route,用于捕获所有"403 forbidden"错误。注意,该route总是会将error-code传给 mistake() 函数,即使你不需要它。所以回调函数至少要接受一个参数,否则会失效。

一样的,同一个回调函数可以捕获多种错误。

@error(404)
@error(403)
def mistake(code):
    return ‘出错啦!‘

效果和下面一样。

@error(403)
def mistake403(code):
    return ‘参数格式错误!‘

@error(404)
def mistake404(code):
    return ‘该页面不存在!‘

总结

通过以上章节,你应该对Bottle框架有了一个大致的了解,可以使用Bottle进行开发了。

接下来的章节会简单介绍一下,如何在大型项目中使用Bottle。此外,我们还会介绍如何将Bottle部署到更高性能的Web服务器上。

部署服务器

到目前为止,我们还是使用Bottle内置的,随Python一起发布的 WSGI reference Server 服务器。尽管该服务器十分适合用于开发环境,但是它确实不适用于大项目。在我们介绍其他服务器之前,我们先看看如何优化内置服务器的设置。

更改服务器的端口和IP

默认的,Bottle会监听127.0.0.1(即 localhost )的 8080 端口。如果要更改该设置,更改 run 函数的参数即可。

更改端口,监听80端口

run(port=80)

更改IP地址

run(host=‘123.45.67.89‘)

可同时使用

run(port=80, host=‘123.45.67.89‘)

当Bottle运行在其他服务器上面时, port 和 host 参数依然适用,稍后会介绍。

在其他服务器上运行

在大型项目上,Bottle自带的服务器会成为一个性能瓶颈,因为它是单线程的,一次只能响应一个请求。Bottle已经可以工作在很多多线程的服务器上面了,例如 CherrypyFapws3Flup 和 Paste ,所以我们建议在大型项目上使用高性能的服务器。

如果想运行在Paste服务器上面,代码如下(译者注:需要先安装Paste)。

from bottle import PasteServer
...
run(server=PasteServer)

其他服务器如 FlupServerCherryPyServer 和 FapwsServer 也类似。

使用 mod_wsgi 运行在Apache上

或许你已经有了一个 Apache 服务器,那么可以考虑使用 mod_wsgi 。

我们假设你的Apache已经能跑起来,且mod_wsgi也能工作了。在很多Linux发行版上,都能通过包管理软件简单地安装mod_wsgi。

Bottle已经自带用于mod_wsgi的适配器,所以让Bottle跑在mod_wsgi上面是很简单的。

接下来的例子里,我们假设你希望通过 http://www.mypage.com/todo 来访问"ToDo list"这个应用,且代码、模板、和SQLite数据库存放在 /var/www/todo 目录。

如果通过mod_wsgi来运行你应用,那么必须从代码中移除 run() 函数。

然后,创建一个 adapter.wsgi 文件,内容如下。

import sys, os, bottle

sys.path = [‘/var/www/todo/‘] + sys.path
os.chdir(os.path.dirname(__file__))

import todo # This loads your application

application = bottle.default_app()

将其保存到 /var/www/todo 目录下面。其实,可以给该文件起任何名字,只要后缀名为 .wsgi 即可。

最后,我们需要在Apache的配置中添加一个虚拟主机。

<VirtualHost *>
    ServerName mypage.com

    WSGIDaemonProcess todo user=www-data group=www-data processes=1 threads=5
    WSGIScriptAlias / /var/www/todo/adapter.wsgi

    <Directory /var/www/todo>
        WSGIProcessGroup todo
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

重启Apache服务器后,即可通过 http://www.mypage.com/todo 来访问你的应用。

结语

现在,我们这个教程已经结束了。我们学习了Bottle的基础知识,然后使用Bottle来写了第一个应用。另外,我们还介绍了如何在大型项目中使用Bottle,以及使用mod_wsgi在Apache中运行Bottle应用。

我们并没有在这份教程里介绍Bottle的方方面面。我们没有介绍如何上传文件,验证数据的可靠性。 还有,我们也没介绍如何在模板中调用另一个模板。以上,可以在 Bottle documentation 中找到答案。

完整代码

译者注:以下内容不翻译

todo.py 文件

import sqlite3
from bottle import route, run, debug, template, request, validate, static_file, error

# only needed when you run Bottle on mod_wsgi
from bottle import default_app

@route(‘/todo‘)
def todo_list():

    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE ‘1‘;")
    result = c.fetchall()
    c.close()

    output = template(‘make_table‘, rows=result)
    return output

@route(‘/new‘, method=‘GET‘)
def new_item():

    if request.GET.get(‘save‘,‘‘).strip():

        new = request.GET.get(‘task‘, ‘‘).strip()
        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()

        c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
        new_id = c.lastrowid

        conn.commit()
        c.close()

        return ‘<p>The new task was inserted into the database, the ID is %s</p>‘ % new_id

    else:
        return template(‘new_task.tpl‘)

@route(‘/edit/:no‘, method=‘GET‘)
@validate(no=int)
def edit_item(no):

    if request.GET.get(‘save‘,‘‘).strip():
        edit = request.GET.get(‘task‘,‘‘).strip()
        status = request.GET.get(‘status‘,‘‘).strip()

        if status == ‘open‘:
            status = 1
        else:
            status = 0

        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit,status,no))
        conn.commit()

        return ‘<p>The item number %s was successfully updated</p>‘ %no

    else:
        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template(‘edit_task‘, old = cur_data, no = no)

@route(‘/item:item#[1-9]+#‘)
def show_item(item):

        conn = sqlite3.connect(‘todo.db‘)
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
        result = c.fetchall()
        c.close()

        if not result:
            return ‘This item number does not exist!‘
        else:
            return ‘Task: %s‘ %result[0]

@route(‘/help‘)
def help():

    static_file(‘help.html‘, root=‘.‘)

@route(‘/json:json#[1-9]+#‘)
def show_json(json):

    conn = sqlite3.connect(‘todo.db‘)
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {‘task‘:‘This item number does not exist!‘}
    else:
        return {‘Task‘: result[0]}

@error(403)
def mistake403(code):
    return ‘There is a mistake in your url!‘

@error(404)
def mistake404(code):
    return ‘Sorry, this page does not exist!‘

debug(True)
run(reloader=True)
#remember to remove reloader=True and debug(True) when you move your application from development to a productive environment

Template make_table.tpl:

%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>

Template edit_task.tpl:

%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>

Template new_task.tpl:

%#template for the form for a new task
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>
时间: 2024-08-03 11:04:52

[转载]Tutorial: Todo-List 应用的相关文章

代码中特殊的注释技术——TODO、FIXME和XXX的用处 (转载)

转自:http://blog.csdn.net/reille/article/details/7161942 作者:reille 本博客网址:http://blog.csdn.net/reille/,转载本博客原创文章请注明出处. 本文内容概要: 代码中特殊的注释技术——TODO.FIXME和XXX的用处. 更多请关注:http://velep.com/ ——————————————————————————————————————————————————————————————————— 前言:

代码中特殊的注释技术——TODO、FIXME和XXX的用处 (转载)

转自:http://blog.csdn.net/reille/article/details/7161942 作者:reille 本博客网址:http://blog.csdn.net/reille/,转载本博客原创文章请注明出处. 本文内容概要: 代码中特殊的注释技术——TODO.FIXME和XXX的用处. 更多请关注:http://velep.com/ ——————————————————————————————————————————————————————————————————— 前言:

[转载] CMake Official Tutorial——教程还是官方的好

CMake官方教程传送门:https://cmake.org/cmake-tutorial/ 以下的内容跟官方教程基本一致,少数地方根据自己的测试有所改动: A Basic Starting Point (Step1) The most basic project is an executable built from source code files. For simple projects a two line CMakeLists.txt file is all that is requ

【转载】Ogre:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构

原文:Beginner Tutorial 1: SceneNode, Entity,和SceneManager 结构 先决条件 这个教程假设你有C++编程的基础并且可以配置并编译OGRE应用程序 (如果你在配置环境方面有问题,请看OGRE + MinGW + Code::Blocks环境的搭建). 除了配置环境之外,你不需要有任何关于OGRE的知识. 介绍 在这个教程中,我会介绍给你OGRE中最基本的结构: SceneManager, SceneNode, 还有Entity 对象.我们不会涉及大

【转载】C# Tutorial - Simple Threaded TCP Server

http://tech.pro/tutorial/704/csharp-tutorial-simple-threaded-tcp-server In this tutorial I'm going to show you how to build a threaded tcp server with C#. If you've ever worked with Window's sockets, you know how difficult this can sometimes be. Howe

【Todo】【转载】Scala中Array, List, Tuple的区别

参考了这篇文章: https://my.oschina.net/u/1034176/blog/512314 1. 在Scala 2.7中,Array.List都不能混合类型,只有Tuple可以:而在Scala以上版本中,3者的元素都可以混合不同的类型(转化为Any类型),只不过是当使用混合类型时,Array和List会将元素类型转化为Any类型,而Tuple则保留每一个元素的初始类型. 2. 可变性 3. 关于初始化 Array 1) val array= new Array[String](3

TODO、FIXME和XXX转载

代码中特殊的注释技术——TODO.FIXME和XXX的用处 // TODO Auto-generated method stub Task Tags可以做什么?

【Todo】【转载】全栈工程师-Hadoop, HBase, Hive, Spark

学习参考这篇文章: http://www.shareditor.com/blogshow/?blogId=96 机器学习.数据挖掘等各种大数据处理都离不开各种开源分布式系统, hadoop用于分布式存储和map-reduce计算, spark用于分布式机器学习, hive是分布式数据库, hbase是分布式kv系统, 看似互不相关的他们却都是基于相同的hdfs存储和yarn资源管理, 本文通过全套部署方法来让大家深入系统内部以充分理解分布式系统架构和他们之间的关系.

【Todo】【转载】Java中如何写一段内存泄露的程序

可以参考这段文章: link