1. 程式人生 > >Django - 基於forms組件和Ajax實現註冊登錄 - FileField字段 - Media配置

Django - 基於forms組件和Ajax實現註冊登錄 - FileField字段 - Media配置

ron efault require result 基於 找到 parent 檢驗 body

  

一.基於forms組件的註冊頁面設計

  點擊頭像==點擊input

  頭像預覽:

    修改 獲取用戶選中的文件對象;獲取文件對象的路徑;修改img標簽的src屬性,讓src=文件對象路徑。

  register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"
> <style> #avatar{ {# 隱藏file的input標簽 #} display: none } #avatar_img{ {# 默認頭像右移20px #} margin-left: 20px; } .error_info{ color: red; } </style> </head> <body> <
h3>註冊頁面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3" > <form id="form"> {% csrf_token %} {% for filed in form %} // for循環這個form就是有user,pwd,re_pwd,email這些字段,進行渲染
<div class="form-group"> {# <label for="">{{ filed.label }}</label>#} <label for="{{ filed.auto_id }}">{{ filed.label }}</label> {# 由於forms組件渲染的input標簽中的id都會自動加上_id,所以在label標簽這裏的for為了能保持一致,#} {#寫上filed.auto_id ,會自動的補上_id#} {{ filed }} // 它渲染出來有input標簽,會跟字段的名字一致,只不過這裏要發ajax </div> {% endfor %} <div class="form-group"> {# 頭像沒有放到input裏邊,把它單獨處理,它不需要檢驗#} <label for="avatar"> {# input中的id和label中的for一致,則點擊頭像和圖片都可以跳轉到input上面 #} 頭像 <img width="60" height="60" id="avatar_img" src="/static/blog/imgs/default.png" alt=""> </label> <input type="file" id="avatar" > {# 此時這個標簽沒有意義了,需要隱藏起來,style="display:None" #} </div> <input type="button" class="btn btn-default reg_btn " value="submit"><span class="error"></span> </form> </div> </div> </div> </body> </html>

  Myforms.py

# -*- coding:utf-8 -*-

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="用戶名",  # label標簽,這樣子就可以渲染出中文了
                           widget=widgets.TextInput(attrs={"class": "form-control"})
                           )  # 為這個字段添加類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"})
                             )

二.註冊頁面的默認頭像

  把img標簽寫到label裏邊去,點擊頭像就相當於點擊input標簽

技術分享圖片

  http://127.0.0.1:8000/static/blog/img/default.png,這樣子就可以訪問到這個默認的註冊頭像

  很多網站是這樣子:把input標簽的長寬跟這個頭像一致,然後利用css裏邊定位position=absolute,把這兩個標簽定位到一個位置上去。再把input標簽透明度顯示為0,這時候它倆疊到一起了,你點擊頭像也就點擊了input標簽。 另外一種思路是:利用label標簽的for的值與input標簽的id值一致就可以了,點擊label相當於點擊input標簽。

技術分享圖片

三.註冊頁面的頭像預覽功能

  Console---》

$("#avatar")
r.fn.init [input#avatar]0: input#avatarlength: 1__proto__: Object(0)
$("#avatar")[0]
<input type=?"file" id=?"avatar">?
$("#avatar")[0].files
FileList {0: File(593239), length: 1}0: File(593239) {name: "distance.png", lastModified: 1510650980163, lastModifiedDate: Tue Nov 14 2017 17:16:20 GMT+0800 (中國標準時間), webkitRelativePath: "", size: 593239, …}length: 1__proto__: FileList
$("#avatar")[0].files[0]
File(593239) {name: "distance.png", lastModified: 1510650980163, lastModifiedDate: Tue Nov 14 2017 17:16:20 GMT+0800 (中國標準時間), webkitRelativePath: "", size: 593239, …}

  頭像預覽:

    修改用戶選中的文件對象;獲取文件對象的路徑;修改img的src屬性,src=文件對象路徑。

<script src="/static/js/jquery-3.3.1.js"></script>

    <script>

        $("#avatar").change(function () {  // change事件,選中文本之後事件就發生了,
            // 頭像預覽:
                    // 1.獲取用戶選中的文件對象
                    var file_obj = $(this)[0].files[0];

                    // 2.獲取文件對象的路徑
                    // 基於文件閱讀器 FileReader,new一個實例對象
                    var reader = new FileReader();

                    reader.readAsDataURL(file_obj);// 讀完之後沒有返回值,而是默認加到你的對象裏面
                                                   // FileReaderURL是讀取文件的URL
                    // 3.修改img的src屬性,src=文件對象的路徑
                    // attr給屬性賦值
                    // reader.readAsDataURL(file_obj)讀的時間很長,而且他們都是異步的,還沒有讀完,就已經執行下面的替換操作了,會找不到路徑

                    // $("#avatar_img").attr("src", reader.result)

                    // 等讀完之後在執行下面這句代碼
                    reader.onload = function () {
                        $("#avatar_img").attr("src", reader.result)  // 結果在reader.result裏邊

                    }
        });
</script>

四.基於Ajax提交formdata數據和註冊頁面顯示錯誤信息

  點擊提交(什麽都不輸入直接submit)的錯誤信息在這裏邊:---->>> { user:null,msg:{ } }

技術分享圖片

<script>
    // 基於ajax提交數據
        $(".reg_btn").click(function () {
            // ajax上傳文件類型一定要換成formdata格式
            // 使用formdata,必須加上這兩個,
            // contentType:false,
            // processData:false,
            // 否則會報錯

            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]);

            // 循環往formdata添加數據
{#            console.log($("#form").serializeArray()); // 打印form表單所有的鍵值對#}
            var request_data = $("#form").serializeArray();
            $.each(request_data, function (index,data) {
                formdata.append(data.name, data.value);
            });

            $.ajax ({
                url:"",
                type:"post",
                contentType:false,
                processData:false,
                data:formdata,  // 把上邊的formdata構建出來以後,加到這裏邊來
                success:function (data) {
                    //console.log(data)
                    if (data.user){
                        // 註冊成功,跳轉到登錄頁面
                        location.href = "/login/"
                    }
                    else { // 註冊失敗
                        // 由於每次展示的時候會將錯誤的信息保存在span中,需要清空,否則會出現問題
                        $("span.error_info").text("");
                        $(".form-group").removeClass("has-error");  //展示之前,移除類has-error
                        // 循環展示此次提交的錯誤信息
                        $.each(data.msg, function (field, error_list){
                            // 判斷是不是個全局的
                            if (field=="__all__"){
                                $("#id_re_pwd").next().text(error_list[0]).parent().addClass("has-error"); // 鏈式操作
                            }
                            // 顯示錯誤信息:next()是找到下一個標簽
                            $("#id_"+field).next().text(error_list[0]);
                            // 顯示紅色邊框:找到父標簽,然後添加一個bootstrap的類has-error
                            $("#id_"+field).parent().addClass("has-error");
                    })
                    }

                }

            })
        })
</script>

  views.py

def register(request):

    # if request.method == "POST": # 在這裏二者都可以
    if request.is_ajax(): # 點擊那個按鈕,既是Ajax請求,又是post請求
        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   # 失敗之後把所有的錯誤信息放到msg裏邊

        return JsonResponse(response)

    form = UserForm()
    print(form)

    return render(request, "register.html", {"form": form})

五.forms組件的局部鉤子與全局鉤子的應用

  Myforms.py

# 加局部鉤子,驗證用戶是否註冊
    def clean_user(self):
        val = self.cleaned_data.get("user")  # 取到已經在上面通過基礎的格式驗證的user

        user = UserInfo.objects.filter(username=val).first()  # 在數據庫中過濾是否有一致的用戶名

        if not user:
            return val
        else:
            raise ValidationError("該用戶已註冊!")

    # 加全局鉤子,驗證兩次密碼是否一致,全局錯誤信息是放在__all__中

    def clean(self):
        pwd = self.cleaned_data.get("pwd")
        re_pwd = self.cleaned_data.get("re_pwd")

        if pwd and re_pwd:  # 當兩次密碼輸入都有值的時候才會對兩者進行比較
            if pwd == re_pwd:
                return self.cleaned_data
            else:
                raise ValidationError("兩次密碼輸入不一致!")
        else:
            return self.cleaned_data

  全局錯誤可以在視圖裏邊取出來,跟之前form表單提交錯誤信息不一樣,之前是把錯誤信息放到視圖裏邊,放到模板裏邊去。這裏可以直接在register裏邊

錯誤信息在errors裏邊,裏邊有個鍵叫__all__,對應列表error_list的信息,發給ajax了,ajax拿到數據做下判斷---->>>

 if (field == "__all__"){   //處理全局錯誤信息;下面找到它的標簽,父標簽給它加上
        $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); //給它放到re_pwd確認密碼的下面 
                        }

六.FileField(可以上傳任何文件)與ImageFiled(只能上傳圖片)

 class UserInfo(AbstractUser):  # 使用用戶認證組件需要使用auth_user表,擴展這個表需要繼承AbstractUser,User繼承的就是AbstractUser
                """
                用戶信息
                用戶信息表和博客信息表是一對一的關系
                """
                nid = models.AutoField(primary_key=True)
                telephone = models.CharField(max_length=11, null=True, unique=True)
                avatar = models.FileField(upload_to=‘avatars/‘, default="/avatars/default.png")  # 存本地用戶圖像字段

            avatar_obj = request.FILES.get("avatar")
            UserInfo.objects.create_user(username=user,password=pwd, email=email, avatar=avatar_obj)  # 這裏avatar一定要接收一個文件對象

            Django實現:
                會將文件對象下載到項目的根目錄中avatars文件夾中(如果沒有avatars文件夾,Django會自動創建),
            user_obj的avatar存的是文件的相對路徑

技術分享圖片

  

  views.py

def register(request):

    # if request.method == "POST": # 在這裏二者都可以
    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")

            # 生成一條用戶記錄
            user = form.cleaned_data.get("user")
            pwd = form.cleaned_data.get("pwd")
            email= form.cleaned_data.get("email")
avatar_obj
= request.FILES.get("avatar") UserInfo.objects.create_user(username=user,password=pwd, email=email, avatar=avatar_obj) else: print(form.cleaned_data) print(form.errors) response["msg"] = form.errors return JsonResponse(response) form = UserForm() print(form) return render(request, "register.html", {"form": form})

七. Media配置之MEDIA_ROOT

Django有兩種靜態文件:

  /static/   :js,css,img

  /media/   :用戶上傳文件; 用戶默認頭像圖片應該在media/avatar裏邊存一份。

class UserInfo(AbstractUser): 
‘‘‘用戶信息‘‘‘
nid = models.AutoField(primary_key=True)
telephone = models.CharField(max_length=11, null=True, unique=True)
avatar = models.FileField(upload_to=‘avatars/‘, default="/avatars/default.png")
avatar_obj = request.FILES.get("avatar")
user_obj = UserInfo.objects.create_user(username = user, password = pwd, email = email, avatar = avatar_obj)

用戶一旦配置了

MEDIA_ROOT = os.path.join(BASE_DIR, "media") 這個路徑(在settings裏邊)
Django會實現:
  會將文件對象下載到MEDIA_ROOT中avatars文件夾中(如果沒有avatar文件夾,Django會自動創建),user_obj的avatar存的是文件的相對路徑。

  settings.py

技術分享圖片
STATIC_URL = ‘/static/‘

STATICFILES_DIRS = [
    os.path.join(BASE_DIR,"static")
]

MEDIA_ROOT = os.path.join(BASE_DIR, "media")
技術分享圖片

八. Media配置之MEDIA_URL

  訪問http://127.0.0.1:8000/static/blog/img/default.png是可以直接訪問到的,如果是http://127.0.0.1:8000/blog/views.py/是訪問不到的,不能把你的源代碼暴露出來啊,static裏邊的無所謂,而static是Django給你配置好的,應該讓用戶看到。同樣的media裏邊應該也讓用戶看到。  瀏覽器如何能直接訪問到media中的數據呢?

  settings.py

MEDIA_URL = ‘/media/‘
#與用戶上傳相關的配置
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = ‘/media/‘

  urls.py

#media配置
re_path(r"media/(?P<path>.*)$", serve, {"document_root":settings.MEDIA_ROOT})

瀏覽器訪問http://127.0.0.1:8000/media/avatars/11.jpg可以直接訪問media裏邊的文件了

註意規範問題:

技術分享圖片

優化代碼問題:

  views.py

def register(request):

    # if request.method == "POST": # 在這裏二者都可以
    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")

            # 生成一條用戶記錄
            user = form.cleaned_data.get("user")
            pwd = form.cleaned_data.get("pwd")
            email= form.cleaned_data.get("email")
            avatar_obj = request.FILES.get("avatar")

            # 如果用戶沒有上傳圖像怎麽辦?
            # 沒有會有個默認的圖像,但是下面這段代碼已經上傳了一個空的avatar_obj,不會走到默認
            # 所以需要加一個判斷
            # UserInfo.objects.create_user(username=user,password=pwd, email=email, avatar=avatar_obj)

            # 重復代碼太多,優化
            # if avatar_obj:
            #     UserInfo.objects.create_user(username=user,password=pwd, email=email, avatar=avatar_obj)
            # else:
            #     UserInfo.objects.create_user(username=user, password=pwd, email=email)

            # def create_user(self, username, email=None, password=None, **extra_fields):
            # create_user中有個參數**extra_fields,所以可以下面這樣來優化
            extra = {}

            if avatar_obj:
                extra[avatar] = avatar_obj

            UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)

        else:
            print(form.cleaned_data)
            print(form.errors)
            response["msg"] = form.errors

        return JsonResponse(response)

    form = UserForm()
    print(form)

    return render(request, "register.html", {"form": form})

Django - 基於forms組件和Ajax實現註冊登錄 - FileField字段 - Media配置