第三章 扩展你的博客应用

来源:互联网 发布:免费网店推广软件 编辑:程序博客网 时间:2024/06/16 17:05

3 扩展你的博客应用

上一章介绍了标签的基础知识,你学会了如何在项目中集成第三方应用。本章将会涉及以下知识点:

  • 创建自定义模板标签和过滤器
  • 添加站点地图和帖子订阅
  • 使用Solr和Haystack构建搜索引擎

3.1 创建自定义模板标签和过滤器

Django提供了大量内置的模板标签,比如{% if %}{% block %}。你已经在模板中使用过几个了。你可以在这里找到所有内置的模板标签和过滤器。

当然,Django也允许你创建自定义模板标签来执行操作。当你需要在模板中添加功能,而Django模板标签不满足需求时,自定义模板标签会非常方便。

3.1.1 创建自定义模板标签

Django提供了以下帮助函数,让你很容易的创建自定义模板标签:

  • simple_tag:处理数据,并返回一个字符串。
  • inclusion_tag:处理数据,并返回一个渲染后的模板。
  • assignment_tag:处理数据,并在上下文中设置一个变量。

模板标签必须存在Django应用中。

blog应用目录中,创建templatetags目录,并在其中添加一个空的__init__.py文件和一个blog_tags.py文件。博客应用的目录看起来是这样的:

blog/    __init__.py    models.py    ...    templatetags/        __init__.py        blog_tags.py
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

文件名非常重要。你将会在模板中使用该模块名来加载你的标签。

我们从创建一个simple_tag标签开始,该标签检索博客中已发布的帖子总数。编辑刚创建的blog_tags.py文件,添加以下代码:

from django import templateregister = template.Library()from ..models import Post@register.simple_tagdef total_posts():    return Post.published.count()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们创建了一个简单的模板标签,它返回已发布的帖子数量。每一个模板标签模块想要作为一个有效的标签库,都需要包含一个名为register的变量。该变量是一个template.Library的实例,用于注册你自己的模板标签和过滤器。然后我们使用Python函数定义了一个名为total_posts的标签,并使用@register.simple_tag定义该函数为一个simple_tag,并注册它。Django将会使用函数名作为标签名。如果你想注册为另外一个名字,可以通过name属性指定,比如@register.simple_tag(name='my_tag')

添加新模板标签模块之后,你需要重启开发服务器,才能使用新的模板标签和过滤器。

使用自定义模板标签之前,你必须使用{% load %}标签让它们在模板中生效。像之前提到的,你需要使用包含模板标签和过滤器的python模块名。打开blog/base.html模板,在顶部添加{% load blog_tags %},来加载你的模板标签模块。然后使用创建的标签显示帖子总数。只需要在模板中添加{% total_posts %}。最终,该模板看起来是这样的:

{% load blog_tags %}{% load staticfiles %}<!DOCTYPE html><html><head>    <title>{% block title %}{% endblock %}</title>    <link href="{% static "css/blog.css" %}" rel="stylesheet"></head><body>    <div id="content">        {% block content %}        {% endblock %}    </div>    <div id="sidebar">        <h2>My blog</h2>        <p>This is my blog. I've written {% total_posts %} posts so far.</p>    </div></body></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

因为在项目中添加了新文件,所以需要重启开发服务器。运行python manage.py runserver启动开发服务器。在浏览器中打开http://127.0.0.1:8000/blog/。你会在侧边栏中看到帖子的总数量,如下图所示:

自定义模板标签的强大之处在于,你可以处理任意数据,并把它添加到任意模板中,不用管视图如何执行。你可以执行QuerySet或处理任意数据,并在模板中显示结果。

现在,我们准备创建另一个标签,在博客侧边栏中显示最近发布的帖子。这次我们使用inclusion_tag标签。使用该标签,可以利用你的模板标签返回的上下文变量渲染模板。编辑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}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

在这段代码中,我们使用@register.inclusion_tag注册模板标签,并用该模板标签的返回值渲染blog/post/latest_posts.html模板。我们的模板标签接收一个默认值为5的可选参数count,用于指定想要显示的帖子数量。我们用该变量限制Post.published.order_by('-publish')[:count]查询返回的结果。注意,该函数返回一个字典变量,而不是一个简单的值。Inclusion标签必须返回一个字典值作为上下文变量,来渲染指定的模板。Inclusion标签返回一个字典。我们刚创建的模板标签可以传递一个显示帖子数量的可选参数,比如{% show_latest_posts 3 %}

现在,在blog/post/目录下新建一个latest_posts.html文件,添加以下代码:

<ul>{% for post in latest_posts %}    <li>        <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>    </li>{% endfor %}</ul>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里,我们用模板标签返回的latest_posts变量显示一个帖子的无序列表。现在,编辑blog/base.html模板,添加新的模板标签,显示最近3篇帖子,如下所示:

<div id="sidebar">    <h2>My blog</h2>    <p>This is my blog. I've written {% total_posts %} posts so far.</p>    <h3>Latest posts</h3>    {% show_latest_posts 3 %}</div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过传递显示的帖子数量调用模板标签,并用给定的上下文在原地渲染模板。

现在回到浏览器,并刷新页面。侧边栏现在看起来是这样的:

最后,我们准备创建一个assignment标签。Assignment标签跟simple标签很像,它们把结果存在指定的变量中。我们会创建一个assignment标签,用于显示评论最多的帖子。编辑blog_tags.py文件,添加以下导入和模板标签:

from django.db.models import Count@register.assignment_tagdef get_most_commented_posts(count=5):    return Post.published.annotate(total_comments=Count('comments')).order_by('-total_comments')[:count]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这个QuerySet使用了annotate()函数,调用Count汇总函数进行汇总查询。我们构造了一个QuerySet,在totaol_comments字段中汇总每篇帖子的评论数,并用该字段对QeurySet排序。我们还提供了一个可选变量count,限制返回的对象数量。

除了Count,Django还提供了AvgMaxMinSum汇总函数。你可以在这里阅读更多关于汇总函数的信息。

编辑blog/base.html模板,在侧边栏的<div>元素中添加以下代码:

<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Assignment模板标签的语法是{% template_tag as variable %}。对于我们这个模板标签,我们使用{% get_most_commented_posts as most_commented_posts %}。这样,我们就在名为most_commented_posts的变量中存储了模板标签的结果。接着,我们用无序列表显示返回的帖子。

现在,打开浏览器,并刷新页面查看最终的结果,如下所示:

你可以在这里阅读更多关于自定义模板标签的信息。

3.1.2 创建自定义模板过滤器

Django内置了各种模板过滤器,可以在模板中修改变量。过滤器就是接收一个或两个参数的Python函数——一个是它要应用的变量的值,以及一个可选参数。它们返回的值可用于显示,或者被另一个过滤器处理。一个过滤器看起来是这样的:{{ variable|my_filter }},或者传递一个参数:{{ variable|my_filter:"foo" }}。你可以在一个变量上应用任意多个过滤器:{{ variable|filter1|filter2 }},每个过滤器作用于前一个过滤器产生的输出。

我们将创建一个自定义过滤器,可以在博客帖子中使用markdown语法,然后在模板中把帖子内容转换为HTML。Markdown是一种纯文本格式化语法,使用起来非常简单,并且可以转换为HTML。你可以在这里学习该格式的基本语法。

首先,使用下面的命令安装Python的markdown模块:

pip install Markdown
  • 1
  • 1

接着,编辑blog_tags.py文件,添加以下代码:

from django.utils.safestring import mark_safeimport markdown@register.filter(name='markdown')def markdown_format(text):    return mark_safe(markdown.markdown(text))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们用与模板标签同样的方式注册模板过滤器。为了避免函数名和markdown模块名的冲突,我们将函数命名为markdown_format,把过滤器命名为markdown,在模板中这样使用:{{ variable|markdown }}。Django会把过滤器生成的HTML代码转义。我们使用Django提供的mark_safe函数,把要在模板中渲染的结果标记为安全的HTML代码。默认情况下,Django不会信任任何HTML代码,并且会在输出结果之前进行转义。唯一的例外是标记为安全的转义变量。这种行为可以阻止Django输出有潜在危险的HTML代码;同时,当你知道返回的是安全的HTML代码时,允许这种例外情况发生。

现在,在帖子列表和详情模板中加载你的模板标签。在post/list.htmlpost/detail.html模板的{% extends %}标签之后添加下面这行代码:

{% load blog_tags %}
  • 1
  • 1

post/detail.html模板中,把这一行:

{{ post.body|linebreaks }}
  • 1
  • 1

替换为:

{{ post.body|markdown }}
  • 1
  • 1

接着,在post/list.html模板中,把这一行:

{{ post.body|truncatewords:30|linebreaks }}
  • 1
  • 1

替换为:

{{ post.body|markdown|truncatewords_html:30 }}
  • 1
  • 1

过滤器truncatewords_html会在指定数量的单词之后截断字符串,并避免没有闭合的HTML标签。

现在,在浏览器中打开http://127.0.0.1:8000/admin/blog/post/add/,并用下面的正文添加一篇帖子:

This is a post formatted with markdown--------------------------------------*This is emphasized* and **this is more emphasized**.Here is a list:* One* Two* ThreeAnd a [link to the Django website](https://www.djangoproject.com/)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

打开浏览器,看看帖子是如何渲染的,如下图所示:

正如你所看到的,自定义模板过滤器对自定义格式非常有用。你可以在这里查看更多自定义过滤器的信息。

3.2 为站点添加站点地图

Django自带一个站点地图框架,可以为站点动态生成站点地图。站点地图是一个XML文件,告诉搜索引擎你的网站有哪些页面,它们之间的关联性,以及更新频率。使用站点地图,可以帮助网络爬虫索引网站的内容。

Django的站点地图框架依赖django.contrib.sites,它允许你将对象关联到在项目中运行的指定网站。当你用单个Django项目运行多个站点时,会变得非常方便。要安装站点地图框架,我们需要在项目中启用sitessitemap两个应用。编辑项目的settings.py文件,并在INSTALLED_APPS设置中添加django.contrib.sitesdjango.contrib.sitemaps。同时为站点ID定义一个新的设置,如下所示:

SITE_ID = 1INSTALLED_APPS = [    # ...    'django.contrib.sites',    'django.contrib.sitemaps',]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

现在,运行以下命令,在数据库中创建sites应用的数据库表:

python manage.py migrate
  • 1
  • 1

你会看到包含这一行的输出:

Applying sites.0001_initial... OK
  • 1
  • 1

现在,sites应用与数据库同步了。在blog应用目录中创建sitemaps.py文件,添加以下代码:

from django.contrib.sitemaps import Sitemapfrom .models import Postclass PostSitemap(Sitemap):    changefreq = 'weekly'    priority = 0.9    def items(self):        return Post.published.all()    def lastmod(self, obj):        return obj.publish
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我们创建了一个自定义的站点地图,它继承自sitemaps模块的Sitemap类。changefreqpriority属性表示帖子页面的更新频率和它们在网站中的关联性(最大值为1)。items()方法返回这个站点地图中包括的对象的QuerySet。默认情况下,Django调用每个对象的get_absolute_url()方法获得它的URL。记住,我们在第一章创建了该方法,用于获得帖子的标准URL。如果你希望为每个对象指定URL,可以在站点地图类中添加location方法。lastmod方法接收items()返回的每个对象,并返回该对象的最后修改时间。changefreqpriority既可以是方法,也可以是属性。你可以在官方文档中查看完整的站点地图参考。

最后,我们只需要添加站点地图的URL。编辑项目的urls.py文件,添加站点地图:

from django.conf.urls import include, urlfrom django.contrib import adminfrom django.contrib.sitemaps.views import sitemap from blog.sitemaps import PostSitemapsitemaps = {    'posts': PostSitemap,}urlpatterns = [    url(r'^admin/', include(admin.site.urls)),    url(r'^blog/',         include('blog.urls'namespace='blog', app_name='blog')),    url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},        name='django.contrib.sitemaps.views.sitemap'),]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我们在这里包括了必需的导入,并定义了一个站点地图的字典。我们定义了一个匹配sitemap.xml的URL模式,并使用sitemap视图。把sitemaps字典传递给sitemap视图。在浏览器中打开http://127.0.0.1:8000/sitemap.xml,你会看到类似这样的XML代码:

<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">    <url>        <loc>http://example.com/blog/2017/04/28/markdown-post/</loc>        <lastmod>2017-04-28</lastmod>        <changefreq>weekly</changefreq>        <priority>0.9</priority>    </url>    <url>        <loc>http://example.com/blog/2017/04/25/one-more-again/</loc>        <lastmod>2017-04-25</lastmod>        <changefreq>weekly</changefreq>        <priority>0.9</priority>    </url>    ...</urlset>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

通过调用get_absolute_url()方法,为每篇帖子构造了URL。我们在站点地图中指定了,lastmod属性对应帖子的publish字段,changefreqpriority属性也是从PostSitemap中带过来的。你可以看到,用于构造URL的域名是example.com。该域名来自数据库中的Site对象。这个默认对象是在我们同步sites框架数据库时创建的。在浏览器中打开http://127.0.0.1/8000/admin/sites/site/,如下图所示:

这是sites框架的管理视图显示的列表。你可以在这里设置sites框架使用的域名或主机,以及依赖它的应用。为了生成存在本机环境中的URL,需要把域名修改为127.0.0.1:8000,如下图所示:

为了方便开发,我们指向了本机。在生产环境中,你需要为sites框架使用自己的域名。

3.3 为博客帖子创建订阅

Django内置一个聚合订阅(syndication feed)框架,可以用来动态生成RSS或Atom订阅,与用sites框架创建站点地图的方式类似。

blog应用目录下创建一个feeds.py文件,添加以下代码:

from django.contrib.syndication.views import Feedfrom django.template.defaultfilters import truncatewordsfrom .models import Postclass LatestPostsFeed(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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

首先,我们从syndication框架的Feed类继承。titlelinkdescription属性分别对应RSS的<title><link><description>元素。

items()方法获得包括在订阅中的对象。我们只检索最近发布的五篇帖子。item_title()item_description()方法接收items()返回的每一个对象,并返回每一项的标题和描述。我们用内置的truncatewords模板过滤器截取前30个单词,用于构造博客帖子的描述。

现在编辑blog应用的urls.py文件,导入刚创建的LatestPostsFeed,并在新的URL模式中实例化:

from .feeds import LatestPostsFeedurlpatterns = [    # ...    url(r'^feed/$', LatestPostsFeed(), name='post_feed'),]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在浏览器中打开http://127.0.0.1:8000/blog/feed/,你会看到RSS订阅包括了最近五篇博客帖子:

<?xml version="1.0" encoding="utf-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">    <channel>        <title>My blog</title>        <link>http://127.0.0.1:8000/blog/</link>        <description>New posts of my blog</description>        <atom:link href="http://127.0.0.1:8000/blog/feed/" rel="self"></atom:link>        <language>en-us</language>        <lastBuildDate>Fri, 28 Apr 2017 05:44:43 +0000</lastBuildDate>        <item>            <title>One More Again</title>            <link>http://127.0.0.1:8000/blog/2017/04/25/one-more-again/</link>            <description>Post body.</description>            <guid>http://127.0.0.1:8000/blog/2017/04/25/one-more-again/</guid>        </item>        <item>            <title>Another Post More</title>            <link>http://127.0.0.1:8000/blog/2017/04/25/another-post-more/</link>            <description>Post body.</description>            <guid>http://127.0.0.1:8000/blog/2017/04/25/another-post-more/</guid>        </item>        ...    </channel></rss>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

如果你在RSS客户端中打开这个URL,你会看到一个用户界面友好的订阅。

最后一步是在博客的侧边栏添加一个订阅链接。打开blog/base.html模板,在侧边栏<div>的帖子总数后面添加这一行代码:

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

现在,在浏览器中打开http://127.0.0.1:8000/blog/,你会看到如下图所示的侧边栏:

3.4 使用Solr和Haystack添加搜索引擎

译者注:暂时跳过这一节的翻译,对于一般的博客,实在是用不上搜索引擎。

3.5 总结

在这一章中,你学习了如何创建自定义Django模板标签和过滤器,为模板提供自定义的功能。你还创建了站点地图,便于搜索引擎爬取你的网站,以及一个RSS订阅,便于用户订阅。同时,你在项目中使用Haystack集成了Solr,为博客构建了一个搜索引擎。

在下一章,你会学习如何使用Django的authentication构建社交网站,创建自定义的用户资料,以及社交认证。

原创粉丝点击