自定义ModelAdmi类
迄今为止,我们做的blank=True
、null=True
和verbose_name
修改其实是模块级别,而不是管理级别的。 也就是说,这些修改实质上是构成模块的一部分,并且正好被管理工具使用,而不是专门针对管理工具的。
除了这些,Django还提供了大量选项让你针对特别的模块自定义管理工具。这些选项都在ModelAdmin classes
里面,这些类包含了管理工具中针对特别模块的配置。
自定义列表
让我们更深一步:自定义Author
模块的列表中的显示字段。 列表默认地显示查询结果中对象的__unicode__()
。 在第五章中,我们定义Author
对象的__unicode__()
方法,用以同时显示作者的姓和名。
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所示,列表中显示的是每个作者的姓名。
图 6-7. 作者列表
我们可以在这基础上改进,添加其它字段,从而改变列表的显示。这个页面应该提供便利,比如说:在这个列表中可以看到作者的邮箱地址。如果能按照姓氏或名字来排序,那就更好了。
为了达到这个目的,我们将为Author
模块定义一个ModelAdmin
类。这个类是自定义管理工具的关键,其中最基本的一件事情是允许你指定列表中的字段。打开admin.py
并修改:
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)
图 6-8. 修改后的author列表页面
接下来,让我们添加一个快速查询栏。向AuthorAdmin
追加search_fields
,如:
class AuthorAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'email')
search_fields = ('first_name', 'last_name')
刷新浏览器,你会在页面顶端看到一个查询栏。 (见图6-9.)我们刚才所作的修改列表页面,添加了一个根据姓名查询的查询框。正如用户所希望的那样,它是大小写敏感,并且对两个字段检索的查询框。如果查询"bar"
,那么名字中含有Barney和姓氏中含有Hobarson的作者记录将被检索出来。
图 6-9. 含search_fields的author列表页面
接下来,让我们为Book
列表页添加一些过滤器。
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显示了修改后的页面。
图 6-10. 含过滤器的book列表页面
“过滤器”同样适用于其它类型的字段,而不单是“日期型”(请在“布尔型” 和“外键” 字段上试试)。当有两个以上值时,过滤器就会显示。
另外一种过滤日期的方式是使用date_hierarchy
选项,如:
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'publisher', 'publication_date')
list_filter = ('publication_date',)
date_hierarchy = 'publication_date'
修改好后,页面中的列表顶端会有一个逐层深入的导航条,效果如图 6-11. 它从可用的年份开始,然后逐层细分到月乃至日。
图 6-11. 含date_hierarchy的book列表页面
请注意,date_hierarchy
接受的是字符串,而不是元组。因为只能对一个日期型字段进行层次划分。
最后,让我们改变默认的排序方式,按publication date
降序排列。列表页面默认按照模块class Meta
(详见第五章)中的ordering
所指的列排序。但目前没有指定ordering值,所以当前排序是没有定义的。
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.)
图 6-12 含排序的book列表页面
我们已经学习了主要的选项。通过使用它们,你可以仅需几行代码就能创建一个功能强大、随时上线的数据编辑界面。
自定义编辑表单
正如自定义列表那样,编辑表单多方面也能自定义。
首先,我们先自定义字段顺序。默认地,表单中的字段顺序是与模块中定义是一致的。 我们可以通过使用ModelAdmin
子类中的fields
选项来改变它:
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
,以防止它被编辑。
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
中,然后看看它的效果。
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
框,还可以移回来。
图 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
选项。它是一个包含外键字段名称的元组,它包含的字段将被展现成“文本框”(<input type="text">
),而不再是“下拉框”(<select>
).。见图 6-14。
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',)
图 6-14. 含raw_id_fields的book编辑页面
在这个输入框中,你输入什么呢? publisher
的数据库ID号。 考虑到人们通常不会记住这些数据库ID,管理工具提供了一个放大镜图标方便你输入。点击那个图标将会弹出一个窗口,在那里你可以选择想要添加的publishe
。
{$ activeFileHint $}