Tornado 简单入门教程(二)——Demo2

CHENGKANG

前面的话

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 %}
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>B06 Innovation Space</title>
        <link rel="stylesheet" type="text/css" href="{{ static_url("css/style.css") }}">
    </head>
    <body>
        <div class="main">
            <a href="/">
                <img class="logo" src="{{ static_url("img/logo.png") }}">
            </a>
            <div class="container">
                <h1>欢迎访问B06创新实验室的博客</h1>
                {% autoescape None %}
                {% for blog in blogs %}
                <div class="content">
                    <div class="Title">
                        <p>
                            <a href="/blog/{{ int(blog['id']) }}">{{ blog['title'] }}</a>
                        </p>
                        <p>
                            <span class="Time">{{ time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(blog['date']) ) }}</span>
                        </p>
                    </div>
                    <div class="Article">
                        <p>
                            {{ blog['content'] }}
                        </p>
                    </div>
                </div>
                {% end %}
                <a href="/add">
                    <input type="button" class="Article Button Submit" value="发    布" onclick="document.location.href('/add/')"/>
                </a>
            </div>
        </div>
    </body>
</html>

blog.html

{% autoescape None %}
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>{{ page_title }}</title>
        <link rel="stylesheet" type="text/css" href="{{ static_url("css/style.css") }}">
    </head>
    <body>
        <div class="main">
            <a href="/">
                <img class="logo" src="{{ static_url("img/logo.png") }}">
            </a>
            <div class="container">
                <h1>欢迎访问B06创新实验室的博客</h1>
                <div class="content">
                    <div class="Title">
                        <p>
                            {{ blog['title'] }}
                        </p>
                        <p>
                            <span class="Time">{{ time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(blog['date']) ) }}</span>
                        </p>
                    </div>
                    <div class="Article">
                        <p>
                            {{ blog['content'] }}
                        </p>
                    </div>
                    <div class="buttonDiv">
                        <a href="/edit/{{ int(blog['id']) }}">
                            <input type="button" class="Article Button Edit" value="编    辑"/>
                        </a>
                        <a href="/delete/{{ int(blog['id']) }}">
                            <input type="button" class="Article Button Delete" value="删    除"/>
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

edit.html

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>B06 Innovation Space</title>
        <link rel="stylesheet" type="text/css" href="{{ static_url("css/style.css") }}">
        <script type="text/javascript">
            window.setInterval(function() {
                go_to();
            },
            100);
            function go_to() {
                if( document.getElementById("myArticle").style.height < (document.getElementById("myArticle").scrollHeight - 4 ) + "px")
                    document.getElementById("myArticle").style.height = document.getElementById("myArticle").scrollHeight + "px";
            }
        </script>
    </head>
    <body>
        <div class="main">
            <a href="/">
                <img class="logo" src="{{ static_url("img/logo.png") }}">
            </a>
            <div class="container">
                <h1>欢迎访问B06创新实验室的博客</h1>
                <div class="content">
                    {% if blog.get('id', None) %}
                    <form method="post" action="/edit/{{ int(blog['id']) }}">
                    {% else%}
                    <form method="post" action="/add">
                    {% end %}
                        <p>文章标题:</p>
                        <input type="text" class="Title" name="title" placeholder="在这里输入你的标题" value="{{ blog.get('title', '') }}" />
                        <p>文章正文:</p>
                        <textarea type="text" class="Article" id="myArticle" name="content" placeholder="在这里输入你的正文">
                            {{ blog.get('content', '') }}
                        </textarea>
                        <br/>
                        {% if blog.get('id', None) %}
                        <input type="submit" class="Article Button Submit" value="修    改"/>
                        {% else%}
                        <input type="submit" class="Article Button Submit" value="发    布"/>
                        {% end %}
                    </form>
                </div>
            </div>
        </div>
    </body>
</html>

应用结构

代码回顾:

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组成。
editdeleteblog方法中,我们需要一个博客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模块,方便我们在模板里输出正确格式的时间。

代码回顾:

<span class="Time">{{ time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(blog['date']) ) }}</span>

看看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是否为空来选择下一步是编辑还是新增

代码回顾:

<a href="/add">
    <input type="button" class="Article Button Submit" value="发    布"/>
</a>

思考一下业务逻辑,当我们点击首页发布按钮,即访问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) %}
<form method="post" action="/edit/{{ int(blog['id']) }}">
{% else%}
<form method="post" action="/add">
{% end %}
    <p>文章标题:</p>
    <input type="text" class="Title" name="title" placeholder="在这里输入你的标题" value="{{ blog.get('title', '') }}" />
    <p>文章正文:</p>
    <textarea type="text" class="Article" id="myArticle" name="content" placeholder="在这里输入你的正文">
        {{ blog.get('content', '') }}
    </textarea>
    <br/>
    {% if blog.get('id', None) %}
    <input type="submit" class="Article Button Submit" value="修    改"/>
    {% else%}
    <input type="submit" class="Article Button Submit" value="发    布"/>
    {% end %}
</form>

edit.html中,我们通过判断blog.get('id', None)的值是否为None来输出不同的form标签。

dict.get('key','Defalt')这个方法用于获取字典中指定键名键值(第一个参数),如果该键名不存在,则返回第二个参数设定的默认值
要调用这个方法,我们必须有一个字典可以查找。所以在EditHandlerget方法中,我们定义了一个空字典blog(还记得吗?回头看一下代码吧。)

继续刚才的业务逻辑,我们渲染了edit.html,并将空字典blog作为参数传递到模板文件中。所以blog.get('id', None) == None,所以输出<form method="post" action="/add">

下面的{{ 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将最大id1赋值给新博客的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=2blog = 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("/")

自己看,就不多说了=。=

删除操作的逻辑是,传递博客idDelHandler,然后调用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方法传递参数。
  • 给我们的表单加上格式验证

同学们可以自己稍作修改尝试一下。

阅读 10k

CHENGKANG
小学生。

LOADING...

1k 声望
65 粉丝
0 条评论

LOADING...

1k 声望
65 粉丝
文章目录
宣传栏