開發一個簡單的BBS論壇
項目需求:
1 整體參考“抽屜新熱榜” + “虎嗅網” 2 實現不同論壇版塊 3 帖子列表展示 4 帖子評論數、點贊數展示 5 在線用戶展示 6 允許登錄用戶發貼、評論、點贊 7 允許上傳文件 8 帖子可被置頂 9 可進行多級評論
知識必備:(注:沒有必備下面知識的同學,請返回去看會之后再看下面的內容防止蒙了~~!)
1 Django 2 HTML\CSS\JS 3 BootStrap 4 Jquery
設計表結構
1、表結構重要性
在開發任何項目的時候,設計到數據庫,第一個事情要做的是設計表結構。表結構設計不好就不要寫代碼,表結構是體現了你業務邏輯關系的。你的數據都要往數據庫里存,其實表結構你要理清了你的架構也就出來了!
2、設計表
#!/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 管理員用戶
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配置
1、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名稱了,是不是所有前端都得修改!並且在有好幾層的時候怎么改使用別名就會非常方便了!
2、前端頁面寫完之后發現圖片無法正常顯示
出現這個問題的原因:他能找到uploads這個目錄嗎?他能直接訪問這個目錄嗎?他不能直接訪問不了!
- 一個是在Linux環境下做一個軟連接連接過去
如果在settings里加入uploads這個目錄,但是這個方法還是有問題!他會去找/static/uploads/uploads目錄,看下面的圖!
但是通過下面的方式就可以訪問(原因就是因為:他去/static/uploads/uploads目錄找了)
2.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': {}}}})
2、前端返回
當咱們把這個字典往前端返回的時候,前端模板里是沒有一個語法遞歸的功能的,雖然咱們的層級關系已經出來了!所以咱們需要自定義一個模板語言然后拼成一個html然后返回給前端展示!
2.1、配置前端吧數據傳給simple_tag
{% load custom_tags %}
{% build_comment_tree article_obj.comment_set.select_related %}
2.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) # 上面完成之后開始遞歸拼接字符串
2、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.4、上面的看起來不是很好看怎么辦?給他增加一個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)
效果如下: