ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第六章:Django站点管理 # 第六章:Django站点管理 对于某一类网站, *管理界面* 是基础设施中非常重要的一部分。 这是以网页和有限的可信任管理者为基础的界面,它可以让你添加,编辑和删除网站内容。 一些常见的例子: 你可以用这个界面发布博客,后台的网站管理者用它来润色读者提交的内容,你的客户用你给他们建立的界面工具更新新闻并发布在网站上,这些都是使用管理界面的例子。 但是管理界面有一问题: 创建它太繁琐。 当你开发对公众的功能时,网页开发是有趣的,但是创建管理界面通常是千篇一律的。 你必须认证用户,显示并管理表格,验证输入的有效性诸如此类。 这很繁琐而且是重复劳动。 Django 在对这些繁琐和重复的工作进行了哪些改进? 它用不能再少的代码为你做了所有的一切。 Django 中创建管理界面已经不是问题。 这一章是关于 Django 的自动管理界面。 这个特性是这样起作用的: 它读取你模式中的元数据,然后提供给你一个强大而且可以使用的界面,网站管理者可以用它立即工作。 请注意我们建议你读这章,即使你不打算用admin。因为我们将介绍一些概念,这些概念可以应用到Django的所有方面,而不仅仅是admin ## django.contrib 包 Django自动管理工具是django.contrib的一部分。django.contrib是一套庞大的功能集,它是Django基本代码的组成部分,Django框架就是由众多包含附加组件(add-on)的基本代码构成的。 你可以把django.contrib看作是可选的Python标准库或普遍模式的实际实现。 它们与Django捆绑在一起,这样你在开发中就不用“重复发明轮子”了。 管理工具是本书讲述django.contrib的第一个部分。从技术层面上讲,它被称作django.contrib.admin。django.contrib中其它可用的特性,如用户鉴别系统(django.contrib.auth)、支持匿名会话(django.contrib.sessioins)以及用户评注系统(django.contrib.comments)。这些,我们将在第十六章详细讨论。在成为一个Django专家以前,你将会知道更多django.contrib的特性。 目前,你只需要知道Django自带很多优秀的附加组件,它们都存在于django.contrib包里。 ## 激活管理界面 Django管理站点完全是可选择的,因为仅仅某些特殊类型的站点才需要这些功能。 这意味着你需要在你的项目中花费几个步骤去激活它。 第一步,对你的settings文件做如下这些改变: 1. 将`'django.contrib.admin'`加入setting的`INSTALLED_APPS`配置中 (`INSTALLED_APPS`中的配置顺序是没有关系的, 但是我们喜欢保持一定顺序以方便人来阅读) 2. 保证`INSTALLED_APPS`中包含`'django.contrib.auth'`,`'django.contrib.contenttypes'`和`'django.contrib.sessions'`,Django的管理工具需要这3个包。 (如果你跟随本文制作mysite项目的话,那么请注意我们在第五章的时候把这三项INSTALLED\_APPS条目注释了。现在,请把注释取消。) 3. 确保`MIDDLEWARE_CLASSES` 包含`'django.middleware.common.CommonMiddleware'` 、`'django.contrib.sessions.middleware.SessionMiddleware'` 和`'django.contrib.auth.middleware.AuthenticationMiddleware'` 。(再次提醒,如果有跟着做mysite的话,请把在第五章做的注释取消。) 运行 `python manage.py syncdb` 。这一步将生成管理界面使用的额外数据库表。 当你把`'django.contrib.auth'`加进`INSTALLED_APPS`后,第一次运行`syncdb`命令时, 系统会请你创建一个超级用户。 如果你不这么作,你需要运行`python manage.py createsuperuser`来另外创建一个admin的用户帐号,否则你将不能登入admin (提醒一句: 只有当`INSTALLED_APPS`包含`'django.contrib.auth'`时,`python manage.py createsuperuser`这个命令才可用.) 第三,将admin访问配置在URLconf(记住,在`urls.py`中). 默认情况下,命令`django-admin.py startproject`生成的文件`urls.py`是将Django admin的路径注释掉的,你所要做的就是取消注释。 请注意,以下内容是必须确保存在的: ``` <pre class="calibre9">``` # Include these import statements... from django.contrib import admin admin.autodiscover() # And include this URLpattern... urlpatterns = patterns('', # ... (r'^admin/', include(admin.site.urls)), # ... ) ``` ``` 当这一切都配置好后,现在你将发现Django管理工具可以运行了。 启动开发服务器(如前:`python manage.py runserver` ),然后在浏览器中访问:`http://127.0.0.1:8000/admin/` ## ,使用管理工具。 管理界面的设计是针对非技术人员的,所以它应该是自我解释的。 尽管如此,这里简单介绍一下它的基本特性。 你看到的第一件事是如图6-1所示的登录屏幕。 ![](https://img.kancloud.cn/ea/9b/ea9b1932de89c2d62d6d49d454e22c6a_828x625.png) 图 6-1. Django的登录截图 你要使用你原来设置的超级用户的用户名和密码。 如果无法登录,请运行`python manage.py createsuperuser` ,确保你已经创建了一个超级用户。 一旦登录了,你将看到管理页面。 这个页面列出了管理工具中可编辑的所有数据类型。 现在,由于我们还没有创建任何模块,所以这个列表只有寥寥数条类目: 它仅有两个默认的管理-编辑模块:用户组(Groups)和用户(Users)。 ![](https://img.kancloud.cn/7a/36/7a3649a9b3b3755db2c36212fb490070_828x625.png) 图 6-2。 Django admin的首页 在Django管理页面中,每一种数据类型都有一个 *change list* 和 *edit form* 。前者显示数据库中所有的可用对象;后者可让你添加、更改和删除数据库中的某条记录。 其它语言 如果你的母语不是英语,而你不想用它来配置你的浏览器,你可以做一个快速更改来观察Django管理工具是否被翻译成你想要的语言。 仅需添加`‘django.middleware.locale.LocaleMiddleware’` 到`MIDDLEWARE_CLASSES` 设置中,并确保它在’django.contrib.sessions.middleware.SessionMiddleware’ *之后* 。 (见上) 完成后,请刷新页面。 如果你设置的语言可用,一系列的链接文字将被显示成这种语言。这些文字包括页面顶端的Change password和Log out,页面中部的Groups和Users。 Django自带了多种语言的翻译。 关于Django更多的国际化特性,请参见第十九章。 点击Uers行中的Change链接,引导用户更改列表。 ![](https://img.kancloud.cn/9f/7c/9f7c837afea1de7bd14705f48564731b_875x677.png) 图 6-3. 典型的改变列表视图 (见上) 这个页面显示了数据库中所有的用户。你可以将它看作是一个漂亮的网页版查询:`SELECT * FROM auth_user;` 如果你一直跟着作练习,并且只添加了一个用户,你会在这个页面中看到一个用户。但是如果你添加了多个用户,你会发现页面中还有过滤器、排序和查询框。 过滤器在右边;排序功能可通过点击列头查看;查询框在页面顶部,它允许你通过用户名查询。 点击其中一个用户名,你会看见关于这个用户的编辑窗口。 ![](https://img.kancloud.cn/ea/29/ea29aae071e31eec3281e58c397e7f9a_875x677.png) 图 6-4. 典型的编辑表格 (见上) 这个页面允许你修改用户的属性,如姓名和权限。 (如果要更改用户密码,你必须点击密码字段下的change password form,而不是直接更改字段值中的哈西码。)另外需要注意的是,不同类型的字段会用不同的窗口控件显示。例如,日期/时间型用日历控件,布尔型用复选框,字符型用简单文本框显示。 你可以通过点击编辑页面下方的删除按钮来删除一条记录。 你会见到一个确认页面。有时候,它会显示有哪些关联的对象将会一并被删除。 (例如,如果你要删除一个出版社,它下面所有的图书也将被删除。) 你可以通过点击管理主页面中某个对象的Add来添加一条新记录。 一个空白记录的页面将被打开,等待你填充。 你还能看到管理界面也控制着你输入的有效性。 你可以试试不填必需的栏目或者在时间栏里填错误的时间,你会发现当你要保存时会出现错误信息,如图6-5所示。 ![](https://img.kancloud.cn/44/b2/44b24c7465952f623c8e04a9486b555b_875x677.png) 图6-5. 编辑表格显示错误信息 (见上) 当你编辑已有的对像时,你在窗口的右上角可以看到一个历史按钮。 通过管理界面做的每一个改变都留有记录,你可以按历史键来检查这个记录(见图6-6)。 ![](https://img.kancloud.cn/f5/24/f524ba1f8d2e65c765730870f94842b4_875x677.png) 图6-6. Django 对像历史页面 (见上) ## 将你的Models加入到Admin管理中 有一个关键步骤我们还没做。 让我们将自己的模块加入管理工具中,这样我们就能够通过这个漂亮的界面添加、修改和删除数据库中的对象了。 我们将继续第五章中的`book` 例子。在其中,我们定义了三个模块: `Publisher` 、 `Author` 和 `Book` 。 在`books` 目录下(`mysite/books` ),创建一个文件:`admin.py` ,然后输入以下代码: ``` <pre class="calibre9">``` from django.contrib import admin from mysite.books.models import Publisher, Author, Book admin.site.register(Publisher) admin.site.register(Author) admin.site.register(Book) ``` ``` 这些代码通知管理工具为这些模块逐一提供界面。 完成后,打开页面 `http://127.0.0.1:8000/admin/` ,你会看到一个Books区域,其中包含Authors、Books和Publishers。 (你可能需要先停止,然后再启动服务(`runserver` ),才能使其生效。) 现在你拥有一个功能完整的管理界面来管理这三个模块了。 很简单吧! 花点时间添加和修改记录,以填充数据库。 如果你跟着第五章的例子一起创建Publisher对象的话(并且没有删除),你会在列表中看到那些记录。 这里需要提到的一个特性是,管理工具处理外键和多对多关系(这两种关系可以在`Book` 模块中找到)的方法。 作为提醒,这里有个`Book` 模块的例子: ``` <pre class="calibre9">``` class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() def __unicode__(self): return self.title ``` ``` 在Add book页面中(`http://127.0.0.1:8000/admin/books/book/add/` ),`外键` publisher用一个选择框显示,`多对多` 字段author用一个多选框显示。 点击两个字段后面的绿色加号,可以让你添加相关的记录。 举个例子,如果你点击Publisher后面的加号,你将会得到一个弹出窗口来添加一个publisher。 当你在那个窗口中成功创建了一个publisher后,Add book表单会自动把它更新到字段上去 花巧. ## Admin是如何工作的 在幕后,管理工具是如何工作的呢? 其实很简单。 当服务启动时,Django从`url.py` 引导URLconf,然后执行`admin.autodiscover()` 语句。 这个函数遍历`INSTALLED_APPS`配置,并且寻找相关的 `admin.py`文件。 如果在指定的app目录下找到`admin.py`,它就执行其中的代码。 在`books` 应用程序目录下的`admin.py` 文件中,每次调用`admin.site.register()` 都将那个模块注册到管理工具中。 管理工具只为那些明确注册了的模块显示一个编辑/修改的界面。 应用程序`django.contrib.auth` 包含自身的`admin.py` ,所以Users和Groups能在管理工具中自动显示。 其它的`django.contrib`应用程序,如`django.contrib.redirects`,其它从网上下在的第三方Django应用程序一样,都会自行添加到管理工具。 综上所述,管理工具其实就是一个Django应用程序,包含自己的模块、模板、视图和URLpatterns。 你要像添加自己的视图一样,把它添加到URLconf里面。 你可以在Django基本代码中的`django/contrib/admin` 目录下,检查它的模板、视图和URLpatterns,但你不要尝试直接修改其中的任何代码,因为里面有很多地方可以让你自定义管理工具的工作方式。 (如果你确实想浏览Django管理工具的代码,请谨记它在读取关于模块的元数据过程中做了些不简单的工作,因此最好花些时间阅读和理解那些代码。) ## 设置字段可选 在摆弄了一会之后,你或许会发现管理工具有个限制:编辑表单需要你填写每一个字段,然而在有些情况下,你想要某些字段是可选的。 举个例子,我们想要`Author`模块中的`email`字段成为可选,即允许不填。 在现实世界中,你可能没有为每个作者登记邮箱地址。 为了指定`email`字段为可选,你只要编辑`Book`模块(回想第五章,它在`mysite/books/models.py`文件里),在`email`字段上加上`blank=True`。代码如下: ``` <pre class="calibre9">``` class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField(**blank=True** ) ``` ``` 这些代码告诉Django,作者的邮箱地址允许输入一个空值。 所有字段都默认`blank=False`,这使得它们不允许输入空值。 这里会发生一些有趣的事情。 直到现在,除了`__unicode__()`方法,我们的模块充当数据库中表定义的角色,即本质上是用Python的语法来写`CREATE TABLE`语句。 在添加`blank=True`过程中,我们已经开始在简单的定义数据表上扩展我们的模块了。 现在,我们的模块类开始成为一个富含`Author`对象属性和行为的集合了。 `email`不但展现为一个数据库中的`VARCHAR`类型的字段,它还是页面中可选的字段,就像在管理工具中看到的那样。 当你添加`blank=True`以后,刷新页面Add author edit form (`http://127.0.0.1:8000/admin/books/author/add/` ),将会发现Email的标签不再是粗体了。 这意味它不是一个必填字段。 现在你可以添加一个作者而不必输入邮箱地址,即使你为这个字段提交了一个空值,也再不会得到那刺眼的红色信息“This field is required”。 ### 设置日期型和数字型字段可选 虽然`blank=True`同样适用于日期型和数字型字段,但是这里需要详细讲解一些背景知识。 SQL有指定空值的独特方式,它把空值叫做NULL。NULL可以表示为未知的、非法的、或其它程序指定的含义。 在SQL中, `NULL`的值不同于空字符串,就像Python中`None`不同于空字符串(`""`)一样。这意味着某个字符型字段(如`VARCHAR`)的值不可能同时包含`NULL`和空字符串。 这会引起不必要的歧义或疑惑。 为什么这条记录有个`NULL`,而那条记录却有个空字符串? 它们之间有区别,还是数据输入不一致? 还有: 我怎样才能得到全部拥有空值的记录,应该按`NULL`和空字符串查找么?还是仅按字符串查找? 为了消除歧义,Django生成`CREATE TABLE`语句自动为每个字段显式加上`NOT NULL`。 这里有个第五章中生成`Author`模块的例子: ``` <pre class="calibre9">``` CREATE TABLE "books_author" ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(40) NOT NULL, "email" varchar(75) NOT NULL ) ; ``` ``` 在大多数情况下,这种默认的行为对你的应用程序来说是最佳的,因为它可以使你不再因数据一致性而头痛。 而且它可以和Django的其它部分工作得很好。如在管理工具中,如果你留空一个字符型字段,它会为此插入一个空字符串(而 *不是*`NULL`)。 但是,其它数据类型有例外:日期型、时间型和数字型字段不接受空字符串。 如果你尝试将一个空字符串插入日期型或整数型字段,你可能会得到数据库返回的错误,这取决于那个数据库的类型。 (PostgreSQL比较严禁,会抛出一个异常;MySQL可能会也可能不会接受,这取决于你使用的版本和运气了。)在这种情况下,`NULL`是唯一指定空值的方法。 在Django模块中,你可以通过添加`null=True`来指定一个字段允许为`NULL`。 因此,这说起来有点复杂: 如果你想允许一个日期型(`DateField`、`TimeField`、`DateTimeField`)或数字型(`IntegerField`、`DecimalField`、`FloatField`)字段为空,你需要使用`null=True` *和*`blank=True`。 为了举例说明,让我们把`Book`模块修改成允许 `publication_date`为空。修改后的代码如下: ``` <pre class="calibre9">``` class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField(**blank=True, null=True** ) ``` ``` 添加`null=True`比添加`blank=True`复杂。因为`null=True`改变了数据的语义,即改变了`CREATE TABLE`语句,把`publication_date`字段上的`NOT NULL`删除了。 要完成这些改动,我们还需要更新数据库。 出于某种原因,Django不会尝试自动更新数据库结构。所以你必须执行`ALTER TABLE`语句将模块的改动更新至数据库。 像先前那样,你可以使用`manage.py dbshell`进入数据库服务环境。 以下是在这个特殊情况下如何删除`NOT NULL`: ``` <pre class="calibre9">``` ALTER TABLE books_book ALTER COLUMN publication_date DROP NOT NULL; ``` ``` (注意:以下SQL语法是PostgreSQL特有的。) 我们将在第十章详细讲述数据库结构更改。 现在让我们回到管理工具,添加book的编辑页面允许输入一个空的publication date。 ## 自定义字段标签 在编辑页面中,每个字段的标签都是从模块的字段名称生成的。 规则很简单: 用空格替换下划线;首字母大写。例如:`Book`模块中`publication_date`的标签是Publication date。 然而,字段名称并不总是贴切的。有些情况下,你可能想自定义一个标签。 你只需在模块中指定`verbose_name`。 举个例子,说明如何将`Author.email`的标签改为e-mail,中间有个横线。 ``` <pre class="calibre9">``` class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField(blank=True, **verbose_name='e-mail'** ) ``` ``` 修改后重启服务器,你会在author编辑页面中看到这个新标签。 请注意,你不必把`verbose_name`的首字母大写,除非是连续大写(如:`"USA state"`)。Django会自动适时将首字母大写,并且在其它不需要大写的地方使用`verbose_name`的精确值。 最后还需注意的是,为了使语法简洁,你可以把它当作固定位置的参数传递。 这个例子与上面那个的效果相同。 ``` <pre class="calibre9">``` class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField(**'e-mail',** blank=True) ``` ``` 但这不适用于`ManyToManyField` 和`ForeignKey`字段,因为它们第一个参数必须是模块类。 那种情形,必须显式使用`verbose_name`这个参数名称。 ## 自定义ModelAdmi类 迄今为止,我们做的`blank=True`、`null=True`和`verbose_name`修改其实是模块级别,而不是管理级别的。 也就是说,这些修改实质上是构成模块的一部分,并且正好被管理工具使用,而不是专门针对管理工具的。 除了这些,Django还提供了大量选项让你针对特别的模块自定义管理工具。 这些选项都在*ModelAdmin classes*里面,这些类包含了管理工具中针对特别模块的配置。 ### 自定义列表 让我们更深一步:自定义`Author`模块的列表中的显示字段。 列表默认地显示查询结果中对象的`__unicode__()`。 在第五章中,我们定义`Author`对象的`__unicode__()`方法,用以同时显示作者的姓和名。 ``` <pre class="calibre9">``` class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField(blank=True, verbose_name='e-mail') **def __unicode__(self):** **return u'%s %s' % (self.first_name, self.last_name)** ``` ``` 结果正如图6-7所示,列表中显示的是每个作者的姓名。 ![](https://img.kancloud.cn/0b/ff/0bfff30d9eb64d95d8a99b21dc11a700_875x677.png) 图 6-7. 作者列表 我们可以在这基础上改进,添加其它字段,从而改变列表的显示。 这个页面应该提供便利,比如说:在这个列表中可以看到作者的邮箱地址。如果能按照姓氏或名字来排序,那就更好了。 为了达到这个目的,我们将为`Author`模块定义一个`ModelAdmin`类。 这个类是自定义管理工具的关键,其中最基本的一件事情是允许你指定列表中的字段。 打开`admin.py`并修改: ``` <pre class="calibre9">``` from django.contrib import admin from mysite.books.models import Publisher, Author, Book **class AuthorAdmin(admin.ModelAdmin):** **list_display = ('first_name', 'last_name', 'email')** admin.site.register(Publisher) **admin.site.register(Author, AuthorAdmin)** admin.site.register(Book) ``` ``` 解释一下代码: > 我们新建了一个类`AuthorAdmin`,它是从`django.contrib.admin.ModelAdmin`派生出来的子类,保存着一个类的自定义配置,以供管理工具使用。 我们只自定义了一项:`list_display`, 它是一个字段名称的元组,用于列表显示。 当然,这些字段名称必须是模块中有的。 > > 我们修改了`admin.site.register()`调用,在`Author`后面添加了`AuthorAdmin`。你可以这样理解: 用`AuthorAdmin`选项注册`Author`模块。 > > `admin.site.register()`函数接受一个`ModelAdmin`子类作为第二个参数。 如果你忽略第二个参数,Django将使用默认的选项。`Publisher`和`Book`的注册就属于这种情况。 弄好了这个东东,再刷新author列表页面,你会看到列表中有三列:姓氏、名字和邮箱地址。 另外,点击每个列的列头可以对那列进行排序。 (参见图 6-8) ![](https://img.kancloud.cn/f4/04/f404d88272cc548a655755b75284b600_875x677.png) 图 6-8. 修改后的author列表页面 接下来,让我们添加一个快速查询栏。 向`AuthorAdmin`追加`search_fields`,如: ``` <pre class="calibre9">``` class AuthorAdmin(admin.ModelAdmin): list_display = ('first_name', 'last_name', 'email') **search_fields = ('first_name', 'last_name')** ``` ``` 刷新浏览器,你会在页面顶端看到一个查询栏。 (见图6-9.)我们刚才所作的修改列表页面,添加了一个根据姓名查询的查询框。 正如用户所希望的那样,它是大小写敏感,并且对两个字段检索的查询框。如果查询`"bar"`,那么名字中含有Barney和姓氏中含有Hobarson的作者记录将被检索出来。 ![](https://img.kancloud.cn/42/03/4203eb9795c049485901246901509645_875x677.png) 图 6-9. 含search\_fields的author列表页面 接下来,让我们为`Book`列表页添加一些过滤器。 ``` <pre class="calibre9">``` from django.contrib import admin from mysite.books.models import Publisher, Author, Book class AuthorAdmin(admin.ModelAdmin): list_display = ('first_name', 'last_name', 'email') search_fields = ('first_name', 'last_name') **class BookAdmin(admin.ModelAdmin):** **list_display = ('title', 'publisher', 'publication_date')** **list_filter = ('publication_date',)** admin.site.register(Publisher) admin.site.register(Author, AuthorAdmin) **admin.site.register(Book, BookAdmin)** ``` ``` 由于我们要处理一系列选项,因此我们创建了一个单独的`ModelAdmin`类:`BookAdmin`。首先,我们定义一个`list_display`,以使得页面好看些。 然后,我们用`list_filter`这个字段元组创建过滤器,它位于列表页面的右边。 Django为日期型字段提供了快捷过滤方式,它包含:今天、过往七天、当月和今年。这些是开发人员经常用到的。 图 6-10显示了修改后的页面。 ![](https://img.kancloud.cn/1b/27/1b27b977ed800f7a14904c7558c47968_875x677.png) 图 6-10. 含过滤器的book列表页面 `过滤器` 同样适用于其它类型的字段,而不单是`日期型` (请在`布尔型` 和`外键` 字段上试试)。当有两个以上值时,过滤器就会显示。 另外一种过滤日期的方式是使用`date_hierarchy`选项,如: ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) **date_hierarchy = 'publication_date'** ``` ``` 修改好后,页面中的列表顶端会有一个逐层深入的导航条,效果如图 6-11. 它从可用的年份开始,然后逐层细分到月乃至日。 ![](https://img.kancloud.cn/4a/6e/4a6e2aa793b3ce9a5d2274c49d747945_875x677.png) 图 6-11. 含date\_hierarchy的book列表页面 请注意,`date_hierarchy`接受的是 *字符串* ,而不是元组。因为只能对一个日期型字段进行层次划分。 最后,让我们改变默认的排序方式,按publication date降序排列。 列表页面默认按照模块`class Meta`(详见第五章)中的`ordering`所指的列排序。但目前没有指定`ordering`值,所以当前排序是没有定义的。 ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) date_hierarchy = 'publication_date' **ordering = ('-publication_date',)** ``` ``` 这个`ordering`选项基本像模块中`class Meta`的`ordering`那样工作,除了它只用列表中的第一个字段名。 如果要实现降序,仅需在传入的列表或元组的字段前加上一个减号(-)。 刷新book列表页面观看实际效果。 注意Publication date列头现在有一个小箭头显示排序。 (见图 6-12.) ![](https://img.kancloud.cn/47/fa/47fab5b7f61b59ec0f1e9fdf0ed4de4f_875x677.png) 图 6-12 含排序的book列表页面 我们已经学习了主要的选项。 通过使用它们,你可以仅需几行代码就能创建一个功能强大、随时上线的数据编辑界面。 ### 自定义编辑表单 正如自定义列表那样,编辑表单多方面也能自定义。 首先,我们先自定义字段顺序。 默认地,表单中的字段顺序是与模块中定义是一致的。 我们可以通过使用`ModelAdmin`子类中的`fields`选项来改变它: ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) date_hierarchy = 'publication_date' ordering = ('-publication_date',) **fields = ('title', 'authors', 'publisher', 'publication_date')** ``` ``` 完成之后,编辑表单将按照指定的顺序显示各字段。 它看起来自然多了——作者排在书名之后。 字段顺序当然是与数据条目录入顺序有关, 每个表单都不一样。 通过fields这个选项,你可以排除一些不想被其他人编辑的fields 只要不选上不想被编辑的field(s)即可。 当你的admi用户只是被信任可以更改你的某一部分数据时,或者,你的数据被一些外部的程序自动处理而改变了了,你就可以用这个功能。 例如,在book数据库中,我们可以隐藏`publication_date`,以防止它被编辑。 ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) date_hierarchy = 'publication_date' ordering = ('-publication_date',) **fields = ('title', 'authors', 'publisher')** ``` ``` 这样,在编辑页面就无法对publication date进行改动。 如果你是一个编辑,不希望作者推迟出版日期的话,这个功能就很有用。 (当然,这纯粹是一个假设的例子。) 当一个用户用这个不包含完整信息的表单添加一本新书时,Django会简单地将`publication_date`设置为`None`,以确保这个字段满足`null=True`的条件。 另一个常用的编辑页面自定义是针对多对多字段的。 真如我们在book编辑页面看到的那样,`多对多字段` 被展现成多选框。虽然多选框在逻辑上是最适合的HTML控件,但它却不那么好用。 如果你想选择多项,你必须还要按下Ctrl键(苹果机是command键)。 虽然管理工具因此添加了注释(help\_text),但是当它有几百个选项时,它依然显得笨拙。 更好的办法是使用`filter_horizontal`。让我们把它添加到`BookAdmin`中,然后看看它的效果。 ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) date_hierarchy = 'publication_date' ordering = ('-publication_date',) **filter_horizontal = ('authors',)** ``` ``` (如果你一着跟着做练习,请注意移除`fields`选项,以使得编辑页面包含所有字段。) 刷新book编辑页面,你会看到Author区中有一个精巧的JavaScript过滤器,它允许你检索选项,然后将选中的authors从Available框移到Chosen框,还可以移回来。 ![](https://img.kancloud.cn/a9/f4/a9f43f34aec709d36233b862ac3d7f18_873x731.png) 图 6-13. 含filter\_horizontal的book编辑页面 我们强烈建议针对那些拥有十个以上选项的`多对多字段` 使用`filter_horizontal`。 这比多选框好用多了。 你可以在多个字段上使用`filter_horizontal`,只需在这个元组中指定每个字段的名字。 `ModelAdmin`类还支持`filter_vertical`选项。 它像`filter_horizontal`那样工作,除了控件都是垂直排列,而不是水平排列的。 至于使用哪个,只是个人喜好问题。 `filter_horizontal`和`filter_vertical`选项只能用在`多对多字段` 上, 而不能用于 `ForeignKey`字段。 默认地,管理工具使用`下拉框` 来展现`外键` 字段。但是,正如`多对多字段` 那样,有时候你不想忍受因装载并显示这些选项而产生的大量开销。 例如,我们的book数据库膨胀到拥有数千条publishers的记录,以致于book的添加页面装载时间较久,因为它必须把每一个publishe都装载并显示在`下拉框` 中。 解决这个问题的办法是使用`raw_id_fields` 选项。它是一个包含外键字段名称的元组,它包含的字段将被展现成`文本框` ,而不再是`下拉框` 。见图 6-14。 ``` <pre class="calibre9">``` class BookAdmin(admin.ModelAdmin): list_display = ('title', 'publisher', 'publication_date') list_filter = ('publication_date',) date_hierarchy = 'publication_date' ordering = ('-publication_date',) filter_horizontal = ('authors',) **raw_id_fields = ('publisher',)** ``` ``` ![](https://img.kancloud.cn/7f/0a/7f0a35710370134d09b0cab8eb2361ad_833x691.png) 图 6-14. 含raw\_id\_fields的book编辑页面 在这个输入框中,你输入什么呢? publisher的数据库ID号。 考虑到人们通常不会记住这些数据库ID,管理工具提供了一个放大镜图标方便你输入。点击那个图标将会弹出一个窗口,在那里你可以选择想要添加的publishe。 ## 用户、用户组和权限 因为你是用超级用户登录的,你可以创建,编辑和删除任何对像。 然而,不同的环境要求有不同的权限,系统不允许所有人都是超级用户。 管理工具有一个用户权限系统,通过它你可以根据用户的需要来指定他们的权限,从而达到部分访问系统的目的。 用户帐号应该是通用的、独立于管理界面以外仍可以使用。但我们现在把它看作是管理界面的一部分。 在第十四章,我们将讲述如何把用户帐号与你的网站(不仅仅是管理工具)集成在一起。 你通过管理界面编辑用户及其许可就像你编辑别的对象一样。 我们在本章的前面,浏览用户和用户组区域的时候已经见过这些了。 如你所想,用户对象有标准的用户名、密码、邮箱地址和真实姓名,同时它还有关于使用管理界面的权限定义。 首先,这有一组三个布尔型标记: - 活动标志,它用来控制用户是否已经激活。 如果一个用户帐号的这个标记是关闭状态,而用户又尝试用它登录时,即使密码正确,他也无法登录系统。 - 成员标志,它用来控制这个用户是否可以登录管理界面(即:这个用户是不是你们组织里的成员) 由于用户系统可以被用于控制公众页面(即:非管理页面)的访问权限(详见第十四章),这个标志可用来区分公众用户和管理用户。 - 超级用户标志,它赋予用户在管理界面中添加、修改和删除任何项目的权限。 如果一个用户帐号有这个标志,那么所有权限设置(即使没有)都会被忽略。 普通的活跃,非超级用户的管理用户可以根据一套设定好的许可进入。 管理界面中每种可编辑的对象(如:books、authors、publishers)都有三种权限: *创建* 许可, *编辑* 许可和 *删除* 许可。 给一个用户授权许可也就表明该用户可以进行许可描述的操作。 当你创建一个用户时,它没有任何权限,该有什么权限是由你决定的。 例如,你可以给一个用户添加和修改publishers的权限,而不给他删除的权限。 请注意,这些权限是定义在模块级别上,而不是对象级别上的。据个例子,你可以让小强修改任何图书,但是不能让他仅修改由机械工业出版社出版的图书。 后面这种基于对象级别的权限设置比较复杂,并且超出了本书的覆盖范围,但你可以在Django documentation中寻找答案。 注释 权限管理系统也控制编辑用户和权限。 如果你给某人编辑用户的权限,他可以编辑自己的权限,这种能力可能不是你希望的。 赋予一个用户修改用户的权限,本质上说就是把他变成一个超级用户。 你也可以给组中分配用户。 一个 *组* 简化了给组中所有成员应用一套许可的动作。 组在给大量用户特定权限的时候很有用。 ## 何时、为什么使用管理界面?何时又不使用呢? 经过这一章的学习,你应该对Django管理工具有所认识。 但是我们需要表明一个观点: *什么时候* 、 *为什么* 用,以及什么时候又 *不* 用。 Django的管理界面对非技术用户要输入他们的数据时特别有用;事实上这个特性就是专门为这个 实现的。 在Django最开始开发的新闻报道的行业应用中,有一个典型的在线自来水的水质专题报道 应用,它的实现流程是这样的: - 负责这个报道的记者和要处理数据的开发者碰头,提供一些数据给开发者。 - 开发者围绕这些数据设计模型然后配置一个管理界面给记者。 - 记者检查管理界面,尽早指出缺少或多余的字段。 开发者来回地修改模块。 - 当模块认可后,记者就开始用管理界面输入数据。 同时,程序员可以专注于开发公众访问视图和模板(有趣的部分)。 换句话说,Django的管理界面为内容输入人员和编程人员都提供了便利的工具。 当然,除了数据输入方面,我们发现管理界面在下面这些情景中也是很有用的: - - 检查模块\* :当你定义好了若干个模块,在管理页面中把他们调出来然后输入一些虚假的数据,这是相当有用的。 有时候,它能显示数据建模的错误或者模块中其它问题。 - - 管理既得数据\* :如果你的应用程序依赖外部数据(来自用户输入或网络爬虫),管理界面提供了一个便捷的途径,让你检查和编辑那些数据。 你可以把它看作是一个功能不那么强大,但是很方便的数据库命令行工具。 - - 临时的数据管理程序\* :你可以用管理工具建立自己的轻量级数据管理程序,比如说开销记录。 如果你正在根据自己的,而不是公众的需要开发些什么,那么管理界面可以带给你很大的帮助。 从这个意义上讲,你可以把它看作是一个增强的关系型电子表格。 最后一点要澄清的是: 管理界面不是终结者。 过往许多年间,我们看到它被拆分、修改成若干个功能模块,而这些功能不是它所支持的。 它不应成为一个 *公众* 数据访问接口,也不应允许对你的数据进行复杂的排序和查询。 正如本章开头所说,它仅提供给可信任的管理员。 请记住这一点,它是有效使用管理界面的钥匙。 ## 下一章 到现在,我们已经创建了一些模块,并且为编辑数据配置了一个优秀的界面。下一章,我们将转入到网站开发中最重要的部分: 表单的创建和处理。