HelloDjango 第 07 篇:創作後台開啟,請開始你的表演!

  • 2019 年 10 月 3 日
  • 筆記

作者: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  電子郵件地址: [email protected]  Password:  Password (again):  Superuser created successfully.

注意:

在命令行輸入密碼時可能不會顯示輸入的字元,不要以為鍵盤壞了,照正常的方式輸入密碼即可。

在 admin 後台註冊模型

要在後台註冊我們自己創建的幾個模型,這樣 django admin 才能知道它們的存在,註冊非常簡單,只需要在 blogadmin.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 這個參數都位於第一個位置,但由於 ForeignKeyManyToManyField 第一個參數必須傳入其關聯的 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 公眾號,獲取更多開源項目的資料和內容