天天看點

django——bbs

今日内容概要

bbs是一個前後端不分離的全棧項目,前端和後端都需要我們自己一步步的完成

  • 表建立及同步
  • 注冊功能
    • forms元件
    • 使用者頭像前端實時展示
    • ajax
  • 登陸功能
    • 自己實作圖檔驗證碼
    • ajax
  • 搭建bbs首頁
    • 導覽列根據使用者是否登陸展示不同的内容

今日内容詳細

資料庫表建立及同步

"""
由于django自帶的sqlite資料庫對日期不敏感,是以我們換成MySQL
"""
from django.db import models

# Create your models here.
"""
先寫普通字段
之後再寫外鍵字段
"""
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    phone = models.BigIntegerField(verbose_name='手機号',null=True)
    # 頭像
    avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='使用者頭像')
    """
    給avatar字段傳檔案對象 該檔案會自動存儲到avatar檔案下 然後avatar字段隻儲存檔案路徑avatar/default.png
    """
    create_time = models.DateField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog',null=True)


class Blog(models.Model):
    site_name = models.CharField(verbose_name='站點名稱',max_length=32)
    site_title = models.CharField(verbose_name='站點标題',max_length=32)
    # 簡單模拟 帶你認識樣式内部原理的操作
    site_theme = models.CharField(verbose_name='站點樣式',max_length=64)  # 存css/js的檔案路徑


class Category(models.Model):
    name = models.CharField(verbose_name='文章分類',max_length=32)
    blog = models.ForeignKey(to='Blog',null=True)


class Tag(models.Model):
    name = models.CharField(verbose_name='文章标簽',max_length=32)
    blog = models.ForeignKey(to='Blog', null=True)


class Article(models.Model):
    title = models.CharField(verbose_name='文章标題',max_length=64)
    desc = models.CharField(verbose_name='文章簡介',max_length=255)
    # 文章内容有很多 一般情況下都是使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateField(auto_now_add=True)

    # 資料庫字段設計優化
    up_num = models.BigIntegerField(verbose_name='點贊數',default=0)
    down_num = models.BigIntegerField(verbose_name='點踩數',default=0)
    comment_num = models.BigIntegerField(verbose_name='評論數',default=0)

    # 外鍵字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category',null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article','tag')
                                  )


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 傳布爾值 存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(verbose_name='評論内容',max_length=255)
    comment_time = models.DateTimeField(verbose_name='評論時間',auto_now_add=True)
    # 自關聯
    parent = models.ForeignKey(to='self',null=True)  # 有些評論就是根評論

           

注冊功能

"""
我們之前是直接在views.py中書寫的forms元件代碼
但是為了接耦合 應該将所有的forms元件代碼單獨寫到一個地方

如果你的項目至始至終隻用到一個forms元件那麼你可以直接建一個py檔案書寫即可
	myforms.py
但是如果你的項目需要使用多個forms元件,那麼你可以建立一個檔案夾在檔案夾内根據
forms元件功能的不同建立不同的py檔案
	myforms檔案夾
		regform.py
		loginform.py
		userform.py
		orderform.py
		...
"""
def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        back_dic = {"code": 1000, 'msg': ''}
        # 校驗資料是否合法
        form_obj = MyRegForm(request.POST)
        # 判斷資料是否合法
        if form_obj.is_valid():
            # print(form_obj.cleaned_data)  # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '[email protected]'}
            clean_data = form_obj.cleaned_data  # 将校驗通過的資料字典指派給一個變量
            # 将字典裡面的confirm_password鍵值對删除
            clean_data.pop('confirm_password')  # {'username': 'jason', 'password': '123', 'email': '[email protected]'}
            # 使用者頭像
            file_obj = request.FILES.get('avatar')
            """針對使用者頭像一定要判斷是否傳值 不能直接添加到字典裡面去"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 直接操作資料庫儲存資料
            models.UserInfo.objects.create_user(**clean_data)
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())

  
<script>
    $("#myfile").change(function () {
        // 檔案閱讀器對象
        // 1 先生成一個檔案閱讀器對象
        let myFileReaderObj = new FileReader();
        // 2 擷取使用者上傳的頭像檔案
        let fileObj = $(this)[0].files[0];
        // 3 将檔案對象交給閱讀器對象讀取
        myFileReaderObj.readAsDataURL(fileObj)  // 異步操作  IO操作
        // 4 利用檔案閱讀器将檔案展示到前端頁面  修改src屬性
        // 等待檔案閱讀器加載完畢之後再執行
        myFileReaderObj.onload = function(){
             $('#myimg').attr('src',myFileReaderObj.result)
        }
    })

    $('#id_commit').click(function () {
        // 發送ajax請求     我們發送的資料中即包含普通的鍵值也包含檔案
        let formDataObj = new FormData();
        // 1.添加普通的鍵值對
        {#console.log($('#myform').serializeArray())  // [{},{},{},{},{}]  隻包含普通鍵值對#}
        $.each($('#myform').serializeArray(),function (index,obj) {
            {#console.log(index,obj)#}  // obj = {}
            formDataObj.append(obj.name,obj.value)
        });
        // 2.添加檔案資料
        formDataObj.append('avatar',$('#myfile')[0].files[0]);

        // 3.發送ajax請求
        $.ajax({
            url:"",
            type:'post',
            data:formDataObj,

            // 需要指定兩個關鍵性的參數
            contentType:false,
            processData:false,

            success:function (args) {
                if (args.code==1000){
                    // 跳轉到登陸頁面
                    window.location.href = args.url
                }else{
                    // 如何将對應的錯誤提示展示到對應的input框下面
                    // forms元件渲染的标簽的id值都是 id_字段名
                    $.each(args.msg,function (index,obj) {
                        {#console.log(index,obj)  //  username        ["使用者名不能為空"]#}
                        let targetId = '#id_' + index;
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    })
    // 給所有的input框綁定擷取焦點事件
    $('input').focus(function () {
        // 将input下面的span标簽和input外面的div标簽修改内容及屬性
        $(this).next().text('').parent().removeClass('has-error')
    })
</script>
              
# 擴充
"""
一般情況下我們在存儲使用者檔案的時候為了避免檔案名沖突的情況
會自己給檔案名加一個字首	
	uuid
	随機字元串
	...
"""
           

注意:'login/' 和 '/login/' 不同,前者 直接接在目前路徑下面,後者 接在 ip + 端口 後面

$.each(args.msg,functiong(index,obj){}) index,obj 是位置參數!!!

登陸功能

"""
img标簽的src屬性
	1.圖檔路徑
	2.url
	3.圖檔的二進制資料

我們的計算機上面緻所有能夠輸出各式各樣的字型樣式
内部其實對應的是一個個.ttf結尾的檔案

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
"""


"""
圖檔相關的子產品
    pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont
"""
Image:生成圖檔
ImageDraw:能夠在圖檔上亂塗亂畫
ImageFont:控制字型樣式
"""
from io import BytesIO,StringIO
"""
記憶體管理器子產品
BytesIO:臨時幫你存儲資料 傳回的時候資料是二進制
StringIO:臨時幫你存儲資料 傳回的時候資料是字元串
"""
import random
def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
    # 推導步驟1:直接擷取後端現成的圖檔二進制資料發送給前端
    # with open(r'static/img/111.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推導步驟2:利用pillow子產品動态産生圖檔
    # img_obj = Image.new('RGB',(430,35),'green')
    # img_obj = Image.new('RGB',(430,35),get_random())
    # # 先将圖檔對象儲存起來
    # with open('xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # # 再将圖檔對象讀取出來
    # with open('xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推導步驟3:檔案存儲繁瑣IO操作效率低  借助于記憶體管理器子產品
    # img_obj = Image.new('RGB', (430, 35), get_random())
    # io_obj = BytesIO()  # 生成一個記憶體管理器對象  你可以看成是檔案句柄
    # img_obj.save(io_obj,'png')
    # return HttpResponse(io_obj.getvalue())  # 從記憶體管理器中讀取二進制的圖檔資料傳回給前端


    # 最終步驟4:寫圖檔驗證碼
    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 産生一個畫筆對象
    img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字型樣式 大小

    # 随機驗證碼  五位數的随機驗證碼  數字 小寫字母 大寫字母
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65,90))
        random_lower = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 從上面三個裡面随機選擇一個
        tmp = random.choice([random_lower,random_upper,random_int])
        # 将産生的随機字元串寫入到圖檔上
        """
        為什麼一個個寫而不是生成好了之後再寫
        因為一個個寫能夠控制每個字型的間隙 而生成好之後再寫的話
        間隙就沒法控制了
        """
        img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
        # 拼接随機字元串
        code += tmp
    print(code)
    # 随機驗證碼在登陸的視圖函數裡面需要用到 要比對 是以要找地方存起來并且其他視圖函數也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())
  
  
  
  
  
  <script>
    $("#id_img").click(function () {
        // 1 先擷取标簽之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src',oldVal += '?')
    })
</script>
           
# 書寫針對使用者表的forms元件代碼
from django import forms
from app01 import models


class MyRegForm(forms.Form):
    username = forms.CharField(label='使用者名', min_length=3, max_length=8,
                               error_messages={
                                   'required': '使用者名不能為空',
                                   'min_length': "使用者名最少3位",
                                   'max_length': "使用者名最大8位"
                               },
                               # 還需要讓标簽有bootstrap樣式
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )

    password = forms.CharField(label='密碼', min_length=3, max_length=8,
                               error_messages={
                                   'required': '密碼不能為空',
                                   'min_length': "密碼最少3位",
                                   'max_length': "密碼最大8位"
                               },
                               # 還需要讓标簽有bootstrap樣式
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )

    confirm_password = forms.CharField(label='确認密碼', min_length=3, max_length=8,
                                       error_messages={
                                           'required': '确認密碼不能為空',
                                           'min_length': "确認密碼最少3位",
                                           'max_length': "确認密碼最大8位"
                                       },
                                       # 還需要讓标簽有bootstrap樣式
                                       widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                                       )
    email = forms.EmailField(label='郵箱',
                             error_messages={
                                 'required': '郵箱不能為空',
                                 'invalid': '郵箱格式不正确'
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
                             )

    # 鈎子函數
    # 局部鈎子:校驗使用者名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        # 去資料庫中校驗
        is_exist = models.UserInfo.objects.filter(username=username)
        if is_exist:
            # 提示資訊
            self.add_error('username', '使用者名已存在')
        return username

    # 全局鈎子:校驗兩次是否一緻
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password', '兩次密碼不一緻')
        return self.cleaned_data