博客园-注册(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/接具体的路径找到用户上传的文件了