Django开发个人博客项目-(11)博客分类与最后功能完善

0.182字数 2715阅读 15

欢迎访问我的博客: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是一样的,这里就不写出来了。

到此,博客分类就完成了

搜索功能

该博客最开始采用的模板是并不包括搜索功能的,在主页只有主页、归档和分类三个部分。最后博主自己添加了搜索框,不过其实不太想让大家使用这个功能,因此将搜索框隐藏了,只有再点击搜索时,才会显现出来。但是这个添加匹配的不太好,导致手机端会有对不齐的现象,以后前端学好了再来修复这个bug。
博客的搜索功能简单来实现的话,通过查询功能查找到符合关键字的对象。但是,对于一个搜索引擎来说,至少应该能够根据用户的搜索关键词对搜索结果进行排序以及高亮关键字。现在我们就来使用 django-haystack 实现这些特性。
django-haystack 是一个专门提供搜索功能的 django 第三方应用,它支持 Solr、Elasticsearch、Whoosh、Xapian 等多种搜索引擎,配合著名的中文自然语言处理库 jieba 分词,就可以为我们的博客提供一个效s果不错的博客文章搜索系统。

安装相关软件

进入我们的虚拟环境,安装以下依赖:

pip install whoosh django-haystack jieba
  • Whoosh。Whoosh 是一个由纯 Python 实现的全文搜索引擎,没有二进制文件等,比较小巧,配置简单方便。
  • 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,虽然目前这个引擎还不存在,但我们接下来会创建它。PATH 指定了索引文件需要存放的位置,我们设置为项目根目录 BASE_DIR 下的 whoosh_index 文件夹(在建立索引是会自动创建)。

HAYSTACK_SEARCH_RESULTS_PER_PAGE 指定如何对搜索结果分页,这里设置为每 10 项结果为一页。

HAYSTACK_SIGNAL_PROCESSOR 指定什么时候更新索引,这里我们使用haystack.signals.RealtimeSignalProcessor,作用是每当有文章更新时就更新索引。由于博客文章更新不会太频繁,因此实时更新没有问题。

处理数据

接下来就要告诉 django haystack 使用那些数据建立索引以及如何存放索引。如果要对 blog 应用下的数据进行全文检索,做法是在 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 的规定。要相对某个 app 下的数据进行全文检索,就要在该 app 下创建一个 search_indexes.py 文件,然后创建一个 XXIndex 类(XX 为含有被检索数据的模型,如这里的 Post),并且继承 SearchIndex 和 Indexable。

为什么要创建索引?索引就像是一本书的目录,可以为读者提供更快速的导航与查找。在这里也是同样的道理,当数据量非常大的时候,若要从这些数据里找出所有的满足搜索条件的几乎是不太可能的,将会给服务器带来极大的负担。所以我们需要为指定的数据添加一个索引(目录),在这里是为 Post 创建一个索引,索引的实现细节是我们不需要关心的,我们只关心为哪些字段创建索引,如何指定。

每个索引里面必须有且只能有一个字段为 document=True,这代表 django haystack 和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。注意,如果使用一个字段设置了document=True,则一般约定此字段名为text,这是在 SearchIndex 类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。

并且,haystack 提供了use_template=True 在 text 字段中,这样就允许我们使用数据模板去建立搜索引擎索引的文件,说得通俗点就是索引里面需要存放一些什么东西,例如 Post 的 title 字段,这样我们可以通过 title 内容来检索 Post 数据了。举个例子,假如你搜索 Python ,那么就可以检索出 title 中含有 Python 的Post了,怎么样是不是很简单?数据模板的路径为templates/search/indexes/youapp/\<model_name>_text.txt(例如templates/search/indexes/myblog/post_text.txt),其内容为:

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

这个数据模板的作用是对 Post.title、Post.body 这两个字段建立索引,当检索的时候会对这两个字段做全文检索匹配,然后将匹配的结果排序后作为搜索结果返回。

配置 URL

接下来就是配置 URL,搜索的视图函数和 URL 模式 django haystack 都已经帮我们写好了,只需要项目的 urls.py 中包含它:

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

修改搜索表单

修改一下搜索表单,让它提交数据到 django haystack 搜索视图对应的 URL:

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

创建搜索结果页面

haystack_search 视图函数会将搜索结果传递给模板 search/search.html,因此创建这个模板文件,对搜索结果进行渲染:

{% 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">没有搜索到相关内容,请重新搜索</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就可以建立索引文件了。

最后,我们可以通过搜索命令给定关键词进行搜索,比如搜索”博客“,便会得到源于"博客"的所有内容。

但是,我们可以看到博客的标题只有关键词, 关键词之前的内容被…替代了。这显然不是我们想要的效果,接下来我们通过修改源码来实现博客标题的完全显示以及对搜索结果的分页并使其序号连续显示。

统计功能

截止到现在,我们博客已经基本实现了该有的功能,但是,我们还没有为博客添加统计功能,在首页中有显示博文的数目、分类的数目以及标签的数目,这些数目其实可以直接在视图函数中写上,然后传递到模板。例如:

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 ,
    })

但是如果这样的话,我们需要在每一个页面的试图函数中添加上述语句,这样一来增加了代码的重复度,二来也加剧了数据库的负担,每一个页面都要对数据库所有进行查询统计。从这两方面考虑,我们可以单独在创建一个数据表,用来记录博客、分类、标签以及主页的访问量。

新建数据表

models.py

class Counts(models.Model):
    """
    统计博客、分类和标签的数目
    """
    blog_nums = models.IntegerField(verbose_name='博客数目', default=0)
    category_nums = models.IntegerField(verbose_name='分类数目', default=0)
    tag_nums = models.IntegerField(verbose_name='标签数目', default=0)
    visit_nums = models.IntegerField(verbose_name='网站访问量', default=0)


    class Meta:
        verbose_name = '数目统计'
        verbose_name_plural = verbose_name

然后执行makemigrations 和migrate。

修改admin.py

我们需要在后台进行数据库的写入,由于我们这个数据表只是用来统计的,因此只需要添加一个对象就行了。

from myblog.models import Counts

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


在数目统计新建一个对象,此时默认的值都为0,我们将数据的统计工作在后台操作时执行,从而减轻前端页面访问时对数据库的压力。

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

    def save_model(self, request, obj, form, change):
        obj.save()
        #统计博客数目
        blog_nums = Blog.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.blog_nums = blog_nums
        count_nums.save()
        #博客分类数目统计
        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):
        # 统计博客数目
        blog_nums = Blog.objects.count()
        count_nums = Counts.objects.get(id=1)
        count_nums.blog_nums = blog_nums - 1
        count_nums.save()
        # 博客分类数目统计
        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()

这样就完成了在对博客进行添加、编辑、删除时,后台数据的统计工作了。

完善相关模板代码

 # 博客、标签、分类数目统计
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 ,
    })

再在模板中将对应位置的数目添加上即可:

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

这样就完成了博客的统计功能。

但是在实际操作过程中,会出现如下bug:

  • 在新增博客时,博客标签的数目并不会同步,需要再次保存后才可以。初步判断是因为新的博客在保存操作时,还没有写入数据库,因此查询到的标签是空的。
  • 在删除博客类别或者标签时,对应的博客删除了,但是对应的统计数据并未实时更新。

404、405页面

  • 404错误:指的是页面未找到,一般情况下都是网址出错了,或者之前的数据被删掉了。
  • 500错误:指的是服务器出错了,可能是服务器内部的程序出错了,也可能是服务器本身出错了。

接下来我们为我们的网站添加上这两个页面。

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页面的内容,这里可以用我的模板,也可以从网上着一些自己喜欢的模板。

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

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

当然,最后我们还需要将django中的从数据库中获取指定数据的get方法改为get_objects_or_404,这样才能在找不到数据的情况下返回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