继续扩展博客功能

在上一章里,使用了基本的表单提交数据与处理,和复杂一些的分组查询,至此写一个前后端关联操作数据库进行增删改查和展示的基础逻辑代码不成问题了。

这一章里对之前的功能进行扩展:

  • 自定义模板标签和filter,也就是自定义一个小函数用来处理
  • 给站点增加站点地图和post feed
  • 用PostgreSQL实现全文搜索

自定义template tag

Django有很多内建的模板标签和filter,就和之前使用的一样,可以直接用关键字。所有内置的模板标签和filter 的文档在这里。当然,也可以自定义,在自定义之前,需要知道模板标签和filter究竟是什么东西。

其实模板标签和filter的本质,就是一个函数,接受变量(就是传给模板的变量),返回字符串(HTML就是字节流,所以返回字符串)。Django提供了帮助建立自定义tag 和 filter的功能:

  • simple_tag: 处理数据并且返回字符串
  • inclusion_tag: 处理数据返回渲染后的页面(其实也是字符串)

自定义的tag 和 filter 是不能够脱离django环境使用的,如果脱离django环境,只是一个函数而已。在blog应用目录里新建一个目录叫templatetags,然后建立一个空白的__init__.py,再建立一个文件blog_tags.py。

注意这里的文件名很关键,一会在模板内使用自定义的东西时候就需要导入该py文件名。

simple_tag

先从建立一个返回所有已经发布文章的数量小功能看看,在刚刚建立的blog_tags.py里写如下代码:

from django import template
from ..models import Post

register = template.Library()


@register.simple_tag
def total_posts():
    return Post.published.count()

每个标签都必须按照上边的写法,实例化一个template里的Library类,然后用这个类的simple_tag作为装饰器装饰自己编写的函数,就可以把自己编写的函数当做tag来使用了。注意,这里的函数名就是tag名,如果要指定名称,可以在装饰器里指定:

@register.simple_tag(name='my_tag')

在添加了新的自定义功能之后,必须重新启动django服务才能生效,而不会像模板和视图函数一样即时生效。

重启了django服务以后,在模板内使用自己的标签的时候,需要配合{% load name %}来使用,其中name是py文件名,不是自定义的具体tag名。

现在来尝试一下,打开base.html,在最前边加上:

{% load blog_tags %}

然后在sidebar里添加一个子元素:

<p>This is my blog. I've written {% total_posts %} posts so far.</p>

刷新页面,就可以看到显示出了文章总数。

自定义标签的威力是很强大的,因为是python功能,可以返回任意的可以被django渲染成字符串的东西或者直接返回字符串,simple_tag的本质就是一个没有参数的函数,返回一个字符串,按照规定写在文件里然后被装饰器装饰,就可以当做模板标签来使用了。

include_tag

再来看一下使用include的模板标签。include的强大之处在于可以使用上下文变量导入模板来渲染,在blog_tags.py里加入如下内容:

@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
    latest_posts = Post.published.order_by('-publish')[:count]
    return {'latest_posts': latest_posts}

看看这里我们做了什么,用了新的装饰器,加了一个默认值参数,控制总数,然后拿到按照发布时间排序的最新的5篇文章,之后返回了一个模板变量的字典,也就是render的参数。

那么在模板里如何使用呢,这是一个带参数的tag,就像之前使用内置的那样,在标签后边加参数: {% show_latest_posts 3 %}

下边在post目录下建立新的latest.html

<ul>
{% for post in latest_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>

这是一个列表,还没完,既然这个tag是渲染了一个列表,那么是不是要把这个列表放到哪里去。再来修改base.html,在sidebar里加上一个文章列表的部分:

<h3>Latest Posts</h3>
{% show_latest_posts %}

由于页面里导入了blog_tags.py,现在可以直接使用新创建的标签,来显示页面看一下:

看,很方便的显示出了最近的一些文章。

可见include tag的作用,就是可以用一段渲染好的页面取代这个标签所在的位置,功能比simple tag 更加强大。动态修改页面的一部分内容的时候,就可以采用这个方法。

灵活使用自定义标签

除了直接返回字符串和渲染后页面之外,还常见的方法是,就把tag当成一个数据对象,一个变量用来存放数据,然后通过方法和属性去展示在页面里即可。比如我们可以让一个标签不返回字符串,而是返回查询结果:

@register.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(total_comments=Count('comments')).order_by('-total_comments')[:count]

这里返回一个QuerySet,没有直接用于模板渲染,但是传给了模板,这样可以在模板里使用QuerySet的各种方法了。

再修改base.html把这块东西加进来。

<h3>Most commented posts</h3>
{% get_most_commented_posts as most_commented_posts %}
<ul>
{% for post in most_commented_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>

启动之后可以看到,又添加了新的内容,每个页面的这个内容不会随着当前页面改变。这个变量是无需通过视图函数传进来,所以有着很高的灵活性。这里用了另外一个名称most_commented_posts替代原来的模板变量名,也可以不替代。

视图函数控制页面的内容的动态部分,而每个页面相同的部分,交给自定义模板变量直接操作比较好,无需把每个页面需要使用的所有变量都通过视图函数传进来。

最后惯例放上自定义template_tag的官方文档

自定义filter

filter在之前使用过类似于linebreakers这种,是一个模板变量加一个管道符,filter的本质也是一个函数,对之前的变量进行操作,返回结果,filter还可以带一个参数,类似这样:{{ variable|my_filter:”foo” }}

filter还可以连用,类似这样:{{ variable|filter1|filter2 }}。

这次自定义filter的功能,我们用来写一个支持markdown语法的东西。也顺便就把markdown模块导入到django项目中来。

pip install Markdown==2.6.11

之后继续在blog_tags.py里编写如下内容:

from django.utils.safestring import mark_safe
import markdown

@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

和之前自定义标签很类似,自定义的filter是一个接受一个参数,返回一个字符串或者其他东西的函数,也用装饰器装饰了一下,然后起了个名字markdown,这意味着filter的名字是markdown

Django在默认情况下,不会渲染filter生成的HTML代码(认定不安全),必须用mark_safe()来将一段内容标记为安全,才会渲染

然后在list.html和detail.html里都导入blog_tags.py,然后把detail.html里的 {{ post.body|linebreaks }} 替换成 {{ post.body|markdown }}

把list.html里的 {{ post.body|truncatewords:30|linebreaks }} 替换成 {{ post.body|markdown|truncatewords_html:30 }}

这里的变化是truncatewords_html和普通的截断不同,不会截断未闭合的HTML标签。

直接启动发现页面没有变化,因为本来的文章里没有markdown标记,实际上就是将内容通过markdown模块解析一下,如果没有markdown标记,不会有变化,如果有了markdown标记,就会看到变化了。所以我们需要到后台增加一篇博客来使用markdown语法。

This is a post formatted with markdown
--------------------------------------
*This is emphasized* and **this is more emphasized**.
Here is a list:

* One
* Two
* Three

And a [link to the Django website](https://www.djangoproject.com/)

之后在页面中查看新的文章详情,可以发现被解释成了markdown语法,这样很轻松的让我们的博客支持markdown语法了。

自定义filter非常灵活,可以方便的引入第三方模块进行数据处理再返回给页面,只不过要注意返回HTML的时候一定要解除Django的限制,否则不会显示。

filter实际上就是一个函数,可以接受也可以不接受参数,放上自定义filter的官方文档

站点地图

站点地图是一个XML文件,用于给搜索引擎提供信息,可以帮助爬虫索引站点的页面信息。

django.contrib.sites 提供了内置的生成站点地图的框架供使用,如果同一个django项目运行多个站点的时候,很有用。

为了使用站点地图功能,需要启用 django.contrib.sitesjango.contrib.sitemaps ,将这两个东西放到settings.py的INSTALLED_APPS中:

SITE_ID = 1
# Application definition
INSTALLED_APPS = [
# ...
'django.contrib.sites',
'django.contrib.sitemaps',

由于添加了新应用,都要先migrate一下。可以看到建立了两个新表。之后在blog应用目录下建立一个新文件sitemaps.py,编辑如下内容:

from django.contrib.sitemaps import Sitemap
from .models import Post


class PostSitemap(Sitemap):
    changefreq = 'weekly'
    priority = 0.9

    def items(self):
        return Post.published.all()

    def lastmod(self, obj):
        return obj.updated

这里首先是继承Sitemap类,做了一个新类。既然是新类肯定要有个性化设置。属性changefreq是更新的频率,priority表示这个页面的权重,即是不是这个站点的核心网页。

items()方法返回所有包含在站点地图里的文章,默认情况下,django调用每个post的.get_absolute_url()方法获取URL,我们在第一章里给Post类创建了这个方法。如果想单独为每个文章创立URL,就涉及到location方法,以后再学。

lastmod方法接收items方法返回的文章,然后返回每一个文章被修改的文章。这两个方法也可以通过属性调用,反正具体怎么用还是要看站点地图的文档。先这么配置。

由于新加了两个应用,肯定要配置urls.py了,打开站点的urls.py:

from django.contrib.sitemaps.views import sitemap
from blog.sitemaps import PostSitemap
sitemaps = {
    'posts': PostSitemap,
}
urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls', namespace='blog')),
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
    name='django.contrib.sitemaps.views.sitemap')
]

接下来启动项目,到新建的URL里去,可以发现显示了一个XML文件的内容,这个就是站点地图了。此外,如果到管理后台去,还可以发现管理后台多了sites内容:

里边是example.com,可以设置具体的主机名称和显示的名称,均可以设置为localhost:8000:

设置之后,后边要建立的RSS or Atom feed的URL会使用这里的名称。如果是生产环境,一定要改成自己的主机和站点名。

为博客的文章建立feeds

如果经常需要得到一些网站的最新消息,很多内容管理网站都提供RSS 或者 Atom feed,只要订阅这些feed,就可以知道这个站点最新的更新。Django由于最初就是为了内容管理网站如新闻网站发布而制作的,所以对于feed也有内置的功能,和建立站点地图类似的方法来建立feeds。

web feed 就是一个数据格式,通常是XML文件,向用户提供这个网站最新的更新,用户通过一个feed代理程序,订阅这个网站的feed。直接来看如何建立feed

在blog应用目录下新建feeds.py:

from django.contrib.syndication.views import Feed
from django.template.defaultfilters import truncatewords
from .models import Post


class LastestPostFeed(Feed):
    title = 'My blog'
    link = '/blog/'
    description = 'New posts of my blog.'

    def items(self):
        return Post.published.all()[:5]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return truncatewords(item.body, 30)

这个和之前使用站点地图很类似,也是继承一个Feed类,然后指定属性,这些属性的意思是:

  • title 就是 RSS 的<title>元素
  • link 就是 RSS 的<link>元素
  • description 就是 RSS 的<description>元素
  • items()方法表示包含在feed中的数据对象
  • item_title()方法表示对于每个数据对象的标题显示什么
  • item_description()表示每个对象的描述显示什么,这里用了内置的截取30个单词的功能。

由于是博客的feeds,用二级路由,所以配置blog/urls.py:

path('feed/', LastestPostFeed(), name='post_feed'),

这个中间的视图变成了实例化的对象,这个可以不用管,就是这么使用的。之后打开http://127.0.0.1:8000/blog/feed/即可看到feed。和 sitemaps还是有点区别的,sitemaps的url指向的是内置的视图函数,传入的参数是站点地图类。

最后一步是添加一个订阅博客RSS的链接,在base.html里在sidebar里追加:

<p><a href='{% url "blog:post_feed" %}'>Subscribe to my RSS feed</a></p>

之后就可以在侧边栏看到订阅链接了,找了个第三方RSS将本地地址输入,发现果然可以订阅:

为博客增加全文搜索功能

博客作为一种典型的内容管理网站,一般都会支持搜索功能,以便让用户找到自己感兴趣的内容。Django针对搜索功能有一些简单的ORM filter参数比如contains和icontains可以使用,比如查找所有包含framework的文章,则可以使用:

Post.objects.filter(body__contains='framework')

这样可以实现功能,但是如果只能精确的查找,如果想使用更加复杂的查找比如相似性和权重等方法,则无法将这个工作交给Django的内置方法来做,而是要使用全文搜索引擎

Django提供了一个来自PostgreSQL全文搜索的强力搜索工具:django.contrib.postgres。这里要注意的是,虽然Django通过ORM模型支持很多数据库,可以免去数据库底层的操作,但是这个内置的搜索方法只能用于PostgreSQL,不能用在其他数据库上。

PostgreSQL的全文搜索引擎可以看这里的介绍

所以要使用这个功能,必须首先安装PostgreSQL数据库,这也是我们从django内置的SQLite转向使用外部数据库的开始。

安装PostgreSQL数据库

先放上PostgreSQL数据库 10.1 的中文手册

现在我们使用的是django自带的SQLlite数据库,由于我们现在的数据量很小,这没什么问题,但在生产环境中,一般都要使用更强力的独立于Django 的数据库,比如MySQL,PostgreSQL或者Oracle等。为了实现全文搜索功能,将转而使用PostgreSQL。

windows下到 https://www.postgresql.org/download/下载PostgreSQL安装,安装完毕后已经自动在系统中启动了数据库服务,端口默认是5432。如果linux则使用:

sudo apt-get install libpq-dev python-dev
sudo apt-get install postgresql postgresql-contrib

在安装完之后,还需要为python安装Psycopg2 PostgreSQL模块,因为django 的ORM是依赖于python的具体数据库接口模块工作的。

pip install psycopg2==2.7.4

最新版目前已经是2.7.5了,装最新版吧。

在windows下安装完之后,开始菜单里有PostgreSQL 10,其中有SQL shell,和图形化管理工具pgAdmin 4。都可以用来操作数据库。

PostgreSQL数据库的一些基本配置

pgAdmin 4 打开看了一眼,好炫酷,感觉被MySQL欺骗了好久。用管理员账号和刚才安装过程中设置的密码登录,然后新建一个用户叫blog并且设置密码,再新建一个数据库也叫blog,owner选blog用户。

之后编辑django的settings.py文件的数据库部分,修改成如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'blog',
        'USER': 'blog',
        'PASSWORD': '*******',
    }
}

有过操作MySQL的经验可以知道,换数据库之后直接运行站点肯定报错 ,因为需要migrate。而且超级用户也没有,必须重新建立。所以先migrate之后,再createsuperuser即可。

现在可以起站点了,发现站点正常运行了,只不过目前没有数据,没关系,重新输入一些数据即可。

开始进行简单的搜索

一开始说要用的django.contrib.postgres加入到INSTALLED_APPS里去。

之后可以在ORM里,用一个特殊的搜索参数 __search 就可以进行搜索了。

在命令行里简单实验一下:

from blog.models import Post
po = Post.objects.filter(body__search='time')

结果发现返回了内容里带有time这个单词的文章数据对象

估计还是没有太懂全文搜索的原理,这里搜索不是单词的词,就无法搜到结果,看来数据库采用了某些分词的东西来认识单词进行搜索。实验了一下自定义的词,也没有问题,看来必须是一个词才行,不能是包含文中的一段内容,估计数据库会用一些方式分词。这个之后再来看全文搜索引擎方面的内容。

这个功能有了以后,很自然的就可以想到,可以做一个搜索功能出来,让用户提交搜索内容,然后展示搜索到的数据。这是肯定的,不过现在搜索太简单,还需要进行复杂一些的搜索。

同时在多个字段进行检索

实现搜索功能的时候不太可能像上边的简单检索一样,让用户选择搜标题还是内容,一般是应该在一篇文章(包括标题,简要,内容,甚至还可能包括作者和评论)中进行搜索,这就是意味着要在这些字段上都搜索内容。

为了实现这个功能,就必须引入复杂的搜索,就是搜索向量,先在命令行里实验一下:

from django.contrib.postgres.search import SearchVector
from blog.models import Post

Post.objects.annotate(search=SearchVector('title','body'),).filter(search='poem')

结果是搜出了三篇文章,两篇标题内带有poem,一篇内容里带有poem。注意这里需要用聚合函数,因为搜索的过程可能会叠加,即一篇文章同时标题和内容里都有,按照搜索结果分组之后,再用fitler让搜索向量等于要搜索的东西就可以了。这里如果需要,还可以把slug也加进来。如果还想搜某个标签,不能直接加,而是要先在前边搜过某个标签之后,在QuerySet的基础上在此搜索。

现在就有了一个很强大的工具,至少对于我们目前的博客系统来说,没有什么是不能检索的

这里要注意的是,由于全文搜索是一个高密集计算的过程,如果要检索的数据大于几百行,最后是使用一个函数来定义你所需要的搜索向量,直接将这个向量定义到模型里,django针对模型提供了 SearchVectorField 字段用来定义搜索向量。具体可以看这里

建立搜索功能

有了在多个字段上搜索的功能,下面就可以来建立搜索功能页面了。还是老的流程,先建立数据模型和表单,然后建立视图,最后是页面。

这里表单就直接采用标准Form类建立,还是到forms.py里:

class SearchForm(forms.Form):
    query = forms.CharField()

这个很简单,建立标准表单,就一个搜索属性,对应一个文本框。再来修改视图:

from django.contrib.postgres.search import SearchVector
from .forms import EmailPostForm, CommentForm, SearchForm

def post_search(request):
    form = SearchForm()
    query = None
    results = []
    if 'query' in request.GET:
        form = SearchForm(request.GET)
        if form.is_valid():
            query = form.cleaned_data['query']
            results = Post.objects.annotate(search=SearchVector('title', 'slug', 'body'), ).filter(search=query)
    return render(request, 'blog/post/search.html', {'query': query, "form": form, 'results': results})

这个视图的作用也很直白,建立空白搜索表单,默认query变量是none,result没结果,用于控制页面显示。如果get里传来了query内容,并且有效,那么就把搜索结果和query的内容都返回给页面,这个时候页面就判断有数据了,就展示结果。

于是页面编写的逻辑也出来了,在对应路径下建立search.html

{% extends 'blog/base.html' %}

{% block title %}
Search
{% endblock %}

{% block content %}
{% if query %}
    <h1>Post containing {{ query }}</h1>
    <h3>
    {% with results.count as total_results %}
        Found {{ total_results }} result{{ total_results|pluralize }}
    {% endwith %}
    </h3>
    {% for post in results %}
        <h4>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </h4>
        {{ post.body|truncatewords:5 }}
    {% empty %}
        <p>There are no results for your query.</p>
    {% endfor %}
    <p><a href="{% url 'blog:post_search' %}">Search again</a></p>
{% else %}
    <h1>Search for posts</h1>
    <form action="." method="get">
    {{ form.as_p }}
        <input type="submit" value="Search">
    </form>
{% endif %}
{% endblock %}

这个页面也很直接,如果用户输入了query内容,则展示检索数据,没有就显示无结果。如果页面没有query内容或者是一进来的默认状态,就展示表单给用户填写。

这里表单用了get请求。留意过google等搜索引擎的用户会知道,一般在搜索引擎中搜索,都用get请求而不是post请求,这也是一种约定。

注意表单里配置了url反向解析,所以最后一步就是给新视图配上url:

path('search/', views.post_search, name='post_search'),

现在启动服务,到 http://127.0.0.1:8000/blog/search/ 就可以发现正常运行了。现在就建立了一个内容搜索引擎啦,搜索和结果页如下:


搜索优化和排名

搜索功能可以使用了,但是也带来了一系列问题,目前的搜索没有任何附加条件,就是按照数据库中的默认顺序进行搜索。在实际的搜索引擎工作的时候,要考虑的东西有很多,比如匹配性,搜索内容出现的次数多少等。

针对搜索排名,Django 提供了一个SearchQuery类用stemming算法计算排名,PostgreSQL也体改了一个排名函数,按照检测内容在一条数据里出现的次数多少进行排名。

先来看如何使用吧,修改views.py:

# 导入新的SearchQuery和SearchRank
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank
# 把原来查询功能那一行删除,改成几个分离的变量和新的查询
    search_vector = SearchVector('title', 'body')
    search_query = SearchQuery(query)
    results = Post.objects.annotate(
                search=search_vector,
                rank=SearchRank(search_vector, search_query)
            ).filter(search=search_query).order_by('-rank')

这里先把查询向量单独建立出来,然后用查询内容query实例化了一个SearchQuery对象。

下边的查询语句写得更加结构化,search字段等于查询向量,查询排名字段rank用导入的SearchRank对象传入查询向量和查询内容对象来赋值。后边的内容也相应修改,然后按照rank字段降序排列,即排名高(匹配数多)的文章靠前。这次再实验一下查询功能。

发现搜索love,排名第一的文章里love出现了6次,第二的文章里出现了3次,第三个文章里出现了2次,而没有按照文章顺序排列。

搜索权重

有的时候,不仅需要看搜索的个数,还要看权重,如果一篇文章的标题里提到了某个词,可能这篇文章对于搜这个词的人来说用处更大。那么可以说,在标题内包含的这个词的权重,要比在内容里包含相同的词的权重要大。用数字来衡量的话,在标题内找到某个词,可以加10分,在内容里每找一个,加1分,最后比较每个文章的总分,按照总分高低排名,这就是搜索权重的意思。

搜索权重的使用在搜索向量的建立过程中用weight关键字指定,修改一下views.py里的搜索部分如下:

search_vector = SearchVector('title', weight='A') + SearchVector('body', weight='B')
results = Post.objects.annotate(
                search=search_vector,
                rank=SearchRank(search_vector, search_query)
            ).filter(rank__gte=0.33).order_by('-rank')

向量是可以用多个向量对象相加的。这是一个新功能,可以单独为每个字段设置权重。这里的ABCD分别代表1.0, 0,4, 0.2, 0.1

在搜索里修改了filter,原来是直接用query作为search的参数过滤,表示只要搜索到就返回结果;现在改用rank大于0.3作为过滤条件,只显示rand大于0.33的项目。

权重具体应该是使用算法计算出来的,可以在控制台里打印每个查询结果的rank来看,标题有搜索关键字的rank在0.6左右,没有的只有0.3左右。实际搜索一下发现确实没有显示小于0.33的文章。这样就实现了权重功能。

三元相似性搜索

之前的检索是精确检索,有就有,没有就没有,而且还会分词。还有一种检索方法是三元相似性检索。

所谓三元相似,就是三个连续的字符,可以比较两个字符串里,有多少个三个连续的字符相同,这种方法常用在检测不同语言的相似性上。

如果要在PostgreSQL中使用三元检索,必须按照一个pg_trgm扩展。

Windows 下安装,需要打开PostgreSQL 10 的pgAdmin 4,找到blog数据库下边的Extensions,之后右击 create 新的 Extension,在列表里找到 pg_trgm,然后点击创建。

然后继续修改views.py,这一次需要导入django 的新组件 TrigramsSimilarity 并且修改查询语句

from django.contrib.postgres.search import TrigramSimilarity
results = Post.objects.annotate(
              similarity=TrigramSimilarity('title',query),
              ).filter(similarity__gte=0.1).order_by('-similarity')

这次没有用权重,也没有用向量,仅用了相似性,分组的时候设置新字段similarity,设置为搜索标题,然后显示相似性大于0.1的内容。来实验一下,发现用ove,cond,等都可以搜索到内容。

至此各种搜索方法都使用过了,在实际的使用中,通常进行多个维度的查询,比如先精确匹配,再执行权重匹配,然后执行相似性匹配,最后把结果返回给用户。

使用其他全文搜索引擎

除了常用的PostgreSQL之外,还有Solr 和 Elasticsearch 等常用的全文搜索引擎,这些数据库并没有被django原生支持,也没有被PyCharm支持,需要通过django的第三方应用 Haystack来让django链接到这些搜索引。

Haystack也提供了和QuerySet类似的API用于操作数据,具体可以看 http://haystacksearch.org/

最后还是放上在django中使用PostgreSQL数据库进行检索的官方文档

总结

这一章里学习了很多高级功能,总结如下:

  • 建立自定义的tag,分为simple_tag和可以渲染一段HTML的include_tag
  • 建立自定义的filter,非常强大,用这个方式很轻松的就让博客支持了markdown语法
  • 通过django内置功能建立站点地图,生成一个XML文件供搜索引擎爬虫读取
  • 通过django内置功能建立RSS feed供RSS阅读器进行读取
  • 配置第三方数据库
  • PostgreSQL的安装和简单使用,pgAdmin图形化界面操作数据库
  • 引入Django中专门为PostgreSQL服务的一系列组件进行搜索
  • 直接用field__search进行简单搜索
  • 建立搜索向量对多个字段进行搜索
  • 按照搜索匹配次数的排名返回搜索结果
  • 分离搜索向量,每个字段建立权重,按权重返回搜索结果
  • 为PostgreSQL建立pg_trgm扩展,然后使用内置的三元相似性搜索
  • Haystack引入其他全文搜索引擎