Python之路【第十八篇】Django小項目簡單BBS論壇部份內容知識點

開發一個簡單的BBS論壇

項目需求:html

1 總體參考「抽屜新熱榜」 + 「虎嗅網」
2 實現不一樣論壇版塊
3 帖子列表展現
4 帖子評論數、點贊數展現
5 在線用戶展現
6 容許登陸用戶發貼、評論、點贊
7 容許上傳文件
8 帖子可被置頂
9 可進行多級評論

知識必備:(注:沒有必備下面知識的同窗,請返回去看會以後再看下面的內容防止蒙了~~!前端

1 Django
2 HTML\CSS\JS
3 BootStrap
4 Jquery

設計表結構

一、表結構重要性node

在開發任何項目的時候,設計到數據庫,第一個事情要作的是設計表結構。表結構設計很差就不要寫代碼,表結構是體現了你業務邏輯關係的。你的數據都要往數據庫裏存,其實表結構你要理清了你的架構也就出來了!python

二、設計表數據庫

 

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User

# Create your models here.

class Article(models.Model):
    '''
    帖子表
    '''
    #標題最大長度255,不能重名
    title = models.CharField(u'文章標題',max_length=255,unique=True)
    #發佈辦款-使用外鍵關聯Category
    category = models.ForeignKey("Category",verbose_name='板塊名稱')
    '''
    這裏在admin中,title默認是顯示英文的,咱們能夠在他的最前面加要給字段,在admin中就能夠顯示中文,他和verbose_name同樣,何時必須使用
    verbose_name呢?好比上面的{category = models.ForeignKey("Category",verbose_name='板塊名稱')} 這個字段第一個字段是關聯的類,這裏
    就必須使用verbose_name
    '''
    #上傳文件
    head_img = models.ImageField(upload_to="uploads")
    #文章內容(文章內容可能有不少,因此咱們就不用"CharField"來寫了,咱們用TextField,不用規定他多長了,爲可擴展長度)
    content = models.TextField(u"內容")
    #文章做者
    author = models.ForeignKey("UserProfile",verbose_name="做者")
    #發佈日期
    publish_date = models.DateTimeField(auto_now=True,verbose_name="發佈日期")
    #是否隱藏
    hidden = models.BooleanField(default=False,verbose_name="是否隱藏")
    #帖子的優先級
    priority = models.IntegerField(default=1000,verbose_name="優先級")

    def __unicode__(self):
        return "<%s,author:%s>" % (self.title,self.author)

class Comment(models.Model):
    '''
    評論表
    '''
    #評論是基於文章的,而且一條評論只屬於一個文章!對多的關係
    #一個文章能夠有多個評論,一個評論只屬於一個文章
    #評論文章
    article = models.ForeignKey("Article")
    #評論用戶
    user = models.ForeignKey("UserProfile")
    #評論內容
    comment = models.TextField(max_length=1000)
    #評論時間
    date = models.DateTimeField(auto_now=True)

    #多級評論,是否是評論評論的當前的表(本身表),因此就得和本身作一個關聯!
    #這裏在關聯本身的時候必須設置一個related_name不然會報錯衝突
    #這裏parent_comment,必須設置爲能夠爲空,由於若是他是第一評論他是沒有父ID的
    parent_comment = models.ForeignKey("self",related_name='p_comment',blank=True,null=True)
    '''
    prent self
    Null    1
    1       2
    1       3
    2       4
    經過上面的這種方法來記錄,評論的級別關係!
    '''
    def __unicode__(self):
        return "<user:%s>" %(self.user)
class ThumbUp(models.Model):
    '''
    點贊
    '''
    #給那個文章點的
    article = models.ForeignKey('Article')
    #用戶名
    user = models.ForeignKey('UserProfile')
    #時間
    date = models.DateTimeField(auto_now=True)


class Category(models.Model):
    '''
    板塊表
    '''
    #板塊名稱
    name = models.CharField(max_length=64,unique=True,verbose_name="板塊名稱")
    #板塊管理員
    admin = models.ManyToManyField("UserProfile",verbose_name="模塊管理員")
    def __unicode__(self):
        return self.name

class UserProfile(models.Model):
    '''
    用戶表
    '''
    #使用Django提供的用戶表,直接繼承就能夠了.在原生的User表裏擴展!(原生的User表裏就有用戶名和密碼)
    #必定要使用OneToOne,若是是正常的ForeignKey的話就表示User中的記錄能夠對應UserProfile中的多條記錄!
    #而且OneToOne的實現不是在SQL級別實現的而是在代碼基本實現的!
    user = models.OneToOneField(User)
    #名字
    name = models.CharField(max_length=32)
    #屬組
    groups = models.ManyToManyField("UserGroup")

    def __unicode__(self):
        return self.name

class UserGroup(models.Model):
    '''
    用戶組表
    '''
    name = models.CharField(max_length=64,unique=True)
    def __unicode__(self):
        return self.name

 

配置Django Admin

配置admin註冊model,不要忘記建立Django 管理員用戶django

from django.contrib import admin
import models
# Register your models here.

admin.site.register(models.Article)
admin.site.register(models.Category)
admin.site.register(models.Comment)
admin.site.register(models.ThumbUp)
admin.site.register(models.UserProfile)
admin.site.register(models.UserGroup)

我建立了幾個板塊,我在板塊中查看的時候。只能看到下面簡單的信息:後端

這裏我想看到板塊中的ID或其餘信息怎麼辦?架構

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from django.contrib import admin
import models
# Register your models here.

#給某個表專門的定製的類
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id','name')

class ArticleAdmin(admin.ModelAdmin):
    list_display = ('id','title','author','hidden','publish_date')

admin.site.register(models.Article,ArticleAdmin) #把自定義的類綁定到註冊的類中
admin.site.register(models.Category,CategoryAdmin)  #把自定義的類綁定到註冊的類中
admin.site.register(models.Comment)
admin.site.register(models.ThumbUp)
admin.site.register(models.UserProfile)
admin.site.register(models.UserGroup)

效果以下:框架

前端頁面&URLorViews配置

一、url別名使用測試

url裏配置別名

url(r'^category/(\d+)/$',views.category,name='category'),

html裏配置的時候就只認那個別名了

          <li role="presentation"><a href="{% url 'category' 1 %}">歐美專區</a></li>
          <li role="presentation"><a href="{% url 'category' 2 %}">日韓專區</a></li>
          <li role="presentation"><a href="{% url 'category' 3 %}">印度專區</a></li>

別名的好處:若是說那天想修改url裏的這個url名稱了,是否是全部前端都得修改!而且在有好幾層的時候怎麼改使用別名就會很是方便了!

二、前端頁面寫完以後發現圖片沒法正常顯示

出現這個問題的緣由:他能找到uploads這個目錄嗎?他能直接訪問這個目錄嗎?他不能直接訪問不了!

  • 一個是在Linux環境下作一個軟鏈接鏈接過去

若是在settings里加入uploads這個目錄,可是這個方法仍是有問題!他會去找/static/uploads/uploads目錄,看下面的圖!

可是經過下面的方式就能夠訪問(緣由就是由於:他去/static/uploads/uploads目錄找了)

2.二、咱們本身寫上傳的方法

 定義form表單認證

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo  LuoTianShuai

from django import forms

class ArticleForm(forms.Form):
    title = forms.CharField(max_length=255,min_length=5)
    summary = forms.CharField(max_length=255,min_length=5)
    head_img = forms.ImageField()
    content = forms.CharField(min_length=10)
    category_id = forms.IntegerField()

定義上傳方法

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo  LuoTianShuai
import os

def handle_upload_file(f,request):  #f這裏獲取到文件句柄
    base_img_upload_path = 'static/Uploads'
    user_path = "%s/%s" % (base_img_upload_path,request.user.userprofile.id)
    if not os.path.exists(user_path):
        os.mkdir(user_path)
    with open('%s/%s'% (user_path,f.name),'wb+') as destinations:
        for chunk in f.chunks():
            destinations.write(chunk)
        #爲了防止用戶傳送圖片進行衝突,咱們爲每一個用戶進行建立用戶


    return "/static/Uploads/%s/%s" % (request.user.userprofile.id,f.name)

定義views

def new_article(request):
    category_list = models.Category.objects.all()
    if request.method == 'POST':
        form = ArticleForm(request.POST,request.FILES)
        if form.is_valid():
            form_data = form.cleaned_data
            form_data['author_id'] = request.user.userprofile.id

            #自定義圖片上傳
            new_img_path = handle_upload_file(request.FILES['head_img'],request)
            #可是在views也保存了一份,咱們給他改掉改爲咱們本身的就好了
            form_data['head_img'] = new_img_path



            #create只能返回成功失敗,我想在建立完成以後返回文章的ID,直接下面那麼寫就能夠
            print form_data
            new_article_obj = models.Article(**form_data)
            new_article_obj.save()#這個對象就直接返回了
            return render(request,'new_article.html',{'new_article_obj':new_article_obj}) #若是沒有這個變量說明是建立新文章呢
        else:
            print form.errors

    return render(request,'new_article.html',{'category_list':category_list})

 

 

 

多級評論實現

用戶能夠直接對貼子進行評論,其它用戶也能夠對別的用戶的評論再進行評論,也就是所謂的壘樓,以下圖:

全部的評論都存在一張表中, 評論與評論以前又有從屬關係,如何在前端 頁面上把這種層級關係體現出來?

首先我們在存儲數據的時候是怎麼來實現記錄層級關係的呢?(下面的圖是通過簡化的把其餘列隱藏了)

咱們在上面建立數據庫表結構的時候,就定義了一個外鍵爲他們本身(parent_comment_id),若是他沒有父級別的ID說明他們是第一層,若是有說明他包含在一個評論以內!(仔細看上面的表結構)

先把評論簡化成一個這樣的模型:

data = [
    (None,'A'),
    ('A','A1'),
    ('A','A1-1'),
    ('A1','A2'),
    ('A1-1','A2-3'),
    ('A2-3','A3-4'),
    ('A1','A2-2'),
    ('A2','A3'),
    ('A2-2','A3-3'),
    ('A3','A4'),
    (None,'B'),
    ('B','B1'),
    ('B1','B2'),
    ('B1','B2-2'),
    ('B2','B3'),
    (None,'C'),
    ('C','C1'),

]

轉換爲字典以後:

data_dic = {
    'A': {
        'A1': {
            'A2':{
                'A3':{
                    'A4':{}
                }
            },
            'A2-2':{
                'A3-3':{}
            }
        }
    },
    'B':{
        'B1':{
            'B2':{
                'B3':{}
            },
            'B2-2':{}
        }
    },
    'C':{
        'C1':{}
    }

}

看上面的字典,咱們能經過for循來獲取他有多少層嗎?固然不行,咱們不知道他有多少層就沒有辦法進行找,或者經過while循環,最好是用遞歸進行一層一層的查找

咱們在前端展現的時候須要知道,那條數據是那一層的,不多是壘下去的!由於他們是有層級關係的!

咱們用後端來實現:我們給前端返回一個字典這樣是不行的,我們在後端把層級關係創建起來~返回的時候直接返回一個完整的HTML

轉換爲字典以後就有層級關係了咱們能夠經過遞歸來實現了!上面再沒有轉換爲字典的時候層級關係就不是很明確了!

 

在循環的過程當中不斷的建立字典,先創建最頂級的,而後在一層一層的創建

先經過一個簡單的例子看下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo  LuoTianShuai



data = [
    (None,'A'),
    ('A','A1'),
    ('A','A1-1'),
    ('A1','A2'),
    ('A1-1','A2-3'),
    ('A2-3','A3-4'),
    ('A1','A2-2'),
    ('A2','A3'),
    ('A2-2','A3-3'),
    ('A3','A4'),
    (None,'B'),
    ('B','B1'),
    ('B1','B2'),
    ('B1','B2-2'),
    ('B2','B3'),
    (None,'C'),
    ('C','C1'),

]

def tree_search(d_dic,parent,son):
    #一層一層找,先撥第一層,一層一層往下找
    for k,v in d_dic.items():
        #舉例來講我先遇到A,我就把A來個深度查詢,A沒有了在找B
        if k == parent:#若是等於就找到了parent,就吧son加入到他下面
            d_dic[k][son] = {} #son下面可能還有兒子
            #這裏找到就直接return了,你找到就直接退出就好了
            return
        else:
            #若是沒有找到,有可能還有更深的地方,的須要剝掉一層
            tree_search(d_dic[k],parent,son)



data_dic = {}

for item in data:
    # 每個item表明兩個值一個父親一個兒子
    parent,son = item
    #先判斷parent是否爲空,若是爲空他就是頂級的,直接吧他加到data_dic
    if parent is None:
        data_dic[son] = {}  #這裏若是爲空,那麼key就是他本身,他兒子就是一個空字典
    else:
        '''
        若是不爲空他是誰的兒子呢?舉例來講A3他是A2的兒子,可是你能直接判斷A3的父親是A2你能直接判斷他是否在A裏面嗎?你只能到第一層.key
        因此我們就得一層一層的找,咱們知道A3他爹確定在字典裏了,因此就得一層一層的找,可是不能循環找,由於你不知道他有多少層,因此經過遞歸去找
        直到找到位置
        '''
        tree_search(data_dic,parent,son) #由於你要一層一層找,你的把data_dic傳進去,還的把parent和son傳進去


for k,v in data_dic.items():
    print(k,v)

執行結果:(完美)

('A', {'A1': {'A2': {'A3': {'A4': {}}}, 'A2-2': {'A3-3': {}}}, 'A1-1': {'A2-3': {'A3-4': {}}}})
('C', {'C1': {}})
('B', {'B1': {'B2-2': {}, 'B2': {'B3': {}}}})

 二、前端返回

當我們把這個字典往前端返回的時候,前端模板裏是沒有一個語法遞歸的功能的,雖然我們的層級關係已經出來了!因此我們須要自定義一個模板語言而後拼成一個html而後返回給前端展現!

2.一、配置前端吧數據傳給simple_tag

{% load custom_tags %}

{% build_comment_tree article_obj.comment_set.select_related %}

2.二、simple_tag獲取數據而後把用戶穿過來的數據進行轉換爲字典

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

def tree_search(d_dic,comment_obj):#這裏不用傳附近和兒子了由於他是一個對象,能夠直接找到父親和兒子
    for k,v_dic in d_dic.items():
        if k == comment_obj.parent_comment:#若是找到了
            d_dic[k][comment_obj] = {} #若是找到父親了,你的把本身存放在父親下面,並把本身當作key,value爲一個空字典
            return
        else:#若是找不到遞歸查找
            tree_search(d_dic[k],comment_obj)




@register.simple_tag
def build_comment_tree(comment_list):
    '''
    把評論傳過來只是一個列表格式(以下),要把列別轉換爲字典,在把字典拼接爲html
    [<Comment: <A,user:羅天帥>>, <Comment: <A2-1,user:羅天帥>>, <Comment: <A3-1,user:羅天帥>>, <Comment: <A2-2,user:羅天帥>>,
    <Comment: <A4-1,user:羅天帥>>, <Comment: <A4-2,user:羅天帥>>, <Comment: <A5-1,user:羅天帥>>, <Comment: <A3-2,user:羅天帥>>,
     <Comment: <B2,user:羅天帥>>, <Comment: <B2-1,user:羅天帥>>]
    :param comment_list:
    :return:
    '''
    comment_dic = {}
    #print(comment_list)
    for comment_obj in comment_list: #每個元素都是一個對象
        if comment_obj.parent_comment is None: #若是沒有父親
            comment_dic[comment_obj] = {}
        else:
            #經過遞歸找
            tree_search(comment_dic,comment_obj)

    # #測試:
    # for k,v in comment_dic.items():
    #     print(k,v)

    # 上面完成以後開始遞歸拼接字符串

二、3生成html標籤

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

def tree_search(d_dic,comment_obj):#這裏不用傳附近和兒子了由於他是一個對象,能夠直接找到父親和兒子
    for k,v_dic in d_dic.items():
        if k == comment_obj.parent_comment:#若是找到了
            d_dic[k][comment_obj] = {} #若是找到父親了,你的把本身存放在父親下面,並把本身當作key,value爲一個空字典
            return
        else:#若是找不到遞歸查找
            tree_search(d_dic[k],comment_obj)



def generate_comment_html(sub_comment_dic):
    #先建立一個html默認爲空
    html = ""
    for k,v_dic in sub_comment_dic.items():#循環穿過來的字典
        html += "<div  class='comment-node'>"  + k.comment + "</div>"
        #上面的只是把第一層加了他可能還有兒子,因此經過遞歸繼續加
        if v_dic:
            html += generate_comment_html(v_dic)
    return html



@register.simple_tag
def build_comment_tree(comment_list):
    '''
    把評論傳過來只是一個列表格式(以下),要把列別轉換爲字典,在把字典拼接爲html
    [<Comment: <A,user:羅天帥>>, <Comment: <A2-1,user:羅天帥>>, <Comment: <A3-1,user:羅天帥>>, <Comment: <A2-2,user:羅天帥>>,
    <Comment: <A4-1,user:羅天帥>>, <Comment: <A4-2,user:羅天帥>>, <Comment: <A5-1,user:羅天帥>>, <Comment: <A3-2,user:羅天帥>>,
     <Comment: <B2,user:羅天帥>>, <Comment: <B2-1,user:羅天帥>>]
    :param comment_list:
    :return:
    '''
    comment_dic = {}
    #print(comment_list)
    for comment_obj in comment_list: #每個元素都是一個對象
        if comment_obj.parent_comment is None: #若是沒有父親
            comment_dic[comment_obj] = {}
        else:
            #經過遞歸找
            tree_search(comment_dic,comment_obj)

    # #測試:
    # for k,v in comment_dic.items():
    #     print(k,v)

    # 上面完成以後開始遞歸拼接字符串

    #div框架
    html = "<div class='comment-box'>"
    margin_left = 0
    for k,v in comment_dic.items():
        #第一層的html
        html += "<div class='comment-node'>" + k.comment + "</div>"
        #經過遞歸把他兒子加上
        html += generate_comment_html(v)
    html += "</div>"
    return mark_safe(html)

效果以下:

2.四、上面的看起來不是很好看怎麼辦?給他增長一個margin-left讓他來顯示層級效果,每次進行遞歸的時候給他加一個值!

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

def tree_search(d_dic,comment_obj):#這裏不用傳附近和兒子了由於他是一個對象,能夠直接找到父親和兒子
    for k,v_dic in d_dic.items():
        if k == comment_obj.parent_comment:#若是找到了
            d_dic[k][comment_obj] = {} #若是找到父親了,你的把本身存放在父親下面,並把本身當作key,value爲一個空字典
            return
        else:#若是找不到遞歸查找
            tree_search(d_dic[k],comment_obj)



def generate_comment_html(sub_comment_dic,margin_left_val):
    #先建立一個html默認爲空
    html = ""
    for k,v_dic in sub_comment_dic.items():#循環穿過來的字典
        html += "<div style='margin-left:%spx'  class='comment-node'>" % margin_left_val + k.comment + "</div>"
        #上面的只是把第一層加了他可能還有兒子,因此經過遞歸繼續加
        if v_dic:
            html += generate_comment_html(v_dic,margin_left_val+15)
    return html



@register.simple_tag
def build_comment_tree(comment_list):
    '''
    把評論傳過來只是一個列表格式(以下),要把列別轉換爲字典,在把字典拼接爲html
    [<Comment: <A,user:羅天帥>>, <Comment: <A2-1,user:羅天帥>>, <Comment: <A3-1,user:羅天帥>>, <Comment: <A2-2,user:羅天帥>>,
    <Comment: <A4-1,user:羅天帥>>, <Comment: <A4-2,user:羅天帥>>, <Comment: <A5-1,user:羅天帥>>, <Comment: <A3-2,user:羅天帥>>,
     <Comment: <B2,user:羅天帥>>, <Comment: <B2-1,user:羅天帥>>]
    :param comment_list:
    :return:
    '''
    comment_dic = {}
    #print(comment_list)
    for comment_obj in comment_list: #每個元素都是一個對象
        if comment_obj.parent_comment is None: #若是沒有父親
            comment_dic[comment_obj] = {}
        else:
            #經過遞歸找
            tree_search(comment_dic,comment_obj)

    # #測試:
    # for k,v in comment_dic.items():
    #     print(k,v)

    # 上面完成以後開始遞歸拼接字符串

    #div框架
    html = "<div class='comment-box'>"
    margin_left = 0
    for k,v in comment_dic.items():
        #第一層的html
        html += "<div class='comment-node'>" + k.comment + "</div>"
        #經過遞歸把他兒子加上
        html += generate_comment_html(v,margin_left+15)
    html += "</div>"
    return mark_safe(html)

效果以下:

相關文章
相關標籤/搜索