1. 程式人生 > >HelloDjango 第 06 篇:部落格從“裸奔”到“有面板”

HelloDjango 第 06 篇:部落格從“裸奔”到“有面板”

文中涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫

在此之前我們已經編寫了部落格的首頁檢視,並且配置了 URL 和模板,讓 django 能夠正確地處理 HTTP 請求並返回合適的 HTTP 響應。不過我們僅僅在首頁返回了一句話:“歡迎訪問我的部落格“,這是個 Hello World 級別的檢視函式,毫無美感。

這篇文章我們需要編寫真正的首頁檢視函式,當用戶訪問我們的部落格首頁時,他將看到我們發表的部落格文章列表,就像 演示專案 裡展示的這樣。

首頁檢視函式

上一節我們闡明瞭 django 的開發流程。即首先配置 URL,把 URL 和相應的檢視函式繫結,一般寫在 urls.py 檔案裡,然後在工程的 urls.py 檔案引入。其次是編寫檢視函式,檢視中需要渲染模板,我們也在 settings.py 中進行了模板相關的配置,讓 django 能夠找到需要渲染的模板。最後把渲染完成的 HTTP 響應返回就可以了。相關的配置和準備工作都在之前完成了,這裡我們只需專心編寫檢視函式,讓它實現我們想要的功能即可。

首頁的檢視函式其實很簡單,程式碼像這樣:

blog/views.py

from django.shortcuts import render
from .models import Post

def index(request):
    post_list = Post.objects.all().order_by('-created_time')
    return render(request, 'blog/index.html', context={'post_list': post_list})

我們曾經在前面的章節講解過模型管理器 objects

的使用。這裡我們使用 all() 方法從資料庫裡獲取了全部的文章,存在了 post_list 變數裡。all 方法返回的是一個 QuerySet(可以理解成一個類似於列表的資料結構),由於通常來說部落格文章列表是按文章發表時間倒序排列的,即最新的文章排在最前面,所以我們緊接著呼叫了 order_by 方法對這個返回的 queryset 進行排序。排序依據的欄位是 created_time,即文章的建立時間。- 號表示逆序,如果不加 - 則是正序。 接著如之前所做,我們渲染了 blog\index.html 模板檔案,並且把包含文章列表資料的 post_list 變數傳給了模板。

處理靜態檔案

我們的專案使用了從網上下載的一套部落格模板(點選這裡下載全套模板)。這裡面除了 HTML 文件外,還包含了一些 CSS 檔案和 JavaScript 檔案以讓網頁呈現出我們現在看到的樣式。同樣我們需要對 django 做一些必要的配置,才能讓 django 知道如何在開發伺服器中引入這些 CSS 和 JavaScript 檔案,這樣才能讓部落格頁面的 CSS 樣式生效。

按照慣例,我們把 CSS 和 JavaScript 檔案放在 blog 應用的 static 目錄下。因此,先在 blog 應用下建立一個 static 資料夾。同時,為了避免和其它應用中的 CSS 和 JavaScript 檔案命名衝突(別的應用下也可能有和 blog 應用下同名的 CSS 、JavaScript 檔案),我們再在 static 目錄下建立一個 blog 資料夾,把下載的部落格模板中的 css 和 js 資料夾連同裡面的全部檔案一同拷貝進這個目錄。最終我們的 blog 應用目錄結構應該是這樣的:

blog\
    __init__.py
    static\
        blog\
            css\
                .css 檔案...
            js\
                .js 檔案...
    admin.py
    apps.py
    migrations\
        __init__.py
    models.py
    tests.py
    views.py

用下載的部落格模板中的 index.html 檔案替換掉之前我們自己寫的 index.html 檔案。如果你好奇,現在就可以執行開發伺服器,看看首頁是什麼樣子。

如圖所示,你會看到首頁顯示的樣式非常混亂,原因是瀏覽器無法正確載入 CSS 等樣式檔案。需要以 django 的方式來正確地處理 CSS 和 JavaScript 等靜態檔案的載入路徑。CSS 樣式檔案通常在 HTML 文件的 head 標籤裡引入,開啟 index.html 檔案,在檔案的開始處找到 head 標籤包裹的內容,大概像這樣:

templates/blog/index.html

<!DOCTYPE html>
<html>
  <head>
      <title>Black &amp; White</title>

      <!-- meta -->
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <!-- css -->
      <link rel="stylesheet" href="css/bootstrap.min.css">
      <link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
      <link rel="stylesheet" href="css/pace.css">
      <link rel="stylesheet" href="css/custom.css">

      <!-- js -->
      <script src="js/jquery-2.1.3.min.js"></script>
      <script src="js/bootstrap.min.js"></script>
      <script src="js/pace.min.js"></script>
      <script src="js/modernizr.custom.js"></script>
  </head>
  <body>
      <!-- 其它內容 -->
      <script src="js/script.js"></script>
  </body>
</html>

CSS 樣式檔案的路徑在 link 標籤的 href 屬性裡,而 JavaScript 檔案的路徑在 script 標籤的 src 屬性裡。可以看到諸如 `href="css/bootstrap.min.css" 或者 src="js/jquery-2.1.3.min.js" 這樣的引用,由於引用檔案的路徑不對,所以瀏覽器引入這些檔案失敗。我們需要把它們改成正確的路徑。把程式碼改成下面樣子,正確地引入 static 檔案下的 CSS 和 JavaScript 檔案:

templates/blog/index.html

+ {% load static %}
<!DOCTYPE html>
<html>
  <head>
      <title>Black &amp; White</title>

      <!-- meta -->
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <!-- css -->
      - <link rel="stylesheet" href="css/bootstrap.min.css">
      <link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
      - <link rel="stylesheet" href="css/pace.css">
      - <link rel="stylesheet" href="css/custom.css">
      + <link rel="stylesheet" href="{% static 'blog/css/bootstrap.min.css' %}">
      + <link rel="stylesheet" href="{% static 'blog/css/pace.css' %}">
      + <link rel="stylesheet" href="{% static 'blog/css/custom.css' %}">

      <!-- js -->
      - <script src="js/jquery-2.1.3.min.js"></script>
      - <script src="js/bootstrap.min.js"></script>
      - <script src="js/pace.min.js"></script>
      - <script src="js/modernizr.custom.js"></script>
      + <script src="{% static 'blog/js/jquery-2.1.3.min.js' %}"></script>
      + <script src="{% static 'blog/js/bootstrap.min.js' %}"></script>
      + <script src="{% static 'blog/js/pace.min.js' %}"></script>
      + <script src="{% static 'blog/js/modernizr.custom.js' %}"></script>
  </head>
  <body>
      <!-- 其它內容 -->
      - <script src="js/script.js' %}"></script>
      + <script src="{% static 'blog/js/script.js' %}"></script>
  </body>
</html>

這裡 - 表示刪掉這一行,而 + 表示增加這一行。(增加了哪些內容看仔細一點,千萬別漏掉)

我們把引用路徑放在了一個奇怪的符號裡,例如:href="{% static 'blog/css/bootstrap.min.css' %}"。用 {% %} 包裹起來的叫做模板標籤。我們前面說過用 {{ }} 包裹起來的叫做模板變數,其作用是在最終渲染的模板裡顯示由檢視函式傳過來的變數值。而這裡我們使用的模板標籤的功能則類似於函式,例如這裡的 static 模板標籤,它把跟在後面的字串 'css/bootstrap.min.css' 轉換成正確的檔案引入路徑。這樣 css 和 js 檔案才能被正確載入,樣式才能正常顯示。

注意:

為了能在模板中使用 {% static %} 模板標籤,別忘了在最頂部 {% load static %} 。static 模板標籤位於 static模組中,只有通過 load 模板標籤將該模組引入後,才能在模板中使用 {% static %} 標籤。

替換完成後你可以重新整理頁面並看看網頁的原始碼,看一看 {% static %} 模板標籤在頁面渲染後究竟被替換成了什麼樣的值。例如我們可以看到

<link rel="stylesheet" href="{% static 'blog/css/pace.css' %}">

這一部分最終在瀏覽器中顯示的是:

<link rel="stylesheet" href="/static/blog/css/pace.css">

這正是 pace.css 檔案所在的路徑,其它的檔案路徑也被類似替換。可以看到 {% static %} 標籤的作用實際就是把後面的字串加了一個 /static/ 字首,比如 {% static 'blog/css/pace.css' %} 最終渲染的值是 /static/blog/css/pace.css。而 /static/ 字首是我們在 settings.py 檔案中通過 STATIC_URL = '/static/' 指定的。事實上,如果我們直接把引用路徑寫成 /static/blog/css/pace.css 也是可以的,那麼為什麼要使用 {% static %} 標籤呢?想一下,目前 URL 的字首是 /static/,如果哪一天因為某些原因,我們需要把 /static/ 改成 /resource/,如果你是直接寫的引用路勁而沒有使用 static 模板標籤,那麼你可能需要改 N 個地方。如果你使用了 static 模板標籤,那麼只要在 settings.py 處改一個地方就可以了,即把 STATIC_URL = '/static/' 改成 STATIC_URL = '/resource/'

提示

有時候按 F5 重新整理後頁面還是很亂,這可能是因為瀏覽器快取了之前的結果。按 Shift + F5(有些瀏覽器可能是 Ctrl + F5)強制重新整理瀏覽器頁面即可。如果還是不行,重啟一下開發伺服器以及清除瀏覽器快取。

注意這裡有一個 CSS 檔案的引入

<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">

我們沒有使用模板標籤,因為這裡的引用的檔案是一個外部檔案,不是我們專案裡 static\blog\css 目錄下的檔案,因此無需使用模板標籤。

正確引入了靜態檔案後樣式顯示正常了。

修改模板

目前我們看到的只是模板中預先填充的一些資料,我們得讓它顯示從資料庫中獲取的文章資料。下面來稍微改造一下模板:

在模板 index.html 中你會找到一系列 article 標籤:

templates/blog/index.html

...
<article class="post post-1">
  ...
</article>

<article class="post post-2">
  ...
</article>

<article class="post post-3">
  ...
</article>
...

這裡麵包裹的內容顯示的就是文章資料了。我們前面在檢視函式 index 裡給模板傳了一個 post_list 變數,它裡面包含著從資料庫中取出的文章列表資料。就像 Python 一樣,我們可以在模板中迴圈這個列表,把文章一篇篇迴圈出來,然後一篇篇顯示文章的資料。要在模板中使用迴圈,需要使用到前面提到的模板標籤,這次使用 {% for %} 模板標籤。將 index.html 中多餘的 article 標籤刪掉,只留下一個 article 標籤,然後寫上下列程式碼:

templates/blog/index.html

...
{% for post in post_list %}
  <article class="post post-{{ post.pk }}">
    ...
  </article>
{% empty %}
  <div class="no-post">暫時還沒有釋出的文章!</div>
{% endfor %}
...

可以看到語法和 Python 的 for 迴圈類似,只是被 {% %} 這樣一個模板標籤符號包裹著。{% empty %} 的作用是當 post_list 為空,即資料庫裡沒有文章時顯示 {% empty %} 下面的內容,最後我們用 {% endfor %} 告訴 django 迴圈在這裡結束了。

你可能不太理解模板中的 postpost_list 是什麼。post_list 是一個 QuerySet(類似於一個列表的資料結構),其中每一項都是之前定義在 blog\models.py 中的 Post 類的例項,且每個例項分別對應著資料庫中每篇文章的記錄。因此我們迴圈遍歷 post_list ,每一次遍歷的結果都儲存在 post 變數裡。所以我們使用模板變數來顯示 post 的屬性值。例如這裡的 {{ post.pk }}(pk 是 primary key 的縮寫,即 post 對應於資料庫中記錄的 id 值,該屬性儘管我們沒有顯示定義,但是 django 會自動為我們新增)。

現在我們可以在迴圈體內通過 post 變數訪問單篇文章的資料了。分析 article 標籤裡面的 HTML 內容,h1 顯示的是文章的標題,

<h1 class="entry-title">
    <a href="single.html">Adaptive Vs. Responsive Layouts And Optimal Text Readability</a>
</h1>

我們把標題替換成 posttitle 屬性值。注意要把它包裹在模板變數裡,因為它最終要被替換成實際的 title 值。

<h1 class="entry-title">
    <a href="single.html">{{ post.title }}</a>
</h1>

下面這 5 個 span 標籤裡分別顯示了分類(category)、文章釋出時間、文章作者、評論數、閱讀量。

<div class="entry-meta">
  <span class="post-category"><a href="#">django 部落格教程</a></span>
  <span class="post-date"><a href="#"><time class="entry-date"
                                            datetime="2012-11-09T23:15:57+00:00">2017年5月11日</time></a></span>
  <span class="post-author"><a href="#">追夢人物</a></span>
  <span class="comments-link"><a href="#">4 評論</a></span>
  <span class="views-count"><a href="#">588 閱讀</a></span>
</div>

再次替換掉一些資料,由於評論數和閱讀量暫時沒法替換,因此先留著,我們在之後實現了這些功能後再來修改它,目前只替換分類、文章釋出時間、文章作者:

<div class="entry-meta">
  <span class="post-category"><a href="#">{{ post.category.name }}</a></span>
  <span class="post-date"><a href="#"><time class="entry-date"
                                            datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
  <span class="post-author"><a href="#">{{ post.author }}</a></span>
  <span class="comments-link"><a href="#">4 評論</a></span>
  <span class="views-count"><a href="#">588 閱讀</a></span>
</div>

這裡 p 標籤裡顯示的是摘要

<div class="entry-content clearfix">
  <p>免費、中文、零基礎,完整的專案,基於最新版 django 1.10 和 Python 3.5。帶你從零開始一步步開發屬於自己的部落格網站,幫助你以最快的速度掌握 django
    開發的技巧...</p>
  <div class="read-more cl-effect-14">
    <a href="#" class="more-link">繼續閱讀 <span class="meta-nav">→</span></a>
  </div>
</div>

替換成 post 的摘要:

<div class="entry-content clearfix">
  <p>{{ post.excerpt }}</p>
  <div class="read-more cl-effect-14">
    <a href="#" class="more-link">繼續閱讀 <span class="meta-nav">→</span></a>
  </div>
</div>

再次訪問首頁,它顯示:暫時還沒有釋出的文章!好吧,做了這麼多工作,但是資料庫中其實還沒有任何資料呀!接下來我們就實際寫幾篇文章儲存到資料庫裡,看看顯示的效果究竟如何。

歡迎關注 HelloGitHub 公眾號,獲取更多開源專案的資料和內