1.Rss簡介
博客提供 RSS 訂閱應該是標配,這樣讀者就可以通過一些聚合閱讀工具訂閱你的博客,時時查看是否有文章更新,而不必每次都跳轉到博客上來查看。現在我們就來為博客添加 RSS 訂閱功能。
RSS(Really Simple Syndication)是一種描述和同步網站內容的格式,它采用 XML 作為內容傳遞的格式。簡單來說就是網站可以把內容包裝成符合 RSS 標準的 XML 格式文檔。一旦網站內容符合一個統一的規范,那么人們就可以開發一種讀取這種規范化的 XML 文檔的工具來聚合各大網站的內容。例如一個讀者可能關注了很多的博客網站,如果這些博客網站都支持 RSS 訂閱的話,他就只需要一個聚合閱讀器訂閱這些博客,就可以在聚合器工具里看到全部博客的更新內容,而不必再分別訪問各個博客去看有沒有內容更新了。
更多 RSS 的介紹,可參考百度。
2.使用 Django Feed 類
根據以上對 RSS 的介紹,我們可以發現關鍵的地方就是根據網站的內容生成規范化的 XML 文檔,幸運的是,Django 已經內置了一些生成這個文檔的方法,下面就使用這些方法來創建 RSS 訂閱文檔。
1)首先我們在 blog 應用的根目錄下(models.py 所在目錄)新建一個 feeds.py 文件以存放和 RSS 功能相關的代碼。讓后在 feeds.py 中寫入如下代碼:
文件位置:blog/feeds.py
from django.contrib.syndication.views import Feedfrom .models import Postclass AllPostsRssFeed(Feed):# 顯示在聚合閱讀器上的標題title = "HelloDjango-blog-tutorial"# 通過聚合閱讀器跳轉到網站的地址link = "/"# 顯示在聚合閱讀器上的描述信息description = "HelloDjango-blog-tutorial 全部文章"# 需要顯示的內容條目def items(self):return Post.objects.all()# 聚合器中顯示的內容條目的標題def item_title(self, item):return "[%s] %s" % (item.category, item.title)# 聚合器中顯示的內容條目的描述def item_description(self, item):return item.body_html
代碼應該不難理解,主要就是指定要生成的 XML 文檔內容。各個屬性和方法的含義已在代碼中注釋,你只需把相關的內容替換成符合你博客的描述即可。
需要注意一點的是def item_description(self, item)這個方法。item 是文章 (Post 模型的實例),聚合內容的描述我們返回了 body_html 屬性的值。模型中原本使用 body 屬性存儲博客文章的內容,但是這些內容是以 Markdown 格式的,并非所有的聚合內容閱讀器都支持 Markdown 格式的解析,因此我們返回的是已經解析后的 HTML 格式內容。但 Post 模型原來并沒有 body_html 這個屬性的,那它是哪里來的呢?
2)回顧一下Post模型,我們來給 Post 模型定義幾個屬性,以便方便地獲取解析后的內容和目錄:
文件位置:blog/models.py
from django.utils.functional import cached_propertyclass Post(models.Model):# 省略其它代碼@propertydef toc(self):return self.rich_content.get("toc", "")@propertydef body_html(self):return self.rich_content.get("content", "")@cached_propertydef rich_content(self):return generate_rich_content(self.body)
首先看到 rich_content 這個方法,它返回的是 generate_rich_content 函數調用后的結果,即將 body 屬性的值經 Markdown 解析后的內容。但要注意的是我們使用了 django 提供的 cached_property 裝飾器,這個裝飾器和 Python 內置的 property 裝飾器功能一樣,可以將方法轉為屬性,這樣就能夠以屬性訪問的方式獲取方法返回的值,不過 cached_property 進一步提供緩存功能,它將被裝飾方法調用返回的值緩存起來,下次訪問時將直接讀取緩存內容,而不需重復執行方法獲取返回結果。例如對博客文章內容的 Markdown 解析是比較耗時的,而解析的結果可能被多次訪問,因此將其緩存起來能起到優化作用。
為了更方便地獲取文章的 HTML 格式的內容和目錄,我們進一步將 generate_rich_content 返回的值放到 toc 和 body_html 兩個屬性中,這里兩個屬性都從 rich_content 中取值,cached_property 的作用就發揮出來了。
3)接下來講解上邊調用的generate_rich_content 函數,回顧一下 PostDetailView 這個視圖:
def get_object(self, queryset=None):# 覆寫 get_object 方法的目的是因為需要對 post 的 body 值進行渲染post = super().get_object(queryset=None)md = markdown.Markdown(extensions=['markdown.extensions.extra','markdown.extensions.codehilite',# 記得在頂部引入 TocExtension 和 slugifyTocExtension(slugify=slugify),])post.body = md.convert(post.body)m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)post.toc = m.group(1) if m is not None else ''return post
我們在獲取博客文章 post 的詳細內容的視圖中,使用 markdown 這個 Python 庫在解析 post.body 這個屬性的值。這個解析的過程其實在很多地方都可能用到,不應該寫死在 PostDetailView 這個視圖里,所以我們先將 PostDetailView 視圖的 get_object 方法解析 Markdown 內容的過程封裝為一個函數,暫且將其放在 blog/models.py 中。
文件位置:blog/models.py
def generate_rich_content(value):md = markdown.Markdown(extensions=["markdown.extensions.extra","markdown.extensions.codehilite",# 記得在頂部引入 TocExtension 和 slugifyTocExtension(slugify=slugify),])content = md.convert(value)m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)toc = m.group(1) if m is not None else ""return {"content": content, "toc": toc}
解析過程和原來一樣,只是將解析得到的 HTML 內容(content)和目錄(toc)放在一個字典里返回。
3.添加 URL
接下來就是指定 URL 模式,讓人們訪問這個 URL 后就可以看到 Feed 生成的內容。通常 RSS 的 URL 配置直接寫在項目的 urls.py 文件里,即打開 blogproject/urls.py 文件,添加如下代碼:
文件位置:blogproject/urls.py
from blog.feeds import AllPostsRssFeedurlpatterns = [path('admin/', admin.site.urls),path('', include('blog.urls')),path('', include('comments.urls')),# 記得在頂部引入 AllPostsRssFeedpath('all/rss/', AllPostsRssFeed(), name='rss'),
]
4.修改模板
簡單修改一下 templates/base.html 模板,把 RSS 的 URL 添加到模板中:
<div class="rss"><a href="{% url 'rss' %}"><span class="ion-social-rss-outline"></span> RSS 訂閱</a>
</div>
此外,由于將 Markdown 解析的內容放在了 body_html 屬性,將之前博客文章詳情模板里獲取文章內容的代碼也做相應調整:
文件位置:templates/blog/detail.html
<div class="entry-content clearfix">{{ post.body_html|safe }}
</div>
運行開發服務器,在側邊欄點擊 RSS 訂閱的鏈接,就跳轉到 /all/rss/,你會看到這對亂碼一樣的東西,這就是生成的 RSS 標準文檔,當然這個文檔不是給你讀的,而是給 RSS 聚合閱讀器工具讀的。
5.RSS 測試插件
可以在本地測試一下訂閱效果,我使用的 Chrome 瀏覽器,安裝了一個 RSS Feed Reader 的應用,如果你也使用的 Chrome 瀏覽器,可以從應用商店添加它,然后就可以在本地測試訂閱效果了。如果是其它瀏覽器,自行查找相關 RSS 閱讀插件即可。
可以看到訂閱成功了,訂閱界面顯示的信息就是我們在 AllPostsRssFeed 類中指定的相關信息。大功告成,現在任何人都可以訂閱我們的博客了!