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')
})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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训练数据并当服务器共享给他人