django自带的admin继承了搜索功能,默认搜索功能支持搜索当前model以及外键关联的model对应的字段,

class Post(models.Model):
    title = models.CharField(max_length=100, default='')
    category = models.ForeignKey(Category, null=True, default=None)

class PostAdmin(VersionAdmin):
  search_fields = ['title', 'category__name']

定义了上述model之后,在admin中post列表页面就会出现一个输入框用以提供搜索功能。假设输入关键词“django”,那么对应生成的查询类似,

SELECT * FROM `blog_post`
LEFT OUTER JOIN `blog_category`
  ON (`blog_post`.`category_id` = `blog_category`.`id`)
WHERE (`blog_post`.`title` LIKE '%django%'
  OR `blog_category`.`name` LIKE '%django%' )

可以看到若search_fields里包含了外键关联的字段那么就会生成一个级连查询,在小数据量下有join操作也没什么,不过若数据表变大后类似这样的搜索就会很慢。搜索慢其实也没什么,但这中满操作却会阻塞对外服务进程。因此最好是在数据量变大的情况下进行优化,当然分离服务也是一个方法,但治标不治本。

避免join操作的一种直观策略就是将一条查询拆分成两条,于是就得看看django admin默认是如何构建最后的查询操作的。一路源码下来,最终代码位于,

django.contrib.admin.views.main.ChangeList.get_query_set

admin则是通过,

ModelAdmin.get_changelist

来获取对应的ChangeList实现。所以优化的思路就有了,

  • 自定义ChangeList实现,override默认的get_query_set操作
  • override admin的get_changelist函数,返回自定义的ChangeList实现

上述简单修改的不足在于多个字段搜索不便,因为无法只返回一个query set用于翻页。不过也有解决之道,

  • 拆分搜索需求

说白了就是在admin上进行搜索操作时目的性都是很明确的,很明白具体想搜索哪个字段。最简单的思路就是在搜索时指定字段,如title=django、category__title=django。对开发人员来说这样输入没有不便,但若是其它人员使用则又不便了,那么就进一步考虑自定义admin页面的展示吧,总归是能够实现的。