zoukankan      html  css  js  c++  java
  • 16 Django

    Django - 登录(含随机生成图片验证码)、注册示例

    一、登录 - 随机生成图片验证码

    1、随机生成验证码

      Python随机生成图片验证码,需要使用PIL模块,安装方式如下:

      pip3 install pillow

      1)创建图片

    from PIL import Image
    img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
    with open('code.png', 'wb') as f:    # 保存在本地(即写入硬盘)
        img.save(f, format='png')

      参数说明:

      mode='RGB'  表示以RGB来表示颜色

      size=(120,30)  表示坐标

      color=(255, 255, 255)  表示白色

      此时,打开启动文件所在目录,里面就有了一个宽120,高30的白色code.png图片。

      2)创建画笔(用于在图片上画任意内容)

    from PIL import Image, ImageDraw
    img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 创建画笔对象draw img.show() # 在图片查看器中打开,这句会调用系统默认的图片管理工具

      3)画点 - point()方法

     
    from PIL import Image, ImageDraw
    img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # point()第一个参数:表示坐标, 第二个参数:表示颜色 draw.point([100, 20], fill='red') draw.point([60, 10], fill=(0, 255, 0)) # 保存在本地 with open('code.png', 'wb') as f: img.save(f, format='png')
     

    效果如下图:

      4)画线 - line()方法

     
    from PIL import Image, ImageDraw
    img = Image.new(mode='RGB', size=(540, 150), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # line()第一个参数:表示起始坐标和结束坐标,第二个参数:表示颜色 draw.line((50, 50, 50, 150), fill='red') # 上面一句表示画一条坐标(x=100,y=100)到(x=100,y=300)的直线 draw.line((50, 100, 150, 50), fill=(120, 120, 120)) draw.line((50, 50, 150, 50), fill=(0, 255, 255)) with open('code.png', 'wb') as f: img.save(f, format='png')
     

    效果如下:

      5)画圆 - arc()方法

     
    from PIL import Image, ImageDraw
    img = Image.new(mode='RGB', size=(500, 140), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一个参数:表示起始坐标和结束坐标(圆要画在其中间,两点确定的矩形的内切圆) # 第二个参数:表示开始角度 # 第三个参数:表示结束角度 # 第四个参数:表示颜色 draw.arc((200, 20, 300, 120), 0, 360, fill='red') with open('code.png', 'wb') as f: img.save(f, format='png')
     

    效果如下:

      6)写文本 - text()方法

     
    from PIL import Image, ImageDraw
    img = Image.new(mode='RGB', size=(80, 20), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') # 第一个参数:表示起始坐标,第二个参数:表示写入的文本,第三个参数:表示颜色 draw.text([0, 0], 'python', 'red') with open('code.png', 'wb') as f: img.save(f, format='png')
     

    效果如下:

      7)特殊字体文字(下载好引用的字体文件)

     
    from PIL import Image, ImageDraw, ImageFont
    img = Image.new(mode='RGB', size=(120, 40), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') font = ImageFont.truetype('kumo.ttf', 28) # 第一个参数:表示字体文件路径 # 第二个参数:表示字体大小 draw.text((0, 0), 'python', 'red', font=font) # 第一个参数:表示起始坐标 # 第二个参数:表示写入内容 # 第三个参数:表示颜色 # 第四个参数:表示字体 with open('code.png', 'wb') as f: img.save(f, format='png')
     

    效果如下:

      8)随机生成图片验证码

     
    import random
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    
    def check_code(width=120, height=30, char_length=5, font_file='../static/font/kumo.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:
            """
            ranNum = str(random.randint(0, 9))
            ranLower = chr(random.randint(65, 90))
            ranUpper = chr(random.randint(97, 120))
            return random.choice([ranNum, ranLower, ranUpper])
    
        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 = ( height - font_size ) / 2
            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__':
        # 1. 直接打开,即用图片查看器查看
        # img,code = check_code()
        # img.show()
    
        # 2. 写入文件
        # img,code = check_code()
        # with open('code.png','wb') as f:   # f是写入磁盘的文件句柄
        #     img.save(f, format='png')
        # data = f.read()   # data是读取图片的字节
    
        # 3. 写入内存(Python3)
        # img,code = check_code()
        # from io import BytesIO    # 内存管理的模块
        # stream = BytesIO()         # stream是写入内存的文件句柄
        # img.save(stream, 'png')
        # data = stream.getvalue()
    
        # 4. 写入内存(Python2)
        # img,code = check_code()
        # import StringIO
        # stream = StringIO.StringIO()  # stream是写入内存的文件句柄
        # img.save(stream, 'png')
        # data = stream.getvalue()
     

    效果如下:

    2、基于ajax实现登录的示例代码

      1)urls.py中关于登录代码:

    path('login/', views.login,),  # 获取登录页面url
    path('get_identifyCode/', views.get_identifyCode,),  # 获取验证码对应url

      2)login.html核心代码:

     
    <body>
    <div id="particles-js">
        <div class="login">
            <p class="login-top">登录</p>
            {% csrf_token %}
            <div class="login-center clearfix">
                <label class="" for="user">用户名</label>
                <input type="text" id="user" placeholder="用户名" />
            </div>
    
            <div class="login-center clearfix">
                <label class="iconfont labelFS" for="pwd">密码</label>
                <input type="password" id="pwd" placeholder="密码" />
            </div>
    
            <div class="login-center clearfix">
                <label class="iconfont labelFS" for="validcode">&#xe615;</label>
                <input type="text" id="validcode" placeholder="验证码" />
                <img src="/get_identifyCode/" alt="验证码" title="换一张" class="validImg" id="img" width="88" height="30" >
            </div>
            <a href="javascript:void(0);" class="login_btn">登录</a>
            <p class="error"></p>
        </div>
    </div>
    
    <script src="jquery.min.js"></script>
    
    <script>
        // ajax 登录
        $(".login_btn").click(function () {
            $.ajax({
                url:"",
                type:"post",
                // data发送urlencoded格式就行,数据没那么深,没必要发json格式
                data:{
                     user:$("#user").val(),
                     pwd:$("#pwd").val(),
                     validcode:$("#validcode").val(),
                     csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
                },
                success:function (response) {
                    console.log(response);
                    if(response.user){
                        // 登录成功
                        location.href="/index/"
                    }
                    else{
                        // 登录失败
                        $(".error").html(response.err_msg)
                    }
                }
            })
        });
    
        //  验证码刷新:img标签有一个天然的发请求的模式,即src的路径后边拼接一个问号就会发一次请求,利用这一原理可以实现验证码刷新
        $("#img").click(function () {
            this.src += "?"
        });
    
    </script>
    </body>
     

      3)views.py中获取随机验证码的视图函数代码(验证码保存利用session)

     
    def get_identifyCode(request):
          img,code = check_code()  # 利用上面的模块得到img对象和验证码code
      
        f = BytesIO()  # 得到写入内存的文件句柄
        img.save(f, "png")   # 写入内存
        data = f.getvalue()   # 从内存中读出
    
        # 将验证码存在各自的session中,这样做的好处是每个人都有自己的验证码,不会相互混淆(一定不能设为全局变量)
        request.session['keep_str'] = code
    
        return HttpResponse(data)
     

      4)views.py中login视图函数代码

     
    from django.contrib import auth
    def login(request):
        # if request.method == "POST":
        if request.is_ajax():    # 判断是否ajax请求
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            validcode = request.POST.get("validcode")
            # Ajax请求通常返回一个自己构建的字典
            response={"user": None, "err_msg": ""}
              # request.session.get("keep_str")取出session中验证码与用户输入作判断
            if validcode.upper() == request.session.get("keep_str").upper():  
                user_obj = auth.authenticate(username=user, password=pwd)
                print("user_obj", user_obj, bool(user_obj))
                if user_obj:
                    response["user"] = user
                    auth.login(request, user_obj)  # 保存用户状态
                else:
                    response['err_msg'] = "用户名或者密码错误!"
            else:
                response["err_msg"] = "验证码错误!"
            return JsonResponse(response)
        else:
            return render(request, "login.html")
     

    二、基于ajax和forms组件实现注册示例

    1)model.py

    from django.contrib.auth.models import AbstractUser
    
    class UserInfo(AbstractUser):  # 将原生auth_user表扩展一个tel手机号字段
        tel=models.CharField(max_length=32)

    2)forms.py(其实代码放在哪里没关系,最重要的是程序能找到,为了解耦,我们可以定义一个form.py)

     
    from django import forms
    # exceptions中存着django的所有错误,错误在核心组件中
    from django.core.exceptions import ValidationError
    from django.forms import widgets
    from app01.models import UserInfo
    
    class UserForm(forms.Form):   # UserForm中定义需要校验的字段
        username=forms.CharField(min_length=5,
                      label="用户名")
        password=forms.CharField(min_length=5,
                     widget=widgets.PasswordInput(),
                     label="密码")
        r_pwd=forms.CharField(min_length=5,
                       widget=widgets.PasswordInput(),
                       label="确认密码")
        email=forms.EmailField(min_length=5,
                    label="邮箱")
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.fields.values():
                field.widget.attrs.update({'class': 'form-control'}) # 统一加class
    
        def clean_user(self):
            val=self.cleaned_data.get("username")
            user=UserInfo.objects.filter(username=val).first()
            if user:
                raise ValidationError("用户已存在!")
            else:
                return val
    
        def clean_pwd(self):
            val=self.cleaned_data.get("password")
            if val.isdigit():
                raise ValidationError("密码不能是纯数字!")
            else:
                return val
    
        def clean_email(self):
            val = self.cleaned_data.get("email")
            if re.search("w+@163.com$", val):
                return val
            else:
                raise ValidationError("邮箱必须是163邮箱!")
            
        def clean(self):
            pwd=self.cleaned_data.get("password")
            r_pwd=self.cleaned_data.get("r_pwd")
    
            if pwd and r_pwd and r_pwd!=pwd:
                self.add_error("r_pwd", ValidationError("两次密码不一致!"))
            else:
                return self.cleaned_data
     

    3)reg.html核心代码:

     
    <body>
    <h3>注册页面</h3>
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <form action="" method="">
                {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        <label for="">{{ field.label }}</label>
                        {{ field }}
                        <span class="error"></span>
                    </div>
                {% endfor %}
                <input type="button" class="btn btn-primary reg_btn" value="注册">
                </form>
            </div>
        </div>
    </div>
    <script src="jquery.min.js"></script>
    <script>
        $(".reg_btn").click(function () {
            $.ajax({
                url:"",
                type:"post",
                data:{
                    username:$("#id_username").val(),
                    password:$("#id_password").val(),
                    r_pwd:$("#id_r_pwd").val(),
                    email:$("#id_email").val(),
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
    
                },
                success:function (res) {
                    if (res.user){
                        // 注册成功
                        location.href="/login/"
                    }
                    else{
                        // 清除错误
                        $(".error").html("");
                        //  展示新的错误
                        $.each(res.err_msg,function (i,j) {
                           $("#id_"+i).next().html(j[0]);
                        })
                    }
                }
            })
        })
    </script>
    </body>
     

    4)views.py中注册的视图函数reg

     
    def reg(request):
        if request.method == "POST":
            form = UserInfo(request.POST)
            res = {"user": None, "err_msg": ""}
            if form.is_valid():
                res["user"] = form.cleaned_data.get("username")
                del form.cleaned_data["r_pwd"]   # 因表中无此字段,只需校验,不插入
                UserInfo.objects.create_user(**form.cleaned_data)
            else:
                res["err_msg"] =form.errors
            return JsonResponse(res)
        else:   # get请求
            form = UserInfoModelForm()
            return  render(request,"reg.html",{"form": form})
     

    三、补充知识点

    1、对原生auth_user表扩展字段(使用AbstractUser)

      我们之前学习用户认证组件时,用的是django提供的auth_user表,即通过引入User对象(from django.contrib.auth.models import User)去操作它,我们又发现源码中User类继承了AbstractUser类,所以AbstractUser和User其实就是一张表,所以当我们想要有用户认证功能,又想要一些auth_user表中没有的字段时,可以按照如下这样做:

      在models.py中,引入AbstractUser,并且自己定义一个用户类(表),这时类中只定义django的auth_user表中没有而你又想使用的字段即可,并且让你定义的类继承AbstractUser,如下:

    from django.contrib.auth.models import AbstractUser
    class UserInfo(AbstractUser): tel=models.CharField(max_length=32) # 扩展了一个手机号字段

      注意:写完以上代码就直接去迁移数据库会报错“HINT: Add or change a related_name argument to the definition for ......”,我们需要在settings.py中加上下面这句话,来告诉Django我们要使用自己定义的表作为用户认证表(因此登录的使用方法不变,认证时django会自己去找这张表):

    AUTH_USER_MODEL="app01.UserInfo"

      这时再去进行数据库迁移,我们发现,数据库中没有auth_user表了,而我们自己定义的表中除了有自己定义的那些字段外,还有之前auth_user表中的所有字段,这就代表已经达到了我们的目的。

      补充:通过命令创建超级用户的方式:

    Tools -- > Run manage.py Task    # 运行起来manage.py,再输入如下命令
    manage.py@myproject > createsuperuser   # 执行后根据提示输入用户名,密码,邮箱
    # 注意:输入的密码会进行加密处理,再存入表中,并且命令输入密码要求最少8位

    2、JsonResponse的使用

    我们发现,一般浏览器发送Ajax请求给服务器时,都会返回一个字典,我们需要先将字典序列化,浏览器接收到后再进行反序列化,你会不会觉得这样做有点繁琐?其实,django为我们提供了一个JsonResponse类,它为我们做好了json的序列化,并且浏览器接收到之后,ajax也会自动为我们反序列化,即ajax中success函数接收到的response就是反序列化之后的数据,直接使用即可,如上面登录示例部分代码:

    from django.http import JsonResponse      # 引入JsonResponse
    def login(request):
            if request.is_ajax():   
                ......            
                response={"user":None, "err_msg": ""}
                ......
                return JsonResponse(response)
     

      分析原因:JsonResponse本质也继承了HttpResponse,而且既为我们做了序列化的操作,还将数据格式设置为json,ajax收到设置了json格式的数据也会为我们自动反序列化,也说明了不仅仅请求头中有content-type,响应头中也有,JsonResponse源码如下:

     
    class JsonResponse(HttpResponse):
        def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
                     json_dumps_params=None, **kwargs):
            if safe and not isinstance(data, dict):
                raise TypeError(
                    'In order to allow non-dict objects to be serialized set the '
                    'safe parameter to False.'
                )
            if json_dumps_params is None:
                json_dumps_params = {}
            kwargs.setdefault('content_type', 'application/json')
            data = json.dumps(data, cls=encoder, **json_dumps_params)
            super().__init__(content=data, **kwargs)
     

    3、forms组件中对渲染出来的input输入框统一增加一个类名

      我们在学习forms组件时,可以分别给每个字段设置一个类名,如class="form-control",但发现像之前那样写的有代码冗余的问题,按照如下方式写可以解决此问题:

     
    from django import forms
    from django.forms import widgets
    class UserForm(forms.Form):
        user=forms.CharField(min_length=5,
                     label="用户名")
        pwd=forms.CharField(min_length=5,
                     widget=widgets.PasswordInput(),
                     label="密码")
        r_pwd=forms.CharField(min_length=5,
                     widget=widgets.PasswordInput(),
                     label="确认密码")
        email=forms.EmailField(min_length=5,
                     label="邮箱")
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for filed in self.fields.values():
                filed.widget.attrs.update({'class': 'form-control'})
     

    4、关于全局钩子__all__的问题

    我们知道全局钩子的错误信息都在__all__中,源码中是这样写的:

    所以知道了这些,我们可以自己设置全局钩子的字段,避免跟其他字段规律不一致造成单独判断的问题,如下方式:

     
      # 全局钩子:校验两次密码不一致
      def clean(self):
          pwd=self.cleaned_data.get("pwd")
          r_pwd=self.cleaned_data.get("r_pwd")
    
          if pwd and r_pwd and r_pwd!=pwd:
              self.add_error("r_pwd", ValidationError("两次密码不一致!"))
            # 自己定义错误信息对应的字段是r_pwd
          else:
              return self.cleaned_data
     

    5、关于具有提交功能的按钮问题

    我们知道form表单是浏览器向服务器发请求的一种方式,提交按钮也有多种,但是要注意,具有提交功能的按钮有两种:<input type="submit" value="提交" />和<button>提交</button>,也就是说,当你想用form表单发请求时,可以用以上两种的任一种,但是当你想基于ajax发送请求时,若有form标签,则一定不要用以上两种提交按钮,否则当你点击按钮发送ajax时会自动以form表单的方式再发一次请求,使用<input type="button" value="提交" />是可以的,因为它没有提交form表单功能。

  • 相关阅读:
    接口表与临时表的用途
    mac电脑连接oracle报错ora-24454,客户主机名未设置
    项目管理口径与法人管理口径会计分录公司信息生成问题
    关于接口的一些理解
    梳理EBS系统中上下文的概念和用法
    数据库系统的用途浅析
    EBS与外围系统数据的交互方式——接口表与API的区别
    四年EBS系统顾问风雨之路回顾——002话
    Web服务器处理请求过程浅谈
    ZOOKEEPER+KAFKA 集群搭建
  • 原文地址:https://www.cnblogs.com/xintiao-/p/10078954.html
Copyright © 2011-2022 走看看