Tornado笔记——用Tornado搭建假单统计考勤系统(十一)

在上一篇博客中,我们实现了填写假单以及查看假单的功能。在这篇博客中,我们将实现审批假单的功能,以及构建用户权限系统,防止用户执行没有权限的功能。

十五 审批假单

审批假单的界面如下所示:

在这个页面,用户可以看到他所有下级提交的假期申请,并对其进行操作批准或拒绝。

打开util/users/timesheetutil.py,实现util函数:

# util/users/timesheetutil.py
def changevacationapplystate(vacationId,state,approveuser):
    vacation = session.query(Vacation).filter(Vacation.id == vacationId).first()
    if type(vacation) is Vacation:
        if vacation.state == 'WaitForApprove':
            vacation.state = state
            vacation.applydate = datetime.date.today()
            vacation.approveuser = approveuser
            result = insertdata(vacation)
            return result
    return 'Fail'

 这个函数没啥说的,就是改变Vacation表里的state状态,更新批准日期和批准人。

回到timesheet_app.py文件,开始实现页面的后端代码:

# timesheet_app.py
class ApproveVacationApply(BaseHandler):
    def get(self):
        username = ''
        bytes_user = self.get_secure_cookie('currentuser')
        if type(bytes_user) is bytes:
            username = str(bytes_user, encoding='utf-8')
        vacationlist = []
        employees = session.query(User).filter(User.supervisor == username)
        for employee in employees:
            tmp_vacationlist = viewvacationapply(employee.username)
            vacationlist += tmp_vacationlist
        approvevacationapplypath = gettemplatepath('approvevacationapply.html')
        self.render(approvevacationapplypath, vacationlist=vacationlist)

    def post(self):
        vacationId = self.get_argument('vacationId')
        operationId = vacationId + '_operation'
        operation = self.get_argument(operationId)
        username = ''
        bytes_user = self.get_secure_cookie('currentuser')
        if type(bytes_user) is bytes:
            username = str(bytes_user, encoding='utf-8')
        result = changevacationapplystate(vacationId,operation,username)
        if result == 'Success':
            self.redirect('/approvevacationapply')
        else:
            resultpath = gettemplatepath('result.html')
            self.render(resultpath,result=result)

 然后,我们建立前端页面文件approvevacationapply.html:

<!--approvevacationapply.html-->
<!--...-->
        {% block content %}
        <div class="page-wrapper">
            <!-- ============================================================== -->
            <!-- Container fluid  -->
            <!-- ============================================================== -->
            <div class="container-fluid">
                <!-- ============================================================== -->
                <!-- Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <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>
                <!-- ============================================================== -->
                <!-- End Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <!-- ============================================================== -->
                <!-- Start Page Content -->
                <!-- ============================================================== -->
                <!-- Row -->
                <div class="row">
                    <!-- Column -->
                    <div class="col-lg-8 col-xlg-9 col-md-7">
                        {% for vacation in vacationlist %}
                        <div class="card">
                            <div class="card-block">
                                    <div class="form-group">
                                        <label class="col-md-12">假单ID: {{ vacation['id'] }}</label>
                                        <label class="col-md-12">请假人: {{ vacation['username'] }}</label>
                                        <label class="col-md-12">假别: {{ escape(vacation['category']) }}</label>
                                        <label class="col-md-12">
                                            {{ vacation['startdate'] }} {{ vacation['starttime'] }} - {{ vacation['enddate'] }} {{ vacation['endtime'] }}
                                        </label>
                                        <label class="col-md-12">
                                            总计 {{ escape(vacation['timesum']) }} 天
                                        </label>
                                        <label class="col-md-12" id="{{ vacation['id'] }}_vacationstate">
                                            状态: {{ escape(vacation['state']) }}
                                        </label>
                                        <form id="{{ vacation['id'] }}" method="post" action="/approvevacationapply">
                                        <label class="col-md-12">
                                        <input type="hidden" id="vacationId" name="vacationId" value="{{ vacation['id'] }}"/>
                                        <select class="form-control form-control-line" name="{{ vacation['id'] }}_operation" id="{{ vacation['id'] }}_operation" >
                                            <option>Approved</option>
                                            <option>Rejected</option>
                                        </select>
                                        <button id="{{ vacation['id'] }}_operationButton" type="submit" class="btn btn-success">操作</button>
                                        </label>
                                        </form>
                                    </div>
                            </div>
                        </div>
                        <script>
                            var vacationstateId = "{{ vacation['id'] }}" + "_vacationstate";
                            var operationButtonId = "{{ vacation['id'] }}" + "_operationButton";
                            var vacationstate = document.getElementById(vacationstateId).innerHTML;
                            console.log(vacationstate);
                            var operationButton = document.getElementById(operationButtonId);
                            if (vacationstate.indexOf("WaitForApprove") == -1)
                            {
                                operationButton.disabled = "disabled";
                            }
                            else
                            {
                                operationButton.disabled = "";
                            }
                        </script>
                        {% end %}
                    </div>
                    <!-- Column -->
                </div>
                <!-- Row -->
                <!-- ============================================================== -->
                <!-- End PAge Content -->
                <!-- ============================================================== -->
                </div>
            <!-- ============================================================== -->
            <!-- End Container fluid  -->
            <!-- ============================================================== -->
            <!-- ============================================================== -->
            <!-- footer -->
            <!-- ============================================================== -->
            <footer class="footer text-center">
                © 2020 Tornado考勤系统
            </footer>
            <!-- ============================================================== -->
            <!-- End footer -->
            <!-- ============================================================== -->
        </div>
        {% end %}
<!--...-->

这里需要注意的是那小段js代码,即如果假单的状态不是WaitForApprove的话,就将submit按钮置灰,防止继续修改。

然后在main.py中加上路由:

# main.py
routelist = [
    # ...
    (r"/approvevacationapply",ApproveVacationApply),
    # ...
]

然后自行在导航栏中加入链接,这里就不再赘述了。

这样,我们就实现了假单系统的整个功能:提交假单、查看假单和审批假单。

下面让我们来看整个系统的最后一部分:用户权限系统。我们的用户权限系统是基于用户组来实现的,即每个用户组具有执行一系列功能的权限。如果用户所属的用户组没有某个功能的权限,在打开页面时就会提示当前用户没有权限。

十六 权限系统

权限系统分三部分:设置权限、查看权限和权限卡控。

设置权限页面如下:

可以为不同的用户组一次性设置各个功能的权限。

查看权限页面如下:

 这个页面会以图片形式来显示用户组可以执行哪些功能,当然,大家也可以自行寻找好看的图。。。。。。

1 设置权限

让我们回忆一下权限系统的数据库部分:在这个系列的第一篇,我们就为权限系统设计了GroupPrivilege表:

这个表将存储每个用户组可执行的功能列表。

我们需要一个地方来储存我们所有的功能,因此,我们打开setting/globalsettings.py,建立一个全局变量来储存我们所有功能的列表ALLFUNCTIONLIST:

# setting/globalsettings.py
# ...
ALLFUNCTIONLIST = []

 然后,我们修改main.py中的make_app函数,在其中填充ALLFUNCTIONLIST的内容:

# server/main.py
# ..
from setting.globalsettings import ALLFUNCTIONLIST
# ...
def make_app():
    # ...
    for funcs in routelist:
        ALLFUNCTIONLIST.append(funcs[1].__name__)
    # ...

这里我们用__name__属性来获得每个RequestHandler的名称,即我们使用RequestHandler的名称来表示功能名。

然后,我们打开users/userutil.py,实现setgroupprivilege和getgroupprivilege函数,前者用于给用户组设置权限,后者用于列出所有用户组的权限。先来看setgroupprivilege函数:

# users/userutil.py
def setgroupprivilege(privilegemap):
    # {'Index_Root':'on','Index_Normal':'off'}
    group_func_map = {}
    result = ''
    groups = session.query(UserGroup)
    for group in groups:
        group_func_map[group.groupname] = ''

    for funcname in privilegemap:
        func = funcname.split('_')[0]
        group = funcname.split('_')[1]
        if privilegemap[funcname] == 'on':
            if group_func_map[group] != '':
                group_func_map[group] += ','
            group_func_map[group] += func

    for group in groups:
        privilege = session.query(GroupPrivilege).filter(GroupPrivilege.groupname == group.groupname).first()
        if privilege is None:
            newPrivlege = GroupPrivilege(groupname=group.groupname,funclist=group_func_map[group.groupname])
            result = insertdata(newPrivlege)
        else:
            privilege.funclist = group_func_map[group.groupname]
            result = insertdata(privilege)
    return result

privilegemap是通过表单获得的一个字典,其形式为{'Index_Root':'on','Index_Normal':'off',...}。Key的构成方式是功能_用户组,用于表示用户组是否对这个功能有权限,value为on或off,对应前端页面checkbox的值。group_func_map是最终权限字典,我们要将这个字典的值存入GroupPrivilege表中,因此其形式为{'Root':'Index,Register,...','Normal':'Index,Login,...'},即key为用户组,value为用户组可执行的功能列表。函数中的前两个for循环就是在构造这个group_func_map字典,而最后一个for循环则是将这个字典存入GroupPrivilege表中。

下面来看getgroupprivilege函数:

# users/userutil.py
# ...
from setting.globalsettings import ALLFUNCTIONLIST
def getgroupprivilege():
    group_func_map = {}
    groups = session.query(UserGroup)

    for func in ALLFUNCTIONLIST:
        for group in groups:
            funcname = func + '_' + group.groupname
            group_func_map[funcname] = 'off'

    for group in groups:
        privilege = session.query(GroupPrivilege).filter(GroupPrivilege.groupname == group.groupname).first()
        if type(privilege) is GroupPrivilege:
            group_funcs = privilege.funclist.split(',')
            for func in ALLFUNCTIONLIST:
                if func in group_funcs:
                    funcname = func + '_' + group.groupname
                    group_func_map[funcname] = 'on'
    return group_func_map

这个函数和setgroupprivilege干的事情相反,是通过GroupPrivilege表中得到group_func_map,即用户组对每个功能的权限。所以,这里的group_func_map形式为{'Index_Root':'on','Index_Normal':'off',...},返回后可供前端显示。

回到user_app.py,实现ModifyGroupPrivilege的后端函数:

# user_app.py
class ModifyGroupPrivilege(BaseHandler):
    def get(self):
        result = {}
        groups = session.query(UserGroup)
        for group in groups:
            result[group.groupname] = ''
        privilegepath = gettemplatepath('modifygroupprivilege.html')
        groupprivilege = getgroupprivilege()
        self.render(privilegepath,groups = result,funclist=ALLFUNCTIONLIST,groupprivilege=groupprivilege)

    def post(self):
        privilegevalue = {}
        groups = session.query(UserGroup)
        for func in ALLFUNCTIONLIST:
            for group in groups:
                privilegename = func + '_' + group.groupname
                privilegevalue[privilegename] = self.get_argument(privilegename,'off')
        result = setgroupprivilege(privilegevalue)
        resultpath = gettemplatepath('result.html')
        if result == 'Success':
            self.redirect('/modifygroupprivilege')
        else:
            result = '操作失败!'
            self.render(resultpath, result=result)

# main.py
def make_app():
    routelist = [
        # ...
        (r"/modifygroupprivilege",ModifyGroupPrivilege),
        # ...
    ]

在get方法里,我们会得到所有的用户组,包括Root和Visitor,并且通过刚才的getgroupprivilege得到用户组的权限。而在post方法里,我们会通过表单构造上面提到的privilegemap字典,并把它丢进setgroupprivilege函数来为用户组添加权限。

我们在template目录中建立modifygroupprivilege.html,实现前端页面:

<!--modifygroupprivilege.html-->
        {% block content %}
        <div class="page-wrapper">
            <!-- ============================================================== -->
            <!-- Container fluid  -->
            <!-- ============================================================== -->
            <div class="container-fluid">
                <!-- ============================================================== -->
                <!-- Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <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>
                <!-- ============================================================== -->
                <!-- End Bread crumb and right sidebar toggle -->
                <!-- ============================================================== -->
                <!-- ============================================================== -->
                <!-- Start Page Content -->
                <!-- ============================================================== -->
                <div class="row">
                    <!-- column -->
                    <div class="col-sm-12">
                        <div class="card">
                            <div class="card-block">
                                <h4 class="card-title">权限</h4>
                                <div class="table-responsive">
                                    <form class="form-horizontal form-material" method="post" action="/modifygroupprivilege" >
                                    <table class="table">
                                        <thead>
                                            <tr>
                                                <th>功能</th>
                                                {% for group in groups %}
                                                <th>{{ group }}</th>
                                                {% end %}
                                            </tr>
                                        </thead>
                                        <tbody>
                                            {% for func in funclist %}
                                            <tr>
                                                <td>{{ func }}</td>
                                                {% for group in groups %}
                                                    {% set func_name = func+'_'+group  %}
                                                    {% if groupprivilege[func_name] == "on" %}
                                                    <td><input type="checkbox" class="checkbox checkbox-circle" name="{{ func }}_{{ group }}" id="{{ func }}_{{ group }}" checked=true></input></td>
                                                    {% else %}
                                                    <td><input type="checkbox" class="checkbox checkbox-circle" name="{{ func }}_{{ group }}" id="{{ func }}_{{ group }}" ></input></td>
                                                    {% end %}
                                                {% end %}
                                            </tr>
                                            {% end %}
                                        </tbody>
                                    </table>
                                    <button type="submit" class="btn btn-success">修改</button>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- ============================================================== -->
                <!-- End PAge Content -->
                <!-- ============================================================== -->
            </div>
            <!-- ============================================================== -->
            <!-- End Container fluid  -->
            <!-- ============================================================== -->
            <!-- ============================================================== -->
            <!-- footer -->
            <!-- ============================================================== -->
            <footer class="footer text-center">
                © 2020 Tornado考勤系统
            </footer>
            <!-- ============================================================== -->
            <!-- End footer -->
            <!-- ============================================================== -->
        </div>
        {% end %}

这个文件需要注意的一点是这句话:{% set func_name = func+'_'+group  %}。它的目的是在前端设置一个局部变量。由于我们groupprivilege的key值是由两个字典的key值拼成的,所以这里需要定义一个临时变量拼接一下,否则直接写如下的语法会报错:

{% if groupprivilege[{{ func }}+'_'+{{ group }}] == "on" %}

 这样,我们就实现了给用户组设置权限的功能。

2 查看权限

查看权限就非常简单了,就是把修改权限页面的表单的checkbox换成对应的对钩和叉子图片即可,后端则和ModifyGroupPrivilege的get方法一样,这里就只贴后端代码了:

# user_app.py
class ViewGroupPrivilege(BaseHandler):
    def get(self):
        result = {}
        groups = session.query(UserGroup)
        for group in groups:
            result[group.groupname] = ''
        privilegepath = gettemplatepath('viewgroupprivilege.html')
        groupprivilege = getgroupprivilege()
        self.render(privilegepath,groups = result,funclist=ALLFUNCTIONLIST,groupprivilege=groupprivilege)

# main.py
def make_app():
    routelist = [
        # ...
        (r"/viewgroupprivilege",ViewGroupPrivilege)
        # ...
    ]
    # ...

3 检查权限

我们将检查权限放在BaseHandler中,这样只需修改少量代码就可以让每个功能都被权限系统控制起来。

# server/apps/basehandler.py
# ...
class BaseHandler(tornado.web.RequestHandler)
    # ...
        def checkprivilege(self):
        functionname = self.__class__.__name__
        functionlist = getprivilegefunclist(self.get_current_user()).split(',')
        if functionname in functionlist:
            return True
        else:
            return False


    def render(self, template_name, **kwargs):
        currentuser = self.get_current_user()
        usergroup = getusergroup(currentuser)
        if not self.checkprivilege():
            resultpath = gettemplatepath('result.html')
            super(BaseHandler, self).render(resultpath, currentuser=currentuser, currentusergroup=usergroup,
                                            result='当前用户无此权限')
            return
        super(BaseHandler, self).render(template_name,currentuser=currentuser,currentusergroup=usergroup,**kwargs)

这里实现一个新函数checkprivilege,它会检查当前功能(RequestHandler)是否在当前用户所处的用户组中。如果当前功能在用户组的权限中,返回True,否则返回False;在render函数中,我们调用这个checkprivilege函数,如果结果是False,那么直接渲染无此权限的页面,否则继续执行正常的渲染函数。

getprivilegefunclist在userutil.py中实现:

# userutil.py
# ...
def getprivilegefunclist(usernaame):
    group = getusergroup(usernaame)
    funclist = session.query(GroupPrivilege).filter(GroupPrivilege.groupname == group).first()
    if funclist is None:
        return ''
    return funclist.funclist

一个很简单的小函数。

4 初始化权限

我们的权限系统已经很完善了,现在需要我们提供一个初始化权限,以便新建的用户组可以基于这个权限开始工作,或增删权限。

在globalsettings.py中声明一个名为DEFAULTFUNCTIONLIST的变量,在其中定义一些我们的基本功能作为新用户组的默认权限:

# globalsettings.py
DEFAULTFUNCTIONLIST='Index,Register,ViewUserGroup,Login,PersonalInfo,LogOut,FillTimeSheet,ViewTimeSheet,CreateTimeSheetEvent,TimeSheetIndex,UserOrganization,ApproveTimeSheetIndex,ApproveTimeSheet,ApproveTimeSheet,RejectTimeSheet,CreateTimeSheetEventCategory,CreateVacationApply,ViewVacationApply,ApproveVacationApply,ViewGroupPrivilege'

然后,修改userutil.py中的createusergroup函数:

# userutil.py
from setting.globalsettings import DEFAULTFUNCTIONLIST
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)
        # 创建用户组权限
        if result == 'Success':
            newgroupprivilege = GroupPrivilege(groupname=groupname,funclist=DEFAULTFUNCTIONLIST)
            result = insertdata(newgroupprivilege)
    return result

这样,当我们建立新的用户组时,也同时使用默认配置为其建立了默认权限。

十七 总结

这个系列到此就结束了。在这个系列的博客中,和大家分享了一些tornado框架搭建网站的过程,也算是自己在学习过程中的笔记和总结。我们这个系统实现了基本的用户注册/登录系统、核心的考勤/请假系统以及最后的权限管控部分,也算是“麻雀虽小,五脏俱全”了。在之后,我会好好思考一下下一个系列是什么,希望可以继续和大家分享程序开发中的一些经历,也希望大家继续关注~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值