2.2 用户登录
我们创建了一个超级管理用户admin,并且可以通过管理员登录页面登录到管理后台,而且在这个后台发布了博客。

通过Users可以向本网站中增加用户,如下图所示就是增加几个用户之后的Users列表

下面首先解决登录问题,如下图所示

2.2.1 创建应用
为了实现用户登录、退出、注册等功能,从而进行用户管理,我们要创建一个新的应用 account。

在./testsite/settings.py中对新应用进行配置。

接着在./testsite/urls.py中进行URL配置。

最后在./account目录中创建urls.py文件,并设置好本应用中的路径。
1 from django.urls import path 2 from .import views 3 4 app_name = 'account' 5 urlpatterns = [ 6 path('login/',views.user_login,name='user_login'), 7 8 ]
views.user_login意味着必须要在views.py中创建一个名为user_login的函数来响应请求。至此,基本配置已完成,下面开始设计用户登录过程。
2.2.2 理解表单类
用户登录框用行话说就是表单。这个表单可以用纯粹前端的HTML代码来编写,也可以用我们下面要介绍的表单类来编写。这两种方式在实践中都可以使用,这里我们首先应用表单类。
在./account目录中创建一个文件forms.py,这个文件是专门存放各种与表单相关的类的,登录表单相关的类的代码如下:
- 1 from django import forms 2 3 class LoginForm(forms.Form): 4 username = forms.CharField() 5 password = forms.CharField(widget=forms.PasswordInput)
为了理解这个表单类,下面在交互模式中进行操作,一步一步地试验。

用from account.forms import LoginForm引入./account/forms.py中建立的表单类LoginForm,然后创建这个类的实例,只不过没有向类中传递任何参数,这个实例可以称为未绑定(数据)实例,然后打印此未绑定实例,结果就是HTML代码。这些HTML代码构成了前端表单,其对话框与我们在./account/forms.py中所写的LoginForm类的属性是对应的。因此,我们可以认为不论是前端的表单,还是后端的表单类,都是对象。这个对象的属性就是表单中的对话框(input)。
下面分析细节部分。
LoginForm中的username = forms.CharField()就是<input name='username' type='text' ...>,而在password=forms.CharField(widget=forms.PasswordInput)中,因为使用了widget=forms.PasswordInput,故其对应<input type="password"...>。在表单类的属性值中,通常使用widget来规定相对应的HTML元素的类型,如本例中规定input的类类型为password。
对象除了属性,还有一些方法。比如表单类的未绑定实例login_form,可以使用Python中已经熟知的自省方法查看其方法。

选择几个后面要用到的关键点来学习一番。

有两个方法,我们将在随后的视图函数中使用,需要在这里熟悉它们。

在创建实例时,如果传递给表单类的数据是正确的,则is_valid()返回True。换个角度说,当前端提交到后端的数据是符合表单类属性要求的,那么is_valid()返回True,这个方法用来检验数据是否合法。

password是不能为空的(参见前面打印出来的HTML,没有用专门的参数声明可以为空,默认都是不能为空的),所以上述数据相对表单类不合法,检验结果是False。

cleaned_data是实例的属性,它以字典形式返回实例的具体数据,即经过检验之后的属性及其值。如果传入的某项数据不合法,则在cleaned_data的结果中不予显示。

上述内容搞清楚了,就可以着手在views.py中编写函数。
2.2.3 登录的视图函数
此处为响应登录的视图函数,比前面的视图函数稍微复杂一些。我们要解决两个问题,一个是响应GET请求,当用户通过浏览器请求某个网址时,显示登录的对话框;另外一个是响应POST请求,用户以POST方式向服务器发送自己的用户名和密码,该视图函数接收到后,判断其是否为本站用户,如果是就许可登录,否则拒绝。如下图是登录的视图函数的执行过程。

根据图中所表达的逻辑过程,编辑./account/views.py文件,实现一个名为user_login()的视图函数。
1 from django.shortcuts import render 2 from django.http import HttpResponse 3 from django.contrib.auth import authenticate,login #① 4 from .forms import LoginForm 5 6 # Create your views here. 7 def user_login(request): #② 8 if request.method == "POST": #③ 9 login_form = LoginForm(request.POST) #④ 10 if login_form.is_valid():#⑤ 11 cd = login_form.cleaned_data #⑥ 12 user = authenticate(username=cd['username'],password=cd['password']) #⑦ 13 if user: 14 login(request,user) #⑧ 15 return HttpResponse("Wellcome You. You have been authenticated successfully") #⑨ 16 else: 17 return HttpResponse("Sorry.Your username or password is not right.") 18 else: 19 return HttpResponse("Invalid login") 20 21 if request.method =="GET": 22 login_form = LoginForm() 23 return render(request,"account/login.html",{"form":login_form})
下面对上述代码的部分内容进行解释:
语句①是从Django默认的(或者说是内置的)用户认证或管理应用中引入的两个方法。Django中内置了一个相当不错的用户认证和管理应用,前面已经通过admin账号登录后台管理端对用户进行管理 ,此功能就是使用了Django内置的用户认证和管理应用。想要了解Django内置了哪些应用,可以打开./testsite/settings.py文件,在INSTALLED_APPS的值中查看,除自己添加的外,其他的都是Django内置的。
语句②命名了一个要处理前端提交的数据的视图函数,并支持前端的显示请求,视图函数必须使用request作为第一个参数。当客户端浏览器通过URL向服务器发送请求时,Django会创建一个HttpRequest对象,request是HttpRequest的替代。在request对象(或者说HttpReques对象)的诸多方法中,常用的有request.method、request.GET和request.POST。
语句③中的request.method就是HttpRequest对象的一个常用属性,它返回HTTP请求类型的字符串,比如:前端向服务器发出的GET请求,那么request.method得到“GET”这个字符串。
1 if request.method =='GET': 2 do_something() 3 if request.method =='POST': 4 do_something_else()
当客户端浏览器向服务器发送GET请求后,request.GET得到一个类字典对象,可以用request.GET.get("name")得到参数name的值。
注意区分GET()和get(),request.GET得到的是包含所提交请求所有参数和值的类字典对象;get()是字典中的一个方法。
语句④中用到了POST。在网络上,常用POST和GET两种方式实现客户端浏览器和服务器之间的数据交互。GET多用于数据查询,而POST多用于数据写入或者更新等(这种划分不是绝对的,根据具体业务要求确定)。在本应用中,前端浏览器向服务器端提交表单内容,用了POST方法。用POST方法提交数据,浏览器的地址栏不会发生变化,这与GET方法不同。在Django中,通过request.POST得到提交的表单数据,也是一个类字典对象。
在Django中,可以给表单类传入字典类型的数据,从而建立一个绑定实例,在语句④中传入的是request.POST返回的类字典数据,依然是建立了一个绑定对象。
语句⑤中的login_form.is_valid()验证所传入的数据是否合法,如果合法就执行后续的语句⑥cd = login_form.cleaned_data,cd引用的是一个字典类型数据,其中以键值对的形式记录了用户名和密码。
语句⑦用到了语句①所引入的authenticate()函数,其作用是检验此用户是否为本网站项目的用户,以及其密码是否正确。如果都对象上号了,就返回User的一个实例对象,否则返回None。
语句⑧中使用了语句①引入的login()函数,以语句⑦所得到的User实例对象作为参数,实现用户登录。用户登录之后,Django会自动调用默认的session应用,将用户ID保存在session中,完成用户登录操作。在通常情况下,login()和authenticate()配合使用。
如果将网页中的HTML看做一个字符串,就可以传给HttpResponse类(这个类在django.http模块内,所以前面要使用from django.http import HttpRespone),从而在客户端返回一个页面,这就是语句⑨的含义,这一点和render不同。
如果客户端对服务器发出的是GET请求,则将login_form = LoginForm()传给指定模板,从而在前端呈现相应的对话框。
编写完登录的视图函数,为了能够在前端显示登录对话框,还要编写相应的前端模板。

2.2.4 登录的前端界面
接下来要编写./templates/account/login.html模板,代码如下:
1 {% extends "base.html" %} 2 3 {% block title %}Login{% endblock %} 4 5 {% block content %} 6 <div class="row text-center vertical-middle-sm"> 7 <h1>Login</h1> 8 <p>Input your username and password</p> 9 <form class="form-horizontal" action="." method="post">{% csrf_token %} #① 10 {{ form.as_p}} #② 11 <input type="submit" value="Login"> 12 </form> 13 </div> 14 {% endblock %}
如果增加了新的文件,一般要重新启动Django服务。在浏览器的地址栏中输入http://127.0.0.1:8000/account/login/,就能看到登录界面了


语句①中的<form>是创建一个前端显示的表单,其中action="."用于显示当前表单的提交地址,这里表示的是当前地址,即调用此模板的视图函数所对应的URL。
语句①中的{% csrf_token %}在表单中是必不可少的,不写在这个位置也行,只要在<form>语句里面即可。所谓CSRF(Cross-Site Request Forgery),中文名称是跨站请求伪造,也被称为one click attack/session riding,简言之就是攻击者盗用了用户的身份,以用户的名义发送恶意请求,导致的不良后果可以想象一下。Django作为一个负责任的框架,内置了一个CSRF中间件,请打开./testsite/settings.py文件,其中有一个参数django.middleware.csrf.CsrfViewMiddleware,有了这个中间件,就能保证Django免受CSRF攻击了。如果前端通过POST方式提交数据,就会被禁止,而POST数据又是必不可少的,解决方法之一就是在表单中使用{% csrf_token %},与表单内容一同被提交。为了验证,不妨将其暂时删除,看看是否会出现如下图所示的结果。

当请求为GET时,视图函数中产生的就是未绑定对象,用这个对象来渲染前端模板,得到一个等待输入的表单,这就是语句②,只不过是使用了实例对象的as_p方法,它使得表单显示为一系列<p>标签,每个表单元素在一对<P>标签内。与之类似的还有as_ul和as_table。查看此时的view-source:http://127.0.0.1:8000/account/login/页面源码

页面导航条中有一个预设的LOGIN,在这里做一个超级链接,指向现在做的登录页面。要编辑./templates/header.html中的部分代码,新修改的部分代码如下:
1 {% load staticfiles %} 2 <div class="container"> 3 <nav class="navbar navbar-default" role="navigation"> 4 <div class="navbar-header"> 5 <a class="navbar-brand" href="http://www.itdiffer.com"><img src="{%static '/images/logo.png'%}" 6 width="100px"></a> 7 </div> 8 <div> 9 <ul class="nav navbar-nav" role="navigation"> 10 <li><a href="{% url 'blog:blog_title' %">BLOG</a> </li> 11 </ul> 12 <ul class="nav navbar-nav navbar-right"> 13 <li><a href="{% url 'account:user_login' %}”>LOGIN</a> </li> #③ 14 </ul> 15 </div> 16 </nav> 17 </div>
而{% url 'account:user_login' %"}这种写法可以满足代码的随意迁移需要。这里的URL是模板中的标签,表示网址,它将引导程序到./testsite/urls.py
和./account/urls.py中“按图索骥”。观察{% url 'account:user_login' %"}的结构,account是本应用的namespace(配置在./testsite/urls.py)
在namespace的名字后面是冒号,冒号后面是URL的name(配置在./account/urls.py中)。
在浏览器访问http://127.0.0.1:8000/blog/,所有导航条的右边同样有LOGIN,如下图是所示。

单击LOGIN,就会跳转到刚刚创建的用户登录页面。
在这里我们使用了{{ form.as_p}}的方式得到了前端模板所要渲染的表单,但这种方式略显死板。比如,要求Username用红色而Password用蓝色字,上面写法就不能实现了。于是修改./templates/account/login.html文件后的代码。
1 {% extends "base.html" %} 2 3 {% load staticfiles %} 4 5 {% block title %}Login{% endblock %} 6 7 {% block content %} 8 <div class="row text-center vertical-middle-sm"> 9 <h1>Login</h1> 10 <p>Input your username and password</p> 11 <form class="form-horizontal" action="." method="post">{% csrf_token %} 12 <!--{{ form.as_p}}--> 13 <div class="form-group"> 14 <label for="{{form.username.id_for_label}}" class="col-md-5 control-label" 15 style="color:red"><span class="glyphicon glyphicon-user"></span>Username</label> 16 <div class="col-md-6 text-left">{{ form.username }}</div> 17 </div> 18 <div class="form-group"> 19 <label for="{{form.password.id_for_label}}" class="col-md-5 control-label" 20 style="color:blue"><span class="glyphicon glyphicon-floppy-open"></span>Password</label> 21 <div class="col-md-6 text-left">{{ form.password }}</div> 22 </div> 23 <input type="submit" class="btn btn-primary btn-lg" value="Login"> 24 </form> 25 </div> 26 {% endblock %}
以上只是更多地使用了Bootstrap的样式。关键是里面的{{ form.username}}和{{ form.password}},用这种方式单独实现了<input>元素。
页面的部分显示效果如下图所示。

2.2.5 知识点
1、内置用户权限管理
利用Django开发网站,可以使用其内置的用户权限管理功能,也可以自己重新编写一个用户管理的应用。在本书中所展示的项目中,都是使用Django默认的用户管理应用。打开./testsite/settings.py文件,会在INSTALLED_APPS的列表中发现django.contrib.auth应用,这是创建本项目默认就有的,她就是Django默认(内置)的用户权限管理应用。这个应用的所有源码都存放在Django本机上的安装目录内,我的计算机中就位于C:\Python35\Lib\site-packages\django\contrib\auth内。
当完成数据迁移后,可以在数据库中看到以auth作为前缀的数据库表,其中auth_user是本节中要关注的,打开它,会看到这个表的基本结构,如下图所示。

完成用户的登录或者2.4节中的注册,就是针对这个表进行操作,它对应着django/contrib/auth/models.py文件中的数据类型模型类User。
用超级管理员身份登录(http://loaclhost:8000/admin),在页面找到对用户进行管理的地方,单击进入,可以查看当前用户,并且能够对用户进行增加、删除、完善信息等操作。这就是Django默认的auth应用中所具有的功能。
2、表单的请求响应过程
Django是如何对响应前端发送表单请求的呢?
- 用户通过自己的浏览器(客户端)第一次向服务器发出含有表单页面的请求,Django会创建一个未绑定数据的表单实例(如form=LoginForm(),form实例就是未绑定实例),饭后反馈到前端页面,等待用户填写内容。
- 用户在客户端填写了表单内容之后,将其提交给服务器,在Django的视图中接收数据(如form = LoginForm(request.POST)),然后验证表单数据(form.is_valid())。
- 通过表单验证之后,可以对表单进行进一步操作,如保存、URL转向等。结束之后,本次表单提交过程完毕。
- 如果没有通过表单验证,就要返回绑定表单实例(携带已经提交的数据和错误信息),让用户修改之后再次提交。

浙公网安备 33010602011771号