zoukankan      html  css  js  c++  java
  • Python学习之路—2018/7/11

    Python学习之路—2018/7/11

    3.功能开发

    3.1 登录验证

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>博客开发系统</title>
        <link rel="stylesheet" href="/static/blog/css/bootstrap.min.css">
        <link rel="icon" href="/static/blog/image/favicon.ico">
        <style type="text/css">
            body {
                background: url(../static/blog/image/bk.jpeg) no-repeat;
                background-size: 100%;
                overflow: hidden;
            }
            h3 {
                padding: 10px;
                border-bottom: 1px solid #ddd;
            }
        </style>
    </head>
    <body>
    <div class="row">
        <div class="col-md-4 col-md-offset-4" style="margin-top: 100px">
            <form>
                {% csrf_token %}
                <div class="well">
                    <h3>登录</h3>
                    <div class="form-group">
                        <label for="username">用户名</label>
                        <input type="text" class="form-control" id="username" placeholder="用户名" autocomplete="off">
                    </div>
                    <div class="form-group">
                        <label for="password">密码</label>
                        <input type="password" class="form-control" id="password" placeholder="密码" autocomplete="off">
                    </div>
                    <div class="row form-group">
                        <div class="col-md-6">
                            <label for="password">验证码</label>
                            <input type="text" class="form-control" id="verify_code" placeholder="请输入验证码" autocomplete="off">
                        </div>
                        <div class="col-md-6">
                            <label for="picture"></label>
                            <img src="/verify_code" style="height: 40px; 183px" id="verify_code_img">
                        </div>
                    </div>
                    <div class="form-group">
                        <span id="error"></span>
                        <input type="button" class="btn btn-success form-control login-btn" value="登录">
                    </div>
                </div>
            </form>
        </div>
    </div>
    </body>
    <script type="text/javascript" src="/static/blog/js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        $("#verify_code_img").click(function () {
            $(this)[0].src += "?"
        });
    
        $(".login-btn").click(function () {
            $.ajax({
                url: "/login/",
                type: "POST",
                data: {
                    "username": $("#username").val(),
                    "password": $("#password").val(),
                    "verify_code": $("#verify_code").val(),
                },
                success:function (data) {
                        if(data["user"]){
                            location.href = "/index/"
                        }
                        else {
                            $("#error").html(data["msg"]).css({"color": "red"})
                        }
                    }
            })
        })
    </script>
    </html>
    

    views.py

    from django.shortcuts import render, HttpResponse
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.contrib import auth
    from blog.utils.verify_code import *
    
    
    @csrf_exempt
    def login(request):
        if request.method == "GET":
            return render(request, "login.html")
        elif request.method == 'POST':
            username = request.POST.get("username")
            password = request.POST.get("password")
            verify_codes = request.POST.get("verify_code")
            verify_code_str = request.session.get("verify_code_str")
            response = {"user": None, "msg": None}
    
            if verify_codes.upper() == verify_code_str.upper():
                user = auth.authenticate(username=username, password=password)
                if user:
                    auth.login(request, user)
                    response["user"] = user.username
                else:
                    response["msg"] = "用户名或者密码错误!"
            else:
                response["msg"] = "验证码错误!"
    
            return JsonResponse(response)
    
    
    def verify_code(request):
        data = get_verify_code(request)
        return HttpResponse(data)
    
    
    def index(request):
        return render(request, "index.html")
    

    verify_code.py

    import random
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    from io import BytesIO
    
    
    def random_color():
        color = (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
        return color
    
    
    def random_color2():
        color = (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
        return color
    
    
    def random_char():
        """
        随机数/字母
        """
        random_num = str(random.randint(0, 9))
        random_low = chr(random.randint(97, 122))  # a~z
        random_upper = chr(random.randint(65, 90))  # A~Z
        random_chars = random.choice([random_num, random_low, random_upper])
        return random_chars
    
    
    def get_verify_code(request):
        """
        动态生成验证码
        """
        image = Image.new("RGB", (183, 40), (255, 255, 255))
        image_font = ImageFont.truetype("static/blog/font/Arial.ttf", 32)
        draw = ImageDraw.Draw(image)
    
        # 给每个坐标填充颜色,填充噪点
        for x in range(183):
            for y in range(40):
                draw.point((x, y), fill=random_color())
    
        verify_code_str = ""
        for i in range(5):
            random_chars = random_char()
            verify_code_str += random_chars
            draw.text((20+i*30, 0), random_chars, font=image_font, fill=random_color2())
        image = image.filter(ImageFilter.BLUR)  # 模糊处理
        
        # 放到磁盘中,但是速度比较慢,推荐放在内存中
        # with open("verify_code.png", "wb") as f:
        #     image.save(f)
        #
        # with open("verify_code.png", "rb") as f:
        #     data = f.read()
        
        request.session["verify_code_str"] = verify_code_str
        f = BytesIO()
        image.save(f, "png")
        data = f.getvalue()
    
        return data
    

    登录界面效果如下图所示:

    总结:

    1. 一次请求伴随着多次静态的请求,例如css文件的请求
    2. 利用PIL模块动态制作验证码
    3. 验证码使用session存储(session首先会生成一个sessionid,接着会在cookie中存储sessionid,最后想session表中插入数据,包括sessionid以及session内容,一个浏览器对应一条session数据)
    4. 验证码的刷新,每次点击验证功能图片时,在其src的末尾加上 ?,这样每次点击图片便会刷新验证码
    5. 遵循“高耦合低内聚”原则,将动态生成验证码的功能存放在另一个py文件中

    进阶-滑动验证码(geetest插件的使用)

    首先需要安装social-auth-app-django模块

    pip install social-auth-app-django
    

    接着从https://github.com/GeeTeam/gt-python-sdk/ 下载geetest.py ,将其放入项目中

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>博客开发系统</title>
        <link rel="stylesheet" href="/static/blog/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/blog/css/blog.css">
        <link rel="icon" href="/static/blog/image/favicon.ico">
        <style type="text/css">
            body {
                background: url(../static/blog/image/bk.jpeg) no-repeat;
                background-size: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
    <div class="row">
        <div class="col-md-4 col-md-offset-4" style="margin-top: 100px">
            <form>
                {% csrf_token %}
                <div class="well">
                    <div class="login-head">
                        {% block head %}
                            <h3 style="display: inline-block">登录</h3>
                            <a id="register">立即注册</a>
                        {% endblock head %}
                    </div>
                    {% block content %}}
                        <div class="form-group">
                            <label for="username">用户名</label>
                            <input type="text" class="form-control" id="username" placeholder="用户名" autocomplete="off">
                        </div>
                        <div class="form-group">
                            <label for="password">密码</label>
                            <input type="password" class="form-control" id="password" placeholder="密码" autocomplete="off">
                        </div>
                    {% endblock content %}
                    <div class="form-group">
                        <span id="error"></span>
                        {% block submit %}
                            <input type="button" class="btn btn-success form-control login-btn" value="登录">
                        {% endblock submit %}
                    </div>
                    <div id="popup-captcha"></div>
                </div>
            </form>
        </div>
    </div>
    </body>
    <script type="text/javascript" src="/static/blog/js/jquery-3.3.1.min.js"></script>
    <script src="https://static.geetest.com/static/tools/gt.js"></script>
    <script src="/static/blog/js/blog.js"></script>
    </html>
    

    blog.css

    a:hover {
        cursor: pointer;
        text-decoration: none;
    }
    
    .login-head {
        margin-bottom: 10px;
        border-bottom: 1px solid #ddd;
    }
    
    .login-head a {
        margin-left: 287px;
        color: #5cb85c;
    }
    

    blog.js

    var handlerPopup = function (captchaObj) {
        // 成功的回调
        captchaObj.onSuccess(function () {
            var validate = captchaObj.getValidate();
            $.ajax({
                url: "/login/", // 进行二次验证
                type: "post",
                dataType: "json",
                data: {
                    username: $('#username').val(),
                    password: $('#password').val(),
                    geetest_challenge: validate.geetest_challenge,
                    geetest_validate: validate.geetest_validate,
                    geetest_seccode: validate.geetest_seccode
                },
                success: function (data) {
                    if(data["user"]){
                        location.href = "/index/"
                    }
                    else {
                        $("#error").html(data["msg"]).css({"color": "red"})  //提示错误信息
                    }
                }
            });
        });
        $(".login-btn").click(function () {
            captchaObj.show();
        });
        // 将验证码加到id为captcha的元素里
        captchaObj.appendTo("#popup-captcha");
        // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
    };
    // 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
    $.ajax({
        url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
        type: "get",
        dataType: "json",
        success: function (data) {
            // 使用initGeetest接口
            // 参数1:配置参数
            // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
            initGeetest({
                gt: data.gt,
                challenge: data.challenge,
                product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
                // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
            }, handlerPopup);
        }
    });
    

    views.py

    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from blog.utils.geetest import GeetestLib
    from django.contrib import auth
    
    @csrf_exempt
    def login(request):
        if request.method == "GET":
            return render(request, "login.html")
        elif request.method == 'POST':
            # 滑动验证码,利用geetest插件
            gt = GeetestLib(pc_geetest_id, pc_geetest_key)
            status = request.session[gt.GT_STATUS_SESSION_KEY]
            username = request.POST.get("username")
            password = request.POST.get("password")
            response = {"user": None, "msg": None}
            if status:
                user = auth.authenticate(username=username, password=password)
                if user:
                    auth.login(request, user)
                    response["user"] = user.username
                else:
                    response["msg"] = "用户名或者密码错误!"
            return JsonResponse(response)
    
    

    展示效果:

    3.2 注册用户

    首先在项目中创建media文件夹,在media目录下创建avatars文件夹,这个文件主要存放用户上传的文件

    Django有两种静态文件,分别是static以及media,static主要存放js,css,img等服务器自己的文件,而media主要存放用户上传的文件。

    我们需要配置settings中的media信息MEDIA_ROOT = os.path.join(BASE_DIR, "media"),配置好以后每次用户上传的文件Django会存放到/media/avatars目录下,如果用户没有创建avatars目录,Django会自动创建该目录。其次需要配置MEDIA_URL = "/media/",并在urls.py中配置相关信息,这样我们便可以在浏览器中访问到media资源

    urls.py

    from django.views.static import serve
    urlpatterns = [
        re_path(r'media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
    ]
    

    settings.py

    MEDIA_ROOT = os.path.join(BASE_DIR, "media")
    MEDIA_URL = "/media/"
    

    register.html

    {% extends "login.html" %}
    {% block head %}
        <h3 style="text-align: center">注册</h3>
    {% endblock %}
    
    {% block content %}
        {% for foo in form %}
            <div class="form-group">
                <label for="{{ foo.auto_id }}">{{ foo.label }}</label>
                {{ foo }}
                <span class="error pull-right"></span>
            </div>
        {% endfor %}
        <div class="form-group">
            <label for="avatar">
                头像
                <img src="/static/blog/avatars/default.gif" id="avatar_img">
            </label>
            <input type="file" id="avatar" style="display: none">
        </div>
    {% endblock content %}
    
    {% block submit %}
        <input type="button" class="btn btn-success form-control" value="注册">
    {% endblock submit %}
    

    blog.css

    #avatar_img {
         60px;
        height: 60px;
        margin-left: 20px;
    }
    .error{
        color: red;
    }
    

    blog.js

    // input标签内容改变事件,即头像上传
    $("#avatar").change(function () {
       var file = $(this)[0].files[0];  // 获取图片路径
       var reader = new FileReader();  // 文本阅读器
       reader.readAsDataURL(file);  // 读取文件的路径
       reader.onload = function () {
           $("#avatar_img").attr("src", reader.result)  // 将图片的路径写入img标签的src中
       }
    });
    // 注册用户
    $(".reg-btn").click(function () {
        var formdata = new FormData();
        var request_list = $("#form").serializeArray();  // 包含form表单中所有标签的name和value
        $.each(request_list, function (index, data) {
            formdata.append(data.name, data.value)
            /*
    		相当于:
    			formdata.append("username",$("#id_username").val());
        		formdata.append("password",$("#id_password").val());
        		formdata.append("re_password",$("#id_re_password").val());
       			formdata.append("email",$("#id_email").val());
    		*/
        });
        formdata.append("avatar", $("#avatar")[0].files[0]);
        $.ajax({
            url: "/register/",
            type: "POST",
            contentType: false,  // 必加参数
            processData: false,  // 必加参数
            data: formdata,
            success: function (data) {
                $("span.error").html("");  // 移除上一次的错误信息
                $(".has-error").removeClass("has-error");  // 移除上一次错误信息框样式
                if (data.user) {
                    location.href = "/login/"  //注册成功则返回登录界面
                }
                else {
                    var error_list = data.msg;
                    $.each(error_list, function (field, error) {
                        if (field == "__all__"){
                            $("#id_re_password").next().html(error[0]).parent().addClass("has-error")  // 对全局钩子的单独处理
                        }
                        $("#id_" + field).next().html(error[0]);  // 找寻对应id的input标签后面的span标签,将错误信息写入
                        $("#id_" + field).parent().addClass("has-error");  // 给含有错误信息的input标签添加css样式
                    })
                }
            }
        })
    });
    

    my_forms.py

    from django import forms
    from .models import *
    from django.core.exceptions import ValidationError
    
    wid_1 = forms.widgets.TextInput(attrs={"class": "form-control", "autocomplete": "off"})
    wid_2 = forms.widgets.PasswordInput(attrs={"class": "form-control"})
    
    
    class UserForm(forms.Form):
        username = forms.CharField(max_length=32, label="用户名", widget=wid_1,
                                   error_messages={"required": "该字段不能为空!"})
        password = forms.CharField(min_length=8, label="密码", widget=wid_2,
                                   error_messages={"min_length": "密码长度至少8位!",
                                                   "required": "该字段不能为空!"})
        re_password = forms.CharField(min_length=8, label="确认密码", widget=wid_2,
                                      error_messages={"min_length": "密码长度至少8位!",
                                                      "required": "该字段不能为空!"})
        email = forms.EmailField(label="邮箱", widget=wid_1,
                                 error_messages={"invalid": "邮箱格式错误!"})
    
        def clean_username(self):
            username = self.cleaned_data.get("username")
            user = User.objects.filter(username=username)
            if not user:
                return username
            else:
                raise ValidationError("该用户已经被注册!")
    
        def clean(self):
            password = self.cleaned_data.get("password")
            re_password = self.cleaned_data.get("re_password")
    
            if password == re_password:
                return self.cleaned_data
            else:
                raise ValidationError("两次密码不一致!")
    

    views.py

    from blog.my_forms import *
    
    def register(request):
        user = UserForm()
        if request.method == "GET":
            return render(request, "register.html", locals())
        elif request.method == "POST":
            response = {"user": None, "msg": None}
            user = UserForm(request.POST)
            if user.is_valid():
                username = user.cleaned_data.get("username")
                response["user"] = username
                password = user.cleaned_data.get("password")
                email = user.cleaned_data.get("email")
                avatar_obj = request.FILES.get("avatar")
                if avatar_obj:  # 如果用户上传了头像,则创建用户时将其传入,没有则传递其他填写的信息
                    user_obj = User.objects.create_user(username, email, password, avatar=avatar_obj)
                else:
                    user_obj = User.objects.create_user(username, email, password)
            else:
                response["msg"] = user.errors  # 包含了所有的错误信息
    
            return JsonResponse(response)
    

    展示效果:

    总结:

    1. label标签添加for属性(内容为某个标签的id),点击label相当于点击for属性中的标签。利用此特性,将文件上传input隐藏,添加一个label与其绑定,接着在label中添加img,这样点击图片便可以实现文件上传。
    2. 文件的路径通过$(xx)[0].files[0]来获取
    3. 实现上传头像的预览主要分为三步,第一获取图片的路径,第二步读取图片的路径,第三步将图片路径写入img标签中
    4. 文本阅读器中的readAsDataURL是异步的,所以会出现还没有读取图片路径就写入了,从而产生一个unknow对象,所以需要用到onload方法,onload会在所有资源加载完之后运行。
    5. 上传文件时需要用到FormData,使用FormData时必须加ajax中加contentType: false,processData: false这两个参数
    6. 向FormData中插入数据的时候,可以通过$("xxx").serializeArray()(包含了form表单中所有输入框的name以及value)循环添加,减少代码量
    7. Media的配置,将服务器的文件存放在static中,用户上传的文件存放在media/avatars中。Media_url的配置,使用户可以从浏览器访问media资源
  • 相关阅读:
    git上传本地项目
    第十一章 持有对象
    java 闭包与回调
    类名.class 类名.this 详解
    匿名内部类
    第十章 内部类
    Java简单调用Zookeeper服务
    Linux下ZooKeeper集群安装
    Linux自动化安装JDK
    linux下初步实现Keepalived+Nginx高可用
  • 原文地址:https://www.cnblogs.com/ExBurner/p/9297363.html
Copyright © 2011-2022 走看看