1. 程式人生 > >BBS+Blog項目流程及補充知識點

BBS+Blog項目流程及補充知識點

randint 產品 blog 打印 認證 csrf 背景顏色 format man

項目流程:

  1. 產品需求

    (1)基於用戶認證組件和Ajax實現登陸驗證(圖片驗證碼)

    (2)基於forms組件和Ajax實現註冊功能

    (3)設計系統首頁(文章列表渲染)

    (4)設計個人站點頁面

    (5)文章詳情頁

    (6)實現文章點贊功能

    (7)實現文章的評論

        --- 文章的評論

        --- 評論的評論

    (8)富文本編輯框的防止xss攻擊

  2. 設計表結構

  3. 按照每個功能進行開發

  4. 功能測試

  5. 項目部署上線

所需知識點:

1. JQuery 獲取input標簽中的文件對象:$("#avatar")[0].files[0]

技術分享圖片

2.

$("#form").serializeArray();  // 返回一個數組,數組中是一個個對象,對象的格式為: {name:"form表單控件的name屬性值",value:"form表單控件的value值"}

瀏覽器效果圖:

技術分享圖片

3. Django的Media配置:

FileField字段和ImageField字段:FileField可用於上傳任何文件,ImageField只能上傳圖片

# 表:    
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 這個字段不傳的時候(avatar字段為空時,也是上傳了 avatar字段),才會使用 default 的默認值 # FileField字段一定要接受一個文件對象;ImageFile字段一定要接受一個圖片對象 # 給上面的表生成記錄: avatar_obj = request.FILES.get("
avatar") user_obj = UserInfo.objects.create_user(username=user,password=psw,email=email,avatar=avatar_obj) # 生成記錄時,Django會自動下載 avatar_obj 這個文件對象,由於 upload_to = "avatars/",Django會把avatar_obj下載到根目錄的 "avatars"文件夾下(沒有"avatars"文件夾 django會自動創建一個),user_obj的avatar字段(UserInfo表中)存的是 文件的相對路徑 Media配置之 MEDIA_ROOT: Django對靜態文件的區分:Django有兩種靜態文件 1. /static/ :js,css,img;服務器自己用到的文件; 2. /media/ :用戶上傳的文件 # 用戶註冊時的代碼: avatar_obj = request.FILES.get("avatar") user_obj = UserInfo.objects.create_user(username=user,password=psw,email=email,avatar=avatar_obj) 上面兩行代碼,一旦在settings.py中配置了 media 文件("media"是project中的一個文件夾名,可放在project下,也可放在 app 下): MEDIA_ROOT = os.path.join(BASE_DIR,"media") Django會自動實現如下功能: 會將文件對象下載到MEDIA_ROOT中的 avatars(因為UserInfo表中是:upload_to="avatars/")文件夾中(如果MEDIA_ROOT中沒有 avatars 這個文件夾,Django會自動創建 );user_obj的avatar存的是文件的相對路徑 Media配置之 MEDIA_URL: 客戶端瀏覽器如何能直接訪問到 media 中的數據? 1. settings.py: MEDIA_URL = "/media/" # MEDIA_URL = "/media/" 也是為上面的 MEDIA_ROOT 那個絕對路徑起了一個別名;效果和 STATIC_URL = ‘/static/‘ 一樣 2. urls.py中的 urlpatterns : from django.views.static import serve re_path(r"media/(?P<path>.*)$",serve,{"document_root":settings.MEDIA_ROOT})

4. Django的 admin 組件:

admin組件:(不是必需的)
    # Django內部提供的一個組件,作用:後臺數據管理組件(通過web頁面)
    # 註:1. 只有超級用戶(superuser)才能登陸 admin 路徑; 2. python manage.py createsuperuser ---> 針對的是用戶認證組件auth對應的那個用戶表(auth_user或者Blog項目中的 blog_userinfo 表)
    
    # 用admin組件對象後臺數據進行操作之前,需要先進行 admin 註冊:
    # admin註冊語法: 每一個 app 下面有一個 admin.py 的文件,在這個文件中做 admin 註冊,如這個 Blog 項目:
    # (1) 先把表拿過來:
    from blog import models
    # (2) 通過 admin.site.register() 註冊 models.py 中的表
    admin.site.register(models.UserInfo)
    admin.site.register(models.Blog)
    admin.site.register(models.Category)
    admin.site.register(models.Tag)
    admin.site.register(models.Article)
    admin.site.register(models.Article2Tag)
    admin.site.register(models.ArticleUpDown)
    admin.site.register(models.Comment)
    # 在 admin 頁面操作 Comment 表,url為:http://127.0.0.1:8000/admin/blog/comment/ # /blog/為app的名字, /comment/為表的名字

5. 日期歸檔查詢

日期歸檔查詢(數據庫)11. date_format(時間相關字段,時間格式)
    # date存“年月日”,time存“時分秒”,datetime存“年月日時分秒”
    
    ===============date,time,datetime==============
    create table t_mul(d date,t time,dt datetime);
    insert into t_mul values(now(),now(),now());
    select * from t_mul;
    
    +------------+----------+---------------------+
    |    d        | t        | dt                 |
    +------------+----------+---------------------+
    | 2017-08-01 | 19:42:22 | 2017-08-01 19:42:22 |
    +------------+----------+---------------------+
    
    select date_format(dt,"%Y-%m") from t_mul;
    # 查詢結果為: 2017-08
    
    2. extra():用於插入sql語句;該函數的調用者是 QuerySet
        extra(select=None,where=None,params=None,tables=None,order_by=None,select_params=None)
        有點情況下,對於一些復雜的sql語句,Django並沒有相應的ORM去對應這些sql語句,這種情況下Django提供了 extra() 函數:QuerySet修改機制---它能在QuerySet生成的SQL從句中註入新子句
        extra() 可以指定一個或多個參數,例如 select,where 或者 tables;這些參數都不是必需的,但至少要使用一個
        
        select參數:select參數可以讓你在 SELECT 從句中添加其它字段信息;它是一個字典,存放著 屬性名到 SQL 從句的映射
        
        如:
        in MySQL:
        article_obj = models.Article.objects.extra(select={"is_recent":"create_time > ‘2017-09-05‘"}.values("title","is_recent")     # 通過 extra() 就可以寫 create_time > ‘2017-09-05‘ 這種SQL的寫法 # 返回結果還可以繼續 annotate()(.values("xxx").annotate())進行分組
        # 返回的結果集(也是一個QuerySet)中每個 Entry 對象都有一個額外的屬性 is_recent,它是一個布爾值(True為1,False為0),表示 Article對象的 create_time 是否晚於 2017-09-05
        

日期歸檔查詢(數據庫)2# 利用Django提供的 TruncMonth() 方法: 截取到月
    from django.db.models.functions import TruncMonth
    Sales.objects
         .annotate(month=TruncMonth(timestamp))  # .annotate()的作用不是分組,而是截取 ‘timestamp‘ 到月,並為每個對象添加一個 截取到月 的字段;# Trunccate to month and add to select list
         .values(month)  # Group by month
         .annotate(c=Count(id))  # Select the count of the grouping
         .values(month,c)  
        

6. inclusion_tag(具體操作見項目)

from django import template
from django.db.models import Count
from blog.models import *

register = template.Library()

@register.inclusion_tag("classification.html")  # inclusion_tag()中的參數表示所要引入的一套模板文件
def get_classification_data(username):  # get_classification_data() 這個方法一旦被調用,它會先執行下面的數據查詢,查詢完之後會把下面的字典返回 "classification.html" 這個模板文件(沒有返回給調用者),因為 "classification.html" 文件會需要下面的這幾個變量;下面的變量傳給 "classification.html"之後會進行 render 渲染,渲染成一堆完整的 html標簽
    user = UserInfo.objects.filter(username=username).first()

    #  查詢當前站點對象
    blog = user.blog

    cate_list = Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values("title", "c")

    tag_list = Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list("title", "c")

    date_list = Article.objects.filter(user=user).extra(
        select={"y_m_date": "date_format(create_time,‘%%Y-%%m‘)"}).values("y_m_date").annotate(c=Count("nid")).values(
        "y_m_date", "c")  # annotate()之前要先 .values()
    return {"username":username,"blog": blog, "cate_list": cate_list, "tag_list": tag_list, "date_list": date_list}

7. KindEditor上傳文件:

uploadJson
    指定上傳文件的服務器端程序。

    數據類型: String
    默認值: basePath + ‘php/upload_json.php’
    uploadJson對應的value是一個路徑
    如: {
             uploadJson:"/upload/", // uploadJson對應的是一個路徑
                extraFileUploadParams:{
                csrfmiddlewaretoken:$("[name=csrfmiddlewaretoken]").val(),  // post請求,需要自己組裝數據,所以要加上這個 key-value
                filePostName:"upload_img"  // 所上傳文件對應的 key
        }
        
        extraFileUploadParams
            上傳圖片、Flash、視音頻、文件時,支持添加別的參數一並傳到服務器。

            數據類型: Array
            默認值: {}
            
        filePostName
            指定上傳文件form名稱。

            數據類型: String
            默認值: imgFile

8. BeautifulSoup:

from bs4 import BeautifulSoup
    # BeautifulSoup的用法1:獲取標簽字符串文內容
    s = "<h1>hello</h1><span>123</span>"
    
    soup = BeautifulSoup(s,"html.parser")  # 第一個參數放標簽字符串,第二參數放 解析器
    print(soup.text)  # soup.text 表示 獲取s 這個標簽字符串的文本
    # 打印結果:
    # hello123
    
    # BeautifulSoup的用法2:防止xss攻擊:過濾出去 <script> 標簽
    s = "<h1>hello</h1><span>123</span><script>alert(123)</script>"
    soup = BeautifulSoup(s,"html.parser")
    print(soup.find_all())  # soup.find_all():獲取標簽字符串中所有的標簽對象;列表的形式
    # 打印結果: [<h1>hello</h1>,<span>123</span>,<script>alert(123)</script>]
    
    for tag in soup.find_all():
        if tag.name == "script":  # tag.name 表示 標簽名(字符串格式)
            tag.decompose()  # 從 soup 中把 該標簽 刪除
    
    print(str(soup))
    # 打印結果:<h1>hello</h1><span>123</span>

9. 圖片驗證碼

from random import randint
import random
def get_random_color():  # 用於隨機生成顏色
    return (randint(0, 255), randint(0, 255), randint(0, 255))

def get_valid_code_img(request):
    
    # 方式四:給生成的圖片(畫板)中添加文字
    from io import BytesIO
    # BytesIO是內存管理工具
    from PIL import Image, ImageDraw, ImageFont
    img = Image.new("RGB", (260, 33), color=get_random_color())  # new()裏面有三個參數:第一個表示模式(RGB表示彩色),第二個表示圖片寬高(需要和css中設置的寬高一致),第三個表示背景顏色 # 得到一個Image對象img
    # 往畫板中添加文字
    draw = ImageDraw.Draw(img)  # 得到一個draw對象 # 可這麽理解:用ImageDraw這個畫筆往 img 畫板上書寫
    kumo_font = ImageFont.truetype("static/font/KumoFont.ttf",
                                   size=20)  # 定義字體;第一個參數是字體樣式的路徑,第二個是字體大小 # 路徑中 static 前不能加 /

    valid_code_str = ""  # 用於保存驗證碼
    for i in range(4):
        random_num = str(randint(0, 9))  # 數字
        random_lower = chr(randint(97, 122))  # 小寫
        random_upper = chr(randint(65, 90))  # 大寫
        random_char = random.choice([random_num, random_lower, random_upper])
        draw.text((i * 60 + 30, 5), random_char, get_random_color(),
                  font=kumo_font)  # draw.text():利用draw對象往畫板裏面書寫文字;第一個參數是一個元組(x,y),表示橫坐標、縱坐標的距離;第二個參數表示文字內容;第三個參數表示字體顏色;第四個表示字體樣式
        valid_code_str += random_char

    # 驗證圖片的噪點、噪線
    width = 260
    height = 33  # width 和 height要和前端驗證圖片的寬高一致
    # 噪線
    for i in range(5):
        x1 = randint(0, width)
        y1 = randint(0, height)  # (x1,y1)是線的起點
        x2 = randint(0, width)
        y2 = randint(0, height)  # (x2,y2)是線的終點
        draw.line((x1, y1, x2, y2), fill=get_random_color())
    # 噪點
    for i in range(100):
        draw.point([randint(0, width), randint(0, height)], fill=get_random_color())  # 在給定的坐標點上畫一些點。
        x = randint(0, width)
        y = randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())  # 在給定的區域內,在開始和結束角度之間繪制一條弧(圓的一部分)
    # 參考鏈接: https://blog.csdn.net/icamera0/article/details/50747084

    # 重點:儲存隨機生成的驗證碼(不能用 global 的方式去處理驗證碼 valid_code_str,因為此時當有其他用戶登陸時驗證碼會被別人刷新掉;正確的方式是把該驗證碼存到 session 中 )
    request.session["valid_code_str"] = valid_code_str  # 註意:這句代碼執行了三個操作過程

    # 內存處理
    f = BytesIO()  # f就是一個內存句柄
    img.save(f, "png")  # 把img保存到內存句柄中;# save()之後就能把img保存到內存中
    data = f.getvalue()  # 把保存到內存中的數據讀取出來
    # BytesIO會有一個自動清除內存的操作

    return data

BBS+Blog項目流程及補充知識點