博客园-注册(form组件,ajax提交数据,头像上传,media)
博客园-注册(form组件,ajax提交数据,头像上传,media)
form类创建
使用form组件需要我们自己创建一个类
import re
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
from blog import models
# Create your views here.
class RegForm(forms.Form):
user = forms.CharField(
max_length=20,
min_length=6,
error_messages={
"required": "用户名不能为空",
"max_length": "用户名不能超过20位",
"min_length": "用户名不能少于6位"
},
widget=widgets.TextInput(attrs={"class": "form-control"})
)
nickname = forms.CharField(
max_length=20,
min_length=3,
error_messages={
"required": "用户名不能为空",
"max_length": "用户名不能超过20位",
"min_length": "用户名不能少于3位"
},
widget=widgets.TextInput(attrs={"class": "form-control"})
)
pwd = forms.CharField(
min_length=6,
widget=widgets.PasswordInput(attrs={"class": "form-control"}),
error_messages={
"required": "密码不能为空",
"min_length": "密码不能少于6位"
}
)
repeat_pwd = forms.CharField(
widget=widgets.PasswordInput(attrs={"class": "form-control"}),
error_messages={
"required": "确认密码不能为空"
}
)
email = forms.EmailField(
error_messages={
"invalid": "格式错误",
"required": "邮箱不能为空"
},
widget=widgets.EmailInput(attrs={"class": "form-control"})
)
tel = forms.IntegerField(
error_messages={
"required": "手机号不能为空"
},
widget=widgets.NumberInput(attrs={"class": "form-control"})
)
def clean_user(self):
user = self.cleaned_data.get("user")
if models.UserInfo.objects.filter(username=user).exists():
raise ValidationError("用户名已存在")
else:
return user
def clean_nickname(self):
nickname = self.cleaned_data.get("nickname")
if models.UserInfo.objects.filter(nickname=nickname).exists():
raise ValidationError("昵称已存在")
else:
return nickname
def clean_tel(self):
tel = self.cleaned_data.get("tel")
if re.search("^1[3458]\d{9}$", str(tel)):
return tel
else:
raise ValidationError("手机号格式错误")
def clean(self):
pwd = self.cleaned_data.get("pwd")
repeat_pwd = self.cleaned_data.get("repeat_pwd")
if pwd == repeat_pwd:
return self.cleaned_data
else:
raise ValidationError("密码不一致")
在这个类中我们定义好每一个字段的类型及一些判断条件
前端页面渲染
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>注册页面</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.min.css' %}">
<script src="{% static 'jquery-3.2.1.min.js' %}"></script>
<script src="{% static 'bootstrap-3.3.7/js/bootstrap.min.js' %}"></script>
<style>
body{
background-color: #eeeeee;
}
.d1{
margin-top: 100px;
}
span{
color: red;
}
.form-group{
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container d1">
<div class="row">
<div class="col-sm-3 col-sm-offset-3">
<h3>注册</h3>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-sm-offset-1 col-sm-10">
<form class="form-horizontal">
{% csrf_token %}
<div class="form-group">
<label for="id_user" class="col-sm-2 control-label">用户名</label>
<div class="col-sm-8">
{{ form_obj.user }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="id_nickname" class="col-sm-2 control-label">昵称</label>
<div class="col-sm-8">
{{ form_obj.nickname }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="id_pwd" class="col-sm-2 control-label">密码</label>
<div class="col-sm-8">
{{ form_obj.pwd }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="id_repeat_pwd" class="col-sm-2 control-label">确认密码</label>
<div class="col-sm-8">
{{ form_obj.repeat_pwd }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="id_email" class="col-sm-2 control-label">邮箱</label>
<div class="col-sm-8">
{{ form_obj.email }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="id_tel" class="col-sm-2 control-label">手机号</label>
<div class="col-sm-8">
{{ form_obj.tel }}
<span class="pull-right"> </span>
</div>
</div>
<div class="form-group">
<label for="avatar" class="col-sm-2 control-label">头像</label>
<div class="col-sm-8">
<label for="avatar"><img src="" alt="" width="60" height="60" id="avatar_img"></label>
<input type="file" style="display: none;" id="avatar">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-5">
<input type="button" class="btn btn-primary btn-block" value="注册">
</div>
</div>
</form>
</div>
</div>
</div>
通过form组件定义的类实例化一个对象,使用这个对象对前端页面进行渲染
头像选择
在前端页面中我们可以看到有一个头像的选项,一般情况下用户点击头像的默认框就可以选择自己要上传的头像
为了实现这个功能,我们先写一个隐藏的input框(type=file),再利用label标签与他绑定关系,将img标签写入label标签内
这样便实现了点击img图片也能上传头像的功能
<div class="form-group">
<label for="avatar" class="col-sm-2 control-label">头像</label>
<div class="col-sm-8">
<label for="avatar"><img src="" alt="" width="60" height="60" id="avatar_img"></label>
<input type="file" style="display: none;" id="avatar">
</div>
</div>
头像预览功能
头像预览功能与后端没有什么关系,可以通过前端获取用户选择的图片的路径,然后直接将img标签的src属性改为用户选择图片的路径,实现图片的预览功能
// 头像预览
$("#avatar").change(function () {
var choose_file = $(this)[0].files[0];
// 实例化一个阅读器对象
var reader = new FileReader();
// 读取文件的路径,没有返回值,结果在reader.result里
reader.readAsDataURL(choose_file);
// 读取需要时间,读完后再修改图片路径
reader.onload=function () {
$("#avatar_img").attr("src",this.result)
}
});
这里我们用到了一个取用户选择的文件对象的方法,先使用$("#avatar")取到上传文件的input标签的jquery对象,通过$("#avatar")[0]取到该标签的DOM对象
再通过DOM对象的files方法拿到上传文件的数组,最后通过索引获取上传的文件对象,即为$("#avatar")[0].files[0]
得到这个对象后,为了获取该文件的路径,我们使用阅读器的功能
先实例化一个阅读器对象var reader = new FileReader(),在利用阅读器对象的readAsDATAURL方法读取文件的路径
由于读取文件路径需要时间,所以我们对该阅读器对象绑定了一个onload方法,待读取完成后,再将img标签的src属性更改为文件的路径
这样便实现了头像预览的功能
ajax传文件
由于需要传文件,如果用form表单传,需要设置enctype="multipart/form-data"
我们这里使用ajax发送数据,需要用到FormData
$(":button").click(function () {
// 实例一个对象,用它来封装数据
var formdata = new FormData();
formdata.append("user",$("#id_user").val());
formdata.append("nickname",$("#id_nickname").val());
formdata.append("pwd",$("#id_pwd").val());
formdata.append("repeat_pwd",$("#id_repeat_pwd").val());
formdata.append("email",$("#id_email").val());
formdata.append("tel",$("#id_tel").val());
formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());
formdata.append("avatar",$("#avatar")[0].files[0]);
$.ajax({
url: "/register/",
type: "post",
data: formdata,
contentType: false,
processData: false,
success: function (data) {
var data = JSON.parse(data);
if (data.reg){
location.href="/login/"
}else{
// 清空上次错误信息
$("form span").html(" ");
$(".form-group").removeClass("has-error");
var errors = data.error_msg;
for (var i in errors){
if (i === "__all__"){
// 边框变红
$("#id_repeat_pwd").parent().parent().addClass("has-error");
$("#id_repeat_pwd").next().text(errors[i][0])
}else{
$("#id_"+i).parent().parent().addClass("has-error");
$("#id_"+i).next().text(errors[i][0])
}
}
}
// 还可以使用each循环
{# $.each(errors, function (field,error) {#}
{# $("#id_"+field).next().text(error[0])#}
{# })#}
}
})
})
注意,这里要加contentType: false, processData: false,否则会出错
拿到后端传回的数据后
先将后端传回的数据进行范序列化,再通过传回数据的键值对进行判断
如果数据没问题,则进行跳转
如果数据有问题,需要将错误信息显示到页面上
由于后端传回的错误信息的键值对的键对应的都是form类中的字段,而通过form类实例化的对象渲染出的页面的input标签的id为id_字段,可以通过字符串拼接的方式,得到每个input框的id
从而取到每个input框的jquery对象,再将相应的错误信息显示到每个input框后面即可
后端数据处理
def register(request):
reg_info = {"reg": True, "error_msg": ""}
if request.method == "POST":
form_obj = RegForm(request.POST)
if form_obj.is_valid():
user = form_obj.cleaned_data.get("user")
nickname = form_obj.cleaned_data.get("nickname")
pwd = form_obj.cleaned_data.get("pwd")
email = form_obj.cleaned_data.get("email")
tel = form_obj.cleaned_data.get("tel")
avatar_obj = request.FILES.get("avatar") # 图片对象
models.UserInfo.objects.create_user(username=user, password=pwd, email=email, telephone=tel, nickname=nickname, avatar=avatar_obj) # 添加avatar字段时会自动下载文件,放到upload_to后的路径里
else:
reg_info["error_msg"] = form_obj.errors
reg_info["reg"] = False
return HttpResponse(json.dumps(reg_info))
form_obj = RegForm()
return render(request, "register.html", locals())
通过前端传来的数据,实例化一个form_obj对象,在通过这个对象的is_vaild()方法对数据进行校验,如果数据没问题,则从form_obj.cleaned_data中取出数据,使用UserInfo.objects.create_user方法添加数据
这里需要注意的是,avatar字段,这里我们直接使用的是avatar=avatar_obj这个文件对象,其实在数据库中该字段还是会存放这个文件的路径,并将用户上传的文件放到相应的路径下
media
在之前的配置中,我们在settings文件中配置过静态文件的存放目录和别名,用来专门存放一些静态文件
而在django中,也会将用户上传的文件统一放到一个地方(类似static配置)
class UserInfo(AbstractUser):
"""
用户信息
"""
nid = models.AutoField(primary_key=True)
nickname = models.CharField(verbose_name='昵称', max_length=32)
telephone = models.CharField(max_length=11, null=True, unique=True)
avatar = models.FileField(upload_to='avatar_dir/', default="/avatar/default.png")
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
blog = models.OneToOneField(to='Blog', to_field='nid', null=True)
针对FileField,Imagefield字段:
avatar = models.FileField(upload_to = 'avatars/',default="/avatar/default.png")
默认会将FileField字段中的upload_to参数对应的值avatars文件下载到项目的根目录下
if 在settings配置了一句:
MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")
将FileField字段中的upload_to参数对应的值avatars下载到MEDIA_ROOT路径下
MEDIA_URL = "/media/" # 别名 MEDIA_ROOT = os.path.join(BASE_DIR, "blog", "media")

这里的配置与static静态文件的配置类型,只不过static配置了下面的内容后
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
用户可以直接通过/static/接文件的路径访问到静态文件,这是因为django在这里自动帮你配置了url
而media这里django并未帮你配置,所以我们需要自己在url里配置
from django.conf.urls import url
from django.contrib import admin
from blog import views
from django.views.static import serve
from s8_cnblog import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.log_in),
url(r'^get_valid_img/', views.get_valid_img),
url(r'^index/', views.index),
url(r'^logout/', views.logout),
url(r'^register/', views.register),
# media 配置
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
]
这样我们便可以通过/media/接具体的路径找到用户上传的文件了


浙公网安备 33010602011771号