教程来源 杜塞-django-vue系列
博客链接 传送门
博客文章通常是需要排版的,否则难以凸显标题、正文、注释等内容之间的区别。对于博客来说,主流的排版就是markdown语法了。
严格来说,Markdown 是一种排版标注规则。他将两个星号包裹的文字标注为重要文本(通常是粗体),比如原始文本中的**Money**
,在Markdown语法中就被渲染为粗体,也就是Money,类似的还有斜体、代码块、表格、公式、标题等注释。
教程来源杜塞-django-vue系列
Markdown,可自行了解。
“渲染”Markdown也就是把原始的文本中的注释转化为前端中真正被用户看到的HTML排版文字。渲染过程可以在前端,也可以在后端吗,本文就学习使用后端渲染,以便理解DRF的相关知识。、
模型和视图
为了文章的Markdown正文渲染为html标签,首先给文章模型添加一个get_md()
方法:
# article/models.py
# 博客文章
class Article(models.Model):
def get_md(self):
md = Markdown(
extensions=[
# 包含 缩写、表格等常用扩展
'markdown.extensions.extra',
# 语法高亮扩展
'markdown.extensions.codehilite',
# 目录扩展
'markdown.extensions.toc',
]
)
md_body = md.convert(self.body)
return md_body, md.toc
方法返回了两个元素,一个是已渲染完成的html正文和目录。
这些渲染后的数据,在文章详情页接口是需要提供的,但是在列表接口却没有必要,因此我们又要用到视图集根据请求方式动态获取序列化器的技术了:
# article/views.py
from .serializers import ArticleDetailSerializer
class ArticleViewSet(viewsets.ModelViewSet):
···
def get_serializer_class(self):
if self.action =='list':
return ArticleSerializer
else:
return ArticleDetailSerializer
序列化器ArticleDetailSerializer
我们还是放在下一步。
序列化器
因为文章列表接口和文章详情接口只有一点点返回字段的区别,绝大多数功能还是一样的,所以,我们处于简洁代码考虑。我们选择继承。
# article/serializers.py
# 把原有的ArticleSerializer改为ArticleBaseSerialize移除class Meta:
class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer):
"""文章序列化器"""
author = ···
tags =···
category = ···
category_id =···
def vaildate_category_id(self, value):
···
def to_internal_value(self, data):
···
# 新增ArticleDetailSerializer 继承ArticleBaseSerializer,添加class Meta:
class ArticleSerializer(ArticleBaseSerializer):
class Meta:
model = Article
fields = '__all__'
extra_kwargs = {'body':{'write_only':True}}
与Django表单类似,你可以继承扩展和重写序列化器。就像上面代码一样,在父类上声明一组通用字段。然后在许多序列化器中使用他们。
但是内部类class Meta
比较特殊,他不会隐式从父类继承,虽然有办法隐式继承,但不推荐,在子类中声明更为清晰。
另外,你如果觉得在列表接口连body
字段也不需要的显示的话,你可以传入extra_kwargs
使其变为尽可以写而不显示的字段。
把这些代码重构
的准备做好了。那就可以开始新的ArticleDetailSerializer
了:
# article/serializer.py
# 继承ArticleBaseSerializer
class ArticleDetailSerializer(ArticleBaseSerializer):
# 渲染后的正文
body_html = serializers.SerializerMethodField()
# 渲染后的目录
toc_html = serializers.SerializerMethodField()
def get_body_html(self, obj):
return obj.get_md()[0]
def get_toc_html(self, obj):
return obj.get_md()[1]
class Meta:
model = Article
fields = "__all__"
body_html
、toc_html
这两个渲染后的字段是经过加工处理后的数据,不存在与原始数据中。为了将这个类只读的附加字段添加进接口里,我们就用到了SerializerMethodField
字段。比如说上面代码中的get_body
字段,让就会自动去调用get_body_html()
方法,并将其返回的结果作为序列化的数据。方法中的obj参数就是序列化化获取的mldel实例,叶菊是文章对象。
这样就完成了,接下来就是测试。
测试
>http http://127.0.0.1:8000/api/article/
HTTP/1.1 200 OK
···
{
"count": 9,
"next": "http://127.0.0.1:8000/api/article/?page=2",
"previous": null,
"results": [
{
"author": {
"date_joined": "2021-06-13T14:58:00",
"id": 3,
"last_login": null,
"username": "xianwei"
},
"category": null,
"created": "2021-06-17T20:52:34.325383",
"tags": [],
"title": "category_11",
"updated": "2021-06-17T20:54:35.888087",
"url": "http://127.0.0.1:8000/api/article/16/"
},
{
"author": {
"date_joined": "2021-06-13T14:58:00",
"id": 3,
"last_login": null,
"username": "xianwei"
},
"category": null,
"created": "2021-06-17T20:51:53.437601",
"tags": [
"python",
"java"
],
"title": "category_1",
"updated": "2021-06-18T17:16:01.303651",
"url": "http://127.0.0.1:8000/api/article/15/"
}
]
}
请求文章列表没有变化,这就是动态选取序列化器起到了作用。
我再请求文章详情:
>http http://127.0.0.1:8000/api/article/15/
HTTP/1.1 200 OK
···
{
"author": {
"date_joined": "2021-06-13T14:58:00",
"id": 3,
"last_login": null,
"username": "xianwei"
},
"body": "aaa",
"body_html": "<p>aaa</p>",
"category": null,
"created": "2021-06-17T20:51:53.437601",
"tags": [
"python",
"java"
],
"title": "category_1",
"toc_html": "<div class=\"toc\">\n<ul></ul>\n</div>\n",
"updated": "2021-06-18T17:16:01.303651",
"url": "http://127.0.0.1:8000/api/article/15/"
}
我们可以发现,多了body_html
、toc_html
两个字段。并且都是html标签,说明已经被渲染完成了,而且本身body
字段依然存在,这就说明没有替换到原来额的内容,而是经过后端渲染了。
原有文章数据存在,就出现了一个问题,有时候你可能出于版权方面的考虑不愿意将原始的 Markdown 文章数据给任意用户,那么这里只要做一次鉴权,根据用户的权限选用不同的序列化器即可。(非管理员不返回原始文章数据)。
来自杜塞博主提醒:
记得原始文本应该用 Markdown 语法编写。成功的话 body_html 字段返回的是带有 html 标签的文本。
代码重构得太早可能会导致某些不必要的抽象,太晚又可能堆积太多”屎山“而无从下手。理想情况下的重构是随着项目的开发同时进行的,在合适的节点进行合适的抽象,看着代码逐渐规整,你也会相当有成就感。