Django 05 :管理员操作 + 用户认证【 中间件 +图片验证码 + Cookie 与 Session】


上一篇: Django 04 :靓号管理【 靓号的增删改 + 搜索 + 分页 + 时间插件 + ModelForm与BootStrap】

1、管理员操作

创建管理员,来管理这些部门用户等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yW4FCVu6-1646192918329)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227224148468.png)]

1.1、创建表

  • models.py
class Admin(models.Model):
    """ 管理员 """
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)

    def __str__(self):
        return self.username
python manage.py makemigrations

python manage.py migrate

造点数据

mysql -u root -p   
show databases;  # 查看数据库 
use 数据库名字;  # 进入数据库
show tables;  
desc 表的名字;
insert into app01_admin(username, password) values("coderz","123456");

1.2、前端基础效果

在这里插入图片描述

  • 导航栏

    <ul class="nav navbar-nav">
                    <li><a href="/admin/list/">管理员账户</a></li>
                    <li><a href="/depart/list/">部门管理</a></li>
                    <li><a href="/user/list/">用户管理</a></li>
                    <li><a href="/mobile/list/">靓号管理</a></li>
                </ul>
    
  • admin_list.html

    {% extends 'layout.html' %}
    
    {% block content %}
        {# 用户列表 #}
        <div class="container">
            <div style="margin-bottom: 10px" class="clearfix">
                <a class="btn btn-success" href="#">
                    {# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
                    <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
                    新建管理员
                </a>
    
                {# 搜索框 #}
                <div style="float: right;width: 300px;">
                    <form method="get">
                        <div class="input-group">
                            <input type="text" name="q" class="form-control" placeholder="Search for..."
                                   value="{{ search_result }}">
                            <span class="input-group-btn">
                            <button class="btn btn-default" type="submit">
                                <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
                            </button>
                          </span>
                        </div>
                    </form>
                </div>
            </div>
    
            {# 管理员列表 #}
            <div class="panel panel-default">
                <div class="panel-heading">
                    <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
                    管理员列表
                </div>
    
                <!-- 表格部分 -->
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th>ID</th>
                        <th>用户名</th>
                        <th>密码</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for obj in queryset %}
                        <tr>
                            <th>{{ obj.id }}</th>
                            <td>{{ obj.username }}</td>
                            <td>***********</td>
                            {# 密码不显示明文 #}
                            <td>
                                <a class="btn btn-primary btn-xs" href="#">编辑</a>
                                <a class="btn btn-danger btn-xs" href="#">删除</a>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
    
            {# 分页 #}
            <div class="clearfix">
                <ul class="pagination">
                    {{ page_string }}
                </ul>
            </div>
    
        </div>
    
    
    
    {% endblock %}
    
  • admin.py

    from django.shortcuts import render, redirect
    from app01 import models
    from app01.utils.pagination import Pagination
    
    
    def admin_list(request):
        """管理员列表"""
        data_dict = {}
        search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
        if search_result:
            data_dict["mobile__contains"] = search_result
        queryset = models.Admin.objects.all()
        page_object = Pagination(request, queryset)
        context = {
            "search_data": search_result,
            "queryset": queryset,
            "page_string": page_object.html()  # 页码
        }
        return render(request, "admin_list.html", context)
    
    
  • utils/encrypt.py

    from django.conf import settings
    import hashlib
    
    
    def md5(data_string):
        obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
        obj.update(data_string.encode('utf-8'))
        return obj.hexdigest()
    
    

效果展示:

在这里插入图片描述

错误显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hr5h91Rq-1646192918331)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220228013314241.png)]

原因:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7JaweY4-1646192918331)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220228014035237.png)]

1.3、新建管理员(确定密码)

我们发现,新建部门、新建用户、新建靓号、新建管理员,这些界面太相似了,就几处文字显示不太,有木有办法可以减少我们反复复制粘贴堆*山??

当然,我们创建一个HTML文件newly_create.html,通过传部分参数,满足需求

{% extends 'layout.html' %}

{% block content %}
    {# 用户列表 #}
    <div class="container">
        <div style="margin-bottom: 10px" class="clearfix">
            <a class="btn btn-success" href="/admin/add/">
                {# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
                <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
                新建管理员
            </a>

            {# 搜索框 #}
            <div style="float: right;width: 300px;">
                <form method="get">
                    <div class="input-group">
                        <input type="text" name="q" class="form-control" placeholder="Search for..."
                               value="{{ search_result }}">
                        <span class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
                        </button>
                      </span>
                    </div>
                </form>
            </div>
        </div>

        {# 管理员列表 #}
        <div class="panel panel-default">
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
                管理员列表
            </div>

            <!-- 表格部分 -->
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for obj in queryset %}
                    <tr>
                        <th>{{ obj.id }}</th>
                        <td>{{ obj.username }}</td>
                        <td>***********</td>
                        {# 密码不显示明文 #}
                        <td>
                            <a class="btn btn-primary btn-xs" href="#">编辑</a>
                            <a class="btn btn-danger btn-xs" href="#">删除</a>
                        </td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>

        {# 分页 #}
        <div class="clearfix">
            <ul class="pagination">
                {{ page_string }}
            </ul>
        </div>

    </div>



{% endblock %}
  • urls.py

    path("admin/list/", admin.admin_list),
    path("admin/add/", admin.admin_add),
    
  • admin.py

    from django.shortcuts import render, redirect
    from app01 import models
    from app01.utils.pagination import Pagination
    from app01.utils.bootstrap import BootStrapModelForm
    from django import forms
    from django.core.exceptions import ValidationError
    from app01.utils.encrypt import md5
    
    
    class AdminModelForm(BootStrapModelForm):
        confirm_password = forms.CharField(
            label="确认密码",
            widget=forms.PasswordInput(render_value=True)
        )
    
        class Meta:
            model = models.Admin
            fields = ["username", 'password', "confirm_password"]
            widgets = {
                "password": forms.PasswordInput(render_value=True)
            }
    
        # 返回密文(数据加密)
        def clean_password(self):
            pwd = self.cleaned_data.get("password")
            return md5(pwd)
    
        def clean_confirm_password(self):
            pwd = self.cleaned_data.get("password")
            confirm = md5(self.cleaned_data.get("confirm_password"))
            if confirm != pwd:  # 密文之间进行比较
                raise ValidationError("密码不一致")
            # 返回什么,此字段以后保存到数据库就是什么。
            return confirm
    
    
    class AdminEditModelForm(BootStrapModelForm):
        class Meta:
            model = models.Admin
            fields = ['username']
    
    
    def admin_list(request):
        """管理员列表"""
        data_dict = {}
        search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
        if search_result:
            data_dict["mobile__contains"] = search_result
    
        queryset = models.Admin.objects.filter(**data_dict)
        page_object = Pagination(request, queryset)
        context = {
            "search_data": search_result,
            "queryset": queryset,
            "page_string": page_object.html()  # 页码
        }
        return render(request, "admin_list.html", context)
    
    
    def admin_add(request):
        """ 添加管理员 """
        title = "新建管理员"
        if request.method == "GET":
            form = AdminModelForm()
            return render(request, 'newly_create.html', {'form': form, "title": title})
        form = AdminModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('/admin/list/')
    
        return render(request, 'newly_create.html', {'form': form, "title": title})
    
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfvvfYoh-1646192918332)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301192653378.png)]

正确输入后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7VTvLii-1646192918332)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301192714037.png)]

不知道大家有没有碰到,点击【新建管理员】,死活进不去/admin/add/,且代码无误

可尝试重启项目

1.4、编辑

  • admin.py

    def admin_edit(request, nid):
        """ 编辑管理员 """
        # 对象 / None
        row_object = models.Admin.objects.filter(id=nid).first()
        if not row_object:  # 判断是否为空
            # return render(request, 'error.html', {"msg": "数据不存在"})
            return redirect('/admin/list/')
    
        title = "编辑管理员"
        if request.method == "GET":
            form = AdminEditModelForm(instance=row_object)  # instance=row_object ==> 显示默认值
            return render(request, 'newly_create.html', {"form": form, "title": title})
    
        form = AdminEditModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/admin/list/')
        return render(request, 'newly_create.html', {"form": form, "title": title})
    
  • urls.py

    path("admin/<int:nid>/edit/", admin.admin_edit),
    
  • admin_list.html

    <td>
                                <a class="btn btn-primary btn-xs" href="/admin/{{ obj.id }}/edit/">编辑</a>
                                <a class="btn btn-danger btn-xs" href="#">删除</a>
                            </td>
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vSXmPXvy-1646192918332)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301215129062.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQo8oMNY-1646192918333)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301215136325.png)]

1.5、删除和重置密码

1.5.1、删除

  • admin.py

    def admin_delete(request, nid):
        """ 删除管理员 """
        models.Admin.objects.filter(id=nid).delete()
        return redirect('/admin/list/')
    
  • urls.py

    path("admin/<int:nid>/detele/", admin.admin_delete),
    
  • admin_list.html

    <td>
                                <a class="btn btn-primary btn-xs" href="/admin/{{ obj.id }}/edit/">编辑</a>
                                <a class="btn btn-danger btn-xs" href="/admin/{{ obj.id }}/detele">删除</a>
                            </td>
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoVO6mAg-1646192918333)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301215654687.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnYyoCDU-1646192918333)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301215713513.png)]

1.5.2、重置密码

  • admin.py

    class AdminResetModelForm(BootStrapModelForm):
        confirm_password = forms.CharField(
            label="确认密码",
            widget=forms.PasswordInput(render_value=True)
        )
    
        class Meta:
            model = models.Admin
            fields = ['password', 'confirm_password']
            widgets = {
                "password": forms.PasswordInput(render_value=True)
            }
    
        def clean_password(self):
            pwd = self.cleaned_data.get("password")
            md5_pwd = md5(pwd)
    
            # 去数据库校验当前密码和新输入的密码是否一致
            exists = models.Admin.objects.filter(id=self.instance.pk, password=md5_pwd).exists()
            if exists:
                raise ValidationError("不能与以前的密码相同")
    
            return md5_pwd
    
        def clean_confirm_password(self):
            pwd = self.cleaned_data.get("password")
            confirm = md5(self.cleaned_data.get("confirm_password"))
            if confirm != pwd:
                raise ValidationError("密码不一致")
            # 返回什么,此字段以后保存到数据库就是什么。
            return confirm
    
    
    def admin_reset(request, nid):
        """ 重置密码 """
        # 对象 / None
        row_object = models.Admin.objects.filter(id=nid).first()
        if not row_object:
            return redirect('/admin/list/')
    
        title = "重置密码 - {}".format(row_object.username)
    
        if request.method == "GET":
            form = AdminResetModelForm()
            return render(request, 'newly_create.html', {"form": form, "title": title})
    
        form = AdminResetModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/admin/list/')
        return render(request, 'newly_create.html', {"form": form, "title": title})
    
  • urls.py

    path('admin/<int:nid>/reset/', admin.admin_reset),
    
  • admin_list.html

    <td>
                                <a href="/admin/{{ obj.id }}/reset/">重置密码</a>
                            </td>
    

效果展示:

先瞅一眼数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NnNHqU2V-1646192918334)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302001025482.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jag20ZOn-1646192918335)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301221700168.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rF4qgP8l-1646192918335)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301221743911.png)]

2、用户认证(登录)

实现用户登录前,我们先补充些知识

2.1、Cookie 和 Session

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

下面段解释转自: Cookie和Session是什么?它们的区别是什么?_qichangjian的博客-CSDN博客_cookie和session

什么是Cookie?

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。

当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
信息保存的时间可以根据需要设置.

如果没有设置Cookie失效日期,它们仅保存到关闭浏览器程序为止.	   
如果将Cookie对象的Expires属性设置为Minvalue,则表示Cookie永远不会过期.

Cookie存储的数据量很受限制,大多数浏览器支持最大容量为4K,因此不要用来保存数据集及其他大量数据.

由于并非所有的浏览器都支持Cookie,并且数据信息是以明文文本的形式保存在客户端的计算机中,
因此最好不要保存敏感的,未加密的数据,否则会影响网站的安全性
什么是Session?

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

每个用户访问服务器都会建立一个session,那服务器是怎么标识用户的唯一身份呢?事实上,用户与服务器建立连接的同时,服务器会自动为其分配一个SessionId。

Session和Cookie的区别?

1、数据存储位置:cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、安全性:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。

3、服务器性能:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。

4、数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、信息重要程度:可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。

下面我们简单从

【http无状态短连接】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nro9dy7k-1646192918335)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301222116327.png)]

左:浏览器,右:网站

- 短链接 ==> 浏览器发送【请求】,网站返回【响应】,然后链接就断开了
- 之后再发送请求,网站就不知道你是谁了

那么咋整呢? Cookie 和 Session

http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWYpv4sm-1646192918336)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220301222134440.png)]

我们基于Cookie 和 Session就可以实现用户登录和认证

2.2、用户认证—基本实现

  • login.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户登录</title>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
        <style>
            .account {
                width: 400px;
                border: 1px solid #dddddd;
                border-radius: 5px;
                box-shadow: 5px 5px 20px #aaa;
    
                margin-left: auto;
                margin-right: auto;
                margin-top: 100px;
                padding: 20px 40px;
            }
    
            .account h2 {
                margin-top: 10px;
                text-align: center;
            }
        </style>
    </head>
    <body>
    <div class="account">
        <h2>用户登录</h2>
        <form method="post" novalidate>  {# 以POST形式提交 #}
            {% csrf_token %}
            <div class="form-group">
                <label>用户名</label>
                {{ form.username }}
                <span style="color: red;">{{ form.username.errors.0 }}</span>
            </div>
            <div class="form-group">
                <label>密码</label>
                {{ form.password }}
                <span style="color: red;">{{ form.password.errors.0 }}</span>
            </div>
    
            <input type="submit" value="登 录" class="btn btn-primary">
        </form>
    </div>
    
    </body>
    </html>
    
    
  • urls.py

    path('login/', account.login),
    
  • account.py

    from django.shortcuts import render, HttpResponse, redirect
    from django import forms
    from app01 import models
    from app01.utils.bootstrap import BootStrapForm
    from app01.utils.encrypt import md5
    
    
    class LoginForm(BootStrapForm):
        username = forms.CharField(
            label="用户名",
            widget=forms.TextInput,
            required=True
        )
        password = forms.CharField(
            label="密码",
            widget=forms.PasswordInput(render_value=True),
            required=True
        )
    
        def clean_password(self):
            pwd = self.cleaned_data.get("password")
            return md5(pwd)
    
    
    def login(request):
        """ 登录 """
        if request.method == "GET":
            form = LoginForm()
            return render(request, 'login.html', {'form': form})
    
        form = LoginForm(data=request.POST)
        if form.is_valid():
            # 去数据库校验用户名和密码是否正确,获取用户对象、若错误则为空返回None
            # admin_object = models.Admin.objects.filter(username=xxx, password=xxx).first()
            admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
            if not admin_object:
                form.add_error("password", "用户名或密码错误")  # 在“密码”输入框下显示【用户名或密码错误】
                # form.add_error("username", "用户名或密码错误")  # 在“用户名”输入框下显示【用户名或密码错误】
                return render(request, 'login.html', {'form': form})
    
            # 用户名和密码正确
            # 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中;
            request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
            # session可以保存7天
            request.session.set_expiry(60 * 60 * 24 * 7)
    
            return redirect("/admin/list/")
    
        return render(request, 'login.html', {'form': form})
    
    

效果展示:

我们先自己创建一个账号密码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vOrl8njO-1646192918336)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302003620641.png)]

输入个错误密码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhPBmIfz-1646192918337)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302003646760.png)]

输入正确账户密码后,重定向至admin/list

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-79rYLtiQ-1646192918337)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302003711306.png)]

BUG:以往设置的账号【coderz02】无法登录了,【重置密码】后又可以用了

【补充:】

我们session信息存哪去了呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q03rGq0Q-1646192918338)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302004622779.png)]

2.3、体验中间件

若用户未登录,那么除了【登录界面】,访问其他界面都会自动跳转到【登录界面】

目标:在所有视图函数前面统一加入判断。

info = request.session.get("info")
if not info:
 return redirect('/login/')

十几个视图函数,一个一个加不太合实际,怎么办呢?

使用Django的【中间件】

实际上我们发送请求到视图函数def index(request): ...会经过几个中间件(类),类中定义了process_request方法

也就是,请求来了要执行一个一个类中的process_request方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DT3sh4SM-1646192918338)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302010928186.png)]

我们app01创个文件夹体验一下【中间件】

  • 定义中间件 app01/middleware/auth.py

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse
    
    
    class M1(MiddlewareMixin):
        """ 中间件1 """
    
        def process_request(self, request):
            # 如果方法中没有返回值(返回None),继续向后走
            # 如果有返回值 HttpResponse、render 、redirect
            print("M1.process_request")
            return HttpResponse("无权访问")
    
        def process_response(self, request, response):
            print("M1.process_response")
            return response
    
    
    class M2(MiddlewareMixin):
        """ 中间件2 """
    
        def process_request(self, request):
            print("M2.process_request")
    
        def process_response(self, request, response):
            print("M2.process_response")
            return response
    
    
  • 应用中间件(在 setings.py中注册中间件)

    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',
        'app01.middleware.auth.M1',
        'app01.middleware.auth.M2',
    ]
    
  • 在中间件的process_request方法

    # 如果方法中没有返回值(返回None),继续向后走
    # 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行。
    

效果展示:

访问http://127.0.0.1:8000/depart/list/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4f9qcAXN-1646192918339)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302013442470.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUgDvIDu-1646192918339)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302013419686.png)]

2.4、中间件实现【登录校验】

  • 编写中间件 app01/middleware/auth.py

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse, redirect
    
    
    class AuthMiddleware(MiddlewareMixin):
    
        def process_request(self, request):
            # 0.排除那些不需要登录就能访问的页面
            #   request.path_info 获取当前用户请求的URL /login/
            if request.path_info == "/login/":
                return
    
            # 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。
            info_dict = request.session.get("info")
            print(info_dict)
            if info_dict:
                return
    
            # 2.没有登录过,重新回到登录页面
            return redirect('/login/')
    

    PS : 不要忘了【排除那些不需要登录就能访问的页面】,否则就会看到那么多次的请求

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPc15D8C-1646192918340)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302014532534.png)]

    访问未成功,返回,再访问,再返回… ,所以出现了上面的情况

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkcvvbTa-1646192918340)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302014723806.png)]

  • 应用中间件

    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',
        'app01.middleware.auth.AuthMiddleware',
    ]
    

然后,我们删掉以前的Cookie(勿忘!!!!!!!)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N68BBkVA-1646192918340)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302022354037.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rGQiAPgf-1646192918341)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302022423950.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QL0tXhhE-1646192918341)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302022437520.png)]

然后访问http://127.0.0.1:8000/depart/list,就会自动跳转到【登录界面】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q9ZKJPAj-1646192918341)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302022603490.png)]

2.5、注销

  • account.py

    def logout(request):
        """ 注销 """
        request.session.clear()
        return redirect('/login/')
    
  • layout.html

    <li><a href="/logout/">注销</a></li>
    
  • urls.py

    path('logout/', account.logout),
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wZYkl8Qj-1646192918342)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302023401122.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qCUcK9V0-1646192918342)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302023409389.png)]

2.6、图片验证码

前面我们实现了用户登录的基础功能,但这样容易被【暴力破解】,所以我们引入【图片验证码】

2.6.1、生成图片验证码

pip install pillow

下面我们写个脚本测试一下(提前准备好字体文件)

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter


def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')

    def rndChar():
        """
        生成随机字母
        :return:
        """
        return chr(random.randint(65, 90))

    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))

    # 写文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())

    # 写干扰点
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())

    # 写干扰圆圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())

    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)

        draw.line((x1, y1, x2, y2), fill=rndColor())

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img, ''.join(code)


if __name__ == '__main__':
    img, code_str = check_code()
    print(code_str)

    with open('code.png', 'wb') as f:
        img.save(f, format='png')

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FT9av6Bu-1646192918343)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302024543790.png)]

2.6.2、图片验证码【显示】

  • account.py

    (1)在class LoginForm(BootStrapForm):加入code = forms.CharField(在前端显示输入框)

    (2再定义函数def img_code(request):

    class LoginForm(BootStrapForm):
        username = forms.CharField(
            label="用户名",
            widget=forms.TextInput,
            required=True
        )
        password = forms.CharField(
            label="密码",
            widget=forms.PasswordInput(render_value=True),
            required=True
        )
    
        code = forms.CharField(
            label="验证码",
            widget=forms.TextInput,
            required=True
        )
    
        def clean_password(self):
            pwd = self.cleaned_data.get("password")
            return md5(pwd)
    
    
    from io import BytesIO
    from app01.utils.img_verification import check_code
    
    
    def img_code(request):
        """ 生成图片验证码 """
        # 调用pillow函数,生成图片
        img, code_string = check_code()
        # 写入到自己的session中(以便于后续获取验证码再进行校验)
        request.session['image_code'] = code_string
    
        # 以前我们用脚本产生的图片验证码是张图片,返还到前端还要再读取,很麻烦
        # 现在我们利用 BytesIO() 相当于 内存中的一个对象(可以理解为把图片直接写入内存中)
        stream = BytesIO()
        img.save(stream, 'png')
        return HttpResponse(stream.getvalue())
    
  • login.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
        <style>
            .account {
                width: 400px;
                border: 1px solid #dddddd;
                border-radius: 5px;
                box-shadow: 5px 5px 20px #aaa;
    
                margin-left: auto;
                margin-right: auto;
                margin-top: 100px;
                padding: 20px 40px;
            }
    
            .account h2 {
                margin-top: 10px;
                text-align: center;
            }
        </style>
    </head>
    <body>
    <div class="account">
        <h2>用户登录</h2>
        <form method="post" novalidate>
            {% csrf_token %}
            <div class="form-group">
                <label>用户名</label>
                {{ form.username }}
                <span style="color: red;">{{ form.username.errors.0 }}</span>
            </div>
            <div class="form-group">
                <label>密码</label>
                {{ form.password }}
                <span style="color: red;">{{ form.password.errors.0 }}</span>
            </div>
            <div class="form-group">
                <label for="id_code">图片验证码</label>
                <div class="row">
                    <div class="col-xs-7">
                        {{ form.code }}
                        <span style="color: red;">{{ form.code.errors.0 }}</span>
                    </div>
                    <div class="col-xs-5">
                        <img id="image_code" src="/image/code/" style="width: 125px;">  {# 通过url访问图片,达到动态生成的效果 #}
                    </div>
                </div>
            </div>
            <input type="submit" value="登 录" class="btn btn-primary">
        </form>
    </div>
    
    </body>
    </html>
    
    
  • urls.py

    path('image/code/', account.img_code),
    
  • auth.py

    勿忘修改auth.py,而且那个"/image/code/"不可写为"image/code/",少一个反斜杠,图片验证码也不能正常显示

    if request.path_info in ["/login/", "/image/code/"]:
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xi8RY0Qi-1646192918343)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302031931587.png)]

点击刷新即可更换验证码

2.6.3、图片验证码【校验】

  • account.py

    user_input_code = form.cleaned_data.pop('code')
            code = request.session.get('image_code', "")
            if code.upper() != user_input_code.upper():
                form.add_error("code", "验证码错误")
                return render(request, 'login.html', {'form': form})
    
    # session可以保存7天(7天免登录,用户信息可以保存七天)
            request.session.set_expiry(60 * 60 * 24 * 7)
    

    整合起来:

    def login(request):
        """ 登录 """
        if request.method == "GET":
            form = LoginForm()
            return render(request, 'login.html', {'form': form})
    
        form = LoginForm(data=request.POST)
        if form.is_valid():
            # 验证成功,获取到的用户名和密码
            # {'username': 'wupeiqi', 'password': '123',"code":123}
            # {'username': 'wupeiqi', 'password': '5e5c3bad7eb35cba3638e145c830c35f',"code":xxx}
            # 验证码的校验
            user_input_code = form.cleaned_data.pop('code')
            code = request.session.get('image_code', "")
            if code.upper() != user_input_code.upper():
                form.add_error("code", "验证码错误")
                return render(request, 'login.html', {'form': form})
    
            # 去数据库校验用户名和密码是否正确,获取用户对象、若错误则为空返回None
            # admin_object = models.Admin.objects.filter(username=xxx, password=xxx).first()
            admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
            if not admin_object:
                form.add_error("password", "用户名或密码错误")  # 在“密码”输入框下显示【用户名或密码错误】
                # form.add_error("username", "用户名或密码错误")  # 在“用户名”输入框下显示【用户名或密码错误】
                return render(request, 'login.html', {'form': form})
    
            # 用户名和密码正确
            # 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中;
            request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
            # session可以保存7天(7天免登录,用户信息可以保存七天)
            request.session.set_expiry(60 * 60 * 24 * 7)
    
            return redirect("/admin/list/")
    
        return render(request, 'login.html', {'form': form})
    
    
  • login.html

    <div class="form-group">
                <label for="id_code">图片验证码</label>
                <div class="row">
                    <div class="col-xs-7">
                        {{ form.code }}
                        <span style="color: red;">{{ form.code.errors.0 }}</span>
                    </div>
                    <div class="col-xs-5">
                        <img id="image_code" src="/image/code/" style="width: 125px;">  {# 通过url访问图片,达到动态生成的效果 #}
                    </div>
                </div>
            </div>
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMQkABmv-1646192918344)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302114010914.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f6e1EYGh-1646192918344)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302114047748.png)]

输入正确的账号密码后,即可进入系统:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmcszNEU-1646192918344)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220302114206121.png)]

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DLNovice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值