bbs 注册登录 验证

发布时间 2023-12-10 14:56:38作者: 拆尼斯、帕丁顿

注册页面
-用户名
-密码
-确认密码
-邮箱
-手机号
-头像


# form组件 可以帮助我们
1 快速生成前端页面
2 数据校验
3 错误处理


# 如何使用
-1 写一个类,继承forms.Form
-2 在类中写属性和方法
-属性:要跟咱们要校验或自动生成页面的字段一一对应
-方法:对字段进行校验:
clean_字段名 给单个字段校验
clean 给多个字段校验
-3 在视图函数中使用
-4 模板中使用



# form表单中,如果定义了button或input 类型是"submit",只要点击,就会默认触发form表单的提交,如果我们又写了ajax提交,就会触发两次---》导致问题-
-把它搞外面
-input 类型是"button"

from django import forms
from django.forms import widgets, ValidationError
from .models import UserInfo


class RegisterForm(forms.Form):
    # max_length=18 最长 18
    # min_length=3 最短 3
    # required=True 必填
    username = forms.CharField(max_length=18, min_length=3, required=True,
                               label='用户名',
                               error_messages={
                                   'required': '用户名字段必填',
                                   'max_length': '长度不能超过18',
                                   'min_length': '最短3'
                               }, widget=widgets.TextInput(attrs={'class': 'form-control'}))

    password = forms.CharField(max_length=18, min_length=3, required=True,
                               label='密码',
                               error_messages={
                                   'required': '用户名字段必填',
                                   'max_length': '长度不能超过18',
                                   'min_length': '最短3'
                               }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))

    re_password = forms.CharField(max_length=18, min_length=3, required=True,
                                  label='确认密码',
                                  error_messages={
                                      'required': '用户名字段必填',
                                      'max_length': '长度不能超过18',
                                      'min_length': '最短3'
                                  }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(max_length=18, min_length=3, required=True,
                             label='邮箱',
                             error_messages={
                                 'required': '用户名字段必填',
                                 'max_length': '长度不能超过18',
                                 'min_length': '最短3'
                             }, widget=widgets.EmailInput(attrs={'class': 'form-control'}))
    phone = forms.CharField(max_length=11, min_length=11, required=True,
                            label='手机号',
                            error_messages={
                                'required': '用户名字段必填',
                                'max_length': '长度不能超过11',
                                'min_length': '必须11为'
                            }, widget=widgets.TextInput(attrs={'class': 'form-control'}))

    # 方法名只能写两类
    # 一类是  clean_字段名  校验单个字段
    def clean_username(self):  # 如果能走到这里,说明上面的校验已经通过了,校验过后的数据都放在一个字典中---》cleaned_data
        username = self.cleaned_data.get('username')
        # 用户名不能以sb开头
        if username.startswith('sb'):
            # 校验不通过,抛异常
            raise ValidationError('名字不能以sb开头')
        # 如果用户名存在,也不能注册了
        res = UserInfo.objects.filter(username=username).exists()
        if res:
            raise ValidationError('该用户已经存在')
        return username

    # 二类是  clean   同时校验多个字段
    def clean(self):
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if not password == re_password:
            raise ValidationError('两次密码不一致')
        return self.cleaned_data

  

注册功能前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>

<div class="container-fluid">


    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">注册功能</h1>
            <form id="register_form">
                {% csrf_token %}
                {% for foo in form %}
                    <div class="form-group">
                        <label for="{{ foo.auto_id }}">{{ foo.label }}</label>
                        {{ foo }} <span class="pull-right error" style="color: red"></span>
                    </div>
                {% endfor %}
                <div class="form-group">
                    <label for="id_avatar">头像
                        <img src="/static/img/default.png" alt="" height="80px" width="80px" id="id_img"
                             style="margin-left: 20px">

                    </label>
                    <input type="file" id="id_avatar" class="form-control" accept="image/*" style="display: none">
                </div>
                <div class="text-center">
                    <input type="button" value="注册" class="btn btn-danger" id="id_submit">
                    <span class="error" style="color: darkred;margin-left: 10px" id="id_error"></span>
                </div>


            </form>
        </div>
    </div>
</div>

</body>

<script>
    // 1 监控文件变化
    $('#id_avatar').change(function () {
        // 读出input 的图片,写到 img标签上
        // 需要借助于文件阅读器
        var fileReader = new FileReader();
        // 把文件对象读入文件阅读器中
        fileReader.readAsDataURL($('#id_avatar')[0].files[0])
        // 等文件读完,再放入
        fileReader.onload = function () {
            //$('#id_img').attr('height', '300px')
            $('#id_img').attr('src', fileReader.result)
            //$('#id_img')[0].src = fileReader.result
        }


    })


    // 2 按钮提交---》注册功能
    $('#id_submit').click(function () {

        var formdata = new FormData()
        // 把文件放入
        formdata.append('my_img', $('#id_avatar')[0].files[0])

  

//放数据:用户名,密码,确认密码,手机号,邮箱 ,你可以一个个放--->

笨办法
/*
formdata.append('username', $('#id_username').val())
formdata.append('password', $('#id_password').val())
formdata.append('re_password', $('#id_re_password').val())
formdata.append('phone', $('#id_phone').val())
formdata.append('email', $('#id_email').val())
formdata.append('csrfmiddlewaretoken', '

{{ csrf_token }}') // csrf 的token
*/

 

简单方案
        var register_form = $('#register_form').serializeArray() // 会把当前form表单中得数据放到列表套字典的形式
        /*
        数组
        [{name:xx,value:yy}, {…}, {…}, {…}, {…}]
        */
        //console.log(register_form)
        // jq 的循环
        $.each(register_form, function (i, v) {
            //console.log(v['name'])
            //console.log(v['value'])
            formdata.append(v['name'], v['value'])
        })

        $.ajax({
            url: '/register/',
            method: 'post',
            processData: false,
            contentType: false,
            data: formdata,
            success: function (data) {
                if (data.code == 100) {
                    location.href = '/login/'
                } else { // 不成功
                    // 两次密码不一致,把错误写在 注册按钮后面
                    // input 自己的错误,写在自己后面
                    // 循环返回的错误
                    $.each(data.errors, function (key, value) {
                        if (key == '__all__') {
                            $('#id_error').html(value[0])
                        }
                        $('#id_' + key).next().html(value[0]).parent().addClass('has-error')

                    })


                    // 过3s后,清空错误,和红框
                    setTimeout(function () {
                        $('.error').html("").parent().removeClass('has-error')
                        //alert('asfdsdaf')
                    }, 3000)


                }
            }
        })


    })

  

 3 当用户名输入框失去焦点,我们就去后端校验用户名是否注册过
    $('#id_username').blur(function () {
        //alert('失去焦点了')
        //var username=$('#id_username').val()
        var username = $(this).val()
        $.ajax({
            url: '/check_username/?username=' + username,
            method: 'get',
            success: function (data) {
                console.log(data)
                if (data.code != 100) {
                    // 1 清空输入框
                    //$(this).val()
                    // 2 错误提示
                    //$(this).next().html(data.msg)
                    console.log('ssss')
                    // 两句可以并做一句---》链式调用
                    // 如果在另一个内部函数中,就不能用this
                    //var ss=$('#id_username').val()
                    //$('#id_username').next().html(data.msg).parent().addClass('has-error').children('input').val("")
                    $('#id_username').val('').next().html(data.msg).parent().addClass('has-error')

                }
            }
        })
    })

</script>
</html>

 

注册功能后端

 

from django.shortcuts import render
from .forms import RegisterForm
from .models import UserInfo
from django.http import JsonResponse


# Create your views here.
def register(request):
    if request.method == 'GET':
        form = RegisterForm()
        return render(request, 'register.html', {'form': form})
    else:
        # # 1 数据
        # print(request.POST)
        # # 2 文件
        # print(request.FILES.get('my_img'))
        # 取出头像
        avatar = request.FILES.get('my_img')
        # 校验数据是否合法
        '''
        username: admin
        password: 123
        email: 306334678@qq.com
        phone: 17717823244
        avatar:文件
        '''
        form = RegisterForm(request.POST)  # 使用form校验传入的数据
        if form.is_valid():  # 校验通过
            # 保存
            data = form.cleaned_data
            # 把re_password 弹出
            data.pop('re_password')
            # 把头像加入
            if avatar:
                data['avatar'] = avatar
            UserInfo.objects.create_user(**data)
            return JsonResponse({'code': 100, 'msg': '注册成功'})
        else:
            return JsonResponse({'code': 101, 'msg': '注册失败', 'errors': form.errors})


# 校验用户名是否存在的接口
def check_username(request):
    username = request.GET.get('username')
    res = UserInfo.objects.filter(username=username).exists()
    if res:
        # 约定状态码:100,表示成功,非100表示失败
        return JsonResponse({'code': 101, 'msg': '用户已经存在'})
    else:
        return JsonResponse({'code': 100, 'msg': '您可以注册'})

  

forms组件
-1 渲染模板
-2 校验数据
-3 渲染错误
- form=RegisterForm()---渲染页面
- form=RegisterForm(requets.POST)---校验数据
# 2 取数据,取文件
-form-data提交数据
-request.POST 中取数据
-request.FILES.get('名字') 取文件
-补:前端是key:value 后端变成了 key:[value]
-request.data 不是真正的字典
{'username': ['admin','xxx'], 'password': ['123']}
request.POST.get('username')
request.POST.getlist('username')

# 3 保存 文件和数据
# data 是form校验过后的数据,没有头像
data.pop('re_password') # 不是表的字段
# 把头像加入 头像是表的字段
if avatar:
data['avatar'] = avatar # 文件对象
# avatar = models.ImageField(upload_to='avatar', default='avatar/default.png')
内部自动:打开一个空文件,把文件写入到空文件中 【/media/avatar/】,并且把路径赋值给avatar数据库字段
UserInfo.objects.create_user(**data)


# 4 前端:头像实时显示
-隐藏了 input file---》input只能接收图片类型
-只要input发生变化(change)---》把图片读出来,写入到 img标签中
var fileReader = new FileReader();
fileReader.readAsDataURL($('#id_avatar')[0].files[0])
fileReader.onload = function () {
$('#id_img').attr('src', fileReader.result)
}



# 5 用户名失去焦点(blur)---》向后端校验

# 6 form表单使用var register_form = $('#register_form').serializeArray() 转到数组中

# 7 $.each(可以被循环的,function(){})

# 8 错误信息渲染
-__all__ 全局错误---》显示在注册后面
if (key == '__all__') {
$('#id_error').html(value[0])
}
-其他错误,显示在自己后面
$('#id_' + key).next().html(value[0]).parent().addClass('has-error')

-定时任务:3s后干什么
setTimeout(function () {
$('.error').html("").parent().removeClass('has-error')
}, 3000)

 

登录功能

 登录前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>

<div class="container-fluid">


    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">登录功能</h1>
            <form>
                <div class="form-group">
                    <label for="">用户名</label>
                    <input type="text" name="username" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">密码</label>
                    <input type="text" name="password" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">验证码</label>
                    <div class="row">
                        <div class="col-md-6">
                            <input type="text" name="code" class="form-control">
                        </div>

                        <img src="/get_code/" alt="" class="col-md-6" height="35">
                    </div>
                </div>
                <div class="text-center" style="margin-top: 50px">
                    <input type="button" value="登录" class="btn btn-danger" id="id_submit">
                    <span class="error" style="color: darkred;margin-left: 10px" id="id_error"></span>
                </div>
            </form>
        </div>
    </div>
</div>

</body>

</html>

  

生成验证码

第三方方案
https://pythonjishu.com/ljpdvvedzkqiovs/

# 自己的方案

 

def get_code(request):
    # 前端显示图片 方式一
    # with open('./static/img/4.jpg', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 方式二:自己生成一张图片,保存到本地-->打开,返回给前端
    # image_tmp = Image.new('RGB', (300, 38), (255, 255, 0))
    #
    # with open('code.png', 'wb') as f:
    #     image_tmp.save(f, 'png')
    #
    # with open('code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 方式三:借助于ByteIo,把文件内容放在内存中
    # image_tmp = Image.new('RGB', (300, 38), (0, 255, 0))
    # # 放在内存中
    # my_io = BytesIO()
    # image_tmp.save(my_io, 'png')
    # return HttpResponse(my_io.getvalue())

    # 方式四: 要在图片上写文字
    # image_tmp = Image.new('RGB', (300, 38), (0, 255, 0))
    # # 把空图片放在了画板上
    # draw = ImageDraw.Draw(image_tmp)
    # draw.text((0, 0), 'lqz')
    # my_io = BytesIO()
    # image_tmp.save(my_io, 'png')
    # return HttpResponse(my_io.getvalue())

    # 方式5 :加入字体文件
    # image_tmp = Image.new('RGB', (300, 38), (0, 255, 0))
    # # 把空图片放在了画板上
    # draw = ImageDraw.Draw(image_tmp)
    # # 加入字体
    # img_font = ImageFont.truetype('./static/font/xgdl.ttf', 23)
    # draw.text((0, 0), '西瓜大朗', fill=(0, 0, 128), font=img_font, )
    # my_io = BytesIO()
    # image_tmp.save(my_io, 'png')
    # return HttpResponse(my_io.getvalue())

    # 方式6 ,随机生成 5 大小写字母和数字,图片背景色和字的颜色每次不一样
    image_tmp = Image.new('RGB', (300, 38), (0, 255, 0))
    # 把空图片放在了画板上
    draw = ImageDraw.Draw(image_tmp)
    # 加入字体
    img_font = ImageFont.truetype('./static/font/xgdl.ttf', 23)
    draw.text((0, 0), '西瓜大朗', fill=(0, 0, 128), font=img_font, )
    my_io = BytesIO()
    image_tmp.save(my_io, 'png')
    return HttpResponse(my_io.getvalue())




import random


def get_random_code():
    code = ''
    for i in range(5):
        # 随机生成一个大写字母
        upper_char = chr(random.randint(65, 90))
        low_char = chr(random.randint(97, 122))
        num_char = str(random.randint(0, 9))
        res = random.choice([upper_char, low_char, num_char])
        code += res
    return code


if __name__ == '__main__':
    print(get_random_code())