美文网首页
(10) Django - Auth认证系统

(10) Django - Auth认证系统

作者: libdream | 来源:发表于2019-03-12 15:20 被阅读0次

    Django除了有强大的Admin管理系统,还提供了完善的用户管理系统。整个用户管理系统可分为三大部分:用户信息、用户权限和用户组,在数据库中分别对应数据表auth_user、auth_permission和auth_group。

    内置USER用户管理

    本节将使用内置的用户管理系统实现用户的注册、登录、修改密码和注销功能。以mysite为例,在项目中创建新的App,命名为user,并在项目的settings.py和urls.py中配置App的信息。

    E:\mysite>python manage.py startapp user

    #settings.py
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'index',
        'user',
    ]
    
    #urls.py
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include('index.urls')),
        path('user/', include('user.urls'))
    ]
    

    完成user的基本配置后,在App中分别添加urls.py和user.html文件。添加后的目录结构如图:


    image.png

    还要在settings.py中启用该模板文件。

    'DIRS': [os.path.join(BASE_DIR, 'user/templates'),
                   ],
    

    App的urls.py设定

    在user下的urls.py中设定4个不同的URL地址,分别代表用户登录、注册、修改密码和用户注销:

    #user的urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('login.html', views.loginView, name='login'),
        path('register.html', views.registerView, name='register'),
        path('setpassword.html', views.setpasswordView, name='setpassword'),
        path('logout.html', views.logoutView, name='logout'),
    ]
    

    上述URL地址分别对应试图函数loginView、registerView、setpasswordView和logoutView。在编写视图函数之前,首先了解一下user.html模板的代码结构:

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
        <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
    </head>
    <body>
    <div class="flex-center">
        <div class="container">
        <div class="flex-center">
        <div class="unit-1-2 unit-1-on-mobile">
            <h1>{{ headers }}</h1>
                {% if tips %}
            <div>{{ tips }}</div>
                {% endif %}
            <form class="form" action="" method="post">
                {% csrf_token %}
                <div>用户名:<input type="text" name='username'></div>
                <div>密 码:<input type="password" name='password'></div>
                {% if new_password %}
                    <div>新密码:<input type="password" name='new_password'></div>
                {% endif %}
                <button type="submit" class="btn btn-primary btn-block">确定</button>
            </form>
            <div class="flex-left top-gap text-small">
                <div class="unit-2-3">
                    <a href="{{ unit_2 }}">{{ unit_2_name }}</a>
                </div>
                <div class="unit-1-3 flex-right">
                    <a href="{{ unit_1 }}">{{ unit_1_name }}</a>
                </div>
            </div>
        </div>
        </div>
        </div>
    </div>
    </body>
    </html>
    

    一个模板分别用于实现用户登录、注册和修改密码,该模板是由两个文本输入框和一个按钮所组成的表单,在该表单下分别设置不同链接,分别指向另外两个URL地址。


    image.png

    用户登录

    接下来,在views.py中实现用户登录功能,视图函数loginView的代码如下:

    #user 的 views.py
    from django.shortcuts import render, redirect
    from django.contrib.auth.models import User
    from django.contrib.auth import login, logout, authenticate
    
    def loginView(request):
        #设置标题和两外两个URL链接
        title = '登录'
        headers = '用户登录'
        unit_2 = '/user/register.html'
        unit_2_name = '立即注册'
        unit_1 = '/user/setpassword.html'
        unit_1_name = '修改密码'
    
        if request.method == 'POST':
            #用户提交登录,获取登录的用户名和密码
            username = request.POST.get('username', '')
            password = request.POST.get('password', '')
            #首先查找User数据表中有没有该用户,没有提示注册
            if User.objects.filter(username=username):
                #如果有该用户,则检查用户名和密码是否正确,错误提示重新输入
                user = authenticate(username=username, password=password)
                if user:
                    if user.is_active:##判断用户是否被激活,是则由内置函数login完成登录,跳转到主页
                        login(request, user)
                    return redirect('/')
                else:
                    tips = '账号密码错误,请重新输入'
            else:
                tips = '用户不存在,请注册'
        return render(request, 'user.html', locals())
    
    

    以上代码就实现了用户的登录,在整个登录过程中,我们并没有对模型User进行定义,而函数中使用的模型User来自于Django的内置模型,在数据库中对应的数据表为auth_user。该表有以下字段:

    字段 说明
    id int类型,数据表主键
    password varchar类型,用户密码
    last_lgoin datetime类型,最近一次登录时间
    is_superuser tinyint类型,是否为超级用户
    username varchar类型,用户账号
    first_name varchar类型,用户的名字
    last_name varchar类型,用户的姓氏
    email 电子邮件
    is_staff 判断用户是否可以可以登录Admin系统
    is_active tinyint类型,判断该用户的状态是否被激活
    date_joined datetime类型,账号的创建时间

    登陆成功后,页面会自动跳转到index的首页,这里将首页的模板稍做修改,添加以下代码:

    #index.html
    <div style="float:right;width: 100px"> 退出登录</div>
    <div style="float:right; width: 150px" > 用户名:{{ username }}</div>
    

    然后在index的views.py的视图函数index中传递变量username给模板。

    #index 下的 views.py
    def index(request):
        username = request.user.username
        #以下代码省略
        #...
    
    这样我们登陆以后就可以看到登陆的用户名。 image.png

    用户注册

    接下来,完成用户注册功能,在user的views.py中编写视图函数registerView,代码如下:

    #user的views.py
    def registerView(request):
        #设置标题和两外两个URL链接
        title = '注册'
        headers = '用户注册'
        unit_2 = 'login.html'
        unit_2_name = '立即登录'
        unit_1 = 'setpassword.html'
        unit_1_name = '修改密码'
    
        if request.method == 'POST':
            #获取用户注册的用户名和密码
            username = request.POST.get('username', '')
            password = request.POST.get('password', '')
            #首先查找User数据表中有没有该用户,有则提示用户已存在
            if User.objects.filter(username=username):
                tips = '用户已存在'
            else:
                #创建新用户
                user = User.objects.create_user(username=username, password=password)
                user.save()
                tips = '注册成功,请登录'
        return render(request, 'user.html', locals())
    

    用户注册和用户登录的流程大致相同,这里需要注意一个问题:
    用户登录或者注册的url链接是 '/user/register.html',user前面的 ’/‘ 符号一定不能缺。

    修改密码

    最后在views.py中编写函数setpasswordView,实现修改密码的功能。

    #user 的view.py 
    def setpasswordView(request):
        #设置标题和两外两个URL链接
        title = '修改密码'
        headers = '密码修改'
        unit_2 = '/user/login.html'
        unit_2_name = '立即登录'
        unit_1 = '/user/register.html'
        unit_1_name = '立即注册'
        new_password = True#控制模板中新密码的文本框是否出现
        if request.method == 'POST':
            username = request.POST.get('username', '')
            old_password = request.POST.get('password', '')
            new_password = request.POST.get('new_password', '')
            if User.objects.filter(username=username):#如果查询到用户表中有该用户,则进行下一步,否则提示用户不存在
                user = authenticate(username=username, password=old_password)#验证用户和密码是否一致
                if user:
                    user.set_password(new_password)#通过验证则通过set_password内置函数设置新密码
                    user.save()
                    tips = '密码修改成功'
                else:
                    tips = '用户名和原密码不一致,请重新输入'
            else:
                tips = '用户不存在'
        return render(request, 'user.html', locals())
    
    密码修改界面比注册和登录界面多出一个文本输入框,该输入框由模板变量new_password控制显示。 image.png

    上面的密码修改是通过内置函数set_password实现的,而函数set_password是在内置函数make_password的基础上进行封装而来的。内置函数make_password主要是用来实现对用户密码的加密功能,并且该函数可以脱离Auth认证系统单独使用,比如对某些特殊数据进行加密处理等。上述例子中,使用函数make_password实现修改密码的代码部分如下:

    from django.contrib.auth.hashers import make_password
    #密码加密处理并保存到数据库
    dj_pw = make_password(new_password, None, 'pbkdf2_sha256')#加密处理
    user.password = dj_pw
    user.save()
    

    除了内置函数make_password,还有内置函数check_password,该函数是对加密前的密码与加密后的密码进行验证匹配。判断两者是否为同一密码。在django的shell模式下使用该函数:

    >>> from django.contrib.auth.hashers import make_password,check_password
    >>> pw = '123456'
    >>> dj_pw = make_password(pw, None, 'pbkdf2_sha256')
    >>> dj_pw
    'pbkdf2_sha256$120000$0ATplZvxaaVN$OIH6gdfMpaYheNtci5iigfod/YIIy4jdkI4UTp60WJM='
    >>> check_password(pw, dj_pw)
    True
    

    用户注销

    用户注销是用户管理系统较为简单的功能,调用内置函数Logout即可实现。代码如下:

    #user 的 views.py
    def logoutView(request):
        logout(request)#退出登录
        return redirect('/user/login.html')#跳转到登录页面
    

    实现发送邮件找回密码

    上面的密码修改是在用户知道密码的情况下实现的,而在日常应用中,还有一种是用户忘记密码的情况下实现密码修改,也叫密码找回。密码找回首先需要对用户账号进行验证,确认该账号是当前用户所拥有的,验证成功后才能给用户重置密码。本节使用Django内置的邮件功能实现邮箱验证,从而实现密码找回功能。
    在实现邮件发送功能之前,需要对邮箱进行相关设置,开启POP3/SMTP服务。以网易163邮箱为例,开通POP3服务时会让你输入一个授权码,这个授权码在后面的开发中需要使用。


    image.png
    image.png

    接下来需要在settings.py中添加邮箱的相关配置

    #mysite 的 settings.py
    #邮箱配置信息
    #设置django与邮件服务器的连接方式为SSL
    EMAIL_USE_SSL = True
    #邮件服务器
    EMAIL_HOST = 'smtp.163.com'
    #邮件服务器端口,若使用SMTP服务器,端口应为465或587
    EMAIL_PORT = 465
    #发送邮件的账号
    EMAIL_HOST_USER = 'wjxlib@163.com'
    #SMTP服务密码,客户端的授权密码
    EMAIL_HOST_PASSWORD = 'fe*********'
    #设置默认发送邮件的账号
    DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
    

    完成邮箱相关配置后,先到user的urls.py中设置密码找回的URL地址信息,添加以下路径

    #user 的 urls.py
    path('findpassword.html', views.findpasswordView, name='findpassword'),
    

    添加好URL地址信息后,我们到视图模块中编写视图函数findpasswordView,代码如下:

    #user 的 views.py
    def findpasswordView(request):
        button = '获取验证码'
        new_password = False
        if request.method == 'POST':
            username = request.POST.get('username','')
            verificationCode = request.POST.get('verificationCode', '')
            password = request.POST.get('password','')
            #验证用户是否存在
            user = User.objects.filter(username=username)
            #用户不存在
            if not user:
                tips = '用户 ' + username + ' 不存在'
            else:
                #用户存在,则判断验证码是否发送
                #如果session中没有验证码信息,则发送验证码并将验证码写入session
                if not request.session.get('verificationCode', ''):
                    button = '重置密码'
                    tips = '验证码已发送'
                    new_password = True#展示新密码文本输入框
                    verificationCode = str(random.randint(1000,9999))#生成随机4位数验证码
                    request.session['verificationCode'] = verificationCode#将验证码写入session
                    user[0].email_user('找回密码', verificationCode)#通过内置函数email_user向用户发送验证码邮件
                #匹配输入的验证码是否正确
                elif verificationCode == request.session.get('verificationCode'):
                    #密码加密处理并保存到数据库
                    dj_pw = make_password(password, None, 'pbkdf2_sha256')
                    user[0].password = dj_pw
                    user[0].save()
                    del request.session['verificationCode']#删除session中的验证码信息
                    tips = '密码已重置'
                else:
                    tips = '验证码错误,请重新获取'
                    new_password = False
                    del request.session['verificationCode']
        return render(request, 'user1.html', locals())
    

    这里用一个新的模板user1.html,与上面的user.html稍有不同。

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>找回密码</title>
        <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
    </head>
    <body>
    <div class="flex-center">
        <div class="container">
        <div class="flex-center">
        <div class="unit-1-2 unit-1-on-mobile">
            <h1>密码找回</h1>
                {% if tips %}
            <div>{{ tips }}</div>
                {% endif %}
            <form class="form" action="{% url 'findpassword' %}" method="post">
                {% csrf_token %}
                <div>用户名:<input type="text" name='username' value="{{ username }}"></div>
                <div>验证码:<input type="text" name='verificationCode'></div>
                {% if new_password %}
                    <div>新密码:<input type="password" name='password'></div>
                {% endif %}
                <button type="submit" class="btn btn-primary btn-block">{{ button }}</button>
            </form>
        </div>
        </div>
        </div>
    </div>
    </body>
    </html>
    

    输入http://127.0.0.1:8000/user/findpassword.html进入密码找回界面

    image.png 。
    如果不输入用户名或者输入不存在的用户名则会提示用户不存在。
    image.png
    我们输入用户名user来尝试以下,
    image.png
    可以看到,网页显示验证码已发送,但是在这之前,需要在数据表中插入用户user的邮箱信息,否则邮件是无法发送出去的。
    image.png
    打开邮箱可以看到验证码已经发送成功,下面就是输入验证码更改密码。
    image.png
    输入完以后点击重置密码,可以看到密码已重置
    image.png
    Django除了内置函数email_user实现邮件发送之外,还另外提供多种邮件发送方法,我们在shell模式下讲解:
    使用 send_mail 实现邮件发送
    >>> from django.core.mail import send_mail
    >>> from django.conf import settings
    #获取settings.py的配置信息
    >>> from_email = settings.DEFAULT_FROM_EMAIL
    #发送邮件,接收邮件以列表表示,说明可设置多个接收对象
    >>> send_mail('邮件头', '邮件主题', from_email, ['30317691@qq.com'])
    1
    
    使用send_mass_mail实现多封邮件同时发送
    >>> from django.core.mail import send_mass_mail
    >>> from django.conf import settings
    >>> from_email = settings.DEFAULT_FROM_EMAIL
    >>> message1 = ('邮件头1','邮件主题1', from_email, ['30317691@qq.com'])
    >>> message2 = ('邮件头2','邮件主题2', from_email, ['30317691@qq.com'])
    #多封邮件信息以元组形式封装发送
    >>> send_mass_mail((message1,message2), fail_silently=False)
    2
    
    image.png
    使用EmailMultiAlternatives 实现邮件发送
    >>> from django.core.mail import EmailMultiAlternatives
    >>> from django.conf import settings
    >>> from_email = settings.DEFAULT_FROM_EMAIL
    >>> content = '<p>这是一封<strong>很重要的</strong>邮件.</p>'
    >>> msg = EmailMultiAlternatives('邮件头', content, from_email, ['30317691@qq.com'])
    #将正文设置为HTML格式
    >>> msg.content_subtype = 'html'
    #可选,对正文内容进行补充和添加
    >>> msg.attach_alternative('<strong>是的很重要...</strong>', 'text/html')
    #可选,添加附件
    >>> msg.attach_file('E://test.txt')
    #发送
    >>> msg.send()
    1
    
    image.png

    扩展User模型

    在开发过程中,模型User的字段可能满足不了复杂的开发需求。现在大多数网站的用户信息都有用户的手机号码、QQ号码和微信号等一系列个人信息。为了满足各种需求,Django还提供了4种模型扩展方法。一般情况下,建议使用AbstractUser扩展模型User,因为该方式对原有模型User影响较少而且无须额外创建数据表。
    下面以mysite项目为例讲解如何使用AbstractUser扩展模型User。
    首先在MySQL中找到项目所使用的数据库,并清除该数据库中全部的数据表,在user的models.py文件中定义模型MyUser,代码如下:

    #user 的 models.py
    from django.db import models
    from django.contrib.auth.models import AbstractUser
    class MyUser(AbstractUser):
        qq = models.CharField('QQ号码', max_length=16)
        weChat = models.CharField('微信账号', max_length=100)
        mobile = models.CharField('手机号码', max_length=11)
        # 设置返回值
        def __str__(self):
            return self.username
    

    模型MyUser继承自AbstractUser类,AbstractUser类已有内置模型User的字段属性,因此模型MyUser具有模型User的全部属性。在执行数据迁移(创建数据表)之前,必须要在项目的settings.py中配置相关信息。

    #settings.py
    AUTH_USER_MODEL = 'user.MyUser'
    

    配置信息是将内置模型User替换成user定义的模型MyUser,若没有设置配置信息,在创建数据表的时候,会分别创建数据表auth_user 和 user_myuser。
    在项目命令行下执行数据迁移:

    python manage.py makemigrations
    python manage.py migrate
    完成数据迁移后,打开数据库查看数据表信息,可以发现内置模型User的数据表auth_user改为数据表user_myuser,并且数据表user_myuser的字段除了具有内置模型User的字段,还额外增加了自定义的字段,如图所示:

    image.png
    接着使用python manage.py createsuperuser创建超级用户并登录Admin后台管理系统。
    image.png
    我们会发现认证与授权没有用户信息表,这是因为模型MyUser是在user的models.py中定义的。若将模型MyUser展示在后台系统,则要在user的admin.py中定义相关的数据对象:
    #admin.py
    from django.contrib import admin
    from .models import MyUser
    from django.contrib.auth.admin import UserAdmin
    from django.utils.translation import gettext_lazy as gl
    
    @admin.register(MyUser)
    class MyUserAdmin(UserAdmin):
        list_display = ['username', 'email', 'mobile', 'qq', 'weChat']
        #修改用户时,在个人信息里添加 'mobile 、'qq'、'weChat'的信息录入
        #将源码的UserAdmin.fieldsets转换成列表格式
        fieldsets = list(UserAdmin.fieldsets)
        #重写UserAdmin的fieldsets,添加'mobile'、'qq'、'weChat'的信息录入
        fieldsets[1] = (gl('Personal info'), {'fields':('first_name','last_name','email','mobile','qq','weChat')})
    
    #__init__.py
    #设置App(user)的中文名
    from django.apps import AppConfig
    import os
    # 修改app在admin后台显示名称
    # default_app_config的值来自apps.py的类名
    default_app_config = 'user.IndexConfig'
    
    # 获取当前app的命名
    def get_current_app_name(_file):
        return os.path.split(os.path.dirname(_file))[-1]
    
    # 重写类IndexConfig
    class IndexConfig(AppConfig):
        name = get_current_app_name(__file__)
        verbose_name = '用户管理'
    

    重启mysite项目并进入Admin后台,可以在界面看到模型MyUser生成的用户信息表。

    image.png
    进入用户信息表,并修改某个用户信息时,发现用户信息的修改界面出现用户的手机号码、QQ号码和微信号码的文本输入框,这是由MyUserAdmin类中重写属性fieldsets实现的。
    image.png
    上述admin.py定义的MyUserAdmin继承自UserAdmin,UserAdmin是内置模型User的Admin数据对象,因此,在定义MyUserAdmin时,直接继承UserAdmin,并通过重写某些属性,可以快速开发扩展模型MyUser的Admin后台数据对象。
    除了继承UserAdmin的Admin数据对象之外,还可以在表单中继承内置模型User所定义的表单类。内置的表单类forms.py说明如下:
    表单类 表单字段 说明
    UserCreationForm username,password1,password2 创建新的用户信息
    UserChangeForm password, 模型User全部字段 修改已有的用户信息
    AuthenticationForm username,password 用户登录时所触发的验证功能
    PasswordResetForm email 将重置密码通过发送邮件方式实现密码找回
    SetPasswordForm password1,password2 修改或新增用户密码,设置密码时,无须对旧密码进行验证
    PasswordChangeForm old_password,newpassword1,new_password2 继承SetPasswordForm,修改密码前需要对旧密码进行验证
    AdminPasswordChangeForm password1,password2 用于Admin后台修改用户密码

    上述内置的表单累都涉及模型User的字段,说明这些表单是在内置模型User的基础上实现的。因此,我们为扩展模型MyUser定义相关的表单累可以继承上述的表单类。以UserCreationForm为例,使用表单类UserCreationForm实现用户注册功能。在user中创建form.py文件:

    #user 的 form.py
    from django.contrib.auth.forms import UserCreationForm
    from .models import MyUser
    
    class MyUserCreationForm(UserCreationForm):
        class Meta(UserCreationForm.Meta):
            model = MyUser
            # 在注册界面添加邮箱、手机号码、微信号码和QQ号码
            fields = UserCreationForm.Meta.fields + ('email', 'mobile', 'weChat', 'qq')
    

    然后在模板user.html和视图函数registerView中编写以下代码:

    #模板user.html
    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>用户注册</title>
        <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
    </head>
    <body>
    <div class="flex-center">
        <div class="container">
        <div class="flex-center">
        <div class="unit-1-2 unit-1-on-mobile">
            <h1>MyDjango Auth</h1>
                {% if tips %}
            <div>{{ tips }}</div>
                {% endif %}
            <form class="form" action="" method="post">
                {% csrf_token %}
                <div>用户名:{{ user.username }}</div>
                <div>邮  箱:{{ user.email }}</div>
                <div>手机号:{{ user.mobile }}</div>
                <div>Q Q 号:{{ user.qq }}</div>
                <div>微信号:{{ user.weChat }}</div>
                <div>密  码:{{ user.password1 }}</div>
                <div>密码确认:{{ user.password2 }}</div>
                <button type="submit" class="btn btn-primary btn-block">注 册</button>
            </form>
        </div>
        </div>
        </div>
    </div>
    </body>
    </html>
    
    #user 的 views.py
    from django.shortcuts import render
    from .form import MyUserCreationForm
    
    # 使用表单实现用户注册
    def registerView(request):
        if request.method == 'POST':
            user = MyUserCreationForm(request.POST)
            if user.is_valid():
                user.save()
                tips = '注册成功'
                user = MyUserCreationForm()
        else:
            user = MyUserCreationForm()
        return render(request, 'user.html',locals())
    
    

    然后在浏览器上访问http://127.0.0.1:8000/user/register.html,就实现了用户注册的功能。

    image.png

    用户权限

    用户权限主要是对不同的用户设置不同的功能使用权限,而每个功能主要以模型来划分。数据库中的数据表auth_permission的每条数据信息就代表项目中某个模型的某个权限:


    image.png

    设置用户权限实质上是对数据表user_myuser和auth_permission之间的数据设置多对多关系。


    项目的数据表

    从整个项目的数据表可以看到,用户、用户权限和用户组分别对应数据表user_myuser、auth_permission和auth_group.无论是设置用户权限、设置用户所属用户组还是设置用户组的权限,其实质都是对两个数据表之间的数据建立多对多的数据关系:

    • 数据表user_myuser_user_permissions:管理数据表user_myuser和auth_permission之间的多对多关系,实现用户权限设置
    • 数据表user_myuser_groups:管理数据表user_myuser和auth_group之间的多对多关系,实现在用户组设置用户
    • 数据表auth_group_permissions:管理数据表auth_group和auth_permission之间的多对多关系,实现用户组设置权限。

    需要注意的是:用户权限的设置只适用于非超级用户。我们在shell模式下简单介绍下用户权限设置。

    #导入模型MyUser
    >>> from user.models import MyUser
    #查询用户信息,filter查询返回列表格式,因此设置列表索引获取User对象
    >>> user = MyUser.objects.filter(username='user')[0]
    #判断当前用户是否具有权限add_product
    #index.add_product是固定写法,index为项目的App名, add_product是数据表auth_permission的字段codename
    >>> user.has_perm('index.add_product')
    False
    #导入模型Permission
    >>> from django.contrib.auth.models import Permission
    #在权限管理表获取权限add_product的数据对象permission
    >>> permission = Permission.objects.filter(codename='add_product')[0]
    #对当前用户对象user 设置权限add_product
    >>> user.user_permissions.add(permission)
    #重新查询用户对象,判断用户权限
    >>> user = MyUser.objects.filter(username='user')[0]
    >>> user.has_perm('index.add_product')
    True
    
    上述代码为用户新增了一条权限,打开数据表user_myuser_user_permissions可以看到新增了一条数据。 image.png

    myuser_id和permission_id分别是数据表user_myuser和auth_permission的主键。
    除了添加权限外,还可以对用户的权限进行删除和查询。

    >>> user = MyUser.objects.filter(username='user')[0]
    >>> permission = Permission.objects.filter(codename='add_product')[0]
    #删除某条权限
    >>> user.user_permissions.remove(permission)
    #重新查询用户对象,判断权限是否被删除
    >>> user = MyUser.objects.filter(username='user')[0]
    >>> user.has_perm('index.add_product')
    False
    #清空当前用户全部权限
    >>> user.user_permissions.clear()
    #将上述删除的权限添加到数据表中再查询当前权限
    >>> user.user_permissions.add(permission)
    >>> user.user_permissions.values()
    <QuerySet [{'id': 21, 'name': 'Can add product', 'content_type_id': 6, 'codename': 'add_product'}]>
    >>>
    

    自定义用户权限

    一般情况下,每个模型默认有增(add)、改(change)、删(delete)权限,但实际开发中,可能要对某个模型设置特殊的权限,比如设置访问权限,这时候我们就需要在定义模型的时候,在模型的Meta中设置自定义权限。
    对index的模型Product重新定义:

    #index 的 models.py
    class Product(models.Model):
        id = models.AutoField('序号', primary_key=True)
        name = models.CharField('名称',max_length=50)
        weight = models.CharField('重量',max_length=20)
        size = models.CharField('尺寸',max_length=20)
        type = models.ForeignKey(Type, on_delete=models.CASCADE,verbose_name='产品类型')
        # 设置返回值
        def __str__(self):
            return self.name
    
        class Meta:
            #自定义权限
            permissions = (
                ('visit_product', 'Can visit Product'),
            )
    

    定义模型Product时,通过重写父类models.Model的permissions属性可实现自定义用户权限。该属性以元组或列表的数据格式表示,每个元素代表一个权限,也以元组或列表表示。一个权限中含有两个元素,分别是数据表auth_permission的codename和name字段。

    在数据库中清除原有的数据表,重新执行数据迁移,执行完成后,打开数据表auth_permission,可以找到自定义权限visit_Product。 image.png

    设置网页的访问权限

    上面定义了自定义用户权限,下面介绍如何在实际开发中使用用户权限。确保数据表auth_permission已自定义权限visit_Product,数据表user_myuser分别创建了一个超级用户和一个普通用户。
    首先在user 的urls.py定义URL信息

    #user 的urls.py
    from django.urls import path
    from . import views
    urlpatterns = [
        #用户登录
        path('login.html', views.loginView, name='login'),
        #用户注册
        path('register.html', views.registerView, name='register'),
        #退出登录
        path('logout.html', views.logoutView, name='logout')
        
    ]
    

    然后编写对应的视图函数

    from django.shortcuts import render,redirect
    from .models import MyUser
    from django.contrib.auth.models import Permission
    from django.contrib.auth import login, authenticate, logout
    
    def loginView(request):
        tips = '请登录'
        title = '用户登录'
        if request.method == 'POST':
            username = request.POST.get('username', '')
            password = request.POST.get('password', '')
            if MyUser.objects.filter(username=username):
                user = authenticate(username=username, password=password)
                if user:
                    if user.is_active:
                        # 登录当前用户
                        login(request, user)
                    return redirect('/')
                else:
                    tips = '账号密码错误,请重新输入'
            else:
                tips = '用户不存在,请注册'
        return render(request, 'user.html', locals())
    
    # 用户注册
    def registerView(request):
        title = '用户注册'
        if request.method == 'POST':
            username = request.POST.get('username', '')
            password = request.POST.get('password', '')
            if MyUser.objects.filter(username=username):
                tips = '用户已存在'
            else:
                user = MyUser.objects.create_user(username=username, password=password)
                user.save()
                # 添加权限
                permission = Permission.objects.filter(codename='visit_Product')[0]
                user.user_permissions.add(permission)
                return redirect('/user/login.html')
        return render(request, 'user.html', locals())
    
    # 退出登录
    def logoutView(request):
        logout(request)
        return redirect('/')
    

    最后编写模板文件user.html

    user 的 templates 的 user.html
    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
        <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css">
    </head>
    <body>
    <div class="flex-center">
        <div class="container">
        <div class="flex-center">
        <div class="unit-1-2 unit-1-on-mobile">
            <h1>MyDjango Auth</h1>
                {% if tips %}
            <div>{{ tips }}</div>
                {% endif %}
            <form class="form" action="" method="post">
                {% csrf_token %}
                <div>用户名:<input type="text" name='username'></div>
                <div>密 码:<input type="password" name='password'></div>
                <button type="submit" class="btn btn-primary btn-block">确定</button>
            </form>
        </div>
        </div>
        </div>
    </div>
    </body>
    </html>
    

    上述代码主要实现一个简单的操作流程,流程顺序为用户注册--->用户登录--->访问首页。
    在user中实现了用户的权限设置,接着在index中实现用户权限的校验。首先编写index的urls.py文件:

    #index 的 urls.py
    from django.urls import path
    from . import views
    urlpatterns = [
        # 首页的URL
        path('', views.index),
    ]
    

    然后编写对应的视图函数:

    #index的views.py
    from django.shortcuts import render
    from django.contrib.auth.decorators import login_required, permission_required
    
    # 使用login_required和permission_required分别对用户登录验证和用户权限验证
    @login_required(login_url='/user/login.html')
    @permission_required(perm='index.visit_Product', login_url='/user/login.html')
    def index(request):
        return render(request, 'index.html', locals())
    

    在视图函数index中使用了装饰起login_required和permission_required,分别对当前用户的登录状态和用户权限进行校验。

    • login_required:设置用户登录访问权限,如果当前用户尚未登录而直接访问首页,程序自动跳转到登录界面,只有用户完成登录后才能访问首页。login_required的参数有redirect_field_name和login_url

    参数redirect_field_name:默认值是next,当登录成功后,程序自动跳转回之前浏览的网页
    参数login_url:设置登录界面的URL地址,默认值是settings.py的属性LOGIN_URL,而属性LOGIN_URL需要开发者自行在settings.py中配置。

    • permission_required:验证当前用户是否拥有相应的权限。若用户没有使用权限,程序会跳转到登录界面或抛出异常。permission_required的参数如下:

    参数perm : 必须参数,判断当前用户是否拥有权限,参数为固定格式,如上例中index为项目的App名,visit_product来自数据表auth_permission的字段codename
    参数login_url: 设置登录界面的URL地址,默认值为None,若不设置参数,验证失败后会抛出404异常。
    参数 raise_exception :设置抛出异常,默认值为False

    装饰器permission_required的作用与内置函数has_perm相同,上述代码也可以使用函数has_perm实现装饰器permission_required的功能。

    # 使用函数has_perm实现装饰器permission_required功能
    from django.shortcuts import render, redirect
    @login_required(login_url='/user/login.html')
    def index(request):
        user = request.user
        if user.has_perm('index.visit_Product'):
            return render(request, 'index.html', locals())
        else:
            return redirect('/user/login.html')
    
    

    最后再模板index.html中实现用户权限判断。模板的<header>标签代码如下:

    #index 的 templates 的 index.html
    <header id="top">
        <!-- 内容显示区域 :width : 1211px -->
        <div id="top_box">
            <ul class="lf">
                <li><a href="#">华为官网</a></li>
                <li><a href="#">华为荣耀</a></li>
            </ul>
            <ul class="rt">
                {#在模版中使用user变量是一个User或者AnoymousUser对象,该对象由模型MyUser实例化#}
                {% if user.is_authenticated %}
                    <li>用户名: {{ user.username }}</li>
                    <li><a href="{% url 'logout' %}">退出登录</a></li>
                {% endif %}
                {#在模版中使用perms变量是Permission对象,该对象由模型Permission实例化#}
                {% if perms.index.add_product %}
                    <li>添加产品信息</li>
                {% endif %}
            </ul>
        </div>
    </header>
    

    从上述例子可以看到,项目的user主要实现权限的设置功能,项目的index主要实现权限的使用,权限的使用主要判断当前用户是否具有权限的使用资格,而权限的判断可以从视图函数或模板语法实现。

    设置用户组

    用户组就是对用户进行分组管理,其作用是在权限控制中批量对用户的权限进行分配,而不用一个个的按用户分配,节省维护的工作量。
    设置用户组分为两个步骤:设置用户组的权限和设置用户组的用户。
    我们在数据表auth_group中创建一个管理员用户组。用户组可以创建任意多个。


    image.png

    然后再shell模式下简单学习用户组的权限配置

    #导入内置模型Group和Permission
    >>> from django.contrib.auth.models import Group
    >>> from django.contrib.auth.models import Permission
    #获取某个权限对象permission
    >>> permission = Permission.objects.get(codename='visit_product')
    #获取某个用户组对象group
    >>> group = Group.objects.get(id=1)
    #将权限permission添加到用户组group中
    >>> group.permissions.add(permission)
    
    #当然也能删除某个权限
    >>> group.permissions.remove(permission)
    #删除全部权限
    >>> group.permissions.clear()
    

    然后我们再学习如何将用户分配到用户组。

    #导入模型MyUser和Group
    >>> from user.models import MyUser
    >>> from django.contrib.auth.models import Group
    #获取用户对象user,对象user代表用户名为user的数据信息
    >>> user = MyUser.objects.get(username='user')
    #获取用户组对象group,对象group代表用户组(管理员)的数据信息
    >>> group = Group.objects.get(id=1)
    #将用户添加到用户组
    >>> user.groups.add(group)
    
    #删除用户组某一用户
    >>> user.groups.remove(group)
    #清空用户组全部用户
    >>> user.groups.clear()
    
    

    相关文章

      网友评论

          本文标题:(10) Django - Auth认证系统

          本文链接:https://www.haomeiwen.com/subject/fnhbpqtx.html