基于forms组件和Ajax实现注册功能
一、基于forms组件的注册页面设计
1、运用forms组件的校验字段功能实现用户注册
views.py: (在钩子中代码解耦,将form放在cnblog/blog/Myforms.py中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from django import forms from django.forms import widgets class UserForm(forms.Form): user = forms.CharField(max_length = 32 , label = "用户名" , widget = widgets.TextInput(attrs = { "class" : "form-control" },) ) pwd = forms.CharField(max_length = 32 , label = "密码" , widget = widgets.PasswordInput(attrs = { "class" : "form-control" }, ) ) re_pwd = forms.CharField(max_length = 32 , label = "确认密码" , widget = widgets.PasswordInput(attrs = { "class" : "form-control" },) ) email = forms.EmailField(max_length = 32 , label = "邮箱" , widget = widgets.EmailInput(attrs = { "class" : "form-control" },) ) def register(request): form = UserForm() # 实例化form对象 return render(request, "register.html" , { "form" : form}) # 注意要传入的是一个字典 |
注意:
(1)在视图层引入widgets模块,配置修改forms类参数。在这里添加了class="form-control"的bootstrap样式。添加了label标签。
(2)这里register视图,实例化的form对象是未绑定数据的form表单。将这个form对象传入render中,注意传入的格式必须是字典的形式。
2、forms表单渲染标签生成注册页面
register.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >注册页面</ title > < link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> </ head > < body > < h3 >注册页面</ h3 > < div class="container"> < div class="row"> < div class="col-md-6 col-lg-offset-3"> < form action=""> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} < div class="form-group"> < label for="user">{{ field.label }}</ label > {{ field }} </ div > {% endfor %} < div class="form-group"> < label for="avator">头像</ label > < input type="file"> </ div > < input type="button" class="btn btn-default login-btn" value="提交">< span class="error"></ span > </ form > </ div > </ div > </ div > < script src="/static/js/jquery-3.3.1.js"></ script > </ body > </ html > |
显示效果如下所示:
二、博客注册页面头像
1、默认头像
register.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >注册页面</ title > < link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> < style > #avator_img { margin-left: 20px; } #avator { display: none; } </ style > </ head > < body > < h3 >注册页面</ h3 > < div class="container"> < div class="row"> < div class="col-md-6 col-lg-offset-3"> < form action=""> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} < div class="form-group"> < label for="{{ field.auto_id }}">{{ field.label }}</ label > {{ field }} </ div > {% endfor %} < div class="form-group"> < label for="avatar"> 头像 < img id="avatar_img" width="60" height="60" src="/static/blog/img/default.png" alt=""> </ label > < input type="file" id="avatar"> </ div > < input type="button" class="btn btn-default reg-btn" value="提交">< span class="error"></ span > </ form > </ div > </ div > </ div > < script src="/static/js/jquery-3.3.1.js"></ script > </ body > </ html > |
显示效果:
注意:
(1)添加头像上传和默认头像时,需要优化显示效果,点击默认头像,显示文件上传框图。这里应用的方法是巧妙使用label标签,将img标签嵌套在label标签中,设置<label for="">中的值与input标签的id相同。实现点击label标签就会默认点击到了input标签。设置完后,隐藏input标签的显示。
(2)<label for="{{ field.auto_id }}"></label>:如果在循环user,filed.auto_id,返回的就是user_id。
2、头像预览功能
首先需要了解jquery事件的change()方法:当元素的值发生改变时,会发生 change 事件;当用于 select 元素时,change 事件会在选择某个选项时发生。当用于 text field 或 text area 时,该事件会在元素失去焦点时发生。
1 2 3 4 5 | <script> $( "#avatar" ).change( function () { // change方法,当输入域发生变化时改变其颜色(在这里是上传图片时触发) alert(123); }) </script> |
上传图片:
获取input标签用户选中的文件对象:
(1)获取用户选中的文件对象
1 2 | // 获取用户选中的文件对象 var file_obj = $( this )[0].files[0]; |
(2)获取文件对象的路径
1 2 3 4 5 6 7 8 9 10 11 | <script> // 头像预览 $( "#avatar" ).change( function () { // 获取用户选中的文件对象 var file_obj = $( this )[0].files[0]; // 获取文件对象路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // reader.result; // 读url的结果保存在result中 </script> |
注意:readerAsDataURL()阅读文件的路径。读完url的结果保存在reader.result中。
(3)修改img的src属性,src=文件对象的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <script> // 头像预览 $( "#avatar" ).change( function () { // change方法,当输入域发生变化时改变其颜色(在这里是上传图片时触发) // 获取用户选中的文件对象 var file_obj = $( this )[0].files[0]; // 获取文件对象路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // reader.result; // 读url的结果保存在result中 reader.onload= function () { // // 修改img的src属性,src=文件对象的路径 $( "#avatar_img" ).attr( "src" , reader.result) // 等事件完成之后再执行 }; }) </script> |
显示效果:
注意:
(1)window.onload()等DOM执行完后,再来执行内部的代码;
(2)reader.readAsDataURL();这个读取的时间比较长,同时,这个只是其中一个线程,如果直接执行
1 | $( "#avatar_img" ).attr( "src" , reader.result) |
这个语句会与readAsDataURL方法并发执行,由于前面方法还没有完成,reader.result还只是一个空值。
(3)因此需要做一个load事件,等readAsDataURL读取完后再执行
1 | $( "#avatar_img" ).attr( "src" , reader.result) |
三、错误信息展示——基于Ajax提交formdata数据
1、在register.html中编写注册提交事件(基于Ajax提交数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 基于Ajax提交数据 $( ".reg-btn" ).click( function () { var formdata = new FormData(); formdata.append( "user" , $( "#id_user" ).val()); formdata.append( "pwd" , $( "#id_pwd" ).val()); formdata.append( "re_pwd" , $( "#id_re_pwd" ).val()); formdata.append( "email" , $( "#id_email" ).val()); formdata.append( "avatar" , $( "#avatar" )[0].files[0]); // 拿到用户传递的文件对象 formdata.append( "csrfmiddlewaretoken" , $( "[name='csrfmiddlewaretoken']" ).val()); $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, // 将formdata对象放在data里面发送 success: function (data) { console.log(data) } }) |
注意:
1)给注册按钮绑定一个click事件;
2)由于上传的数据中包含一个图片文件,类型必须换为multipart/form-data格式,不再使用form-urlencoded 方式提交数据;
3)构建的formdata对象:
1 | var formdata = new FormData(); |
4)给对象添加键值:
1 | formdata.append( "user" , $( "#id_user" ).val()); |
5)添加文件对象:
1 | formdata.append( "avatar" , $( "#avatar" )[0].files[0]); // 拿到用户传递的文件对象 |
("#avatar")[0].files[0]得到用户选中的文件对象(jquery取上传文件固定语法 :$("#avatar")[0].files[0]);
6)基于Ajax文件上传,传formdata数据一定要写下面这两个参数:
1 2 | contentType: false , // 在ajax这一层不做任何编码(不设置内容类型) processData: false , // 在ajax这一层不做预处理(不处理数据) |
7)要避免forbidden报错,需要添加csrf_token,查看模板中写入的{% csrf_token %}在页面中显示:
然后,添加csrf_token键值:
1 | formdata.append( "csrfmiddlewaretoken" , $( "[name='csrfmiddlewaretoken']" ).val()); |
2、在视图中处理Ajax请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def register(request): if request.is_ajax(): # 也可以使用 if request.method == "POST": # 如果是一个Ajax请求 print (request.POST) # 所有提交的数据 form = UserForm(request.POST) # 创建UserForm对象,传入当前提交的字典 response = { "user" : None , "msg" : None } if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true) # 类定义的字段全符合要求,返回true response[ "user" ] = form.cleaned_data.get( "user" ) else : # 包含错误信息返回false print (form.cleaned_data) # 字段值符合要求的放在cleaned_data print (form.errors) # 字段不符合要求的对应的键作为键,错误信息作为值 response[ "msg" ] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html" , { "form" : form}) # 注入要传入的是一个字典 |
在视图函数register中处理Ajax提交的请求需要注意的是:
1)formdata提交给register处理,即是post请求,又是ajax请求,因此可以用两种方式来获取;
2)form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true);
展示效果如下所示:
3、基于Ajax提交formdata数据的优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 基于Ajax提交数据 $( ".reg-btn" ).click( function () { var formdata = new FormData(); var request_data = $( "#form" ).serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操作 formdata.append(data.name, data.value) }); formdata.append( "avatar" , $( "#avatar" )[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { console.log(data) } }) }) |
控制台输出如下:
注意:
(1)jQuery ajax - serializeArray() 方法
serializeArray() 方法通过序列化表单值来创建对象数组(名称和值)。可以选择一个或多个表单元素(比如 input 及/或 textarea),或者 form 元素本身。
在register.html中给form元素添加id="form"。在登录按钮的点击事件中添加:
1 2 | var ret = $( "#form" ).serializeArray(); console.log(ret); |
浏览器控制台输出如下:
打印出一个列表,列表中保存一个个object类型,每个object类型都有name和value.
(2)jqeury的遍历方法:.each()
each() 方法为每个匹配元素规定运行的函数。返回 false 可用于及早停止循环。
由于serializeArray()返回的是一个数组,因此这里each处理的request_data是一个数组。each方法会对数组中子元素的逐个进行fn函数调用,直至调用某个子元素返回的结果为false
$.each(request_data, function (index, data) {}在这里index表示数组当前下标,data则表示数组当前元素。
4、基于Ajax在注册页面显示错误信息
(1) 在register.html中添加注册失败信息<span>标签:
1 2 3 4 5 6 7 8 9 10 11 | < form id="form"> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} < div class="form-group"> < label for="{{ field.auto_id }}">{{ field.label }}</ label > {{ field }} < span class="error pull-right"></ span > </ div > {% endfor %} ...... < form > |
(2)register.html中的注册点击事件ajax请求回调函数做如下处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 基于Ajax提交数据 $( ".reg-btn" ).click( function () { var formdata = new FormData(); var request_data = $( "#form" ).serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操作 formdata.append(data.name, data.value) }); formdata.append( "avatar" , $( "#avatar" )[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); $( "#id_" +field).next().html(error_list[0]); }) } } }) }) |
注意:
1)在回调函数中处理注册信息,success:function(data){}中的data就是视图函数register中返回的response对象:
1 2 3 4 5 | from django.http import JsonResponse def register(request): if request.is_ajax(): ... return JsonResponse(response) |
字典放进去直接序列化,ajax拿到的就是对象,两边都不需要进行json的序列化与反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); } } }) |
在视图函数中,当校验成功时,会给字典response的键user赋值,否则仍为None。Ajax中依此判断注册是否成功。
2)当注册失败时,处理视图函数中返回的response的键msg的值(错误信息):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list) }) } } }) |
浏览器控制台输出如下:
3)由于错误字段的名字和input标签的id相同:可以用"id"和field拼接为id_user、id_re_pwd等input标签的id值。
又由于error_list是一个数组,且数组中保存的是一个字符串,因此只需要取数组的第一条记录即可:
1 | $( "#id_" +field).next().html(error_list[0]); |
(3)给错误信息设置样式
1 2 3 4 5 6 7 8 9 10 11 | <style> #avatar_img { margin-left : 20px ; } #avatar { display : none ; } .error { color : red ; } </style> |
显示效果如下:
(4)每次点击提交,在展示span标签的错误信息前,都应该将所有span标签清空一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg);<br> // 清空错误信息 $( "span.error" ).html( "" );<br> $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); $( "#id_" +field).next().html(error_list[0]); }) } } }) |
这样在第一次所有注册信息都错误,第二次注册填写的用户名正确时,就只会显示当前错误的提示了。
(5)进一步优化,让错误的注册信息的input标签边框变红
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // 基于Ajax提交数据 $( ".reg-btn" ).click( function () { var formdata = new FormData(); var request_data = $( "#form" ).serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操作 formdata.append(data.name, data.value) }); formdata.append( "avatar" , $( "#avatar" )[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); // 清空错误信息 $( "span.error" ).html( "" ); $( ".form-group" ).removeClass( "has-error" ); // 展示此次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); $( "#id_" +field).next().html(error_list[0]); $( "#id_" +field).parent().addClass( "has-error" ); }) } } }) }) |
显示效果:
注意:
1)对input标签的父标签添加bootstrap的类has-error。
Bootstrap 对表单控件的校验状态,如 error、warning 和 success 状态,都定义了样式。使用时,添加 .has-warning
、.has-error
或 .has-success
类到这些控件的父元素即可。
2)清空错误信息时也需要同时清掉class = "form-group"的组件的表框变红样式
1 2 3 4 5 6 7 8 9 | // 清空错误信息 $( "span.error" ).html( "" ); $( ".form-group" ).removeClass( "has-error" ); // 展示此次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); $( "#id_" +field).next().html(error_list[0]); $( "#id_" +field).parent().addClass( "has-error" ); }) |
5、forms组件的局部钩子和全局钩子校验
(1)首先代码解耦
创建/cnblog/blog/Myforms.py文件存放forms组件和钩子,并在views.py中引入forms
1 | from blog.Myforms import UserForm |
(2)应用引入widgets模块,配置修改forms类参数
在字段构造参数中设置"error_messages"修改字段错误提示消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from django import forms from django.forms import widgets from blog.models import UserInfo class UserForm(forms.Form): user = forms.CharField(max_length = 32 , error_messages = { "required" : "该字段不能为空" }, label = "用户名" , widget = widgets.TextInput(attrs = { "class" : "form-control" },) ) pwd = forms.CharField(max_length = 32 , label = "密码" , widget = widgets.PasswordInput(attrs = { "class" : "form-control" }, ) ) re_pwd = forms.CharField(max_length = 32 , label = "确认密码" , widget = widgets.PasswordInput(attrs = { "class" : "form-control" },) ) email = forms.EmailField(max_length = 32 , label = "邮箱" , widget = widgets.EmailInput(attrs = { "class" : "form-control" },) ) |
其中"requried"负责字段不能为空消息提示;"invalid"负责格式错误消息提示等.
(3)添加局部钩子,校验用户是否注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from django import forms from django.forms import widgets from blog.models import UserInfo from django.core.exceptions import ValidationError class UserForm(forms.Form): ... # 局部钩子 def clean_user( self ): val = self .cleaned_data.get( "user" ) user = UserInfo.objects. filter (username = val).first() if not user: return val else : raise ValidationError( "该用户已注册!" ) |
显示效果:
注意:
1)引入ValidationError模块
1 | from django.core.exceptions import ValidationError |
2)val = self.cleaned_data.get("user") 在用户输入的用户名不为空时,符合字段要求,因此添加在cleaned_data中,通过此方法可以拿到用户输入的用户名。
3)再根据这个用户名去数据库表blog_userinfo中查找相关用户:
1 | user = UserInfo.objects. filter (username = val).first() |
如果这个用户名不存在则通过校验,如果存在则通过ValidationError设置错误提示:
1 2 3 4 | if not user: return val else : raise ValidationError( "该用户已注册!" ) |
4)需要注意的是当检验通过时,需要返回的是用户输入的用户名而不是数据库中查找的记录,如果返回的值有问题,在生成用户记录时,往往会报ValueError: The given username must be set。这样的错误。
(4)添加全局钩子——检验两次密码不一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | from django import forms from django.forms import widgets from blog.models import UserInfo from django.core.exceptions import ValidationError class UserForm(forms.Form): ...... # 局部钩子 def clean_user( self ): user = self .cleaned_data.get( "user" ) user = UserInfo.objects. filter (username = user).first() if not user: return user else : raise ValidationError( "该用户已注册!" ) # 全局钩子 def clean( self ): pwd = self .cleaned_data.get( "pwd" ) re_pwd = self .cleaned_data.get( "re_pwd" ) if pwd and re_pwd: # 如果两个都有值 if pwd = = re_pwd: # 验证成功 return self .cleaned_data else : # 验证失败 raise ValidationError( "两次密码不一致!" ) else : # 如果任有一个没有值则不做处理 return self .cleaned_data |
注意:
1)在register.html中ajax回调函数中,通过__all__取到全局错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); // 清空错误信息 $( "span.error" ).html( "" ); $( ".form-group" ).removeClass( "has-error" ); // 展示此次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); if (field == "__all__" ){ // 全局错误信息 $( "#id_re_pwd" ).next().html(error_list[0]).parent().addClass( "has-error" ); } $( "#id_" +field).next().html(error_list[0]); $( "#id_" +field).parent().addClass( "has-error" ); }) } } }) |
查看页面信息:
2)如上所示在找到field=="__all__"的情况下,在id=“id_re_pwd”的input标签的下一个兄弟标签即span标签中填写错误信息,并在input的标签的父标签div中添加类has_error,修改input标签的显示样式。
1 2 3 4 | if (field == "__all__" ){ // 全局错误信息 $( "#id_re_pwd" ).next().html(error_list[0]).parent().addClass( "has-error" ); } |
3)当两个密码都有输入时,验证两个密码是否一致;当两个密码任有一个没输入则不做处理
1 2 3 4 5 6 7 8 9 10 11 | if pwd and re_pwd: # 如果两个都有值 if pwd = = re_pwd: # 验证成功 return self .cleaned_data else : # 验证失败 raise ValidationError( "两次密码不一致!" ) else : # 如果任有一个没有值则不做处理 return self .cleaned_data |
显示效果:
四、数据校验通过——FileField字段处理
1、在register.html的ajax回调函数中注册成功时跳转到登录页面登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $.ajax({ url: "" , type: "post" , contentType: false , processData: false , data: formdata, success: function (data) { // console.log(data) if (data.user){ // 注册成功,跳转登录页面 location.href = "/login/" } else { // 注册失败 console.log(data.msg); // 清空错误信息 $( "span.error" ).html( "" ); $( ".form-group" ).removeClass( "has-error" ); // 展示此次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每个字段字符串; 值:错误信息列表 console.log(field, error_list); if (field == "__all__" ){ // 全局错误信息 $( "#id_re_pwd" ).next().html(error_list[0]).parent().addClass( "has-error" ); } $( "#id_" +field).next().html(error_list[0]); $( "#id_" +field).parent().addClass( "has-error" ); }) } } }) |
2、在视图函数register中处理校验通过后,用户注册操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from blog.Myforms import UserForm from blog.models import UserInfo def register(request): if request.is_ajax(): # 也可以使用 if request.method == "POST": # 如果是一个Ajax请求 print (request.POST) # 所有提交的数据 form = UserForm(request.POST) # 创建UserForm对象,传入当前提交的字典 response = { "user" : None , "msg" : None } if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true) # 类定义的字段全符合要求,返回true response[ "user" ] = form.cleaned_data.get( "user" ) # 生成一条用户记录 user = form.cleaned_data.get( "user" ) pwd = form.cleaned_data.get( "pwd" ) email = form.cleaned_data.get( "email" ) avatar_obj = request.FILES.get( "avatar" ) user_obj = UserInfo.objects.create_user(username = user, password = pwd, email = email, avatar = avatar_obj) # 用户认证组件使用create_user辅助函数创建用户 else : # 包含错误信息返回false print (form.cleaned_data) # 字段值符合要求的放在cleaned_data print (form.errors) # 字段不符合要求的对应的键作为键,错误信息作为值 response[ "msg" ] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html" , { "form" : form}) # 注入要传入的是一个字典 |
注意:
(1)需要在视图中引入模型
1 | from blog.models import UserInfo |
(2)form.cleaned_data:字段值符合要求的放在cleaned_data中。字典数据类型。由于校验通过,所以用户名、密码、邮箱信息都可以用如下方法拿到:
1 2 3 4 | # 生成一条用户记录 user = form.cleaned_data.get( "user" ) pwd = form.cleaned_data.get( "pwd" ) email = form.cleaned_data.get( "email" ) |
(3)django操作models.FileField数据类型:
1 2 3 4 5 6 | class UserInfo(AbstractUser): ...... avatar = models.FileField(upload_to = 'avatars/' , default = "/avatars/default.png" ) # 该字段存放每个用户的头像文件 def __str__( self ): return self .username |
FileField与ImageField用法类似,FileField这个字段一定要接受一个文件对象;ImageField一定要接受一个图片对象。
接受文件对象,django会做的事情:默认下载文件到项目的根目录;设置upload_to字段后,会下载到项目根目录的avatars目录下(没有该目录则创建一个)。
user_obj的avatar存的是文件的相对路径
(4)用户认证组件使用create_user辅助函数创建用户
1 | user_obj = UserInfo.objects.create_user(username = user, password = pwd, email = email, avatar = avatar_obj) |
3、注册验证
(1)注册完成后页面跳转到登录页面
(2)数据库的blog_userinfo表产生一条新的用户数据,且avatar保存的确实是文件的相对路径
(3)设置upload_to字段后,会下载到项目根目录的avatars目录下,没有提前创建目录的情况下会自动创建:
五、media配置——MEDIA_ROOT
1、Django静态文件区分
Django有两种静态文件:
/static/:服务器自己用到的文件,如:js,css,img
/media/:用户上传的文件
2、在settings.py中配置MEDIA_ROOT
1 | MEDIA_ROOT = os.path.join(BASE_DIR, "media" ) |
一旦配置了MEDIA_ROOT,Django会将文件对象下载到MEDIA_ROOT中的avatar目录中。如果没有avatar文件夹,Django也会自动创建,user_obj的avatar存的是文件对象。
3、注册一个新用户项目效果
4、media配置的用处
在使用FileFIeld或者ImageField时,上传实例化的对象,所有上传的文件都会放在media/"定义的文件夹名称"/,与服务器的文件分开,耦合性非常好!
5、根据头像上传不同情况生成用户对象
(1)在注册时,考虑到有的人没有上传头像,可以做如下处理
首先将default.png拷到/media/avatars/目录下,
然后修改register视图函数在生成用户记录时做如下判定:
1 2 3 4 5 6 7 8 9 10 11 12 | # 生成一条用户记录 user = form.cleaned_data.get( "user" ) pwd = form.cleaned_data.get( "pwd" ) email = form.cleaned_data.get( "email" ) avatar_obj = request.FILES.get( "avatar" ) if avatar_obj: # 如果上传了头像,即有值 # 用户认证组件使用create_user辅助函数创建用户 user_obj = UserInfo.objects.create_user(username = user, password = pwd, email = email, avatar = avatar_obj) else : # 如果没有传头像,就用默认的default.png user_obj = UserInfo.objects.create_user(username = user, password = pwd, email = email) |
(2)生成用户对象代码优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | from blog.Myforms import UserForm from blog.models import UserInfo def register(request): if request.is_ajax(): # 也可以使用 if request.method == "POST": # 如果是一个Ajax请求 print (request.POST) # 所有提交的数据 form = UserForm(request.POST) # 创建UserForm对象,传入当前提交的字典 response = { "user" : None , "msg" : None } if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true) # 类定义的字段全符合要求,返回true response[ "user" ] = form.cleaned_data.get( "user" ) # 生成一条用户记录 user = form.cleaned_data.get( "user" ) pwd = form.cleaned_data.get( "pwd" ) email = form.cleaned_data.get( "email" ) avatar_obj = request.FILES.get( "avatar" ) extra = {} # 空字典 if avatar_obj: extra[ "avatar" ] = avatar_obj # 传值进空字典 UserInfo.objects.create_user(username = user, password = pwd, email = email, * * extra) # **extra代表额外的参数 """ if avatar_obj: # 如果上传了头像,即有值 # 用户认证组件使用create_user辅助函数创建用户 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # 如果没有传头像,就用默认的default.png user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email) """ else : # 包含错误信息返回false print (form.cleaned_data) # 字段值符合要求的放在cleaned_data print (form.errors) # 字段不符合要求的对应的键作为键,错误信息作为值 response[ "msg" ] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html" , { "form" : form}) # 注入要传入的是一个字典 |
注意:
1)create_user方法的源代码如下:
1 2 3 4 | def create_user( self , username, email = None , password = None , * * extra_fields): extra_fields.setdefault( 'is_staff' , False ) extra_fields.setdefault( 'is_superuser' , False ) return self ._create_user(username, email, password, * * extra_fields) |
说明额外的参数通过键值对的方式传入即可。因此创建空字典extra。
2)由于user_obj并没有用到,因此删除这个赋值。
六、MEDIA配置——MEDIA_URL
客户端浏览器可以访问到static下的数据,但如何能直接访问到media中的数据。
1、在settings.py中配置一个MEDIA_URL
1 | MEDIA_URL = "/media/" |
2、在路由控制中添加media配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from django.contrib import admin from django.urls import path,re_path from django.views.static import serve from blog import views from cnblog import settings urlpatterns = [ path( 'admin/' , admin.site.urls), path( 'login/' , views.login), path( 'index/' , views.index), path( 'get_validCode_img/' , views.get_validCode_img), path( 'register/' , views.register), # media配置 # 匹配以media开头的任意路径 re_path(r "media/(?P<path>.*)$" , serve, { "document_root" : settings.MEDIA_ROOT}) ] |
3、显示效果
七、代码格式规范
1、导包规范
导入的包排列顺序是:python标准库、第三方插件的包、自己定义的包。
2、在代码中去除print
变量解释可以放在函数下注释来描述:
3、遵循PEP8 Python 编码规范
option+command+L,Python编辑器自动规范代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术