zoukankan      html  css  js  c++  java
  • Django实战(一)-----用户登录与注册系统3(前端页面、登录视图)

    基本框架搭建好了后,我们就要开始丰富页面内容了。最起码,得有一个用户登录的表单不是么?(注册的事情我们先放一边。)

    一、 原生HTML页面

    删除原来的login.html文件中的内容,写入下面的代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
    
        <div style="margin: 15% 40%;">
            <h1>欢迎登录!</h1>
           <form action="/login/" method="post">
                <p>
                    <label for="id_username">用户名:</label>
                    <input type="text" id="id_username" name="username" placeholder="用户名" autofocus required />
                </p>
                <p>
                    <label for="id_password">密码:</label>
                    <input type="password" id="id_password" placeholder="密码" name="password" required >
                </p>
                <input type="submit" value="确定">
            </form>
        </div>
    
    </body>
    </html>

    简单解释一下:

    • form标签主要确定目的地url和发送方法;
    • p标签将各个输入框分行;
    • label标签为每个输入框提供一个前导提示,还有助于触屏使用;
    • placeholder属性为输入框提供默认值;
    • autofocus属性为用户名输入框自动聚焦
    • required表示该输入框必须填写
    • passowrd类型的input标签不会显示明文密码

    启动服务器,访问http://127.0.0.1:8000/login/,可以看到如下图的页面:

    二、引入Bootstrap

    大多数使用Django的人都不具备多高的前端水平,通常也没有专业的前端工程师配合,自己写的CSS和JS往往惨不忍睹。怎么办?没关系,我们有现成的开源前端框架!

    Bootstrap就是最好的框架之一!

    前往Bootstrap中文网下载当前最新的v3.3.7版本代码,下载用于生产环境的 Bootstrap。

    在Django的根目录下新建一个static目录,并将解压后的bootstrap-3.3.7-dist目录,整体拷贝到static目录中,如下图所示:

    由于Bootstrap依赖JQuery,所以我们需要提前下载并引入JQuery,我这里使用的是jquery-3.2.1.js,当然别的版本也是可以的。(请自行下载JQuery)

    在static目录下,新建一个css和js目录,作为以后的样式文件和js文件的存放地,将我们的jquery文件拷贝到static/js目录下。

     

    三、静态文件设置

    关于静态文件的设置,是很多初学者吐槽最多的地方。以后我们会一点一点讲解,这里先按步骤进行。

    打开项目的settings文件,在最下面添加这么一行配置,用于指定静态文件的搜索目录:

    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, "static"),
    ]
    

    四、创建base.html模板

    既然要将前端页面做得像个样子,那么就不能和前面一样,每个页面都各写各的,单打独斗。一个网站有自己的统一风格和公用部分,可以把这部分内容集中到一个基础模板base.html中。现在,在根目录下的templates中新建一个base.html文件用作站点的基础模板。

    在Bootstrap文档中,为我们提供了一个非常简单而又实用的基本模板,代码如下:

    <!DOCTYPE html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
        <title>Bootstrap 101 Template</title>
    
        <!-- Bootstrap -->
        <link href="css/bootstrap.min.css" rel="stylesheet">
    
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
          <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
      </head>
      <body>
        <h1>你好,世界!</h1>
    
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="js/bootstrap.min.js"></script>
      </body>
    </html>
    

    我们将它整体拷贝到base.html文件中。然后修改login/templates/login/login.html文件,删除原来的所有内容,只写入如下一行代码:

    {% extends 'base.html' %}

    模板语言{% extends 'base.html' %},表示当前页面继承base.html文件中的所有内容。

    启动服务器,访问localhost:8000/login/,暂时只能看见下面的内容,但没有关系,这表示我们的模板继承系统工作正常。

     

    五、创建页面导航条

    下面我们为页面增加一个导航条,Bootstrap提供了现成的导航条组件,代码如下:

    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Brand</a>
        </div>
    
        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
          <ul class="nav navbar-nav">
            <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
            <li><a href="#">Link</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><a href="#">Action</a></li>
                <li><a href="#">Another action</a></li>
                <li><a href="#">Something else here</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="#">Separated link</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="#">One more separated link</a></li>
              </ul>
            </li>
          </ul>
          <form class="navbar-form navbar-left">
            <div class="form-group">
              <input type="text" class="form-control" placeholder="Search">
            </div>
            <button type="submit" class="btn btn-default">Submit</button>
          </form>
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#">Link</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><a href="#">Action</a></li>
                <li><a href="#">Another action</a></li>
                <li><a href="#">Something else here</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="#">Separated link</a></li>
              </ul>
            </li>
          </ul>
        </div><!-- /.navbar-collapse -->
      </div><!-- /.container-fluid -->
    </nav>
    

    显示效果是这样的:

    23.png-6.4kB

    其中有一些部分,比如搜索框是我们目前还不需要的,需要将多余的内容裁剪掉。同时,有一些名称和url地址等需要按我们的实际内容修改。最终导航条的代码如下:

    <nav class="navbar navbar-default">
          <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" aria-expanded="false">
                <span class="sr-only">切换导航条</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Mysite</a>
            </div>
    
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="my-nav">
              <ul class="nav navbar-nav">
                <li class="active"><a href="/index/">主页</a></li>
              </ul>
              <ul class="nav navbar-nav navbar-right">
                <li><a href="/login/">登录</a></li>
                <li><a href="/register/">注册</a></li>
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    

    砍掉了很多内容,不过没关系,有需要的话随时可以增加。

    下面用上面的代码,替换掉base.html中的h1标签。再次刷新页面,显示效果如下:

    为什么会这样,因为Bootstrap的CSS和JS文件没有被正常导入!

    六、使用Boostrap静态文件

    通过{% static '相对路径' %}这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来。当然,还有一种办法是使用CDN,如果有可靠的源,也是可以使用的。

    最后,base.html内容如下:

    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
        <title>{% block title %}base{% endblock %}</title>
    
        <!-- Bootstrap -->
        <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet">
    
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
          <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
        {% block css %}{% endblock %}
      </head>
      <body>
        <nav class="navbar navbar-default">
          <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" aria-expanded="false">
                <span class="sr-only">切换导航条</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Mysite</a>
            </div>
    
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="my-nav">
              <ul class="nav navbar-nav">
                <li class="active"><a href="/index/">主页</a></li>
              </ul>
              <ul class="nav navbar-nav navbar-right">
                <li><a href="/login/">登录</a></li>
                <li><a href="/register/">注册</a></li>
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    
        {% block content %}{% endblock %}
    
    
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="{% static 'js/jquery-3.2.1.js' %}"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
      </body>
    </html>
    

    要点:

    • 通过页面顶端的{% load staticfiles %}加载后,才可以使用static方法;
    • 通过{% block title %}base{% endblock %},设置了一个动态的页面title块;
    • 通过{% block css %}{% endblock %},设置了一个动态的css加载块;
    • 通过{% block content %}{% endblock %},为具体页面的主体内容留下接口;
    • 通过{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}将样式文件指向了我们的实际静态文件,下面的js脚本也是同样的道理。

    更多的前端页面知识,实在难以一言述尽,只能一点点解说,所以需要大家具备一定的基础。做Django开发,其实就是全栈开发,没有一定的前端能力,是做不好的。

    好了,现在刷新下我们的页面,可以看到如下的效果:

    由于Bootstrap以移动端优先的特性,我们的这个页面是可以动态调整的,尝试缩放一下屏幕大小,导航条会自动折叠和展开,也就是说你写的前端页面可以在手机、ipad上正常访问,但是IE.....,我们忘记它吧,哭。

    七、设计登录页面

    当前的登录页面只有网站的整体导航条,还没有添加最主要的登录表单。

    Bootstarp提供了一个基本的表单样式,代码如下:

    <form>
      <div class="form-group">
        <label for="exampleInputEmail1">Email address</label>
        <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">
      </div>
      <div class="form-group">
        <label for="exampleInputPassword1">Password</label>
        <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
      </div>
      <div class="form-group">
        <label for="exampleInputFile">File input</label>
        <input type="file" id="exampleInputFile">
        <p class="help-block">Example block-level help text here.</p>
      </div>
      <div class="checkbox">
        <label>
          <input type="checkbox"> Check me out
        </label>
      </div>
      <button type="submit" class="btn btn-default">Submit</button>
    </form>
    

    外观如下:

    我们结合Bootstrap和前面自己写的form表单,修改login/templates/login/login.html成符合项目要求的样子:

    {% extends 'base.html' %}
    {% load staticfiles %}
    {% block title %}登录{% endblock %}
    {% block css %}<link href="{% static 'css/login.css' %}" rel="stylesheet"/>{% endblock %}
    
    
    {% block content %}
        <div class="container">
            <div class="col-md-4 col-md-offset-4">
              <form class='form-login' action="/login/" method="post">
                  <h2 class="text-center">欢迎登录</h2>
                  <div class="form-group">
                    <label for="id_username">用户名:</label>
                    <input type="text" name='username' class="form-control" id="id_username" placeholder="Username" autofocus required>
                  </div>
                  <div class="form-group">
                    <label for="id_password">密码:</label>
                    <input type="password" name='password' class="form-control" id="id_password" placeholder="Password" required>
                  </div>
                  <button type="reset" class="btn btn-default pull-left">重置</button>
                  <button type="submit" class="btn btn-primary pull-right">提交</button>
              </form>
            </div>
        </div> <!-- /container -->
    {% endblock %}
    

    说明:

    • 通过{% extends 'base.html' %}继承了‘base.html’模板的内容;
    • 通过{% block title %}登录{% endblock %}设置了专门的title;
    • 通过block css引入了针对性的login.css样式文件;
    • 主体内容定义在block content内部
    • 添加了一个重置按钮。

    static/css目录中新建一个login.css样式文件,这里简单地写了点样式,不算复杂。

    body {
      background-color: #eee;
    }
    .form-login {
      max- 330px;
      padding: 15px;
      margin: 0 auto;
    }
    .form-login .form-control {
      position: relative;
      height: auto;
      -webkit-box-sizing: border-box;
         -moz-box-sizing: border-box;
              box-sizing: border-box;
      padding: 10px;
      font-size: 16px;
    }
    .form-login .form-control:focus {
      z-index: 2;
    }
    .form-login input[type="text"] {
      margin-bottom: -1px;
      border-bottom-right-radius: 0;
      border-bottom-left-radius: 0;
    }
    .form-login input[type="password"] {
      margin-bottom: 10px;
      border-top-left-radius: 0;
      border-top-right-radius: 0;
    }
    

    刷新页面,效果如下:

    这个UI算不上好看,同样可以称为简陋,但是比较纯粹,你完全可以在这个基础上,定制一些自己的CSS样式,比如添加个背景图。

    这些工作不是本项目实战的重点,大家可以自行设计。

    八、登录视图

    根据我们在路由中的设计,用户通过login.html中的表单填写用户名和密码,并以POST的方式发送到服务器的/login/地址。

    服务器通过login/views.py中的login()视图函数,接收并处理这一请求。

    我们可以通过下面的方法接收和处理请求:

    def login(request):
        if request.method == "POST":
            username = request.POST.get('username')
            password = request.POST.get('password')
            print(username, password)
            return redirect('/index/')
        return render(request, 'login/login.html')
    

    说明:

    • 每个视图函数都至少接收一个参数,并且是第一位置参数,该参数封装了当前请求的所有数据;
    • 通常将第一参数命名为request,当然也可以是别的;
    • request.method中封装了数据请求的方法,如果是“POST”(全大写),将执行if语句的内容,如果不是,直接返回最后的render()结果;
    • request.POST封装了所有POST请求中的数据,这是一个字典类型,可以通过get方法获取具体的值。
    • 类似get('username')中的键‘username’是HTML模板中表单的input元素里‘name’属性定义的值。所以在编写form表单的时候一定不能忘记添加name属性。
    • 利用print函数在开发环境中验证数据;
    • 利用redirect方法,将页面重定向到index页。

    启动服务器,然后在http://127.0.0.1:8000/login/的表单中随便填入用户名和密码,然后点击提交。然而,页面却出现了错误提示,如下图所示:

    错误原因是CSRF验证失败,请求被中断。CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的网络攻击手段,具体原理和技术内容请自行百科。Django自带对许多常见攻击手段的防御机制,CSRF就是其中一种,还有XSS、SQL注入等。

    为了解决这个问题,我们需要在前端页面的form表单内添加一个{% csrf_token %}标签:

    这个标签必须放在form表单内部,但是内部的位置可以随意。

    重新刷新login页面,确保csrf的标签生效,然后再次输入内容并提交。这次就可以成功地在Pycharm开发环境中看到接收的用户名和密码,同时浏览器页面也跳转到了首页。

    二、数据验证

    要对用户发送的数据进行验证。数据验证分前端页面验证和后台服务器验证。前端验证可以通过专门的插件或者自己写JS代码实现,也可以简单地使用HTML5的新特性。这里,我们使用的是HTML5的内置验证功能,如下图所示:

     29.png-23.7kB

    它帮我们实现了下面的功能:

    • 必填字段不能为空
    • 密码部分用圆点替代

    如果你还想要更强大和丰富的验证功能,比如限定密码长度不低于8位,用户名不能包含特殊字符等等,可以搜索并使用一些插件。

    前端页面的验证都是用来给守法用户做提示和限制的,并不能保证绝对的安全,后端服务器依然要重新对数据进行验证。

    我们前面的视图函数,没有对数据进行任何的验证,如果你在用户名处输入个空格,是可以正常提交的,这显然不行。

    甚至,如果跳过浏览器伪造请求,那么用户名是None也可以发送过来。

    通常,除了数据内容本身,我们至少需要保证各项内容都提供了且不为空,对于用户名、邮箱、地址等内容往往还需要剪去前后的空白,防止用户未注意到的空格。

    修改一下前面的代码:

    def login(request):
        if request.method == "POST":
            username = request.POST.get('username', None)
            password = request.POST.get('password', None)
            if username and password:  # 确保用户名和密码都不为空
                username = username.strip()
                # 用户名字符合法性验证
                # 密码长度验证
                # 更多的其它验证.....            
                return redirect('/index/')
        return render(request, 'login/login.html')
    
    • 通过get('username', None)的调用方法,确保当数据请求中没有username键时不会抛出异常,而是返回一个我们指定的默认值None;
    • 通过if username and password:确保用户名和密码都不为空;
    • 通过strip()方法,将用户名前后无效的空格剪除;
    • 更多的数据验证需要根据实际情况增加,原则是以最低的信任度对待发送过来的数据。

    三、验证用户名和密码

    数据验证通过了,不代表用户就可以合法登录了,因为最基本的密码对比还未进行。

    通过唯一的用户名,使用Django的ORM去数据库中查询用户数据,如果有匹配项,则进行密码对比,如果没有匹配项,说明用户名不存在。如果密码对比错误,说明密码不正确。

    def login(request):
        if request.method == "POST":
            username = request.POST.get('username', None)
            password = request.POST.get('password', None)
            if username and password:  # 确保用户名和密码都不为空
                username = username.strip()
                # 用户名字符合法性验证
                # 密码长度验证
                # 更多的其它验证.....
                try:
                    user = models.User.objects.get(name=username)
                except:
                    return render(request, 'login/login.html')
                if user.password == password:
                    return redirect('/index/')
        return render(request, 'login/login.html')
    

    说明:

    • 首先要在顶部导入models模块;
    • 使用try异常机制,防止数据库查询失败的异常;
    • 如果未匹配到用户,则执行except中的语句;
    • models.User.objects.get(name=username)是Django提供的最常用的数据查询API,不再赘述;
    • 通过user.password == password进行密码比对,成功则跳转到index页面,失败则什么都不做。

    重启服务器,然后在登录表单内,使用错误的用户名和密码,以及我们先前在admin中创建的合法的测试用户,分别提交试试。

    四、 添加提示信息

    上面的代码还缺少很重要的一部分内容,提示信息!无论是登录成功还是失败,用户都没有得到任何提示信息,这显然是不行的。

    修改一下login视图:

    from django.shortcuts import render
    from django.shortcuts import redirect
    from . import models
    def login(request):
        if request.method == "POST":
            username = request.POST.get('username', None)
            password = request.POST.get('password', None)
            message = "所有字段都必须填写!"
            if username and password:  # 确保用户名和密码都不为空
                username = username.strip()
                # 用户名字符合法性验证
                # 密码长度验证
                # 更多的其它验证.....
                try:
                    user = models.User.objects.get(name=username)
                    if user.password == password:
                        return redirect('/index/')
                    else:
                        message = "密码不正确!"
                except:
                    message = "用户名不存在!"
            return render(request, 'login/login.html', {"message": message})
        return render(request, 'login/login.html')
    

    增加了message变量,用于保存提示信息。

    当有错误信息的时候,将错误信息打包成一个字典,然后作为第三个参数提供给render()方法。这个数据字典在渲染模板的时候会传递到模板里供你调用。

    为了在前端页面显示信息,还需要对login.html进行修改:

     {% if message %}
    <div class="alert alert-warning">{{ message }}</div>
     {% endif %}
    

    Django的模板语言{% if xxx %}{% endif %}非常类似Python的if语句,也可以添加{% else %}分句。例子中,通过判断是message变量是否不为空,也就是是否有错误提示信息,如果有,就显示出来!这里使用了Bootstrap的警示信息类alert,你也可以自定义CSS或者JS。

    顺便我们把index.html主页模板也修改一下,删除原有内容,添加下面的代码:

    {% extends 'base.html' %}
    {% block title %}主页{% endblock %}
    {% block content %}
        <h1>欢迎回来!</h1>
    {% endblock %}
    

    好了,重启服务器,尝试用错误的和正确的用户名及密码登录,看看页面效果吧!下面是错误信息的展示:

     

  • 相关阅读:
    [环境]Java 环境变量
    [BZOJ 4008][HNOI2015]亚瑟王(期望Dp)
    [BZOJ 3295][Cqoi2011]动态逆序对(CDQ分治)
    [BZOJ 3668&UOJ #2][Noi2014]起床困难综合症(贪心)
    [BZOJ 4571][Scoi2016]美味(主席树)
    [BZOJ 4408][Fjoi 2016]神秘数(主席树+思路)
    [BZOJ 2212][Poi2011]Tree Rotations(线段树合并)
    [BZOJ 4592][Shoi2015]脑洞治疗仪(线段树)
    [BZOJ 2054]疯狂的馒头(并查集)
    [BZOJ 1455]罗马游戏(左偏树+并查集)
  • 原文地址:https://www.cnblogs.com/jinyuanliu/p/10527304.html
Copyright © 2011-2022 走看看