当前位置: 首页 > 后端技术 > Python

HelloDjangoPart10-小细节Markdown文章自动生成目录,提升阅读体验

时间:2023-03-26 19:40:47 Python

作者:HelloGitHub-文章中涉及的示例代码已经更新到HelloGitHub-团队仓库上一篇我们使用Markdown创建文章提供排版支持。Markdown在解析内容的同时,可以自动提取整个内容的目录结构。现在让我们使用Markdown为文章自动生成一个目录。在文本中插入一个目录我们先回顾一下博客的Post(文章)模型,其中body是我们存放Markdown文本的字段:blog/models.pyfromdjango.dbimportmodelsclassPost(models.Model):#Otherfields...body=models.TextField()我们再回顾一下文章详情页的视图。在detailview函数中,我们将帖子body字段中的Markdown文本解析为HTML文本,然后传递给模板进行展示。blog/views.pydefdetail(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',])returnrender(request,'blog/detail.html',context={'post':post})markdown.markdown()方法将Markdown文本放入post.body解析为HTML文本。同时,我们还为这个方法提供了一个额外的扩展参数。其中markdown.extensions.toc是自动生成的目录的扩展(这里可以看出我们有先见之明,如果之前没有添加,记得现在添加)。在渲染Markdown文本时添加toc扩展后,您可以在文本中插入目录。方法是在编写Markdown文本时,在要生成目录的地方插入[TOC]标签。比如你写一篇新的Markdown博文,Markdown文本内容如下:[TOC]##我是Title1,这是Title下面的文字##我是Title2,这是Title2下面的文字###我是Title2This的副标题是第二个标题下的副标题的文字##我是第三个标题这是第三个标题下的文字。最终分析的效果是:原来的[TOC]标记被内容目录所取代。在页面的任意位置插入目录上述方法的一个限制是目录只能通过[TOC]标记插入到文章内容中。如果我想在页面的其他地方(例如边栏)插入目录怎么办?方法其实很简单,只需要稍微改变一下解析Markdown文本内容的方式,具体代码如下:blog/views.pydefdetail(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)发布。toc=md.tocreturnrender(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实例本身没有atoc属性是的,我们给它动态添加了toc属性,这就是Python动态语言的好处)。接下来在博客文章详情页的文章目录侧边栏渲染文章目录!删除占位符目录的内容并替换为以下代码:{%blocktoc%}文章目录{{post.toc|safe}}

{%endblocktoc%}使用模板变量标签{{post.toc}}显示模板变量的值。请注意,post.toc实际上是一段HTML代码。我们知道django会识别template中的HTML代码进行转义,所以使用safe标签来防止django对其进行转义。最终渲染效果为:处理空目录现在可以完美生成目录了,但是还是有一个异常,当文章没有任何title元素时,Markdown无法提取目录结构,而post.toc是一个空的div标签如下:
    对于没有目录结构的这篇文章,在侧边栏显示一个目录是没有意义的,所以我们希望只在article侧边栏的目录仅在存在目录结构时显示。那么应该怎么办呢?分析toc的内容,如果有目录结构,ul标签中就有值,否则没有值。我们可以使用正则表达式来检测ul标签中是否有包裹的元素来判断目录是否存在。defdetail(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'\s*
      (.*)
    \s*
    ',md.toc,re.S)post.toc=m.group(1)如果m不是Noneelse''returnrender(request,'blog/detail.html',context={'post':post})这里我们使用正则表达式来匹配生成目录中ul标签包裹的内容。如果不为空,则指定目录,将ul标签中的值(目的是只包含目录内容的核心部分,丢弃多余的HTML标签结构)提取到post.toc;否则,将post的toc设置为空字符串,然后我们可以通过判断模板中的post.toc是否为空来决定是否显示侧边栏目录:{%blocktoc%}{%ifpost.toc%}文章目录
      {{post.toc|safe}}
    {%endif%}{%endblocktoc%}这里我们看到一个新的模板标签{%if%},该标签用于条件判断,类似于Python中的if条件判断美化标题锚点URL文章内容的标题设置为锚点,点击某个目录中的title,页面会跳转到文章内容中title所在的位置。这时候浏览器的URL显示的值可能不是很漂亮,比如下面:http://127.0.0.1:8000/posts/8/#_1http://127.0.0.1:8000/posts/8/#_3#_1是锚点。Markdown在设置锚点时使用标题的值。由于我们的标题通常是中文的,Markdown无法处理它们,所以它会忽略标题值,只是简单地附加一个锚值,如_1。为了解决这个问题,需要修改传递给扩展的参数。具体方法如下:blog/views.pyfromdjango.utils.textimportslugifyfrommarkdown.extensions.tocimportTocExtensiondefdetail(request,pk):post=get_object_or_404(Post,pk=pk)md=markdown.Markdown(extensions=['markdown.extensions.extra','markdown.extensions.codehilite',#记得在顶部引入TocExtension和slugifyTocExtension(slugify=slugify),])post.body=md.convert(post.body)m=re.search(r'\s*\s*',md.toc,re.S)post.toc=m.group(1)如果m不是Noneelse''returnrender(request,'blog/detail.html',context={'post':post})andbefore不同的是,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/#我是标题二下的副标题欢迎关注HelloGitHub公众号,获取更多关于开源项目的信息和内容“讲解开源项目系列”上线——让对开源项目感兴趣的人不再害怕,让开源项目的发起者不再孤单。关注我们的文章,您将发现编程的乐趣,使用并发现参与开源项目是多么容易。欢迎联系我们投稿,让更多的人爱上开源,为开源做贡献~