思路:
文件上傳通過前端的input標簽,input設置display:none屬性。 內容顯示需要讓前端通過<img>標簽讀取圖片內容,可以通過<label>標簽連接<img>和<input>屬性。 文件上傳后通過ajax提交到后台,驗證成功后,通過locations.href實現頁面跳轉。
前端需要使用的方法:
# 當標簽內的內容出現變化時,要觸發相應的方法。 $("#id").change(function(){ ... }) # 創建一個讀取文件的對象 var obj = new FileReader(); # 當全部文件取完畢后,把圖片加載到img標簽中 onload $("id").attr("src", "圖片路徑") # ajax提交圖片文件 var formData = new FormData(); # input框綁定獲取焦點的事件 $('form input').focus(function(){ ... })
設計數據庫模型

class UserInfo(AbstractUser): """ 用戶信息表 """ nid = models.AutoField(primary_key=True) phone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to="avatars/", default="avatars/default.png", verbose_name="頭像") create_time = models.DateTimeField(auto_now_add=True) blog = models.OneToOneField(to="Blog", to_field="nid", null=True, on_delete=models.CASCADE) def __str__(self): return self.username class Blog(models.Model): """ 博客信息 """ nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) # 個人博客標題 site = models.CharField(max_length=32, unique=True) # 個人博客后綴 theme = models.CharField(max_length=32) # 博客主題 def __str__(self): return self.title class Category(models.Model): """ 個人博客文章分類 """ nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) # 分類標題 blog = models.ForeignKey(to="Blog", to_field="nid", on_delete=models.CASCADE) # 外鍵關聯博客,一個博客站點可以有多個分類 def __str__(self): return self.title class Tag(models.Model): """ 標簽 """ nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) # 標簽名 blog = models.ForeignKey(to="Blog", to_field="nid", on_delete=models.CASCADE) # 所屬博客 def __str__(self): return self.title class Article(models.Model): """ 文章 """ nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50) # 文章標題 desc = models.CharField(max_length=255) # 文章描述 create_time = models.DateTimeField() # 創建時間 category = models.ForeignKey(to="Category", to_field="nid", null=True, on_delete=models.CASCADE) user = models.ForeignKey(to="UserInfo", to_field="nid", on_delete=models.CASCADE) tags = models.ManyToManyField( # 中介模型 to="Tag", through="Article2Tag", through_fields=("article", "tag"), # 注意順序!!! ) def __str__(self): return self.title class ArticleDetail(models.Model): """ 文章詳情表 """ nid = models.AutoField(primary_key=True) content = models.TextField() article = models.OneToOneField(to="Article", to_field="nid", on_delete=models.CASCADE) class Article2Tag(models.Model): """ 文章和標簽的多對多關系表 """ nid = models.AutoField(primary_key=True) article = models.ForeignKey(to="Article", to_field="nid", on_delete=models.CASCADE) tag = models.ForeignKey(to="Tag", to_field="nid", on_delete=models.CASCADE) class Meta: unique_together = (("article", "tag"),) class ArticleUpDown(models.Model): """ 點贊表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey(to="UserInfo", null=True, on_delete=models.CASCADE) article = models.ForeignKey(to="Article", null=True, on_delete=models.CASCADE) is_up = models.BooleanField(default=True) class Meta: unique_together = (("article", "user"),) class Comment(models.Model): """ 評論表 """ nid = models.AutoField(primary_key=True) article = models.ForeignKey(to="Article", to_field="nid", on_delete=models.CASCADE) user = models.ForeignKey(to="UserInfo", to_field="nid", on_delete=models.CASCADE) content = models.CharField(max_length=255) # 評論內容 create_time = models.DateTimeField(auto_now_add=True) parent_comment = models.ForeignKey("self", null=True, on_delete=models.CASCADE) def __str__(self): return self.content
# upload_to參數會自動將圖片下載到本地服務器,這里為avatar目錄
通過forms設置前端字段,實現輸入內容過濾:
from django import forms from django.core.exceptions import ValidationError class RegForm(forms.Form): username = forms.CharField( max_length=16, label="用戶名", error_messages={ "max_length": "用戶名最長16位", "required": "用戶名不能為空!", }, widget=forms.TextInput( attrs={"class":"form-control", "placeholder": "用戶名"}, ) ) password = forms.CharField( min_length=6, label="密碼", error_messages={ "required": "密碼不能為空", "min_length": "密碼不能少於6位", }, widget = forms.PasswordInput( attrs={"class": "form-control", "placeholder": "密碼"}, ) ) re_password = forms.CharField( min_length=6, label="確認密碼", error_messages={ "required": "密碼不能為空", "min_length": "密碼不能少於6位", }, widget=forms.PasswordInput( attrs={"class": "form-control", "placeholder": "密碼"}, ) ) email = forms.EmailField( label="郵箱", error_messages={ "invalid": "請輸入正確的郵箱格式", "required": "郵箱不能為空", }, widget=forms.EmailInput( attrs={"class": "form-control", "placeholder": "郵箱"}, ) ) # 重寫全局的鈎子函數,對確認密碼做校驗 def clean(self): password = self.cleaned_data.get("password") re_password = self.cleaned_data.get("re_password") if re_password and re_password != password: self.add_error("re_password", ValidationError("兩次密碼不一致")) else: return self.cleaned_data
后台方法
def register(request): if request.method == "POST": ret = {"status": 0, "msg": ""} form_obj = forms.RegForm(request.POST) # print(request.POST) if form_obj.is_valid(): # 數據庫中沒有re_password字段,需要從字典中剔除re_password字段 form_obj.cleaned_data.pop("re_password") # 接收從ajax發送過來的的圖片數據 avatar_img = request.FILES.get("avatar") print(avatar_img) models.UserInfo.objects.create_user(**form_obj.cleaned_data, avatar=avatar_img) ret["msg"] = "/reg/" return JsonResponse(ret) else: ret["status"] = 1 ret["msg"] = form_obj.errors print(ret) return JsonResponse(ret) form_obj = forms.RegForm() return render(request, 'register.html', {"form_obj": form_obj}) def reg(request): return render(request, 'index.html')
前端代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注冊</title> </head> <body> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/backend.css"> <div class="container register"> <div class="row"> <div class="col-md-6 col-md-offset-3"> {# form添加novalidate參數,代表取消前端h5的驗證,比如郵箱格式驗證#} <form novalidate action="/register/" method="post" class="form-horizontal" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <label for="{{ form_obj.username.id_for_label }}" class="col-sm-2 control-label"> {{ form_obj.username.label }} </label> <div class="col-sm-8"> {{ form_obj.username }} <span class="help-block">{{ form_obj.username.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.password.id_for_label }}" class="col-sm-2 control-label"> {{ form_obj.password.label }} </label> <div class="col-sm-8"> {{ form_obj.password }} <span class="help-block">{{ form_obj.password.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.re_password.id_for_label }}" class="col-sm-2 control-label"> {{ form_obj.re_password.label }} </label> <div class="col-sm-8"> {{ form_obj.re_password }} <span class="help-block">{{ form_obj.re_password.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.email.id_for_label }}" class="col-sm-2 control-label"> {{ form_obj.email.label }} </label> <div class="col-sm-8"> {{ form_obj.email }} <span class="help-block">{{ form_obj.email.errors.0 }}</span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label"> 頭像 </label> <div class="col-sm-8"> <label for="id_avatar"><img id="avatar-img" src="/static/img/default.png"></label> <input type="file" id="id_avatar" style="display: none;" name="avatar"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-8"> <button id="reg-submit" type="button" class="btn btn-success">注冊</button> </div> </div> </form> </div> </div> </div> {# <script src="/static/js/bootstrap.min.js"></script>#} <script src="/static/js/jquery-1.12.4.js"></script> <script> $("#id_avatar").change(function () { // 創建一個文件讀取對象 var fileReader = new FileReader; // 在更改前端圖片之前,把文件內容讀取完 fileReader.readAsDataURL(this.files[0]); // 讀取文件是需要時間的 // 文件讀取完后,重新加載到img當中 fileReader.onload = function () { $("#avatar-img").attr("src", fileReader.result); } }); $("#reg-submit").click(function () { var formData = new FormData; formData.append("username", $("#id_username").val()); formData.append("password", $("#id_password").val()); formData.append("re_password", $("#id_re_password").val()); formData.append("email", $("#id_email").val()); // 這里傳遞的是文件對象,是為了能夠定位文件,后台獲取到這個文件對象后會通過models字段單中的upload_to="avator"參數傳遞到服務器目錄。 formData.append("avatar", $("#id_avatar")[0].files[0]); formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); /* console.log($("#id_avatar")); // 獲取到了是一個jquery對象 // jQuery.fn.init [input#id_avatar, context: document, selector: "#id_avatar"] console.log($("#id_avatar")[0]); // 獲取到了input整個標簽 // <input type="file" id="id_avatar" style="display:none" name="avatar"> console.log($("#id_avatar")[0].files); // 獲取了input標簽當中的type="file"類型中的內容 //FileList {0: File, length: 1} // 0: File {name: "風景.jpg", lastModified: 1553135747721, lastModifiedDate: Thu Mar 21 2019 10:35:47 GMT+0800 (中國標准時間), webkitRelativePath: "", size: 27556, …} // length: 1 // __proto__: FileList console.log($("#id_avatar")[0].files[0]); // 獲取到了整個上傳的文件內容 // File {name: "風景.jpg", lastModified: 1553135747721, lastModifiedDate: Thu Mar 21 2019 10:35:47 GMT+0800 (中國標准時間), webkitRelativePath: "", size: 27556, …} */ $.ajax({ url: "/register/", type: "post", // 當需要傳輸圖片的時候,需要將processData和contentType設置為false processData: false, contentType: false, data: formData, success:function (data) { // 這里data是后端返回的一個字典ret = {"status": 0, "msg": "/reg/"} if (data.status){ // 有錯誤就展示錯誤 // console.log(data.msg); // 將報錯信息填寫到頁面上 $.each(data.msg, function (k,v) { // console.log("id_"+k, v[0]); // console.log($("#id_"+k)); $("#id_"+k).next("span").text(v[0]).parent().parent().addClass("has-error"); }) //console.log(123) }else { // 沒有錯誤就跳轉到指定頁面,這里data是后端返回的一個字典ret = {"status": 0, "msg": "/reg/"} location.href = data.msg; } } }) }); // 當input獲取焦點的事件,移除報錯的樣式,並且晴空報錯信息。 $("form input").focus(function () { $(this).next().text("").parent().parent().removeClass("has-error"); }) </script> </body> </html>
路由設置
path('register/', backend.register), path('reg/', backend.reg),