• 登录、注册验证


    考虑用户是自创表,还是对User表进行扩展,我们选择对User表进行扩展,这样也可以继续使用django内置auth验证用户是否登录

    对于扩展表,我们要做的是如下操作

    1.model

    from django.contrib.auth.models import AbstractUser
    
    class UserInfo(AbstractUser):
        avatar=models.FileField(upload_to='avatar/',default='avatar/default.png') # 扩展字段

    2.settings

    AUTH_USER_MODEL='app01.UserInfo'

    应用目录创建myfrom

    LoginForm

    from django import forms
    from django.conf import settings
    from app01 import models
    from django.contrib.auth import authenticate
    
    class LoginForm(forms.Form):
        """ 用户登录表单 """
        username=forms.CharField(label='用户名',min_length=3,max_length=12,
                                 error_messages={
                                     "required":"用户名不能为空",
                                     "min_length":"用户名不能少于3位",
                                     "max_length":"用户名最大8位"
                                 },
                                 widget=forms.widgets.TextInput(attrs={"class":"form-control",'placeholder':'username'}))
    
        password=forms.CharField(label='密码',min_length=6,max_length=12,
                                 error_messages={
                                     "required":"密码不能为空",
                                     "min_length":"密码不能少于6位",
                                     "max_length":"密码最大12位"
                                 },
                                 widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password'}))
    
        verify_code = forms.CharField(label='验证码', min_length=5,max_length=5,
                                      error_messages={
                                          'required':'验证码不能为空',
                                          'min_length':'验证码必须5位',
                                          'max_length':'验证码必须5位',
                                      },
                                      widget=forms.widgets.TextInput(attrs={'class': 'form-control verifycode', 'placeholder': '验证码'}))
    
        def __init__(self,request,*args,**kwargs):
            super().__init__(*args,**kwargs)
            self.request=request
    
        def clean_password(self):
            username = self.cleaned_data.get('username', None)
            password = self.cleaned_data.get('password', None)
            # 这里用的是auth模块,如果是自创建用户表,就需要models
            is_exsit = authenticate(username=username,password=password)
            print(is_exsit)
            if not is_exsit:
                self.add_error('password','用户名或密码不对')
            return password
    
        def clean_verify_code(self):
            """ 验证用户输入的验证码是否正确 """
            verify_code = self.cleaned_data['verify_code']
            if not verify_code:
                self.add_error('verify_code','请输入验证码')
            # 转变大小写,也就是做大小写忽略
            if not str(verify_code).lower() == self.request.session[settings.CODE].lower():
                self.add_error('verify_code', '验证码错误')
            return verify_code

    RegForm

    from django import forms
    from app01 import models
    
    class RegFrom(forms.Form):
        username=forms.CharField(label='用户名',min_length=3,max_length=12,
                                 error_messages={
                                     "required":"用户名不能为空",
                                     "min_length":"用户名不能少于3位",
                                     "max_length":"用户名最大8位"
                                 },
                                 widget=forms.widgets.TextInput(attrs={"class":"form-control",'placeholder':'username'}))
    
        password=forms.CharField(label='密码',min_length=6,max_length=12,
                                 error_messages={
                                     "required":"密码不能为空",
                                     "min_length":"密码不能少于6位",
                                     "max_length":"密码最大12位"
                                 },
                                 widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password'}))
    
        confirm_password=forms.CharField(label='确认密码',min_length=6,max_length=12,
                                 error_messages={
                                      "required": "密码不能为空",
                                      "min_length": "密码不能少于6位",
                                      "max_length": "密码最大12位"
                                 },
                                 widget=forms.widgets.PasswordInput(attrs={"class":"form-control",'placeholder':'password2'}))
    
        email=forms.EmailField(label="邮箱",
                               error_messages={
                                   'required':'邮箱不能为空',
                                   'invalid':'邮箱格式错误'
                               },
                               widget=forms.widgets.EmailInput(attrs={'class':'form-control','placeholder':'email'}))
    
        def clean_username(self):
            username=self.cleaned_data.get('username')
            is_exist=models.UserInfo.objects.filter(username=username)
            if is_exist:
                self.add_error('username', '用户名已存在')
            return username
    
        def clean(self):
            password=self.cleaned_data.get('password')
            confirm_password=self.cleaned_data.get('confirm_password')
            if not password == confirm_password:
                self.add_error('cronfirm_password','2次密码不一致')
            return self.cleaned_data

    视图函数VIEWS

    def register(request):
        form = RegFrom.RegFrom()
        if request.method == 'POST':
            back_dic = {"code": 1000, 'msg': ''}
            # 校验数据是否合法
            form = RegFrom.RegFrom(request.POST)
            # 判断数据是否合法
            if form.is_valid():
                # print(form_obj.cleaned_data)  # {'username': 'liqianlong', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
                clean_data = form.cleaned_data  # 将校验通过的数据字典赋值给一个变量
                # 将字典里面的confirm_password键值对删除
                clean_data.pop('confirm_password')  # {'username': 'liqianlong', 'password': '123', 'email': '123@qq.com'}
                # 用户头像
                file_obj = request.FILES.get('avatar')
                # 针对用户头像一定要判断是否传值 不能直接添加到字典里面去
                if file_obj:
                    clean_data['avatar'] = file_obj
                # 直接操作数据库保存数据
                models.UserInfo.objects.create_user(**clean_data)
                back_dic['url'] = '/login/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = form.errors
            return JsonResponse(back_dic)
        return render(request,'register.html',{'form':form})
    
    def login(request):
        form = LoginForm.LoginForm(request)
        if request.method=="POST":
            back_dic = {"code": 1000, 'msg': ''}
            form= LoginForm.LoginForm(request,request.POST)
            if form.is_valid():
                # form验证通过,用户验证信息加入session
                user=authenticate(username=form.cleaned_data['username'],password=form.cleaned_data['password'])
                request.session[settings.SESSION_COOKIE_NAME] = {'id': user.id, 'user': user.username}
                if request.POST.get('keyfree', None) == '1':
                    # 用户点点击,就是10秒,否则默认2周缓存
                    request.session.set_expiry(10)
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = form.errors
            return JsonResponse(back_dic)
        return render(request,"login.html",{"form":form})
    
    def get_code(request):
        # 验证码
        return get_codes.get_code(request)def logout(request):
        request.session.flush()
        return redirect('/login/')

    模板 

    Login

    {% load Icon %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css">
        <style>
            i {
                color: #4cae4c;
            }
            .verifycode {
                position: relative;
            }
            #id_img {
                position: absolute;
                right: 0;
                bottom: 0;
                height: 34px;
                 200px;
            }
            #id_verify_code {
                 40%;
            }
        </style>
    </head>
    
    <body>
    
    <!--LOgin-->
    <div class="container">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <h2 class="text-center">登录页面<i class="fa fa-bandcamp" aria-hidden="true"></i></h2>
    
                <form id="myform">
                    <div class="form-group">
                        {% csrf_token %}
                        {% for formobj in form %}
                            {% if not formobj.name == 'verify_code' %}
                                <div class="form-group">
                                    <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %}
                                    {{ formobj }}
                                    <span class="pull-right text-danger"></span>
                                </div>
                            {% else %}
                                <div class="form-group verifycode">
                                    <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %}
                                    {{ formobj }}
                                    <span class="text-danger"></span>
                                    <img src="{% url 'get_code' %}" alt="验证码" id="id_img">
                                </div>
                            {% endif %}
                        {% endfor %}
                    </div>
                    <div class="form-group">
                        <div class="checkbox">
                            <label>
                                <input type="checkbox" name="keyfree" value="1">免密钥登录
                            </label>
                            <abbr title="10s内无需重复登录,不选则为2周内无需重复登录。">详细</abbr>
                            <a href="{% url 'register' %}"><span class="pull-right">注册</span></a>
                        </div>
                    </div>
    
                    <div class="form-group">
                         <button type="button" class="btn btn-primary btn-block" id="id_commit">登录</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/gt.js"></script>
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    
    <script>
    
        $('#id_img').click(function () {
            var oldVal=$(this).attr('src');
            $(this).attr('src',oldVal+'?');
        });
    
        $('#id_commit').click(function () {
            $.ajax({
                url:"/login/",
                type:'post',
                data:$('#myform').serialize(),
                success:function (args) {
                    if (args.code==1000){
                        // 跳转到登陆页面
                        window.location.href = args.url
                    }else{
                        // 如何将对应的错误提示展示到对应的input框下面
                        // forms组件渲染的标签的id值都是 id_字段名
                        $.each(args.msg,function (index,obj) {
                            {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                            var targetId = '#id_' + index;
                            $(targetId).next().text(obj[0]).parent().addClass('has-error')
                        })
                    }
                }
            })
        });
    
        $('input').focus(function () {
            $(this).next().text('').parent().removeClass('has-error')
        });
    
    
    </script>
    
    </body>
    </html>

    Reg

    {% load Icon %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css">
        <style>
            i {
                color: #4cae4c;
            }
        </style>
    </head>
    <body>
    
    
    <div class="container">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <h2 class="text-center">注册页面<i class="fa fa-bandcamp" aria-hidden="true"></i></h2>
    
                <form id="myform">
                    {% csrf_token %}
                    {% for formobj in form %}
                        <div class="form-group">
                            <label for="{{ formobj.auto_id }}">{{ formobj.label }}</label>{% Icon_html formobj %}
                            {{ formobj }}
                            <span class="pull-right text-danger"></span>
                        </div>
                    {% endfor %}
    
                    <div class="form-group">
                        <label for="myfile">
                            <img src="/static/img/default.png" alt="用户头像" id="myimg" class="img-rounded" style=" 100px">
                        </label>
                        <input type="file" id="myfile" name="avatar" style="display: none">
                    </div>
    
                    <div class="form-group">
                        <a href="{% url 'login' %}" class="btn btn-primary">返回登录</a>
                        <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
                    </div>
                
                </form>
            </div>
        </div>
    </div>
    
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    
    
    <script>
        $('#myfile').change(function () {
            // 文件阅读器对象
            // 1 先生成一个文件阅读器对象
            let myFileReaderObj = new FileReader();
            // 2 获取用户上传的头像文件
            let fileObj = $(this)[0].files[0];
            // 3 将文件对象交给阅读器对象读取
            myFileReaderObj.readAsDataURL(fileObj);  // 异步操作  IO操作
            // 4 利用文件阅读器将文件展示到前端页面  修改src属性
            // 等待文件阅读器加载完毕之后再执行
            myFileReaderObj.onload = function(){
                 $('#myimg').attr('src',myFileReaderObj.result)
            }
        });
    
        $('#id_commit').click(function () {
            // 1 创建文件对象
            var formDataObj=new FormData;
            // 2 遍历添加普通数据
            $.each($('#myform').serializeArray(),function (index,obj) {
                formDataObj.append(obj.name,obj.value)
            });
            // 3 添加文件数据
            formDataObj.append('avatar',$('#myfile')[0].files[0]);
            // 4 发送ajax请求
            $.ajax({
                url:"{% url 'register' %}",
                type:'post',
                data:formDataObj,
    
                // 需要指定两个关键性的参数
                contentType:false,
                processData:false,
    
                success:function (args) {
                    if (args.code==1000){
                        // 跳转到登陆页面
                        window.location.href = args.url
                    }else{
                        // 如何将对应的错误提示展示到对应的input框下面
                        // forms组件渲染的标签的id值都是 id_字段名
                        $.each(args.msg,function (index,obj) {
                            {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                            var targetId = '#id_' + index;
                            $(targetId).next().text(obj[0]).parent().addClass('has-error')
                        })
                    }
                }
            })
        });
    
        $('input').focus(function () {
            $(this).next().text('').parent().removeClass('has-error')
        })
    
    
    </script>
    
    </body>
    </html>

    说明一下,模板里面 {% load Icon %},其实是给页面上加了一些图标,利用了 inclusion_tag

    首先在应用里面创建templatetags,创建Icon.py

    from django.template import Library
    
    register=Library()
    
    @register.inclusion_tag('Icon.html')
    def Icon_html(formobj):
        dic={
            'username':'fa-user',
            'password':'fa-key',
            'confirm_password':'fa-key',
            'email':'fa-envelope-o',
            'verify_code':'fa-code'
        }
        for i in dic:
            if formobj.name.strip().lower() == i.strip().lower():
                tag=dic[i]
                return {'tag':tag}

    Icom.html

    <i class="fa {{ tag }}" aria-hidden="true"></i>

    登录中间件判断是否登录

    应用下创建middl目录,创建md_login.py

    import re
    # from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import redirect
    from django.conf import settings
    
    
    class MiddlewareMixin(object):
        def __init__(self, get_response=None):
            self.get_response = get_response
            super(MiddlewareMixin, self).__init__()
    
        def __call__(self, request):
            response = None
            if hasattr(self, 'process_request'):
                response = self.process_request(request)
            if not response:
                response = self.get_response(request)
            if hasattr(self, 'process_response'):
                response = self.process_response(request, response)
            return response
    
    class LoginMiddle(MiddlewareMixin):
        def process_request(self,request):
            '''
            无返回值:继续执行后续中间件--django
            有返回值:执行自己的process_response和上面的response
            request.xxx=888
            request.path_info
            '''
            current_url=request.path_info
            for url in settings.VALID_URL:
                if re.match(url,current_url):
                    print(url,current_url)
                    return None
    
            userinfo=request.session.get(settings.SESSION_COOKIE_NAME)
    
            if not userinfo:
                return redirect('/login/')
            else:
                request.userinfo=userinfo
                return None
    
        def process_response(self,request,response):
            return response

    情况说明

    settings

    STATIC_URL = '/static/'
    
    STATICFILES_DIRS=[os.path.join(BASE_DIR,'static'),]
    
    AUTH_USER_MODEL='app01.UserInfo'  # 扩展字段
    
    CODE = 'VerCode'          # 验证码,这也是要存session里面
    
    SESSION_COOKIE_NAME = 'UserInfo' # 用户信息
    
    VALID_URL=[        # 登录白名单
        '/login/',
        '/admin.*',
        '/get_code/',
        '/register/',
    ]

    验证码

    应用同级创建utils目录,创建get_code.py,验证码逻辑就是生成验证码存session,跟你输入的作对比

    """
    图片相关的模块
        pip3 install pillow
    """
    import os,random
    from PIL import Image,ImageDraw,ImageFont
    from django.conf import settings
    from django.http import HttpResponse
    """
    Image:生成图片
    ImageDraw:能够在图片上乱涂乱画
    ImageFont:控制字体样式
    """
    from io import BytesIO,StringIO
    """
    内存管理器模块
    BytesIO:临时帮你存储数据 返回的时候数据是二进制
    StringIO:临时帮你存储数据 返回的时候数据是字符串
    """
    
    def get_code(request):
        # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
        # with open(r'static/img/111.jpg','rb') as f:
        #     data = f.read()
        # return HttpResponse(data)
    
        # 推导步骤2:利用pillow模块动态产生图片
        # img_obj = Image.new('RGB',(430,35),'green')
        # img_obj = Image.new('RGB',(430,35),get_random())
        # # 先将图片对象保存起来
        # with open('xxx.png','wb') as f:
        #     img_obj.save(f,'png')
        # # 再将图片对象读取出来
        # with open('xxx.png','rb') as f:
        #     data = f.read()
        # return HttpResponse(data)
    
        # 推导步骤3:文件存储繁琐IO操作效率低  借助于内存管理器模块
        # img_obj = Image.new('RGB', (430, 35), get_random())
        # io_obj = BytesIO()  # 生成一个内存管理器对象  你可以看成是文件句柄
        # img_obj.save(io_obj,'png')
        # return HttpResponse(io_obj.getvalue())  # 从内存管理器中读取二进制的图片数据返回给前端
    
    
        # 最终步骤4:写图片验证码
        img_obj = Image.new('RGB', (200, 34), (random.randint(0,255),random.randint(0,255),random.randint(0,255)))
        img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
        font_path=os.path.join(settings.BASE_DIR,'static','font','Monaco.ttf')
        img_font = ImageFont.truetype(font_path,28)  # 字体样式 大小
    
        # 随机验证码  五位数的随机验证码  数字 小写字母 大写字母
        code = ''
        for i in range(5):
            random_upper = chr(random.randint(65,90))
            random_lower = chr(random.randint(97,122))
            random_int = str(random.randint(0,9))
            # 从上面三个里面随机选择一个
            tmp = random.choice([random_lower,random_upper,random_int])
            # 将产生的随机字符串写入到图片上
            """
            为什么一个个写而不是生成好了之后再写
            因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
            间隙就没法控制了
            """
            img_draw.text((10+i*40,0),tmp,(random.randint(0,255),random.randint(0,255),random.randint(0,255)),img_font)
            # 拼接随机字符串
            code += tmp
        # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
        request.session[settings.CODE] = code
        io_obj = BytesIO()
        img_obj.save(io_obj,'png')
        return HttpResponse(io_obj.getvalue())
    背景+随机字符
    import random
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    
    _letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
    _upper_cases = _letter_cases.upper()  # 大写字母
    _numbers = ''.join(map(str, range(3, 10)))  # 数字
    init_chars = ''.join((_letter_cases, _upper_cases, _numbers))
    
    
    def create_validate_code(size=(120, 30),
                             chars=init_chars,
                             img_type="GIF",
                             mode="RGB",
                             bg_color=(255, 255, 255),
                             fg_color=(0, 0, 255),
                             font_size=18,
                             font_type="Monaco.ttf",
                             length=4,
                             draw_lines=True,
                             n_line=(1, 2),
                             draw_points=True,
                             point_chance=2):
        """
        @todo: 生成验证码图片
        @param size: 图片的大小,格式(宽,高),默认为(120, 30)
        @param chars: 允许的字符集合,格式字符串
        @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
        @param mode: 图片模式,默认为RGB
        @param bg_color: 背景颜色,默认为白色
        @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
        @param font_size: 验证码字体大小
        @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
        @param length: 验证码字符个数
        @param draw_lines: 是否划干扰线
        @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
        @param draw_points: 是否画干扰点
        @param point_chance: 干扰点出现的概率,大小范围[0, 100]
        @return: [0]: PIL Image实例
        @return: [1]: 验证码图片中的字符串
        """
    
        width, height = size  # 宽高
        # 创建图形
        img = Image.new(mode, size, bg_color)
        draw = ImageDraw.Draw(img)  # 创建画笔
    
        def get_chars():
            """生成给定长度的字符串,返回列表格式"""
            return random.sample(chars, length)
    
        def create_lines():
            """绘制干扰线"""
            line_num = random.randint(*n_line)  # 干扰线条数
    
            for i in range(line_num):
                # 起始点
                begin = (random.randint(0, size[0]), random.randint(0, size[1]))
                # 结束点
                end = (random.randint(0, size[0]), random.randint(0, size[1]))
                draw.line([begin, end], fill=(0, 0, 0))
    
        def create_points():
            """绘制干扰点"""
            chance = min(100, max(0, int(point_chance)))  # 大小限制在[0, 100]
    
            for w in range(width):
                for h in range(height):
                    tmp = random.randint(0, 100)
                    if tmp > 100 - chance:
                        draw.point((w, h), fill=(0, 0, 0))
    
        def create_strs():
            """绘制验证码字符"""
            c_chars = get_chars()
            strs = ' %s ' % ' '.join(c_chars)  # 每个字符前后以空格隔开
    
            font = ImageFont.truetype(font_type, font_size)
            font_width, font_height = font.getsize(strs)
    
            draw.text(((width - font_width) / 3, (height - font_height) / 3),
                      strs, font=font, fill=fg_color)
    
            return ''.join(c_chars)
    
        if draw_lines:
            create_lines()
        if draw_points:
            create_points()
        strs = create_strs()
    
        # 图形扭曲参数
        params = [1 - float(random.randint(1, 2)) / 100,
                  0,
                  0,
                  0,
                  1 - float(random.randint(1, 10)) / 100,
                  float(random.randint(1, 2)) / 500,
                  0.001,
                  float(random.randint(1, 2)) / 500
                  ]
        img = img.transform(size, Image.PERSPECTIVE, params)  # 创建扭曲
        img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  # 滤镜,边界加强(阈值更大)
        return img, strs
    背景+随机字符+干扰线

    https://gitee.com/jokerbj/verification-code

    效果所示

     

    基于此我们项目一图书管理系统

    https://www.cnblogs.com/jokerbj/p/13920324.html

  • 相关阅读:
    访客登录方案设计与应用
    VS Code下载
    dockerfile COPY命令失效
    mysql排序字段值相等时,分页数据重复
    go使用json包Marshal方法得到异常结果[123 125]
    Mysql知识点概览
    dockercompose安装
    docker安装
    Shell脚本执行报错:Syntax error: "(" unexpected
    二进制数的位运算,角色权限,多种账号来源
  • 原文地址:https://www.cnblogs.com/jokerbj/p/14078416.html
Copyright © 2020-2023  润新知