1
+ import re
2
+
1
3
import markdown
2
4
from django .contrib .auth .models import User
3
5
from django .db import models
4
6
from django .urls import reverse
5
7
from django .utils import timezone
8
+ from django .utils .functional import cached_property
6
9
from django .utils .html import strip_tags
10
+ from django .utils .text import slugify
11
+ from markdown .extensions .toc import TocExtension
12
+
13
+
14
+ def generate_rich_content (value ):
15
+ md = markdown .Markdown (
16
+ extensions = [
17
+ "markdown.extensions.extra" ,
18
+ "markdown.extensions.codehilite" ,
19
+ # 记得在顶部引入 TocExtension 和 slugify
20
+ TocExtension (slugify = slugify ),
21
+ ]
22
+ )
23
+ content = md .convert (value )
24
+ m = re .search (r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>' , md .toc , re .S )
25
+ toc = m .group (1 ) if m is not None else ""
26
+ return {"content" : content , "toc" : toc }
7
27
8
28
9
29
class Category (models .Model ):
@@ -16,10 +36,11 @@ class Category(models.Model):
16
36
Django 内置的全部类型可查看文档:
17
37
https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-types
18
38
"""
19
- name = models .CharField ('分类名' , max_length = 100 )
39
+
40
+ name = models .CharField ("分类名" , max_length = 100 )
20
41
21
42
class Meta :
22
- verbose_name = '分类'
43
+ verbose_name = "分类"
23
44
verbose_name_plural = verbose_name
24
45
25
46
def __str__ (self ):
@@ -31,10 +52,11 @@ class Tag(models.Model):
31
52
标签 Tag 也比较简单,和 Category 一样。
32
53
再次强调一定要继承 models.Model 类!
33
54
"""
34
- name = models .CharField ('标签名' , max_length = 100 )
55
+
56
+ name = models .CharField ("标签名" , max_length = 100 )
35
57
36
58
class Meta :
37
- verbose_name = '标签'
59
+ verbose_name = "标签"
38
60
verbose_name_plural = verbose_name
39
61
40
62
def __str__ (self ):
@@ -47,19 +69,19 @@ class Post(models.Model):
47
69
"""
48
70
49
71
# 文章标题
50
- title = models .CharField ('标题' , max_length = 70 )
72
+ title = models .CharField ("标题" , max_length = 70 )
51
73
52
74
# 文章正文,我们使用了 TextField。
53
75
# 存储比较短的字符串可以使用 CharField,但对于文章的正文来说可能会是一大段文本,因此使用 TextField 来存储大段文本。
54
- body = models .TextField ('正文' )
76
+ body = models .TextField ("正文" )
55
77
56
78
# 这两个列分别表示文章的创建时间和最后一次修改时间,存储时间的字段用 DateTimeField 类型。
57
- created_time = models .DateTimeField (' 创建时间' , default = timezone .now )
58
- modified_time = models .DateTimeField (' 修改时间' )
79
+ created_time = models .DateTimeField (" 创建时间" , default = timezone .now )
80
+ modified_time = models .DateTimeField (" 修改时间" )
59
81
60
82
# 文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错。
61
83
# 指定 CharField 的 blank=True 参数值后就可以允许空值了。
62
- excerpt = models .CharField ('摘要' , max_length = 200 , blank = True )
84
+ excerpt = models .CharField ("摘要" , max_length = 200 , blank = True )
63
85
64
86
# 这是分类与标签,分类与标签的模型我们已经定义在上面。
65
87
# 我们在这里把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。
@@ -70,22 +92,22 @@ class Post(models.Model):
70
92
# 同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True。
71
93
# 如果你对 ForeignKey、ManyToManyField 不了解,请看教程中的解释,亦可参考官方文档:
72
94
# https://docs.djangoproject.com/en/2.2/topics/db/models/#relationships
73
- category = models .ForeignKey (Category , verbose_name = '分类' , on_delete = models .CASCADE )
74
- tags = models .ManyToManyField (Tag , verbose_name = '标签' , blank = True )
95
+ category = models .ForeignKey (Category , verbose_name = "分类" , on_delete = models .CASCADE )
96
+ tags = models .ManyToManyField (Tag , verbose_name = "标签" , blank = True )
75
97
76
98
# 文章作者,这里 User 是从 django.contrib.auth.models 导入的。
77
99
# django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程,User 是 Django 为我们已经写好的用户模型。
78
100
# 这里我们通过 ForeignKey 把文章和 User 关联了起来。
79
101
# 因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。
80
- author = models .ForeignKey (User , verbose_name = '作者' , on_delete = models .CASCADE )
102
+ author = models .ForeignKey (User , verbose_name = "作者" , on_delete = models .CASCADE )
81
103
82
104
# 新增 views 字段记录阅读量
83
105
views = models .PositiveIntegerField (default = 0 , editable = False )
84
106
85
107
class Meta :
86
- verbose_name = '文章'
108
+ verbose_name = "文章"
87
109
verbose_name_plural = verbose_name
88
- ordering = [' -created_time' ]
110
+ ordering = [" -created_time" ]
89
111
90
112
def __str__ (self ):
91
113
return self .title
@@ -95,10 +117,9 @@ def save(self, *args, **kwargs):
95
117
96
118
# 首先实例化一个 Markdown 类,用于渲染 body 的文本。
97
119
# 由于摘要并不需要生成文章目录,所以去掉了目录拓展。
98
- md = markdown .Markdown (extensions = [
99
- 'markdown.extensions.extra' ,
100
- 'markdown.extensions.codehilite' ,
101
- ])
120
+ md = markdown .Markdown (
121
+ extensions = ["markdown.extensions.extra" , "markdown.extensions.codehilite" ,]
122
+ )
102
123
103
124
# 先将 Markdown 文本渲染成 HTML 文本
104
125
# strip_tags 去掉 HTML 文本的全部 HTML 标签
@@ -110,8 +131,20 @@ def save(self, *args, **kwargs):
110
131
# 自定义 get_absolute_url 方法
111
132
# 记得从 django.urls 中导入 reverse 函数
112
133
def get_absolute_url (self ):
113
- return reverse (' blog:detail' , kwargs = {'pk' : self .pk })
134
+ return reverse (" blog:detail" , kwargs = {"pk" : self .pk })
114
135
115
136
def increase_views (self ):
116
137
self .views += 1
117
- self .save (update_fields = ['views' ])
138
+ self .save (update_fields = ["views" ])
139
+
140
+ @property
141
+ def toc (self ):
142
+ return self .rich_content .get ("toc" , "" )
143
+
144
+ @property
145
+ def body_html (self ):
146
+ return self .rich_content .get ("content" , "" )
147
+
148
+ @cached_property
149
+ def rich_content (self ):
150
+ return generate_rich_content (self .body )
0 commit comments