《Tango with Django》-第9章 用户身份验证

接下来的三章为您介绍 Django 提供的用户身份验证机制。我们将使用 django.contrib.auth 包中的 auth 应用。根据 Django 文档,这个应用提供了下述概念和功能:
❏ 用户和用户模型
❏ 权限,判断用户可以做什么及不可以做什么的旗标(是/否)
❏ 用户组,把相关权限一次赋予多个用户
❏ 可配置的密码哈希系统,保证数据安全不可或缺
❏ 登录或限制性内容所需的表单和视图
在用户身份验证方面,Django 提供了足够的机制。本章介绍基础知识,让你了解可用的工具和底层概念。

9.1 设置身份验证

在使用 Django 提供的身份验证机制之前,要在项目的 settings.py 文件中添加相关的设置。
在 settings.py 文件中找到 INSTALLED_APPS 列表,检查有没有列出 django.contrib.auth 和django.contrib.contenttypes。INSTALLED_APPS 列表应该类似下面这样:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rango',
]

django.contrib.auth 用于访问 Django 提供的身份验证系统,而 django.contrib.contenttypes 供auth 应用跟踪数据库中的模型。

9.2 密码哈希

Django 的 auth 应用默认存储的是经过 PBKDF2 算法计算过的密码哈希值,可以有效保护用户数据的安全。然而,如果你想进一步控制生成密码哈希值的方式,可以在项目的 settings.py 模块中更换 Django 使用的算法。为此,添加 PASSWORD_HASHERS 元组,例如:

PASSWORD_HASHERS = (
	'django.contrib.auth.hashers.PBKDF2PasswordHasher',
	'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', )

9.3 密码验证器

在 Django 项目的 settings.py 模块中有个字典构成的列表,名为 AUTH_PASSWORD_VALIDATORS。在嵌套的字典中可以清楚地看出,Django 1.9 自带了一些常用的密码验证器,例如针对长度的验证器。每个验证器都有 OPTIONS 字典,以便自定义选项。假如你想确保密码最短为 6 个字符,那么可以把 MinimumLengthValidator 的 min_length 选项设为 6,如下所示:

AUTH_PASSWORD_VALIDATORS = [
	...
	{
    
    
	'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
	'OPTIONS': {
    
     'min_length': 6, }
	},
	...
]

9.4 User 模型

User 对象(django.contrib.auth.models.User)是 Django 身份验证系统的核心,表示与 Django应用交互的每个个体。根据 Django 文档,身份验证系统的很多方面都能用到 User 对象,例如访问限制、注册新用户,以及网站内容与创建者之间的关系。
User 模型有 5 个主要属性:
❏ 用户账户的用户名(username)
❏ 用户账户的密码(password)
❏ 用户的电子邮件地址(email)
❏ 用户的名字(first_name)
❏ 用户的姓(last_name)

9.5 增加用户属性

如果你想导入其它的与用户相关的属性而不仅仅是User模型提供的,那你需要创建一个模型与User模型关联。对于我们的rango应用,我们希望为每个用户帐户导入两个额外的属性。具体来说,我们希望导入:

  • 一个URLField,让rango用户能够指定他们自己的网页;
  • 一个ImageField,让用户为它们的头像指定一个图像。
    这个可以通过在models.py文件里创建一个额外的模型来实现。让我们添加一个新的模型叫UserProfile:
    rango/models.py
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User,on_delete=models.CASCADE)
    website = models.URLField(blank=True)
    picture = models.ImageField(upload_to='profile_images',blank=True)

    def __str__(self):
        return self.user.username

要让UserProfile模型数据能通过管理界面访问,你需要将UserProfile模型导入到rango的admin.py文件中。现在你可以通过如下代码将新模型注册到管理界面了。
admin.py

from rango.models import UserProfile

admin.site.register(UserProfile)
  • 再迁移一次
    记住当你创建了新的模型后,数据库必须要同步更新。运行命令python manage.py makemigration rango创建一个迁移脚本。然后运行命令Python manage.py migrate执行迁移,在数据库中创建相关的表。

9.6 创建用户注册视图和模板

在设计好应用认证功能的结构后,我们现在需要开发模块,实现让应用的用户能够创建用户帐号。因此,我们需要创建一个新的视图、模板和URL映射来处理用户注册。
要实现用户注册功能,我们需要完成下面这几个步骤:

  • 创建UserForm和UserProfileForm;
  • 增加一个视图处理新用户的创建;
  • 创建模板显示UserForm和UserProfileForm;
  • 将创建的视图映射到一个URL上。
  • 最后一步就是将我们的注册功能增加到原来的应用上去:将index主页链接到注册页面。

定义 UserForm 和 UserProfileForm

在rango/forms.py中,让我们先创建两个类并继承forms.ModelForm类,如下代码所示:

from django import forms
from rango.models import Page,Category,UserProfile
from django.contrib.auth.models import User


class UserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput())

    class Meta:
        model = User
        fields = ('username', 'email', 'password')


class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('website','picture')

定义 register() 视图

下一步,我们需要需然表单并处理表单输入数据。在rango的views.py文件中,为新类UserForm和UserProfileForm增加导入语句。做完之后按照如下代码添加register()函数。

扫描二维码关注公众号,回复: 12906496 查看本文章
from rango.forms import UserForm,UserProfileForm

def register(request):
    registered = False
    if request.method == 'POST':
        user_form = UserForm(data=request.POST)
        profile_form = UserProfileForm(data=request.POST)

        if user_form.is_valid() and profile_form.is_valid():
            user = user_form.save()
            user.set_password(user.password)
            user.save()
            profile = profile_form.save(commit=False)
            profile.user = user

            if 'picture' in request.FILES:
                profile.picture = request.FILES['picture']

            profile.save()
            registered = True
        else:
            print(user_form.errors,profile_form.errors)
    else:
        user_form = UserForm()
        profile_form = UserProfileForm()
    
    return render(request,'rango/register.html',{
    
    'user_form':user_form,'profile_form':profile_form,'registered':registered})

创建注册页面的模板

现在我们需要建立一个模板供register视图使用。创建一个新的模板文件rango/register.html,并添加如下代码:

{
    
    % extends 'rango/base.html' %}
{
    
    % load static %}

{
    
    % block title_block %}
    Register
{
    
    % endblock %}

{
    
    % block body_block %}
    <h1>Register for Rango</h1>
    {
    
    % if registered %}
        Rango says: <strong>thank you for registering!</strong>
        <a href="{% url 'index' %}">Return to the homepage.</a><br />
    {
    
    % else %}
        Rango says: <strong>register here!</strong><br />
        <form id="user_form" method="post" action="{% url 'register' %}" enctype="multipart/form-data"></form>
        
        {
    
    % csrf_token %}
        {
    
    {
    
     user_form.as_p }}
        {
    
    {
    
     profile_form.as_p }}

        <input type="submit" name="submit" value="Register" />
        </form>
    {
    
    % endif %}
{
    
    % endblock %}

这里第一个要注意的就是这个模板使用了在视图中定义的registered变量,来表示注册是否成功。注意为了让模板能展示注册表单,registered必须要被设置为False,要不然就直接显示成功注册信息了。
接下来,我们使用了user_form和profile_form的模板函数as_p。这个函数会把表单中的每个元素封装成一段(用

显示)。这样就保证了每个元素显示在每一行。
最后,在元素中,我们导入了属性enctype。这是因为如果用户想上传图片,表单的响应包会包含二进制数据,而且可能比较大。因此响应包有可能被拆分成多个传回到服务器。所以我们需要声明enctype=“multipart/form-data”。要不然,服务器就不会全部接收用户上传的数据。

添加 URL 映射

在创建好新的视图和相关的模板后,我们现在需要为其田间一个URL映射。在rango/urls.py里,修改urlparrerns元组,如下所示:

from django.conf.urls import url
from rango import views

urlpatterns = [
    url(r'^$',views.index,name='index'),
    url(r'^add_category/$',views.add_category,name='add_category'),
    url(r'^category/(?P<category_name_slug>[\w\-]+)/$',views.show_category, name='show_category'),
    url(r'about/$', views.about, name='about'),
    url(r'^category/(?P<category_name_slug>[\w\-]+)/add_page/$', views.add_page, name='add_page'),
    url(r'^register/$',views.register,name='register')
    ]

新增加的模式(注释了)将URL/rango/register指向了register()视图。同时还要注意我们新URL模式里包含了name属性,赋值为register,通过模板里的url标签使用,如{% url ‘register’ %}。

添加链接

最后,我们修改一下base.html文件,添加一行链接用来指向注册的URL。这样每个页面的无序列表里都会产生一个链接指向注册页面。

		<div>
			<ul>
				<li><a href="{% url 'add_category' %}">Add New category</a></li>
				<li><a href="{% url 'about' %}">About</a></li>
				<li><a href="{% url 'index' %}">index</a></li>
				<li><a href="{% url 'register' %}">Register Here</a></li>
			</ul>
		</div>

检验结果

在这里插入图片描述

9.7 实现登录功能

现在注册用户的功能已经完成,我们现在开始为用户提供登录功能。要实现这个功能,我们就需要完成如下的工作:

  • 创建一个登录视图处理用户认证过程
  • 创建登录模板展现登录表单
  • 将登录视图映射到URL
  • 将登录链接插入到index页面

定义登录视图

首先,打开 Rango 应用的 views.py 模块,定义一个新视图,名为 user_login()。这个视图负责处理登录表单提交的数据,以及登入用户。

  • rango/views.py
from django.contrib.auth import authenticate,login
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse

def user_login(request):
    if request.method == 'POST':
        # 获取用户在登录表单中输入的用户名和密码
        # 我们使用的是 request.POST.get('<variable>')
        # 而不是 request.POST['<variable>']
        # 这是因为对应的值不存在时,前者返回 None,
        # 而后者抛出 KeyError 异常
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 使用 Django 提供的函数检查 username/password 是否有效
        # 如果有效,返回一个 User 对象
        user = authenticate(username=username,password=password)

        # 如果得到了 User 对象,说明用户输入的凭据是对的
        # 如果是 None(Python 表示没有值的方式),说明没找到与凭据匹配的用户
        if user:
            if user.is_active:
                # 登入有效且已激活的账户
                # 然后重定向到首页
                login(request,user)
                return HttpResponseRedirect(reverse('index'))
            else:
                return HttpResponse("Your Rango account is disabled.")
        else:
            print("Invalid login details:{0},{1}".format(username,password))
            return HttpResponse("Invalid login details supplied.")

    # 不是 HTTP POST 请求,显示登录表单
    # 极有可能是 HTTP GET 请求
    else:
        # 没什么上下文变量要传给模板系统
        # 因此传入一个空字典
        return render(request,'rango/login.html',{
    
    })

创建登录页面的模板

有了视图之后,我们还要创建一个模板,让用户输入登录凭据。现在你应该知道要把模板放在templates/rango/ 目录中。在模板login.html中写入下述代码:

  • templates/rango/login.html
{
    
    % extends 'rango/base.html' %}
{
    
    % load static %}

{
    
    % block title_block %}
    Login
{
    
    % endblock %}

{
    
    % block body_block %}
<h1>Login to Rango</h1>
<form id='login_form' method='post' action="{% url 'login' %}">
    {
    
    % csrf_token %}
    Username:<input type="text" name="username" value="" size="50"/>
    <br/>
    Password:<input type="password" name="password" value="" sieze="50"/>
    <br/>
    <input type="submit" value="submit" />
</form>

{
    
    % endblock %}

添加 URL 映射

接下来,需要对user_login()视图映射URL上。修改urls.py文件里的urlpatterns元组如下。

url(r'^login/$', views.user_login, name='login'),

添加链接

最后一步是为Rango用户提供一个方便的链接,以访问登录页面。为此,我们将编辑目录templates/rango/的base.html模板。将以下链接添加到您的列表。

<ul>
...
<li><a href="{% url 'login' %}">Login</a><li>
</ul>

我们将编辑目录templates/rango/的index.html模板。hey there partner!替代为以下

        <div>
            {
    
    % if user.is_authenticated %}
                <h1>Rango says... hello {
    
    {
    
     user.username }}!</h1>
            {
    
    % else %}
                <h1>Rango says... hello world!</h1>
            {
    
    % endif %}
            <br />
       </div>

检验结果

启动服务器,尝试登陆。
在这里插入图片描述

9.8 限制访问

现在用户可以登录到Rango,现在我们可以根据规范限制对应用程序的特定部分的访问,即只有注册用户可以添加类别和页面。使用Django,有几种方法,我们可以实现这一目标。

  • 在模板中,我们可以使用{% if user.authenticated %}模板标记来修改页面的呈现方式(如图所示)。
  • 在视图中,我们可以直接检查request对象并检查用户是否通过身份验证。
  • 或者,我们可以使用Django提供的装饰器函数@login_required来检查用户是否通过身份验证。
    第二个是通过user.is_authenticated()方法查看用户是否登录。user对象是通过request对象传递给视图的.下面是简单的例子。
def some_view(request):
    if not request.user.is_authenticated():
        return HttpResponse("You are logged in.")
    else:
        return HttpResponse("You are not logged in.")

第三个是使用了python装饰器.装饰器是由同名的软件设计模式命名的。它们可以动态的修改一个函数,方法或者类而不用去直接修改它们的源代码。
Django提供了叫做login_required()的装饰器,它可以在视图里要求用户进行登录.如果一个用户没有登录并且尝试访问一个视图,那么这个用户将会重定向到你设定的页面,通常是一个登录页面。

使用装饰器限制访问

在rango应用的views.py文件里新增一个视图retricted(),代码如下:

from django.contrib.auth.decorators import login_required

@login_required
def restricted(request):
    return HttpResponse("Since you're logged in, you can see this text!")

在Rango应用的url.py模块添加一个URL映射:

url(r'^restricted/',views.restricted,name='restricted'),

我们同样需要处理用户未登录时的restricted()视图.我们怎么做呢?最简单的就是重定向用户的浏览器,Django允许我们自定义项目里的settings.py文件,在settings.py文件里设置LOGIN_URL变量为我们希望跳转的URL,例如登录页位于/rango/login/。这样设定后,login_required()装饰器将把未登录的用户重新定向到/rango/login/URL。

LOGIN_URL = '/rango/login/'

若没有必要向未登录的用户显示退出链接,则修改Rango应用的base.html模板,通过模板上下文的user对象判断要显示哪些链接。

			<ul>
			{
    
    % if user.is_authenticated %}
				<li><a href="{% url 'restricted' %}">Restricted Page</a></li>
				<li><a href="{% url 'logout' %}">Logout</a></li>
			{
    
    % else %}
				<li><a href="{% url 'login' %}">login</a></li>
				<li><a href="{% url 'register' %}">Register Here</a></li>
			{
    
    % endif %}
				<li><a href="{% url 'add_category' %}">Add New category</a></li>
				<li><a href="{% url 'about' %}">About</a></li>
				<li><a href="{% url 'index' %}">index</a></li>
			</ul>

9.9 退出

Django的logout()函数将会确保用户能安全的退出。在rango/views.py中加入user_logout()视图:

from django.contrib.auth import logout
# 只有已登录的用户才能访问这个视图
@login_required
def user_logout(request):
    logout(request)
    return HttpResponseRedirect(reverse('index'))

修改Rango应用的url.py模块,把URL/rango/logout/映射到user_logout()视图上:

url(r'^logout/$', views.user_logout, name='logout'),

9.10 扩展功能

在这一章中,我们讲解了django中几个重要的用户管理认证的模块。我们还讲解了安装django中django.contrib.auth应用到我们项目的基础。除此之外,我们还演示了在django.contrib.models.User模型的基础上如何实现一个添加额外项的用户模型。同时还详细描述了如何实现用户注册、登录、退出和控制访问等功能。要获取更多关于用户认证和注册的信息请咨询django官方文档。
但是许多web应用还会进一步加强用户认证功能。比如说,注册用户时,你需要定义不同的安全等级,还要确保提供一个合法的email地址。虽然我们可以实现这个功能,但是当这个功能已经存在的时候,我们为什么还要重复发明轮子呢?django-registeration-redux应用已经开发了相当简单的功能用来实现与用户认证相关的其它有用的功能。在十一章,我们将会讲解如何使用这个包。

练习

在这里插入图片描述

  • 改进Rango应用,只让已登录的用户添加分类和页面,而未登录的用户只能查看分类和网页。还要确保只有已登录的用户才能看到“Add a Page”链接。
    *变更模板base.html
			<ul>
			{
    
    % if user.is_authenticated %}
				<li><a href="{% url 'restricted' %}">Restricted Page</a></li>
				<li><a href="{% url 'logout' %}">Logout</a></li>
				<li><a href="{% url 'add_category' %}">Add New category</a></li>
			{
    
    % else %}
				<li><a href="{% url 'login' %}">login</a></li>
				<li><a href="{% url 'register' %}">Register Here</a></li>
			{
    
    % endif %}
				<li><a href="{% url 'about' %}">About</a></li>
				<li><a href="{% url 'index' %}">index</a></li>
			</ul>

*这是未登录的界面。
在这里插入图片描述
*这是已登录的界面。
在这里插入图片描述

  • 用户输入错误的用户名或密码时显示有用的错误信息。
    *变更模板rango/views.py中的user_login视图
def user_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(username=username,password=password)
        if user:
            if user.is_active:
                login(request,user)
                return HttpResponseRedirect(reverse('index'))
            else:
                return HttpResponse("Your Rango account is disabled.")
        else:
            print("Invalid login details:{0},{1}".format(username,password))
            #return HttpResponse("Invalid login details supplied.")
            return HttpResponse("Your username or password is wrong,pls try again.")
    else:
        return render(request,'rango/login.html',{
    
    })

*这是登录信息错误时的界面。
在这里插入图片描述

  • 使用模板重构限制访问内容那个页面。把模板命名为restricted.html。这个模板同样继承Rango应用的base.html模板。

小结:

  • 错误:django2.x报错No module named ‘django.core.urlresolvers’。原因就是:django2.0 把原来的 django.core.urlresolvers 包 更改为了 django.urls包,所以我们需要把导入的包都修改一下就可以了。

猜你喜欢

转载自blog.csdn.net/m0_46629123/article/details/112638498
今日推荐