項目需求: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
配置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)
效果以下:框架
一、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這個目錄嗎?他能直接訪問這個目錄嗎?他不能直接訪問不了!
若是在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)
效果以下: