博客园-注册(form组件,ajax提交数据,头像上传,media)

Posted .

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了博客园-注册(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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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("&nbsp;");
                    $(".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/接具体的路径找到用户上传的文件了

以上是关于博客园-注册(form组件,ajax提交数据,头像上传,media)的主要内容,如果未能解决你的问题,请参考以下文章

python学习点滴记录-Day22

Django - 基于forms组件和Ajax实现注册登录 - FileField字段 - Media配置

Django框架form表单配合ajax注册

1.4 博客系统| 基于Ajax提交数据

网站之注册

博客系统需求分析和表结构设计