当前位置:   article > 正文

Django学习记录——管理员-登录注销的实现

Django学习记录——管理员-登录注销的实现

1.管理员案例

1.1管理员数据库

1.1.1 表结构

在这里插入图片描述

1.1.2 管理员表的建立

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

1.2 管理员数据的管理

1.2.1 管理员列表的展示

admin_list.html

{% extends 'layout.html' %}

{% block content %}
    <div style="margin-bottom: 10px">
        <a type="button" class="btn btn-success" href="/admin/add"><span class="glyphicon glyphicon-plus-sign"
                                                                         aria-hidden="true"></span> 添加管理员</a>
        <div style="width: 300px; float: right">

            <form method="get" action="/admin/list">
                <div class="input-group">
                    <input type="text" class="form-control" name="username" placeholder="Search for..."
                           value="{{ username }}">
                    <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">管理员列表</div>
        <div class="bs-example" data-example-id="hoverable-table">
            <table class="table table-hover">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for obj in queryset %}
                    <tr>
                        <td>{{ obj.id }}</td>
                        <td>{{ obj.username }}</td>
                        <td>******</td>
                        <td>
                            <a class="btn btn-success" href="/admin/{{ obj.id }}/reset_password">重置密码</a>
                            <a class="btn btn-warning btn-sm" href="/admin/{{ obj.id }}/update">编辑</a>
                            <a class="btn btn-danger btn-sm" href="/admin/{{ obj.id }}/del">删除</a>
                        </td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
        <ul class="pagination">
            {{ page_str }}
        </ul>

    </div>
{% endblock %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

admin.py

def admin_list(request):
    """管理员列表"""
    # 搜索
    data_dict = {}
    username = request.GET.get('username', "")
    if username:
        # {关键字: 范围, }
        data_dict['username__contains'] = username
    # data_dict = {'username__contains': 'Y'}
    queryset = models.Admin.objects.filter(**data_dict)

    # 分页功能
    page_object = Pagination(request, queryset)
    context = {
        "queryset": page_object.query_set,
        "page_str": page_object.createHtml()
    }
    return render(request, "admin_list.html", context)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 搜索功能

{关键字:范围} 利用字典作为搜索的条件集合

# 搜索
    data_dict = {}
    username = request.GET.get('username', "")
    if username:
        # {关键字: 范围, }
        data_dict['username__contains'] = username
    # data_dict = {'username__contains': 'Y'}
    queryset = models.Admin.objects.filter(**data_dict)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 分页功能
page_object = Pagination(request, queryset)
context = {
    "queryset": page_object.query_set,
    "page_str": page_object.createHtml()
}
  • 1
  • 2
  • 3
  • 4
  • 5

1.2.2 管理员的添加

admin.py

def admin_add(request):
    if request.method == "GET":
        form = adminModelForm()
        return render(request, "admin_add.html", {"form": form})
    else:
        form = adminModelForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("/admin/list")
        else:
            return render(request, "admin_add.html", {"form": form})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

form = adminModelForm()adminModelForm()中创建表单的样式并且规定一些校验规则

  • 表单样式创建
class adminModelForm(BootStrapModelForm):
    # 新增一个确认密码字段
    confirm_password = forms.CharField(
        max_length=64,
        label='确认密码',
        widget=forms.PasswordInput  # 密码格式
        # 加上后,若校验失败,密码不会清空
        # widget=forms.PasswordInput(render_value=True)
    )

    class Meta:
        model = models.Admin
        fields = ['username', 'password', 'confirm_password']
        # 额外给password增加密码输入框的样式
        widgets = {
            'password': forms.PasswordInput
            # 加上后,若校验失败,密码不会清空
            # widget=forms.PasswordInput(render_value=True)
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
class BootStrapModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for key, value in self.fields.items():
            value.widget.attrs = {'class': 'form-control', "placeholder": value.label}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 新建的管理员名称不允许重复(钩子函数实现
def clean_username(self):
    username = self.cleaned_data.get('username')
    # 判断管理员是否存在
    flag = models.Admin.objects.filter(username=username).exists()
    if flag:
        raise ValidationError("该管理员已存在")
    return username
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 确认两次输入密码一致

    • md5的应用

    encrypt.py

    def md5(data_string):
        # 导入django自带的sault
        obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
        obj.update(data_string.encode('utf-8'))
        return obj.hexdigest()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 钩子函数实现判断规则
    # 钩子函数对密码进行md5加密
    def clean_password(self):
        password = self.cleaned_data.get('password')
        return md5(password)  # md5加密并password返回该值
    
    # 钩子函数校验确认密码
    def clean_confirm_password(self):
        # txt_password 表示用户输入的md5加密完的密码
        # txt_confirm_password 表示用户输入的确认密码
        txt_password = self.cleaned_data.get('password')
        txt_confirm_password = self.cleaned_data.get('confirm_password')
        # md5_confirm_password 表示用户输入的md5加密后的确认密码
        md5_confirm_password = md5(txt_confirm_password)
    
        if txt_password == md5_confirm_password:
            # 校验通过(密码一致),返回输入的数据
            # 对于此案例,不保存confirm_password字段,因此用处不大
            return md5_confirm_password
        else:
            raise ValidationError("密码不一致")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

forms.py

class adminModelForm(BootStrapModelForm):
    # 新增一个确认密码字段
    confirm_password = forms.CharField(
        max_length=64,
        label='确认密码',
        widget=forms.PasswordInput  # 密码格式
        # 加上后,若校验失败,密码不会清空
        # widget=forms.PasswordInput(render_value=True)
    )

    class Meta:
        model = models.Admin
        fields = ['username', 'password', 'confirm_password']
        # 额外给password增加密码输入框的样式
        widgets = {
            'password': forms.PasswordInput
            # 加上后,若校验失败,密码不会清空
            # widget=forms.PasswordInput(render_value=True)
        }
    def clean_username(self):
        username = self.cleaned_data.get('username')
        # 判断管理员是否存在
        flag = models.Admin.objects.filter(username=username).exists()
        if flag:
            raise ValidationError("该管理员已存在")
        return username

    # 钩子函数对密码进行md5加密
    def clean_password(self):
        password = self.cleaned_data.get('password')
        return md5(password)  # md5加密并password返回该值

    # 钩子函数校验确认密码
    def clean_confirm_password(self):
        # txt_password 表示用户输入的md5加密完的密码
        # txt_confirm_password 表示用户输入的确认密码
        txt_password = self.cleaned_data.get('password')
        txt_confirm_password = self.cleaned_data.get('confirm_password')
        # md5_confirm_password 表示用户输入的md5加密后的确认密码
        md5_confirm_password = md5(txt_confirm_password)

        if txt_password == md5_confirm_password:
            # 校验通过(密码一致),返回输入的数据
            # 对于此案例,不保存confirm_password字段,因此用处不大
            return md5_confirm_password
        else:
            raise ValidationError("密码不一致")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

1.2.3管理员的删除

def admin_del(request, nid):
    models.Admin.objects.filter(id=nid).delete()
    return redirect("/admin/list")
  • 1
  • 2
  • 3

1.2.4 管理员的更新

views.py

def admin_update(request, nid):
    row_object = models.Admin.objects.filter(id=nid).first()
    # 先判断删除的id数据库是否存在
    if not row_object:
        return redirect("/admin/list")
    title = "管理员编辑"
    if request.method == "GET":
        # instance=row_object将查询到待修改的数据填充到表单
        form = admin_editModelForm(instance=row_object)

        return render(request, "change.html", {"form": form, "title": title})
    # instance=row_object 更新后,覆盖原有数据
    form = admin_editModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        form.save()
        return redirect("/admin/list")
    return render(request, "change.html", {"form": form, "title": title})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.2.5 管理员密码的重置

views.py

def admin_reset_password(request, nid):
    row_object = models.Admin.objects.filter(id=nid).first()
    title = "重置密码--{}".format(row_object.username)
    if not row_object:
        return redirect("/admin/list")
    if request.method == "GET":
        form = admin_reset_password_ModelForm()
        return render(request, "change.html", {"form": form, "title": title})

    form = admin_reset_password_ModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        form.save()
        return redirect("/admin/list")
    return render(request, "change.html", {"form": form, "title": title})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

校验规则(钩子函数)

admin_reset_password_ModelForm中实现

  • 重置的密码不能与原来密码一致
def clean_password(self):
    password = self.cleaned_data.get('password')
    MD5_pwd = md5(password)
    #  form = admin_reset_password_ModelForm(data=request.POST, instance=row_object)
    # 此句话为调用admin_reset_password_ModelForm返回的表单
    # 利用 self.instance.pk 可以获取到row_object该行数据的id
    # 判断该id对应的密码是否为输入的密码(比较的是加密完的密码)
    result = models.Admin.objects.filter(id=self.instance.pk,password=MD5_pwd).exists()
    if result:
        raise ValidationError("不能与原密码一致!")
    return MD5_pwd  # md5加密并password返回该值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 两次输入的密码应一致
def clean_confirm_password(self):
    # txt_password 表示用户输入的md5加密完的密码
    # txt_confirm_password 表示用户输入的确认密码
    txt_password = self.cleaned_data.get('password')
    txt_confirm_password = self.cleaned_data.get('confirm_password')
    # md5_confirm_password 表示用户输入的md5加密后的确认密码
    md5_confirm_password = md5(txt_confirm_password)

    if txt_password == md5_confirm_password:
        # 校验通过(密码一致),返回输入的数据
        # 对于此案例,不保存confirm_password字段,因此用处不大
        return md5_confirm_password
    else:
        raise ValidationError("密码不一致")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

1.3 更新页面的整合

各个部分的更新页面都差不多,因此对页面进行整合为change.html

change.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.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.css' %}">
    <style>
        .navbar {
            border-radius: 0;
        }
    </style>
    {% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">管理系统</a>
        </div>

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <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="/number/list">靓号管理</a></li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">登录</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">admin<span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">个人中心</a></li>
                        <li><a href="#">设置</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">注销</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>
<div class="container">

    <div class="panel panel-default">
        <div class="panel-heading">{{ title }}</div>
        <div class="panel-body">
            <div class="bs-example" data-example-id="simple-form-inline">
                <form class="form" method="post" novalidate>
                    {% csrf_token %}
                    {#创建表单,form为userinfo各字段的表单#}
                    {#每一个field都是一个字段的输入框#}

                    {% for field in form %}
                        <div class="form-group">
                            <label>{{ field.label }}</label>
                            {{ field }}
                            <span style="color: red">{{ field.errors.0 }}</span>
                            {# field.errors.0显示第一条错误即可 #}
                        </div>
                    {% endfor %}
                    <input type="submit" class="btn btn-success" value="提交">

                </form>
            </div>
        </div>
    </div>

</div>

<script src="{% static 'js/jquery-3.7.1.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datepicker.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script>


<script>
    $(function () {
        $('#dt_creat_time').datepicker({
            format: 'yyyy-mm-dd',
            //startDate: '0',//最早日期为当前日期,无法wangqian
            language: "zh-CN",
            autoclose: true
        });

    })
</script>

{% block js %}{% endblock %}
</body>
</html>

{% block content %}

{% endblock %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

固定参数

  • 表单标题
<div class="panel-heading">{{ title }}</div>
  • 1

从每个操作的视图函数中传进来

  • 表单
    form为view函数创建好的表单标签(包含input输入框、样式、value值)
<div class="panel-body">
            <div class="bs-example" data-example-id="simple-form-inline">
                <form class="form" method="post" novalidate>
                    {% csrf_token %}
                    {#创建表单,form为userinfo各字段的表单#}
                    {#每一个field都是一个字段的输入框#}

                    {% for field in form %}
                        <div class="form-group">
                            <label>{{ field.label }}</label>
                            {{ field }}
                            <span style="color: red">{{ field.errors.0 }}</span>
                            {# field.errors.0显示第一条错误即可 #}
                        </div>
                    {% endfor %}
                    <input type="submit" class="btn btn-success" value="提交">

                </form>
            </div>
        </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • form标签中的action

    对于form中的action关键字,指定了表单传数据的目的地,若不写默认form变量的来源位置

    使用默认位置,不仅简便,而且便于整合,不用额外在action中拼接nid

2. 登录页面

2.1登陆功能的实现

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.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.css' %}">
    <style>
        .login {
            position: fixed;
            width: 400px;
            height: 300px;
            border: 1px solid #adadad;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            padding: 20px 10px;
            box-shadow: 2px 2px 5px #8c8c8c;
        }

        .font {
            text-align: center;
        }
    </style>
</head>
<body>
<div class="login">
    <form method="post">
        {% csrf_token %}
        <div class="font"><h1>用户登录</h1></div>
        <div class="form-group">
            {% for obj in form %}
                <label>{{ obj.label }}</label>
                {{ obj }}
                <span style="color: red">{{ obj.errors.0 }}</span>
            {% endfor %}
        </div>
        <button type="submit" class="btn btn-success">登录</button>
    </form>
</div>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

login.py

def login(request):
    if request.method == 'GET':
        form = login_ModelForm()
        return render(request, 'login.html', {'form': form})
    form = login_ModelForm(request.POST)
    # 将输入的用户名密码与数据库的用户名密码进行比对
    if form.is_valid():
        dic = form.cleaned_data
        # 判断用户名密码是否正确(是否查到)
        admin_object = models.Admin.objects.filter(**dic).first()
        if not admin_object:
            # 在form中添加错误(错误地方,错误)   '
            # 将错误信息展示到密码上
            form.add_error("password", "用户名或密码错误")
            return render(request, 'login.html', {'form': form})
        # 验证成功——开始cookie验证
        # request.session['info'] = admin_object.username
        # 以下这句话,完成了以下功能
        """
        1.生成cookie存储到浏览器中
        2.将cookie和浏览器的信息存储到session中(django存到数据库中)
            session主要存储   key(cookie)   data(根据下面数据形成的)  日期等等
        """
        request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
        return redirect('/admin/list')
    return render(request, 'login.html', {'form': form})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • login_ModelForm()表单类
    BootStrapModelForm 对表单的样式进行更改
    实现了表单的建立和对输入密码进入md5加密,便于与数据库比较
class login_ModelForm(BootStrapModelForm):
  class Meta:
      model = models.Admin
      fields = ['username', 'password']

  # 将输入的密码进行md5,加密
  def clean_password(self):
      password = self.cleaned_data.get('password')
      MD5_pwd = md5(password)
      return MD5_pwd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 获取表单数据的方法

  • form.cleaned_data.get(xxx)

    该方法能获取对应name的数据,返回一个字符串

username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
res = models.Admin.objects.filter(username=username,password=password).first()
  • 1
  • 2
  • 3
  • form.cleaned_data

    该方法返回表单中所有的输入数据,将name与数据以字典的形式对应返回

# 返回值的字典形式可以直接传入查询数据库的条件
dic = form.cleaned_data
# 判断用户名密码是否正确(是否查到)
admin_object = models.Admin.objects.filter(**dic).first()
  • 1
  • 2
  • 3
  • 4
  • 错误信息的提示

    对于用户名与密码不匹配的情况,要显示错误提示并重新登录

    • 判断过程中需要获取输入的数据以及识别失败返回登陆页面,因此需要在视图函数中判断,而不能在钩子函数实现(不匹配时需返回登陆页面)
    • 判断表单是否返回数据(表单是否为空)
admin_object = models.Admin.objects.filter(**dic).first()
if not admin_object:
   form.add_error("password", "用户名或密码错误")
   return render(request, 'login.html', {'form': form})
request.session['info']={'id':admin_object.id,'username': admin_object.username}
  • 1
  • 2
  • 3
  • 4
  • 5

form.add_error("password", "用户名或密码错误") 参数1表示错误信息显示的位置 参数2表示显示的错误信息 该语句新增错误信息到form的errors中,便于提示错误信息

使用位置login.html

<div class="login">
   <form method="post">
       {% csrf_token %}
       <div class="font"><h1>用户登录</h1></div>
       <div class="form-group">
           {% for obj in form %}
               <label>{{ obj.label }}</label>
               {{ obj }}
               <span style="color: red">{{ obj.errors.0 }}</span>
           {% endfor %}
       </div>
       <button type="submit" class="btn btn-success">登录</button>
   </form>
</div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2 cookie与session

2.2.1 基本介绍

  • 短连接
http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/
  • 1
  • 2

对于http来说,建立无状态短连接(浏览器发送请求,后端网页接收请求,并响应请求,完成这个过程后断开连接)

在这里插入图片描述

实现浏览器与后端网页长久通信

​ 当浏览器向后端网页发送请求,后端网页向浏览器发送一个响应的过程中,后端网页给该浏览器生成一个为一个标识码**(字典的形式)来代表该浏览器,该标识码存储在该浏览器内,这个标识码就叫做cookie**;在后端网页给浏览器生成发送标识码时,也将该标识码与对应的浏览器存储在后端网页的session中;在之后的连接中,浏览器直接向后端网页发送cookie标识,在后端网页的session中查询是否存在,从而确定是否连接。

在这里插入图片描述

  • cookie:随机字符串(用来标识)
  • session:后端网页存储用户信息的部分,主要形式是数据库、redis、文件(django主要是数据库形式)
def login(request):
    if request.method == 'GET':
        form = login_ModelForm()
        return render(request, 'login.html', {'form': form})
    form = login_ModelForm(request.POST)
    if form.is_valid():件
        dic = form.cleaned_data
        # 判断用户名密码是否正确(是否查到)
        admin_object = models.Admin.objects.filter(**dic).first()
        if not admin_object:
            form.add_error("password", "用户名或密码错误")
            return render(request, 'login.html', {'form': form})
        request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
        return redirect('/admin/list')
    return render(request, 'login.html', {'form': form})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当用户名与密码匹配成功后,实现浏览器与后端网页长久通信(验证cookie)

request.session['info']={'id': admin_object.id, 'username': admin_object.username}

以下这句话,完成了以下功能:

  1. 将后端网页生成cookie存储到浏览器中

  2. 将cookie和浏览器的信息存储到session中(django存到数据库中)、

session主要存储 key(cookie) data(根据下面数据形成的) 日期等等

生成的cookie存储到session的cookie部分
信息info 存储对session对应的data部分
  • 1
  • 2

2.2.2基本应用

# 验证成功,生成cookie和session,从session获取
# request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
info_dic = request.session.get('info')
# 若未登录,则info_dic为空  若登录,则info_dic为传入的数据
if not info_dic:
	return redirect('/login/')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在每一个视图函数中都加入该语句,直接视图函数时,判断是否有cookie(是否登录)

  • 若已登录,则对应浏览器生成cookie并且存取相应的数据信息到session中,根据获取某个浏览器在session中的数据来判断是否处于登录状态
  • 若未登录,直接跳转到登录页,不再继续执行视图函数

缺点

每一个视图函数均需要增添这些语句,非常冗余

2.2.3 中间件

2.2.3.1 中间件的原理

在这里插入图片描述

  • 中间件=django中的类

书写中间件(类的位置):app01-middleware-auth.py

引用的Django类:from django.utils.deprecation import MiddlewareMixin

中间件的注册:setting.py 中的MIDDLEWARE添加'app01.middleware.auth.M1'

  • 浏览器向django发送请求时,需要依次经过多个中间件后,视图函数才可以接收请求.请求顺序:浏览器-M1-M2-M3-视图函数

  • 视图函数响应请求时,也是依次经过中间件后,浏览器才可以接收到响应。响应顺序:视图函数-M3-M2-M1-浏览器

auth.py

class M1(MiddlewareMixin):
def process_request(self, request):
  print("m1-process_request")
  return 
  # return HttpResponse("无权访问")

def process_response(self, request, response):
  print("m1-process_response")
  return response


class M2(MiddlewareMixin):
def process_request(self, request):
  print("m2-process_request")
  return

def process_response(self, request, response):
  print("m2-process_response")
  return response

class M3(MiddlewareMixin):
def process_request(self, request):
  print("m3-process_request")
  return

def process_response(self, request, response):
  print("m3-process_response")
  return response
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

请求与响应顺序:

m1-process_request
m2-process_request
m3-process_request
m3-process_response
m2-process_response
m1-process_response
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 若在依次请求中间件时,某个中间件直接返回响应,则其他中间件不在执行,直接响应浏览器

    用于实现登录校验,符合无返回值,不符合有返回值

    • 如果方法中没有返回值(返回None),继续向后走
    • 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行
class M1(MiddlewareMixin):
 def process_request(self, request):
     print("m1-process_request")
     return HttpResponse("无权访问")

 def process_response(self, request, response):
     print("m1-process_response")
     return response


class M2(MiddlewareMixin):
 def process_request(self, request):
     print("m2-process_request")
     return

 def process_response(self, request, response):
     print("m2-process_response")
     return response


class M3(MiddlewareMixin):
 def process_request(self, request):
     print("m3-process_request")
     return

 def process_response(self, request, response):
     print("m3-process_response")
     return response
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
m1-process_request
m1-process_response

中间件M1直接return HttpResponse("无权访问")  则不再执行其他中间,直接响应浏览器
  • 1
  • 2
  • 3
  • 4
2.2.3.2 中间件的登录校验
  • 中间件的设计
模板:
class AuthMiddleware(MiddlewareMixin):
    def process_request(self, request):
        ...
        ...
        ...
        return 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

auth.py

class AuthMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 1.中间件只能检验出登陆视图函数的其他函数
        # request.path_info 获取当前用户请求的url  判断是否为登录视图的url
        # 如果是,不进行任何检验,直接进入登陆页面
        print(request.path_info)
        if request.path_info == '/login/':
            
        info_dic = request.session.get('info')
        if info_dic:
            return
        return redirect('/login')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 中间件只能检验出登陆视图函数的其他函数
  • request.path_info 获取当前用户请求的url 判断是否为登录视图的url
    如果是登陆页面,不进行任何检验,直接进入登陆页面
  • 如果不是登录页面,则需要判断是否有登录信息
  1. 获取生成cookie时存的数据
  • 获取生成cookie时存的数据
  • 校验失败,直接返回,不允许进入视图函数

2.3 用户注销功能

删除存储的session信息,并返回登录页面即可

login.py

def logout(request):
    # 注销就是清除clear
    request.session.clear()
    return redirect('/login')
  • 1
  • 2
  • 3
  • 4

layout.html

...
...
...
<body>
<nav class="navbar navbar-default">

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <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="/number/list">靓号管理</a></li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">{{ request.session.info.username }}<span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">个人中心</a></li>
                        <li><a href="#">设置</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="/logout">注销</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>
...
...
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

{{ request.session.info.username }}用户的登录信息存储在session中

来源:login视图函数中request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}

2.4 图片验证码功能

2.4.1 python生成图片

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():
        # ASCII码对应字母
        return chr(random.randint(65, 90))

    # 生成随机颜色
    def rndColor():
        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__':
    # check_code()返回一张图片以及生成的随机字母
    img, code_str = check_code()
    print(code_str)
    # 保存图片
    with open('code.png', 'wb') as f:
        img.save(f, format='png')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

2.4.2 验证码输入框与验证码图片在django项目的实现

  • 在登录的表单中添加验证码文本框
class login_ModelForm(BootStrapModelForm):
    password = forms.CharField(widget=forms.PasswordInput, label='密码')
    # 新增
    code = forms.CharField(widget=forms.TextInput, label='验证码', )

    class Meta:
        model = models.Admin
        # 新增
        fields = ['username', 'password', 'code']

    # 将输入的密码进行md5,加密
    def clean_password(self):
        password = self.cleaned_data.get('password')
        MD5_pwd = md5(password)
        return MD5_pwd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 登陆页面login.html
...
...
...

<div class="form-group">
    <label>{{ form.code.label }}</label>
    <div class="row">
        <div class="col-xs-7">
            {{ form.code }}
            <span style="color: red;font-size: 10px">{{ form.code.errors.0 }}</span>
        </div>
        <div class="col-xs-5">
            {#图片的地址直接转到生成图片的视图函数中#}
            {#特别注意:cookie验证时不仅要排除登录视图函数,还得排除生成图片视图函数#}
            <img id="img_code" src="/image/code" style="width: 125px">
        </div>
    </div>
</div>
...
...
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 图片验证码的增加

    • 生成随机的图片验证码

    app-utils-code.py

    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 chr(random.randint(65, 90))
    
        # 生成随机颜色
        def rndColor():
            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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    check_code()返回两个值

    img:<class 'PIL.Image.Image'>类型的图片

    code:图片验证码上的字符串

    • 图片验证码的显示
    <div class="col-xs-5">
    	<img id="img_code" src="/image/code" style="width: 125px">
    </div>
    
    • 1
    • 2
    • 3

    图片的地址采用视图函数来实现,image_code视图函数自动接收生成的图片,加载内存中传入html中

    def image_code(request):
        img, code_string = check_code()
        request.session['image_code'] = code_string
        # 给session设置60s超时,超过60s,自动无效
        request.session.set_expiry(60)
        # 向内存传入图片
        stream = BytesIO()
        img.save(stream, 'png')
        return HttpResponse(stream.getvalue())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 要点1

    img, code_string = check_code()调用pillow函数生成图片

    img:<class 'PIL.Image.Image'>类型的图片

    code_string为图片上的文字,用于校验

    1. 要点2

    request.session['image_code'] = code_string

    验证码的校验利用session 将图片码写入自己的session中,以便于后续获取验证码进行校验
    在session存储的数据也加一条 {image_code:code_string}

    1. 要点3

    request.session.set_expiry(60)给session设置60s超时,超过60s,自动无效

    1. 要点4
    stream = BytesIO()
    img.save(stream, 'png')
    return HttpResponse(stream.getvalue())
    
    • 1
    • 2
    • 3

    向内存中传输图片,传入html中显示

图片的地址直接转到生成图片的视图函数中,特别注意:生成图片的视图函数也不需要cookie验证,cookie验证时不仅要排除登录视图函数,还得排除生成图片视图函数

auth.py

class AuthMiddleware(MiddlewareMixin):
    def process_request(self, request)
        if request.path_info in ['/login/','/image/code']:
            return
        # 2.获取生成cookie时存的数据
        info_dic = request.session.get('info')
        # 校验成功,允许进入视图函数
        if info_dic:
            return
        # 校验失败,直接返回,不允许进入视图函数
        return redirect('/login')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.4.3 图片验证码的校验

已知在生成图片验证码时已经将图片的中的随机验证码字符串传入request.session['image_code'] = code_string,因此request.session中多存储一条code_string的信息。

如此,用户输入完验证码时,通过form.cleaned_data获取前端表单输入的数据,取出图片验证码code与session中的code_string进行对比即可

def login(request):
    if request.method == 'GET':
        form = login_ModelForm()
        return render(request, 'login.html', {'form': form})
    form = login_ModelForm(request.POST)
    
    if form.is_valid():
        # 用户输入的code 并且取出form.cleaned_data中的code,此时form.cleaned_data仅有username和password信息,使用户名和密码可以校验
        in_code= form.cleaned_data.pop("code")
        # 从session提取图片字母
        image_code = request.session.get('image_code','')
        
        # 判断验证码
        if not (in_code.upper()==image_code):
            # 在form中添加错误(错误地方,错误)   '
            # 将错误信息展示到密码上
            form.add_error("code", "验证码错误")
            return render(request, 'login.html', {'form': form})
        
        # 判断用户名密码是否正确(是否查到)
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error("password", "用户名或密码错误")
            return render(request, 'login.html', {'form': form})


        request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
        # cookie 保存7天
        request.session.set_expiry(60*60*60*7)
        return redirect('/admin/list')
    return render(request, 'login.html', {'form': form})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/173836
推荐阅读
相关标签
  

闽ICP备14008679号