Python+Django搭建自己的blog網(wǎng)站
一、前言
1.1.環(huán)境
python版本:3.6
Django版本:1.11.6
1.2.預覽效果
最終搭建的blog的樣子,基本上滿足需求了??蚣艽詈昧?,至于CSS,可以根據(jù)自己喜好隨意搭配。

二、建立博客應用
2.1.建立項目和應用
創(chuàng)建工程blogproject
python manage.py startproject blogproject
python manage.py startpapp blog
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.目錄結(jié)構(gòu)

三、創(chuàng)建blog的數(shù)據(jù)庫模型
3.1.設計博客的數(shù)據(jù)庫表結(jié)構(gòu)
博客最主要的功能就是展示我們寫的文章,它需要從某個地方獲取博客文章數(shù)據(jù)才能把文章展示出來,通常來說這個地方就是數(shù)據(jù)庫。我們把寫好的文章永久地保存在數(shù)據(jù)庫里,當用戶訪問我們的博客時,Django 就去數(shù)據(jù)庫里把這些數(shù)據(jù)取出來展現(xiàn)給用戶。
博客的文章應該含有標題、正文、作者、發(fā)表時間等數(shù)據(jù)。一個更加現(xiàn)代化的博客文章還希望它有分類、標簽、評論等。為了更好地存儲這些數(shù)據(jù),我們需要合理地組織數(shù)據(jù)庫的表結(jié)構(gòu)。
我們的博客初級版本主要包含博客文章,文章會有分類以及標簽。一篇文章只能有一個分類,但可以打上很多標簽。我們把分類和標簽做成單獨的數(shù)據(jù)庫表,再把文章和分類、標簽關(guān)聯(lián)起來。下面分別是分類和標簽的數(shù)據(jù)庫表:
分類id 分類名
1 python
2 Django
標簽id 標簽名
1 python學習
2 Django學習
3.2.編寫博客模型代碼
分類數(shù)據(jù)庫表:
# 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 類。模型的代碼通常寫在相關(guān)應用的 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 的數(shù)據(jù)類型,CharField 是字符型, CharField 的 max_length 參數(shù)指定其最大長度,超過這個長度的分類名就不能被存入數(shù)據(jù)庫。 ''' 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() # 這兩個列分別表示文章的創(chuàng)建時間和最后一次修改時間,存儲時間的字段用 DateTimeField 類型。 created_time = models.DateTimeField() modified_time = models.DateTimeField() # 文章摘要,可以沒有文章摘要,但默認情況下 CharField 要求我們必須存入數(shù)據(jù),否則就會報錯。 # 指定 CharField 的 blank=True 參數(shù)值后就可以允許空值了。 excerpt = models.CharField(max_length=200,blank=True) # 我們在這里把文章對應的數(shù)據(jù)庫表和分類、標簽對應的數(shù)據(jù)庫表關(guān)聯(lián)了起來,但是關(guān)聯(lián)形式稍微有點不同。 # 我們規(guī)定一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,所以我們使用的是 ForeignKey,即一對多的關(guān)聯(lián)關(guān)系。 # 而對于標簽來說,一篇文章可以有多個標簽,同一個標簽下也可能有多篇文章,所以我們使用 ManyToManyField,表明這是多對多的關(guān)聯(lián)關(guān)系。 # 同時我們規(guī)定文章可以沒有標簽,因此為標簽 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 內(nèi)置的應用,專門用于處理網(wǎng)站用戶的注冊、登錄等流程,User 是 Django 為我們已經(jīng)寫好的用戶模型。 # 這里我們通過 ForeignKey 把文章和 User 關(guān)聯(lián)了起來。 # 因為我們規(guī)定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關(guān)聯(lián)關(guān)系,和 Category 類似。 author = models.ForeignKey(User,on_delete=models.CASCADE)
四、遷移數(shù)據(jù)庫
4.1.設置數(shù)據(jù)庫為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', #數(shù)據(jù)庫名字
'USER': 'root', #賬號
'PASSWORD': '123456', #密碼
'HOST': '127.0.0.1', #IP
'PORT': '3306', #端口
}
}
# blog/__init__.py import pymysql pymysql.install_as_MySQLdb()
4.2.遷移數(shù)據(jù)庫
分別運行下面兩條命令
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations 后,Django 在 blog 應用的 migrations\ 目錄下生成了一個 0001_initial.py 文件,這個文件是 Django 用來記錄我們對模型做了哪些修改的文件。目前來說,我們在 models.py 文件里創(chuàng)建了 3 個模型類,Django 把這些變化記錄在了 0001_initial.py 里。不過此時還只是告訴了 Django 我們做了哪些改變,為了讓 Django 真正地為我們創(chuàng)建數(shù)據(jù)庫表,接下來又執(zhí)行了 python manage.py migrate 命令。Django 通過檢測應用中 migrations\ 目錄下的文件,得知我們對數(shù)據(jù)庫做了哪些操作,然后它把這些操作翻譯成數(shù)據(jù)庫操作語言,從而把這些操作作用于真正的數(shù)據(jù)庫。
你可以看到命令的輸出除了 Applying blog.0001_initial... OK 外,Django 還對其它文件做了操作。這是因為除了我們自己建立的 blog 應用外,Django 自身還內(nèi)置了很多應用,這些應用本身也是需要存儲數(shù)據(jù)的??梢栽?settings.py 的 INSTALLED_APP 設置里看到這些應用,當然我們目前不必關(guān)心這些。
#blogproject/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', ]
python manage.py sqlmigrate blog 0001
4.3.存數(shù)據(jù)
打開一個交互式命令行
python manage.py shell

我們首先導入 3 個之前寫好的模型類,然后實例化了一個 Category 類和一個 Tag 類,為他們的屬性 name 賦了值。為了讓 Django 把這些數(shù)據(jù)保存進數(shù)據(jù)庫,調(diào)用實例的 save方法即可。
創(chuàng)建文章之前,我們需要先創(chuàng)建一個 User,用于指定文章的作者。創(chuàng)建 User 的命令 Django 已經(jīng)幫我們寫好了,依然是通過 manage.py 來運行。首先exit()退出命令交互欄,運行 python manage.py createsuperuser 命令并根據(jù)提示創(chuàng)建用戶:用戶名,郵箱,密碼
再次運行 python manage.py shell 進入 Python 命令交互欄,開始創(chuàng)建文章:

由于我們重啟了 shell,因此需要重新導入了 Category、Tag、Post 以及 User。我們還導入了一個 Django 提供的輔助模塊 timezone,這是因為我們需要調(diào)用它的 now() 方法為 created_time 和 modified_time 指定時間,容易理解 now 方法返回當前時間。然后我們根據(jù)用戶名和分類名,通過 get 方法取出了存在數(shù)據(jù)庫中的 User 和 Category(取數(shù)據(jù)的方法將在下面介紹)。接著我們?yōu)槲恼轮付?nbsp;title、body 、created_time、modified_time值,并把它和前面創(chuàng)建的 Category 以及 User 關(guān)聯(lián)了起來。允許為空 excerpt、tags 我們就沒有為它們指定值了。
4.4.取數(shù)據(jù)
數(shù)據(jù)已經(jīng)存入數(shù)據(jù)庫了,現(xiàn)在要把它們?nèi)〕鰜砜纯矗?/p>

objects 是我們的模型管理器,它為我們提供一系列從數(shù)據(jù)庫中取數(shù)據(jù)方法,這里我們使用了 all 方法,表示我們要把對應的數(shù)據(jù)全部取出來??梢钥吹?nbsp;all 方法都返回了數(shù)據(jù),這些數(shù)據(jù)應該是我們之前存進去的,但是顯示的字符串有點奇怪,無法看出究竟是不是我們之前存入的數(shù)據(jù)。為了讓顯示出來的數(shù)據(jù)更加人性化一點,我們?yōu)?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 的數(shù)據(jù)類型,CharField 是字符型, CharField 的 max_length 參數(shù)指定其最大長度,超過這個長度的分類名就不能被存入數(shù)據(jù)庫。 ''' 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() # 這兩個列分別表示文章的創(chuàng)建時間和最后一次修改時間,存儲時間的字段用 DateTimeField 類型。 created_time = models.DateTimeField() modified_time = models.DateTimeField() # 文章摘要,可以沒有文章摘要,但默認情況下 CharField 要求我們必須存入數(shù)據(jù),否則就會報錯。 # 指定 CharField 的 blank=True 參數(shù)值后就可以允許空值了。 excerpt = models.CharField(max_length=200,blank=True) # 我們在這里把文章對應的數(shù)據(jù)庫表和分類、標簽對應的數(shù)據(jù)庫表關(guān)聯(lián)了起來,但是關(guān)聯(lián)形式稍微有點不同。 # 我們規(guī)定一篇文章只能對應一個分類,但是一個分類下可以有多篇文章,所以我們使用的是 ForeignKey,即一對多的關(guān)聯(lián)關(guān)系。 # 而對于標簽來說,一篇文章可以有多個標簽,同一個標簽下也可能有多篇文章,所以我們使用 ManyToManyField,表明這是多對多的關(guān)聯(lián)關(guān)系。 # 同時我們規(guī)定文章可以沒有標簽,因此為標簽 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 內(nèi)置的應用,專門用于處理網(wǎng)站用戶的注冊、登錄等流程,User 是 Django 為我們已經(jīng)寫好的用戶模型。 # 這里我們通過 ForeignKey 把文章和 User 關(guān)聯(lián)了起來。 # 因為我們規(guī)定一篇文章只能有一個作者,而一個作者可能會寫多篇文章,因此這是一對多的關(guān)聯(lián)關(guān)系,和 Category 類似。 author = models.ForeignKey(User,on_delete=models.CASCADE) def __str__(self): return self.title
定義好 __str__ 方法后,解釋器顯示的內(nèi)容將會是 __str__ 方法返回的內(nèi)容。這里 Category 返回分類名 name ,Tag 返回標簽名,而 Post 返回它的 title。
exit() 退出 Shell,再重新運行 python manage.py shell 進入 Shell。

可以看到返回的是我們之前存入的數(shù)據(jù)。
此外我們在創(chuàng)建文章時提到了通過 get 方法來獲取數(shù)據(jù),這里 all 方法和 get 方法的區(qū)別是:all 方法返回全部數(shù)據(jù),是一個類似于列表的數(shù)據(jù)結(jié)構(gòu)(QuerySet);而 get 返回一條記錄數(shù)據(jù),如有多條記錄或者沒有記錄,get 方法均會拋出相應異常。
五、博客首頁視圖
5.1.Django處理HTTP請求
Web 應用的交互過程其實就是 HTTP 請求與響應的過程。無論是在 PC 端還是移動端,我們通常使用瀏覽器來上網(wǎng),上網(wǎng)流程大致來說是這樣的:
我們打開瀏覽器,在地址欄輸入想訪問的網(wǎng)址,比如 http://www.cnblogs.com/。瀏覽器知道我們想要訪問哪個網(wǎng)址后,它在后臺幫我們做了很多事情。主要就是把我們的訪問意圖包裝成一個 HTTP 請求,發(fā)給我們想要訪問的網(wǎng)址所對應的服務器。通俗點說就是瀏覽器幫我們通知網(wǎng)站的服務器,說有人來訪問你啦,訪問的請求都寫在 HTTP 里了,你按照要求處理后告訴我,我再幫你回應他!服務器處理了HTTP 請求,然后生成一段 HTTP 響應給瀏覽器。瀏覽器解讀這個響應,把相關(guān)的內(nèi)容在瀏覽器里顯示出來,于是我們就看到了網(wǎng)站的內(nèi)容。比如你訪問了我的博客主頁http://www.cnblogs.com/derek1184405959/,服務器接收到這個請求后就知道用戶訪問的是首頁,首頁顯示的是全部文章列表,于是它從數(shù)據(jù)庫里把文章數(shù)據(jù)取出來,生成一個寫著這些數(shù)據(jù)的 HTML 文檔,包裝到 HTTP 響應里發(fā)給瀏覽器,瀏覽器解讀這個響應,把 HTML 文檔顯示出來,我們就看到了文章列表的內(nèi)容。
因此,Django 作為一個 Web 框架,它的使命就是處理流程中的第二步。即接收瀏覽器發(fā)來的 HTTP 請求,返回相應的 HTTP 響應。于是引出這么幾個問題:
Django 如何接收 HTTP 請求? Django 如何處理這個 HTTP 請求? Django 如何生成 HTTP 響應?
對于如何處理這些問題,Django 有其一套規(guī)定的機制。我們按照 Django 的規(guī)定,就能開發(fā)出所需的功能
Hello視圖函數(shù)
我們先以一個最簡單的 Hello World 為例來看看 Django 處理上述問題的機制是怎么樣的。
綁定url和視圖函數(shù)
首先 Django 需要知道當用戶訪問不同的網(wǎng)址時,應該如何處理這些不同的網(wǎng)址(即所說的路由)。Django 的做法是把不同的網(wǎng)址對應的處理函數(shù)寫在一個 urls.py 文件里,當用戶訪問某個網(wǎng)址時,Django 就去會這個文件里找,如果找到這個網(wǎng)址,就會調(diào)用和它綁定在一起的處理函數(shù)(叫做視圖函數(shù))。
下面是具體的做法,首先在 blog 應用的目錄下創(chuàng)建一個 urls.py 文件,在 blog\urls.py 中寫入這些代碼:
# blog/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$',views.index,name='index'), ]
url 函數(shù),又從當前目錄下導入了 views 模塊。然后我們把網(wǎng)址和處理函數(shù)的關(guān)系寫在了 urlpatterns 列表里。綁定關(guān)系的寫法是把網(wǎng)址和對應的處理函數(shù)作為參數(shù)傳給 url 函數(shù)(第一個參數(shù)是網(wǎng)址,第二個參數(shù)是處理函數(shù)),另外我們還傳遞了另外一個參數(shù) name,這個參數(shù)的值將作為處理函數(shù) index 的別名,這在以后會用到。
注意這里我們的網(wǎng)址是用正則表達式寫的,Django 會用這個正則表達式去匹配用戶實際輸入的網(wǎng)址,如果匹配成功,就會調(diào)用其后面的視圖函數(shù)做相應的處理。
比如說我們本地開發(fā)服務器的域名是 http://127.0.0.1:8000,那么當用戶輸入網(wǎng)址 http://127.0.0.1:8000 后,Django 首先會把協(xié)議 http、域名 127.0.0.1 和端口號 8000 去掉,此時只剩下一個空字符串,而 r'^$' 的模式正是匹配一個空字符串(這個正則表達式的意思是以空字符串開頭且以空字符串結(jié)尾),于是二者匹配,Django 便會調(diào)用其對應的 views.index 函數(shù)。
注意:在項目根目錄的 blogproject\ 目錄下(即 settings.py 所在的目錄),原本就有一個 urls.py 文件,這是整個工程項目的 URL 配置文件。而我們這里新建了一個 urls.py 文件,且位于 blog 應用下。這個文件將用于 blog 應用相關(guān)的 URL 配置。不要把兩個文件搞混了。
編寫視圖函數(shù)
第二步就是要實際編寫我們的 views.index 視圖函數(shù)了,按照慣例視圖函數(shù)定義在 views.py 文件里:
from django.shortcuts import HttpResponse
def index(request):
return HttpResponse('歡迎訪問我的博客')
這個兩行的函數(shù)體現(xiàn)了這個過程。它首先接受了一個名為 request 的參數(shù),這個 request就是 Django 為我們封裝好的 HTTP 請求,它是類 HttpRequest 的一個實例。然后我們便直接返回了一個 HTTP 響應給用戶,這個 HTTP 響應也是 Django 幫我們封裝好的,它是類 HttpResponse 的一個實例,只是我們給它傳了一個自定義的字符串參數(shù)。
瀏覽器接收到這個響應后就會在頁面上顯示出我們傳遞的內(nèi)容:歡迎訪問我的博客
配置項目URL
還差最后一步了,我們前面建立了一個 urls.py 文件,并且綁定了 URL 和視圖函數(shù) 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 函數(shù),然后利用這個函數(shù)把 blog 應用下的 urls.py 文件包含了進來。此外 include 前還有一個 r'',這是一個空字符串。這里也可以寫其它字符串,Django 會把這個字符串和后面 include 的 urls.py 文件中的 URL 拼接。比如說如果我們這里把 r'' 改成 r'blog/',而我們在 blog.urls 中寫的 URL 是 r'^$',即一個空字符串。那么 Django 最終匹配的就是 blog/ 加上一個空字符串,即 blog/。運行結(jié)果
運行 python manage.py runserver 打開開發(fā)服務器,在瀏覽器輸入開發(fā)服務器的地址 http://127.0.0.1:8000/,可以看到 Django 返回的內(nèi)容了。
5.2.使用Django模板系統(tǒng)
這基本上就上 Django 的開發(fā)流程了,寫好處理 HTTP 請求和返回 HTTP 響應的視圖函數(shù),然后把視圖函數(shù)綁定到相應的 URL 上。
但是等一等!我們看到在視圖函數(shù)里返回的是一個 HttpResponse 類的實例,我們給它傳入了一個希望顯示在用戶瀏覽器上的字符串。但是我們的博客不可能只顯示這么一句話,它有可能會顯示很長很長的內(nèi)容。比如我們發(fā)布的博客文章列表,或者一大段的博客文章。我們不能每次都把這些大段大段的內(nèi)容傳給 HttpResponse。
Django 對這個問題給我們提供了一個很好的解決方案,叫做模板系統(tǒng)。Django 要我們把大段的文本寫到一個文件里,然后 Django 自己會去讀取這個文件,再把讀取到的內(nèi)容傳給 HttpResponse。讓我們用模板系統(tǒng)來改造一下上面的例子。
首先在我們的項目根目錄(即 manage.py 文件所在目錄)下建立一個名為 templates 的文件夾,用來存放我們的模板。然后在 templates\ 目錄下建立一個名為 blog 的文件夾,用來存放和 blog 應用相關(guān)的模板。
當然模板存放在哪里是無關(guān)緊要的,只要 Django 能夠找到的就好。但是我們建立這樣的文件夾結(jié)構(gòu)的目的是把不同應用用到的模板隔離開來,這樣方便以后維護。我們在 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>
{{ title }},{{ welcome }}。這是 Django 規(guī)定的語法。用 {{ }} 包起來的變量叫做模板變量。Django 在渲染這個模板的時候會根據(jù)我們傳遞給模板的變量替換掉這些變量。最終在模板中顯示的將會是我們傳遞的值。模板寫好了,還得告訴 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 把這兩個路徑連起來,構(gòu)成完整的模板路徑,Django 就知道去這個路徑下面找我們的模板了。視圖函數(shù)可以改一下了:
from django.http import HttpResponse
from django.shortcuts import render
def index(request):
return render(request, 'blog/index.html', context={
'title': '我的博客首頁',
'welcome': '歡迎訪問我的博客首頁'
})
HttpResponse 了,而是調(diào)用 Django 提供的 render 函數(shù)。這個函數(shù)根據(jù)我們傳入的參數(shù)來構(gòu)造 HttpResponse。我們首先把 HTTP 請求傳了進去,然后 render 根據(jù)第二個參數(shù)的值 blog/index.html 找到這個模板文件并讀取模板中的內(nèi)容。之后 render 根據(jù)我們傳入的 context 參數(shù)的值把模板中的變量替換為我們傳遞的變量的值,{{ title }} 被替換成了 context 字典中 title對應的值,同理 {{ welcome }} 也被替換成相應的值。
最終,我們的 HTML 模板中的內(nèi)容字符串被傳遞給 HttpResponse 對象并返回給瀏覽器(Django 在 render 函數(shù)里隱式地幫我們完成了這個過程),這樣用戶的瀏覽器上便顯示出了我們寫的 HTML 模板的內(nèi)容
六、真正的Django博客首頁視圖
在此之前我們已經(jīng)編寫了 Blog 的首頁視圖,并且配置了 URL 和模板,讓 Django 能夠正確地處理 HTTP 請求并返回合適的 HTTP 響應。不過我們僅僅在首頁返回了一句話:歡迎訪問我的博客。這是個 Hello World 級別的視圖函數(shù),我們需要編寫真正的首頁視圖函數(shù),當用戶訪問我們的博客首頁時,他將看到我們發(fā)表的博客文章列表。像前面演示的那樣

6.1.首頁視圖函數(shù)
上一節(jié)我們闡明了 Django 的開發(fā)流程。即首先配置 URL,把 URL 和相應的視圖函數(shù)綁定,一般寫在 urls.py 文件里,然后在工程的 urls.py 文件引入。其次是編寫視圖函數(shù),視圖中需要渲染模板,我們也在 settings.py 中進行了模板相關(guān)的配置,讓 Django 能夠找到需要渲染的模板。最后把渲染完成的 HTTP 響應返回就可以了。相關(guān)的配置和準備工作都在之前完成了,這里我們只需專心編寫視圖函數(shù),讓它實現(xiàn)我們想要的功能即可。
首頁的視圖函數(shù)其實很簡單,代碼像這樣:
# 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() 方法從數(shù)據(jù)庫里獲取了全部的文章,存在了 post_list 變量里。all 方法返回的是一個 QuerySet(可以理解成一個類似于列表的數(shù)據(jù)結(jié)構(gòu)),由于通常來說博客文章列表是按文章發(fā)表時間倒序排列的,即最新的文章排在最前面,所以我們緊接著調(diào)用了 order_by 方法對這個返回的 queryset 進行排序。排序依據(jù)的字段是 created_time,即文章的創(chuàng)建時間。- 號表示逆序,如果不加 - 則是正序。 接著如之前所做,我們渲染了 blog\index.html 模板文件,并且把包含文章列表數(shù)據(jù)的 post_list 變量傳給了模板。6.2.處理靜態(tài)文件
我們的項目使用了從網(wǎng)上下載的一套博客模板 點擊這里下載全套模板。這里面除了HTML 文檔外,還包含了一些 CSS 文件和 JavaScript 文件以讓網(wǎng)頁呈現(xiàn)出我們現(xiàn)在看到的樣式。同樣我們需要對 Django 做一些必要的配置,才能讓 Django 知道如何在開發(fā)服務器中引入這些 CSS 和 JavaScript 文件,這樣才能讓博客頁面的 CSS 樣式生效。
按照慣例,我們把 CSS 和 JavaScript 文件放在 blog 應用的 static\ 目錄下。因此,先在 blog 應用下建立一個 static 文件夾。同時,為了避免和其它應用中的 CSS 和 JavaScript 文件命名沖突(別的應用下也可能有和 blog 應用下同名的 CSS 、JavaScript 文件),我們再在 static\ 目錄下建立一個 blog 文件夾,把下載的博客模板中的 css 和 js 文件夾連同里面的全部文件一同拷貝進這個目錄。目錄結(jié)構(gòu)

用下載的博客模板中的 index.html 文件替換掉之前我們自己寫的 index.html 文件。如果你好奇,現(xiàn)在就可以運行開發(fā)服務器,看看首頁是什么樣子。

如圖所示,你會看到首頁顯示的樣式非?;靵y,原因是瀏覽器無法正確加載 CSS 等樣式文件。需要以 Django 的方式來正確地處理 CSS 和 JavaScript 等靜態(tài)文件的加載路徑。CSS 樣式文件通常在 HTML 文檔的 head 標簽里引入,打開 index.html 文件,在文件的開始處找到 head 標簽包裹的內(nèi)容,大概像這樣:
<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" rel="external nofollow" rel="external nofollow" > <link rel="stylesheet" rel="external nofollow" rel="external nofollow" rel="external nofollow" > <link rel="stylesheet" href="css/pace.css" rel="external nofollow" > <link rel="stylesheet" href="css/custom.css" rel="external nofollow" > <!-- 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 屬性里??梢钥吹街T如 `href="css/bootstrap.min.css" rel="external nofollow" rel="external nofollow" 或者 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' %}" rel="external nofollow" rel="external nofollow" >
<link rel="stylesheet" rel="external nofollow" rel="external nofollow" rel="external nofollow" >
<link rel="stylesheet" href="{% static 'blog/css/pace.css' %}" rel="external nofollow" rel="external nofollow" >
<link rel="stylesheet" href="{% static 'blog/css/custom.css' %}" rel="external nofollow" >
<!-- 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>
<!-- 其它內(nèi)容 -->
<script src="{% static 'blog/js/script.js' %}"></script>
</body>
</html>
static 模板標簽,它把跟在后面的字符串 'css/bootstrap.min.css' 轉(zhuǎn)換成正確的文件引入路徑。這樣 css 和 js 文件才能被正確加載,樣式才能正常顯示。為了能在模板中使用 {% static %} 模板標簽,別忘了在最頂部添加 {% load staticfiles %} 。static 模板標簽位于 staticfiles 模塊中,只有通過 load 模板標簽將該模塊引入后,才能在模板中使用 {% static %} 標簽。
替換完成后你可以刷新頁面并看看網(wǎng)頁的源代碼,看一看 {% static %} 模板標簽在頁面渲染后究竟被替換成了什么樣的值。例如我們可以看到
<link rel="stylesheet" href="{% static 'blog/css/pace.css' %}" rel="external nofollow" rel="external nofollow" >
這一部分最終在瀏覽器中顯示的是:
<link rel="stylesheet" href="/static/blog/css/pace.css" rel="external nofollow" >
{% 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 文件的引入
我們沒有使用模板標簽,因為這里的引用的文件是一個外部文件,不是我們項目里 static\blog\css\ 目錄下的文件,因此無需使用模板標簽。
正確引入了靜態(tài)文件后樣式顯示正常了。

6.3修改模板
目前我們看到的只是模板中預先填充的一些數(shù)據(jù),我們得讓它顯示從數(shù)據(jù)庫中獲取的文章數(shù)據(jù)。下面來稍微改造一下模板:
在模板 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> ...
這里面包裹的內(nèi)容顯示的就是文章數(shù)據(jù)了。我們前面在視圖函數(shù) index 里給模板傳了一個 post_list 變量,它里面包含著從數(shù)據(jù)庫中取出的文章列表數(shù)據(jù)。就像 Python 一樣,我們可以在模板中循環(huán)這個列表,把文章一篇篇循環(huán)出來,然后一篇篇顯示文章的數(shù)據(jù)。要在模板中使用循環(huán),需要使用到前面提到的模板標簽,這次使用 {% 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">暫時還沒有發(fā)布的文章!</div>
{% endfor %}
...
可以看到語法和 Python 的 for 循環(huán)類似,只是被 {% %} 這樣一個模板標簽符號包裹著。{% empty %} 的作用是當 post_list 為空,即數(shù)據(jù)庫里沒有文章時顯示 {% empty %} 下面的內(nèi)容,最后我們用 {% endfor %} 告訴 Django 循環(huán)在這里結(jié)束了。
你可能不太理解模板中的 post 和 post_list 是什么。post_list 是一個 QuerySet(類似于一個列表的數(shù)據(jù)結(jié)構(gòu)),其中每一項都是之前定義在 blog\models.py 中的 Post 類的實例,且每個實例分別對應著數(shù)據(jù)庫中每篇文章的記錄。因此我們循環(huán)遍歷 post_list ,每一次遍歷的結(jié)果都保存在 post變量里。所以我們使用模板變量來顯示 post 的屬性值。例如這里的 {{ post.pk }}(pk 是 primary key 的縮寫,即 post 對應于數(shù)據(jù)庫中記錄的 id 值,該屬性盡管我們沒有顯示定義,但是 Django 會自動為我們添加)。
我們把標題替換成 post 的 title 屬性值。注意要把它包裹在模板變量里,因為它最終要被替換成實際的 title 值。
<h1 class="entry-title">
<a href="single.html" rel="external nofollow" >{{ post.title }}</a>
</h1>
下面這 5 個 span 標簽里分別顯示了分類(category)、文章發(fā)布時間、文章作者、評論數(shù)、閱讀量。
<div class="entry-meta">
<span class="post-category"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Django 博客教程</a></span>
<span class="post-date"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><time class="entry-date"
datetime="2012-11-09T23:15:57+00:00">2017年5月11日</time></a></span>
<span class="post-author"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >追夢人物</a></span>
<span class="comments-link"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >4 評論</a></span>
<span class="views-count"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >588 閱讀</a></span>
</div>
再次替換掉一些數(shù)據(jù),由于評論數(shù)和閱讀量暫時沒法替換,因此先留著,我們在之后實現(xiàn)了這些功能后再來修改它,目前只替換分類、文章發(fā)布時間、文章作者:
<div class="entry-meta">
<span class="post-category"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.category.name }}</a></span>
<span class="post-date"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><time class="entry-date"
datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
<span class="post-author"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.author }}</a></span>
<span class="comments-link"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >4 評論</a></span>
<span class="views-count"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >588 閱讀</a></span>
</div>
這里 p 標簽里顯示的是摘要
<div class="entry-content clearfix"> <p>免費、中文、零基礎(chǔ),完整的項目,基于最新版 Django 1.10 和 Python 3.5。帶你從零開始一步步開發(fā)屬于自己的博客網(wǎng)站,幫助你以最快的速度掌握 Django 開發(fā)的技巧...</p> <div class="read-more cl-effect-14"> <a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="more-link">繼續(xù)閱讀 <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="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="more-link">繼續(xù)閱讀 <span class="meta-nav">→</span></a>
</div>
</div>
再次訪問首頁,它顯示:暫時還沒有發(fā)布的文章!好吧,做了這么多工作,但是數(shù)據(jù)庫中其實還沒有任何數(shù)據(jù)呀!接下來我們就實際寫幾篇文章保存到數(shù)據(jù)庫里,看看顯示的效果究竟如何。
七、在Django Admin后臺發(fā)布文章
在此之前我們完成了 Django 博客首頁視圖的編寫,我們希望首頁展示發(fā)布的博客文章列表,但是它卻抱怨:暫時還沒有發(fā)布的文章!如它所言,我們確實還沒有發(fā)布任何文章,本節(jié)我們將使用 Django 自帶的 Admin 后臺來發(fā)布我們的博客文章。
7.1.在Django后臺注冊模型
前面已經(jīng)用python manage.py createsuperuser,創(chuàng)建了超級用戶。
要在后臺注冊我們自己創(chuàng)建的幾個模型,這樣 Django Admin 才能知道它們的存在,注冊非常簡單,只需要在 blog\admin.py 中加入下面的代碼:
# blog/admin.py from django.contrib import admin from .models import Post,Category,Tag admin.site.register(Post) admin.site.register(Category) admin.site.register(Tag)
運行開發(fā)服務器,訪問 http://127.0.0.1:8000/admin/ ,就進入了到了Django Admin 后臺登錄頁面,輸入剛才創(chuàng)建的管理員賬戶密碼就可以登錄到后臺了。

可以看到我們剛才注冊的三個模型了,點擊 Posts 后面的增加按鈕,將進入添加 Post 的頁面,也就是新增博客文章。然后在相關(guān)的地方輸入一些測試用的內(nèi)容,增加完后點擊保存,這樣文章就添加完畢了,你也可以多添加幾篇看看效果。注意每篇文章必須有一個分類,在添加文章時你可以選擇已有分類。如果數(shù)據(jù)庫中還沒有分類,在選擇分類時點擊 Category 后面的 + 按鈕新增一個分類即可。
訪問 http://127.0.0.1:8000/ 首頁,你就可以看到你添加的文章列表了,下面是我所在環(huán)境的效果圖:

7.2.定制Admin后臺
在 admin post 列表頁面,我們只看到了文章的標題,但是我們希望它顯示更加詳細的信息,這需要我們來定制 Admin 了,在 admin.py 添加如下代碼:
# blog/admin.py from django.contrib import admin from .models import Post,Category,Tag class PostAdmin(admin.ModelAdmin): list_display = ['title', 'created_time', 'modified_time', 'category', 'author'] admin.site.register(Post,PostAdmin) admin.site.register(Category) admin.site.register(Tag)
這里只是為了簡單地到達期望效果
刷新 Admin Post 列表頁面,可以看到顯示的效果好多了。

八、博客文章詳情頁
首頁展示的是所有文章的列表,當用戶看到感興趣的文章時,他點擊文章的標題或者繼續(xù)閱讀的按鈕,應該跳轉(zhuǎn)到文章的詳情頁面來閱讀文章的詳細內(nèi)容。現(xiàn)在讓我們來開發(fā)博客的詳情頁面,有了前面的基礎(chǔ),開發(fā)流程都是一樣的了:首先配置 URL,即把相關(guān)的 URL 和視圖函數(shù)綁定在一起,然后實現(xiàn)視圖函數(shù),編寫模板并讓視圖函數(shù)渲染模板。
8.1.設置文章詳情頁的url
回顧一下我們首頁視圖的 URL,在 blog\urls.py 文件里,我們寫了:
blog/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ]
首頁視圖匹配的 URL 去掉域名后其實就是一個空的字符串。對文章詳情視圖而言,每篇文章對應著不同的 URL。比如我們可以把文章詳情頁面對應的視圖設計成這個樣子:當用戶訪問 <網(wǎng)站域名>/post/1/ 時,顯示的是第一篇文章的內(nèi)容,而當用戶訪問 <網(wǎng)站域名>/post/2/ 時,顯示的是第二篇文章的內(nèi)容,這里數(shù)字代表了第幾篇文章,也就是數(shù)據(jù)庫中 Post 記錄的 id 值。下面依照這個規(guī)則來綁定 URL 和視圖:
# blog/urls.py from django.conf.urls import url from . import views app_name = 'blog' urlpatterns = [ url(r'^$',views.index,name='index'), url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail'), ]
Django 使用正則表達式來匹配用戶訪問的網(wǎng)址。這里 r'^post/(?P<pk>[0-9]+)/$' 整個正則表達式剛好匹配我們上面定義的 URL 規(guī)則。這條正則表達式的含義是,以 post/ 開頭,后跟一個至少一位數(shù)的數(shù)字,并且以 / 符號結(jié)尾,如 post/1/、 post/255/ 等都是符合規(guī)則的,[0-9]+ 表示一位或者多位數(shù)。此外這里 (?P<pk>[0-9]+) 表示命名捕獲組,其作用是從用戶訪問的 URL 里把括號內(nèi)匹配的字符串捕獲并作為關(guān)鍵字參數(shù)傳給其對應的視圖函數(shù) detail。比如當用戶訪問 post/255/ 時(注意 Django 并不關(guān)心域名,而只關(guān)心去掉域名后的相對 URL),被括起來的部分 (?P<pk>[0-9]+) 匹配 255,那么這個 255 會在調(diào)用視圖函數(shù) detail 時被傳遞進去,實際上視圖函數(shù)的調(diào)用就是這個樣子:detail(request, pk=255)。我們這里必須從 URL 里捕獲文章的 id,因為只有這樣我們才能知道用戶訪問的究竟是哪篇文章。
此外我們通過 app_name='blog' 告訴 Django 這個 urls.py 模塊是屬于 blog 應用的,這種技術(shù)叫做視圖函數(shù)命名空間。我們看到 blog\urls.py 目前有兩個視圖函數(shù),并且通過 name 屬性給這些視圖函數(shù)取了個別名,分別是 index、detail。但是一個復雜的 Django 項目可能不止這些視圖函數(shù),例如一些第三方應用中也可能有叫 index、detail 的視圖函數(shù),那么怎么把它們區(qū)分開來,防止沖突呢?方法就是通過 app_name 來指定命名空間,命名空間具體如何使用將在下面介紹。
為了方便地生成上述的 URL,我們在 Post 類里定義一個 get_absolute_url方法,注意 Post 本身是一個 Python 類,在類中我們是可以定義任何方法的。
blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
...
def __str__(self):
return self.title
# 自定義 get_absolute_url 方法
# 記得從 django.urls 中導入 reverse 函數(shù)
def get_absolute_url(self):
return reverse('blog:detail', kwargs={'pk': self.pk})
注意到 URL 配置中的 url(r'^post/(?P<pk>[0-9]+)/$', views.detail, name='detail') ,我們設定的 name='detail' 在這里派上了用場??吹竭@個 reverse 函數(shù),它的第一個參數(shù)的值是 'blog:detail',意思是 blog 應用下的 name=detail 的函數(shù),由于我們在上面通過 app_name = 'blog' 告訴了 Django 這個 URL 模塊是屬于 blog 應用的,因此 Django 能夠順利地找到 blog 應用下 name 為 detail 的視圖函數(shù),于是 reverse 函數(shù)會去解析這個視圖函數(shù)對應的 URL,我們這里 detail 對應的規(guī)則就是 post/(?P<pk>[0-9]+)/這個正則表達式,而正則表達式部分會被后面?zhèn)魅氲膮?shù) pk 替換,所以,如果 Post 的 id(或者 pk,這里 pk 和 id 是等價的) 是 255 的話,那么 get_absolute_url 函數(shù)返回的就是 /post/255/ ,這樣 Post 自己就生成了自己的 URL。
8.2.編寫detail視圖函數(shù)
接下來就是實現(xiàn)我們的 detail 視圖函數(shù)了:
# blog/views.py
from django.shortcuts import render,get_object_or_404
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})
def detail(request,pk):
post = get_object_or_404(Post,pk=pk)
return render(request,'blog/detail.html',{'post':post})
視圖函數(shù)很簡單,它根據(jù)我們從 URL 捕獲的文章 id(也就是 pk,這里 pk 和 id 是等價的)獲取數(shù)據(jù)庫中文章 id 為該值的記錄,然后傳遞給模板。注意這里我們用到了從 django.shortcuts 模塊導入的 get_object_or_404 方法,其作用就是當傳入的 pk 對應的 Post 在數(shù)據(jù)庫存在時,就返回對應的 post,如果不存在,就給用戶返回一個 404 錯誤,表明用戶請求的文章不存在。
8.3.編寫詳情頁模板
接下來就是書寫模板文件,從下載的博客模板中把 single.html 拷貝到 templates\blog 目錄下(和 index.html 在同一級目錄),然后改名為 detail.html。
在 index 頁面博客文章列表的標題和繼續(xù)閱讀按鈕寫上超鏈接跳轉(zhuǎn)的鏈接,即文章 post 對應的詳情頁的 URL,讓用戶點擊后可以跳轉(zhuǎn)到 detail 頁面:
<article class="post post-{{ post.pk }}">
<header class="entry-header">
<h1 class="entry-title">
<a href="{{ post.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.title }}</a>
</h1>
<div class="entry-meta">
<span class="post-category"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.category.name }}</a></span>
<span class="post-date"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><time class="entry-date"
datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
<span class="post-author"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.author }}</a></span>
<span class="comments-link"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >4 評論</a></span>
<span class="views-count"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >588 閱讀</a></span>
</div>
</header>
<div class="entry-content clearfix">
<p>{{ post.excerpt }}</p>
<div class="read-more cl-effect-14">
<a href="{{ post.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="more-link">繼續(xù)閱讀 <span class="meta-nav">→</span></a>
</div>
</div>
</article>
這里我們修改兩個地方,第一個是文章標題處:
<h1 class="entry-title">
<a href="{{ post.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.title }}</a>
</h1>
我們把 a 標簽的 href 屬性的值改成了 {{ post.get_absolute_url }}?;仡櫼幌履0遄兞康挠梅?,由于 get_absolute_url 這個方法(我們定義在 Post 類中的)返回的是 post 對應的 URL,因此這里 {{ post.get_absolute_url }} 最終會被替換成該 post 自身的 URL。
同樣,第二處修改的是繼續(xù)閱讀按鈕的鏈接:
<a href="{{ post.get_absolute_url }}" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="more-link">繼續(xù)閱讀 <span class="meta-nav">→</span>
</a>
這樣當我們點擊首頁文章的標題或者繼續(xù)閱讀按鈕后就會跳轉(zhuǎn)到該篇文章對應的詳情頁面了。然而如果你嘗試跳轉(zhuǎn)到詳情頁后,你會發(fā)現(xiàn)樣式是亂的。這在 真正的 Django 博客首頁 時講過,由于我們是直接復制的模板,還沒有正確地處理靜態(tài)文件。我們可以按照介紹過的方法修改靜態(tài)文件的引入路徑,但很快你會發(fā)現(xiàn)在任何頁面都是需要引入這些靜態(tài)文件,如果每個頁面都要修改會很麻煩,而且代碼都是重復的。下面就介紹 Django 模板繼承的方法來幫我們消除這些重復操作。
8.4.模板繼承
我們看到 index.html 文件和 detail.html 文件除了 main 標簽包裹的部分不同外,其它地方都是相同的,我們可以把相同的部分抽取出來,放到 base.html 里。首先在 templates\ 目錄下新建一個 base.html 文件
把 index.html 的內(nèi)容全部拷貝到 base.html 文件里,然后刪掉 main 標簽包裹的內(nèi)容,替換成如下的內(nèi)容。
templates/base.html
...
<main class="col-md-8">
{% block main %}
{% endblock main %}
</main>
<aside class="col-md-4">
{% block toc %}
{% endblock toc %}
...
</aside>
...
這里 block 也是一個模板標簽,其作用是占位。比如這里的 {% block main %}{% endblock main %} 是一個占位框,main 是我們給這個 block 取的名字。下面我們會看到 block 標簽的作用。同時我們也在 aside 標簽下加了一個 {% block toc %}{% endblock toc %} 占位框,因為 detail.html 中 aside 標簽下會多一個目錄欄。當 {% block toc %}{% endblock toc %} 中沒有任何內(nèi)容時,{% block toc %}{% endblock toc %} 在模板中不會顯示。但當其中有內(nèi)容是,模板就會顯示 block 中的內(nèi)容。
在 index.html 里,我們在文件最頂部使用 {% extends 'base.html' %} 繼承 base.html,這樣就把 base.html 里的代碼繼承了過來,另外在 {% block main %}{% endblock main %} 包裹的地方填上 index 頁面應該顯示的內(nèi)容:
templates/blog/index.html
{% extends 'blog/base.html' %}
{% block main %}
{% for post in post_list %}
<article class="post post-1">
...
</article>
{% empty %}
<div class="no-post">暫時沒有發(fā)布文章!</div>
{% endfor %}
<!-- 簡單分頁效果
<div class="pagination-simple">
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >上一頁</a>
<span class="current">第 6 頁 / 共 11 頁</span>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >下一頁</a>
</div>
-->
<div class="pagination">
...
</div>
{% endblock main %}
這樣 base.html 里的代碼加上 {% block main %}{% endblock main %} 里的代碼就和最開始 index.html 里的代碼一樣了。這就是模板繼承的作用,公共部分的代碼放在 base.html 里,而其它頁面不同的部分通過替換 {% block main %}{% endblock main %} 占位標簽里的內(nèi)容即可。
如果你對這種模板繼承還是有點糊涂,可以把這種繼承和 Python 中類的繼承類比。base.html 就是父類,index.html 就是子類。index.html 繼承了 base.html 中的全部內(nèi)容,同時它自身還有一些內(nèi)容,這些內(nèi)容就通過 “覆寫” {% block main %}{% endblock main %}(把 block 看做是父類的屬性)的內(nèi)容添加即可。
detail 頁面處理起來就簡單了,同樣繼承 base.html ,在 {% block main %}{% endblock main %} 里填充 detail.html 頁面應該顯示的內(nèi)容,以及在 {% block toc %}{% endblock toc %} 中填寫 base.html 中沒有的目錄部分的內(nèi)容。不過目前的目錄只是占位數(shù)據(jù),我們在以后會實現(xiàn)如何從文章中自動摘取目錄。
templates/blog/detail.html
{% extends 'blog/base.html' %}
{% block main %}
<article class="post post-1">
...
</article>
<section class="comment-area">
...
</section>
{% endblock main %}
{% block toc %}
<div class="widget widget-content">
<h3 class="widget-title">文章目錄</h3>
<ul>
<li>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >教程特點</a>
</li>
<li>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >誰適合這個教程</a>
</li>
<li>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >在線預覽</a>
</li>
<li>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >資源列表</a>
</li>
<li>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >獲取幫助</a>
</li>
</ul>
</div>
{% endblock toc %}
修改 article 標簽下的一些內(nèi)容,讓其顯示文章的實際數(shù)據(jù):
<article class="post post-{{ post.pk }}">
<header class="entry-header">
<h1 class="entry-title">{{ post.title }}</h1>
<div class="entry-meta">
<span class="post-category"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.category.name }}</a></span>
<span class="post-date"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><time class="entry-date"
datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
<span class="post-author"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >{{ post.author }}</a></span>
<span class="comments-link"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >4 評論</a></span>
<span class="views-count"><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >588 閱讀</a></span>
</div>
</header>
<div class="entry-content clearfix">
{{ post.body }}
</div>
</article>
再次從首頁點擊一篇文章的標題或者繼續(xù)閱讀按鈕跳轉(zhuǎn)到詳情頁面,可以看到預期效果了!

九、支持Markdown語法和代碼高亮
為了讓博客文章具有良好的排版,顯示更加豐富的格式,我們使用 Markdown 語法來書寫我們的博文。Markdown 是一種 HTML 文本標記語言,只要遵循它約定的語法格式,Markdown 的渲染器就能夠把我們寫的文章轉(zhuǎn)換為標準的 HTML 文檔,從而讓我們的文章呈現(xiàn)更加豐富的格式,例如標題、列表、代碼塊等等 HTML 元素。由于 Markdown 語法簡單直觀,不用超過 5 分鐘就可以掌握常用的標記語法,因此大家青睞使用 Markdown 書寫 HTML 文檔。下面讓我們的博客也支持使用 Markdown 書寫。
- 開啟Django博客的RSS功能的實現(xiàn)方法
- Django 博客實現(xiàn)簡單的全文搜索的示例代碼
- 基于Django統(tǒng)計博客文章閱讀量
- Python使用Django實現(xiàn)博客系統(tǒng)完整版
- 使用python和Django完成博客數(shù)據(jù)庫的遷移方法
- 利用Celery實現(xiàn)Django博客PV統(tǒng)計功能詳解
- Python采用Django開發(fā)自己的博客系統(tǒng)
- Python Django搭建網(wǎng)站流程圖解
- Django如何實現(xiàn)網(wǎng)站注冊用戶郵箱驗證功能
- python3.6+django2.0+mysql搭建網(wǎng)站過程詳解
- 用Django寫天氣預報查詢網(wǎng)站
- 利用django創(chuàng)建一個簡易的博客網(wǎng)站的示例
相關(guān)文章
python使用numpy中的size()函數(shù)實例用法詳解
在本篇文章里小編給整理的是一篇關(guān)于python使用numpy中的size()函數(shù)實例用法詳解內(nèi)容,有興趣的朋友們可以學習下。2021-01-01
python中django框架通過正則搜索頁面上email地址的方法
這篇文章主要介紹了python中django框架通過正則搜索頁面上email地址的方法,涉及django框架及正則表達式的使用技巧,需要的朋友可以參考下2015-03-03

