整体设计理念

  1. 1. 基于Ajaxforms组件实现注册功能
  2. 1. form组件的作用:校验字段值,渲染标签
  3. 2. 三种渲染标签的方法:for循环渲染,逐个字段渲染
  4. 2. 实现过程
  5. ---点击头像===点击选择文件按钮
  6. ---头像预览:
  7. 1. 获取用户选择的文件对象
  8. 2. 获取文件对象的路径
  9. 3. 修改img标签的src属性,使得src==文件对象的路径
  10. 3. 展示错误信息[通过H5标签绑定数据上传和错误处理,相当于中间表]
  11. 1. views:
  12. form.errors # {"#user":[......]}
  13. 2. Ajax.success:
  14. $.each(data.msg, function (field, error_list) {
  15. $("#id_" + field).next().html(error_list[0]);
  16. $("#id_" + field).parent().addClass("has-error");
  17. }
  18. 3. 局部钩子和全局钩子的校验
  19. 1. user字段不能重复
  20. 2. 两次密码不一致
  21. 4. FileField ImageField 前者传任何文件,后者只能传图片文件
  22. [models]class UserInfo(AbstractUser):
  23. """用户信息"""
  24. nid = models.AutoField(primary_key=True)
  25. telephone = models.CharField(max_length=11, null=True, unique=True)
  26. # 存储用户头像文件,创建并且放置在avatars文件夹下
  27. avatar = models.FileField(upload_to='avatars/', default="avatars/default.png")
  28. # 在生成字段时就以当前时间存储,用于计算园龄
  29. create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
  30. # 业务分离,用户名下blog删除,则站点blog同时删除
  31. blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.PROTECT,
  32. related_name="UserInfo_blog")
  33. [views视图]avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象
  34. UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj)
  35. Django会做的事情:
  36. 1. 将文件下载到项目文件的根目录
  1. 5 media配置
  2. 1. 静态文件的分类
  3. 1. static jss img css 系统前端支持文件
  4. 2. media 用户上传的文件
  5. 2. settings中的配置
  6. 用户是用户,服务器是服务器
  7. 添加此路径之后,上传文件之后会首先在media下创建models指定的avatar文件夹,再放入用户上传的头像信息
  8. 3. 用户如何从外部访问static的文件
  9. static_url管理接入,该配置可自定义,若禁用则无法通过该方法访问静态文件
  10. 4. media的配置
  11. static有何不同?前者的URL起着前缀的作用,是一种映射关系
  12. 后者是一种解释作用,直接包含在路径中,可以直接修改[系统安全防护级别不一样]

第一个版本

设计理念

在views中使用form表单制造字段,在前端修改页面,使用模板标签循环渲染字段

代码

  1. # views
  2. from django import forms
  3. class UserForm(forms.Form):
  4. """均为登录时需要校验的字段"""
  5. user = forms.CharField(max_length=32)
  6. pwd = forms.CharField(max_length=32)
  7. re_pwd = forms.CharField(max_length=32)
  8. email = forms.EmailField(max_length=32)
  9. def registry(request):
  10. # 实例化对象,然后传递给蒙版
  11. form = UserForm()
  12. return render(request, "blog/registry.html", {"form":form})
  13. # registry页面
  14. <!DOCTYPE html>
  15. {% load static %}
  16. <html lang="en">
  17. <head>
  18. <meta charset="UTF-8">
  19. <title>注册</title>
  20. <link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}">
  21. </head>
  22. <body>
  23. <h3>Welcome to Blog of Caesar Tylor</h3>
  24. <div class="container">
  25. <div class="row">
  26. <div class="col-md-6 col-lg-offset-3"> {# 占用六个,右倾 #}
  27. <form> {# action不再定义,基于Ajax提交 #}
  28. {% csrf_token %} {# 增加CSRF防护令牌 #}
  29. {% for field in form %}
  30. <div class="form-group">
  31. {# 依次渲染user pwd re_pwd#}
  32. <label for="user">{{ field.label }}</label>
  33. {{ field }}
  34. </div>
  35. {% endfor %}
  36. </form>
  37. </div>
  38. </div>
  39. </div>
  40. <script src="{% static '/static/js/jquery-3.6.0.min.js' %}"></script>
  41. <script>
  42. </script>
  43. </body>
  44. </html>

实现效果

第二个版本

前端调试—美化输入框

调试方法

后端view修改

  1. from django import forms
  2. from django.forms import widgets
  3. class UserForm(forms.Form):
  4. """均为登录时需要校验的字段"""
  5. user = forms.CharField(max_length=32, widget=widgets.TextInput(attrs={"class":"form-control"}))
  6. pwd = forms.CharField(max_length=32, widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  7. re_pwd = forms.CharField(max_length=32, widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  8. email = forms.EmailField(max_length=32, widget=widgets.EmailInput(attrs={"class":"form-control"}))
  9. def registry(request):
  10. # 实例化对象,然后传递给蒙版
  11. form = UserForm()
  12. return render(request, "blog/registry.html", {"form":form})

实现效果

注册页面的设计 - 图4

小型增补—英文标记为中文

  1. class UserForm(forms.Form):
  2. """均为登录时需要校验的字段"""
  3. user = forms.CharField(max_length=32, label="用户名", widget=widgets.TextInput(attrs={"class":"form-control"}))
  4. pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  5. re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  6. email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class":"form-control"}))

实现效果

注册页面的设计 - 图5

小型增补—增加图像上传的初步功能

前端新增代码

  1. <form> {# action不再定义,基于Ajax提交 #}
  2. {% csrf_token %} {# 增加CSRF防护令牌 #}
  3. {% for field in form %}
  4. <div class="form-group">
  5. {#依次渲染user pwd re_pwd#}
  6. <label for="user">{{ field.label }}</label>
  7. {{ field }}
  8. </div>
  9. {% endfor %}
  10. <div class="form-group">
  11. {#依次渲染user pwd re_pwd#}
  12. <label for="avatar">头像</label>
  13. <input type="file">
  14. </div>
  15. <input type="button" class="btn btn-default login_btn" value="submit">
  16. </form>

前端效果

注册页面的设计 - 图6

第三个版本

增加点击头像等同于选择文件标签的功能

前端观察字段写法

注册页面的设计 - 图7

for模板标签渲染的页面中,每一个ID都是在原有名字的基础上添加一个ID前缀,为了使得for字段与ID保持一致,对源码做如下修改

后端代码修改

  1. <div class="form-group">
  2. {#依次渲染user pwd re_pwd#}
  3. <label for="{{ field.auto_id }}">{{ field.label }}</label>
  4. {{ field }}
  5. </div>
  6. {% endfor %}

修改后的效果

注册页面的设计 - 图8

隐藏选择文件的标签

后端代码

  1. <form> {# action不再定义,基于Ajax提交 #}
  2. {% csrf_token %} {# 增加CSRF防护令牌 #}
  3. {% for field in form %}
  4. <div class="form-group">
  5. {#依次渲染user pwd re_pwd#}
  6. <label for="{{ field.auto_id }}">{{ field.label }}</label>
  7. {{ field }}
  8. </div>
  9. {% endfor %}
  10. <div class="form-group">
  11. {#依次渲染user pwd re_pwd#}
  12. <label for="avatar">
  13. 头像
  14. <img width="200" height="200" src="{% static '/static/blog/img/default_avatar.png' %}" alt="">
  15. </label>
  16. <input type="file" id="avatar" style="display: none">
  17. </div>
  18. <input type="button" class="btn btn-default login_btn" value="submit">
  19. </form>

实现效果

注册页面的设计 - 图9

头像预览效果

实现逻辑

使用Ajax绑定事件

前端调试

注册页面的设计 - 图10

后端代码调整

  1. <script>
  2. $("#avatar").change(function(){
  3. // 获取用户选中的文件对象
  4. var file_obj = $(this)[0].files[0];
  5. // 获取文件对象的路径---使用文件阅读器
  6. var reader = new FileReader();
  7. reader.readAsDataURL(file_obj);
  8. // 修改img的src属性,src=文件对象路径
  9. $("#avatar_img").attr("src",reader.result);
  10. })
  11. </script>

前端调试路径替换

注册页面的设计 - 图11

可能存在的问题

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

前端图像预览为空

注册页面的设计 - 图12

预览图片成功

注册页面的设计 - 图13

后端代码及解决方案

在异步线程中设置事件等待执行

  1. <script>
  2. $("#avatar").change(function(){
  3. // 获取用户选中的文件对象
  4. var file_obj = $(this)[0].files[0];
  5. // 获取文件对象的路径---使用文件阅读器,异步线程,修改src也在执行
  6. var reader = new FileReader();
  7. reader.readAsDataURL(file_obj);
  8. // 修改img的src属性,src=文件对象路径,等待以上操作执行完毕再修改属性
  9. reader.onload=function (){
  10. $("#avatar_img").attr("src",reader.result);
  11. }
  12. })
  13. </script>

第四个版本

注册时实现提交选框数据

预期效果

注册页面的设计 - 图14

实现逻辑

  1. 1. 在注册页面添加一个Ajax事件,由它提交数据给form表单

前端代码—registry

  1. // 基于Ajax提交数据
  2. $(".reg_btn").click(function(){
  3. // 构建一个变量获取用户输入的值
  4. var formdata=new FormData();
  5. formdata.append("user",$("#id_user").val());
  6. formdata.append("pwd",$("#id_pwd").val());
  7. formdata.append("re_pwd",$("#id_re_pwd").val());
  8. formdata.append("email",$("#id_email").val());
  9. formdata.append("avatar",$("#avatar")[0].files[0]);
  10. $.ajax({
  11. url:"", //使用当前的URL
  12. type:"post", //网络请求的方式
  13. contentType:false,
  14. processData:false,
  15. data:formdata,
  16. success:function(data){
  17. console.log(data)
  18. }
  19. })
  20. })

后端代码—views.py

  1. def registry(request):
  2. """
  3. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  4. """
  5. if request.is_ajax():
  6. print(request.POST)
  7. form = UserForm(request.POST) # 由UserForm做验证
  8. response={"user":None,"msg":None}
  9. if form.is_valid():
  10. response["user"]=form.cleaned_data.get("user")
  11. else:
  12. print(form.cleaned_data)
  13. print(form.errors)
  14. response["msg"] = form.errors
  15. return JsonResponse(response, "/blog/registry.html",{"form":form})
  16. # 实例化对象,然后传递给蒙版
  17. form = UserForm()
  18. return render(request, "blog/registry.html", {"form":form})

第一次前端测试—console提示禁止访问

注册页面的设计 - 图15

解决方案—Ajax添加CSRF—token

  1. $(".reg_btn").click(function(){
  2. // 构建一个变量获取用户输入的值
  3. var formdata=new FormData();
  4. formdata.append("user",$("#id_user").val());
  5. formdata.append("pwd",$("#id_pwd").val());
  6. formdata.append("re_pwd",$("#id_re_pwd").val());
  7. formdata.append("email",$("#id_email").val());
  8. formdata.append("avatar",$("#avatar")[0].files[0]);
  9. formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());

前端提供的信息

注册页面的设计 - 图16

最终效果

注册页面的设计 - 图17

代码优化—减少Ajax中重复代码

需求分析

  1. 1. 一个表单中会提交多个选框中的数据,那就意味着为每一个选框指定一次数据添加
  2. 2. 产生需求:能否自动化提取多个选框的数据,然后使用for循环将数据填入数据对象当中?

具体实现

  1. # 保存现场
  2. $(".reg_btn").click(function(){
  3. console.log($("#form").serializeArray());
  4. // 构建一个变量获取用户输入的值
  5. var formdata=new FormData();
  6. formdata.append("user",$("#id_user").val());
  7. formdata.append("pwd",$("#id_pwd").val());
  8. formdata.append("re_pwd",$("#id_re_pwd").val());
  9. formdata.append("email",$("#id_email").val());
  10. formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());
  11. formdata.append("avatar",$("#avatar")[0].files[0]);
  12. # 第二种方法
  13. $(".reg_btn").click(function(){
  14. //console.log($("#form").serializeArray());
  15. var formdata = new FormData();
  16. var request_data = $("#form").serializeArray();
  17. $.each(request_data, function (index, data) {
  18. formdata.append(data.name, data.value)
  19. });
  20. formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());
  21. formdata.append("avatar", $("#avatar")[0].files[0]);

预期实现的功能:将报错放置在各自字段的后面

前端展示

注册页面的设计 - 图18

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

最终实现的效果

注册页面的设计 - 图19

后端Ajax代码

  1. if(data.user){
  2. // 注册成功
  3. }
  4. else{
  5. // 注册失败;取出所有错误
  6. {#console.log(data)#}
  7. $.each(data.msg, function (field,error_list){
  8. console.log(field, error_list);
  9. $("#id_"+field).next().html(error_list[0])
  10. })
  11. }

后端CSS效果

  1. # 选框控制字段的修改
  2. <div class="form-group">
  3. {#依次渲染user pwd re_pwd#}
  4. <label for="{{ field.auto_id }}">{{ field.label }}</label>
  5. {{ field }} <span class="error pull-right"></span>
  6. </div>
  7. # 增加CSS效果,设置字体为红色
  8. <head>
  9. <meta charset="UTF-8">
  10. <title>注册</title>
  11. <link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}">
  12. <style>
  13. #avatar_img {
  14. margin-left: 80px;
  15. }
  16. #avatar {
  17. display: none;
  18. }
  19. .error {
  20. color: red;
  21. }
  22. </style>
  23. </head>

部分注册信息通过时取消该选框的错误提示

预期实现的效果

注册页面的设计 - 图20

后端代码—第十五行为本次修改的代码

实现逻辑:前端提交错误信息时,先清空本次传递的错误信息,然后重新检查字段,传递错误提示信息

  1. $.ajax({
  2. url: "", //使用当前的URL
  3. type: "post", //网络请求的方式
  4. contentType: false,
  5. processData: false,
  6. data: formdata,
  7. success: function (data) {
  8. {#console.log(data);#}
  9. if (data.user) {
  10. // 注册成功
  11. } else {
  12. // 注册失败;取出所有错误
  13. {#console.log(data)#}
  14. // 在针对字段添加错误提示之前先清空提交时传递的错误信息
  15. $("span.error").html("");
  16. $.each(data.msg, function (field, error_list) {
  17. console.log(field, error_list);
  18. // 使用字段拼接来实现字段的遍历指定
  19. $("#id_" + field).next().html(error_list[0])
  20. })
  21. }
  22. }
  23. })

修改后的效果

注册页面的设计 - 图21

未填写的选框变红

预期效果

注册页面的设计 - 图22

前端调试预期结果

注册页面的设计 - 图23

调试代码

  1. $("#id_user")
  2. S.fn.init [input#id_user.form-control]
  3. $("#id_user").parent()
  4. S.fn.init [div.form-group, prevObject: S.fn.init(1)]
  5. $("#id_user").parent().addClass("has-error")

清除提交后又填入选框信息后仍然显示红色

需求预览

注册页面的设计 - 图24

后端代码—第十六行为新增代码

  1. $.ajax({
  2. url: "", //使用当前的URL
  3. type: "post", //网络请求的方式
  4. contentType: false,
  5. processData: false,
  6. data: formdata,
  7. success: function (data) {
  8. {#console.log(data);#}
  9. if (data.user) {
  10. // 注册成功
  11. } else {
  12. // 注册失败;取出所有错误
  13. {#console.log(data)#}
  14. // 在针对字段添加错误提示之前先清空提交时传递的错误信息
  15. $("span.error").html("");
  16. $(".form-group").removeClass("has-error");
  17. $.each(data.msg, function (field, error_list) {
  18. console.log(field, error_list);
  19. // 使用字段拼接来实现字段的遍历指定
  20. $("#id_" + field).next().html(error_list[0]);
  21. $("#id_" + field).parent().addClass("has-error");
  22. })
  23. }
  24. }
  25. })

最终结果

注册页面的设计 - 图25

第五个版本

整体功能设计

预期实现的功能:将注册时提交的用户名与数据库存储的用户信息表进行比对,看是否已经存在

验证重复密码是否与第一次输入时一致

对form表单数据解耦,建立单独文件Myforms,不再放置于views.py文件当中

局部钩子和全局钩子:可能指的是提示信息针对的字段,在H5中以标签作为对象呈现,具体在all

第一个设计:功能解耦

Myforms.py文件中的内容

注意引入两个包,分别是forms 和widgets

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/8/27 10:10
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : Myforms.py
  6. # @Project : whereabouts
  7. from django import forms
  8. from django.forms import widgets
  9. class UserForm(forms.Form):
  10. """均为登录时需要校验的字段"""
  11. user = forms.CharField(max_length=32, label="用户名", widget=widgets.TextInput(attrs={"class":"form-control"}))
  12. pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  13. re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  14. email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class":"form-control"}))

views.py中引入外部文件Myforms.py

  1. from django import forms
  2. from django.forms import widgets
  3. from blog.Myforms import UserForm
  4. def registry(request):
  5. """
  6. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  7. """
  8. if request.is_ajax():
  9. print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
  10. form = UserForm(request.POST) # 由UserForm做验证
  11. response={"user":None,"msg":None} # 用于前端交互,传递message
  12. if form.is_valid():
  13. response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
  14. else:
  15. print(form.cleaned_data)
  16. print(form.errors)
  17. response["msg"] = form.errors
  18. return JsonResponse(response)
  19. # 实例化对象,然后传递给蒙版
  20. form = UserForm()
  21. return render(request,"blog/registry.html",{"form":form})

第二个设计:自定义字段未提交时的错误提示信息

代码—第十二行是新增信息

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/8/27 10:10
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : Myforms.py
  6. # @Project : whereabouts
  7. from django import forms
  8. from django.forms import widgets
  9. class UserForm(forms.Form):
  10. """均为登录时需要校验的字段"""
  11. user = forms.CharField(max_length=32, error_messages={"required":"该字段必填,请仔细检查"}, label="用户名", widget=widgets.TextInput(attrs={"class":"form-control"}))
  12. pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  13. re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  14. email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class":"form-control"}))

实现效果

注册页面的设计 - 图26

第三个设计:校验用户名是否已存在

概览

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

业务逻辑绘图

注册页面的设计 - 图27

首先引入报错提示的包,该包存在于forms.py的包引用中

  1. from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

Myforms.py代码

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/8/27 10:10
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : Myforms.py
  6. # @Project : whereabouts
  7. from django import forms
  8. from django.forms import widgets
  9. from blog.models import UserInfo
  10. from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
  11. class UserForm(forms.Form):
  12. """均为登录时需要校验的字段"""
  13. user = forms.CharField(max_length=32, error_messages={"required":"该字段必填,请仔细检查"}, label="用户名", widget=widgets.TextInput(attrs={"class":"form-control"}))
  14. pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  15. re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}))
  16. email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class":"form-control"}))
  17. def clean_user(self):
  18. val=self.cleaned_data.get("user")
  19. user=UserInfo.objects.filter(username=val).first()
  20. print(user)
  21. if not user:
  22. return val
  23. else:
  24. raise ValidationError("该用户已注册!")

最终实现效果

注册页面的设计 - 图28

第四个设计:校验密码重复是否正确

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

为什么要设置全局报错—默认位置在控制台:需要指定位置

注册页面的设计 - 图29

registry.html中的代码

第二十行为新增代码主要匹配H5中的字段,全局的钩子,匹配完成之后给出返回的对象,has-error用于将选框遍红

  1. $.ajax({
  2. url: "", //使用当前的URL
  3. type: "post", //网络请求的方式
  4. contentType: false,
  5. processData: false,
  6. data: formdata,
  7. success: function (data) {
  8. {#console.log(data);#}
  9. if (data.user) {
  10. // 注册成功
  11. } else {
  12. // 注册失败;取出所有错误
  13. {#console.log(data)#}
  14. // 在针对字段添加错误提示之前先清空提交时传递的错误信息
  15. $("span.error").html("");
  16. $(".form-group").removeClass("has-error");
  17. $.each(data.msg, function (field, error_list) {
  18. console.log(field, error_list);
  19. if (field=="__all__"){
  20. $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error");
  21. }
  22. // 使用字段拼接来实现字段的遍历指定
  23. $("#id_" + field).next().html(error_list[0]);
  24. $("#id_" + field).parent().addClass("has-error");
  25. })
  26. }
  27. }
  28. })

最终实现效果注册页面的设计 - 图30

第六个版本

概念

  1. 1. 预期实现的功能
  2. 1. 增加验证逻辑,如果没有输入两次密码,则不校验密码一致性
  3. 2. 在完成注册页面信息填写及校验之后,跳转到注册页面
  4. 3. 在注册页面提交的信息,存储到数据库表UserInfo
  5. 4. 处理头像文件的提取和存储

第一个设计:若未输入两次密码,则放弃一致性校验

前端中的问题呈现—仅输入确认密码的情况下

注册页面的设计 - 图31

后端代码增加—Myforms.py

  1. def clean(self):
  2. # self是实例化对象,form = UserForm(request.POST)
  3. pwd=self.cleaned_data.get("pwd")
  4. re_pwd=self.cleaned_data.get("re_pwd")
  5. if pwd and re_pwd:
  6. if pwd==re_pwd:
  7. # print(self.cleaned_data)
  8. return self.cleaned_data
  9. else:
  10. raise ValidationError("两次密码不一致")
  11. else:
  12. return self.cleaned_data

最终效果

注册页面的设计 - 图32

第二个设计—头像文件的存储和定位

  1. 1. 核心业务逻辑:前端Ajax传递数据给views,views调用Myforms处理完数据进行用户创建
  2. 2. 用户创建时,用户名,密码和邮箱传递给MySQL,由UserInfo存储进入userinform数据表
  3. 2. 而头像文件则根据Userinfo字段avatar规定的路径,传入项目文件中

修改views.py文件—数据库创建语句[11-18]

  1. def registry(request):
  2. """
  3. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  4. """
  5. if request.is_ajax():
  6. # print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
  7. form = UserForm(request.POST) # 由UserForm做验证
  8. # print(form)
  9. response={"user":None,"msg":None} # 用于前端交互,传递message
  10. if form.is_valid():
  11. response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
  12. # 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
  13. # 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
  14. user = form.cleaned_data.get("user")
  15. pwd = form.cleaned_data.get("pwd")
  16. email = form.cleaned_data.get("email")
  17. avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象
  18. UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj) # avatar是UserInfo的field, avatar_obj是前端传递的文件
  19. else:
  20. # print(form.cleaned_data)
  21. # print(form.errors)
  22. response["msg"] = form.errors
  23. return JsonResponse(response)
  24. # 实例化对象,然后传递给蒙版
  25. form = UserForm()
  26. return render(request,"blog/registry.html",{"form":form}

最终效果

前端浏览器页面[注册->登录->首页信息]

注册页面的设计 - 图33

数据库用户字段查询

注册页面的设计 - 图34

注册页面的设计 - 图35

项目中图片的存储路径

注册页面的设计 - 图36

细节优化—未上传头像则使用默认路径下的头像文件[18-21]

  1. def registry(request):
  2. """
  3. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  4. """
  5. if request.is_ajax():
  6. # print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
  7. form = UserForm(request.POST) # 由UserForm做验证
  8. # print(form)
  9. response={"user":None,"msg":None} # 用于前端交互,传递message
  10. if form.is_valid():
  11. response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
  12. # 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
  13. # 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
  14. user = form.cleaned_data.get("user")
  15. pwd = form.cleaned_data.get("pwd")
  16. email = form.cleaned_data.get("email")
  17. avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象
  18. if avata_obj:
  19. UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avata_obj) # avatar是UserInfo的field, avatar_obj是前端传递的文件
  20. else:
  21. UserInfo.objects.create_user(username=user, password=pwd, email=email)
  22. else:
  23. # print(form.cleaned_data)
  24. # print(form.errors)
  25. response["msg"] = form.errors
  26. return JsonResponse(response)
  27. # 实例化对象,然后传递给蒙版
  28. form = UserForm()
  29. return render(request,"blog/registry.html",{"form":form})

实现效果

注册页面的设计 - 图37

注册页面的设计 - 图38

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

配置static_url

注册页面的设计 - 图39

注册页面的设计 - 图40

修改之后的效果

注册页面的设计 - 图41

注册页面的设计 - 图42

配置MEDIA_ROOT & MEDIA_URL

配置信息

  1. # Media相关配置
  2. # https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#media-root
  3. # https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#media-url
  4. MEDIA_ROOT = os.path.join(BASE_DIR, "media")
  5. MEDIA_URL = "/media/"

默认路径

注册页面的设计 - 图43

自定义后的效果

注册页面的设计 - 图44

第八个版本—遵循开发规范优化代码

1 views.py文件中提交与未提交的创建信息差异

原来的代码注册页面的设计 - 图45

优化后的代码注册页面的设计 - 图46

参数说明

  1. # django.contrib.auth.models
  2. def create_user(self, username, email=None, password=None, **extra_fields):
  3. extra_fields.setdefault('is_staff', False)
  4. extra_fields.setdefault('is_superuser', False)
  5. return self._create_user(username, email, password, **extra_fields)

测试是否生效

注册页面的设计 - 图47

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

注册页面的设计 - 图48

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

注册页面的设计 - 图49

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

注册页面的设计 - 图50

总结

做项目过程中就把代码注释写好,尤其是参数和调用的包,那些陌生的扩展包的用法,不要等到最后阶段总结才开始弄;

注重项目文件之间的代码和数据传递,调用或者引用都在两处显著标明,免得最后找不到参数是从哪里来的,究竟干什么

代码汇总—环境现场保存

views.py

  1. from django.shortcuts import render, HttpResponse, redirect
  2. from django.http import JsonResponse
  3. from django.contrib import auth
  4. import PIL, random
  5. from blog.models import UserInfo
  6. from blog.Myforms import UserForm
  7. from blog.utils.validCode import get_valide_code_img
  8. def login(request):
  9. """
  10. 功能设计:验证码和用户信息的校验
  11. 不区分验证码大小写,统一转换为大写 uppercase
  12. auth.login:在请求中保留用户id和后端。这样,用户就不必在每次请求时都重新验证。请注意,匿名会话期间的数据集在用户登录时保留。
  13. auth.authenticate: 从client请求中提取数据,将数据与数据库进行匹配
  14. response: 字典,作为message传递提示信息
  15. request.POST: 包含所有前端传递的信息
  16. auth.login:保存单个用户的单次登录信息
  17. JsonResponse:Json化后端生成的提示信息
  18. """
  19. if request.method == "POST":
  20. response = {"user": None, "msg": None}
  21. user = request.POST.get("user")
  22. # print(user)
  23. pwd = request.POST.get("pwd")
  24. # 前端提交的验证码
  25. valid_code_one = request.POST.get("valid_code")
  26. valid_code = str(valid_code_one)
  27. # 后端生成的验证码,由get_validCode_img负责生成
  28. valid_code_str = request.session.get("valid_code_str")
  29. # print(valid_code) 测试后端在提交前端显示之前保存的验证码
  30. # print(valid_code_str) 测试前端POST请求提交时给出的验证码
  31. if valid_code.upper() == valid_code_str.upper():
  32. user = auth.authenticate(username=user, password=pwd) # 将前端提交的密码与后端MySQL存储的用户名与密码匹配
  33. if user:
  34. auth.login(request, user) # 匹配成功后则将其注册request.user==当前登录对象,存储当前登录对象
  35. response["user"] = user.username
  36. else:
  37. response["msg"] = "username or password error!"
  38. else:
  39. response["msg"] = "valide code error!"
  40. return JsonResponse(response)
  41. return render(request, "blog/login.html")
  42. def get_validCode_img(request):
  43. """
  44. 调用blog/utils/valid_code程序生成代码
  45. 用request.session传递后端生成验证码
  46. """
  47. data = get_valide_code_img(request)
  48. # print(type(data))
  49. return HttpResponse(data)
  50. def index(request):
  51. return render(request, "blog/index.html")
  52. def registry(request):
  53. """
  54. UserForm验证提交的用户名,密码,邮箱等数据
  55. 用settings中的media处理头像文件
  56. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  57. """
  58. if request.is_ajax():
  59. # print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
  60. form = UserForm(request.POST) # 由UserForm做验证
  61. # print(form)
  62. response = {"user": None, "msg": None} # 用于前端交互,传递message
  63. if form.is_valid():
  64. response["user"] = form.cleaned_data.get("user") # 验证通过则会传递用户名
  65. # 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
  66. # 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
  67. user = form.cleaned_data.get("user")
  68. pwd = form.cleaned_data.get("pwd")
  69. email = form.cleaned_data.get("email")
  70. avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象
  71. extra = {}
  72. if avata_obj:
  73. extra["avatar"] = avata_obj
  74. UserInfo.objects.create_user(username=user, password=pwd, email=email,
  75. **extra) # avatar是UserInfo的field, avatar_obj是前端传递的文件
  76. else:
  77. # print(form.cleaned_data)
  78. # print(form.errors)
  79. response["msg"] = form.errors
  80. return JsonResponse(response)
  81. # 实例化对象,
  82. form = UserForm()
  83. # form为提示信息
  84. return render(request, "blog/registry.html", {"form": form})

registry.html

  1. <!DOCTYPE html>
  2. {% load static %}
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>注册</title>
  7. <link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}">
  8. <style>
  9. #avatar_img {
  10. margin-left: 80px;
  11. }
  12. #avatar {
  13. display: none;
  14. }
  15. .error {
  16. color: red;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <h3>Welcome to Blog of Caesar Tylor</h3>
  22. <div class="container">
  23. <div class="row">
  24. <div class="col-md-6 col-lg-offset-3"> {# 占用六个,右倾 #}
  25. <form id="alex"> {# action不再定义,基于Ajax提交 #}
  26. {% csrf_token %} {# 增加CSRF防护令牌 #}
  27. {% for field in form %}
  28. <div class="form-group">
  29. {#依次渲染user pwd re_pwd#}
  30. <label for="{{ field.auto_id }}">{{ field.label }}</label>
  31. {{ field }} <span class="error pull-right"></span>
  32. </div>
  33. {% endfor %}
  34. <div class="form-group">
  35. {#依次渲染user pwd re_pwd#}
  36. <label for="avatar">
  37. 头像
  38. <img id="avatar_img" width="200" height="200"
  39. src="{% static '/static/blog/img/default_avatar.png' %}" alt="">
  40. </label>
  41. <input type="file" id="avatar">
  42. </div>
  43. <input type="button" class="btn btn-default reg_btn" value="submit">
  44. </form>
  45. </div>
  46. </div>
  47. </div>
  48. <script src="{% static '/static/js/jquery-3.6.0.min.js' %}"></script>
  49. <script>
  50. $("#avatar").change(function () {
  51. // 获取用户选中的文件对象
  52. var file_obj = $(this)[0].files[0];
  53. // 获取文件对象的路径---使用文件阅读器,异步线程,修改src也在执行
  54. var reader = new FileReader();
  55. reader.readAsDataURL(file_obj);
  56. // 修改img的src属性,src=文件对象路径,等待以上操作执行完毕再修改属性
  57. reader.onload = function () {
  58. $("#avatar_img").attr("src", reader.result);
  59. }
  60. })
  61. // 基于Ajax提交数据
  62. $(".reg_btn").click(function () {
  63. //console.log($("#form").serializeArray());
  64. var formdata = new FormData();
  65. // #alex---指的是提取的字段ID
  66. var request_data = $("#alex").serializeArray();
  67. $.each(request_data, function (index, data) {
  68. formdata.append(data.name, data.value)
  69. });
  70. formdata.append("avatar", $("#avatar")[0].files[0]);
  71. $.ajax({
  72. url: "", //使用当前的URL
  73. type: "post", //网络请求的方式
  74. contentType: false,
  75. processData: false,
  76. data: formdata,
  77. success: function (data) {
  78. {#console.log(data);#}
  79. if (data.user) {
  80. // 注册成功
  81. location.href = "/login/"
  82. } else {
  83. // 注册失败;取出所有错误
  84. {#console.log(data)#}
  85. // 在针对字段添加错误提示之前先清空提交时传递的错误信息
  86. $("span.error").html("");
  87. $(".form-group").removeClass("has-error");
  88. $.each(data.msg, function (field, error_list) {
  89. console.log(field, error_list);
  90. // 用于提示两次输入的密码不一致
  91. if (field == "__all__") {
  92. $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error");
  93. }
  94. // 使用字段拼接来实现字段的遍历指定
  95. $("#id_" + field).next().html(error_list[0]);
  96. $("#id_" + field).parent().addClass("has-error");
  97. })
  98. }
  99. }
  100. })
  101. })
  102. </script>
  103. <script>
  104. </script>
  105. </body>
  106. </html>

Myforms.html

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/8/27 10:10
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : Myforms.py
  6. # @Project : whereabouts
  7. from django import forms
  8. from django.forms import widgets
  9. from blog.models import UserInfo
  10. from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
  11. class UserForm(forms.Form):
  12. """均为登录时需要校验的字段"""
  13. user = forms.CharField(max_length=32, error_messages={"required": "该字段必填,请仔细检查"}, label="用户名",
  14. widget=widgets.TextInput(attrs={"class": "form-control"}))
  15. pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}))
  16. re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}))
  17. email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class": "form-control"}))
  18. def clean_user(self):
  19. """局部钩子:校验用户创建时输入的用户名和MySQL存储的用户名"""
  20. val = self.cleaned_data.get("user")
  21. user = UserInfo.objects.filter(username=val).first()
  22. if not user:
  23. return val
  24. else:
  25. raise ValidationError("该用户已注册!")
  26. def clean(self):
  27. """全局钩子:校验两次输入的密码是否一致"""
  28. # self是实例化对象,form = UserForm(request.POST)
  29. pwd = self.cleaned_data.get("pwd")
  30. re_pwd = self.cleaned_data.get("re_pwd")
  31. if pwd and re_pwd:
  32. if pwd == re_pwd:
  33. # print(self.cleaned_data)
  34. return self.cleaned_data
  35. else:
  36. raise ValidationError("两次密码不一致")
  37. else:
  38. return self.cleaned_data

valide_Code.py

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/8/25 9:53
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : validCode.py
  6. # @Project : whereabouts
  7. from io import BytesIO
  8. from PIL import Image, ImageDraw, ImageFont
  9. import random
  10. def get_random_color():
  11. """生成三组随机RGB数字,以便构成颜色"""
  12. a = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
  13. return a
  14. def get_valide_code_img(request):
  15. """
  16. 功能设计:分别为绘图,随机噪点,随机噪线
  17. Image.new:生成图片对象,分别是显示模式,大小[长宽],RGB数字
  18. ImageDraw.Draw: 导入并且绘制图像
  19. ImageFont:指定文字文件地址,TTF格式字体
  20. """
  21. img = Image.new("RGB", (240, 30), color=get_random_color())
  22. draw = ImageDraw.Draw(img)
  23. FiraCode = ImageFont.truetype("static/font/FiraCode-Regular.ttf", size=24)
  24. valid_code_str = ""
  25. for i in range(6):
  26. random_num = random.randint(0, 9) # 随机数字
  27. random_low_alpha = chr(random.randint(95, 122)) # 随机小写字符,ASCⅡ大小写字母范围
  28. random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字符
  29. random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
  30. draw.text((i * 50 + 20, 5), str(random_char), get_random_color(), font=FiraCode) # 转换第二个参数的类型
  31. # 保存验证码字符串
  32. valid_code_str += str(random_char)
  33. width = 250
  34. height = 40
  35. for i in range(10):
  36. x1 = random.randint(0, width)
  37. x2 = random.randint(0, width)
  38. y1 = random.randint(0, height)
  39. y2 = random.randint(0, height)
  40. draw.line((x1, x2, y1, y2), fill=get_random_color())
  41. for i in range(200):
  42. draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())
  43. x = random.randint(0, width)
  44. y = random.randint(0, height)
  45. draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())
  46. # 打印保存的验证码
  47. # print("valid_code_str", valid_code_str)
  48. request.session["valid_code_str"] = valid_code_str
  49. """
  50. 1. 生成随机字符串
  51. 2. 设置一个cookie{sessionid:随机字符串}
  52. 3. django——session存储session_key---随机字符串,session_data---验证码
  53. """
  54. f = BytesIO()
  55. img.save(f, "png")
  56. data = f.getvalue()
  57. return data

urls.py

  1. """whereabouts URL Configuration
  2. The `urlpatterns` list routes URLs to views. For more information please see:
  3. https://docs.djangoproject.com/en/3.2/topics/http/urls/
  4. Examples:
  5. Function views
  6. 1. Add an import: from my_app import views
  7. 2. Add a URL to urlpatterns: path('', views.home, name='home')
  8. Class-based views
  9. 1. Add an import: from other_app.views import Home
  10. 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
  11. Including another URLconf
  12. 1. Import the include() function: from django.urls import include, path
  13. 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
  14. """
  15. from django.contrib import admin
  16. from django.urls import path, include
  17. from django.conf import settings
  18. from django.conf.urls.static import static
  19. from blog import views
  20. urlpatterns = [
  21. path('admin/', admin.site.urls),
  22. path('summernote/', include('django_summernote.urls')),
  23. # http://127.0.0.1:8001/login/
  24. path('login/', views.login),
  25. path('get_validCode_img/', views.get_validCode_img),
  26. # http://127.0.0.1:8001/index/
  27. path('index/', views.index),
  28. # http://127.0.0.1:8001/registry/
  29. path('registry/', views.registry),
  30. ]
  31. if settings.DEBUG:
  32. urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)