Tornado笔记——用Tornado搭建假单统计考勤系统(八)
在上一篇博客中,我們補全了一些用戶系統的相關功能,這期讓我們來實現用戶的上下級關系以及考勤審批
十 用戶上下級和考勤審批
在我們的系統中,每個用戶只有一個上級,但每個用戶可以有多個下級。因此,我們需要給User表加一個名為supervisor的字段,表明該用戶的上級是誰。一個用戶的可以審批其所有下級的考勤,他自身的考勤也只能被他的上級審批。
我們migrate目錄下打開Powershell,輸入以下命令:
alembic revision -m "add supervisor on user table"alembic會在versions下生成如下命名方式的文件:ead049362c51_add_supervisor_on_user_table.py。開頭的16進制字符串為當前數據庫的版本,每人都是不同的。
在ead049362c51_add_supervisor_on_user_table.py中輸入以下內容,為User表加入supervisor字段:
# add_supervisor_on_user_table.py"""add supervisor on user tableRevision ID: ead049362c51 Revises: ac252cf2f6aa Create Date: 2020-11-10 20:47:31.458590""" from alembic import op import sqlalchemy as sa# revision identifiers, used by Alembic. revision = 'ead049362c51' down_revision = 'ac252cf2f6aa' branch_labels = None depends_on = Nonedef upgrade():op.add_column('user', sa.Column('supervisor', sa.String))def downgrade():pass然后回到powershell,輸入以下命令完成對數據庫的升版:
alembic upgrade head再打開database/tbluser.py,把新的supervisor字段加到User類中:
# database/tbluser.pyfrom database.tablebase import Base from sqlalchemy import Column,String,Integer,Date,DateTimeclass User(Base):__tablename__ = 'user'id = Column(Integer,autoincrement=True,primary_key=True)username = Column(String,unique=True,nullable=False)password = Column(String,nullable=False)email = Column(String,unique=True,nullable=False)usergroup = Column(String,nullable=False)state = Column(String)registerdate = Column(Date)lastlogintime = Column(DateTime)supervisor = Column(String)def __repr__(self):return '<user(username=%s,email=%s,registerdate=%s)>' % (self.username,self.email,self.registerdate)這樣,我們數據庫層面的準備工作就大功告成,下面讓我們開始實現調整用戶上下級的功能(俗稱“掛人”,即把某個用戶掛在某個用戶的下面)。實現后的效果如下圖:
在這個頁面中,我們可以對用戶的上下級關系進行調整,也可以對用戶所屬的用戶組進行調整。?
我們打開user_app/user_app.py,建立UserOrganization RequestHandler:
# user_app.py # ...class UserOrganization(BaseHandler):def get(self):userorganizationpath = gettemplatepath('userorganization.html')users = session.query(User).filter(User.username != 'Root')usergroups = getallusergroup()userInfos = []for user in users:usergrouplist = []usersupervisorlist = []userInfo = {}userInfo['username'] = user.usernameuserInfo['usergroup'] = user.usergroupif user.usergroup not in usergrouplist:usergrouplist.append(user.usergroup)if user.supervisor not in usersupervisorlist and type(user.supervisor) is str:usersupervisorlist.append(user.supervisor)userInfo['formid'] = user.iduserInfo['groupid'] = str(user.id)+'_group'userInfo['supervisorid'] = str(user.id) + '_supervisor'for othergroup in usergroups:if othergroup.groupname not in usergrouplist:usergrouplist.append(othergroup.groupname)for otheruser in users:if otheruser.username not in usersupervisorlist:usersupervisorlist.append(otheruser.username)userInfo['usergrouplist'] = usergrouplistuserInfo['usersupervisorlist'] = usersupervisorlistuserInfos.append(userInfo)self.render(userorganizationpath,userInfos=userInfos)def post(self):userid = self.get_argument('userid')username = self.get_argument('username')usergroupid = userid + '_group'usergroup = self.get_argument(usergroupid)supervisorid = userid + '_supervisor'supervisor = self.get_argument(supervisorid)result = 'Fail'result = changeuserorganization(username,usergroup,supervisor)resultpath = gettemplatepath('result.html')if result == 'Success':self.redirect('/userorganization')else:result = '操作失敗!'self.render(resultpath,result=result)# main.py # ... routelist = [# ...(r"/userorganization",UserOrganization),# ... ] # ...在get請求中,我們會獲得除了Root用戶外的所有用戶,并將其相關信息返回到前端頁面顯示;在post頁面則是根據選擇的用戶組和上級用戶將選定用戶的用戶組和上級改變。
changeuserorganization函數位于userutil.py中,代碼如下:
# util/users/userutil.pydef changeuserorganization(username,usergroup,supervisor):print(username)user = session.query(User).filter(User.username == username).first()result = 'Fail'if type(user) is User:user.usergroup = usergroupuser.supervisor = supervisorresult = insertdata(user)if result == 'Success':pass#sendapprovemail(user.username,usergroup,user.email)return result這個函數沒啥說的,根據傳入的用戶名去修改用戶組和上級,如果修改成功后,還可以調用之前寫的email模塊向用戶發送郵件,這里就先用pass占個位。
調整用戶組織的前端頁面代碼如下:
<!--userorganization.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"><table class="table"><thead><tr><th>#</th><th>用戶名</th><th>用戶組</th><th>直屬領導</th><th>操作</th></tr></thead><tbody>{% for userinfo in userInfos %}<tr><form method="post" id="{{ userinfo['formid'] }}" action="/userorganization"><td><input type="text" value="{{ userinfo['formid'] }}" readonly=true id='userid' name='userid'/></td><td><input type="text" value="{{ escape(userinfo['username']) }}" readonly=true id='username' name='username'/></td><td><select class="form-control form-control-line" name="{{ escape(userinfo['groupid']) }}" id="{{ escape(userinfo['groupid']) }}" >{% for usergroup in userinfo['usergrouplist'] %}<option>{{ escape(usergroup) }}</option>{% end %}</select></td><td><select class="form-control form-control-line" name="{{ escape(userinfo['supervisorid']) }}" id="{{ escape(userinfo['supervisorid']) }}" >{% for supervisor in userinfo['usersupervisorlist'] %}<option>{{ escape(supervisor) }}</option>{% end %}</select></td><td><button type="submit" class="btn btn-success">修改</button></td></form></tr>{% end %}</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 %}?這個頁面的代碼與上篇博客中寫的用戶審批頁面基本相似,都是為每個用戶建立一個表單,用于對其修改。
最后,不要忘了在導航欄中加入這部分的導航,我們當前的導航欄代碼應該是這個樣子,包含了我們之前實現的所有功能,以及一些我們還沒有介紹到的功能:
<!--base_nav.html--><nav class="sidebar-nav"><ul id="sidebarnav"><li><a href="/" class="waves-effect"><i class="fa fa-bank m-r-10" aria-hidden="true"></i>主頁</a></li><li><a href="#" data-toggle="collapse" data-target="#timesheetmanage"><i class="fa fa-calendar m-r-10" aria-hidden="true"></i>考勤管理</a><ul id="timesheetmanage" class="collapse"><li><a href="/createtimesheetevent" class="waves-effect"><i class="fa fa-clock-o m-r-10" aria-hidden="true"></i>創建考勤事件</a></li><li><a href="/timesheetindex" class="waves-effect"><i class="fa fa-pencil m-r-10" aria-hidden="true"></i>考勤</a></li><li><a href="/approvetimesheetindex" class="waves-effect"><i class="fa fa-check m-r-10" aria-hidden="true"></i>審批考勤</a></li></ul></li><li><a href="#" data-toggle="collapse" data-target="#usergroupmanage"><i class="fa fa-sitemap m-r-10" aria-hidden="true"></i>用戶組管理</a><ul id="usergroupmanage" class="collapse"><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-info-circle m-r-10" aria-hidden="true"></i>查看用戶組</a></li></ul></li><li><a href="#" data-toggle="collapse" data-target="#usermanage"><i class="fa fa-user-circle m-r-10" aria-hidden="true"></i>用戶管理</a><ul id="usermanage" class="collapse"><li><a href="/usermanage" class="waves-effect"><i class="fa fa-user-o m-r-10" aria-hidden="true"></i>用戶審批</a></li><li><a href="/userorganization" class="waves-effect"><i class="fa fa-info-circle m-r-10" aria-hidden="true"></i>調整用戶組織</a></li></ul></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>在把用戶們“掛”好后,讓我們開始實現審批考勤的功能。審批考勤功能主頁面如下:
從圖中可以看出,test用戶下面掛著test1和test2兩名用戶,且test1填寫了今年1月、10月和11月的考勤(這有點不太合理,之后會對功能做一些限制);test2填寫了今年1月和3月的考勤。除了test1在11月的考勤已被批準外,剩下的考勤均處于待批準狀態。
點開批準鏈接,可以進入到審批考勤頁面:
?
同之前查看考勤的頁面基本相同,只不過底下多了批準和拒絕的選項。
我們打開timesheet_app/timesheet_app.py,輸入以下代碼:
# timesheet_app/timesheet_app.py # ...class ApproveTimeSheetIndex(BaseHandler):def get(self):approveinfolist = []username = ''bytes_user = self.get_secure_cookie('currentuser')if type(bytes_user) is bytes:username = str(bytes_user, encoding='utf-8')approvetimesheetindexpath = gettemplatepath('approvetimesheetindex.html')employees = session.query(User).filter(User.supervisor==username)year = datetime.datetime.today().yearfor employee in employees:approveinfo = {}monthlist = []approveinfo['employee'] = employee.usernametimesheets = session.query(TimeSheet).filter(and_(TimeSheet.username == employee.username,TimeSheet.year == year))for timesheet in timesheets:monthlist.append(timesheet.month)approveinfo[timesheet.month] = timesheet.stateapproveinfo['monthlist'] = monthlistapproveinfolist.append(approveinfo)self.render(approvetimesheetindexpath,approveinfolist=approveinfolist,year=year) # ...這里首先會選出當前用戶的所有下級,并將他們今年填寫過的考勤一并列出。
前端頁面approvetimesheetindex.html如下:
<!--approvetimesheetindex.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"><table class="table"><thead><tr><th>員工</th><th>年</th><th>月</th><th>操作</th></tr></thead><tbody>{% for approveinfo in approveinfolist %}{% for month in approveinfo['monthlist'] %}<tr><td>{{ escape(approveinfo['employee']) }}</td><td>{{ year }}</td><td>{{ month }}</td>{% if approveinfo[month] == 'Approved' %}<td><a href="/approvetimesheet/year={{ year }}&month={{ month }}&employee={{ approveinfo['employee'] }}">查看</a></td>{% else %}<td><a href="/approvetimesheet/year={{ year }}&month={{ month }}&employee={{ approveinfo['employee'] }}">批準</a></td>{% end %}</tr>{% end %}{% end %}</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中添加其路由:
# main.py routelist = [# ...(r"/approvetimesheetindex",ApproveTimeSheetIndex),# ... ]回到timesheet_app/timesheet_app.py,實現ApproveTimeSheet和RejectTimeSheet功能:
# timesheet_app/timesheet_app.pyclass ApproveTimeSheet(BaseHandler):def get(self,year,month,employee):year = int(year.split('=')[1])month = int(month.split('=')[1])employee = employee.split('=')[1]timesheetviewer = TimeSheetViewer(username=employee,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()approvetimesheetpath = gettemplatepath('approvetimesheet.html')self.render(approvetimesheetpath, monthdaymap=monthday_map,weeklist=week_list,timesheetmap=timesheet_map,timesheetstate=timesheet_state,timesheetapprovedate=timesheet_approvedate,timesheetapproveuser=timesheet_approveuser,employee=employee,year=year,month=month)def post(self):username = ''bytes_user = self.get_secure_cookie('currentuser')if type(bytes_user) is bytes:username = str(bytes_user, encoding='utf-8')employee = self.get_argument('employee')year = self.get_argument('year')month = self.get_argument('month')result = changetimesheetstate(username,employee,int(year),int(month),'Approved')resultpath = gettemplatepath('timesheetfail.html')if result == 'Success':self.redirect('/approvetimesheetindex')else:result = '操作失敗!'self.render(resultpath,result=result)class RejectTimeSheet(BaseHandler):def get(self,year,month,employee):username = ''bytes_user = self.get_secure_cookie('currentuser')if type(bytes_user) is bytes:username = str(bytes_user, encoding='utf-8')employee = employee.split('=')[1]year = year.split('=')[1]month = month.split('=')[1]result = changetimesheetstate(username,employee,int(year),int(month),'Reject')resultpath = gettemplatepath('timesheetfail.html')if result == 'Success':self.redirect('/approvetimesheetindex')else:result = '操作失敗!'self.render(resultpath,result=result)我們的ApproveTimeSheet分兩部分:get部分用于顯示之前的頁面,基本上可以視為ViewTimeSheet的翻版,只不過多傳入了employee的信息;而post頁面會調用changetimesheetstate函數來改變指定timesheet的狀態,改變完畢后重定向到approvetimesheetindex頁面中。changetimesheetstate函數位于timesheet/timesheetutil.py中:
# timesheet/timesheetutil.pydef changetimesheetstate(approver,employee,year,month,state):timesheet = session.query(TimeSheet).filter(and_(TimeSheet.username == employee,TimeSheet.year == year, TimeSheet.month == month)).first()result = 'Fail'if type(timesheet) is TimeSheet:timesheet.state = statetimesheet.approveusername = approvertimesheet.approvedate = datetime.datetime.today()result = insertdata(timesheet)return result# ...這個函數沒什么說的,基本的數據修改操作。
RejectTimeSheet函數使用get請求方式,同樣調用changetimesheetstate函數來拒絕掉考勤。
以上兩個功能對應的前端代碼如下:
<!--approvetimesheet.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">{{ escape(employee) }} - {{ year }} - {{ month }}</h3><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/approvetimesheetindex">審批考勤</a></li><li class="breadcrumb-item active">{{ escape(employee) }} - {{ year }} - {{ month }}</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 %}{% if timesheetstate != 'Approved' %}<table class="table"><thead><tr><th>員工</th><th>年份</th><th>月份</th><th>操作</th></tr></thead><tbody><tr><form method="post" action="/approvetimesheet"><td><input type="text" value="{{ employee }}" readonly=true id='employee' name='employee'/></td><td><input type="text" value="{{ year }}" readonly=true id='year' name='year'/></td><td><input type="text" value="{{ month }}" readonly=true id='month' name='month'/></td><td><button type="submit" class="btn btn-success">批準</button> |<a href="/rejecttimesheet/year={{ year }}&month={{ month }}&employee={{ employee }}">拒絕</a></td></form></tr></tbody></table>{% end %}</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 %} <!-- ... -->這里需要注意的是,“批準”是作為表單的submit按鈕實現的,因此“批準”所需的參數都可以從表單中獲得,無需帶路由的參數;但“拒絕”則是使用普通的a標簽,因此這里需要帶路由的參數:
# main.py# ... routelist = [# ...(r"/approvetimesheet/(year=\d*)&(month=\d*)&(employee=.*)",ApproveTimeSheet),(r"/approvetimesheet", ApproveTimeSheet),(r"/rejecttimesheet/(year=\d*)&(month=\d*)&(employee=.*)", RejectTimeSheet),# ... ]我們對ApproveTimeSheet設置了兩種不同的路由:帶參數的路由用于從ApproveTimeSheetIndex進入到具體的審批頁面,而不帶參數的用于具體的審批功能,因為年、月以及雇員都已經在審批頁面中顯示出來了,通過表單可直接獲取到;RejectTimeSheet則是使用帶參數的路由。
在這篇博客中,我們實現了用戶的上下級關系調整以及審批考勤的功能,整個考勤系統初步完成。在之后的博客中,我們將繼續開發請假系統以及權限控制相關的內容,希望大家繼續關注~
總結
以上是生活随笔為你收集整理的Tornado笔记——用Tornado搭建假单统计考勤系统(八)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 九 Java_集合框架
- 下一篇: tomcat配置SSI