欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-30 16:33:59
...

在上一篇博客里,我们实现了建立用户组的功能,现在让我们来注册我们的第一个用户,并实现一个初始化系统的功能。

七 用户注册

我们的用户注册界面长这个样子:

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

这个页面包含一个简单的表单,可供用户输入用户名、密码、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是合法的地址但不是存在的地址,下面让我们看看登录功能。

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

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

八 用户登录

与注册界面一样,我们的用户登录界面也是个非常简单的表单:

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

 这个表单包含三项元素:用户名、密码以及一个是否记住用户的单选框。当用户上次登录选择了“记住我”后,在下次来到这个页面时,应自动填入用户名和密码。

用户登录的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。

在登录后,我们的用户名会显示在网站右上角。

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

九 系统初始化

对于我们这个系统而言,最基本的用户组有两个: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用户组。

在这篇博客中,我们进一步完善了现有的用户系统,实现了注册、登录以及初始化网站功能。在之后的博客中,将继续介绍用户系统剩余的部分:用户管理以及查看用户信息,然后我们就将进入假单及考勤部分的开发,希望大家继续关注~