利用 Django admin 完成更多任務
Django admin
Django 為未來的開發人員提供了許多功能:一個成熟的標准庫,一個活躍的用戶社區,以及 Python 語言的所有好處。雖然其他 Web 框架也聲稱能提供同樣的內容,但 Django 的獨特之處在於它內置了管理應用程序 —— admin。
admin 提供了開箱即用的高級 Create-Read-Update-Delete (CRUD) 功能,減少了重復工作所需的時間。這是許多 Web 應用程序的關鍵所在,程序員可以在開發時快速瀏覽他們的數據庫模型;非技術最終用戶可以在部署時使用 admin 添加和編輯站點內容。
在現實中,總需要執行某些定制操作。關於 admin 外觀的基本情況,Django 文檔提供許多指南,Django 自身也包含了一些簡單的方法用來改寫 admin 行為的子集。如果您需要更多功能怎么辦呢?從哪里開始着手呢?本文將指導您如何進行一些高級 adimin 定制。
admin 快速瀏覽
大多數 Django 開發人員都很熟悉 admin 的默認功能。讓我們快速回顧一下,首先編輯頂級 urls.py 啟用 admin,見清單 1。
清單 1. 在 urls.py 中啟用 admin
|
1
2
3
4
5
6
7
8
9
|
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
(r'^admin/(.*)', admin.site.root),
)
|
您還需要將 django.contrib.admin 應用程序添加到 settings.INSTALLED_APPS。
在繼續下一步前,建議計划擴展 admin 的用戶熟悉一下源代碼。對於支持快捷鍵和符號鏈接的操作系統,創建一個指向 admin 應用程序的快捷鍵或符號鏈接會很有用。
admin 包含在 Django 包中。假如已經使用安裝工具安裝了 admin,則它位於 django/contrib/admin 下的 site-packages 中。以下是一個項目到 Django admin 源的符號鏈接樣例,您可以根據操作系統和 Django 安裝的位置定制,以便更輕松的復制和引用:
$ ln -s /path/to/Python/install/site-packages/django/contrib/admin admin-source
admin.autodiscover() 方法迭代設置 .INSTALLED_APPS 中的每個應用程序,並查找名為 admin.py 的文件。該文件通常位於應用程序目錄的最上方,與 models.py 級別一樣。
樣例應用程序需要清單 2 中提供的 models.py。相應的 admin.py 如下所示。
清單 2. 該應用程序的樣例 models.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from django.db import models
class Document(models.Model):
'''A Document is a blog post or wiki entry with some text content'''
name = models.CharField(max_length=255)
text = models.TextField()
def __unicode__(self):
return self.name
class Comment(models.Model):
'''A Comment is some text about a given Document'''
document = models.ForeignKey(Document, related_name='comments')
text = models.TextField()
|
現在,您可以通過運行 Django 開發服務器調用 admin:
|
1
|
python manage.py runserver
|
admin 可從默認位置 http://localhost:8000/admin/ 獲取。登錄之后,您可以看到基本的 admin 屏幕,如下所示。
圖 1. 基本的 Django admin 屏幕

注意,您的模型現在尚不可用,因為您還沒有創建 admin.py。清單 3 展示的代碼讓您能在 admin 中使用模型。
清單 3. 樣例 admin.py
|
1
2
3
4
5
6
7
8
9
10
11
|
from django.contrib import admin
from more_with_admin.examples import models
class DocumentAdmin(admin.ModelAdmin):
pass
class CommentAdmin(admin.ModelAdmin):
pass
admin.site.register(models.Document, DocumentAdmin)
admin.site.register(models.Comment, CommentAdmin)
|
現在如果您在 admin 中重載主頁,您將看到可用的新模型,如下所示。
圖 2. 可以支持定制模型的 Django admin

定制 admin 模型頁面
理解在不修改 Django 源代碼的情況下如何定制 admin 的關鍵在於,記住 admin 像其他程序一樣只是一個普通的 Django 應用程序。最重要的一點是,這意味着可以使用 Django 模版繼承系統。
Django 的模版搜索順序總是將您自己項目的模版排在其他系統之前。此外,admin 在恢復到默認情況前,會嘗試搜索匹配每個模型的硬編碼模版。這為輕松定制提供了一個擴展點。
首先,確保 Django 通過編輯項目的 settings.py 來查看您的模版目錄。
清單 4. 編輯 settings.py 以查看模版目錄
|
1
2
3
4
|
TEMPLATE_DIRS = (
"/path/to/project/more_with_admin/templates",
"/path/to/project/more_with_admin/examples/templates",
)
|
然后在項目中創建以下目錄:
|
1
2
|
$ mkdir templates/admin/examples/document/
$ mkdir templates/admin/examples/comment/
|
Django admin 的特殊行為將檢查目錄和應用程序名稱(這里是 examples),然后是模型的名稱(document 和 comment),然后才能使用系統模版呈現該管理頁面。
重寫單個模型添加/編輯頁面
admin 用來添加和編輯模型實例的頁面名稱是 change_form.html。首先在 Document 模型目錄中創建一個名為 templates/admin/examples/document/change_form.html 的頁面,然后將 Django 模版繼承線置入其中:{% extends "admin/change_form.html" %}。
現在可以進行定制了。花一些時間熟悉實際的 admin/change_form.html 的內容。它很合理地將一些可以重寫的模板塊組織到一起,但是有些定制可能需要大量復制模塊。不過,使用基於塊的模板重寫總是比復制整個頁面要好。
您想對添加/編輯頁面執行哪種定制?對於系統中的每個 Document,您應該展示 5 個最近評論的預覽。
首先,創建一些樣例內容。
清單 5. 使用 Django shell 創建帶幾個評論的樣例Document
|
1
2
3
4
5
6
|
$ python manage.py shell
In [1]: from examples import models
In [2]: d = models.Document.objects.create(name='Test document',
text='This is a test document.')
In [3]: for c in range(0, 10):
...: models.Comment.objects.create(text='Comment number %s' % c, document=d)
|
現在,admin 列表頁面展示一個 Document。選擇該 Document 打開默認的添加/編輯頁面,如下所示。
圖 3. 帶有 Document 的默認添加/編輯頁面

注意,相關評論不顯示。在 admin 中顯示相關模型的標准方法是使用強大的 Inline 類。Inline 類允許 admin 用戶在單個頁面編輯或添加多個相關模型。要查看運行的 inline,按照清單 6 編輯應用程序 admin.py。
清單 6. 向 Document admin 添加相關模型評論
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from django.contrib import admin
from more_with_admin.examples import models
class CommentInline(admin.TabularInline):
model = models.Comment
class DocumentAdmin(admin.ModelAdmin):
inlines = [CommentInline,]
class CommentAdmin(admin.ModelAdmin):
pass
admin.site.register(models.Document, DocumentAdmin)
admin.site.register(models.Comment, CommentAdmin)
|
圖 4 展示了添加 TabularInline 控件之后新的添加/編輯頁面。
圖 4. 將評論模型作為 Inline 添加之后的添加/編輯頁面

這無疑非常強大,但是如果只想快速預覽評論的話就沒必要這樣做了。
這里您可以采取兩種方法。一種是使用 Django admin widget 接口編輯與 inline 關聯的 HTML widget;Django 文檔詳細描述了 widget。另一種方法是直接修改添加/編輯頁面。如果不希望使用任何特定於 admin 的功能,那么該方法非常有用。
如果不允許編輯評論(可能是由於用戶沒有足夠的權限),但是又想 讓用戶看到評論,那么可以修改 change_form.html。
Django admin 提供的變量
要在模型實例頁面添加功能,您需要了解 admin 已經可以使用什么數據。兩個鍵變量的說明如下。
表 1. 定制 admin 模版需要的變量
為 admin 頁面中的內容創建模板標記
列出無法直接輸入 Django 模板的相關評論查詢代碼。最佳的解決方案是使用模板標記。首先,創建模板標記目錄 and __init__.py 文件:
|
1
2
|
$ mkdir examples/templatetags/
$ touch examples/templatetags/__init__.py
|
創建一個名為 examples/templatetags/example_tags.py 的新文件,並添加以下代碼。
清單 7. 根據給定 Document ID 檢索評論的模板標簽
|
1
2
3
4
5
6
7
8
9
10
|
from django import template
from examples import models
register = template.Library()
@register.inclusion_tag('comments.html')
def display_comments(document_id):
document = models.Document.objects.get(id__exact=document_id)
comments = models.Comment.objects.filter(document=document)[0:5]
return { 'comments': comments }
|
由於這是一個包含標簽,您需要創建相應的模板文件:comments.html。編輯 examples/templates/comments.html 文件並輸入清單 8 中的代碼。
清單 8. 顯示評論預覽集的模板
|
1
2
3
|
{% for comment in comments %}
<
blockquote
>{{ comment.text }}</
blockquote
>
{% endfor %}
|
現在可以將它添加到 admin 頁面了。在 admin.py 中注釋掉對 CommentInline 的引用,並按照清單 9 更改 change_form.html 的本地版本。
清單 9. 在添加/編輯頁面包含模板標簽
|
1
2
3
4
5
6
7
|
{% extends "admin/change_form.html" %}
{% load example_tags %}
{% block after_field_sets %}
{% if object_id %}{% display_comments object_id %}{% endif %}
{% endblock %}
|
在使用前檢查 object_id 的存在很重要,因為 change_form.html 還可以用來創建新實例,在這種情況下 object_id 不可用。after_field_sets 塊只是 admin 中提供的眾多擴展點之一。其他請參考 change_form.html 源頁面。
圖 5 展示了更新后的表格。
圖 5. 包含模板標記之后的添加/編輯頁面

修改 admin 行為
模板重寫只能做這么多了。如果您想更改 admin 的實際流和行為怎么辦呢?修改源代碼不是不可能,但是那會讓您受制於更新時使用的特定 Django 版本。
重寫 AdminModel 方法
默認情況下,在 admin 中單擊 Save 將用戶帶回到列表頁面。通常這沒有問題,但是如果您想直接到 admin 外部的對象預覽頁面,那應該怎么辦?在開發內容管理系統 (CMS) 時這種情況很常見。
admin 應用程序中的大部分功能都附加到 admin.ModelAdmin 類。這是該對象從 admin.py 中繼承的類。您可以重寫許多許多公開方法。類定義請查看 admin-source/options.py 中的源代碼。
有兩種方法可以更改 Save 按鈕的行為:您可以重寫 admin.ModelAdmin.response_add,該按鈕負責保存后的實際重定向;還可以重寫 admin.ModelAdmin.change_view。后一種方式更為簡單。
清單 10. 保存事件之后重寫指向用戶的頁面
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class DocumentAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(DocumentAdmin, self).change_view(request, object_id, extra_context)
document = models.Document.objects.get(id__exact=object_id)
if not request.POST.has_key('_addanother') and
not request.POST.has_key('_continue'):
result['Location'] = document.get_absolute_url()
return result
|
現在用戶單擊 Save 時,他們將被指向預覽頁面,而不是展示所有 Documents 的列表頁面。
使用 signals 向 admin 添加功能
signals 是 Django 中較少使用的功能,它可以提高代碼的模塊化程度。signals 定義保存模型或加載模板的事件,無論它在哪里運行,Django 項目都可以偵聽到並對它做出反應。這意味着您可以輕松的提高應用程序的行為,而無需直接修改它們。
admin 提供了一個應用程序開發人員經常想修改的功能:通過 django.contrib.auth.models.User 類管理用戶。Django 用戶往往只能添加或修改 admin,這使得這個有用的類很難定制。
想象一下,您希望每次創建一個新的 User 對象時,站點管理員都能收到一封電子郵件。因為 User 模塊無法直接在項目中使用,實現該目標的唯一方法似乎是子類化 User 或者使用間接方法,比如創建虛擬配置文件對象進行修改。
清單 11 展示了在保存 User 實例時添加運行的函數有多么簡單。signals 通常被添加到 models.py。
清單 11. 添加新用戶時使用 Django signals 進行通知
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
from django.core.mail import send_mail
class Document(models.Model):
[...]
class Comment(models.Model):
[...]
def notify_admin(sender, instance, created, **kwargs):
'''Notify the administrator that a new user has been added.'''
if created:
subject = 'New user created'
message = 'User %s was added' % instance.username
from_addr = 'no-reply@example.com'
recipient_list = ('admin@example.com',)
send_mail(subject, message, from_addr, recipient_list)
signals.post_save.connect(notify_admin, sender=User)
|
post_save signal 由 Django 提供,每次保存或創建模型時都會激活。connect() 方法帶有兩個參數:一個回調參數(notify_admin)和 sender 參數,后者指定該回調只關注 User 模型的保存事件。
在回調中,post_save signal 傳遞發送方(模型類)、該模型的實例和提示是否剛剛創建了實例的布爾值。在本例中,如果創建了 User,該方法將發送一封電子郵件;否則不執行任何操作。
有關其他 Django 提供的 signals 列表,請參見 參考資料,以及介紹如何編寫 signals 的文檔。
進一步修改:添加低級權限
一個常用的 Django admin 特性是它的權限系統,該系統可以擴展以包含低級權限。默認情況下,admin 可以細粒度控制角色和權限,但是這些角色僅應用於類級別:用戶可以修改所有 Document 或不修改任何 Document。
往往只允許用戶修改特定的對象。這常常稱為低級 權限,因為它們只能修改特定數據庫表格行,而綜合權限可以修改表格中的任何記錄。樣例應用程序中的情況可能是您只希望用戶看到他們創建的 Document。
首先,更新 models.py 以包含創建 Document 的屬性記錄,如下所示。
清單 12. 更新 models.py 以記錄創建每個 Document 的用戶
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
from django.core.mail import send_mail
class Document(models.Model):
name = models.CharField(max_length=255)
text = models.TextField()
added_by = models.ForeignKey(User,
null=True, blank=True)
def get_absolute_url(self):
return 'http://example.com/preview/document/%d/' % self.id
def __unicode__(self):
return self.name
[...]
|
接下來,需要添加代碼自動記錄哪個用戶創建了 Document。signals 無法用於這種情況,因為 signal 沒有訪問用戶對象。但是,ModelAdmin 類提供了一個方法,使用該請求和當前用戶作為參數。
修改 admin.py 中的 save_model() 方法,如下所示。
清單 13. 重寫 DocumentAdmin 中的方法,以在創建數據庫時保存當前用戶
|
1
2
3
4
5
6
7
8
9
|
from django.contrib import admin
class DocumentAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'added_by', None) is None:
obj.added_by = request.user
obj.last_modified_by = request.user
obj.save()
[...]
|
如果 added_by 的值是 None,那么這就是一個尚未保存的新記錄。(您還可以檢查 change 是否為 false,這指示是否將添加記錄,但是檢查 added_by 是否為空表示是否填寫添加到 admin 之外的記錄)。
另一個低級權限是將文檔列表限制在創建它們的用戶范圍內。ModelAdmin 類為此提供了一個鈎子程序 —— 它有一個名為 queryset() 的方法,該方法可以確定任何列表頁面返回的默認查詢集。
如清單 14 所示,重寫 queryset() 以便將清單限制在當前用戶創建的這些 Document 中。超級用戶可以看到所有文檔。
清單 14. 重寫列表頁面返回的查詢集
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from django.contrib import admin
from more_with_admin.examples import models
class DocumentAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(DocumentAdmin, self).queryset(request)
# If super-user, show all comments
if request.user.is_superuser:
return qs
return qs.filter(added_by=request.user)
[...]
|
現在,任何對 admin 中 Document 列表頁面的查詢只顯示當前用戶創建的文檔(除非當前用戶是高級用戶,這種情況下將顯示所有文檔)。
當然,只要知道 ID,用戶就可以訪問編輯頁面查看未授權的文檔,當前對此沒有任何阻止手段。真正安全的低級權限需要重寫更多方法。因為 admin 用戶通常都達到某種水平的信任,所以有時一些基本的權限便足以提供精簡的工作流了。
結束語
定制 Django admin 不需要 admin 源代碼知識,也不需要修改源代碼。admin 可以使用普通的 Python 繼承和一些 Django 特有功能(比如 signals)進行擴展。
通過創建全新管理界面定制 admin 的優點很多:
- 保證活動開發持續進行的同時,您的應用程序可以從 Django 的優勢中受益。
- admin 已經支持大部分通用用例。
- 添加到項目的外部應用程序可以自動與代碼並排管理。
