BBS项目-注册功能

1 forms组件应用

注册功能涉及到前端input框渲染,后端的数据校验,错误信息展示等,因此我们需要利用forms组件帮我们快速实现注册功能。

我们之前是直接在views.py中书写的forms组件代码,但是为了解耦合,应该将所有的forms组件代码单独写到一个地方。如果你的项目至始至终只用到一个forms组件,那么你可以直接建一个py文件书写即可。但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹,在文件夹内根据forms组件功能的不同创建不同的py文件,如下:

myforms文件夹
    -regform.py
    -loginform.py
    -userform.py
    -orderform.py

 

2 forms组件书写

在应用(app01)目录下,创建一个myforms.py文件,定义一个注册相关的forms类

from django import forms
from app01 import models


class MyRegForm(forms.Form):
    username = forms.CharField(max_length=8, min_length=4, label='用户名',
                               error_messages={
                                   "required": "用户名不能为空",
                                   "max_length": "用户名最长8位",
                                   "min_length": "用户名最短4位",
                               },
                               # 渲染input框,添加bootsrtap样式
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )
    password = forms.CharField(max_length=8, min_length=4, label='密码',
                               error_messages={
                                   "required": "密码不能为空",
                                   "max_length": "密码最长8位",
                                   "min_length": "密码最短4位",
                               },
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )
    confirm_password = forms.CharField(max_length=8, min_length=4, label='确认密码',
                                       error_messages={
                                           "required": "确认密码不能为空",
                                           "max_length": "确认密码最长8位",
                                           "min_length": "确认密码最短4位",
                                       },
                                       widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                                       )
    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 "required": "邮箱不能为空",
                                 "invalid": "邮箱格式不正确"
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))
    
    # 局部钩子,校验用户名是否存在
    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('confirm_password', '两次密码不一致')
        return self.cleaned_data

 

3 注册页面搭建

有了forms组件,在from表单内所有获取用户输入的标签都不需要自己写了,forms组件能自动渲染生成标签。

先在后端用定义的 MyRegForm类生成一个空对象,把空对象返回到前端页面,利用模板语法进行标签渲染。

views.py

from django.shortcuts import render
from app01.myforms import MyRegForm

# Create your views here.

def register(request):
    form_obj = MyRegForm()
    return render(request, 'register.html', locals())

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
    <title>Title</title>
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <h1 class="text-center">注册</h1>
                <form id="myform">  <!--不用form表单提交数据,只是单纯用form标签-->
                    {% csrf_token %}
                    {% for form in form_obj %}
                        <div class="form-group">
                            <lable for="">{{ form.label }}</lable>
                            {{ form }}
                            <span style="color: red">{{ form.errors.0 }}</span>
                        </div>
                    {% endfor %}
                </form>
                <div class="form-group">
                    <lable for="">头像</lable>
                    <input type="file" id="myfile" name="avatar">
                </div>
                <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
            </div>
        </div>

    </div>
</body>
</html>

初步的注册页面

注意:

前端html页面单纯用form标签渲染input框,而不用form表单直接提交数据(用ajax提交数据),因此没用acthion参数。

forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox),不能帮你渲染提交按钮,因此自己写一个button按钮。

头像字段并没有注册在forms组件里,因此需要自己渲染input框,type为文件类型。

 

4 用户头像展示

我们想让头像字段显示图片。新建static文件夹,再在static文件夹下创建一个img文件夹,拷贝一张默认的用户头像图片。

在register.html页面,头像input框下加上img标签,传入默认头像图片

<div class="form-group">
    <lable for="">头像</lable>
    <input type="file" id="myfile" name="avatar">
</div>
<img src="{% static 'img/default.png' %}" alt="" width="80" style="margin-left: 10px">
<input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">

页面显示如下:

要想实现上述3点需求,我们需要对register.html页面进行调整。

<div class="form-group">
    <label for="myfile">头像
        <img src="{% static 'img/default.png' %}" id="myimg" alt="" width="80" style="margin-left: 10px">
    </label>
    <input type="file" id="myfile" name="avatar" style="display: none">
</div>
<input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">

label标签,for参数关联input框的id,然后把img标签放到label标签内,这样点击头像和图片都能弹出文件选择框,最后用display参数隐藏input框。

页面展示如下:

我们在点击“头像”弹出文件选择框后,选中了图片点击确认,在注册页面显示出该图片(预览功能),需要给input框绑定一个文本域变化事件。

<script>
    // 给获取图片的input标签绑定一个文本域变化事件,一选择文件就触发
    $('#myfile').change(function () {
        // 利用文件阅读器对象获取图片文件并实时展示
        // 1、生成一个文件阅读器对象(内置对象)
        let myFileReaderObj = new FileReader();
        // 2、获取用户上传的头像文件,this指代的当前被操作对象$('#myfile'),jQurey对象先转成原生js对象,用files方法取索引0
        let fileObj = $(this)[0].files[0];
        // 3、将文件对象交给阅读器对象读取
        myFileReaderObj.readAsDataURL(fileObj);
        // 4、利用文件阅读器将文件展示到前端页面,即修改img标签的src属性
        // 注意:阅读器对象读取文件本身是IO操作,但它是异步提交,意味着文件仍在读取时,已经走到下一行代码修改img标签src属性,所以图片显示不出来
        // 因此,最后一步展示操作要等待文件阅读器加载完毕之后再执行,放在函数内执行
        myFileReaderObj.onload=function (){
            $('#myimg').attr('src', myFileReaderObj.result)
        }
    })
</script>

 

5 ajax提交数据

用ajax向后端发送post请求,发送的数据包括普通键值对和文件,需要借助于js内置对象FormData。

// button按钮绑定点击事件
    $('#id_commit').click(function (){
        // 1、生成文件对象
        let $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:'',
            type:'post',
            data:$formDataObj,
            contentType:false,
            processData:false,
            success:function (args){
                alert(args)}
        })
    })

5.1 serializeArray()方法

前端用forms组件渲染的标签,用form表单可以直接提交post请求,后端获取校验,并利用模板语法可以在前端提示错误信息。

form表单提交数据,文件对象添加普通键值对的方式如下,它能通过查找input标签,.val()获取输入值。

$formDateObj.append('username',$('#d1').val());
$formDateObj.append('password',$('#d2').val());

如果用ajax提交数据,无法利用标签.val()获取用户输入的值,因此需要用到 serializeArray()方法。

前端渲染的input标签都嵌套在一个form标签内,通过id查找到form标签,它有serializeArray()方法,这个方法可以一次性拿到form标签内所有的普通键值对,打包成数组套自定义对象的格式。

console.log($('#myform').serializeArray()) 

控制台输出结果为:

5.2 each()方法

通过serializeArray() 方法,获取到一个数组,数组里面包含的自定义对象,就是一个个普通键值对,利用循环取出数组里的自定义对象。

$.each(容器对象, function(index, obj){console.log(index, obj)})

上述方法,for循环容器对象,把里面的元素放到函数取处理;数组循环出来的index是索引,obj是对象(字典循环出来的index是键,obj是值)。

$.each($('#myform').serializeArray(), function (index, obj) {
            console.log(index, obj)
        })

控制台输出结果为:

循环出来的obj是 {name:'xxx',  value: 'yyy'} 自定义对象,取出键值对,添加到文件对象中去。

$.each($('#myform').serializeArray(), function (index, obj) {
           $formDateObj.append(obj.name, obj.value)
       })

5.3 后端操作

前端通过ajax发送post请求,发送了普通键值对和文件对象。后端接收并校验数据,如果校验通过,则操作数据库存储用户信息,并返回给前端操作成功的信息,如果校验未通过,则返回给前端操作失败的信息。

views.py

def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        # 前后端ajax进行数据交互,定义一个回调字典,返回状态码和信息,方便前端自定义对象取值
        back_dic = {'code': 0, 'msg': ''}
        # 调用MyRegForm将待校验的数据组织成字典的形式传入,request.POST就是字典,得到一个form对象
        form_obj = MyRegForm(request.POST)
        # 1 校验数据,只校验MyRegForm类定义的字段,多传的忽略
        if form_obj.is_valid():
            # 4个字段全部通过校验并以字典形式返回,赋值给一个变量,方便操作
            clean_data = form_obj.cleaned_data
            # confirm_password字段不用存储到数据库,先剔除
            clean_data.pop('confirm_password')
            """
            头像字段在FILES方法中获取并返回字符串形式的文件路径,把文件路径添加到字典中,然后存入数据库
            一定要判断是否传值,如果用户没上传文件,返回的是None,我们用默认路径即可,不用把None添加到字典
            如果用户不传值,这个字段有默认值,如果传入None就存成None了,默认头像就没有了
            """
            file_obj = request.FILES.get('avatar')
            if file_obj:
                clean_data['avatar'] = file_obj
            # 存入数据库,字典以**打散为key=value,符合create方法
            models.UserInfo.objects.create_user(**clean_data)
            # 注册成功,给回调字典添加一个字段,告诉前端指定跳转到'/login/'路由
            back_dic['url'] = '/login/'
        else:
            """
            如果不合法,说明有字段校验没通过,修改响应状态码,方便前端做判断
            并且返错回误信息,错误信息都在form_obj.errors中
            """
            back_dic['code'] = 1
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    # get请求来,把MyRegForm()空对象返回前端进行input标签渲染
    return render(request, 'register.html', locals())

5.4 回调函数处理

后端操作数据库后,给前端返回一个back_dic字典,里面包含有状态码、成功后跳转的url以及错误信息。前端通过状态码判断,做出对应的逻辑,如果返回成功信息,只需利用DOM操作跳转到返回的url即可,但如果返回的错误信息,如何将对应的错误提示展示到对应的input框下面???

如果用form表单提交post请求,返回的错误信息可以通过模板语法获取。但用ajax提交post请求,如何实现呢?我们先打印回调的信息看看它的格式。

success: function (args) {
    console.log(args.msg)
}

 输出如下:

可以看出,msg是一个自定义对象(字典),键是字段名,值是错误信息。问题是如何将错误提示展示到对应的input框下面?我们通过浏览器调试,去查看这些input标签。

这些input标签的id是有规律的,forms组件渲染的input标签的id值,都是id_字段名,而前端能够拿到msg自定义对象,恰巧有字段名。

利用each()方法,for循环自定义对象,index就是字段名,obj就是错误信息。利用字符串拼接,得到input标签的id,就能操作input标签。

success:function (args){
                if(args.code===0){
                    // 判断响应状态码,如果注册成功,跳转到指定页面
                    window.location.href=args.url
                }else{
                    $.each(args.msg, function (index, obj){
                        // console.log(index, obj)  ====> username ['用户名不能为空']
                        // 拼接出input标签的id,前面加#,方便直接用变量名查找标签
                        let targetId='#id_' + index;
                        // 找到input标签同级别的下一个标签,即span标签,为其添加文本,注意:obj是数组,用索引取值
                        // for循环会一一循环出对应的input与span
                        // 利用jQurey链式操作,找到span的父标签<div class="form-group">,添加一个类属性,让input框标红
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }

页面输入效果:

 细节处理一:label标签用模板语法 {{form.auto_id}},可以自动关联input标签的id。

<form id="myform"> 
    {% csrf_token %}
    {% for form in form_obj %}
    <div class="form-group">
        <label for="{{form.auto_id}}">{{ form.label }}</label>
        {{ form }}
        <span style="color: red" class="pull-right"></span>
    </div>
    {% endfor %}
</form>

关联之后,在页面上点字段名,相当于点击进入到input框内。

细节处理二:用户点击到input框内,取消掉span标签报错提示和框的标红,需要给所有的input框绑定获取焦点事件。

// 给所有的input框绑定获取焦点事件,点击input框,取消掉span标签报错提示和框的标红
$('input').focus(function (){
    // 将input标签同级别的下一个标签span标签和input外面的div标签修改内容及属性
    $(this).next().text('').parent().removeClass('has-error')
})

 

posted @   不会钓鱼的猫  阅读(40)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示