Tornado笔记——用Tornado搭建假单统计系统(二)

在上一篇博客里,我们用Tornado初步搭好了这个系统的架子,现在让我们开始往里填充一些内容。

五 Tornado基础介绍

首先让我们复习一下这个架子的后端代码:

# server/main.py
 
import sys
import platform
import os
import tornado.ioloop
import tornado.web
from tornado.web import url
from setting.globalsettings import getconfig,gettemplatepath
 
if platform.system() == 'Windows':
    import asyncio
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
 
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie('currentuser')
 
class Index(BaseHandler):
    def get(self):
        indexpath = gettemplatepath('index.html')
        self.render(indexpath)
 
def make_app():
    routelist = [
        (r"/",Index)
    ]
    return tornado.web.Application(routelist,cookie_secret='12f6352#527')
 
if __name__ == '__main__':
    app = make_app()
    port = int(getconfig('PORT'))
    app.listen(port)
    mainserver = tornado.ioloop.IOLoop.current()
    mainserver.start()

在上篇博客的结尾,我们用这堆代码将我们的网站跑了起来,现在让我们看一下这些代码都在干什么。

在代码的开头部分,我们判断了当前程序所运行的平台,如果当前程序运行在Windows平台下,则设置相应的事件循环策略。由于Tornado是基于协程和事件循环来作用,如果不设置这个循环策略,则程序无法运行。

随后我们基于Tornado的RequestHandler写了一个自己的BaseHandler,重写了它的get_current_user方法,通过名为currentuser的cookie来拿到当前登录的用户。

1 Tornado的RequestHandler

当Tornado作为Web框架时,它会提供RequestHandler作为接收HTTP请求的对象,这个对象提供了常用的http方法,如get和post。通过这个对象,我们可以获得客户端(浏览器)发送到服务器的各个请求参数,并将网页渲染回浏览器。

RequestHandler提供的常用方法如下:

  • get:用于处理HTTP的Get请求,通常用于显示页面。
  • post:用于接收表单数据。
  • get_cookie:获得指定名称的cookie值,注意其返回值为bytes。
  • set_cookie:设定指定名称的cookie值,默认期限为30天。
  • 与以上两个方法对应的是get/set_secure_cookie,需要设定一个密钥以便对cookie加密。
  • get_argument:获得指定名称的元素的值,用于接收表单中元素的值。如果不给默认值,而表单又没有传值的话,则会产生一个MissingArgumentError的错误。
  • render:渲染指定html模板到浏览器,可向其传递参数作为模板中参数的值。

通常来说,我们对每一个RequestHandler要实现它的get方法,用于显示该页面;如果一个页面还要处理表单数据,则还要实现它的post方法以便处理表单数据。

所以,我们现在的Index就很好理解了:一个简单的get方法,渲染了index.html这个模板到浏览器。

2 Tornado的路由

接下来让我们看一下make_app函数,这个函数用于建立网站的路由,并依据路由列表来创建服务器。

在Tornado框架中,其通过Router类的实现对HTTP请求的路由,将不同的HTTP请求交给不同的RequestHandler去处理。为了实现这种路由关系,最简单的方式是直接使用Tornado提供的tornado.web.Application来创建一个Web应用,并将路由列表传递进去。

所谓的路由列表,就是指URL和RequestHandler的对应关系。一个URL对应一个RequestHandler。Tornado提供了三种匹配URL的方式:HostMatches,DefaultHostMatches和PathMatches,最常用的是PathMatches,即根据路径去匹配RequestHandler。

现在,我们在make_app里建立了一个名为routelist的列表,里面存了一个元组,表明当我们访问/这个路径,则将HTTP请求交给Index这个RequestHandler去处理。随着我们页面的增加,这个routelist会越来越长。

然后我们以其建立了一个Application返回出去,并设置了安全cookie的密钥。

3 主函数

主函数的部分相对简单。我们通过上面的make_app得到带有路由的Application,随后从配置文件中读出端口号,然后让Application监听这个端口,启动事件循环,整个页面就起来了。

现在我们对Tornado的运行原理有了一个大概的了解,可以开始往架子里写内容了。

六 前端大变脸以及创建用户组

我们的架子实在有些简陋,因此我们需要对它来一次变脸,并以此为基础风格建立我们的整个系统。

在这篇博客结束后,我们的系统应该是长这个样子:

在这个画面中,左侧边栏是三个做好的导航栏:主页,创建用户组和查看用户组,底下则是模板自带的标签,暂时保持不动;右边上侧导航栏右边为注册及登录,底下为之后要做的一些数据展示。

我们选用的模板为MonsterAdmin,大家可以去这里下载:https://themefisher.com/products/free-bootstrap-admin-dashboard-template/

在下载好模板后,我们把它放到我们的工程目录里,目录结构如下:

 其中,模板自带的css,js和scss目录扔进template目录,而assets放在template的上一层目录,这是因为我们要保持原模板的相对引用关系。images为我们新建的目录,用于之后存储一些自定义图片。

我们再将这几个模板目录配进nginx配置文件:

# nginx配置文件
...
    server {
        listen       9000;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            #root   D:\\LeaveManage\\server;
            proxy_pass http://localhost:8000;
            #index  index.html index.htm;
        }
        
        location /css/ {
            #root   html;
            root D:\\LeaveManage\\server\\template;
            #index  index.html index.htm;
        }
        
        location /scss/ {
            #root   html;
            root D:\\LeaveManage\\server\\template;
            #index  index.html index.htm;
        }
        
        location /assets/ {
            #root   html;
            root D:\\LeaveManage\\server;
            #index  index.html index.htm;
        }
        
        location ~ \.(gif|jpg|png)$ {
            #root   html;
            root D:\\LeaveManage\\server;
            #index  index.html index.htm;
        }
        
        location /js/ {
            #root   html;
            root D:\\LeaveManage\\server\\template;
            #index  index.html index.htm;
        }
        ...
    }
...

(当然,大家也可以自己选喜欢的模板,再根据实际情况来配置nginx以及以下的操作) 

在配置好nginx后,我们可以开始以此为基础开始建立我们的前端模板。我们随意选一个模板提供的html(404那个除外),改名为base_nav.html,以此作为我们的基础模板:

<!--base_nav.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <!-- 保持原模板代码不动-->
    <title>Tornado考勤系统</title>
    <!-- 保持原模板代码不动-->
<![endif]-->
</head>

<body class="fix-header fix-sidebar card-no-border">
    <!-- ============================================================== -->
    <!-- Preloader - style you can find in spinners.css -->
    <!-- ============================================================== -->
    <div class="preloader">
        <!-- 保持原模板代码不动-->
    </div>
    <!-- ============================================================== -->
    <!-- Main wrapper - style you can find in pages.scss -->
    <!-- ============================================================== -->
    <div id="main-wrapper">
        <!-- ============================================================== -->
        <!-- Topbar header - style you can find in pages.scss -->
        <!-- ============================================================== -->
        {% block nav %}
        <header class="topbar">
            <nav class="navbar top-navbar navbar-toggleable-sm navbar-light">
                <!-- ============================================================== -->
                <!-- Logo -->
                <!-- ============================================================== -->
                <div class="navbar-header">
                    <a class="navbar-brand" href="/">
                        <!-- Logo icon -->
                        <b>
                            <!--You can put here icon as well // <i class="wi wi-sunset"></i> //-->
                            <!-- Dark Logo icon -->
                            <img src="../assets/images/logo-icon.png" alt="homepage" class="dark-logo" />
                            
                        </b>
                        <!--End Logo icon -->
                        <!-- Logo text -->
                        <span>
                            <!-- dark Logo text -->
                            <img src="../assets/images/logo-text.png" alt="homepage" class="dark-logo" />
                        </span>
                    </a>
                </div>
                <!-- ============================================================== -->
                <!-- End Logo -->
                <!-- ============================================================== -->
                <div class="navbar-collapse">
                    <!-- ============================================================== -->
                    <!-- toggle and nav items -->
                    <!-- ============================================================== -->
                    <ul class="navbar-nav mr-auto mt-md-0 ">
                        <!-- This is  -->
                        <li class="nav-item"> <a class="nav-link nav-toggler hidden-md-up text-muted waves-effect waves-dark" href="javascript:void(0)"><i class="ti-menu"></i></a> </li>
                        <li class="nav-item hidden-sm-down">
                            <form class="app-search p-l-20">
                                <input type="text" class="form-control" placeholder="Search for..."> <a class="srh-btn"><i class="ti-search"></i></a>
                            </form>
                        </li>
                    </ul>
                    <!-- ============================================================== -->
                    <!-- User profile and search -->
                    <!-- ============================================================== -->
                    <ul class="navbar-nav my-lg-0">
                        {% if currentusergroup == 'Visitor' %}
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle text-muted waves-effect waves-dark" href="/register" aria-haspopup="true" aria-expanded="false">注册</a>|<a class="nav-link dropdown-toggle text-muted waves-effect waves-dark" href="/login" aria-haspopup="true" aria-expanded="false">登录</a>
                        </li>
                        {% else %}
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle text-muted waves-effect waves-dark" href="/personalinfo" aria-haspopup="true" aria-expanded="false">{{ escape(currentuser) }}</a>|<a class="nav-link dropdown-toggle text-muted waves-effect waves-dark" href="/logout" aria-haspopup="true" aria-expanded="false">登出</a>
                        </li>
                        {% end %}
                    </ul>
                </div>
            </nav>
        </header>
        <!-- ============================================================== -->
        <!-- End Topbar header -->
        <!-- ============================================================== -->
        <!-- ============================================================== -->
        <!-- Left Sidebar - style you can find in sidebar.scss  -->
        <!-- ============================================================== -->
        <aside class="left-sidebar">
            <!-- Sidebar scroll-->
            <div class="scroll-sidebar">
                <!-- Sidebar navigation-->
                <nav class="sidebar-nav">
                    <ul id="sidebarnav">
                        <li>
                            <a href="/" class="waves-effect"><i class="fa fa-group m-r-10" aria-hidden="true"></i>主页</a>
                        </li>
                        <li>
                            <a href="/createusergroup" class="waves-effect"><i class="fa fa-group m-r-10" aria-hidden="true"></i>创建用户组</a>
                        </li>
                        <li>
                            <a href="/viewusergroup" class="waves-effect"><i class="fa fa-group m-r-10" aria-hidden="true"></i>查看用户组</a>
                        </li>
                        <li>
                            <a href="icon-fontawesome.html" class="waves-effect"><i class="fa fa-font m-r-10" aria-hidden="true"></i>Icons</a>
                        </li>
                        <li>
                            <a href="pages-blank.html" class="waves-effect"><i class="fa fa-columns m-r-10" aria-hidden="true"></i>Blank Page</a>
                        </li>
                    </ul>
                </nav>
                <!-- End Sidebar navigation -->
            </div>
            <!-- End Sidebar scroll-->
        </aside>
        {% end %}
        <!-- ============================================================== -->
        <!-- End Left Sidebar - style you can find in sidebar.scss  -->
        <!-- ============================================================== -->
        <!-- ============================================================== -->
        <!-- Page wrapper  -->
        <!-- ============================================================== -->
        {% block content %}
        {% end %}
        <!-- ============================================================== -->
        <!-- End Page wrapper  -->
        <!-- ============================================================== -->
    </div>
    <!-- ============================================================== -->
    <!-- End Wrapper -->
    <!-- ============================================================== -->
    <!-- ============================================================== -->
    <!-- All Jquery -->
    <!-- ============================================================== -->
    <!-- 保持原模板代码不动-->
</body>

</html>

在这个模板中,我们要关注的是两个block:nav和content。nav为左侧及上方导航栏,而content是右侧下半部分,为其他页面的具体内容。我们之后的所有页面都会继承这个模板来实现,以确保导航栏风格的统一。

我们可以看到,在前端模板中有{% if %},{% else %}和{% end %}的字样,这是因为,Tornado提供的前端模板可以支持一些逻辑判断,循环甚至是对象成员的访问,这使得我们可以通过后端来控制前端的内容显示,大大增加了灵活性。如在这里,我们会判断当前用户所属的用户组是否为Visitor来显示不同页面。

现在先让我们把index页面换掉。直接修改模板自带的index.html,使其继承base_nav.html,再重写content块,这里只列出关键代码:开头的{% extends "base_nav.html" %}表面我们要继承base_nav这个模板,而在底下的{% block content %}至{% end %}的包裹部分为重写的content块。如无特别说明,之后所有页面都只列出content块的内容。

<!--index.html-->
{% extends "base_nav.html" %}
...
    <!-- ============================================================== -->
    <!-- Main wrapper - style you can find in pages.scss -->
    <!-- ============================================================== -->
    <div id="main-wrapper">
        <!-- ============================================================== -->
        <!-- Topbar header - style you can find in pages.scss -->
        <!-- ============================================================== -->
        <!-- ============================================================== -->
        <!-- End Left Sidebar - style you can find in sidebar.scss  -->
        <!-- ============================================================== -->
        <!-- ============================================================== -->
        <!-- Page wrapper  -->
        <!-- ============================================================== -->
        {% block content %}
        <div class="page-wrapper">
            <!-- ============================================================== -->
            <!-- Container fluid  -->
            <!-- ============================================================== -->
            <div class="container-fluid">
                <!-- ============================================================== -->
                <!-- Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <!-- ============================================================== -->
                <!-- End Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <!-- ============================================================== -->
                <!-- Start Page Content -->
                <!-- ============================================================== -->
                <!-- Row -->
                <div class="row">
                    <!-- Column -->
                    <div class="col-sm-6">
                        <div class="card">
                            <div class="card-block">
                                <h4 class="card-title">填写考勤</h4>
                                <div class="text-right">
                                    <h2 class="font-light m-b-0">20天</h2>
                                    <span class="text-muted">已出勤天数</span>
                                </div>
                                <span class="text-success">80%</span>
                                <div class="progress">
                                    <div class="progress-bar bg-success" role="progressbar" style="width: 80%; height: 6px;" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Column -->
                    <!-- Column -->
                    <div class="col-sm-6">
                        <div class="card">
                            <div class="card-block">
                                <h4 class="card-title">填写假单</h4>
                                <div class="text-right">
                                    <h2 class="font-light m-b-0">10天</h2>
                                    <span class="text-muted">假期余额</span>
                                </div>
                                <span class="text-info">30%</span>
                                <div class="progress">
                                    <div class="progress-bar bg-info" role="progressbar" style="width: 30%; height: 6px;" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Column -->
                </div>
                <!-- Row -->
                <!-- Row -->
                <div class="row">
                    <!-- column -->
                    <div class="col-sm-12">
                        <div class="card">
                            <div class="card-block">
                                <h4 class="card-title">Revenue Statistics</h4>
                                <div class="flot-chart">
                                    <div class="flot-chart-content" id="flot-line-chart"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- column -->
                </div>
                <!-- Row --
                <!-- Row -->
                <!-- Row -->
                <!-- ============================================================== -->
                <!-- End PAge Content -->
                <!-- ============================================================== -->
            </div>
            <!-- ============================================================== -->
            <!-- End Container fluid  -->
            <!-- ============================================================== -->
            <!-- ============================================================== -->
            <!-- footer -->
            <!-- ============================================================== -->
            <footer class="footer text-center">
                © 2020 Tornado考勤系统
            </footer>
            <!-- ============================================================== -->
            <!-- End footer -->
            <!-- ============================================================== -->
        </div>
        {% end %}
        <!-- ============================================================== -->
        <!-- End Page wrapper  -->
        <!-- ============================================================== -->
    </div>
...

我们现在可以来实现我们的第一个功能了——建立用户组。在这个系统中,每个用户都会从属于一个用户组,而在后期会给每个用户组加上不同的权限,防止用户去操作那些没有权限的功能。因此,我们首先要提供一个建立用户组的功能。

我们在server里建立一个新的util包,并在其中再建立一个users包,再新建名为userutil.py文件。这个py文件将存放所有和用户相关的函数。创建用户组的函数createusergroup如下:

# server/util/users/userutil.py
from database.dbcore import session
from database.curd import insertdata,deletedata
from database.tblusergroup import UserGroup
from database.tbluser import User
from datetime import date,datetime
import hashlib
from sqlalchemy import and_
import re

def createusergroup(groupname):
    usergroup = session.query(UserGroup).filter(UserGroup.groupname==groupname).first()
    result = 'Fail'
    if usergroup is None:
        # 用户组不存在,创建
        newusergroup = UserGroup(groupname=groupname,createdate=date.today())
        result = insertdata(newusergroup)
    return result


def getusergroup(username):
    user = session.query(User).filter(User.username == username).first()
    group = 'Visitor'
    if type(user) is User:
        group = user.usergroup
    return group

这段函数很简单,通过我们之前建立的SQLAlchemy提供的session以groupname查询数据库中有没有这个usergroup存在,如果没有,则创建之。而getusergroup是查询一个指定用户属于哪个用户组,如果查不到,则算为游客组,在后续的权限控制中,游客组为权限最少的一组。

然后让我们回到main.py,去实现它的RequestHandler。这里我们需要分别实现它的get和post方法。

# server/main.py
# ...
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        currentuser = ''
        bytes_user = self.get_secure_cookie('currentuser')
        if type(bytes_user) is bytes:
            currentuser = str(bytes_user,encoding='utf-8')
        return currentuser

    def render(self, template_name, **kwargs):
        currentuser = self.get_current_user()
        usergroup = getusergroup(currentuser)
        super(BaseHandler, self).render(template_name,currentuser=currentuser,currentusergroup=usergroup,**kwargs)

# ...

class CreateUserGroup(BaseHandler):
    def get(self):
        createusergrouppath = gettemplatepath('createusergroup.html')
        self.render(createusergrouppath)

    def post(self):
        groupname = self.get_argument('usergroupname')
        result = 'Fail'
        if groupname != '':
            result = createusergroup(groupname)
        resultpath = gettemplatepath('result.html')
        if result == 'Success':
            result = '成功创建用户组!'
        else:
            result = '创建用户组失败!'
        self.render(resultpath,result=result)

# ...

def make_app():
    routelist = [
        # ...
        (r"/createusergroup",CreateUserGroup),
        # ...
    ]
# ...

这里我们又重写了BaseHandler的get_current_user方法,让它返回一个str而不是之前的bytes变量;我们还重写了render方法,因为在我们的导航模板中,右上角的元素由后端传入的用户组以及用户决定,因此我们让render直接写入currentuser和currentusergroup,以免每个页面都要传一次。

CreateUserGroup就是我们处理创建用户组功能的Handler。它的get和post方法都很简单:get直接渲染了我们的新页面createusergroup.html,而post方法中使用get_argument接收了usergroupnname表单元素的值,并调用刚才的createusergroup函数创建,然后将结果返回到result.html中。

createusergroup.html和result.html如下:

<!-- createusergroup.html-->
        {% block content %}
        <div class="page-wrapper">
            <div class="container-fluid">
                <div class="row page-titles">
                    <div class="col-md-6 col-8 align-self-center">
                        <h3 class="text-themecolor m-b-0 m-t-0">创建用户组</h3>
                        <ol class="breadcrumb">
                            <li class="breadcrumb-item"><a href="/">Home</a></li>
                            <li class="breadcrumb-item active">创建用户组</li>
                        </ol>
                    </div>
                </div>
                <div class="row">
                    <!-- Column -->
                    <div class="col-lg-8 col-xlg-9 col-md-7">
                        <div class="card">
                            <div class="card-block">
                                <form class="form-horizontal form-material" action="/createusergroup" method="post" >
                                    <div class="form-group">
                                        <label class="col-md-12">用户组名</label>
                                        <div class="col-md-12">
                                            <input type="text" class="form-control form-control-line" required=true name="usergroupname" id="usergroupname">
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            <button type="submit" class="btn btn-success">创建</button>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                    <!-- Column -->
                </div>
                </div>
            <footer class="footer text-center">
                © 2020 Tornado考勤系统
            </footer>
        </div>
        {% end %}
<!-- result.html-->
        {% block content %}
        <div class="page-wrapper">
            <div class="container-fluid">
                <div class="row page-titles">
                    <div class="col-md-6 col-8 align-self-center">
                        <h3 class="text-themecolor m-b-0 m-t-0">执行结果</h3>
                        <ol class="breadcrumb">
                            <li class="breadcrumb-item"><a href="javascript:void(0)">Home</a></li>
                            <li class="breadcrumb-item active">执行结果</li>
                        </ol>
                    </div>
                </div>
                <div class="row">
                    <div class="col-12">
                        <div class="card">
                            <div class="card-block">
                                {{ result }}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <footer class="footer text-center">
                © 2020 Tornado考勤系统
            </footer>
        </div>
        {% end %}

最后,我们在make_app函数中将CreateUserGroup加入路由列表,然后就可以来创建我们的用户组了:

 

 好了,在这一篇博客中,我们对前端来了个大变脸,并写了我们在tornado中的第一个功能。在下一篇博客中,将继续介绍用户模块的其他功能,包括用户注册、用户登录、用户个人信息以及查看用户组等功能,希望大家继续关注~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值