Bootstrap

Django入门教程——用户管理实现

第六章 用户管理实现

教学目的

  • 复习数据的增删改查的实现。
  • 了解数据MD5加密算法以及实现
  • 模型表单中,自定义控件的使用
  • 中间件的原理和使用

需求分析

系统问题

  • 员工档案涉及到员工的秘密,不能让任何人都可以看到,主要是人事部门进行数据的维护,公司领导具有数据的查看权限。
  • 现在我们开发出来的功能,所有人都可以进行访问,都可以进行数据的修改,不符合需求。

系统功能

  • 管理员用户可以进行用户管理,实现用户的添加、修改、删除等
  • 普通用户可以登录系统,修改个人密码等

用户管理相关数据模型

数据模型设计

在这里插入图片描述

添加用户管理相关数据模型

  1. 添加新的应用python manage.py startapp userinfo

  2. 注册新的应用

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'archives',
        'userinfo',
    ]
    
  3. 创建数据模型

    # 用户信息
    class User(models.Model):
        username = models.CharField(verbose_name='用户账号', max_length=30)
        password = models.CharField(verbose_name='用户密码', max_length=40)
        employee = models.ForeignKey(to="archives.Employee", on_delete=models.CASCADE, verbose_name='员工ID', null=True)
        avatarUrl = models.CharField(verbose_name="头像", max_length=250, null=True, blank=True)
        role = models.ForeignKey(to="Role", to_field="id", on_delete=models.SET_NULL, verbose_name='角色', null=True)
        last_login = models.DateTimeField(verbose_name='最后登录时间', auto_now=True)
        create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
        class Meta:
            db_table = 'sys_user'
        
    # 角色
    class Role(models.Model):
        name = models.CharField(verbose_name='角色名称', max_length=50)
        remark = models.CharField(verbose_name='备注', max_length=255, null=True, blank=True, )
        class Meta:
            db_table = 'sys_role'
        def __str__(self):
            return self.name
    
  4. 执行命令创建数据表

    python manage.py makemigrations          
    python manage.py migrate
    

用户的增删改查

  1. 用户列表页面user_list.html

    {%extends 'common/base_list.html'%}
    {% block templet %}
    <!-- 顶部工具栏模板 -->
    <script type="text/html" id="toolbar">
        <div class="layui-btn-container">
            <button class="layui-btn  layui-btn-sm" lay-event="add">
                <i class="layui-icon layui-icon-addition"></i>
                新增
            </button>
        </div>
    </script>
    <!-- 表格操作模板 -->
    <script type="text/html" id="TableBar">
        <div class="layui-clear-space">
            <a class="layui-btn layui-btn-xs " lay-event="edit">
                <i class="layui-icon layui-icon-edit"></i>
                编辑
            </a>
            <a class="layui-btn layui-btn-xs layui-bg-red" lay-event="delete">
                <i class="layui-icon layui-icon-delete"></i>
                删除
            </a>
        </div>
    </script>
    {%endblock%}
    {% block js %}
    <script>
    layui.use(function(){
        var $ = layui.jquery,
            form = layui.form,
            table = layui.table,
            layer = layui.layer;
        // 渲染表格
        table.render({
            elem: '#TableId',
            url: '/user/list/',
            method:'post',
            toolbar: '#toolbar',
            defaultToolbar: ['filter', 'exports', 'print'],
            cols: [[
                {type: "checkbox", width: 50},
                {field: 'index', width: 80, title: '序号',align:'center', sort: true},
                {field: 'id', width: 80, title: 'ID',align:'center', sort: true,hide:true},
                {field: 'username', width: 150, title: '用户名',align:'center',sort: true},
                {field: 'employee', width: 150, title: '姓名',align:'center',sort: true},
                {field: 'role', width: 150, title: '角色',align:'center',sort: true},
                {field: 'last_login', width: 150, title: '最近登录',align:'center',sort: true},
                {title: '操作', minWidth: 240, toolbar: '#TableBar', align: "center", fixed: 'right' }
            ]],
            limits: [10, 20, 50,100,200,500],
            limit: 10,
            page: true,
            skin: 'row',
            even: true,
        });
        // 头部工具栏事件
        table.on('toolbar(TableFilter)', function (obj) {
            if (obj.event === 'add') {
                layer.open({
                    title: ['新增', 'font-size:18px;font-weight:bold;'],
                    type: 2,
                    shade: 0.3,
                    maxmin:true,
                    shadeClose: true,
                    area: ['60%', '600px'],
                    content: '/user/add/',
                });
            }
        });
    });
    </script>
    {%endblock%}
    
  2. 建立视图函数

    # 用户列表
    @csrf_exempt
    def user_list(request):
        if request.method == "GET":
            return render(request,"userinfo/user_list.html")
        page = request.POST.get('page', 1)
        limit = request.POST.get('limit', 10)
        searchParams = request.POST.get('searchParams', None)
        data_obj = User.objects.all()
        data_page = Paginator(data_obj, limit).page(page)
        data_list=[]
        count = (int(page) - 1) * int(limit)
        for row in data_page:
            count += 1
            item_dict = {}
            field_names = [field.name for field in row._meta.fields]
            for field in field_names:
                if field == "employee":
                    item_dict[field] = row.employee.name
                elif field == "role":
                    item_dict[field] = row.role.name
                else:
                    item_dict[field] = getattr(row,field)
            item_dict["index"]=count
            data_list.append(item_dict)
        return res_json_data.table_api(data=data_list,count=len(data_obj))
    
  3. 建立url路由

    from userinfo import views as user_views
    path("user/list/",user_views.user_list),
    
  4. 建立用户编辑表单类

    class edit_form(forms.ModelForm):
        class Meta:
            model = models.User
            fields = ['username','role','employee']
            widgets = {
                "employee":forms.Select(attrs={"lay-search":""}),
            }
        
        def __init__(self,*args,**kwargs):
            super().__init__(*args,**kwargs)
            for name,field in self.fields.items():
                field.widget.attrs.update({"class":"layui-input","placeholder":"输入"+field.label})
    
  5. 建立添加视图函数

    #settings.py
    DEFAULT_PASSWORD = '123'  # 设置默认密码
    
    from django.conf import settings
    # 用户添加
    def user_add(request):
        if request.method == "GET":
            form = user_form.edit_form()
            return render(request,"userinfo/user_edit.html",{"form":form})
        form = user_form.edit_form(data=request.POST)
        if form.is_valid():
            form.instance.password = settings.DEFAULT_PASSWORD
            form.save()
            messages.success(request, '添加成功')
            return render(request,"userinfo/user_edit.html",{"form":form})
        print(form.errors)
        return render(request,"userinfo/user_edit.html",{"form":form})
    
  6. 验证用户名不能重复

    def clean_username(self):
        username = self.cleaned_data.get("username")
        user = models.User.objects.filter(username=username).first()
        if user:
            raise forms.ValidationError("用户名已存在")
        return username
    
  7. 控制员工选择框只能选择没有创建用户的人员

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        user_obj = models.User.objects.all()
        user_id_list = [obj.employee_id for obj in user_obj]
        self.fields['employee'].queryset = Employee.objects.exclude(id__in=user_id_list)
        for name,field in self.fields.items():
            field.widget.attrs.update({"class":"layui-input","placeholder":"输入"+field.label})
    

对数据库中的密码进行加密

MD5简介

MD5加密算法是一种单向加密算法,是不可逆的一种的加密方式。MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

加密实现

  1. 建立encryption.py文件

  2. 建立md5加密算法函数

    import hashlib
    from django.conf import settings
    def MD5(str):
        obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
        obj.update(str.encode('utf-8'))
        return obj.hexdigest()
    
  3. 修改视图函数

    form.instance.password = MD5(settings.DEFAULT_PASSWORD)
    

实现密码重置

  1. 表格行操作模板

    <!-- 表格操作模板 -->
    <script type="text/html" id="TableBar">
        <div class="layui-clear-space">
            <a class="layui-btn layui-btn-xs " lay-event="reset">
                <i class="layui-icon layui-icon-edit"></i>
                重置密码
            </a>
            <a class="layui-btn layui-btn-xs layui-bg-red" lay-event="delete">
                <i class="layui-icon layui-icon-delete"></i>
                删除
            </a>
        </div>
    </script>
    
  2. 建立重置密码视图函数

    # 重置密码
    @csrf_exempt
    def reset_password(request):
        id = request.POST.get("id") #用户id
        password = MD5(settings.DEFAULT_PASSWORD)
        User.objects.filter(id=id).update(password=password)
        return res_json_data.success_api()
    
  3. 通过ajax实现密码重置

    // 单元格事件
    table.on('tool(TableFilter)', function (obj) {
        var data = obj.data; //获得当前行数据
        if (obj.event === 'reset') {
            layer.confirm('确认重置为默认密码吗?', {
                btn: ['确定', '取消'] //按钮
            }, function(index){
                $.post(
                    '/user/reset/', 
                    {"id":data.id},
                    function(data){
                    if (data.success) {
                        layer.msg(data.msg);
                    }
                })
            });
        }
    });
    

    jQuery get() 和 post() 方法用于通过 HTTP GET 或 POST 请求从服务器请求数据。

    语法:$.get( URL [, data ] [, callback ] [, dataType ] )

    • URL:发送请求的 URL字符串。
    • data:可选的,发送给服务器的字符串或 key/value 键值对(字典)。
    • callback:可选的,请求成功后执行的回调函数。
    • dataType:可选的,从服务器返回的数据类型。默认:智能猜测(可以是xml, json, script, 或 html)。

用户登录的实现

界面原型

在这里插入图片描述

代码实现

  1. 用户登录路由path("",user_views.user_login)

  2. 登录视图函数

    # 用户登录
    def user_login(request):
        if request.method == "GET":
            form = user_form.login_form()
            return render(request,"userinfo/login.html",{"form":form})
    
  3. 用户登录页面模板

    {% load static %}
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>员工信息管理系统</title>
        <link href="{%static 'layui/css/layui.css'%}" rel="stylesheet">
        <style>
            body {
                background:url("{% static 'loginbg.png'%}") 0% 0% / cover no-repeat;
                font-size:12px;
            }
            .main-body {
                top:50%;
                left:50%;
                position:absolute;
                transform:translate(-50%,-50%);
                overflow:hidden;
                width: 428px;
                background-color: #fff;
                border-radius:12px;
            }
            .login-top {
                height:117px;
                background-color:#148be4;
                border-radius:12px 12px 0 0;
                font-size:30px;
                font-weight:400;
                font-stretch:normal;
                color:#fff;
                line-height:117px;
                text-align:center;
                overflow:hidden;
            }
            .login-top .bg1 {
                display:inline-block;
                width:74px;
                height:74px;
                opacity:.1;
                border-radius:0 74px 0 0;
                position:absolute;
                left:0;
                top:43px;
                background-color: #fff;
            }
            .login-top .bg2 {
                display:inline-block;
                width:94px;
                height:94px;
                opacity:.1;
                border-radius:50%;
                position:absolute;
                right:-16px;
                top:-16px;
                background-color: #fff;
            }
            .center {
                width:288px;
                margin:0 auto;
                padding-top:40px;
                padding-bottom:15px;
                position:relative;
            }
            .login-btn {
                width:288px;
                height:40px;
                background-color:#1E9FFF;
                border-radius:16px;
                margin:24px auto 0;
                text-align:center;
                line-height:40px;
                color:#fff;
                font-size:14px;
                letter-spacing:0;
                cursor:pointer;
                border:none;
            }
            .center {
                width:288px;
                margin:0 auto;
                padding-top:40px;
                padding-bottom:15px;
                position:relative;
            }
        </style>
    </head>
    <body>
        <div class="main-body">
            <div class="login-top">
                <span>员工信息管理系统</span>
                <span class="bg1"></span>
                <span class="bg2"></span>
            </div>
           
        </div>
    
    <script src="{%static 'layui/layui.js'%}"></script>
    <script>
    layui.use(function(){
        var $ = layui.jquery;
        var layer = layui.layer;
        {% for msg in messages %}
            layer.alert('{{ msg}}');
        {% endfor %}
    });     
    </script>
    </body>
    </html>
    

    绝对定位布局(position)

    1. 定位方式属性
      • static 默认,元素在文档常规流中当前的布局位置
      • relative :不会脱离文档流,不会浮起来,一般为里面的absolute 元素提供定位依据
      • absolute 元素会被移出正常文档流,相对于上一个绝对定位的父元素来进行定位
      • fixed 元素会被移出正常文档流,相对于屏幕视口(viewport)的位置来指定元素位置
    2. absolute相对定位详解
      • 父元素没有设置相对定位或绝对定位的情况下,元素相对于根元素定位(即html元素)
      • 父元素设置了相对定位或绝对定位,元素会相对于离自己最近的设置了相对或绝对定位的父元素进行定位
    3. 定位属性
      • top样式属性定义了定位元素的上外边距边界与其包含块上边界之间的偏移
      • bottom 定位元素下外边距边界与其包含块下边界之间的偏移
      • left 定位元素的上外边距边界与其包含块上边界之间的偏移
      • right 定位元素的右外边距边界与其包含块右边界之间的偏移
      • transform: translate(12px, 50%); 水平平移,第一个参数对应X轴,第二个参数对应Y轴。如果第二个参数未提供,则默认值为0,注意参数用逗号分隔
  4. 用户登录表单类

    # 登录表单类
    class login_form(forms.ModelForm):
        class Meta:
            model =models.User
            fields = ['username','password']
            widgets ={
                'password':forms.PasswordInput(attrs={"lay-affix":"eye"},render_value=True),
            }
        
        def clean_password(self):
            password = self.cleaned_data.get("password")
            return MD5(password)
        
        def __init__(self,*args,**kwargs):
            super().__init__(*args,**kwargs)
            for name,field in self.fields.items():
                field.widget.attrs.update({"class":"layui-input","placeholder":"输入"+field.label})
    
    • render_value=True ——保持上次输入的值
  5. 修改视图函数,进行用户登录验证

    # 用户登录
    def user_login(request):
        if request.method == "GET":
            form = user_form.login_form()
            return render(request,"userinfo/login.html",{"form":form})
        form = user_form.login_form(data=request.POST)
        if form.is_valid():
            print(form.cleaned_data,"===校验完成") #打印form提交的所有数据
            user_obj = User.objects.filter(**form.cleaned_data).first()
            if not user_obj:
                messages.warning(request, '用户名或密码错误')
                return render(request,"userinfo/login.html",{"form":form})
            # 登录成功
            return redirect("/employee/list/")
        print(form.errors)
        return render(request,"userinfo/login.html",{"form":form})
    

是否登录检验

会话Session

  1. 简介

    Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。

  2. 操作语法

    • 设置session,通过request.session字典

      request.session['user_info'] = {
          'id':user_obj.id,
          'user_id':user_obj.id_number,
          'user_name':user_obj.user_name,
          'role_id':user_obj.role_id,
      }
      
    • session有效期设置

      request.session.set_expiry(value)

      • 如果value是个整数,session会在设置秒数后失效。
      • 如果value是个datatimetimedelta,session就会在这个时间后失效。
      • 如果value是0,用户关闭浏览器session就会失效。//我们用的就是这种方式
      • 如果value是None,session会依赖全局session失效策略。
    • 获取session值

      后端:user_info = request.session.get('user_info')

      前端:request.session.user_info.name

修改代码实现访问授权

  1. 通过session记录登录用户信息

    # 登录成功
    request.session["user_info"] = {
        "id":user_obj.id,
        "username":user_obj.username,
        "name":user_obj.employee.name,
    }
    request.session.set_expiry(0)
    return redirect("/employee/list/")
    
  2. 用户登录状态验证

    user_info = request.session.get("user_info")
    print(user_info,"==用户信息")
    if not user_info:
        return redirect("/")
    

中间件

中间件在Django中是一种轻量级、底层的“插件”系统,用于全局地处理请求和响应。它位于Django的请求和响应处理过程之间,可以对HTTP请求和响应进行预处理和后处理,从而实现诸如权限控制、日志记录、数据压缩、缓存等功能。

中间件的请求流程:

在这里插入图片描述

用中间件进行登录验证拦截

  1. 建立auth.py文件,创建中间件类

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import redirect
    from datetime import datetime
    class AuthMiddleware(MiddlewareMixin):
        def process_request(self, request):
            print("===访问地址:",request.path,datetime.now())
            user_info = request.session.get('user_info')
            print(user_info,"==登陆用户信息")
            if  user_info:
                return
            return redirect("/")
    
  2. 在setting中注册中间件

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'userinfo.auth.AuthMiddleware',
    ]
    
    • 第9行:注册中间件
  3. 处理302错误,重定向错误

    class AuthMiddleware(MiddlewareMixin):
        def process_request(self, request):
            print("===访问地址:",request.path,datetime.now())
            pass_urls = ['/']
            if request.path  in pass_urls:
                return
            
            user_info = request.session.get('user_info')
            print(user_info,"==登陆用户信息")
            if  user_info:
                return
            return redirect("/")
    
    • return 没有返回值,将进行放行,否则将进行拦截

注销和密码修改

注销操作

  1. 修改公共模板页面添加用户信息和菜单。

    <li class="layui-nav-item" style="float: right;">
        <a href="javascript:void(0);">
            <img src="https://unpkg.com/[email protected]/demo/avatar/1.jpg" class="layui-nav-img">
            {{request.session.user_info.name}}
        </a>
        <dl class="layui-nav-child">
            <dd><a href="">修改密码</a></dd>
            <dd><a href="/user/logout/">退出</a></dd>
        </dl>
    </li>
    
  2. 添加注销视图函数

    # 用户退出
    def user_logout(request):
        request.session.clear()
        return redirect("/")
    

密码修改

  1. 创建单行编辑模版页面single_edit_dlg.html

    {%extends 'common/layout_dlg.html'%}
    {% block css %}
    <style>
        .layui-input-block{
            margin-left: 150px; 
        }
        .layui-form-label{
            width: 120px;
        }
    </style>
    {%endblock%}
    {%block content%}
    <div class="layui-card-body">
        <form class="layui-form"  method="post" novalidate>
                {% csrf_token %}
                {% for field in form %}
                <div class="layui-form-item">
                    {% if field.field.required %}
                        <label class="layui-form-label label-required-next">{{ field.label }}:</label>
                    {% else %}
                        <label class="layui-form-label">{{ field.label }}:</label>
                    {% endif %}
                        <div class="layui-input-block">
                            {{ field }}
                            <span style="color: red">{{ field.errors.0 }}</span>
                        </div>
                </div>
                {% endfor %}
             
            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button type="submit" class="layui-btn" lay-submit>确认提交</button>
                </div>
            </div>
        </form>
    </div>
    {% endblock %}
    {%block js%}
    {% block extendsjs %}{% endblock %}
    {% for msg in messages %}
    <script>
        layui.use(['layer'], function () {
            var iconValue = "{{msg.tags}}" == 'success' ? 1 : 2;
            layer.msg(
                '{{msg}}',
                { icon: iconValue, time: 2000 },
                function (){
                    if( "{{msg.tags}}" == 'success' ){
                        parent.layer.close(parent.layer.getFrameIndex(window.name)); //关闭当前页
                        parent.layui.table.reload('TableId');//重新加载父窗口表格
                    }
                }
            )
        })
    </script>
    {% endfor %}
    {% endblock %}
    
  2. 创建密码修改表单类

    # 密码修改表单类
    class password_form(forms.ModelForm):
        confirm_password = forms.CharField(
            label="确认新密码",
            required=True,
            widget=forms.PasswordInput(render_value=True,attrs={"lay-affix":"eye"})
        )
        new_password = forms.CharField(
            label="新密码",
            required=True,
            widget=forms.PasswordInput(render_value=True,attrs={"lay-affix":"eye"})
        )
        class Meta:
            model =models.User
            fields = ['username','password','new_password','confirm_password']
            widgets ={
                'password':forms.PasswordInput(attrs={"lay-affix":"eye"},render_value=True),
            }
        
        def __init__(self,*args,**kwargs):
            super().__init__(*args,**kwargs)
            for name,field in self.fields.items():
                field.widget.attrs.update({"class":"layui-input","placeholder":"输入"+field.label})
    
  3. 完善密码修改视图函数

    # 修改密码
    def change_password(request):
        if request.method == "GET":
            form = user_form.password_form()
            return render(request,"common/single_edit_dlg.html",{"form":form})
        user_info = request.session['user_info']
        user_id = user_info['id']
        row_obj = User.objects.filter(id=user_id).first()
        form = user_form.password_form(data=request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = MD5(form.cleaned_data['password'])
            if row_obj.username != username or row_obj.password!=password:
                messages.error(request, '用户名或密码不正确')
                return render(request,"common/single_edit_dlg.html",{"form":form})
            if form.cleaned_data['new_password']!= form.cleaned_data['confirm_password']:
                messages.error(request, '两次密码输入不一致')
                return render(request,"common/single_edit_dlg.html",{"form":form})
            new_password = MD5(form.cleaned_data['new_password'])
            User.objects.filter(id=user_id).update(password=new_password)
            messages.success(request, '修改成功')
            return render(request,"common/single_edit_dlg.html",{"form":form})
        
        return render(request,"common/single_edit_dlg.html",{"form":form})
    

‘password’])
if row_obj.username != username or row_obj.password!=password:
messages.error(request, ‘用户名或密码不正确’)
return render(request,“common/single_edit_dlg.html”,{“form”:form})
if form.cleaned_data[‘new_password’]!= form.cleaned_data[‘confirm_password’]:
messages.error(request, ‘两次密码输入不一致’)
return render(request,“common/single_edit_dlg.html”,{“form”:form})
new_password = MD5(form.cleaned_data[‘new_password’])
User.objects.filter(id=user_id).update(password=new_password)
messages.success(request, ‘修改成功’)
return render(request,“common/single_edit_dlg.html”,{“form”:form})

   return render(request,"common/single_edit_dlg.html",{"form":form})









;