blog/models.pyfrom django.db import modelsclass Post(models.Model):# Other fields ...body = models.TextField()

再来回顾一下文章详情页的视图,我们在 detail 视图函数中将 post 的 body 字段中的 Markdown 文本解析成了 HTML 文本,然后通报给模板显示。

blog/views.pydef detail(request, pk):post = get_object_or_404(Post, pk=pk)post.body = markdown.markdown(post.body, extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ])return render(request, 'blog/detail.html', context={'post': post})

markdown.markdown() 方法把 post.body 中的 Markdown 文本解析成了 HTML 文本。
同时我们还给该方法供应了一个 extensions 的额外参数。
个中 markdown.extensions.toc 便是自动天生目录的拓展(这里可以看出我们有先见之明,如果你之前没有添加的话记得现在添加进去)。

在渲染 Markdown 文本时加入了 toc 拓展后,就可以在文中插入目录了。
方法是在书写 Markdown 文本时,在你想天生目录的地方插入 [TOC] 标记即可。
例如新写一篇 Markdown 博文,其 Markdown 文本内容如下:

html文章目录自动生成第 10 篇小细节 Markdown 文章主动生成目次晋升浏览体验 PHP
(图片来自网络侵删)

[TOC]## 我是标题一这是标题一下的正文## 我是标题二这是标题二下的正文### 我是标题二下的子标题这是标题二下的子标题的正文## 我是标题三这是标题三下的正文

其终极解析后的效果便是:

原来 [TOC] 标记的地方被内容的目录更换了。

在页面的任何地方插入目录

上述办法的一个局限性便是只能通过 [TOC] 标记在文章内容中插入目录。
如果我想在页面的其它地方,比如侧边栏插入一个目录该怎么做呢?方法实在也很大略,只须要轻微改动一下解析 Markdown 文本内容的办法即可,详细代码就像这样:

blog/views.pydef detail(request, pk):post = get_object_or_404(Post, pk=pk)md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc',])post.body = md.convert(post.body)post.toc = md.tocreturn render(request, 'blog/detail.html', context={'post': post})

和之前的代码不同,我们没有直接用 markdown.markdown() 方法来渲染 post.body 中的内容,而是先实例化了一个 markdown.Markdown 工具 md,和 markdown.markdown() 方法一样,也传入了 extensions 参数。
接着我们便利用该实例的 convert 方法将 post.body 中的 Markdown 文本解析成 HTML 文本。
而一旦调用该方法后,实例 md 就会多出一个 toc 属性,这个属性的值便是内容的目录,我们把 md.toc 的值赋给 post.toc 属性(要把稳这个 post 实例本身是没有 toc 属性的,我们给它动态添加了 toc 属性,这便是 Python 动态措辞的好处)。

接下来就在博客文章详情页的文章目录侧边栏渲染文章的目录吧!
删掉占位用的目录内容,更换成如下代码:

{% block toc %}<div class=\"大众widget widget-content\"大众><h3 class=\"大众widget-title\"大众>文章目录</h3> {{ post.toc|safe }} </div>{% endblock toc %}

即利用模板变量标签 {{ post.toc }} 显示模板变量的值,把稳 post.toc 实际是一段 HTML 代码,我们知道 django 会对模板中的 HTML 代码进行转义,以是要利用 safe 标签防止 django 对其转义。
其终极渲染后的效果便是:

处理空目录

现在目录已经可以完美天生了,不过还有一个非常情形,当文章没有任何标题元素时,Markdown 就提取不出目录构造,post.toc 便是一个空的 div 标签,如下:

<div class=\"大众toc\"大众> <ul></ul></div>

对付这种没有目录构造的文章,在侧边栏显示一个目录是没故意义的,以是我们希望只有在文章存在目录构造时,才显示侧边栏的目录。
那么该当怎么做呢?

剖析 toc 的内容,如果有目录构造,ul 标签中就有值,否则就没有值。
我们可以利用正则表达式来测试 ul 标签中是否包裹有元向来确定是否存在目录。

def detail(request, pk): post = get_object_or_404(Post, pk=pk) md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) post.body = md.convert(post.body) m = re.search(r'<div class=\公众toc\公众>\s<ul>(.)</ul>\s</div>', md.toc, re.S) post.toc = m.group(1) if m is not None else '' return render(request, 'blog/detail.html', context={'post': post})

这里我们正则表达式去匹配天生的目录中包裹在 ul 标签中的内容,如果不为空,解释目录,就把 ul 标签中的值提取出来(目的是只要包含目录内容的最核心部分,多余的 HTML 标签构造丧失落)赋值给 post.toc;否则,将 post 的 toc 置为空字符串,然后我们就可以在模板中通过判断 post.toc 是否为空,来决定是否显示侧栏目录:

{% block toc %} {% if post.toc %} <div class=\公众widget widget-content\"大众> <h3 class=\公众widget-title\"大众>文章目录</h3> <div class=\"大众toc\公众> <ul> {{ post.toc|safe }} </ul> </div> </div> {% endif %}{% endblock toc %}

这里我们看到了一个新的模板标签 {% if %},这个标签用来做条件判断,和 Python 中的 if 条件判断是类似的。

美化标题的锚点 URL

文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太都雅,比如像下面的样子:

http://127.0.0.1:8000/posts/8/#_1http://127.0.0.1:8000/posts/8/#_3

#_1 便是锚点,Markdown 在设置锚点时利用的是标题的值,由于常日我们的标题都是中文,Markdown 没法处理,以是它就忽略的标题的值,而是大略地在后面加了个 \_1 这样的锚点值。
为理解决这一个问题,须要修正一下传给 extentions 的参数,其详细做法如下:

blog/views.pyfrom django.utils.text import slugifyfrom markdown.extensions.toc import TocExtensiondef detail(request, pk): post = get_object_or_404(Post, pk=pk) md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', # 记得在顶部引入 TocExtension 和 slugify TocExtension(slugify=slugify), ]) post.body = md.convert(post.body) m = re.search(r'<div class=\"大众toc\公众>\s<ul>(.)</ul>\s</div>', md.toc, re.S) post.toc = m.group(1) if m is not None else '' return render(request, 'blog/detail.html', context={'post': post})

和之前不同的是,extensions 中的 toc 拓展不再是字符串 markdown.extensions.toc ,而是 TocExtension 的实例。
TocExtension 在实例化时其 slugify 参数可以接管一个函数,这个函数将被用于处理标题的锚点值。
Markdown 内置的处理方法不能处理中文标题,以是我们利用了 django.utils.text 中的 slugify 方法,该方法可以很好地处理中文。

这时候标题的锚点 URL 变得好看多了。

http://127.0.0.1:8000/posts/8/#我是标题一http://127.0.0.1:8000/posts/8/#我是标题二下的子标题

References

[1] HelloGitHub-Team 仓库: https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial

欢迎关注 HelloGitHub "大众年夜众号,获取更多开源项目的资料和内容

『讲解开源项目系列』启动——让对开源项目感兴趣的人不再畏惧、让开源项目的发起者不再孤单。
随着我们的文章,你会创造编程的乐趣、利用和创造参与开源项目如此大略。
欢迎联系我们给我们投稿,让更多人爱上开源、贡献开源~