登录、注册验证

考虑用户是自创表,还是对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;
            width: 200px;
        }
        #id_verify_code {
            width: 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="width: 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

 

posted @ 2020-12-03 11:06  liqianlong  阅读(1121)  评论(0编辑  收藏  举报