在上一篇博客里,我们实现了建立用户组的功能,现在让我们来注册我们的第一个用户,并实现一个初始化系统的功能。
七 用户注册
我们的用户注册界面长这个样子:
这个页面包含一个简单的表单,可供用户输入用户名、密码、Email以及选择用户组。
这次我们先写后端函数,我们需要两个util函数来完成用户注册功能:createuser和encryption。前者用于根据参数来创建用户,而后者实现对密码的加密功能,最终存放在数据库中的是加密后的密码。
# util/users/userutil.py
# ...
import hashlib
# ...
def validateemail(email):
pattern_email = re.compile(r'^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}$')
if pattern_email.match(email):
return True
return False
def encryption(password):
m = hashlib.md5()
byte_password = bytes(password,encoding='utf-8')
m.update(byte_password)
newpassword = m.hexdigest()
return newpassword
def createuser(username,password,email,usergroup):
result = 'Fail'
if not validateemail(email):
return result
user = session.query(User).filter(User.username == username).first()
if user is not None:
return result
user = session.query(User).filter(User.email == email).first()
if user is not None:
return result
if usergroup == 'Root' or usergroup == 'Visitor':
return result
# user和email都不重复,则创建user
password = encryption(password)
newuser = User(username=username,password=password,email=email,usergroup=usergroup,state='WaitForApprove',registerdate=date.today(),
lastlogintime=datetime.now())
result = insertdata(newuser)
return result
在encyption函数中,我们对传入的密码做MD5,使得最终存放在数据库中的值是密码的MD5值;在createuser函数中,我们要使用validateemail函数对用户输入的email地址进行校验,如果是合法的email地址,则再根据用户名和email地址分别查询数据库,若都不重复,则根据这些参数来创建用户。此外,我们这里特意排除了Root和Visitor用户组,不允许用户注册为这两个用户组的用户。注意,对密码要先调用encyption函数进行一下加密。新注册用户的状态为WaitForApprove,需要之后得到管理员的批准。
让我们回到main.py,实现注册功能的RequestHandler:
# server/main.py
# ...
class Register(BaseHandler):
def get(self):
registerpath = gettemplatepath('register.html')
usergroups = getallusergroup()
self.render(registerpath,usergroups=usergroups)
def post(self):
username = self.get_argument('username')
password = self.get_argument('password')
email = self.get_argument('email')
usergroup = self.get_argument('usergroup')
result = createuser(username,password,email,usergroup)
resultpath = gettemplatepath('result.html')
if result == 'Success':
result = '注册成功!'
else:
result = '注册失败!'
self.render(resultpath, result=result)
def make_app():
routelist = [
# ...
(r"/createusergroup",CreateUserGroup),
# ...
]
这个get函数非常简单,通过调用getallusergroup拿到所有可选的(排除Root和Visitor)用户组,并将其渲染到页面中,作为下拉列表的选项;而post函数则是多次调用get_argument方法拿到表单的值,并调用createuser函数来建立用户;在创建好后,根据注册成功与否显示不同的提示信息。最后,别忘了把它加到路由列表中。
对应的register.html如下:
<!--register.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">
<form class="form-horizontal form-material" method="post" action="/register" >
<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="username" id="username">
</div>
</div>
<div class="form-group">
<label for="example-email" class="col-md-12">密码 *</label>
<div class="col-md-12">
<input type="password" class="form-control form-control-line" required=true name="password" id="password">
</div>
</div>
<div class="form-group">
<label class="col-md-12">Email *</label>
<div class="col-md-12">
<input type="text" class="form-control form-control-line" required=true name="email" id="email">
</div>
</div>
<div class="form-group">
<label class="col-sm-12">用户组 *</label>
<div class="col-sm-12">
<select class="form-control form-control-line" name="usergroup" id="usergroup" >
{% for usergroup in usergroups %}
<option>{{ escape(usergroup.groupname) }}</option>
{% end %}
</select>
</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 %}
这里要注意的点是,在select表单元素中,我们使用{% for %}循环,将后端传入的usergroups的每个元素作为了下拉框的选择项。{% for %}循环是前端模板中非常常用的关键词,其语法和python语法一致,可循环读可迭代对象的元素值,通常用于建立表格以及作为下拉框的选择项。
现在我们注册了第一个用户,注意这里的email是合法的地址但不是存在的地址,下面让我们看看登录功能。
八 用户登录
与注册界面一样,我们的用户登录界面也是个非常简单的表单:
这个表单包含三项元素:用户名、密码以及一个是否记住用户的单选框。当用户上次登录选择了“记住我”后,在下次来到这个页面时,应自动填入用户名和密码。
用户登录的util函数很简单,根据用户名以及加密后的密码去数据库查询有无匹配的用户,若有,则修改它的lastlogintime值,否则返回Fail。
# util/users/userutil.py
# ...
from sqlalchemy import and_
# ...
def loginuser(username,password):
password = encryption(password)
user = session.query(User).filter(and_(User.username == username,User.password == password)).first()
result = 'Fail'
if type(user) is User:
user.lastlogintime = datetime.now()
result = insertdata(user)
return result
它的Handler稍微复杂一些,主要是涉及到了cookie的设置:
# server/main.py
# ...
class Login(BaseHandler):
def get(self):
loginpath = gettemplatepath('login.html')
loginusername = self.get_secure_cookie('loginusername')
loginuserpassword = self.get_secure_cookie('loginuserpassword')
remembermevalue = self.get_secure_cookie('rememberme')
remembermevalue = str(remembermevalue,encoding='utf-8')
if loginusername is None and loginuserpassword is None:
loginusername = ''
loginuserpassword = ''
self.render(loginpath,loginusername=loginusername,loginuserpassword=loginuserpassword,remembermevalue=remembermevalue)
def post(self):
username = self.get_argument('username')
password = self.get_argument('password')
rememberme = self.get_argument('rememberme')
if rememberme == 'on':
self.set_secure_cookie('loginusername',username)
self.set_secure_cookie('loginuserpassword',password)
self.set_secure_cookie('rememberme','T')
result = loginuser(username,password)
resultpath = gettemplatepath('result.html')
if result == 'Success':
result = '登录成功!'
self.set_secure_cookie('currentuser',username)
self.redirect('/')
else:
result = '用户名或密码错误!'
self.render(resultpath, result=result)
# ...
def make_app():
routelist = [
# ...
(r"/login",Login),
# ...
]
# ...
在Login的get方法中,会尝试去取loginusername、loginuserpassword和rememberme这三个cookie的值,并将其值反映到表单上;而在post方法中,则是根据表单中“记住我”是否被勾上而设置以上三个cookie的值。若根据输入的用户名和密码成功登录,则将当前用户的用户名放在currentuser中,表示该用户处于登录状态。在成功登录后,使用redirect方法重定向到主页,若登录失败,则提示错误信息。
前端页面代码如下:
<!--login.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">
<form class="form-horizontal form-material" method="post" action="/login" >
<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="username" id="username" value="{{ loginusername }}">
</div>
</div>
<div class="form-group">
<label class="col-md-12">密码</label>
<div class="col-md-12">
<input type="password" class="form-control form-control-line" required=true name="password" id="password" value="{{ loginuserpassword }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
{% if remembermevalue == "T" %}
<input type="checkbox" class="checkbox checkbox-circle" name="rememberme" id="rememberme" checked>记住我</input>
{% else %}
<input type="checkbox" class="checkbox checkbox-circle" name="rememberme" id="rememberme">记住我</input>
{% end %}
</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 %}
这里值得一提的是,我们可以使用后端传进来的变量作为表单的属性值,如用户名和密码的value值就来自于后端的传入;而对于checkbox,决定其打勾与否的属性名叫checked,这里也是使用{% if %}来生成打勾与否的两种checkbox。
在登录后,我们的用户名会显示在网站右上角。
九 系统初始化
对于我们这个系统而言,最基本的用户组有两个:Root组和Visitor组。Root组毫无疑问,是根用户组,其下也只有一个用户Root;而Visitor组作为未登录用户的组,拥有最少的权限。
我们打开util/users/userutil.py,编写inituser和hasinit函数。前者用于创建Root用户,创建Root和Visitor用户组,而后者用于判断系统是否已初始化。
# util/users/userutil.py
# ...
def inituser():
# 创建Root用户组以及Root用户
usergroup = session.query(UserGroup).filter(UserGroup.groupname == 'Root').first()
result = 'Fail'
if usergroup is None:
# 用户组不存在,创建
newusergroup = UserGroup(groupname='Root', createdate=date.today())
result = insertdata(newusergroup)
if result == 'Success':
user = session.query(User).filter(User.username == 'Root').first()
if user is None:
rootuser = User(username='Root',password=encryption('123456'),email='<email地址>',usergroup='Root',state='Approved',registerdate=date.today(),
lastlogintime=datetime.now())
result = insertdata(rootuser)
if result == 'Success':
usergroup = session.query(UserGroup).filter(UserGroup.groupname == 'Visitor').first()
if usergroup is None:
newusergroup = UserGroup(groupname='Visitor', createdate=date.today())
result = insertdata(newusergroup)
if result == 'Success':
print('系统初始化完成')
return result
def hasinit():
result = False
usergroup = session.query(UserGroup).filter(UserGroup.groupname == 'Root').first()
if type(usergroup) is not UserGroup:
return result
else:
rootuser = session.query(User).filter(User.username == 'Root').first()
if type(rootuser) is not User:
return result
else:
result = True
return result
在inituser中,我们依次判断Root用户组、Root用户和Visitor用户组是否存在,若都不存在,则依次创建,并返回初始化结果。这里要注意的是,由于之前的createuser不允许建立Root组的用户,因此我们需要使用直接建立数据库对象的方式来建立Root用户,这里要记得对密码加密;而hasinit主要判断Root用户组和Root用户是否存在,若不存在,则需要在主函数中调用inituser函数来进行初始化:
# server/main.py
# ...
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)
mainserver = tornado.ioloop.IOLoop.current()
mainserver.start()
现在,当我们第一次运行系统时,就会自动创建Root用户以及Root和Visitor用户组。
在这篇博客中,我们进一步完善了现有的用户系统,实现了注册、登录以及初始化网站功能。在之后的博客中,将继续介绍用户系统剩余的部分:用户管理以及查看用户信息,然后我们就将进入假单及考勤部分的开发,希望大家继续关注~