bbs是一個先後端不分離的全棧項目,前端和後端都須要咱們本身一步步的完成php
1 """ 2 因爲django自帶的sqlite數據庫對日期不敏感,因此咱們換成MySQL 3 """ 4 from django.db import models 5 6 # Create your models here. 7 """ 8 先寫普通字段 9 以後再寫外鍵字段 10 """ 11 from django.contrib.auth.models import AbstractUser 12 13 14 class UserInfo(AbstractUser): 15 phone = models.BigIntegerField(verbose_name='手機號',null=True) 16 # 頭像 17 avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用戶頭像') 18 """ 19 給avatar字段傳文件對象 該文件會自動存儲到avatar文件下 而後avatar字段只保存文件路徑avatar/default.png 20 """ 21 create_time = models.DateField(auto_now_add=True) 22 23 blog = models.OneToOneField(to='Blog',null=True) 24 25 26 class Blog(models.Model): 27 site_name = models.CharField(verbose_name='站點名稱',max_length=32) 28 site_title = models.CharField(verbose_name='站點標題',max_length=32) 29 # 簡單模擬 帶你認識樣式內部原理的操做 30 site_theme = models.CharField(verbose_name='站點樣式',max_length=64) # 存css/js的文件路徑 31 32 33 class Category(models.Model): 34 name = models.CharField(verbose_name='文章分類',max_length=32) 35 blog = models.ForeignKey(to='Blog',null=True) 36 37 38 class Tag(models.Model): 39 name = models.CharField(verbose_name='文章標籤',max_length=32) 40 blog = models.ForeignKey(to='Blog', null=True) 41 42 43 class Article(models.Model): 44 title = models.CharField(verbose_name='文章標題',max_length=64) 45 desc = models.CharField(verbose_name='文章簡介',max_length=255) 46 # 文章內容有不少 通常狀況下都是使用TextField 47 content = models.TextField(verbose_name='文章內容') 48 create_time = models.DateField(auto_now_add=True) 49 50 # 數據庫字段設計優化 51 up_num = models.BigIntegerField(verbose_name='點贊數',default=0) 52 down_num = models.BigIntegerField(verbose_name='點踩數',default=0) 53 comment_num = models.BigIntegerField(verbose_name='評論數',default=0) 54 55 # 外鍵字段 56 blog = models.ForeignKey(to='Blog', null=True) 57 category = models.ForeignKey(to='Category',null=True) 58 tags = models.ManyToManyField(to='Tag', 59 through='Article2Tag', 60 through_fields=('article','tag') 61 ) 62 63 64 class Article2Tag(models.Model): 65 article = models.ForeignKey(to='Article') 66 tag = models.ForeignKey(to='Tag') 67 68 69 class UpAndDown(models.Model): 70 user = models.ForeignKey(to='UserInfo') 71 article = models.ForeignKey(to='Article') 72 is_up = models.BooleanField() # 傳佈爾值 存0/1 73 74 75 class Comment(models.Model): 76 user = models.ForeignKey(to='UserInfo') 77 article = models.ForeignKey(to='Article') 78 content = models.CharField(verbose_name='評論內容',max_length=255) 79 comment_time = models.DateTimeField(verbose_name='評論時間',auto_now_add=True) 80 # 自關聯 81 parent = models.ForeignKey(to='self',null=True) # 有些評論就是根評論
1 # 書寫針對用戶表的forms組件代碼 2 from django import forms 3 from app01 import models 4 5 6 class MyRegForm(forms.Form): 7 username = forms.CharField(label='用戶名', min_length=3, max_length=8, 8 error_messages={ 9 'required': '用戶名不能爲空', 10 'min_length': "用戶名最少3位", 11 'max_length': "用戶名最大8位" 12 }, 13 # 還須要讓標籤有bootstrap樣式 14 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) 15 ) 16 17 password = forms.CharField(label='密碼', min_length=3, max_length=8, 18 error_messages={ 19 'required': '密碼不能爲空', 20 'min_length': "密碼最少3位", 21 'max_length': "密碼最大8位" 22 }, 23 # 還須要讓標籤有bootstrap樣式 24 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) 25 ) 26 27 confirm_password = forms.CharField(label='確認密碼', min_length=3, max_length=8, 28 error_messages={ 29 'required': '確認密碼不能爲空', 30 'min_length': "確認密碼最少3位", 31 'max_length': "確認密碼最大8位" 32 }, 33 # 還須要讓標籤有bootstrap樣式 34 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) 35 ) 36 email = forms.EmailField(label='郵箱', 37 error_messages={ 38 'required': '郵箱不能爲空', 39 'invalid': '郵箱格式不正確' 40 }, 41 widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) 42 ) 43 44 # 鉤子函數 45 # 局部鉤子:校驗用戶名是否已存在 46 def clean_username(self): 47 username = self.cleaned_data.get('username') 48 # 去數據庫中校驗 49 is_exist = models.UserInfo.objects.filter(username=username) 50 if is_exist: 51 # 提示信息 52 self.add_error('username', '用戶名已存在') 53 return username 54 55 # 全局鉤子:校驗兩次是否一致 56 def clean(self): 57 password = self.cleaned_data.get('password') 58 confirm_password = self.cleaned_data.get('confirm_password') 59 if not password == confirm_password: 60 self.add_error('confirm_password', '兩次密碼不一致') 61 return self.cleaned_data
1 """ 2 咱們以前是直接在views.py中書寫的forms組件代碼 3 可是爲了接耦合 應該將全部的forms組件代碼單獨寫到一個地方 4 5 若是你的項目至始至終只用到一個forms組件那麼你能夠直接建一個py文件書寫便可 6 myforms.py 7 可是若是你的項目須要使用多個forms組件,那麼你能夠建立一個文件夾在文件夾內根據 8 forms組件功能的不一樣建立不一樣的py文件 9 myforms文件夾 10 regform.py 11 loginform.py 12 userform.py 13 orderform.py 14 ... 15 """ 16 def register(request): 17 form_obj = MyRegForm() 18 if request.method == 'POST': 19 back_dic = {"code": 1000, 'msg': ''} 20 # 校驗數據是否合法 21 form_obj = MyRegForm(request.POST) 22 # 判斷數據是否合法 23 if form_obj.is_valid(): 24 # print(form_obj.cleaned_data) # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} 25 clean_data = form_obj.cleaned_data # 將校驗經過的數據字典賦值給一個變量 26 # 將字典裏面的confirm_password鍵值對刪除 27 clean_data.pop('confirm_password') # {'username': 'jason', 'password': '123', 'email': '123@qq.com'} 28 # 用戶頭像 29 file_obj = request.FILES.get('avatar') 30 """針對用戶頭像必定要判斷是否傳值 不能直接添加到字典裏面去""" 31 if file_obj: 32 clean_data['avatar'] = file_obj 33 # 直接操做數據庫保存數據 34 models.UserInfo.objects.create_user(**clean_data) 35 back_dic['url'] = '/login/' 36 else: 37 back_dic['code'] = 2000 38 back_dic['msg'] = form_obj.errors 39 return JsonResponse(back_dic) 40 return render(request,'register.html',locals()) 41 42 43 # 擴展 44 """ 45 通常狀況下咱們在存儲用戶文件的時候爲了不文件名衝突的狀況 46 會本身給文件名加一個前綴 47 uuid 48 隨機字符串 49 ... 50 """
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>註冊</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 8 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 </head> 11 <body> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-8 col-md-offset-2"> 15 <h1 class="text-center">註冊</h1> 16 <form id="myform"> <!--這裏咱們不用form表單提交數據 知識單純的用一下form標籤而已--> 17 {% csrf_token %} 18 {% for form in form_obj %} 19 <div class="form-group"> 20 <label for="{{ form.auto_id }}">{{ form.label }}</label> 21 {{ form }} 22 <span style="color: red" class="pull-right"></span> 23 </div> 24 {% endfor %} 25 <div class="form-group"> 26 <label for="myfile">頭像 27 {% load static %} 28 <img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px"> 29 </label> 30 <input type="file" id="myfile" name="avatar" style="display: none" > 31 </div> 32 33 <input type="button" class="btn btn-primary pull-right" value="註冊" id="id_commit"> 34 </form> 35 </div> 36 </div> 37 </div> 38 39 <script> 40 $("#myfile").change(function () { 41 // 文件閱讀器對象 42 // 1 先生成一個文件閱讀器對象 43 let myFileReaderObj = new FileReader(); 44 // 2 獲取用戶上傳的頭像文件 45 let fileObj = $(this)[0].files[0]; 46 // 3 將文件對象交給閱讀器對象讀取 47 myFileReaderObj.readAsDataURL(fileObj) // 異步操做 IO操做 48 // 4 利用文件閱讀器將文件展現到前端頁面 修改src屬性 49 // 等待文件閱讀器加載完畢以後再執行 50 myFileReaderObj.onload = function(){ 51 $('#myimg').attr('src',myFileReaderObj.result) 52 } 53 }) 54 55 $('#id_commit').click(function () { 56 // 發送ajax請求 咱們發送的數據中即包含普通的鍵值也包含文件 57 let formDataObj = new FormData(); 58 // 1.添加普通的鍵值對 59 {#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通鍵值對#} 60 $.each($('#myform').serializeArray(),function (index,obj) { 61 {#console.log(index,obj)#} // obj = {} 62 formDataObj.append(obj.name,obj.value) 63 }); 64 // 2.添加文件數據 65 formDataObj.append('avatar',$('#myfile')[0].files[0]); 66 67 // 3.發送ajax請求 68 $.ajax({ 69 url:"", 70 type:'post', 71 data:formDataObj, 72 73 // 須要指定兩個關鍵性的參數 74 contentType:false, 75 processData:false, 76 77 success:function (args) { 78 if (args.code==1000){ 79 // 跳轉到登錄頁面 80 window.location.href = args.url 81 }else{ 82 // 如何將對應的錯誤提示展現到對應的input框下面 83 // forms組件渲染的標籤的id值都是 id_字段名 84 $.each(args.msg,function (index,obj) { 85 {#console.log(index,obj) // username ["用戶名不能爲空"]#} 86 let targetId = '#id_' + index; 87 $(targetId).next().text(obj[0]).parent().addClass('has-error') 88 }) 89 } 90 } 91 }) 92 }) 93 // 給全部的input框綁定獲取焦點事件 94 $('input').focus(function () { 95 // 將input下面的span標籤和input外面的div標籤修改內容及屬性 96 $(this).next().text('').parent().removeClass('has-error') 97 }) 98 </script> 99 </body> 100 </html>
1 """ 2 img標籤的src屬性 3 1.圖片路徑 4 2.url 5 3.圖片的二進制數據 6 7 咱們的計算機上面致全部可以輸出各式各樣的字體樣式 8 內部其實對應的是一個個.ttf結尾的文件 9 10 http://www.zhaozi.cn/ai/2019/fontlist.php?ph=1&classid=32&softsq=%E5%85%8D%E8%B4%B9%E5%95%86%E7%94%A8 11 """ 12 13 14 def login(request): 15 return render(request,'login.html') 16 17 """ 18 圖片相關的模塊 19 pip3 install pillow 20 """ 21 from PIL import Image,ImageDraw,ImageFont 22 """ 23 Image:生成圖片 24 ImageDraw:可以在圖片上亂塗亂畫 25 ImageFont:控制字體樣式 26 """ 27 from io import BytesIO,StringIO 28 """ 29 內存管理器模塊 30 BytesIO:臨時幫你存儲數據 返回的時候數據是二進制 31 StringIO:臨時幫你存儲數據 返回的時候數據是字符串 32 """ 33 import random 34 def get_random(): 35 return random.randint(0,255),random.randint(0,255),random.randint(0,255) 36 def get_code(request): 37 # 推導步驟1:直接獲取後端現成的圖片二進制數據發送給前端 38 # with open(r'static/img/111.jpg','rb') as f: 39 # data = f.read() 40 # return HttpResponse(data) 41 42 # 推導步驟2:利用pillow模塊動態產生圖片 43 # img_obj = Image.new('RGB',(430,35),'green') 44 # img_obj = Image.new('RGB',(430,35),get_random()) 45 # # 先將圖片對象保存起來 46 # with open('xxx.png','wb') as f: 47 # img_obj.save(f,'png') 48 # # 再將圖片對象讀取出來 49 # with open('xxx.png','rb') as f: 50 # data = f.read() 51 # return HttpResponse(data) 52 53 # 推導步驟3:文件存儲繁瑣IO操做效率低 藉助於內存管理器模塊 54 # img_obj = Image.new('RGB', (430, 35), get_random()) 55 # io_obj = BytesIO() # 生成一個內存管理器對象 你能夠當作是文件句柄 56 # img_obj.save(io_obj,'png') 57 # return HttpResponse(io_obj.getvalue()) # 從內存管理器中讀取二進制的圖片數據返回給前端 58 59 60 # 最終步驟4:寫圖片驗證碼 61 img_obj = Image.new('RGB', (430, 35), get_random()) 62 img_draw = ImageDraw.Draw(img_obj) # 產生一個畫筆對象 63 img_font = ImageFont.truetype('static/font/222.ttf',30) # 字體樣式 大小 64 65 # 隨機驗證碼 五位數的隨機驗證碼 數字 小寫字母 大寫字母 66 code = '' 67 for i in range(5): 68 random_upper = chr(random.randint(65,90)) 69 random_lower = chr(random.randint(97,122)) 70 random_int = str(random.randint(0,9)) 71 # 從上面三個裏面隨機選擇一個 72 tmp = random.choice([random_lower,random_upper,random_int]) 73 # 將產生的隨機字符串寫入到圖片上 74 """ 75 爲何一個個寫而不是生成好了以後再寫 76 由於一個個寫可以控制每一個字體的間隙 而生成好以後再寫的話 77 間隙就無法控制了 78 """ 79 img_draw.text((i*60+60,-2),tmp,get_random(),img_font) 80 # 拼接隨機字符串 81 code += tmp 82 print(code) 83 # 隨機驗證碼在登錄的視圖函數裏面須要用到 要比對 因此要找地方存起來而且其餘視圖函數也能拿到 84 request.session['code'] = code 85 io_obj = BytesIO() 86 img_obj.save(io_obj,'png') 87 return HttpResponse(io_obj.getvalue())
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>登陸</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 8 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 {% load static %} 11 </head> 12 <body> 13 <div class="container-fluid"> 14 <div class="row"> 15 <div class="col-md-8 col-md-offset-2"> 16 <h1 class="text-center">登錄</h1> 17 <div class="form-group"> 18 <label for="username">用戶名</label> 19 <input type="text" name="username" id="username" class="form-control"> 20 </div> 21 <div class="form-group"> 22 <label for="password">密碼</label> 23 <input type="password" name="password" id="password" class="form-control"> 24 </div> 25 <div class="form-group"> 26 <label for="">驗證碼</label> 27 28 <div class="row"> 29 <div class="col-md-6"> 30 <input type="text" name="code" id="id_code" class="form-control"> 31 </div> 32 <div class="col-md-6"> 33 <img src="/get_code/" alt="" width="430" height="35" id="id_img"> 34 </div> 35 </div> 36 37 </div> 38 <input type="button" class="btn btn-success" value="登錄"> 39 </div> 40 </div> 41 </div> 42 <script> 43 $("#id_img").click(function () { 44 // 1 先獲取標籤以前的src 45 let oldVal = $(this).attr('src'); 46 $(this).attr('src',oldVal += '?') 47 }) 48 </script> 49 </body> 50 </html>