作者:HelloGitHub-追夢人物
文中涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫
在此之前我們完成了 django 部落格首頁檢視的編寫,我們希望首頁展示釋出的部落格文章列表,但是它卻抱怨:暫時還沒有釋出的文章!如它所言,我們確實還沒有釋出任何文章,本節我們將使用 django 自帶的 admin 後臺來發布我們的部落格文章。
建立 admin 後臺管理員賬戶
要想進入django admin 後臺,首先需要建立一個超級管理員賬戶。我們在 Django 遷移、運算元據庫 中已經建立了一個後臺賬戶,但如果你沒有按照前面的步驟建立賬戶的話,可以進入專案根目錄,執行 pipenv run python manage.py createsuperuser
命令新建一個:
> pipenv run python manage.py createsuperuser
使用者名稱 (leave blank to use 'yangxg'): admin
電子郵件地址: admin@example.com
Password:
Password (again):
Superuser created successfully.
注意:
在命令列輸入密碼時可能不會顯示輸入的字元,不要以為鍵盤壞了,照正常的方式輸入密碼即可。
在 admin 後臺註冊模型
要在後臺註冊我們自己建立的幾個模型,這樣 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)
執行開發伺服器,訪問 http://127.0.0.1:8000/admin/ ,就進入了到了django admin 後臺登入頁面,輸入剛才建立的管理員賬戶密碼就可以登入到後臺了。
可以看到我們剛才註冊的三個模型了,點選 Posts 後面的增加按鈕,將進入新增 Post 的頁面,也就是新增部落格文章。然後在相關的地方輸入一些測試用的內容,增加完後點選儲存,這樣文章就新增完畢了,你也可以多新增幾篇看看效果。注意每篇文章必須有一個分類,在新增文章時你可以選擇已有分類。如果資料庫中還沒有分類,在選擇分類時點選 Category 後面的 + 按鈕新增一個分類即可。
你可能想往文章內容中新增圖片,但目前來說還做不到。在支援 Markdown 語法部分中將介紹如何在文章中插入圖片的方法。
訪問 http://127.0.0.1:8000/ 首頁,你就可以看到你新增的文章列表了,下面是我所在環境的效果圖:
定製 admin 後臺
使用 admin 後臺的時候,我們發現了下面的一些體驗相關的問題:
- admin 後臺本身的頁面元素是已經漢化了的,但是我們自己的 blog 應用,以及 Post、Category、Tag 在頁面中顯示卻是英文的,以及釋出文章的時候,表單各欄位的 label 也是英文的。
- 在 admin 後臺的 post 列表頁面,我們只看到了文章的標題,但是我們希望它顯示更加詳細的資訊,例如作者、釋出時間、修改時間等。
- 新增文章時,所有資料都要自己手動填寫。但是,有些資料應該是自動生成。例如文章釋出時間 created_time 和修改時間 modified_time,應該在建立或者修改文章時自動生成,而不是手動控制。同時我們的部落格是單人部落格系統,釋出者肯定是文章作者,這個也應該自動設定為 admin 後臺的登入賬戶。
雖然 django 的 admin 應用開箱即用,但也提供了豐富的定製功能,這正是 django 吸引人的地方,下面我們根據需求來一個個定製。
漢化 blog 應用
首先來看一下需要漢化的地方,admin 首頁每個版塊代表一個 app,比如 BLOG 版塊表示 blog 應用,版塊標題預設顯示的就是應用名。應用版塊下包含了該應用全部已經註冊到 admin 後臺的 model,之前我們註冊了 Post、Category 和 Tag,所以顯示的是這三個 model,顯示的名字就是 model 的名字。如下圖所示:
其次是新增 post 頁面的表單,各個欄位的 label 由定義在 Post 類的 Field 名轉換而來,比如 Post 模型中定義了 title 欄位,則對應表單的 label 就是 Title。
首先是 BLOG 版塊的標題 BLOG,一個版塊代表一個應用,顯然這個標題使用應用名轉換而來,在 blog 應用下有一個 app.py 模組,其程式碼如下:
from django.apps import AppConfig
class BlogConfig(AppConfig):
name = 'blog'
這些是我們在執行 startapp 建立 blog 應用時自動生成的程式碼,可以看到有一個 BlogConfig
類,其繼承自 AppConfig
類,看名字就知道這是和應用配置有關的類。我們可以通過設定這個類中的一些屬性的值來配置這個應用的一些特性的。比如這裡的 name 是用來定義 app 的名字,需要和應用名保持一致,不要改。要修改 app 在 admin 後臺的顯示名字,新增 verbose_name 屬性。
class BlogConfig(AppConfig):
name = 'blog'
verbose_name = '部落格'
同時,我們此前在 settings 中註冊應用時,是直接註冊的 app 名字 blog,現在在 BlogConfig 類中對 app 做了一些配置,所以應該將這個類註冊進去:
INSTALLED_APPS = [
'django.contrib.admin',
...
'blog.apps.BlogConfig', # 註冊 blog 應用
]
再次登入後臺,就可以看到 BLOG 版塊的標題已經顯示為部落格了。
接下來是讓應用下注冊的 model 顯示為中文,既然應用是在 apps.py 中配置,那麼和 model 有關的配置應該去找相對應的 model 。配置 model 的一些特性是通過 model 的內部類 Meta
中來定義。比如對於 Post 模型,要讓他在 admin 後臺顯示為中文,如下:
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
同樣地,這裡通過 verbose_name
來指定對應的 model 在 admin 後臺的顯示名稱,這裡 verbose_name_plural
用來表示多篇文章時的複數顯示形式。英語中,如果有多篇文章,就會顯示為 Posts,表示複數,中文沒有複數表現形式,所以定義為和 verbose_name
一樣。
同樣的可以把 Tag 和 Category 也設定一下:
class Category(models.Model):
name = models.CharField(max_length=100)
class Meta:
verbose_name = '分類'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=100)
class Meta:
verbose_name = '標籤'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
在 admin 就可以看到漢化後的效果了。
然後就是修改 post 的表單的 label,label 由定義在 model 中的 Field 名轉換二來,所以在 Field 中修改。
class Post(models.Model):
title = models.CharField('標題', max_length=70)
body = models.TextField('正文')
created_time = models.DateTimeField('建立時間')
modified_time = models.DateTimeField('修改時間')
excerpt = models.CharField('摘要', max_length=200, blank=True)
category = models.ForeignKey(Category, verbose_name='分類', on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, verbose_name='標籤', blank=True)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE)
可以看到我們給每個 Field 都傳入了一個位置引數,引數值即為 field 應該顯示的名字(如果不傳,django 自動根據 field 名生成)。這個引數的名字也叫 verbose_name
,絕大部分 field 這個引數都位於第一個位置,但由於 ForeignKey
、ManyToManyField
第一個引數必須傳入其關聯的 Model,所以 category、tags 這些欄位我們使用了關鍵字引數 verbose_name
。
文章列表顯示更加詳細的資訊
在 admin 後臺的文章列表頁面,我們只看到了文章的標題,但是我們希望它顯示更加詳細的資訊,這需要我們來定製 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']
# 把新增的 Postadmin 也註冊進來
admin.site.register(Post, PostAdmin)
admin.site.register(Category)
admin.site.register(Tag)
重新整理 admin Post 列表頁面,可以看到顯示的效果好多了。
簡化新增文章的表單
接下來優化新增文章時,填寫表單資料的不合理的地方。文章的建立時間和修改時間應該根據當前時間自動生成,而現在是由人工填寫,還有就是文章的作者應該自動填充為後臺管理員使用者,那麼這些自動填充資料的欄位就不需要在新增文章的表單中出現了。
此前我們在 blog/admin.py 中定義了一個 PostAdmin
來配置 Post 在 admin 後臺的一些展現形式。list_display 屬性控制 Post 列表頁展示的欄位。此外還有一個 fields 屬性,則用來控制表單展現的欄位,正好符合我們的需求:
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
fields = ['title', 'body', 'excerpt', 'category', 'tags']
這裡 fields 中定義的欄位就是表單中展現的欄位。
接下來是填充建立時間,修改時間和文章作者的值。之前提到,文章作者應該自動設定為登入後臺釋出此文章的管理員使用者。釋出文章的過程實際上是一個 HTTP 請求過程,此前提到,django 將 HTTP 請求封裝在 HttpRequest 物件中,然後將其作為第一個引數傳給檢視函式(這裡我們沒有看到新增文章的檢視,因為 django admin 已經自動幫我們生成了),而如果使用者登入了我們的站點,那麼 django 就會將這個使用者例項繫結到 request.user 屬性上,我們可以通過 request.user 取到當前請求使用者,然後將其關聯到新建立的文章即可。
Postadmin 繼承自 ModelAdmin,它有一個 save_model 方法,這個方法只有一行程式碼:obj.save()。它的作用就是將此 Modeladmin 關聯註冊的 model 例項(這裡 Modeladmin 關聯註冊的是 Post)儲存到資料庫。這個方法接收四個引數,其中前兩個,一個是 request,即此次的 HTTP 請求物件,第二個是 obj,即此次建立的關聯物件的例項,於是通過複寫此方法,就可以將 request.user 關聯到建立的 Post 例項上,然後將 Post 資料再儲存到資料庫:
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
fields = ['title', 'body', 'excerpt', 'category', 'tags']
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)
最後還剩下文章的建立時間和修改時間需要填充,一個想法我們可以沿用上面的思路,複寫 save_model 方法,將建立的 post 物件關聯當前時間,但是這存在一個問題,就是這樣做的話只有通過 admin 後臺建立的文章才能自動關聯這些時間,但建立文章不一定是在 Admin,也可能通過命令列。這時候我們可以通過對 Post 模型的定製來達到目的。
首先,Model 中定義的每個 Field 都接收一個 default 關鍵字引數,這個引數的含義是,如果將 model 的例項儲存到資料庫時,對應的 Field 沒有設定值,那麼 django 會取這個 default 指定的預設值,將其儲存到資料庫。因此,對於文章建立時間這個欄位,初始沒有指定值時,預設應該指定為當前時間,所以剛好可以通過 default 關鍵字引數指定:
from django.utils import timezone
class Post(models.Model):
...
created_time = models.DateTimeField('建立時間', default=timezone.now)
...
這裡 default 既可以指定為一個常量值,也可以指定為一個可呼叫(callable)物件,我們指定 timezone.now 函式,這樣如果沒有指定 created_time 的值,django 就會將其指定為 timezone.now 函式呼叫後的值。timezone.now 是 django 提供的工具函式,返回當前時間。因為 timezone 模組中的函式會自動幫我們處理時區,所以我們使用的是 django 為我們提供的 timezone 模組,而不是 Python 提供的 datetime 模組來處理時間。
那麼修改時間 modified_time 可以用 default 嗎?答案是不能,因為雖然第一次儲存資料時,會根據預設值指定為當前時間,但是當模型資料第二次修改時,由於 modified_time 已經有值,即第一次的預設值,那麼第二次儲存時預設值就不會起作用了,如果我們不修改 modified_time 的值的話,其值永遠是第一次儲存資料庫時的預設值。
所以這裡問題的關鍵是每次儲存模型時,都應該修改 modified_time 的值。每一個 Model 都有一個 save 方法,這個方法包含了將 model 資料儲存到資料庫中的邏輯。通過覆寫這個方法,在 model 被 save 到資料庫前指定 modified_time 的值為當前時間不就可以了?程式碼如下:
from django.utils import timezone
class Post(models.Model):
...
def save(self, *args, **kwargs):
self.modified_time = timezone.now()
super().save(*args, **kwargs)
要注意在指定完 modified_time 的值後,別忘了呼叫父類的 save 以執行資料儲存回資料庫的邏輯。
歡迎關注 HelloGitHub 公眾號,獲取更多開源專案的資料和內容