歡迎訪問我的博客: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