前面的话
在Demo1
里面,我们练习了如何部署应用、tornado
框架的基本结构以及应用如何处理请求。
其实Demo1
算不上一个博客啦。一个最基本的信息系统一定要包含对数据库的增
、删
、改
和查
。所以这次,我们来将Demo1
升级为Demo2
,添加上基本的增删改查
。
源码
终于=。=在github上创建了项目,把源码传上去了。有需要的同学自己去下载吧。
https://github.com/cAntCheng/simple_tutorial_of_tornado
呐,还是把源码在这里贴一下
demo.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os.path
import tornado.auth
import tornado.escape
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
import pymongo
define("port", default=8002, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/edit/([0-9Xx\-]+)", EditHandler),
(r"/add", EditHandler),
(r"/delete/([0-9Xx\-]+)", DelHandler),
(r"/blog/([0-9Xx\-]+)", BlogHandler),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=True,
)
conn = pymongo.Connection("localhost", 27017)
self.db = conn["demo2"]
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
def get(self):
import time
coll = self.application.db.blog
blogs = coll.find().sort("id",pymongo.DESCENDING)
self.render(
"index.html",
blogs = blogs,
time = time,
)
class EditHandler(tornado.web.RequestHandler):
def get(self, id=None):
blog = dict()
if id:
coll = self.application.db.blog
blog = coll.find_one({"id": int(id)})
self.render("edit.html",
blog = blog)
def post(self, id=None):
import time
coll = self.application.db.blog
blog = dict()
if id:
blog = coll.find_one({"id": int(id)})
blog['title'] = self.get_argument("title", None)
blog['content'] = self.get_argument("content", None)
if id:
coll.save(blog)
else:
last = coll.find().sort("id",pymongo.DESCENDING).limit(1)
lastone = dict()
for item in last:
lastone = item
blog['id'] = int(lastone['id']) + 1
blog['date'] = int(time.time())
coll.insert(blog)
self.redirect("/")
class DelHandler(tornado.web.RequestHandler):
def get(self, id=None):
coll = self.application.db.blog
if id:
blog = coll.remove({"id": int(id)})
self.redirect("/")
class BlogHandler(tornado.web.RequestHandler):
def get(self, id=None):
import time
coll = self.application.db.blog
if id:
blog = coll.find_one({"id": int(id)})
self.render("blog.html",
page_title = "我的博客",
blog = blog,
time = time,
)
else:
self.redirect("/")
def main():
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
index.html
{% autoescape None %}
B06 Innovation Space
blog.html
{% autoescape None %}
{{ page_title }}
edit.html
B06 Innovation Space
应用结构
代码回顾:
handlers = [
(r"/", MainHandler),
(r"/edit/([0-9Xx\-]+)", EditHandler),
(r"/add", EditHandler),
(r"/delete/([0-9Xx\-]+)", DelHandler),
(r"/blog/([0-9Xx\-]+)", BlogHandler),
]
在Demo2
中定义了5个handler
,分别是
-
(r"/", MainHandler)
->博客列表 -
(r"/edit/([0-9Xx\-]+)", MainHandler)
->编辑博客 -
(r"/add", MainHandler)
->发表博客 -
(r"/delete/([0-9Xx\-]+)", MainHandler)
->删除博客 -
(r"/blog/([0-9Xx\-]+)", MainHandler)
->查看博客
通过这五个handler
,我们终于能写出一个真正的博客了噢耶。
有没有注意到/([0-9Xx\-]+)
!!!这是干嘛的呢?恩,这是一个url
参数。这个正则表达式规定参数由0-9
的数字和X
组成。
在edit
、delete
和blog
方法中,我们需要一个博客id参数来找到指定的博客进行响应操作,所以我们在这里添加了一个url
参数。
博客首页
代码回顾:
class MainHandler(tornado.web.RequestHandler):
def get(self):
import time
coll = self.application.db.blog
blogs = coll.find().sort("id",pymongo.DESCENDING)
self.render(
"index.html",
blogs = blogs,
time = time,
)
在MainHandler
中,我们通过find()
查询所有的博客,并通过sort("id",pymongo.DESCENDING)
对博客id
进行倒序排序
(因为id
越大,博文就越新,那它就应该在越前面显示嘛)。
同时载入了time
模块,方便我们在模板里输出正确格式的时间。
代码回顾:
{{ time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(blog['date']) ) }}
看看index.html
,在输出时间的这一行,调用了time
模块的strftime()
方法,将在数据库储存的时间按照制定的格式%Y-%m-%d %H:%M:%S
输出。
注意:可以看到,在
self.render()
方法中,我们将time
对象作为参数传递到模板中。
这样我们才可以在模板中调用time
对象的方法。
代码回顾:
{% for blog in blogs %}
...
{% end %}
通过for
循环输出博客列表。
来看看效果(不要在意里面乱七八糟的内容=。=):
增
代码回顾:
(r"/add", EditHandler),
有没有觉得奇怪=。=,为什么add
(发表博客
)调用的还是EditHandler
。
解释:
一般的博客系统,发表博客
和编辑博客
的模板
(html文件
)其实是一样的。无非是编辑博客
的模板
里填充了博客的内容,而发表博客
的模板
是一个空表单
。
因此我们常常把新增
和编辑
放在同一个方法里处理。具体的处理方法我们下面继续聊。
代码回顾:
class EditHandler(tornado.web.RequestHandler):
def get(self, id=None):
blog = dict()
if id:
coll = self.application.db.blog
blog = coll.find_one({"id": int(id)})
self.render("edit.html",
blog = blog)
在get
方法中,我们新建了一个空字典blog
。接下来判断id
是否为空。我们通过判断id
是否为空来选择下一步是编辑
还是新增
。
代码回顾:
思考一下业务逻辑,当我们点击首页发布按钮,即访问http://127.0.0.1:8002/add
,由EditHandler
执行get
方法。
代码回顾:
def get(self, id=None):
因为没有传递url
参数,所以id
赋值为默认值None
。
代码回顾:
if id:
coll = self.application.db.blog
blog = coll.find_one({"id": int(id)})
self.render("edit.html",
blog = blog)
因为id = None
,所以if id:
为false
。因此接下来执行self.render("edit.html",blog = blog)
。
代码回顾:
{% if blog.get('id', None) %}
在edit.html
中,我们通过判断blog.get('id', None)
的值是否为None
来输出不同的form
标签。
dict.get('key','Defalt')
这个方法用于获取字典
中指定键名
的键值
(第一个参数),如果该键名
不存在,则返回第二个参数设定的默认值
。
要调用这个方法,我们必须有一个字典
可以查找。所以在EditHandler
的get
方法中,我们定义了一个空字典
:blog
(还记得吗?回头看一下代码吧。)
继续刚才的业务逻辑,我们渲染了edit.html
,并将空字典
:blog
作为参数传递到模板
文件中。所以blog.get('id', None) == None
,所以输出。
下面的{{ blog.get('title', '') }}
和{{ blog.get('content', '') }}
,同上,均为空字符串。所以我们最终得到了一个空表单
页面,也就是我们的发布博客
页面。
当我们填写好表单,点击发布按钮
,表单就以POST
方式被提交到/add
(相对路径
,对应的绝对路径
为http://127.0.0.1:8002/add
)。这时候EditHandler
执行post
方法。
代码回顾:
def post(self, id=None):
import time
coll = self.application.db.blog
blog = dict()
if id:
blog = coll.find_one({"id": int(id)})
blog['title'] = self.get_argument("title", None)
blog['content'] = self.get_argument("content", None)
if id:
coll.save(blog)
else:
last = coll.find().sort("id",pymongo.DESCENDING).limit(1)
lastone = dict()
for item in last:
lastone = item
blog['id'] = int(lastone['id']) + 1
blog['date'] = int(time.time())
coll.insert(blog)
self.redirect("/")
在post
方法中,同样的逻辑判断执行新增
还是编辑
。
代码回顾:
blog['title'] = self.get_argument("title", None)
blog['content'] = self.get_argument("content", None)
将获取的表单填充到空字典
:blog
中,然后调用coll.insert(blog)
方法向数据库中插入文档
。
诶诶诶,等等,好像插入之前还有好几行啊!!
好吧=。=这几行代码是用来生成新博客id
的。我们的博客需要一个id
来标识身份
,所以我们给每一篇博客设置一个id
。一般就是用一个从1
开始自增
的整数作为id
啦。
解释:
因为mongodb
不像mysql
那样可以设置自增字段
,所以我们需要自己生成自增
的id
(查过资料应该是有办法设置自增,但是文档没太看明白=。=就当没有办法吧。除了文档中的办法,我查了好久,发现都是靠自己写函数生成自增
的id
,大家有兴趣可以自己去查一下。在这里我就用自己的方法了。)
我的方法是这样的:last = coll.find().sort("id",pymongo.DESCENDING).limit(1)
倒序查询数据库获取最后id
最大的那一条记录。因为find()
函数返回的结果是一个数组?
所以要用for
循环取值(因为只查询了一条记录,所以用一个循环也不会太奢侈吧啦啦啦)。blog['id'] = int(lastone['id']) + 1
将最大id
加1
赋值给新博客的id
。因为mongodb
在存整型
数的时候好像会默认存为浮点型
(具体可以自行百度),所以用int()
方法处理lastone['id']
保证我们处理过程中数据类型的正确。
好了,这样我们终于成功新增
了一篇博客!!!
查
当我们在首页点击某一篇博客的标题的时候,比如点击第二篇,我们会访问http://127.0.0.1:8002/blog/2
,进入到这个页面:
这个过程是这样的:/blog
对应BlogHandler
,执行get
方法。
代码回顾:
class BlogHandler(tornado.web.RequestHandler):
def get(self, id=None):
import time
coll = self.application.db.blog
if id:
blog = coll.find_one({"id": int(id)})
self.render("blog.html",
page_title = "我的博客",
blog = blog,
time = time,
)
else:
self.redirect("/")
if
判断id
是否存在,不存在则跳转到首页
。我们这里get
方法获取到url
参数,因此id=2
。blog = coll.find_one({"id": int(id)})
查询该博客并渲染
博客页面。
这样,我们就完成了博客查看的功能。
留一个小问题。
因为这个demo
是刚开始学tornado
的时候写的,所以代码其实写得很糟糕。BlogHandler
可能这样写会更好一点,初学的同学看一看,思考一下为什么。我就不改demo
里的代码了。class BlogHandler(tornado.web.RequestHandler): def get(self, id=0): import time coll = self.application.db.blog blog = coll.find_one({"id": int(id)}) if blog: self.render("blog.html", page_title = "我的博客", blog = blog, time = time, ) else: self.redirect("/")
改
代码回顾:
class EditHandler(tornado.web.RequestHandler):
def get(self, id=None):
blog = dict()
if id:
coll = self.application.db.blog
blog = coll.find_one({"id": int(id)})
self.render("edit.html",
blog = blog)
def post(self, id=None):
import time
coll = self.application.db.blog
blog = dict()
if id:
blog = coll.find_one({"id": int(id)})
blog['title'] = self.get_argument("title", None)
blog['content'] = self.get_argument("content", None)
if id:
coll.save(blog)
else:
last = coll.find().sort("id",pymongo.DESCENDING).limit(1)
lastone = dict()
for item in last:
lastone = item
blog['id'] = int(lastone['id']) + 1
blog['date'] = int(time.time())
coll.insert(blog)
self.redirect("/")
自己看,就不多说了=。=
删
删除操作的逻辑是,传递博客id
给DelHandler
,然后调用remove()
方法从数据库删除指定博客。
代码回顾:
class DelHandler(tornado.web.RequestHandler):
def get(self, id=None):
coll = self.application.db.blog
if id:
blog = coll.remove({"id": int(id)})
self.redirect("/")
<( ̄ˇ ̄)/相信聪明的你已经看懂了!
总结
至此,我们练习了mongodb
的增删改查
,也实现了应用
的增删改查
。
当然这个Demo
还有很多需要改进的地方,比如:
- 构造形如
http://127.0.0.1:8002/delete?id=2
的链接,通过get
方法传递参数。 - 给我们的表单加上
格式验证
。
同学们可以自己稍作修改尝试一下。