让博客支持 Markdown 语法和代码高亮

2019-08-2120055 阅读78 评论

为了让博客文章具有良好的排版,显示更加丰富的格式,我们使用 Markdown 语法来书写博文。Markdown 是一种 HTML 文本标记语言,只要遵循它约定的语法格式,Markdown 的解析工具就能够把 Markdown 文档转换为标准的 HTML 文档,从而使文章呈现更加丰富的格式,例如标题、列表、代码块等等 HTML 元素。由于 Markdown 语法简单直观,不用超过 5 分钟就可以轻松掌握常用的标记语法,因此大家青睐使用 Markdown 书写 HTML 文档。下面让我们的博客也支持使用 Markdown 写作。

安装 Python Markdown

将 Markdown 格式的文本解析成标准的 HTML 文档是一个复杂的工程,好在已有好心人帮我们完成了这些工作,直接拿来使用即可。首先安装 Markdown,这是一个 Python 第三方库,在项目根目录下运行命令 pipenv install markdown

在 detail 视图中解析 Markdown

将 Markdown 格式的文本解析成 HTML 文本非常简单,只需调用这个库的 markdown 方法。我们书写的博客文章内容存在 Postbody 属性里,回到我们的详情页视图函数,对 postbody 的值做一下解析,把 Markdown 文本转为 HTML 文本再传递给模板:

blog/views.py

import markdown
from django.shortcuts import get_object_or_404, render

from .models import Post

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

这样我们在模板中显示 {{ post.body }} 的时候,就不再是原始的 Markdown 文本了,而是解析过后的 HTML 文本。注意这里我们给 markdown 解析函数传递了额外的参数 extensions,它是对 Markdown 语法的拓展,这里使用了三个拓展,分别是 extra、codehilite、toc。extra 本身包含很多基础拓展,而 codehilite 是语法高亮拓展,这为后面的实现代码高亮功能提供基础,而 toc 则允许自动生成目录(在以后会介绍)。

来测试一下效果,进入后台,这次我们发布一篇用 Markdown 语法写的测试文章看看,你可以使用以下的 Markdown 测试代码进行测试,也可以自己书写你喜欢的 Markdown 文本。假设你是 Markdown 新手请参考一下这些教程,一定学一下,保证你可以在 5 分钟内掌握常用的语法格式,而以后对你写作受用无穷。可谓充电 5 分钟,通话 2 小时。以下是我学习中的一些参考资料:

# 一级标题

## 二级标题

### 三级标题

- 列表项1
- 列表项2
- 列表项3

> 这是一段引用

​```python
def 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 引入图片的语法为:![图片说明](图片链接)

safe 标签

我们在发布的文章详情页没有看到预期的效果,而是类似于一堆乱码一样的 HTML 标签,这些标签本应该在浏览器显示它自身的格式,但是 django 出于安全方面的考虑,任何的 HTML 代码在 django 的模板中都会被转义(即显示原始的 HTML 代码,而不是经浏览器渲染后的格式)。为了解除转义,只需在模板变量后使用 safe 过滤器即可,告诉 django,这段文本是安全的,你什么也不用做。在模板中找到展示博客文章内容的 {{ post.body }} 部分,为其加上 safe 过滤器:{{ post.body|safe }},大功告成,这下看到预期效果了。

Markdown 测试

safe 是 django 模板系统中的过滤器(Filter),可以简单地把它看成是一种函数,其作用是作用于模板变量,将模板变量的值变为经过滤器处理过后的值。例如这里 {{ post.body|safe }},本来 {{ post.body }}经模板系统渲染后应该显示 body 本身的值,但是在后面加上 safe 过滤器后,渲染的值不再是 body 本身的值,而是由 safe 函数处理后返回的值。过滤器的用法是在模板变量后加一个 | 管道符号,再加上过滤器的名称。可以连续使用多个过滤器,例如 {{ var|filter1|filter2 }}。

代码高亮

程序员写博客免不了要插入一些代码,Markdown 的语法使我们容易地书写代码块,但是目前来说,显示的代码块里的代码没有任何颜色,很不美观,也难以阅读,要是能够像代码编辑器里一样让代码高亮就好了。

代码高亮我们借助 js 插件来实现,其原理就是 js 解析整个 html 页面,然后找到代码块元素,为代码块中的元素添加样式。我们使用的插件叫做 highlight.js 和 highlightjs-line-numbers.js,前者提供基础的代码高亮,后者为代码块添加行号。

首先在 base.html 的 head 标签里引入代码高亮的样式,有多种样式供你选择,这里我们选择 Github 主题的样式。样式文件直接通过 CDN 引入,同时在 style 标签里自定义了一点元素样式,使得代码块的显示效果更加完美。

<head>
  ...
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet">

  <style>
    .codehilite {
      padding: 0;
    }

    /* for block of numbers */
    .hljs-ln-numbers {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;

      text-align: center;
      color: #ccc;
      border-right: 1px solid #CCC;
      vertical-align: top;
      padding-right: 5px;
    }

    .hljs-ln-n {
      width: 30px;
    }

    /* for block of code */
    .hljs-ln .hljs-ln-code {
      padding-left: 10px;
      white-space: pre;
    }
  </style>
</head>

然后是引入 js 文件,因为应该等整个页面加载完,插件再去解析代码块,所以把 js 文件的引入放在 body 底部:

<body>
  <script src="https://cdn.bootcss.com/highlight.js/9.15.8/highlight.min.js"></script>
  <script src="https://cdn.bootcss.com/highlightjs-line-numbers.js/2.7.0/highlightjs-line-numbers.min.js"></script>
  <script>
    hljs.initHighlightingOnLoad();
    hljs.initLineNumbersOnLoad();
  </script>
</body>
</body>

非常简单,通过 CDN 引入 highlight.js 和 highlightjs-line-numbers.js,然后初始化了两个插件。再来看下效果,非常完美!

代码高亮

-- EOF --

78 评论
登录后回复
DOWOJU
2023-03-17 15:52:22

在后台添加测试markdown时不要直接复制网页里的

回复
Feko
2022-07-27 17:53:05

通过+1

回复
Fan()
2020-10-19 12:23:13

图挂了, 大佬修复下么, 公众号看着没有这里方便..

回复
Martentite
2020-07-18 16:43:35

为什么我用了markdown后有一些格矢显示不出来,比如“*”有时可以显示为列表,有时就不转换。

还有markdown似乎不支持latex,一些数学公式不能用。

回复
追梦人物 Martentite
2020-07-19 20:20:08

可能是格式输入不对吧,latex 需要额外的拓展支持。

回复
Martentite 追梦人物
2020-07-20 11:44:15

解决了

回复
mango-star
2020-06-02 00:18:50

你好,请问我在post.body后面加上过滤器 |safe ,但是没有成功,报错 name 'safe' is not defined
下面是view.py中的部分代码。

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

回复
mango-star mango-star
2020-06-02 01:02:15

已解决

回复
黑镜wr mango-star
2020-07-24 17:06:52

您好,我也遇到相同的问题,请问你是怎么解决的?

回复
ning2510 黑镜wr
2020-08-14 21:17:02

应该在templates/blog/detail.html中找到{{post.body}}并更改为{{post.body|safe}}

回复
DoubleZha
2020-05-30 22:01:04

博主你好,出现了以下报错该怎么解决

IntegrityError at /admin/blog/post/add/
NOT NULL constraint failed: blog_post.author_id

回复
DoubleZha DoubleZha
2020-05-30 22:05:01

这是通过 admin 添加文章的时候遇到的问题,之前还是可以添加的。

回复
DoubleZha DoubleZha
2020-05-30 22:50:56

已经解决。

回复
HuangChangHuai DoubleZha
2020-06-14 17:36:54

非空约束,
添加文章的时候作者要指定的,而你的user表当时应该是没有记录,空的

回复
Cercis-chinensis DoubleZha
2021-04-16 19:31:33

怎么解决的呢,我也碰到了

回复
Cercis-chinensis Cercis-chinensis
2021-04-16 19:50:42

已经解决,个人解决方案:在admin.py中的class PostAdmin(admin.ModelAdmin)的fields里添加author

回复
kouyongqi
2020-04-11 16:52:01

代码高亮怎么调试都是显示不出来 崩溃

回复
Bad水 kouyongqi
2020-07-10 12:22:46

代码要手打才可以,直接复制粘贴的不可以

回复
Feko Bad水
2022-07-27 17:54:14

可以复制pycharm编辑器的 可以正常换行

回复
Ipfirn
2020-04-03 16:35:08

博主,你的图片什么时候可以啊

回复
Ipfirn
2020-03-10 15:16:02

我的markdown代码还是没有高亮

回复
Ipfirn
2020-03-10 14:28:12

博主,blog/views.py你删除了index(),所以blog/urls.py也要更改

回复
Ipfirn Ipfirn
2020-03-10 14:43:21

不用删除index()

回复
zws
2020-02-29 19:56:07

markdown.extensions.codehilite
不适应 highlight.js
可以注释掉这块,采用,markdown.extensions.fenced_code
就可以正常生成 pre/code 形式的内容,并被 highlight.js 识别了。

回复
_大西洋暖流_ zws
2020-03-21 21:38:08

用了你的方法,确实可行。

回复
_大西洋暖流_ zws
2020-03-21 21:53:51

有点不对,直接将markdown.extensions.codehilite注释掉,markdown语法就能实现高亮。加不加markdown.extensions.fenced_code没有差别。

回复
YAO ZELIANG zws
2020-04-03 04:03:57

确定可行,点赞

回复
Linpean
2020-02-29 15:25:53

safe 是 django 模板系统中的过滤器(Filter),可以简单地把它看成是一种函数,其作用是作用于模板变量,将模板变量的值变为经过滤器处理过后的值。例如这里 {{ post.body|safe }},本来 {{ post.body }}经模板系统渲染后应该显示 body 本身的值,但是在后面加上 safe 过滤器后,渲染的值不再是 body 本身的值,而是由 safe 函数处理后返回的值。

以上这句话我有点费解:这句话的意思是渲染的值不再是Body本身的值,而是由safe函数处理后返回的值,那上文中的“告诉 django,这段文本是安全的,你什么也不用做”这句话岂不是自相矛盾,下文说进行另外的渲染处理,上文又说Django不需要做额外处理,照我的理解:|通道符后面的函数,会被跳过,那这里加了safe的模板变量,等于没有任何变化,仅仅只是没有被safe进行处理。

回复
追梦人物 Linpean
2020-03-01 08:35:23

可能我的表述有问题。从另一个角度来说。
1. 出于安全考虑,Django 在渲染模板时会对所有变量的值进行转义。默认情况下,变量的 HTML 文本都会被转义,因此在浏览器显示的转义后的内容。
2. 为了让浏览器显示正常的 HTML 内容,需要通过 safe 过滤器将被过滤的值标记为安全的。这样 django 在渲染模板时不会对标记的内容进行转义。

不知道这样是否更好地理解一点?

回复
HuangChangHuai Linpean
2020-06-14 17:48:29

这么说吧,有个不怀好意的家伙到你的博客评论里留下而已的代码,
比如利用script的src可以跨域请求的属性:,他的评论内容:
<script src="恶意服务器/xxxx"></srcipt>
他点击提交了,ok,再访问的时候,django拿到这个评论,要把它插入到一个网页里,
问题是这个内容是"script src=xxx>..."啊,
django以为他就是html代码了,先在服务端把它渲染好,再把网页传回来, 哦豁完蛋,这个网页夹带了私货...
safe这个东西就是为了django在转义渲染的时候,告诉django,这个评论里的"<script...",真的知识个字符串而已,可千万别把它转换成实质的html代码...

回复
HuangChangHuai Linpean
2020-06-14 17:53:13

这个safe可以理解为不是让django变得safe,而是让django的行为别那么unsafe,尼玛作死啥都转义呢?

回复
HuangChangHuai HuangChangHuai
2020-06-14 22:34:48

不好意思我概念说反了

回复
追梦人物 HuangChangHuai
2020-06-17 11:38:47

简单理解就是,safe将指定的值标记为“安全的”,所以django不再对其进行额外处理。django 默认任何值都不安全。

回复
wkz2003
2020-02-13 13:58:28
print('代码高亮')
回复
ZLam
2020-01-28 17:59:47

id | name
---|-----
1 | haha
2 | abc

上面到md解析成下面
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>haha</td>
</tr>
<tr>
<td>2</td>
<td>abc</td>
</tr>
</tbody>
</table>

但在浏览器出来的显示不是表格,请问为什么呢

回复
ZLam ZLam
2020-01-28 18:16:01

看文档解析出来的html是没问题的喔

回复
alone-zd
2020-01-14 21:08:42

你好,麻烦问一下为啥博客中所有上传的图片都无法显示呀

回复
追梦人物 alone-zd
2020-02-25 11:13:59

图床挂挂了,还在修复中...

回复
MagicalMiles
2020-01-11 21:43:09

老哥 边学你这个边开发自己的是不是比较好

回复
冰加可乐__
2019-12-30 14:42:39
def 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})
回复
冰加可乐__ 冰加可乐__
2019-12-30 14:44:14

复制自己的一段代码,然后在上边一行和最后边一行加上```就行了,别直接复制博主的案例,就可以高亮显示行数了

回复
Linpean 冰加可乐__
2020-02-29 15:11:16

+1,先检查自己的代码是否正确,然后从文本编辑器中复制自己的代码,加上标记```即可

回复
yzn2019
2019-11-29 16:01:54

你好 我想咨询一下 按照你的博客 相应js 和 css 文件正常引入 但是并没有高亮显示 和行号 代码是纯手敲的 这应该怎么处理 ?
另外 我这里看不到你博客里的图片 想问一下 如果是一段连在一起未换行的代码 是否可以自动解析成相应的格式?

回复
追梦人物 yzn2019
2019-12-12 16:26:37

看下是不是代码块的录入格式不对?

回复
yzn2019 追梦人物
2019-12-17 08:57:10

相同的一段文字 按照下面评论中提到的mistune库进行解析 就可以正常的显示 行号 高亮都有 但是用markdown只能显示代码块 没有行号高亮效果

回复
zhengzhong0522
2019-11-10 07:03:09

python def 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}) ​
你这个好像也无法解析

回复
zhengzhong0522 zhengzhong0522
2019-11-10 08:11:07

代码都得手打才可以解析。。。。

回复
Ipfirn zhengzhong0522
2020-03-10 16:06:54

你的代码要换行

回复
Feko zhengzhong0522
2022-07-27 17:49:27

复制例子的代码是不换行,不过,手打或者复制其他的就正常换行

回复
zhengzhong0522
2019-11-10 01:17:44
print("Hello")
回复
zizxzy
2019-10-25 12:51:01

博主,你好,我在后台粘贴经过tab缩进的代码段(整体再缩进了一次,为了符合markdown语法),发现在页面上是无法正常显示代码的。

回复
zizxzy zizxzy
2019-10-25 12:53:24

sorry,是可以显示的。

回复
ArdxWe
2019-10-13 15:09:33

为什么我的浏览器post detail里面显示的是html代码?
我查了很久也没有找到问题,期待大佬的答复。

回复
ArdxWe ArdxWe
2019-10-13 15:17:38

sorry, 我没有往后看...

回复
terryoyty
2019-10-08 16:55:58

3.运行

直接像.exe文件那样运行.py 文件,在windows上是不行的,但是,在Mac和Linux上是可以的

直接运行的方法

  1. 在.py文件的第一行加上一个特殊的注释:

    #!/usr/bin/env python3
    print('hello world')
    
  2. 然后通过命令给hello.py以执行权限

    $ chmod a+x hello.py
    
  3. 运行hello.py

    python hello.py
    
回复
terryoyty terryoyty
2019-10-08 16:58:11

奥 刚才没有把缩进粘贴过来 你看 我加了缩进就这个样子了

回复
追梦人物 terryoyty
2019-10-09 06:55:46

嗯,目前使用的 Markdown 解析库,确实存在这个问题。

回复
terryoyty 追梦人物
2019-10-09 16:36:53

缩进代码块的解析问题我换了mistune库可以解决,另外之前直接copy您的代码过来之所以无法解析是因为copy过来的代码在django后台粘贴入库时会在前面加个隐藏空白符,直接删除还无法删除,后把首行```删除后再次手动输入可以解析测试代码段

回复
追梦人物 terryoyty
2019-10-11 07:39:17

嗯,可能 Python Markdown 不支持那样的语法。

回复
terryoyty
2019-10-08 16:52:29

3.运行

直接像.exe文件那样运行.py 文件,在windows上是不行的,但是,在Mac和Linux上是可以的

直接运行的方法

  1. 在.py文件的第一行加上一个特殊的注释:
#!/usr/bin/env python3
print('hello world')
  1. 然后通过命令给hello.py以执行权限
$ chmod a+x hello.py
  1. 运行hello.py
python hello.py
回复
terryoyty terryoyty
2019-10-08 16:53:51

我同样的源码 粘贴过来 你这边是可以显示代码框的,但同样存在缩进和列表序号的问题

回复
terryoyty
2019-10-08 16:49:15

博主你好,我在使用markdown模块时发现在列表中缩进的代码块无法正常显示,没有缩进的代码块可以正常显示,不知您那有没有什么好的解决办法

回复
1352483315_ce6684
2019-10-06 23:37:16

markdown的测试

markdown

回复
Ep流苏
2019-09-22 22:23:11

安装完markdown在引入时出错了是什么原因?
unresolved import 'markdown'

我在pipfile文件中能看到
[packages]
django = "==2.2.3"
markdown = "*"

回复
Ep流苏 Ep流苏
2019-09-22 22:25:34

markdown语法显示为这样:
```md

一级标题

回复
Ep流苏 Ep流苏
2019-09-22 22:26:22
<h1 id="_1">一级标题</h1>>
回复
gMerlinTeach
2019-09-17 11:38:05

我不大明白你说的markdown代码块语法是什么意思,我最初自己写:

class Foo:
    def foo:
        pass

发现不起作用,然后我复制了你的演示代码,发现依然没有正常显示。

回复
gMerlinTeach gMerlinTeach
2019-09-17 11:38:42

在你的博客页面显示是正常的,一眼的代码和格式

回复
gMerlinTeach gMerlinTeach
2019-09-17 11:39:56

这是不是和我在admin界面输入有关系

回复
追梦人物 gMerlinTeach
2019-09-17 11:46:26

我的博客显示没问题说明你的输入应该是正确的。都是用的同一个库。你看下markdown代码高亮的拓展有没有开启,对比一下教程里的代码。

回复
gMerlinTeach 追梦人物
2019-09-17 11:52:06

好的,万分感激

回复
gMerlinTeach
2019-09-17 11:20:45

你好,我的代码块没法换行,直接复制你的示例也不行,Google未搜索到相关问题,希望你能帮我解答一下

回复
追梦人物 gMerlinTeach
2019-09-17 11:30:00

你的代码块使用了 Markdown 的代码块语法吗?

回复
管世勋
2019-09-02 15:36:55

你好,我这里代码一直无法高亮,查了下 highlight.js 的文档, 上面说会自动检测 <pre><code></code></pre> 中的代码,但是使用 markdown 库生成的 html 只有 pre 标签, 没有 code 标签, 我调了很久,也无法高亮, 我的代码跟你的没有不同,但是你这里就可以高亮, 现在不知道是什么问题

回复
追梦人物 管世勋
2019-09-09 16:20:34

代码高亮的样式文件引入了吗?不仅仅是js,还要引入一个 css 样式文件。

回复
boyang 管世勋
2019-10-15 22:44:05

我也遇到和你一样的问题,请问你解决了吗?
不过好像这里就可以

python def 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}) ​

回复
cao-weiwei 管世勋
2021-02-01 10:24:35

请问解决了吗?同样的问题。以及导入了CSS和JS文件。而且我查看了不加safe过滤条件时的html标签,我这里是这样的

<p><code>python def 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})</code></p>

回复
cao-weiwei cao-weiwei
2021-02-01 10:51:49

已经解决。
因为我从解析出来的html标签来看貌似不正确,所以怀疑🤨是markdown解析库的问题。然后更换了markdown2的解析库。再搭配博主的css和js代码可一正常使用了。view函数参考代码如下:

def blog_post_detail(request, post_id):
    blog_post = get_object_or_404(BlogPost, pk=post_id)
    # blog_post.text = markdown.markdown(
    #     blog_post.text,
    #     extentions=[
    #         'markdown.extensions.extra',
    #         'markdown.extensions.codehilite',
    #         # 'markdown.extensions.fenced_code',
    #         'markdown.extensions.toc',
    #     ])
    blog_post.text = markdown2.markdown(
        blog_post.text,
        extras=[
            'fenced-code-blocks',
            'code-color',
            'code-friendly'
        ]
    )

    context = {
        'website_title': 'White & Black',
        'blog_post': blog_post,
    }

    return render(request, 'blog/blog_post_detail.html', context)

markdown2的GitHub地址:🔗
markdown扩展:Implemented Extras

回复