摘要:本教程內容已過時,更新版教程請訪問博客開發入門教程。當分頁較多時,總是顯示當前頁及其前幾頁和后幾頁的頁碼教程中使用的是兩頁,其他頁碼用省略號代替。
本教程內容已過時,更新版教程請訪問: django 博客開發入門教程。
摘要:前兩期教程我們實現了博客的 Model 部分,以及 Blog 的首頁視圖 IndexView,詳情頁面 DetailView,以及分類頁面 CategoryView,前兩期教程鏈接請戳:
Django 學習小組:博客開發實戰第一周教程 —— 編寫博客的 Model 與首頁面
Django 學習小組:博客開發實戰第二周教程 —— 博客詳情頁面和分類頁面
本周我們將繼續完善我們的個人博客,來實現分頁和代碼高亮的功能。
提示:在閱讀教程的過程中,如有任何問題請訪問我們項目的 GithHub 或評論留言以獲取幫助,本教程的相關代碼已全部上傳在 Github。如果你對我們的教程或者項目有任何改進建議,請您隨時告知我們。更多交流請加入我們的郵件列表?django_study@groups.163.com 和關注我們在 Github 上的項目。
本文首發于編程派微信公眾號:編程派(微信號:codingpy)是一個專注Python編程的公眾號,每天更新有關Python的國外教程和優質書籍等精選干貨,歡迎關注。
我們的數據庫中會有越來越多的文章,把它們全部用一個列表顯示在首頁好像不太合適,如果顯示一定數量的文章,比如8篇,這就需要用到分頁功能。
Django提供了一些類來幫助你管理分頁的數據 -- 也就是說,數據被分在不同頁面中,并帶有“上一頁/下一頁”標簽。這些類位于django/core/paginator.py中。
文章過多,為了提高用戶體驗,一次只展示部分文章,為用戶提供一個分頁功能,就像下面這樣:
比較完善的分頁效果,應該是這樣的:
用戶在哪一頁,則當前頁號高亮以提示用戶所在位置,比如上圖顯示用戶正處在第二頁。
當用戶所處的位置還有上一頁時,顯示上一頁按鈕;當還有下一頁時,顯示下一頁按鈕,否則不顯示。
當分頁較多時,總是顯示當前頁及其前幾頁和后幾頁的頁碼(教程中使用的是兩頁),其他頁碼用省略號代替。
總是顯示第一頁和最后一頁的頁碼。
根據上面的需求,我們開始編寫相應代碼。
關于分頁需要使用到的的 API ,Django 官方文檔對此有十分詳細的介紹,它還給出了一個完整示例,讀懂它的代碼后仿照它即可實現基本的分頁功能。請參考官方文檔對于分頁的示例,如果你不習慣英文的話,也可以參照網友的翻譯版本Django 中文文檔:分頁。下面就根據官方的示例來實現我們的需求。
盡管可以把分頁邏輯直接寫在視圖內,但是為了通用性,我們使用一點點 Django 更加高級的技巧——模板標簽(TemplateTags)。分頁功能的實現有很多第三方 APP 可以直接使用,但是為了學習 Django 的知識,所以我們自己實現一個。這些第三方 APP 基本都是使用的模板標簽,因此這可能是一種比較好的實踐。
為了使用模板標簽,Django 要求我們先建立一個 templatetags 文件夾,并在里面加上 __init__.py文件以指示 python 這是一個模塊(python 把含有該問價的文件夾當做一個模塊,具體請參考任何一個關于 python 模塊的教程)。并且 templatetags 文件夾和你的 model.py,views.py 文件是同級的,也就是說你的目錄結構看起來應該是這樣:
polls/ __init__.py models.py templatetags/ __init__.py poll_extras.py views.py
(這個目錄結構引自官方文檔,關于詳細的模板標簽的介紹請參考官方文檔:custom template tags,不一定全部讀懂,但還是推薦花幾十分鐘掃一遍明白其大致說了什么)。
在 templatetags 目錄下建立一個 paginate_tags .py 文件,準備工作做完,結合 Django 的模板系統,我們來看看該如何編寫我們的程序。
首先來回顧一下 Django 的模板系統是如何工作的,回想一下視圖函數的工作流程,視圖函數接收一個 Http 請求,經過一系列處理,通常情況下其會渲染某個模板文件,把模板文件中的一些用 {{ }} 包裹的變量替換成從該視圖函數中相應變量的值。事實上在此過程中 Django 悄悄幫我們做了一些事情,它把視圖函數中的變量的值封裝在了一個 Context (一般翻譯成上下文)對象中,只要模板文件中的變量在 Context 中有對應的值,它就會被相應的值替換。因此,我們的程序可以這樣做:首先把取到的文章列表(官方術語是一個 queryset)分頁,用戶請求第幾頁,我們就把第幾頁的文章列表傳遞給模板文件;另外還要根據上面的需求傳遞頁碼值給模板文件,這樣只要把模板文件中的變量替換成我們傳遞過去的值,那么就達到本文開篇處那樣的分頁顯示效果了。
開始編寫我們的代碼了,慣例依然是先看代碼,然后我們再逐行解釋:
paginate_tags.py from django import template from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage register = template.Library() @register.simple_tag(takes_context=True) def paginate(context, object_list, page_count): left = 3 right = 3 paginator = Paginator(object_list, page_count) page = context["request"].GET.get("page") try: object_list = paginator.page(page) context["current_page"] = int(page) pages = get_left(context["current_page"], left, paginator.num_pages) + get_right(context["current_page"], right, paginator.num_pages) except PageNotAnInteger: object_list = paginator.page(1) context["current_page"] = 1 pages = get_right(context["current_page"], right, paginator.num_pages) except EmptyPage: object_list = paginator.page(paginator.num_pages) context["current_page"] = paginator.num_pages pages = get_left(context["current_page"], left, paginator.num_pages) context["article_list"] = object_list context["pages"] = pages context["last_page"] = paginator.num_pages context["first_page"] = 1 try: context["pages_first"] = pages[0] context["pages_last"] = pages[-1] + 1 except IndexError: context["pages_first"] = 1 context["pages_last"] = 2 return "" # 必須加這個,否則首頁會顯示個None def get_left(current_page, left, num_pages): if current_page == 1: return [] elif current_page == num_pages: l = [i - 1 for i in range(current_page, current_page - left, -1) if i - 1 > 1] l.sort() return l l = [i for i in range(current_page, current_page - left, -1) if i > 1] l.sort() return l def get_right(current_page, right, num_pages): if current_page == num_pages: return [] return [i + 1 for i in range(current_page, current_page + right - 1) if i < num_pages - 1]
首先讓我們來看看整個分頁程序的執行過程,模板標簽本質上來說就是一個 python 函數而已,只是該函數可以被用在 Django 的模板系統里面。函數就是接受參數,返回一個值。例如我們這里定義的 def paginate(context, object_list, page_count): 分頁函數,它接收了這么一些參數,經過各種處理,最終返回了 None 。
逐行解釋:
paginate.py from django import template from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage # 這是分頁功能涉及的一些類和異常,官方文檔對此有詳細介紹。當然從命名也可以直接看出它們的用途:Paginator(分頁),PageNotAnInteger(頁碼不是一個整數異常),EmptyPage(空的頁碼號異常) register = template.Library() # 這是定義模板標簽要用到的 @register.simple_tag(takes_context=True) # 這個裝飾器表明這個函數是一個模板標簽,takes_context = True 表示接收上下文對象,就是前面所說的封裝了各種變量的 Context 對象。 def paginate(context, object_list, page_count): # context是Context 對象,object_list是你要分頁的對象,page_count表示每頁的數量 left = 3 # 當前頁碼左邊顯示幾個頁碼號 -1,比如3就顯示2個 right = 3 # 當前頁碼右邊顯示幾個頁碼號 -1 paginator = Paginator(object_list, page_count) # 通過object_list分頁對象 page = context["request"].GET.get("page") # 從 Http 請求中獲取用戶請求的頁碼號 try: object_list = paginator.page(page) # 根據頁碼號獲取第幾頁的數據 context["current_page"] = int(page) # 把當前頁封裝進context(上下文)中 pages = get_left(context["current_page"], left, paginator.num_pages) + get_right(context["current_page"], right, paginator.num_pages) # 調用了兩個輔助函數,根據當前頁得到了左右的頁碼號,比如設置成獲取左右兩邊2個頁碼號,那么假如當前頁是5,則 pages = [3,4,5,6,7],當然一些細節需要處理,比如如果當前頁是2,那么獲取的是pages = [1,2,3,4] except PageNotAnInteger: # 異常處理,如果用戶傳遞的page值不是整數,則把第一頁的值返回給他 object_list = paginator.page(1) context["current_page"] = 1 # 當前頁是1 pages = get_right(context["current_page"], right, paginator.num_pages) except EmptyPage: # 如果用戶傳遞的 page 值是一個空值,那么把最后一頁的值返回給他 object_list = paginator.page(paginator.num_pages) context["current_page"] = paginator.num_pages # 當前頁是最后一頁,num_pages的值是總分頁數 pages = get_left(context["current_page"], left, paginator.num_pages) context["article_list"] = object_list # 把獲取到的分頁的數據封裝到上下文中 context["pages"] = pages # 把頁碼號列表封裝進去 context["last_page"] = paginator.num_pages # 最后一頁的頁碼號 context["first_page"] = 1 # 第一頁的頁碼號為1 try: # 獲取 pages 列表第一個值和最后一個值,主要用于在是否該插入省略號的判斷,在模板文件中將會體會到它的用處。注意這里可能產生異常,因為pages可能是一個空列表,比如本身只有一個分頁,那么pages就為空,因為我們永遠不會獲取頁碼為1的頁碼號(至少有1頁,1的頁碼號已經固定寫在模板文件中) context["pages_first"] = pages[0] context["pages_last"] = pages[-1] + 1 # +1的原因是為了方便判斷,在模板文件中將會體會到其作用。 except IndexError: context["pages_first"] = 1 # 發生異常說明只有1頁 context["pages_last"] = 2 # 1 + 1 后的值 return "" # 必須加這個,否則首頁會顯示個None def get_left(current_page, left, num_pages): """ 輔助函數,獲取當前頁碼的值得左邊兩個頁碼值,要注意一些細節,比如不夠兩個那么最左取到2,為了方便處理,包含當前頁碼值,比如當前頁碼值為5,那么pages = [3,4,5] """ if current_page == 1: return [] elif current_page == num_pages: l = [i - 1 for i in range(current_page, current_page - left, -1) if i - 1 > 1] l.sort() return l l = [i for i in range(current_page, current_page - left, -1) if i > 1] l.sort() return l def get_right(current_page, right, num_pages): """ 輔助函數,獲取當前頁碼的值得右邊兩個頁碼值,要注意一些細節,比如不夠兩個那么最右取到最大頁碼值。不包含當前頁碼值。比如當前頁碼值為5,那么pages = [6,7] """ if current_page == num_pages: return [] return [i + 1 for i in range(current_page, current_page + right - 1) if i < num_pages - 1]
把需要變量值都添加到上下文了,看看我們的模板文件該怎么寫:
templates/blog/pagination.html
至此代碼部分編寫完了,看看如何使用這個模板標簽吧,比如我們要在首頁對文章列表進行分頁:
templates/blog/index.html {% load paginate_tags %} # 首先必須通過load模板標簽載入分頁標簽 {% paginate article_list 7 %} 把文章列表傳給paginate函數,每頁分7個,context上下文則自動被傳入,無需顯示指定 {% for article in article_list %} display the article information {% endfor %} {% include "blog/pagination.html" %} # 這里用到一個 include 技巧,把pagination的模板代碼寫在多帶帶的pagination.html文件中,這樣哪里需要用到哪里就 include 進來就行,提高代碼的復用性。
至此,整個分頁功能就完成了,看看效果:
支持 fetch code 與代碼高亮 fetch code我們的博客文章是支持 markdown 語法標記的(使用的是 markdown2 第三方 app),markdown 比較常用的兩個特性是 fetch code 和語法高亮。由于我們目前沒有對博客文章的 markdown 標記做任何拓展,因此要標記一段代碼,我們必須在每行代碼前縮進 4 個空格,這很不方便。而 fetch code 可以讓我們在寫文章時只按照下面的輸入就可以標記一段代碼,相比每行縮進四個空格要方便很多:
?``` def test_function(): print("fectch code like this!") ?```
下面來拓展它,很簡單,把用 markdown 標記的語句拓展一下,在 Views.py 中找到 IndexView,其中有一句代碼的作用是來 markdown 我們的博客文章的:
for article in article_list: article.body = markdown2.markdown(article.body, )
將 markdown 函數拓展一下,傳入如下參數即可:
for article in article_list: article.body = markdown2.markdown(article.body, extras=["fenced-code-blocks"], )
這樣,每次要輸入一段代碼時,按照上面的語法輸入就可以了,比如我輸入下面的代碼段:
?``` # 注意這個符號是半角下波浪符號,即數字1左邊的那個鍵對應的符號 class ArticleDetailView(DetailView): model = Article template_name = "blog/detail.html" context_object_name = "article" pk_url_kwarg = "article_id" def get_object(self, queryset=None): obj = super(ArticleDetailView, self).get_object() obj.body = markdown2.markdown(obj.body, extras=["fenced-code-blocks"], ) return obj ?```
來看看效果:
此外別忘了把其他做了 markdown 標記的地方也做相應拓展,目前我們一共有三處:IndexView,DetailView,CategoryView。
代碼高亮現在輸入代碼方便了,但是美中不足的是代碼只有一種顏色,我們想要代碼高亮,需要使用到 Pygments 包。先安裝它:pip install pygments,安裝好后別忘了添加到 settings.py 中:
settings.py INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "blog", "markdown2", "pygments", # 添加進來 ]
pygments 的工作原理是把代碼切分成一個個單詞,然后為這些單詞添加 css 樣式,不同的詞應用不同的樣式,這樣就實現了代碼顏色的區分,即高亮了語法,因此我們要引入一些 css 樣式文件。在我們的 GitHub 項目的 DjangoBlog/blog/static/blog/css 目錄下有相應的文件,拷貝下來添加到你的項目相同目錄下就可以了。之后再模板中引入樣式文件:
templates/base.htmlMyblog ... 引入上面的樣式文件,當然里面有很多樣式文件,喜歡哪個引哪個,比如我引的是github風格的語法高亮 ...
再次輸入代碼塊看看:
?```python # 注:這里一定要指定相應語言,否則無法高亮代碼 class ArticleDetailView(DetailView): model = Article template_name = "blog/detail.html" context_object_name = "article" pk_url_kwarg = "article_id" def get_object(self, queryset=None): obj = super(ArticleDetailView, self).get_object() obj.body = markdown2.markdown(obj.body, extras=["fenced-code-blocks"], ) return obj ?```
看看效果:
這里比較麻煩的是必須指定代碼對應的語言,有人說 pygments 可以自動識別語言的,但是我目前的測試來看似乎沒有效果。目前沒有找到設置方法,如有知道的朋友請告知。
整個完整的 Blog 項目代碼請訪問我們的 GitHub 組織倉庫獲取。
聲明:本教程只是演示如何實現分頁和 markdown 語法高亮功能,在細節上處理上還有很多需要斟酌的地方,如果您有更好的實現方式或者實踐經驗,懇請傳授我們。如果您對本教程有任何不清晰的地方或者其他意見和建議,請及時通過郵件列表或者 GitHub Issue 或者評論留言反饋給我們。您的反饋和建議是我們持續改善本教程的最佳方式。
接下來做什么?個人博客功能逐步完善,接下來的教程我們將繼續實現個人博客常帶的功能:標簽云和文章歸檔,敬請期待下一期教程。如果你還有其他想實現的功能,也請告訴我們,我們會在教程中陸續實現。
Django學習小組簡介django學習小組是一個促進 django 新手互相學習、互相幫助的組織。
小組在一邊學習 django 的同時將一起完成幾個項目,包括:
一個簡單的 django 博客,用于發布小組每周的學習和開發文檔;
django中國社區,為國內的 django 開發者們提供一個長期維護的 django 社區;
上面所說的這個社區類似于 segmentfault 和 stackoverflow ,但更加專注(只專注于 django 開發的問題)。
目前小組正在完成第一個項目,本文即是該項目第三周的相關文檔。
更多的信息請關注我們的?github 組織,本教程項目的相關源代碼也已上傳到 github 上。
同時,你也可以加入我們的郵件列表?django_study@groups.163.com?,隨時關注我們的動態。我們會將每周的詳細開發文檔和代碼通過郵件列表發出。
如有任何建議,歡迎提 Issue,歡迎 fork,pr,當然也別忘了 star 哦!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37980.html
摘要:本教程內容已過時,更新版教程請訪問博客開發入門教程。表示降序排列,默認是升序排列。學習小組簡介學習小組是一個促進新手互相學習互相幫助的組織。我們會將每周的詳細開發文檔和代碼通過郵件列表發出。 本教程內容已過時,更新版教程請訪問: django 博客開發入門教程。 通過前四周的時間我們開發了一個簡單的個人 Blog,教程地址: 第一周:Django 學習小組:博客開發實戰第一周教程 ——...
摘要:本教程內容已過時,更新版教程請訪問博客開發入門教程。我們的評論表單放在中,評論成功后返回到原始提交頁面。學習小組簡介學習小組是一個促進新手互相學習互相幫助的組織。 本教程內容已過時,更新版教程請訪問: django 博客開發入門教程。 通過前四周的時間我們開發了一個簡單的個人 Blog,前幾期教程地址: 第一周:Django 學習小組:博客開發實戰第一周教程 —— 編寫博客的 Mode...
摘要:本教程內容已過時,更新版教程請訪問博客開發入門教程。我們的評論表單放在中,評論成功后返回到原始提交頁面。學習小組簡介學習小組是一個促進新手互相學習互相幫助的組織。 本教程內容已過時,更新版教程請訪問: django 博客開發入門教程。 通過前四周的時間我們開發了一個簡單的個人 Blog,前幾期教程地址: 第一周:Django 學習小組:博客開發實戰第一周教程 —— 編寫博客的 Mode...
摘要:本教程首先介紹兩個項目中遇到的通用視圖和。語句的作用是添加了到上下文中,還要把默認的一些上下文變量也返回給視圖函數,以便其后續處理。 通過三周的時間我們開發了一個簡單的個人 Blog,教程地址: 第一周:Django 學習小組:博客開發實戰第一周教程 —— 編寫博客的 Model 和首頁面 第二周:Django 學習小組:博客開發實戰第二周教程 —— 博客詳情頁面和分類頁面 第三周:D...
摘要:本節接上周的文檔學習小組博客開發實戰第一周教程編寫博客的首頁面,我們繼續給博客添加功能,以及改善前面不合理的部分。返回該視圖要顯示的對象。目前小組正在完成第一個項目,本文即是該項目第二周的相關文檔。 本教程內容已過時,更新版教程請訪問: django 博客開發入門教程。 上周我們完成了博客的 Model 部分,以及 Blog 的首頁視圖 IndexView。 本節接上周的文檔 Djan...
閱讀 2850·2021-08-20 09:37
閱讀 1607·2019-08-30 12:47
閱讀 1090·2019-08-29 13:27
閱讀 1685·2019-08-28 18:02
閱讀 749·2019-08-23 18:15
閱讀 3084·2019-08-23 16:51
閱讀 931·2019-08-23 14:13
閱讀 2125·2019-08-23 13:05