Django项目实战(一):图书管理系统---第四阶段---基于用户认证组件(登录功能+修改密码)

下面是小凰凰的简介,看下吧!
💗人生态度:珍惜时间,渴望学习,热爱音乐,把握命运,享受生活
💗学习技能:网络 -> 云计算运维 -> python全栈( 当前正在学习中)
💗您的点赞、收藏、关注是对博主创作的最大鼓励,在此谢过!
有相关技能问题可以写在下方评论区,我们一起学习,一起进步。
后期会不断更新python全栈学习笔记,秉着质量博文为原则,写好每一篇博文。

一、效果展示

1、登录功能

在这里插入图片描述
由于图片大小限制只能做这么大的动图了!

2、修改密码功能

在这里插入图片描述密码修改之后,会自动强制退出当前页面!

相比第三阶段改进点添加登录功能、修改密码功能,自定义中间件实现未登录用户不准进入除login外其他页面!

项目不足之处:
未实现注册功能(这个需要结合forms组件来完成因此没有在这里一起实现),作者信息、书籍信息、出版社信息没有实现分页浏览未支持事务

二、项目目录展示

在这里插入图片描述中间那个static里面放的是整个项目所需要的一些静态文件!

三、项目源码

下面我只写变动的文件!

1、views.py
from django.shortcuts import render
from app01.models import Book, Publisher, Authors, AuthorDetail
from django.shortcuts import redirect, reverse
from django.http import HttpResponse
from django.contrib import auth
import json


# Create your views here.
def book_view(request):
    book_list = Book.objects.all()
    return render(request, "index.html", {'book_list': book_list, 'username': request.user})


def author_view(request):
    author_list = Authors.objects.all()
    return render(request, 'author_view.html', {'author_list': author_list, 'username': request.user})



def publisher_view(request):
    publisher_list = Publisher.objects.all()
    return render(request, 'publisher_view.html', {'publisher_list': publisher_list, 'username': request.user})

# 上面三个函数的return都添加了{'username': request.user},为了把username通过模板语法渲染到三个视图对应的界面上!

# 新加了两个函数
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        json_dict = json.loads(request.body.decode("utf-8"))
        user = auth.authenticate(username=json_dict['login'], password=json_dict['pwd'])
        if user:
            auth.login(request, user)
            return HttpResponse('ok')
        else:
            return HttpResponse('No')


def secret_modify(request):
    old_secret = request.POST.get('old_secret')
    new_secret = request.POST.get('new_secret')
    is_pwd = request.user.check_password(old_secret)
    if is_pwd:
        request.user.set_password(new_secret)
        request.user.save()
        return HttpResponse('ok')
    else:
        return HttpResponse('No')
2、middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect, reverse


class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
        if request.path == '/login/':
            return None
        else:
            is_auth = request.user.is_authenticated
            if not is_auth:
                return redirect(reverse('login_view'))
3、urls.py
# 添加了这两条路由!
path('login/', views.login, name='login_view'),
path('secret_mdf/', views.secret_modify, name='secret_modify'),
4、模板
(1)index.html

加了些样式,加了一个修改密码的模态框!并为修改密码添加了两个事件!

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% block page_title %}
        <title>图书管理系统</title>
    {% endblock %}
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        .table_area {
            width: 800px;
            position: absolute;
            top: 150px;
            left: 250px;
        }

        .add_btn {
            font-size: 18px;
            font-family: "Kaiti SC";
            font-weight: bolder;
        }

        .sys-menu {
            position: relative;
            top: auto;
            left: 35px;
            width: 150px;
            height: 200px;
        }

        .page_top {
            background-color: #146896;
            width: 100%;
            height: 50px;
        }

        .panel-heading {
            text-align: center;
            font-weight: bolder;
        }

        .panel-body {
            text-align: center;
            width: 100%;
        }

        .panel-body > a {
            text-decoration: none;
        }

        .panel-body > a:hover {
            color: skyblue;
        }

        .active {
            background-color: blanchedalmond;
        }

        #exampleModalLabel {
            font-family: "Kaiti SC";
            font-size: 20px;
            font-weight: bolder;
        }

        .login-area {
            margin-right: 20px;
        }

        .modify-key, .user-name, .head-img {
            float: right;
            color: skyblue;
            margin-right: 15px;
        }

        .modify-key:hover {
            color: red;
            text-decoration: none;
            cursor: pointer;
        }

        .page_top > div {
            position: relative;
            top: 15px;
        }

        .head-img {
            width: 45px;
            height: 45px;
            border-radius: 45px;
            overflow: hidden;
            position: relative;
            top: -12px;
            border: 2px solid white;
        }

        .user-name {
            font-family: "Kaiti SC";
            font-weight: bolder;
        }

        .head-img > img {
            width: 100%;
        }

        #warning-key {
            color: red;
            font-family: "Kaiti SC";
        }

    </style>
</head>
<body>
<div class="page_top">
    <div class="login-area">
        <a type="button" class=" modify-key" data-toggle="modal" data-target="#exampleModal"
           data-whatever="@mdo">修改密码
        </a>

        <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                                aria-hidden="true">&times;</span></button>
                        <h4 class="modal-title" id="exampleModalLabel">密码修改</h4>
                    </div>
                    <div class="modal-body">
                        <form>
                            <div class="form-group">
                                <label for="old_secret" class="control-label">原密码</label>
                                <input type="password" class="form-control" id="old_secret">
                            </div>
                            <div class="form-group">
                                <label for="new_secret" class="control-label">新密码</label>
                                <input type="password" class="form-control" id="new_secret">
                            </div>
                            <div class="form-group">
                                <label for="re_new_secret" class="control-label">
                                    再次确认新密码&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span id="warning-key"></span>
                                </label>
                                <input type="password" class="form-control" id="re_new_secret">
                            </div>
                            <div class="modal-footer">
                                <button id="sub-mdkey" type="button" class="btn btn-primary login-btn" data-dismiss="modal">提交</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <span class="user-name">{{ username }}</span>
        <div class="head-img">
            <img src="http://127.0.0.1:8000/static/img/head-img.jpg" alt="">
        </div>
    </div>
</div>

<div class="panel panel-default sys-menu">
    <div class="panel-heading">
        功能系统
    </div>
    {% block sys_view %}
        <div class="panel-body active">
            <a href="{% url 'book_view' %}">图书管理系统</a>
        </div>
        <div class="panel-body">
            <a href="{% url 'author_view' %}">作者管理系统</a>
        </div>
        <div class="panel-body">
            <a href="{% url 'publisher_view' %}">出版社管理系统</a>
        </div>
    {% endblock %}
</div>

{% block master_content %}
    <div class="table_area">
        {% csrf_token %}
        <table class="table table-hover">
            <thead>
            <tr>
                <th>书籍编号</th>
                <th>书名</th>
                <th>价格</th>
                <th>出版时间</th>
                <th>出版社</th>
                <th>作者</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for book_obj in book_list %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ book_obj.name }}</td>
                    <td>{{ book_obj.price }}</td>
                    <td>{{ book_obj.pub_date|date:'Y-m-d' }}</td>
                    <td>{{ book_obj.publish.name }}</td>
                    <td>
                        {% for author_obj in book_obj.authors.all %} <!--book_obj.authors.all得到的是一个作者对象的queryset-->
                            {% if forloop.last %}
                                <!--
                                    有人会问这里不是对象吗?为什么打印对象?不应该是author_obj.name吗?
                                    我在model中写了双下方法str-->
                                {{ author_obj }}
                            {% else %}
                                {{ author_obj }},
                            {% endif %}

                        {% endfor %}
                    </td>
                    <td>
                        <a href="{% url 'book_edit' book_obj.pk %}" class="btn btn-warning btn-xs">编辑</a>
                        <!-- 删除我们应该使用ajax进行局部刷新,不应该和编辑一样使用页面刷新 -->
                        <a id="del_book" class="btn btn-danger btn-xs" book_id={{ book_obj.pk }}>删除</a>
                    </td>
                </tr>
                {% if forloop.last and forloop.counter > 0 %}
                    <tr>
                        <td colspan="7">
                            <a href="{% url 'book_add' %}" class="btn btn-info btn-xs form-control add_btn">添加书籍</a>
                        </td>
                    </tr>
                {% endif %}
            {% empty %}
                <tr align="center">
                    <td colspan="7">
                        暂未上架任何书籍... <br>
                        <span>请点击:<a href="{% url 'book_add' %}" class="btn btn-info btn-xs">添加书籍</a></span>
                    </td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}

</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
{% block script_area %}
    <script>
        $(function () {
            $("tbody").on('click', '#del_book', function () {
                let $ele = $(this).parent().parent()
                $.ajax({
                    url: `/books/del/${$(this).attr('book_id')}/`,
                    type: "post",
                    data: {
                        csrfmiddlewaretoken: $("input[name='csrfmiddlewaretoken']").val()
                    },
                    success: function (response) {
                        response = JSON.parse(response)
                        if (response["status"] == true) {
                            $ele.remove()
                        } else {
                            alert('书籍删除失败!请重试')
                        }
                    }
                })
            })
            
            # 两次新密码验证前端做了就行了,不用再发到后端验证
            $('#re_new_secret').blur(function () {
                let re_val = $(this).val()
                let val = $('#new_secret').val()
                if (re_val != val) {
                    $('#warning-key').text('两次密码输入不一致,请重新输入')
                    setTimeout(function () {
                        $('#warning-key').text('')
                    }, 3000)
                }
            })
            
            # 发送ajax请求
            $('.login-btn').click(function () {
                $.ajax({
                    url: '{% url 'secret_modify' %}',
                    type: 'post',
                    data: {
                        old_secret: $('#old_secret').val(),
                        new_secret: $('#new_secret').val()
                    },
                    success: function (response) {
                        if (response == 'ok') {
                            alert('密码修改成功!')
                            setTimeout(function () {
                                location.href = "{% url 'login_view' %}"
                            },3000)
                        } else {
                            alert('密码修改失败!')
                        }
                    }
                })
            })
        })
    </script>
{% endblock %}
</html>
(2)login.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <title>图书管理系统</title>
    <link href="../static/css/default.css" rel="stylesheet" type="text/css"/>
    <!--必要样式-->
    <link href="../static/css/styles.css" rel="stylesheet" type="text/css"/>
    <link href="../static/css/demo.css" rel="stylesheet" type="text/css"/>
    <link href="../static/css/loaders.css" rel="stylesheet" type="text/css"/>
    <style>
        .login-btn{
            float: right;
        }
    </style>
</head>
<body>
{% csrf_token %}
<div class='login'>
    <div class='login_title'>
        <span>管理员登录</span>
    </div>
    <div class='login_fields'>
        <div class='login_fields__user'>
            <div class='icon'>
                <img alt="" src='../static/img/user_icon_copy.png'>
            </div>
            <input name="login" placeholder='用户名' maxlength="16" type='text' autocomplete="off"/>
            <div class='validation'>
                <img alt="" src='../static/img/tick.png'>
            </div>
        </div>
        <div class='login_fields__password'>
            <div class='icon'>
                <img alt="" src='../static/img/lock_icon_copy.png'>
            </div>
            <input name="pwd" placeholder='密码' maxlength="16" type='text' autocomplete="off">
            <div class='validation'>
                <img alt="" src='../static/img/tick.png'>
            </div>
        </div>
        <div class='login_fields__password'>
            <div class='icon'>
                <img alt="" src='../static/img/key.png'>
            </div>
            <input name="code" placeholder='验证码' maxlength="4" type='text' name="ValidateNum" autocomplete="off">
            <div class='validation' style="opacity: 1; right: -5px;top: -3px;">
                <canvas class="J_codeimg" id="myCanvas" onclick="Code();">对不起,您的浏览器不支持canvas,请下载最新版浏览器!</canvas>
            </div>
        </div>
        <div class='login_fields__submit'>
            <input class="login-btn" type='button' value='登录'>
        </div>
    </div>
    <div class='success'>
    </div>
    <div class='disclaimer'>
        <p>欢迎登陆图书管理系统</p>
    </div>
</div>
<div class='authent'>
    <div class="loader" style="height: 44px;width: 44px;margin-left: 28px;">
        <div class="loader-inner ball-clip-rotate-multiple">
            <div></div>
            <div></div>
            <div></div>
        </div>
    </div>
    <p>认证中...</p>
</div>
<div class="OverWindows"></div>
<link href="../static/layui/css/layui.css" rel="stylesheet" type="text/css"/>
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script type="text/javascript" src="../static/js/jquery-ui.min.js"></script>
<script type="text/javascript" src='../static/js/stopExecutionOnTimeout.js?t=1'></script>
<script src="../static/layui/layui.js" type="text/javascript"></script>
<script src="../static/js/Particleground.js" type="text/javascript"></script>
<script src="../static/js/Treatment.js" type="text/javascript"></script>
<script src="../static/js/jquery.mockjax.js" type="text/javascript"></script>
<script src="../static/js/jquery.cookie.js"></script>
<script type="text/javascript">
    var canGetCookie = 0;//是否支持存储Cookie 0 不支持 1 支持
    var CodeVal = 0;
    Code();

    function Code() {
        if (canGetCookie == 1) {
            createCode("AdminCode");
            var AdminCode = getCookieValue("AdminCode");
            showCheck(AdminCode);
        } else {
            showCheck(createCode(""));
        }
    }

    function showCheck(a) {
        CodeVal = a;
        var c = document.getElementById("myCanvas");
        var ctx = c.getContext("2d");
        ctx.clearRect(0, 0, 1000, 1000);
        ctx.font = "80px 'Hiragino Sans GB'";
        ctx.fillStyle = "#E8DFE8";
        ctx.fillText(a, 0, 100);
    }

    $(document).keypress(function (e) {
        // 回车键事件
        if (e.which == 13) {
            $('input[type="button"]').click();
        }
    });
    //粒子背景特效
    $('body').particleground({
        dotColor: '#E8DFE8',
        lineColor: '#133b88'
    });
    $('input[name="pwd"]').focus(function () {
        $(this).attr('type', 'password');
    });
    $('input[type="text"]').focus(function () {
        $(this).prev().animate({'opacity': '1'}, 200);
    });
    $('input[type="text"],input[type="password"]').blur(function () {
        $(this).prev().animate({'opacity': '.5'}, 200);
    });
    $('input[name="login"],input[name="pwd"]').keyup(function () {
        var Len = $(this).val().length;
        if (!$(this).val() == '' && Len >= 5) {
            $(this).next().animate({
                'opacity': '1',
                'right': '30'
            }, 200);
        } else {
            $(this).next().animate({
                'opacity': '0',
                'right': '20'
            }, 200);
        }
    });
    var open = 0;
    layui.use('layer', function () {
        //非空验证
        $('input[type="button"]').click(function () {
            var login = $('input[name="login"]').val();
            var pwd = $('input[name="pwd"]').val();
            var code = $('input[name="code"]').val();
            if (login == '') {
                ErroAlert('请输入您的账号');
            } else if (pwd == '') {
                ErroAlert('请输入密码');
            } else if (code == '' || code.length != 4) {
                ErroAlert('输入验证码');
            } else if (code.toUpperCase() != CodeVal.toUpperCase()) {
                ErroAlert('验证码错误,请重新输入!');
            } else {
                //认证中..
                fullscreen();
                $('.login').addClass('test'); //倾斜特效
                setTimeout(function () {
                    $('.login').addClass('testtwo'); //平移特效
                }, 300);
                setTimeout(function () {
                    $('.authent').show().animate({right: -320}, {
                        easing: 'easeOutQuint',
                        duration: 600,
                        queue: false
                    });
                    $('.authent').animate({opacity: 1}, {
                        duration: 200,
                        queue: false
                    }).addClass('visible');
                }, 500);

                var JsonData = {login: login, pwd: pwd};
                $.ajax({
                    url: "{% url 'login_view' %}",
                    type: "post",
                    contentType: "json",
                    headers:{"X-CSRFToken":$.cookie('csrftoken')},
                    data: JSON.stringify(JsonData),
                    success: function (response) {
                        console.log(response)
                        if (response == 'ok') {
                            location.href = "{% url 'book_view' %}"
                        } else {
                            ErroAlert('用户名或密码错误,请重新输入!');
                            setTimeout(function () {
                                location.href = "{% url 'login_view' %}"
                            },2000)
                        }
                    }
                })
            }
        })
    })
    var fullscreen = function () {
        elem = document.body;
        if (elem.webkitRequestFullScreen) {
            elem.webkitRequestFullScreen();
        } else if (elem.mozRequestFullScreen) {
            elem.mozRequestFullScreen();
        } else if (elem.requestFullScreen) {
            elem.requestFullscreen();
        } else {
            //浏览器不支持全屏API或已被禁用
        }
    }

</script>
</body>
</html>

注:登录界面不是我写的,是在点击我这里找的!static里面除了img里的一个用户头像是我的,其他都是这个登录界面的样式和js、图片!

四、项目心得体会

1、静态文件引入

settings.py最下面加上:
在这里插入图片描述
然后你的static文件夹就可以这样放置文件!
在这里插入图片描述
上面做完之后有什么用呢?我们项目程序代码文件,用户是无法通过浏览器输入地址看到的,前端代码除外。但是像一些图片、css、js等,浏览器拿到你的html文件,会向href中定义的url发送请求你这些文件,但是通过url服务器是拒绝给予请求资源的。因此我们static就是开了一个口,这里面的文件用户通过浏览器是可以下载浏览的,这样也是方便我们引入这些静态文件!

2、ajax请求的json格式如何通过csrf校验

详情点我

我们这里只有login页面的登录提交请求采用了ajax的json格式,通过方法如下:

<script src="../static/js/jquery.cookie.js"></script>
<script>
	$.ajax({
	    url: "{% url 'login_view' %}",
	    type: "post",
	    contentType: "json",
	    headers:{"X-CSRFToken":$.cookie('csrftoken')},
	    data: JSON.stringify(JsonData),
	    success: function (response) {
	        console.log(response)
	        if (response == 'ok') {
	            location.href = "{% url 'book_view' %}"
	        } else {
	            ErroAlert('用户名或密码错误,请重新输入!');
	            setTimeout(function () {
	                location.href = "{% url 'login_view' %}"
	            },2000)
	        }
	    }
	})
</script>

我这里为了方便都采用的硬编码url,如果生产环境一定要使用{% static '' %}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凤求凰的博客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值