整体设计理念
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表单制造字段,在前端修改页面,使用模板标签循环渲染字段
代码
# views
from django import forms
class 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 forms
from django.forms import widgets
class 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:"", //使用当前的URL
type:"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.errors
return 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: "", //使用当前的URL
type: "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: "", //使用当前的URL
type: "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 : whereabouts
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"}))
在views.py
中引入外部文件Myforms.py
from django import forms
from django.forms import widgets
from blog.Myforms import UserForm
def registry(request):
"""
如果提交的数据错误,则由一个字典在原页面上显示提示信息
"""
if request.is_ajax():
print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': ['1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
form = UserForm(request.POST) # 由UserForm做验证
response={"user":None,"msg":None} # 用于前端交互,传递message
if form.is_valid():
response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
else:
print(form.cleaned_data)
print(form.errors)
response["msg"] = form.errors
return 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 : whereabouts
from django import forms
from django.forms import widgets
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"}))
实现效果
第三个设计:校验用户名是否已存在
概览
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 : whereabouts
from django import forms
from django.forms import widgets
from blog.models import UserInfo
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
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"}))
def clean_user(self):
val=self.cleaned_data.get("user")
user=UserInfo.objects.filter(username=val).first()
print(user)
if not user:
return val
else:
raise ValidationError("该用户已注册!")
最终实现效果
第四个设计:校验密码重复是否正确
1. 整体设计:提取前端获取的数据,导入数据表进行查询,将查询结果用变量报错,然后进行二者的匹配
2. 具体实现:需要导入异常处理包,负责报错功能;导入models中的UserInfo表,进行数据库查询;
使用django.form.form,cleaned_data.get()分别获取输入的密码和重复输入的密码
用逻辑运算符匹配两个变量的值,最后用ValidationError()抛出报错
3. 调试的方法:每检查一次功能实现,都是在注册页面点击提交
为什么要设置全局报错—默认位置在控制台:需要指定位置
registry.html中的代码
第二十行为新增代码主要匹配H5中的字段,全局的钩子,匹配完成之后给出返回的对象,has-error用于将选框遍红
$.ajax({
url: "", //使用当前的URL
type: "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_data
else:
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} # 用于前端交互,传递message
if form.is_valid():
response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
user = 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.errors
return 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} # 用于前端交互,传递message
if form.is_valid():
response["user"]=form.cleaned_data.get("user") # 验证通过则会传递用户名
# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
user = 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.errors
return 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-url
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
默认路径
自定义后的效果
第八个版本—遵循开发规范优化代码
1 views.py
文件中提交与未提交的创建信息差异
原来的代码
优化后的代码
参数说明
# django.contrib.auth.models
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)
测试是否生效
2 导入包的优化—标准库->插件包->自定义包
3添加文档字符串解释参数
4 使用工具优化代码—包括符号,缩进,参数
总结
做项目过程中就把代码注释写好,尤其是参数和调用的包,那些陌生的扩展包的用法,不要等到最后阶段总结才开始弄;
注重项目文件之间的代码和数据传递,调用或者引用都在两处显著标明,免得最后找不到参数是从哪里来的,究竟干什么
代码汇总—环境现场保存
views.py
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from django.contrib import auth
import PIL, random
from blog.models import UserInfo
from blog.Myforms import UserForm
from blog.utils.validCode import get_valide_code_img
def login(request):
"""
功能设计:验证码和用户信息的校验
不区分验证码大小写,统一转换为大写 uppercase
auth.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.username
else:
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} # 用于前端交互,传递message
if form.is_valid():
response["user"] = form.cleaned_data.get("user") # 验证通过则会传递用户名
# 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
# 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
user = 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_obj
UserInfo.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.errors
return 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---指的是提取的字段ID
var 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: "", //使用当前的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)#}
// 在针对字段添加错误提示之前先清空提交时传递的错误信息
$("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 : whereabouts
from django import forms
from django.forms import widgets
from blog.models import UserInfo
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
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"}))
def clean_user(self):
"""局部钩子:校验用户创建时输入的用户名和MySQL存储的用户名"""
val = self.cleaned_data.get("user")
user = UserInfo.objects.filter(username=val).first()
if not user:
return val
else:
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_data
else:
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 : whereabouts
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import random
def get_random_color():
"""生成三组随机RGB数字,以便构成颜色"""
a = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
return a
def 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 = 250
height = 40
for 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 Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from blog import views
urlpatterns = [
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)