Django開發(fā)個人博客項目-(11)博客分類與最后功能完善

歡迎訪問我的博客:https://wouldmissyou.com

博客分類

分類頁面也是老套路,category.html是分類樹,category_detail.html是分類下的文章宴凉,我這里直接上代碼:

views.py
class CategoryView(View):
    # 分類樹視圖
    def get(self, request):
        nodes = Category.objects.all()
        return render(request, 'category.html', {
            'nodes': nodes,
                })

分類視圖views.py
class CategoryDetaiView(View):
    # 分類文章列表
    def get(self, request, category_name):
        category = Category.objects.filter(name=category_name).first()
        cate_blogs = category.blog_set.all()    
        # 分頁
        try:
            page = request.GET.get('page', 1)
        except PageNotAnInteger:
            page = 1
        p = Paginator(cate_blogs, 1, request=request)
        cate_blogs = p.page(page)
        return render(request, 'category_detail.html', {
            'cate_blogs': cate_blogs,
            'category_name': category_name,
          
        })

url.py
path('categories',CategoryView.as_view(), name = 'categories' ),
re_path(r'^category/(?P<category_name>\w+)/$', CategoryDetaiView.as_view(), name='category_name'),
category.html

{% recursetree nodes %}
    <li class="category-list-item">
        {% if node.is_leaf_node %}
            <a href="{% url 'category_name' node.name %}">{{ node.name }}</a>
        {% else %}
            <a href="{% url 'category_name' node.name %}">{{ node.name }}</a>
            <ul class="category-list-child">
                    <a href="#">{{ children }}</a>
            </ul>
        {% endif %}
    </li>
{% endrecursetree %}


category_detail.html
這個頁面和tag_detail.html是一樣的甜害,這里就不寫出來了。

到此揉忘,博客分類就完成了

搜索功能

該博客最開始采用的模板是并不包括搜索功能的,在主頁只有主頁、歸檔和分類三個部分骇扇。最后博主自己添加了搜索框,不過其實不太想讓大家使用這個功能面粮,因此將搜索框隱藏了少孝,只有再點擊搜索時,才會顯現(xiàn)出來熬苍。但是這個添加匹配的不太好稍走,導致手機端會有對不齊的現(xiàn)象,以后前端學好了再來修復這個bug。
博客的搜索功能簡單來實現(xiàn)的話婿脸,通過查詢功能查找到符合關鍵字的對象粱胜。但是,對于一個搜索引擎來說盖淡,至少應該能夠根據(jù)用戶的搜索關鍵詞對搜索結果進行排序以及高亮關鍵字∧昴現(xiàn)在我們就來使用 django-haystack 實現(xiàn)這些特性。
django-haystack 是一個專門提供搜索功能的 django 第三方應用褪迟,它支持 Solr冗恨、Elasticsearch、Whoosh味赃、Xapian 等多種搜索引擎掀抹,配合著名的中文自然語言處理庫 jieba 分詞,就可以為我們的博客提供一個效s果不錯的博客文章搜索系統(tǒng)心俗。

安裝相關軟件

進入我們的虛擬環(huán)境傲武,安裝以下依賴:

pip install whoosh django-haystack jieba
  • Whoosh。Whoosh 是一個由純 Python 實現(xiàn)的全文搜索引擎城榛,沒有二進制文件等揪利,比較小巧,配置簡單方便狠持。
  • jieba 中文分詞疟位。由于 Whoosh 自帶的是英文分詞,對中文的分詞支持不是太好喘垂,所以使用 jieba 替換Whoosh 的分詞組件甜刻。

配置 Haystack

安裝完成后,需要在setting.py中進行相關配置:

INSTALLED_APPS = [
    'django.contrib.admin',
    # 其它 app...
    'haystack',
]

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'myblog.whoosh_cn_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
    },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

HAYSTACK_CONNECTIONS 的 ENGINE 指定了 django haystack 使用的搜索引擎正勒,這里我們使用了 blog.whoosh_cn_backend.WhooshEngine得院,雖然目前這個引擎還不存在,但我們接下來會創(chuàng)建它章贞。PATH 指定了索引文件需要存放的位置祥绞,我們設置為項目根目錄 BASE_DIR 下的 whoosh_index 文件夾(在建立索引是會自動創(chuàng)建)。

HAYSTACK_SEARCH_RESULTS_PER_PAGE 指定如何對搜索結果分頁鸭限,這里設置為每 10 項結果為一頁就谜。

HAYSTACK_SIGNAL_PROCESSOR 指定什么時候更新索引,這里我們使用haystack.signals.RealtimeSignalProcessor里覆,作用是每當有文章更新時就更新索引丧荐。由于博客文章更新不會太頻繁,因此實時更新沒有問題喧枷。

處理數(shù)據(jù)

接下來就要告訴 django haystack 使用那些數(shù)據(jù)建立索引以及如何存放索引虹统。如果要對 blog 應用下的數(shù)據(jù)進行全文檢索弓坞,做法是在 myblog 應用下建立一個 search_indexes.py 文件,寫上如下代碼:

from haystack import indexes
from myblog.models import Blog

class BlogIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)

    def get_model(self):
        return Blog

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

這是 django haystack 的規(guī)定车荔。要相對某個 app 下的數(shù)據(jù)進行全文檢索渡冻,就要在該 app 下創(chuàng)建一個 search_indexes.py 文件,然后創(chuàng)建一個 XXIndex 類(XX 為含有被檢索數(shù)據(jù)的模型忧便,如這里的 Post)族吻,并且繼承 SearchIndex 和 Indexable。

為什么要創(chuàng)建索引珠增?索引就像是一本書的目錄超歌,可以為讀者提供更快速的導航與查找。在這里也是同樣的道理蒂教,當數(shù)據(jù)量非常大的時候巍举,若要從這些數(shù)據(jù)里找出所有的滿足搜索條件的幾乎是不太可能的,將會給服務器帶來極大的負擔凝垛。所以我們需要為指定的數(shù)據(jù)添加一個索引(目錄)懊悯,在這里是為 Post 創(chuàng)建一個索引,索引的實現(xiàn)細節(jié)是我們不需要關心的梦皮,我們只關心為哪些字段創(chuàng)建索引炭分,如何指定。

每個索引里面必須有且只能有一個字段為 document=True剑肯,這代表 django haystack 和搜索引擎將使用此字段的內(nèi)容作為索引進行檢索(primary field)捧毛。注意,如果使用一個字段設置了document=True退子,則一般約定此字段名為text,這是在 SearchIndex 類里面一貫的命名型将,以防止后臺混亂寂祥,當然名字你也可以隨便改,不過不建議改七兜。

并且丸凭,haystack 提供了use_template=True 在 text 字段中,這樣就允許我們使用數(shù)據(jù)模板去建立搜索引擎索引的文件腕铸,說得通俗點就是索引里面需要存放一些什么東西惜犀,例如 Post 的 title 字段,這樣我們可以通過 title 內(nèi)容來檢索 Post 數(shù)據(jù)了狠裹。舉個例子虽界,假如你搜索 Python ,那么就可以檢索出 title 中含有 Python 的Post了涛菠,怎么樣是不是很簡單莉御?數(shù)據(jù)模板的路徑為templates/search/indexes/youapp/\<model_name>_text.txt(例如templates/search/indexes/myblog/post_text.txt)撇吞,其內(nèi)容為:

{{ object.title }}
{{ object.content }}

這個數(shù)據(jù)模板的作用是對 Post.title、Post.body 這兩個字段建立索引礁叔,當檢索的時候會對這兩個字段做全文檢索匹配牍颈,然后將匹配的結果排序后作為搜索結果返回。

配置 URL

接下來就是配置 URL琅关,搜索的視圖函數(shù)和 URL 模式 django haystack 都已經(jīng)幫我們寫好了煮岁,只需要項目的 urls.py 中包含它:

url(r'^search/', include('haystack.urls')),

修改搜索表單

修改一下搜索表單,讓它提交數(shù)據(jù)到 django haystack 搜索視圖對應的 URL:

<form action="{% url 'haystack_search' %}">
     <input id="unit" type="search" name="q" placeholder="搜索">
</form>

創(chuàng)建搜索結果頁面

haystack_search 視圖函數(shù)會將搜索結果傳遞給模板 search/search.html涣易,因此創(chuàng)建這個模板文件画机,對搜索結果進行渲染:

{% extends 'base.html' %}
{% load highlight %}
{% block content %}
<div class="content-wrap">
{% for blog in page.object_list %}
    <div>
        <a href="{% url 'blog_id' blog.object.id %}">
            <h3>{{ forloop.counter }}、{% highlight blog.object.title with query %}</h3>
        </a>
        <div style="word-wrap: break-word">
            {% highlight blog.object.content with query %}
        </div>
        {% if forloop.counter == page.object_list|length %}
            {% else %}
            <hr>
        {% endif %}
    </div>
{% empty %}
    <div class="no-post">沒有搜索到相關內(nèi)容都毒,請重新搜索</div>
{% endfor %}
</div>
{% endblock %}

修改搜索引擎為中文分詞

我們使用 Whoosh 作為搜索引擎色罚,但在 django haystack 中為 Whoosh 指定的分詞器是英文分詞器,可能會使得搜索結果不理想账劲,我們把這個分詞器替換成 jieba 中文分詞器戳护。從你安裝的 haystack 中把 haystack/backends/whoosh_backends.py 文件拷貝到 myblog/ 下,重命名為 whoosh_cn_backends.py(之前我們在 settings.py 中 的 HAYSTACK_CONNECTIONS 指定的就是這個文件)瀑焦,然后找到如下一行代碼:

schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)

將其中的 analyzer 改為 ChineseAnalyzer腌且,當然為了使用它,你需要在文件頂部引入:from jieba.analyse import ChineseAnalyzer榛瓮。

建立索引文件

最后一步就是建立索引文件了铺董,運行命令 python manage.py rebuild_index就可以建立索引文件了。

最后禀晓,我們可以通過搜索命令給定關鍵詞進行搜索精续,比如搜索”博客“,便會得到源于"博客"的所有內(nèi)容粹懒。

但是重付,我們可以看到博客的標題只有關鍵詞, 關鍵詞之前的內(nèi)容被…替代了凫乖。這顯然不是我們想要的效果确垫,接下來我們通過修改源碼來實現(xiàn)博客標題的完全顯示以及對搜索結果的分頁并使其序號連續(xù)顯示。

統(tǒng)計功能

截止到現(xiàn)在帽芽,我們博客已經(jīng)基本實現(xiàn)了該有的功能删掀,但是,我們還沒有為博客添加統(tǒng)計功能导街,在首頁中有顯示博文的數(shù)目披泪、分類的數(shù)目以及標簽的數(shù)目,這些數(shù)目其實可以直接在視圖函數(shù)中寫上搬瑰,然后傳遞到模板付呕。例如:

blog_nums = Blog.objects.count()
category_nums = Category.objects.count()
tag_nums = Tag.objects.count()

return render(request, '....html', {
    'blog_nums': blog_nums,
    'category_nums ': category_nums ,
    'tag_nums ': tag_nums ,
    })

但是如果這樣的話计福,我們需要在每一個頁面的試圖函數(shù)中添加上述語句,這樣一來增加了代碼的重復度徽职,二來也加劇了數(shù)據(jù)庫的負擔象颖,每一個頁面都要對數(shù)據(jù)庫所有進行查詢統(tǒng)計。從這兩方面考慮姆钉,我們可以單獨在創(chuàng)建一個數(shù)據(jù)表说订,用來記錄博客、分類潮瓶、標簽以及主頁的訪問量陶冷。

新建數(shù)據(jù)表

models.py

class Counts(models.Model):
    """
    統(tǒng)計博客、分類和標簽的數(shù)目
    """
    blog_nums = models.IntegerField(verbose_name='博客數(shù)目', default=0)
    category_nums = models.IntegerField(verbose_name='分類數(shù)目', default=0)
    tag_nums = models.IntegerField(verbose_name='標簽數(shù)目', default=0)
    visit_nums = models.IntegerField(verbose_name='網(wǎng)站訪問量', default=0)


    class Meta:
        verbose_name = '數(shù)目統(tǒng)計'
        verbose_name_plural = verbose_name

然后執(zhí)行makemigrations 和migrate毯辅。

修改admin.py

我們需要在后臺進行數(shù)據(jù)庫的寫入埂伦,由于我們這個數(shù)據(jù)表只是用來統(tǒng)計的,因此只需要添加一個對象就行了思恐。

from myblog.models import Counts

@admin.register(Counts)
class CountsAdmin(admin.ModelAdmin):
    list_display = ['blog_nums', 'category_nums', 'tag_nums', 'visit_nums']


在數(shù)目統(tǒng)計新建一個對象沾谜,此時默認的值都為0,我們將數(shù)據(jù)的統(tǒng)計工作在后臺操作時執(zhí)行胀莹,從而減輕前端頁面訪問時對數(shù)據(jù)庫的壓力基跑。

class BlogAdmin(admin.ModelAdmin):
    list_display = ['title', 'click_nums', 'category', 'create_time', 'modify_time']

    def save_model(self, request, obj, form, change):
        obj.save()
        #統(tǒng)計博客數(shù)目
        blog_nums = Blog.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.blog_nums = blog_nums
        count_nums.save()
        #博客分類數(shù)目統(tǒng)計
        obj_category = obj.category
        category_number = obj_category.blog_set.count()
        obj_category.number = category_number
        obj_category.save()
       
    def delete_model(self, request, obj):
        # 統(tǒng)計博客數(shù)目
        blog_nums = Blog.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.blog_nums = blog_nums - 1
        count_nums.save()
        # 博客分類數(shù)目統(tǒng)計
        obj_category = obj.category
        category_number = obj_category.blog_set.count()
        obj_category.number = category_number - 1
        obj_category.save()
       
class CategoryAdmin(admin.ModelAdmin):
    list_display = ['name', 'number']

    def save_model(self, request, obj, form, change):
        obj.save()
        category_nums = Category.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.category_nums = category_nums
        count_nums.save()

    def delete_model(self, request, obj):
        obj.delete()
        category_nums = Category.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.category_nums = category_nums
        count_nums.save()

class TagAdmin(admin.ModelAdmin):
    list_display = ['name', 'number']

    def save_model(self, request, obj, form, change):
        obj.save()
        tag_nums = Tag.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.tag_nums = tag_nums
        count_nums.save()

    def delete_model(self, request, obj):
        obj.delete()
        tag_nums = Tag.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.tag_nums = tag_nums
        count_nums.save()

這樣就完成了在對博客進行添加、編輯描焰、刪除時媳否,后臺數(shù)據(jù)的統(tǒng)計工作了。

完善相關模板代碼

 # 博客荆秦、標簽篱竭、分類數(shù)目統(tǒng)計
count_nums = Counts.objects.get(id=1)
blog_nums = count_nums.blog_nums
cate_nums = count_nums.category_nums
tag_nums = count_nums.tag_nums

return render(request, '....html', {
    'blog_nums': blog_nums,
    'cate_nums ': category_nums ,
    'tag_nums ': tag_nums ,
    })

再在模板中將對應位置的數(shù)目添加上即可:

{{ blog_nums }}
{{ cate_nums }}
{{ tag_nums }}

這樣就完成了博客的統(tǒng)計功能。

但是在實際操作過程中步绸,會出現(xiàn)如下bug:

  • 在新增博客時掺逼,博客標簽的數(shù)目并不會同步,需要再次保存后才可以靡努。初步判斷是因為新的博客在保存操作時坪圾,還沒有寫入數(shù)據(jù)庫晓折,因此查詢到的標簽是空的惑朦。
  • 在刪除博客類別或者標簽時,對應的博客刪除了漓概,但是對應的統(tǒng)計數(shù)據(jù)并未實時更新漾月。

404、405頁面

  • 404錯誤:指的是頁面未找到胃珍,一般情況下都是網(wǎng)址出錯了梁肿,或者之前的數(shù)據(jù)被刪掉了蜓陌。
  • 500錯誤:指的是服務器出錯了,可能是服務器內(nèi)部的程序出錯了吩蔑,也可能是服務器本身出錯了钮热。

接下來我們?yōu)槲覀兊木W(wǎng)站添加上這兩個頁面。

view.py
#配置404 500錯誤頁面
def page_not_found(request):
    return render(request, '404.html')

def page_errors(request):
    return render(request, '500.html')

當然烛芬,我們應當在模板中寫好我們的404.html和500.html頁面的內(nèi)容隧期,這里可以用我的模板,也可以從網(wǎng)上著一些自己喜歡的模板赘娄。

urls.py
# 配置全局404頁面
hander404 = 'myblog.views.page_not_found'

# 配置全局505頁面
hander505 = 'myblog.views.page_errors'

當然仆潮,最后我們還需要將django中的從數(shù)據(jù)庫中獲取指定數(shù)據(jù)的get方法改為get_objects_or_404,這樣才能在找不到數(shù)據(jù)的情況下返回404錯誤遣臼,否則就返回500服務器錯誤了性置。

view.py
from django.shortcuts import get_object_or_404

#博客詳情
blog = get_object_or_404(Blog, pk=blog_id)

#博客分類
category = get_object_or_404(Category, name=category_name)

#標簽下的所有博客
tag = get_object_or_404(Tag, name=tag_name)

到此本項目算是全部完成了,里面有好多還沒有完善的地方以后再慢慢完善吧揍堰,另外上傳本項目的地址
GitHub:django-blog

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹏浅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子个榕,更是在濱河造成了極大的恐慌篡石,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件西采,死亡現(xiàn)場離奇詭異凰萨,居然都是意外死亡,警方通過查閱死者的電腦和手機械馆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門胖眷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人霹崎,你說我怎么就攤上這事珊搀。” “怎么了尾菇?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵境析,是天一觀的道長。 經(jīng)常有香客問我派诬,道長劳淆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任默赂,我火速辦了婚禮沛鸵,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己曲掰,他們只是感情好疾捍,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栏妖,像睡著了一般乱豆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吊趾,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天咙鞍,我揣著相機與錄音,去河邊找鬼趾徽。 笑死续滋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的孵奶。 我是一名探鬼主播疲酌,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼了袁!你這毒婦竟也來了朗恳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤载绿,失蹤者是張志新(化名)和其女友劉穎粥诫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崭庸,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡怀浆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怕享。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片执赡。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖函筋,靈堂內(nèi)的尸體忽然破棺而出沙合,到底是詐尸還是另有隱情,我是刑警寧澤跌帐,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布首懈,位于F島的核電站,受9級特大地震影響谨敛,放射性物質(zhì)發(fā)生泄漏究履。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一佣盒、第九天 我趴在偏房一處隱蔽的房頂上張望挎袜。 院中可真熱鬧顽聂,春花似錦肥惭、人聲如沸盯仪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽全景。三九已至,卻和暖如春牵囤,著一層夾襖步出監(jiān)牢的瞬間爸黄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工揭鳞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炕贵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓野崇,卻偏偏與公主長得像称开,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乓梨,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355