72-django-BBS項目開發

今日內容概要

bbs是一個先後端不分離的全棧項目,前端和後端都須要咱們本身一步步的完成php

  • 表建立及同步
  • 註冊功能
    • forms組件
    • 用戶頭像前端實時展現
    • ajax
  • 登錄功能
    • 本身實現圖片驗證碼
    • ajax

今日內容詳細

BBS表關係圖解

數據庫表建立及同步

 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)  # 有些評論就是根評論
數據庫表建立及同步

註冊功能

myforms.py

 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
myforms.py

views.py

 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 """
views.py

register.html

  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>
register.html

登陸功能

views.py

 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())
views.py

login.html

 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>
login.html
相關文章
相關標籤/搜索