整体设计理念
1. 基于Ajax和forms组件实现注册功能1. form组件的作用:校验字段值,渲染标签2. 三种渲染标签的方法:for循环渲染,逐个字段渲染2. 实现过程---点击头像===点击选择文件按钮---头像预览:1. 获取用户选择的文件对象2. 获取文件对象的路径3. 修改img标签的src属性,使得src==文件对象的路径3. 展示错误信息[通过H5标签绑定数据上传和错误处理,相当于中间表]1. views:form.errors # {"#user":[......]}2. Ajax.success:$.each(data.msg, function (field, error_list) {$("#id_" + field).next().html(error_list[0]);$("#id_" + field).parent().addClass("has-error");}3. 局部钩子和全局钩子的校验1. user字段不能重复2. 两次密码不一致4. FileField 与ImageField 前者传任何文件,后者只能传图片文件[models]class UserInfo(AbstractUser):"""用户信息"""nid = models.AutoField(primary_key=True)telephone = models.CharField(max_length=11, null=True, unique=True)# 存储用户头像文件,创建并且放置在avatars文件夹下avatar = models.FileField(upload_to='avatars/', default="avatars/default.png")# 在生成字段时就以当前时间存储,用于计算园龄create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)# 业务分离,用户名下blog删除,则站点blog同时删除blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.PROTECT,related_name="UserInfo_blog")[views视图]avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj)Django会做的事情:1. 将文件下载到项目文件的根目录
5 media配置1. 静态文件的分类1. static jss img css 系统前端支持文件2. media 用户上传的文件2. settings中的配置用户是用户,服务器是服务器添加此路径之后,上传文件之后会首先在media下创建models指定的avatar文件夹,再放入用户上传的头像信息3. 用户如何从外部访问static的文件由static_url管理接入,该配置可自定义,若禁用则无法通过该方法访问静态文件4. media的配置与static有何不同?前者的URL起着前缀的作用,是一种映射关系后者是一种解释作用,直接包含在路径中,可以直接修改[系统安全防护级别不一样]
第一个版本
设计理念
在views中使用form表单制造字段,在前端修改页面,使用模板标签循环渲染字段
代码
# viewsfrom django import formsclass UserForm(forms.Form):"""均为登录时需要校验的字段"""user = forms.CharField(max_length=32)pwd = forms.CharField(max_length=32)re_pwd = forms.CharField(max_length=32)email = forms.EmailField(max_length=32)def registry(request):# 实例化对象,然后传递给蒙版form = UserForm()return render(request, "blog/registry.html", {"form":form})# registry页面<!DOCTYPE html>{% load static %}<html lang="en"><head><meta charset="UTF-8"><title>注册</title><link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}"></head><body><h3>Welcome to Blog of Caesar Tylor</h3><div class="container"><div class="row"><div class="col-md-6 col-lg-offset-3"> {# 占用六个,右倾 #}<form> {# action不再定义,基于Ajax提交 #}{% csrf_token %} {# 增加CSRF防护令牌 #}{% for field in form %}<div class="form-group">{# 依次渲染user pwd re_pwd#}<label for="user">{{ field.label }}</label>{{ field }}</div>{% endfor %}</form></div></div></div><script src="{% static '/static/js/jquery-3.6.0.min.js' %}"></script><script></script></body></html>
实现效果
第二个版本
前端调试—美化输入框
调试方法
后端view修改
from django import formsfrom django.forms import widgetsclass UserForm(forms.Form):"""均为登录时需要校验的字段"""user = forms.CharField(max_length=32, widget=widgets.TextInput(attrs={"class":"form-control"}))pwd = forms.CharField(max_length=32, widget=widgets.PasswordInput(attrs={"class":"form-control"}))re_pwd = forms.CharField(max_length=32, widget=widgets.PasswordInput(attrs={"class":"form-control"}))email = forms.EmailField(max_length=32, widget=widgets.EmailInput(attrs={"class":"form-control"}))def registry(request):# 实例化对象,然后传递给蒙版form = UserForm()return render(request, "blog/registry.html", {"form":form})
实现效果

小型增补—英文标记为中文
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"}))
实现效果

小型增补—增加图像上传的初步功能
前端新增代码
<form> {# action不再定义,基于Ajax提交 #}{% csrf_token %} {# 增加CSRF防护令牌 #}{% for field in form %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="user">{{ field.label }}</label>{{ field }}</div>{% endfor %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="avatar">头像</label><input type="file"></div><input type="button" class="btn btn-default login_btn" value="submit"></form>
前端效果

第三个版本
增加点击头像等同于选择文件标签的功能
前端观察字段写法

for模板标签渲染的页面中,每一个ID都是在原有名字的基础上添加一个ID前缀,为了使得for字段与ID保持一致,对源码做如下修改
后端代码修改
<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="{{ field.auto_id }}">{{ field.label }}</label>{{ field }}</div>{% endfor %}
修改后的效果

隐藏选择文件的标签
后端代码
<form> {# action不再定义,基于Ajax提交 #}{% csrf_token %} {# 增加CSRF防护令牌 #}{% for field in form %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="{{ field.auto_id }}">{{ field.label }}</label>{{ field }}</div>{% endfor %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="avatar">头像<img width="200" height="200" src="{% static '/static/blog/img/default_avatar.png' %}" alt=""></label><input type="file" id="avatar" style="display: none"></div><input type="button" class="btn btn-default login_btn" value="submit"></form>
实现效果

头像预览效果
实现逻辑
使用Ajax绑定事件
前端调试

后端代码调整
<script>$("#avatar").change(function(){// 获取用户选中的文件对象var file_obj = $(this)[0].files[0];// 获取文件对象的路径---使用文件阅读器var reader = new FileReader();reader.readAsDataURL(file_obj);// 修改img的src属性,src=文件对象路径$("#avatar_img").attr("src",reader.result);})</script>
前端调试路径替换

可能存在的问题
无法正确预览图片,异步执行修改时未能加载图片[个人理解为上传至服务器的时间差]
前端图像预览为空

预览图片成功

后端代码及解决方案
在异步线程中设置事件等待执行
<script>$("#avatar").change(function(){// 获取用户选中的文件对象var file_obj = $(this)[0].files[0];// 获取文件对象的路径---使用文件阅读器,异步线程,修改src也在执行var reader = new FileReader();reader.readAsDataURL(file_obj);// 修改img的src属性,src=文件对象路径,等待以上操作执行完毕再修改属性reader.onload=function (){$("#avatar_img").attr("src",reader.result);}})</script>
第四个版本
注册时实现提交选框数据
预期效果

实现逻辑
1. 在注册页面添加一个Ajax事件,由它提交数据给form表单
前端代码—registry
// 基于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]);$.ajax({url:"", //使用当前的URLtype:"post", //网络请求的方式contentType:false,processData:false,data:formdata,success:function(data){console.log(data)}})})
后端代码—views.py
def registry(request):"""如果提交的数据错误,则由一个字典在原页面上显示提示信息"""if request.is_ajax():print(request.POST)form = UserForm(request.POST) # 由UserForm做验证response={"user":None,"msg":None}if form.is_valid():response["user"]=form.cleaned_data.get("user")else:print(form.cleaned_data)print(form.errors)response["msg"] = form.errorsreturn JsonResponse(response, "/blog/registry.html",{"form":form})# 实例化对象,然后传递给蒙版form = UserForm()return render(request, "blog/registry.html", {"form":form})
第一次前端测试—console提示禁止访问

解决方案—Ajax添加CSRF—token
$(".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中重复代码
需求分析
1. 一个表单中会提交多个选框中的数据,那就意味着为每一个选框指定一次数据添加2. 产生需求:能否自动化提取多个选框的数据,然后使用for循环将数据填入数据对象当中?
具体实现
# 保存现场$(".reg_btn").click(function(){console.log($("#form").serializeArray());// 构建一个变量获取用户输入的值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("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());formdata.append("avatar",$("#avatar")[0].files[0]);# 第二种方法$(".reg_btn").click(function(){//console.log($("#form").serializeArray());var formdata = new FormData();var request_data = $("#form").serializeArray();$.each(request_data, function (index, data) {formdata.append(data.name, data.value)});formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());formdata.append("avatar", $("#avatar")[0].files[0]);
预期实现的功能:将报错放置在各自字段的后面
前端展示

明确预期结果搞清楚出问题的环节 逻辑和代码错误,【字段指定错误】没有找到指定的标签可以直接取标签和字段,不加ID,不加#没问题,只有一个form标签,#指的是根据ID取对象jquery 取标签部分的知识
最终实现的效果

后端Ajax代码
if(data.user){// 注册成功}else{// 注册失败;取出所有错误{#console.log(data)#}$.each(data.msg, function (field,error_list){console.log(field, error_list);$("#id_"+field).next().html(error_list[0])})}
后端CSS效果
# 选框控制字段的修改<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="{{ field.auto_id }}">{{ field.label }}</label>{{ field }} <span class="error pull-right"></span></div># 增加CSS效果,设置字体为红色<head><meta charset="UTF-8"><title>注册</title><link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}"><style>#avatar_img {margin-left: 80px;}#avatar {display: none;}.error {color: red;}</style></head>
部分注册信息通过时取消该选框的错误提示
预期实现的效果

后端代码—第十五行为本次修改的代码
实现逻辑:前端提交错误信息时,先清空本次传递的错误信息,然后重新检查字段,传递错误提示信息
$.ajax({url: "", //使用当前的URLtype: "post", //网络请求的方式contentType: false,processData: false,data: formdata,success: function (data) {{#console.log(data);#}if (data.user) {// 注册成功} else {// 注册失败;取出所有错误{#console.log(data)#}// 在针对字段添加错误提示之前先清空提交时传递的错误信息$("span.error").html("");$.each(data.msg, function (field, error_list) {console.log(field, error_list);// 使用字段拼接来实现字段的遍历指定$("#id_" + field).next().html(error_list[0])})}}})
修改后的效果

未填写的选框变红
预期效果

前端调试预期结果

调试代码
$("#id_user")S.fn.init [input#id_user.form-control]$("#id_user").parent()S.fn.init [div.form-group, prevObject: S.fn.init(1)]$("#id_user").parent().addClass("has-error")
清除提交后又填入选框信息后仍然显示红色
需求预览

后端代码—第十六行为新增代码
$.ajax({url: "", //使用当前的URLtype: "post", //网络请求的方式contentType: false,processData: false,data: formdata,success: function (data) {{#console.log(data);#}if (data.user) {// 注册成功} else {// 注册失败;取出所有错误{#console.log(data)#}// 在针对字段添加错误提示之前先清空提交时传递的错误信息$("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");})}}})
最终结果

第五个版本
整体功能设计
预期实现的功能:将注册时提交的用户名与数据库存储的用户信息表进行比对,看是否已经存在
验证重复密码是否与第一次输入时一致
对form表单数据解耦,建立单独文件
Myforms,不再放置于views.py文件当中局部钩子和全局钩子:可能指的是提示信息针对的字段,在H5中以标签作为对象呈现,具体在all中
第一个设计:功能解耦
Myforms.py文件中的内容
注意引入两个包,分别是forms 和widgets
# -*- coding: utf-8 -*-# @Time : 2021/8/27 10:10# @Author : 41999# @Email : 419997284@qq.com# @File : Myforms.py# @Project : whereaboutsfrom django import formsfrom django.forms import widgetsclass 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"}))
在views.py中引入外部文件Myforms.py
from django import formsfrom django.forms import widgetsfrom blog.Myforms import UserFormdef registry(request):"""如果提交的数据错误,则由一个字典在原页面上显示提示信息"""if request.is_ajax():print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>form = UserForm(request.POST) # 由UserForm做验证response={"user":None,"msg":None} # 用于前端交互,传递messageif form.is_valid():response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名else:print(form.cleaned_data)print(form.errors)response["msg"] = form.errorsreturn JsonResponse(response)# 实例化对象,然后传递给蒙版form = UserForm()return render(request,"blog/registry.html",{"form":form})
第二个设计:自定义字段未提交时的错误提示信息
代码—第十二行是新增信息
# -*- coding: utf-8 -*-# @Time : 2021/8/27 10:10# @Author : 41999# @Email : 419997284@qq.com# @File : Myforms.py# @Project : whereaboutsfrom django import formsfrom django.forms import widgetsclass 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"}))
实现效果

第三个设计:校验用户名是否已存在
概览
1. 业务逻辑:由view.py响应前端的请求,并且对其做出处置。这里是对前端提交的用户名与数据库已经存储的用户名进行匹配,如果不匹配,则允许此输入,如果匹配,则提示该用户已经存在2. 具体实现方法在业务绘图里面
业务逻辑绘图

首先引入报错提示的包,该包存在于forms.py的包引用中
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
Myforms.py代码
# -*- coding: utf-8 -*-# @Time : 2021/8/27 10:10# @Author : 41999# @Email : 419997284@qq.com# @File : Myforms.py# @Project : whereaboutsfrom django import formsfrom django.forms import widgetsfrom blog.models import UserInfofrom django.core.exceptions import NON_FIELD_ERRORS, ValidationErrorclass 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"}))def clean_user(self):val=self.cleaned_data.get("user")user=UserInfo.objects.filter(username=val).first()print(user)if not user:return valelse:raise ValidationError("该用户已注册!")
最终实现效果

第四个设计:校验密码重复是否正确
1. 整体设计:提取前端获取的数据,导入数据表进行查询,将查询结果用变量报错,然后进行二者的匹配2. 具体实现:需要导入异常处理包,负责报错功能;导入models中的UserInfo表,进行数据库查询;使用django.form.form,cleaned_data.get()分别获取输入的密码和重复输入的密码用逻辑运算符匹配两个变量的值,最后用ValidationError()抛出报错3. 调试的方法:每检查一次功能实现,都是在注册页面点击提交
为什么要设置全局报错—默认位置在控制台:需要指定位置

registry.html中的代码
第二十行为新增代码主要匹配H5中的字段,全局的钩子,匹配完成之后给出返回的对象,has-error用于将选框遍红
$.ajax({url: "", //使用当前的URLtype: "post", //网络请求的方式contentType: false,processData: false,data: formdata,success: function (data) {{#console.log(data);#}if (data.user) {// 注册成功} else {// 注册失败;取出所有错误{#console.log(data)#}// 在针对字段添加错误提示之前先清空提交时传递的错误信息$("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");})}}})
最终实现效果
第六个版本
概念
1. 预期实现的功能1. 增加验证逻辑,如果没有输入两次密码,则不校验密码一致性2. 在完成注册页面信息填写及校验之后,跳转到注册页面3. 在注册页面提交的信息,存储到数据库表UserInfo中4. 处理头像文件的提取和存储
第一个设计:若未输入两次密码,则放弃一致性校验
前端中的问题呈现—仅输入确认密码的情况下

后端代码增加—Myforms.py
def clean(self):# self是实例化对象,form = UserForm(request.POST)pwd=self.cleaned_data.get("pwd")re_pwd=self.cleaned_data.get("re_pwd")if pwd and re_pwd:if pwd==re_pwd:# print(self.cleaned_data)return self.cleaned_dataelse:raise ValidationError("两次密码不一致")else:return self.cleaned_data
最终效果

第二个设计—头像文件的存储和定位
1. 核心业务逻辑:前端Ajax传递数据给views,views调用Myforms处理完数据进行用户创建2. 用户创建时,用户名,密码和邮箱传递给MySQL,由UserInfo存储进入userinform数据表2. 而头像文件则根据Userinfo字段avatar规定的路径,传入项目文件中
修改views.py文件—数据库创建语句[11-18]
def registry(request):"""如果提交的数据错误,则由一个字典在原页面上显示提示信息"""if request.is_ajax():# print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>form = UserForm(request.POST) # 由UserForm做验证# print(form)response={"user":None,"msg":None} # 用于前端交互,传递messageif form.is_valid():response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.createuser = form.cleaned_data.get("user")pwd = form.cleaned_data.get("pwd")email = form.cleaned_data.get("email")avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj) # avatar是UserInfo的field, avatar_obj是前端传递的文件else:# print(form.cleaned_data)# print(form.errors)response["msg"] = form.errorsreturn JsonResponse(response)# 实例化对象,然后传递给蒙版form = UserForm()return render(request,"blog/registry.html",{"form":form}
最终效果
前端浏览器页面[注册->登录->首页信息]

数据库用户字段查询


项目中图片的存储路径

细节优化—未上传头像则使用默认路径下的头像文件[18-21]
def registry(request):"""如果提交的数据错误,则由一个字典在原页面上显示提示信息"""if request.is_ajax():# print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>form = UserForm(request.POST) # 由UserForm做验证# print(form)response={"user":None,"msg":None} # 用于前端交互,传递messageif form.is_valid():response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.createuser = form.cleaned_data.get("user")pwd = form.cleaned_data.get("pwd")email = form.cleaned_data.get("email")avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象if avata_obj:UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj) # avatar是UserInfo的field, avatar_obj是前端传递的文件else:UserInfo.objects.create_user(username=user, password=pwd, email=email)else:# print(form.cleaned_data)# print(form.errors)response["msg"] = form.errorsreturn JsonResponse(response)# 实例化对象,然后传递给蒙版form = UserForm()return render(request,"blog/registry.html",{"form":form})
实现效果


第七个版本—如何配置静态文件
配置static_url


修改之后的效果


配置MEDIA_ROOT & MEDIA_URL
配置信息
# Media相关配置# https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#media-root# https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#media-urlMEDIA_ROOT = os.path.join(BASE_DIR, "media")MEDIA_URL = "/media/"
默认路径

自定义后的效果

第八个版本—遵循开发规范优化代码
1 views.py文件中提交与未提交的创建信息差异
原来的代码
优化后的代码
参数说明
# django.contrib.auth.modelsdef 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)
测试是否生效

2 导入包的优化—标准库->插件包->自定义包

3添加文档字符串解释参数

4 使用工具优化代码—包括符号,缩进,参数

总结
做项目过程中就把代码注释写好,尤其是参数和调用的包,那些陌生的扩展包的用法,不要等到最后阶段总结才开始弄;
注重项目文件之间的代码和数据传递,调用或者引用都在两处显著标明,免得最后找不到参数是从哪里来的,究竟干什么
代码汇总—环境现场保存
views.py
from django.shortcuts import render, HttpResponse, redirectfrom django.http import JsonResponsefrom django.contrib import authimport PIL, randomfrom blog.models import UserInfofrom blog.Myforms import UserFormfrom blog.utils.validCode import get_valide_code_imgdef login(request):"""功能设计:验证码和用户信息的校验不区分验证码大小写,统一转换为大写 uppercaseauth.login:在请求中保留用户id和后端。这样,用户就不必在每次请求时都重新验证。请注意,匿名会话期间的数据集在用户登录时保留。auth.authenticate: 从client请求中提取数据,将数据与数据库进行匹配response: 字典,作为message传递提示信息request.POST: 包含所有前端传递的信息auth.login:保存单个用户的单次登录信息JsonResponse:Json化后端生成的提示信息"""if request.method == "POST":response = {"user": None, "msg": None}user = request.POST.get("user")# print(user)pwd = request.POST.get("pwd")# 前端提交的验证码valid_code_one = request.POST.get("valid_code")valid_code = str(valid_code_one)# 后端生成的验证码,由get_validCode_img负责生成valid_code_str = request.session.get("valid_code_str")# print(valid_code) 测试后端在提交前端显示之前保存的验证码# print(valid_code_str) 测试前端POST请求提交时给出的验证码if valid_code.upper() == valid_code_str.upper():user = auth.authenticate(username=user, password=pwd) # 将前端提交的密码与后端MySQL存储的用户名与密码匹配if user:auth.login(request, user) # 匹配成功后则将其注册request.user==当前登录对象,存储当前登录对象response["user"] = user.usernameelse:response["msg"] = "username or password error!"else:response["msg"] = "valide code error!"return JsonResponse(response)return render(request, "blog/login.html")def get_validCode_img(request):"""调用blog/utils/valid_code程序生成代码用request.session传递后端生成验证码"""data = get_valide_code_img(request)# print(type(data))return HttpResponse(data)def index(request):return render(request, "blog/index.html")def registry(request):"""UserForm验证提交的用户名,密码,邮箱等数据用settings中的media处理头像文件如果提交的数据错误,则由一个字典在原页面上显示提示信息"""if request.is_ajax():# print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>form = UserForm(request.POST) # 由UserForm做验证# print(form)response = {"user": None, "msg": None} # 用于前端交互,传递messageif form.is_valid():response["user"] = form.cleaned_data.get("user") # 验证通过则会传递用户名# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.createuser = form.cleaned_data.get("user")pwd = form.cleaned_data.get("pwd")email = form.cleaned_data.get("email")avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象extra = {}if avata_obj:extra["avatar"] = avata_objUserInfo.objects.create_user(username=user, password=pwd, email=email,**extra) # avatar是UserInfo的field, avatar_obj是前端传递的文件else:# print(form.cleaned_data)# print(form.errors)response["msg"] = form.errorsreturn JsonResponse(response)# 实例化对象,form = UserForm()# form为提示信息return render(request, "blog/registry.html", {"form": form})
registry.html
<!DOCTYPE html>{% load static %}<html lang="en"><head><meta charset="UTF-8"><title>注册</title><link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}"><style>#avatar_img {margin-left: 80px;}#avatar {display: none;}.error {color: red;}</style></head><body><h3>Welcome to Blog of Caesar Tylor</h3><div class="container"><div class="row"><div class="col-md-6 col-lg-offset-3"> {# 占用六个,右倾 #}<form id="alex"> {# action不再定义,基于Ajax提交 #}{% csrf_token %} {# 增加CSRF防护令牌 #}{% for field in form %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="{{ field.auto_id }}">{{ field.label }}</label>{{ field }} <span class="error pull-right"></span></div>{% endfor %}<div class="form-group">{#依次渲染user pwd re_pwd#}<label for="avatar">头像<img id="avatar_img" width="200" height="200"src="{% static '/static/blog/img/default_avatar.png' %}" alt=""></label><input type="file" id="avatar"></div><input type="button" class="btn btn-default reg_btn" value="submit"></form></div></div></div><script src="{% static '/static/js/jquery-3.6.0.min.js' %}"></script><script>$("#avatar").change(function () {// 获取用户选中的文件对象var file_obj = $(this)[0].files[0];// 获取文件对象的路径---使用文件阅读器,异步线程,修改src也在执行var reader = new FileReader();reader.readAsDataURL(file_obj);// 修改img的src属性,src=文件对象路径,等待以上操作执行完毕再修改属性reader.onload = function () {$("#avatar_img").attr("src", reader.result);}})// 基于Ajax提交数据$(".reg_btn").click(function () {//console.log($("#form").serializeArray());var formdata = new FormData();// #alex---指的是提取的字段IDvar request_data = $("#alex").serializeArray();$.each(request_data, function (index, data) {formdata.append(data.name, data.value)});formdata.append("avatar", $("#avatar")[0].files[0]);$.ajax({url: "", //使用当前的URLtype: "post", //网络请求的方式contentType: false,processData: false,data: formdata,success: function (data) {{#console.log(data);#}if (data.user) {// 注册成功location.href = "/login/"} else {// 注册失败;取出所有错误{#console.log(data)#}// 在针对字段添加错误提示之前先清空提交时传递的错误信息$("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");})}}})})</script><script></script></body></html>
Myforms.html
# -*- coding: utf-8 -*-# @Time : 2021/8/27 10:10# @Author : 41999# @Email : 419997284@qq.com# @File : Myforms.py# @Project : whereaboutsfrom django import formsfrom django.forms import widgetsfrom blog.models import UserInfofrom django.core.exceptions import NON_FIELD_ERRORS, ValidationErrorclass 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"}))def clean_user(self):"""局部钩子:校验用户创建时输入的用户名和MySQL存储的用户名"""val = self.cleaned_data.get("user")user = UserInfo.objects.filter(username=val).first()if not user:return valelse:raise ValidationError("该用户已注册!")def clean(self):"""全局钩子:校验两次输入的密码是否一致"""# self是实例化对象,form = UserForm(request.POST)pwd = self.cleaned_data.get("pwd")re_pwd = self.cleaned_data.get("re_pwd")if pwd and re_pwd:if pwd == re_pwd:# print(self.cleaned_data)return self.cleaned_dataelse:raise ValidationError("两次密码不一致")else:return self.cleaned_data
valide_Code.py
# -*- coding: utf-8 -*-# @Time : 2021/8/25 9:53# @Author : 41999# @Email : 419997284@qq.com# @File : validCode.py# @Project : whereaboutsfrom io import BytesIOfrom PIL import Image, ImageDraw, ImageFontimport randomdef get_random_color():"""生成三组随机RGB数字,以便构成颜色"""a = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))return adef get_valide_code_img(request):"""功能设计:分别为绘图,随机噪点,随机噪线Image.new:生成图片对象,分别是显示模式,大小[长宽],RGB数字ImageDraw.Draw: 导入并且绘制图像ImageFont:指定文字文件地址,TTF格式字体"""img = Image.new("RGB", (240, 30), color=get_random_color())draw = ImageDraw.Draw(img)FiraCode = ImageFont.truetype("static/font/FiraCode-Regular.ttf", size=24)valid_code_str = ""for i in range(6):random_num = random.randint(0, 9) # 随机数字random_low_alpha = chr(random.randint(95, 122)) # 随机小写字符,ASCⅡ大小写字母范围random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字符random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])draw.text((i * 50 + 20, 5), str(random_char), get_random_color(), font=FiraCode) # 转换第二个参数的类型# 保存验证码字符串valid_code_str += str(random_char)width = 250height = 40for i in range(10):x1 = random.randint(0, width)x2 = random.randint(0, width)y1 = random.randint(0, height)y2 = random.randint(0, height)draw.line((x1, x2, y1, y2), fill=get_random_color())for i in range(200):draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())x = random.randint(0, width)y = random.randint(0, height)draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())# 打印保存的验证码# print("valid_code_str", valid_code_str)request.session["valid_code_str"] = valid_code_str"""1. 生成随机字符串2. 设置一个cookie{sessionid:随机字符串}3. django——session存储session_key---随机字符串,session_data---验证码"""f = BytesIO()img.save(f, "png")data = f.getvalue()return data
urls.py
"""whereabouts URL ConfigurationThe `urlpatterns` list routes URLs to views. For more information please see:https://docs.djangoproject.com/en/3.2/topics/http/urls/Examples:Function views1. Add an import: from my_app import views2. Add a URL to urlpatterns: path('', views.home, name='home')Class-based views1. Add an import: from other_app.views import Home2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')Including another URLconf1. Import the include() function: from django.urls import include, path2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))"""from django.contrib import adminfrom django.urls import path, includefrom django.conf import settingsfrom django.conf.urls.static import staticfrom blog import viewsurlpatterns = [path('admin/', admin.site.urls),path('summernote/', include('django_summernote.urls')),# http://127.0.0.1:8001/login/path('login/', views.login),path('get_validCode_img/', views.get_validCode_img),# http://127.0.0.1:8001/index/path('index/', views.index),# http://127.0.0.1:8001/registry/path('registry/', views.registry),]if settings.DEBUG:urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
