利用 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 已经支持大部分通用用例。
- 添加到项目的外部应用程序可以自动与代码并排管理。