博客文章需要排版,,否則難以凸顯標題,、正文,、注釋等內(nèi)容之間的區(qū)別,。作為博客寫手來說,,比較流行且好用的排版是采用 Markdown 語法,。
嚴格來說, Markdown 是一種排版標注規(guī)則,。它將兩個星號包裹的文字標注為重要文本(通常也就是粗體字),比如原始文本中的 **Money**
,,在 Markdown 語法中應該被”渲染“為粗體,,也就是 Money 。類似的還有斜體,、代碼塊,、表格、公式等注釋,,就請讀者自行了解了,。
關于 Markdown[1] 的簡單介紹。
”渲染“ Markdown 也就是把原始文本中的注釋轉(zhuǎn)化為前端中真正被用戶看到的 HTML 排版文字,。渲染過程可以在前端也可以在后端,,本文將使用后端渲染,,以便你理解 DRF 的相關知識。
模型和視圖
為了將博文的 Markdown 正文渲染為 html 標簽,,首先給文章模型添加一個 get_md()
方法:
# article/models.py
from markdown import Markdown
...
class Article(models.Model):
...
# 新增方法,,將 body 轉(zhuǎn)換為帶 html 標簽的正文
def get_md(self):
md = Markdown(
extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
]
)
md_body = md.convert(self.body)
# toc 是渲染后的目錄
return md_body, md.toc
方法返回了包含了兩個元素的元組,分別為已渲染為 html 的正文和目錄,。
這些渲染后的數(shù)據(jù),,在文章詳情接口是需要的,但是在列表接口卻沒太有必要,,因此又要用到視圖集根據(jù)請求方式動態(tài)獲取序列化器的技術了:
# article/views.py
from article.serializers import ArticleDetailSerializer
...
# 新增 get_serializer_class() 方法
class ArticleViewSet(viewsets.ModelViewSet):
...
def get_serializer_class(self):
if self.action == 'list':
return ArticleSerializer
else:
return ArticleDetailSerializer
序列化器 ArticleDetailSerializer
還沒有寫,,這就來搞定它。
序列化器
因為文章列表接口和詳情接口只有一點點返回字段的區(qū)別,,其實大部分功能還是一樣的,。那么被面向?qū)ο缶幊萄盏哪悖鞍阉橄蟪筛割?!?應該可以脫口而出:
# article/serializers.py
...
# 將已有的 ArticleSerializer 里的東西全部挪到這個 ArticleBaseSerializer 里來
# 除了 Meta 類保留
class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer):
author = ...
category = ...
category_id = ...
tags = ...
def to_internal_value(self, data):
...
def validate_category_id(self, value):
...
# 保留 Meta 類
# 將父類改為 ArticleBaseSerializer
class ArticleSerializer(ArticleBaseSerializer):
class Meta:
model = Article
fields = '__all__'
extra_kwargs = {'body': {'write_only': True}}
與 Django 表單類似,,你可以繼承擴展和重用序列化器。就像上面的代碼一樣,,在父類上聲明一組通用的字段或方法,,然后在許多序列化程序中使用它們。
但是內(nèi)部類 class Meta
比較特殊,,它不會隱式從父類繼承,。雖然有辦法讓它隱式繼承,但這是不被推薦的,,你應該顯式聲明它,,以使得序列化器的行為更清晰。
另外,,如果你覺得在列表接口連 body
字段也不需要顯示的話,,你可以傳入 extra_kwargs
使其變成僅可寫卻不顯示的字段。
把這些代碼重構(gòu)的準備工作都搞定之后,,就可以正式寫這個新的 ArticleDetailSerializer
了:
# article/serializers.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
這兩個渲染后的字段是經(jīng)過加工后的數(shù)據(jù),不存在于原始的數(shù)據(jù)中,。為了將這類只讀的附加字段添加到接口里,,就可以用到 SerializerMethodField()
字段了。比如說上面代碼中的 body_html
字段,,它會自動去調(diào)用 get_body_html()
方法,,并將其返回結(jié)果作為需要序列化的數(shù)據(jù)。方法中的 obj
參數(shù)是序列化器獲取到的 model 實例,,也就是文章對象了,。
這樣就大功告成了,,讀者自己測試一下,順利的話詳情接口就可以返回 Markdown 渲染后的數(shù)據(jù)了,。
記得原始文本應該用 Markdown 語法編寫,。成功的話 body_html
字段返回的是帶有 html 標簽的文本。
代碼重構(gòu)得太早可能會導致某些不必要的抽象,,太晚又可能堆積太多”屎山“而無從下手,。理想情況下的重構(gòu)是隨著項目的開發(fā)同時進行的,在合適的節(jié)點進行合適的抽象,,看著代碼逐漸規(guī)整,,你也會相當有成就感。
另一個問題是,,有時候你可能出于版權(quán)方面的考慮不愿意將原始的 Markdown 文章數(shù)據(jù)給任意用戶,,那么這里只要做一次鑒權(quán),根據(jù)用戶的權(quán)限選用不同的序列化器即可,。(非管理員不返回原始文章數(shù)據(jù))
參考資料
[1]關于 Markdown: https://www./article/20/