一、前言 1.1.環境 python版本:3.6 Django版本:1.11.6 1.2.預覽效果 最終搭建的blog的樣子,基本上滿足需求了。框架搭好了,至於CSS,可以根據自己喜好隨意搭配。 二、建立博客應用 2.1.建立項目和應用 創建工程blogproject 創建blog應用 打開 blo ...
一、前言
1.1.環境
python版本:3.6
Django版本:1.11.6
1.2.預覽效果
最終搭建的blog的樣子,基本上滿足需求了。框架搭好了,至於CSS,可以根據自己喜好隨意搭配。
二、建立博客應用
2.1.建立項目和應用
創建工程blogproject
python manage.py startproject blogproject
創建blog應用
python manage.py startpapp blog
打開 blogproject\ 目錄下的 settings.py 文件,找到 INSTALLED_APPS
設置項,將 blog 應用添加進去。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', ]
2.2.目錄結構
三、創建blog的資料庫模型
3.1.設計博客的資料庫表結構
博客最主要的功能就是展示我們寫的文章,它需要從某個地方獲取博客文章數據才能把文章展示出來,通常來說這個地方就是資料庫。我們把寫好的文章永久地保存在資料庫里,當用戶訪問我們的博客時,Django 就去資料庫里把這些數據取出來展現給用戶。
博客的文章應該含有標題、正文、作者、發表時間等數據。一個更加現代化的博客文章還希望它有分類、標簽、評論等。為了更好地存儲這些數據,我們需要合理地組織資料庫的表結構。
我們的博客初級版本主要包含博客文章,文章會有分類以及標簽。一篇文章只能有一個分類,但可以打上很多標簽。我們把分類和標簽做成單獨的資料庫表,再把文章和分類、標簽關聯起來。下麵分別是分類和標簽的資料庫表:
分類id 分類名
1 python
2 Django
標簽id 標簽名
1 python學習
2 Django學習
3.2.編寫博客模型代碼
分類資料庫表:
# blog/models.py from django.db import models class Category(models.Model): name = models.CharField(max_length=100)
Category
就是一個標準的 Python 類,它繼承了 models.Model
類,類名為 Category
。Category
類有一個屬性 name
,它是 models.CharField
的一個實例。
我們需要 3 個表格:文章(Post)、分類(Category)以及標簽(Tag),下麵就來分別編寫它們對應的 Python 類。模型的代碼通常寫在相關應用的 models.py 文件里
# blog/models.py from django.db import models from django.contrib.auth.models import User class Category(models.Model): ''' Django 要求模型必須繼承 models.Model 類。 Category 只需要一個簡單的分類名 name 就可以了。 CharField 指定了分類名 name 的數據類型,CharField 是字元型, CharField 的 max_length 參數指定其最大長度,超過這個長度的分類名就不能被存入資料庫。 ''' name = models.CharField(max_length=100) class Tag(models.Model): '''標簽''' name = models.CharField(max_length=100) class Post(models.Model): '''文章''' # 文章標題 title = models.CharField(max_length=70) # 文章正文,我們使用了 TextField。 # 存儲比較短的字元串可以使用 CharField,但對於文章的正文來說可能會是一大段文本,因此使用 TextField 來存儲大段文本。 body = models.TextField() # 這兩個列分別表示文章的創建時間和最後一次修改時間,存儲時間的欄位用 DateTimeField 類型。 created_time = models.DateTimeField() modified_time = models.DateTimeField() # 文章摘要,可以沒有文章摘要,但預設情況下 CharField 要求我們必須存入數據,否則就會報錯。 # 指定 CharField 的 blank=True 參數值後就可以允許空值了。 excerpt = models.CharField(max_length=200,blank=True) # 我們在這裡把文章對應的資料庫表和分類、標簽對應的資料庫表關聯了起來,但是關聯形式稍微有點不同。 # 我們規定一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,所以我們使用的是 ForeignKey,即一對多的關聯關係。 # 而對於標簽來說,一篇文章可以有多個標簽,同一個標簽下也可能有多篇文章,所以我們使用 ManyToManyField,表明這是多對多的關聯關係。 # 同時我們規定文章可以沒有標簽,因此為標簽 tags 指定了 blank=True。 category = models.ForeignKey(Category,on_delete=models.CASCADE) tags = models.ManyToManyField(Tag,blank=True) # 文章作者,這裡 User 是從 django.contrib.auth.models 導入的。 # django.contrib.auth 是 Django 內置的應用,專門用於處理網站用戶的註冊、登錄等流程,User 是 Django 為我們已經寫好的用戶模型。 # 這裡我們通過 ForeignKey 把文章和 User 關聯了起來。 # 因為我們規定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關聯關係,和 Category 類似。 author = models.ForeignKey(User,on_delete=models.CASCADE)
四、遷移資料庫
4.1.設置資料庫為Mysql
更改setting.py預設配置
# DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django', #資料庫名字 'USER': 'root', #賬號 'PASSWORD': '123456', #密碼 'HOST': '127.0.0.1', #IP 'PORT': '3306', #埠 } }
導入Pymysql
# blog/__init__.py import pymysql pymysql.install_as_MySQLdb()
4.2.遷移資料庫
分別運行下麵兩條命令
python manage.py makemigrations
python manage.py migrate
當我們執行了 python manage.py makemigrations
後,Django 在 blog 應用的 migrations\ 目錄下生成了一個 0001_initial.py 文件,這個文件是 Django 用來記錄我們對模型做了哪些修改的文件。目前來說,我們在 models.py 文件里創建了 3 個模型類,Django 把這些變化記錄在了 0001_initial.py 里。
不過此時還只是告訴了 Django 我們做了哪些改變,為了讓 Django 真正地為我們創建資料庫表,接下來又執行了 python manage.py migrate
命令。Django 通過檢測應用中 migrations\ 目錄下的文件,得知我們對資料庫做了哪些操作,然後它把這些操作翻譯成資料庫操作語言,從而把這些操作作用於真正的資料庫。
你可以看到命令的輸出除了 Applying blog.0001_initial... OK 外,Django 還對其它文件做了操作。這是因為除了我們自己建立的 blog 應用外,Django 自身還內置了很多應用,這些應用本身也是需要存儲數據的。可以在 settings.py 的 INSTALLED_APP
設置里看到這些應用,當然我們目前不必關心這些。
#blogproject/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', ]
運行下麵的命令將看到輸出了經 Django 翻譯後的資料庫表創建語句,這有助於你理解 Django ORM 的工作機制。
python manage.py sqlmigrate blog 0001
4.3.存數據
打開一個互動式命令行
python manage.py shell
首先我們來創建一個分類和一個標簽:
我們首先導入 3 個之前寫好的模型類,然後實例化了一個 Category
類和一個 Tag
類,為他們的屬性 name
賦了值。為了讓 Django 把這些數據保存進資料庫,調用實例的 save
方法即可。
創建文章之前,我們需要先創建一個 User,用於指定文章的作者。創建 User 的命令 Django 已經幫我們寫好了,依然是通過 manage.py 來運行。首先exit()退出命令交互欄,運行 python manage.py createsuperuser
命令並根據提示創建用戶:用戶名,郵箱,密碼
再次運行 python manage.py shell
進入 Python 命令交互欄,開始創建文章:
由於我們重啟了 shell,因此需要重新導入了 Category
、Tag
、Post
以及 User
。我們還導入了一個 Django 提供的輔助模塊 timezone,這是因為我們需要調用它的 now()
方法為 created_time
和 modified_time
指定時間,容易理解 now
方法返回當前時間。然後我們根據用戶名和分類名,通過 get
方法取出了存在資料庫中的 User
和 Category
(取數據的方法將在下麵介紹)。接著我們為文章指定了 title
、body
、created_time
、modified_time
值,並把它和前面創建的 Category 以及 User 關聯了起來。允許為空 excerpt
、tags
我們就沒有為它們指定值了。
4.4.取數據
數據已經存入資料庫了,現在要把它們取出來看看:
objects
是我們的模型管理器,它為我們提供一系列從資料庫中取數據方法,這裡我們使用了 all
方法,表示我們要把對應的數據全部取出來。可以看到 all
方法都返回了數據,這些數據應該是我們之前存進去的,但是顯示的字元串有點奇怪,無法看出究竟是不是我們之前存入的數據。為了讓顯示出來的數據更加人性化一點,我們為 3 個模型分別增加一個 __str__
方法:
# blog/models.py from django.db import models from django.contrib.auth.models import User class Category(models.Model): ''' Django 要求模型必須繼承 models.Model 類。 Category 只需要一個簡單的分類名 name 就可以了。 CharField 指定了分類名 name 的數據類型,CharField 是字元型, CharField 的 max_length 參數指定其最大長度,超過這個長度的分類名就不能被存入資料庫。 ''' name = models.CharField(max_length=100) def __str__(self): return self.name class Tag(models.Model): '''標簽''' name = models.CharField(max_length=100) def __str__(self): return self.name class Post(models.Model): '''文章''' # 文章標題 title = models.CharField(max_length=70) # 文章正文,我們使用了 TextField。 # 存儲比較短的字元串可以使用 CharField,但對於文章的正文來說可能會是一大段文本,因此使用 TextField 來存儲大段文本。 body = models.TextField() # 這兩個列分別表示文章的創建時間和最後一次修改時間,存儲時間的欄位用 DateTimeField 類型。 created_time = models.DateTimeField() modified_time = models.DateTimeField() # 文章摘要,可以沒有文章摘要,但預設情況下 CharField 要求我們必須存入數據,否則就會報錯。 # 指定 CharField 的 blank=True 參數值後就可以允許空值了。 excerpt = models.CharField(max_length=200,blank=True) # 我們在這裡把文章對應的資料庫表和分類、標簽對應的資料庫表關聯了起來,但是關聯形式稍微有點不同。 # 我們規定一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,所以我們使用的是 ForeignKey,即一對多的關聯關係。 # 而對於標簽來說,一篇文章可以有多個標簽,同一個標簽下也可能有多篇文章,所以我們使用 ManyToManyField,表明這是多對多的關聯關係。 # 同時我們規定文章可以沒有標簽,因此為標簽 tags 指定了 blank=True。 category = models.ForeignKey(Category,on_delete=models.CASCADE) tags = models.ManyToManyField(Tag,blank=True) # 文章作者,這裡 User 是從 django.contrib.auth.models 導入的。 # django.contrib.auth 是 Django 內置的應用,專門用於處理網站用戶的註冊、登錄等流程,User 是 Django 為我們已經寫好的用戶模型。 # 這裡我們通過 ForeignKey 把文章和 User 關聯了起來。 # 因為我們規定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關聯關係,和 Category 類似。 author = models.ForeignKey(User,on_delete=models.CASCADE) def __str__(self): return self.title__str__
定義好 __str__
方法後,解釋器顯示的內容將會是 __str__
方法返回的內容。這裡 Category
返回分類名 name
,Tag
返回標簽名,而 Post
返回它的 title
。
exit() 退出 Shell,再重新運行 python manage.py shell
進入 Shell。
可以看到返回的是我們之前存入的數據。
此外我們在創建文章時提到了通過 get
方法來獲取數據,這裡 all
方法和 get
方法的區別是:all
方法返回全部數據,是一個類似於列表的數據結構(QuerySet);而 get
返回一條記錄數據,如有多條記錄或者沒有記錄,get
方法均會拋出相應異常。
五、博客首頁視圖
5.1.Django處理HTTP請求
Web 應用的交互過程其實就是 HTTP 請求與響應的過程。無論是在 PC 端還是移動端,我們通常使用瀏覽器來上網,上網流程大致來說是這樣的:
- 我們打開瀏覽器,在地址欄輸入想訪問的網址,比如 http://www.cnblogs.com/。
- 瀏覽器知道我們想要訪問哪個網址後,它在後臺幫我們做了很多事情。主要就是把我們的訪問意圖包裝成一個 HTTP 請求,發給我們想要訪問的網址所對應的伺服器。通俗點說就是瀏覽器幫我們通知網站的伺服器,說有人來訪問你啦,訪問的請求都寫在 HTTP 里了,你按照要求處理後告訴我,我再幫你回應他!
- 伺服器處理了HTTP 請求,然後生成一段 HTTP 響應給瀏覽器。瀏覽器解讀這個響應,把相關的內容在瀏覽器里顯示出來,於是我們就看到了網站的內容。比如你訪問了我的博客主頁http://www.cnblogs.com/derek1184405959/,伺服器接收到這個請求後就知道用戶訪問的是首頁,首頁顯示的是全部文章列表,於是它從資料庫里把文章數據取出來,生成一個寫著這些數據的 HTML 文檔,包裝到 HTTP 響應里發給瀏覽器,瀏覽器解讀這個響應,把 HTML 文檔顯示出來,我們就看到了文章列表的內容。
因此,Django 作為一個 Web 框架,它的使命就是處理流程中的第二步。即接收瀏覽器發來的 HTTP 請求,返回相應的 HTTP 響應。於是引出這麼幾個問題:
- Django 如何接收 HTTP 請求?
- Django 如何處理這個 HTTP 請求?
- Django 如何生成 HTTP 響應?
對於如何處理這些問題,Django 有其一套規定的機制。我們按照 Django 的規定,就能開發出所需的功能
Hello視圖函數
我們先以一個最簡單的 Hello World 為例來看看 Django 處理上述問題的機制是怎麼樣的。
綁定url和視圖函數
首先 Django 需要知道當用戶訪問不同的網址時,應該如何處理這些不同的網址(即所說的路由)。Django 的做法是把不同的網址對應的處理函數寫在一個 urls.py 文件里,當用戶訪問某個網址時,Django 就去會這個文件里找,如果找到這個網址,就會調用和它綁定在一起的處理函數(叫做視圖函數)。
下麵是具體的做法,首先在 blog 應用的目錄下創建一個 urls.py 文件,在 blog\urls.py 中寫入這些代碼:
# blog/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$',views.index,name='index'), ]
我們首先從 django.conf.urls 導入了 url
函數,又從當前目錄下導入了 views 模塊。然後我們把網址和處理函數的關係寫在了 urlpatterns
列表裡。
綁定關係的寫法是把網址和對應的處理函數作為參數傳給 url
函數(第一個參數是網址,第二個參數是處理函數),另外我們還傳遞了另外一個參數 name
,這個參數的值將作為處理函數 index
的別名,這在以後會用到。
註意這裡我們的網址是用正則表達式寫的,Django 會用這個正則表達式去匹配用戶實際輸入的網址,如果匹配成功,就會調用其後面的視圖函數做相應的處理。
比如說我們本地開發伺服器的功能變數名稱是 http://127.0.0.1:8000,那麼當用戶輸入網址 http://127.0.0.1:8000 後,Django 首先會把協議 http、功能變數名稱 127.0.0.1 和埠號 8000 去掉,此時只剩下一個空字元串,而 r'^$'
的模式正是匹配一個空字元串(這個正則表達式的意思是以空字元串開頭且以空字元串結尾),於是二者匹配,Django 便會調用其對應的 views.index
函數。
註意:在項目根目錄的 blogproject\ 目錄下(即 settings.py 所在的目錄),原本就有一個 urls.py 文件,這是整個工程項目的 URL 配置文件。而我們這裡新建了一個 urls.py 文件,且位於 blog 應用下。這個文件將用於 blog 應用相關的 URL 配置。不要把兩個文件搞混了。
編寫視圖函數
第二步就是要實際編寫我們的 views.index
視圖函數了,按照慣例視圖函數定義在 views.py 文件里:
from django.shortcuts import HttpResponse def index(request): return HttpResponse('歡迎訪問我的博客')
我們前面說過,Web 伺服器的作用就是接收來自用戶的 HTTP 請求,根據請求內容作出相應的處理,並把處理結果包裝成 HTTP 響應返回給用戶。
這個兩行的函數體現了這個過程。它首先接受了一個名為 request
的參數,這個 request
就是 Django 為我們封裝好的 HTTP 請求,它是類 HttpRequest
的一個實例。然後我們便直接返回了一個 HTTP 響應給用戶,這個 HTTP 響應也是 Django 幫我們封裝好的,它是類 HttpResponse
的一個實例,只是我們給它傳了一個自定義的字元串參數。
瀏覽器接收到這個響應後就會在頁面上顯示出我們傳遞的內容:歡迎訪問我的博客
配置項目URL
還差最後一步了,我們前面建立了一個 urls.py 文件,並且綁定了 URL 和視圖函數 index
,但是 Django 並不知道。Django 匹配 URL 模式是在 blogproject\ 目錄(即 settings.py 文件所在的目錄)的 urls.py 下的,所以我們要把 blog 應用下的 urls.py 文件包含到 blogproject\urls.py 里去:
# blogproject/urls.py from django.contrib import admin from django.conf.urls import url,include urlpatterns = [ url('admin/', admin.site.urls), url('', include('blog.urls')), ]
我們這裡導入了一個 include
函數,然後利用這個函數把 blog 應用下的 urls.py 文件包含了進來。此外 include 前還有一個 r''
,這是一個空字元串。這裡也可以寫其它字元串,Django 會把這個字元串和後面 include 的 urls.py 文件中的 URL 拼接。比如說如果我們這裡把 r''
改成 r'blog/'
,而我們在 blog.urls 中寫的 URL 是 r'^$'
,即一個空字元串。那麼 Django 最終匹配的就是 blog/ 加上一個空字元串,即 blog/。
運行結果
運行 python manage.py runserver
打開開發伺服器,在瀏覽器輸入開發伺服器的地址 http://127.0.0.1:8000/,可以看到 Django 返回的內容了。
5.2.使用Django模板系統
這基本上就上 Django 的開發流程了,寫好處理 HTTP 請求和返回 HTTP 響應的視圖函數,然後把視圖函數綁定到相應的 URL 上。
但是等一等!我們看到在視圖函數里返回的是一個 HttpResponse
類的實例,我們給它傳入了一個希望顯示在用戶瀏覽器上的字元串。但是我們的博客不可能只顯示這麼一句話,它有可能會顯示很長很長的內容。比如我們發佈的博客文章列表,或者一大段的博客文章。我們不能每次都把這些大段大段的內容傳給 HttpResponse
。
Django 對這個問題給我們提供了一個很好的解決方案,叫做模板系統。Django 要我們把大段的文本寫到一個文件里,然後 Django 自己會去讀取這個文件,再把讀取到的內容傳給 HttpResponse
。讓我們用模板系統來改造一下上面的例子。
首先在我們的項目根目錄(即 manage.py 文件所在目錄)下建立一個名為 templates 的文件夾,用來存放我們的模板。然後在 templates\ 目錄下建立一個名為 blog 的文件夾,用來存放和 blog 應用相關的模板。
當然模板存放在哪裡是無關緊要的,只要 Django 能夠找到的就好。但是我們建立這樣的文件夾結構的目的是把不同應用用到的模板隔離開來,這樣方便以後維護。我們在 templates\blog 目錄下建立一個名為 index.html 的文件
在 templates\blog\index.html 文件里寫入下麵的代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ title }}</title> </head> <body> <h1>{{ welcome }}</h1> </body> </html>
這是一個標準的 HTML 文檔,只是裡面有兩個比較奇怪的地方:{{ title }}
,{{ welcome }}
。這是 Django 規定的語法。用 {{ }} 包起來的變數叫做模板變數。Django 在渲染這個模板的時候會根據我們傳遞給模板的變數替換掉這些變數。最終在模板中顯示的將會是我們傳遞的值。
模板寫好了,還得告訴 Django 去哪裡找模板,在 settings.py 文件里設置一下模板文件所在的路徑。在 settings.py 找到 TEMPLATES
選項,其中 DIRS
就是設置模板的路徑,在 [] 中寫入 os.path.join(BASE_DIR, 'templates')
,即像下麵這樣:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
這裡 BASE_DIR
是 settings.py 在配置開頭前面定義的變數,記錄的是工程根目錄 blogproject\ 的值(註意是最外層的 blogproject\ 目錄)。在這個目錄下有模板文件所在的目錄 templates\,於是利用os.path.join
把這兩個路徑連起來,構成完整的模板路徑,Django 就知道去這個路徑下麵找我們的模板了。
視圖函數可以改一下了:
from django.http import HttpResponse from django.shortcuts import render def index(request): return render(request, 'blog/index.html', context={ 'title': '我的博客首頁', 'welcome': '歡迎訪問我的博客首頁' })
這裡我們不再是直接把字元串傳給 HttpResponse
了,而是調用 Django 提供的 render
函數。這個函數根據我們傳入的參數來構造 HttpResponse
。
我們首先把 HTTP 請求傳了進去,然後 render
根據第二個參數的值 blog/index.html 找到這個模板文件並讀取模板中的內容。之後 render
根據我們傳入的 context
參數的值把模板中的變數替換為我們傳遞的變數的值,{{ title }}
被替換成了 context
字典中 title
對應的值,同理 {{ welcome }}
也被替換成相應的值。
最終,我們的 HTML 模板中的內容字元串被傳遞給 HttpResponse
對象並返回給瀏覽器(Django 在 render
函數里隱式地幫我們完成了這個過程),這樣用戶的瀏覽器上便顯示出了我們寫的 HTML 模板的內容
六、真正的Django博客首頁視圖
在此之前我們已經編寫了 Blog 的首頁視圖,並且配置了 URL 和模板,讓 Django 能夠正確地處理 HTTP 請求並返回合適的 HTTP 響應。不過我們僅僅在首頁返回了一句話:歡迎訪問我的博客。這是個 Hello World 級別的視圖函數,我們需要編寫真正的首頁視圖函數,當用戶訪問我們的博客首頁時,他將看到我們發表的博客文章列表。像前面演示的那樣
6.1.首頁視圖函數
上一節我們闡明瞭 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',{'post_list':post_list})
我們曾經在前面的章節講解過模型管理器 objects
的使用。這裡我們使用 all()
方法從資料庫里獲取了全部的文章,存在了 post_list
變數里。all
方法返回的是一個 QuerySet
(可以理解成一個類似於列表的數據結構),由於通常來說博客文章列表是按文章發表時間倒序排列的,即最新的文章排在最前面,所以我們緊接著調用了 order_by
方法對這個返回的 queryset 進行排序。排序依據的欄位是 created_time
,即文章的創建時間。-
號表示逆序,如果不加 -
則是正序。 接著如之前所做,我們渲染了 blog\index.html 模板文件,並且把包含文章列表數據的 post_list
變數傳給了模板。
6.2.處理靜態文件
我們的項目使用了從網上下載的一套博客模板 點擊這裡下載全套模板。這裡面除了HTML 文檔外,還包含了一些 CSS 文件和 JavaScript 文件以讓網頁呈現出我們現在看到的樣式。同樣我們需要對 Django 做一些必要的配置,才能讓 Django 知道如何在開發伺服器中引入這些 CSS 和 JavaScript 文件,這樣才能讓博客頁面的 CSS 樣式生效。
按照慣例,我們把 CSS 和 JavaScript 文件放在 blog 應用的 static\ 目錄下。因此,先在 blog 應用下建立一個 static 文件夾。同時,為了避免和其它應用中的 CSS 和 JavaScript 文件命名衝突(別的應用下也可能有和 blog 應用下同名的 CSS 、JavaScript 文件),我們再在 static\ 目錄下建立一個 blog 文件夾,把下載的博客模板中的 css 和 js 文件夾連同裡面的全部文件一同拷貝進這個目錄。目錄結構
用下載的博客模板中的 index.html 文件替換掉之前我們自己寫的 index.html 文件。如果你好奇,現在就可以運行開發伺服器,看看首頁是什麼樣子。
如圖所示,你會看到首頁顯示的樣式非常混亂,原因是瀏覽器無法正確載入 CSS 等樣式文件。需要以 Django 的方式來正確地處理 CSS 和 JavaScript 等靜態文件的載入路徑。CSS 樣式文件通常在 HTML 文檔的 head 標簽里引入,打開 index.html 文件,在文件的開始處找到 head 標簽包裹的內容,大概像這樣:
<head> <title>Black & 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>
CSS 樣式文件的路徑在 link 標簽的 href 屬性里,而 JavaScript 文件的路徑在 script 標簽的 src 屬性里。可以看到諸如 `href="css/bootstrap.min.css" 或者 src="js/jquery-2.1.3.min.js" 這樣的引用,由於引用文件的路徑不對,所以瀏覽器引入這些文件失敗。我們需要把它們改成正確的路徑。把代碼改成下麵樣子,正確地引入 static 文件下的 CSS 和 JavaScript 文件:
{% load staticfiles %} <!DOCTYPE html> <html> <head> <title>Black & White</title> <!-- meta --> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- css --> <link rel="stylesheet" href="{% static 'blog/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> <link rel="stylesheet" href="{% static 'blog/css/pace.css' %}"> <link rel="stylesheet" href="{% static 'blog/css/custom.css' %}"> <!-- js --> <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="{% static 'blog/js/script.js' %}"></script> </body> </html>
我們把引用路徑放在了一個奇怪的符號里,例如:href="{% static 'blog/css/bootstrap.min.css' %}"。用 {% %} 包裹起來的叫做模板標簽。我們前面說過用 {{ }} 包裹起來的叫做模板變數,其作用是在最終渲染的模板里顯示由視圖函數傳過來的變數值。而這裡我們使用的模板標簽的功能則類似於函數,例如這裡的 static
模板標簽,它把跟在後面的字元串 'css/bootstrap.min.css'
轉換成正確的文件引入路徑。這樣 css 和 js 文件才能被正確載入,樣式才能正常顯示。
為了能在模板中使用 {% static %} 模板標簽,別忘了在最頂部添加 {% load staticfiles %} 。static 模板標簽位於 staticfiles 模塊中,只有通過 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/'
。
註意這裡有一個 CSS 文件的引入
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
我們沒有使用模板標簽,因為這裡的引用的文件是一個外部文件,不是我們項目里 static\blog\css\ 目錄下的文件,因此無需使用模板標簽。
正確引入了靜態文件後樣式顯示正常了。
6.3修改模板
目前我們看到的只是模板中預先填充的一些數據,我們得讓它顯示從資料庫中獲取的文章數據。下麵來稍微改造一下模板:
在模板 index.html 中你會找到一系列 article 標簽:
templa