这篇和上一篇博文隔的时间有点远了,希望大家还记得我们这个系统之前都做了什么。在上一篇博客中,我们构造了一个复杂的表单和calendar类来实现填写考勤的功能。现在,我们要实现查看考勤以及审批考勤的功能。
6 项目目录结构的调整
现在我们的项目已经有了很多的RequestHandler,把这么多RequestHandler都放在一个main.py文件里会很不好管理,因此,我们需要对项目的结构做一点调整,把这些RequestHandler按功能放到不同的目录里。
调整后的目录结构如下:
我们主要调整server目录的结构:在server下新建一个名为apps的package,再在apps包中新建timesheet_app和user_app这两个package,分别存放user和timesheet相关功能的RequestHandler。在timesheet_app包中新建timesheet_app.py,把如下RequestHandler从main.py中挪进去:
RequestHandler名称 | 对应功能 |
TimeSheetIndex | 考勤 |
FillTimeSheet | 填写考勤 |
在user_app包中新建user_app.py,把如下RequestHandler从main.py中挪进去:
RequestHandler名称 | 对应功能 |
Register | 注册用户 |
Login | 用户登录 |
CreateUserGroup | 创建用户组 |
ViewUserGroup * | 查看用户组 |
PersonalInfo * | 个人信息查看 |
UserManage * | 用户管理 |
Approve * | 批准用户 |
LogOff * | 注销用户 |
LogOut * | 用户登出 |
翻了翻前面的博客,发现带*的功能还没有写到博客中,等我们将考勤部分写的差不多了再来将这些功能补全。
最后,在apps包下建立basehandler.py,将我们的BaseHandler放进去,大家可以自行补全timesheet_app.py、user_app.py和basehandler.py中的import部分。
这样,我们的目录结构就调整完毕,main.py会变的简洁很多,新的main.py代码如下所示:
# server/main.py
import sys
import platform
import os
sys.path.append(os.path.abspath('..'))
from setting.globalsettings import getconfig
from util.users.userutil import hasinit, inituser
import tornado.ioloop
import tornado.web
from server.apps.user_app.user_app import *
from server.apps.timesheet_app.timesheet_app import *
if platform.system() == 'Windows':
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
class Index(BaseHandler):
def get(self):
indexpath = gettemplatepath('index.html')
self.render(indexpath)
def make_app():
routelist = [
# 保持不变
]
return tornado.web.Application(routelist,cookie_secret='12f6352#527',autoreload=True,debug=True,template_path='D:\\LeaveManage\\server\\template')
if __name__ == '__main__':
init_result = hasinit()
if init_result == False:
result = inituser()
if result == 'Fail':
print('初始化网站失败!')
exit(0)
app = make_app()
port = int(getconfig('PORT'))
app.listen(port)
print('系统启动')
tornado.ioloop.IOLoop.current().start()
这里注意,当我们建立Application时,我们要指定template_path到我们的template目录,否则在调整完目录结构后,tornado不会找到这个目录而是按RequestHandler所在目录去寻找模板文件,那样肯定找不到。
现在,我们可以继续往下进行了,实现查看考勤以及批准考勤的功能。
7 创建考勤事件
在实现查看考勤之前,让我们先实现另一个小功能——创建考勤事件。通过这个功能,可以让我们添加不同的考勤事件,如工作、病假、调休等,以供在之前的填写考勤功能中选择。
该功能的页面如下:
这个页面十分简单, 上半部分列出现有的考勤事件,下半部分的表单用于建立新的表单事件。事件名即上半部分的事件代码,是事件真正存在DB中的值,而事件别名是用于查看考勤时显示出的事件名称。
我们在timesheetutil.py中编写createtimesheetevent函数,用于生成考勤事件:
# timesheetutil.py
# ...
from database.tbltimesheetevent import TimeSheetEvent
def createtimesheetevent(eventcode,nickname):
timesheetevent = session.query(TimeSheetEvent).filter(TimeSheetEvent.eventcode == eventcode)
result = 'Fail'
if type(timesheetevent) is not TimeSheetEvent:
newtimesheetevent = TimeSheetEvent(eventcode=eventcode,nickname=nickname)
result = insertdata(newtimesheetevent)
return result
这个小函数没啥说的,根据eventcode查一下是否有考勤事件存在,如果没有的话则创建之。
然后,我们在timesheet_app.py实现CreateTimeSheetEvent RequestHandler:
# timesheet_app.py
class CreateTimeSheetEvent(BaseHandler):
def get(self):
timesheeteventpath = gettemplatepath('createtimesheetevent.html')
timesheetevents = session.query(TimeSheetEvent)
self.render(timesheeteventpath,timesheetevents=timesheetevents)
def post(self):
eventcode = self.get_argument('eventcode')
eventnickname = self.get_argument('eventnickname')
result = createtimesheetevent(eventcode,eventnickname)
resultpath = gettemplatepath('result.html')
if result == 'Fail':
self.render(resultpath,result=result)
else:
self.redirect('/createtimesheetevent')
# main.py
# ...
routelist = [
# ...
(r"/createtimesheetevent",CreateTimeSheetEvent),
# ...
]
# ...
这个页面也没什么说的,常规的抓取数据后显示/插入,不要忘了在main.py中注册其路由。
前端页面createtimesheetevent.html的block块代码如下:
<!--createtimesheetevent.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">
<div class="card">
<div class="card-block">
<h4 class="card-title">当前考勤事件</h4>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>事件代码</th>
<th>事件别名</th>
</tr>
</thead>
<tbody>
{% for event in timesheetevents %}
<tr>
<td>{
{ event.id }}</td>
<td>{
{ escape(event.eventcode) }}</td>
<td>{
{ escape(event.nickname) }}</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
<div class="card-block">
<form class="form-horizontal form-material" action="/createtimesheetevent" 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="eventcode" id="eventcode">
</div>
<label class="col-md-12">别名 *</label>
<div class="col-md-12">
<input type="text" class="form-control form-control-line" required=true name="eventnickname" id="eventnickname">
</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>
<!-- Row -->
<!-- ============================================================== -->
<!-- End PAge Content -->
<!-- ============================================================== -->
</div>
<!-- ============================================================== -->
<!-- End Container fluid -->
<!-- ============================================================== -->
<!-- ============================================================== -->
<!-- footer -->
<!-- ============================================================== -->
<footer class="footer text-center">
© 2020 Tornado考勤系统
</footer>
<!-- ============================================================== -->
<!-- End footer -->
<!-- ============================================================== -->
</div>
{% end %}
在实现了这个功能后,我们就可以按照上图所示将我们的考勤事件建立起来,以便在之后的查看考勤功能中使用。
8 查看考勤
在准备好了考勤事件后,我们就可以实现查看考勤功能了。我们需要实现一个TimeSheetViewer类,这个类会返回一个key为日期value为事件别名的字典,与之前的Calendar类配合可以让我们查看用户填写的考勤结果。
打开timesheetutil.py,实现TimeSheetViewer类:
# timesheetutil.py
class TimeSheetViewer:
def __init__(self,username,year,month):
self.__username = username
self.__year = year
self.__month = month
self.__timesheetmap = {}
self.__state = ''
self.__approveuser = ''
self.__approvedate = ''
def __gettimesheetinfo(self):
timesheet = session.query(TimeSheet).filter(and_(TimeSheet.username == self.__username,TimeSheet.year == self.__year, TimeSheet.month == self.__month)).first()
timecalendar = TimeSheetCalendar(self.__year,self.__month)
timecalendar.generatecalendar()
monthmap = timecalendar.getmonthmap()
print(type(timesheet))
if type(timesheet) is TimeSheet:
self.__state = timesheet.state
self.__approveuser = timesheet.approveusername
self.__approvedate = timesheet.approvedate
for days in monthmap:
tmpday = int(days.split('-')[2])
tmpmonth = int(days.split('-')[1])
dayinfo = 'day' + str(tmpday)
if tmpmonth == self.__month:
event = session.query(TimeSheetEvent).filter(TimeSheetEvent.eventcode == getattr(timesheet,dayinfo,'')).first()
if type(event) is TimeSheetEvent:
self.__timesheetmap[days] = event.nickname
else:
self.__timesheetmap[days] = 'N/A'
else:
self.__timesheetmap[days] = 'N/A'
else:
for days in monthmap:
self.__timesheetmap[days] = 'N/A'
def gettimesheetmap(self):
self.__gettimesheetinfo()
return self.__timesheetmap
def getstate(self):
return self.__state
def getapproveuser(self):
return self.__approveuser
def getapprovedate(self):
return self.__approvedate
先看看这个类中的参数介绍:
- __username:用户名
- __year:年
- __month:月
- __timesheetmap:最终返回的结果,key为日期value为事件别名的字典
- __state:考勤表状态
- __approveuser:批准的用户
- __approvedate:批准日期
这个类的核心函数为__gettimesheetinfo。在这个函数中,首先通过year和month参数从TimeSheet表中拿到此月的数据,再调用之前的Calendar类获得此月的monthmap。然后,从TimeSheet中获得考勤表的状态、批准的用户以及批准日期,再按日期去获得每天的考勤事件;如果某个日期的考勤事件不存在,则用N/A代替。这样,我们就得到了一个key为日期value为事件别名的字典,以及拿到了考勤表的状态、批准用户以及批准日期。底下的几个getter函数就不再赘述了。
我们打开timesheet_app.py,实现ViewTimeSheet:
# timesheet_app.py
class ViewTimeSheet(BaseHandler):
def get(self,year,month):
username = ''
bytes_user = self.get_secure_cookie('currentuser')
if type(bytes_user) is bytes:
username = str(bytes_user, encoding='utf-8')
year = int(year.split('=')[1])
month = int(month.split('=')[1])
timesheetviewer = TimeSheetViewer(username=username,year=year,month=month)
timesheetviewer.gettimesheetmap()
timesheetcalendar = TimeSheetCalendar(year, month)
timesheetcalendar.generatecalendar()
monthday_map = timesheetcalendar.getmonthmap()
week_list = timesheetcalendar.getweeklist()
timesheet_map = timesheetviewer.gettimesheetmap()
timesheet_state = timesheetviewer.getstate()
timesheet_approveuser = timesheetviewer.getapproveuser()
timesheet_approvedate = timesheetviewer.getapprovedate()
viewtimesheetpath = gettemplatepath('viewtimesheet.html')
self.render(viewtimesheetpath, monthdaymap=monthday_map,weeklist=week_list,timesheetmap=timesheet_map,timesheetstate=timesheet_state,
timesheetapprovedate=timesheet_approvedate,timesheetapproveuser=timesheet_approveuser)
# main.py
# ...
routelist = [
# ...
(r"/viewtimesheet/(year=\d*)&(month=\d*)",ViewTimeSheet),
# ...
]
# ...
这个页面和之前的FillTimeSheet大致一样,只不过多调用了新写的TimeSheetViewer类,以及将其结果作为参数传入render函数中。依旧不要忘记在main.py中注册路由。
viewtimesheet.html的block块如下:
<!--viewtimesheet.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="/timesheetindex">考勤</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">
{% for week in weeklist %}
<table class="table">
<thead>
<tr>
{% for day in week %}
<th>{
{ day }}
{% if monthdaymap[day] == 'Mon' or monthdaymap[day] == 'Tues' or monthdaymap[day] == 'Wed' or monthdaymap[day] == 'Thur' or monthdaymap[day] == 'Fri' or monthdaymap[day] == 'Sat' or monthdaymap[day] == 'Sun' %}
({
{ monthdaymap[day] }})
{% else %}
(N/A)
{% end %}
</th>
{% end %}
</tr>
</thead>
<tbody>
<tr>
{% for day in week %}
<td>
{
{ timesheetmap[day] }}
</td>
{% end %}
</tr>
</tbody>
</table>
{% end %}
<table class="table">
<thead>
<tr>
<th>状态</th>
<th>审批人</th>
<th>审批日期</th>
</tr>
</thead>
<tbody>
<tr>
<td>{
{ timesheetstate }}</td>
<td>{
{ timesheetapproveuser }}</td>
<td>{
{ timesheetapprovedate }}</td>
</tr>
</tbody>
</table>
</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 %}
这个页面同样和填写考勤的页面很相似,只不过是把每周日期下的内容从下拉表单变为了实际填写的结果,并且在底下新增了一个表格用于显示审批状态、审批人以及审批日期。
效果如下所示,在日期下方显示了填写的考勤项,最下面显示考勤状态和审批人。
在这篇博客中,我们对项目结构进行了一番调整,将各类功能分门别类放到不同的包中,使得main.py文件相比以前大为简洁;实现了一个简单的小功能——创建考勤事件,这使得我们可以将考勤事件按照别名显示出来;最后,我们实现了一个TimeSheetViewer类用于生成日期-考勤的map,并构造页面将其显示出来,同时显示考勤审批的状态。
在下一篇博客中,我们将继续实现真正的审批功能,希望大家继续关注~