你应该使用 Django admin 的 9 个理由

linbei888 9年前

来自: http://python.jobbole.com/84340/

本文源自 Reddit 上对我 最近的一个帖子 的评论:

“问题是,我问到的每个人都持反对意见,他们认为 admin 只限于超级用户,很不灵活并且是难以定制。”

—来自 Reddit 的 andybak

</div>

我现在要澄清这个误解。Django 的 admin 绝对是软件中的亮点,可以有效的加速你的开发。

这里有一些我能想到的很有用的 Django 的 admin 模块的窍门。

(对于 Django admin 不太熟悉的人,先简单解释几个名词)

Changeform 是可以编辑对象的页面。

Changelist 页面可以列出指定类型的对象。你可以指定过滤对象的条件及对对象的操作。点击changelist 里的对象一般会跳转到对象的 changeform 页面。

为了让这些敲门更具可操作性,我们使用了与真实问题几乎一致的场景。假设我们有一个简单的网站,访客可以上传可爱的动物图片并进行评论。这是不是很流行呢?

Tip 1:Django admin 后台不限于用 Django 开发的网站

虽然 Django admin 管理界面可以非常友好的用在 Django 项目的其它部分,它同样可以很容易用于其它像传统的数据库或具有一个可怕的的管理界面的网站。而且这也是评估 Django 是否会满足您的需求的最佳途径。

你需要做的仅是:

  1. 在你的 Django 项目中建立一个新的应用,并确保你已经连接好传统数据库 ,通过 settings.py 文件中的 DATABASES 的设置。
  2. 将你的数据表定义为 Django 的模型。正如它的名字所表述的,manage.py inspectdb 是一个非常有用的命令:检测现有的数据库,并打印出自动生成的 Django 模型。
  3. 创建 admin.py 文件,并放在那里,唉,管理相关的。稍后将详细说明这个。

说到我们的动物“的网站,是由进屎的脑袋写出来的,所以管理界面看起来像……你知道的,不是很好。为了解决这个问题,我们通过几个 Django 模型重构了数据库结构,实现一个简单的管理界面:

# models.py  class Picture(models.Model):      DOG = 1      CAT = 2      ANIMAL_KIND_CHOICES = (          (DOG, 'dog'),          (CAT, 'cat'),      )        title = models.CharField(max_length=200)      author = models.ForeignKey(Author, related_name='pictures')      animal_kind = models.IntegerField(choices=ANIMAL_KIND_CHOICES)      photo = models.ImageField(upload_to='animals')      is_promoted = models.BooleanField(default=False)    class Author(models.Model):      name = models.CharField(max_length=100)      email = models.EmailField()    class Comment(models.Model):      author = models.ForeignKey(Author, related_name='comments')      picture = models.ForeignKey(Picture, related_name='comments')      comment = models.TextField()      editors_note = models.TextField()    # admin.py  class PictureAdmin(admin.ModelAdmin):      list_display_fields = ('photo', 'animal_kind', 'author', 'is_promoted', )    class AuthorAdmin(admin.ModelAdmin):      list_display_fields = ('name', 'email', )    class CommentAdmin(admin.ModelAdmin):      list_display_fields = ('picture', 'author', )
# models.py  class Picture(models.Model):      DOG = 1      CAT = 2      ANIMAL_KIND_CHOICES = (          (DOG, 'dog'),          (CAT, 'cat'),      )         title = models.CharField(max_length=200)      author = models.ForeignKey(Author, related_name='pictures')      animal_kind = models.IntegerField(choices=ANIMAL_KIND_CHOICES)      photo = models.ImageField(upload_to='animals')      is_promoted = models.BooleanField(default=False)     class Author(models.Model):      name = models.CharField(max_length=100)      email = models.EmailField()     class Comment(models.Model):      author = models.ForeignKey(Author, related_name='comments')      picture = models.ForeignKey(Picture, related_name='comments')      comment = models.TextField()      editors_note = models.TextField()     # admin.py  class PictureAdmin(admin.ModelAdmin):      list_display_fields = ('photo', 'animal_kind', 'author', 'is_promoted', )     class AuthorAdmin(admin.ModelAdmin):      list_display_fields = ('name', 'email', )     class CommentAdmin(admin.ModelAdmin):      list_display_fields = ('picture', 'author', )

Tip #2: 按你喜欢的方式筛选你的数据

很多人使用 Django admin 后台对指定字段进行筛选。要知道,把一个字段名放到 list_filter 列表里就可以了。同时它也非常容易地创建一个自定义过滤器!

假如最终你决定要推广所有有 100+ 的帖子的作者。但是,我们如何区分它们?让我们创建一个过滤器,并把它添加到我们的变更列表。

class ProductiveAuthorsFilter(admin.SimpleListFilter):      parameter_name = 'is_productive'      title = 'Productive author'      YES, NO = 1, 0        # Number of comments for an author to be considered a productive one      THRESHOLD = 100        def lookups(self, request, model_admin):          return (              (self.YES, 'yes'),              (self.NO, 'no'),          )        def queryset(self, request, queryset):          qs = queryset.annotate(Count('comments'))            # Note the syntax. This way we avoid touching the queryset if our          # filter is not used at all.          if self.value() == self.YES:              return qs.filter(comments__count__gte=self.THRESHOLD)          if self.value() == self.NO:              return qs.filter(comments__count__lt=self.THRESHOLD)            return queryset    class PictureAdmin(admin.ModelAdmin):      list_filters = [..., ProductiveAuthorsFilter]
class ProductiveAuthorsFilter(admin.SimpleListFilter):      parameter_name = 'is_productive'      title = 'Productive author'      YES, NO = 1, 0         # Number of comments for an author to be considered a productive one      THRESHOLD = 100         deflookups(self, request, model_admin):          return (              (self.YES, 'yes'),              (self.NO, 'no'),          )         defqueryset(self, request, queryset):          qs = queryset.annotate(Count('comments'))             # Note the syntax. This way we avoid touching the queryset if our          # filter is not used at all.          if self.value() == self.YES:              return qs.filter(comments__count__gte=self.THRESHOLD)          if self.value() == self.NO:              return qs.filter(comments__count__lt=self.THRESHOLD)             return queryset     class PictureAdmin(admin.ModelAdmin):      list_filters = [..., ProductiveAuthorsFilter]

现在,我们可以很容易地选出我们的核心作者。那么我们如何开始向他们推广呢?让我们进入下一部分。

Tip #3:添加动作(操作函数)到 ‘actions’

这可是内容管理者的天赐之物。还记得在每个模型的列表顶部的“动作”工具栏不?我们是不是非常方便的先选择一些图片,然后只需单击一下就“推广”给作者了?现在让我们来实现它:

class PictureAdmin(admin.ModelAdmin):      actions = ['promote', ]        def promote(self, request, queryset):          queryset.update(is_promoted=True)          self.message_user(request, 'The posts are promoted')      promote.short_description = 'Promote the pictures'
class PictureAdmin(admin.ModelAdmin):      actions = ['promote', ]         defpromote(self, request, queryset):          queryset.update(is_promoted=True)          self.message_user(request, 'The posts are promoted')      promote.short_description = 'Promote the pictures'

就是这样!不用再一个挨一个的打开每个表单!另外,它很容易进一步增加我们的动作,例如,添加一个过渡表单。关于这点,Django 文档 有段非常棒的讲解

Tip #4: 搜索你需要的所有字段

好吧,过滤器是很酷,但让我们关注了一下就搜索工具。在几乎所有的安装我见过的搜索框是用来在一个模型中的字段搜索。但是,当你意识到它可以处理关系的 Django 搜索真正的亮点。因此,假设我们希望它在图片“的标题,作者姓名和注释”文本进行搜索。我们如何做到这一点?

class PictureAdmin(admin.ModelAdmin):      search_fields = ('title', 'author__name', 'comments__text', )
class PictureAdmin(admin.ModelAdmin):      search_fields = ('title', 'author__name', 'comments__text', )

如果你的数据库是够大,不要忘记添加一些全文索引来增加搜索速度。

Tip #5: “在站点查看”的简单实现

在站点查看一个对象的界面是非常普及的需求,默认情况下,你必须打开该对象的表单,然后点击按钮“在站点查看”。以下代码展示如何使此过程更容易一些:

class PictureAdmin(admin.ModelAdmin):      list_fields = [..., 'object_link']        def object_link(self, item):          url = item.get_absolute_url()          return u'<a href={url}>open</a>'.format(url=url)      object_link.short_description = 'View on site'      object_link.allow_tags = True
class PictureAdmin(admin.ModelAdmin):      list_fields = [..., 'object_link']         defobject_link(self, item):          url = item.get_absolute_url()          return u'<a href={url}>open</a>'.format(url=url)      object_link.short_description = 'View on site'      object_link.allow_tags = True

这段代码给列表中每个对象都添加了“在站点查看”的链接。在此,我们假定你的模型(Model)已经实现了get_absolute_url()方法。如果还没有 – 那现在就去实现 ,这将为你节省很多时间。你也可能会想将这个片段转移到一个 mixin,或公用的 admin 基类。

Tip #6: 在列表页就地编辑字段

假设我们需要给评论加一个编辑的备注。很自然,我们希望不需要对每条评论都去打开评论的changeform。要做到这点,我们可以稍微修改一下ModelAdmin:

class CommentAdmin(admin.ModelAdmin):      list_display_fields = ('picture', 'author', 'editors_note', )      list_editable = ('editors_note', )
class CommentAdmin(admin.ModelAdmin):      list_display_fields = ('picture', 'author', 'editors_note', )      list_editable = ('editors_note', )

这样就搞定了,现在打开评论列表,可以按照需要进行过滤,还可以在评论上即时添加备注。

Tip #7: 根据需要自定义 total 字段

每个 changelist 最下方都有一行列出总数(total)。假设我们需要把猫和狗的图片数量区分开来。这个功能需要的代码稍微多一些:我们需要重载 changelist 和 html 模板(更多内容参考 模板重载 )。

from django.contrib.admin.views.main import ChangeList    class PicturesChangeList(admin.ChangeList):      def get_results(self, request):          super(PicturesChangeList, self).get_results(request)          totals = self.result_list.aggregate(              dogs_count=Sum(Case(When(animal_kind=Picture.DOG, then=1),                             output_field=IntegerField())),              cats_count=Sum(Case(When(animal_kind=Picture.CAT, then=1),                             output_field=IntegerField())))          self.totals = totals    class PictureAdmin(admin.ModelAdmin):      def get_changelist(self, request):          return PicturesChangeList
fromdjango.contrib.admin.views.mainimportChangeList     class PicturesChangeList(admin.ChangeList):      defget_results(self, request):          super(PicturesChangeList, self).get_results(request)          totals = self.result_list.aggregate(              dogs_count=Sum(Case(When(animal_kind=Picture.DOG, then=1),                            output_field=IntegerField())),              cats_count=Sum(Case(When(animal_kind=Picture.CAT, then=1),                            output_field=IntegerField())))          self.totals = totals     class PictureAdmin(admin.ModelAdmin):      defget_changelist(self, request):          return PicturesChangeList

模板的内容:

{% extends 'admin/change_list.html' %}  {% block result_list %}      {{ block.super }}      <p>          There are          <strong>              {{ cl.totals.dogs_count|default:'none' }} dogs and              {{ cl.totals.cats_count|default:'none' }} cats          </strong>          on this page.      </p>  {% endblock %}
{% extends 'admin/change_list.html' %}  {% blockresult_list %}      {{ block.super }}      <p>          There are          <strong>              {{ cl.totals.dogs_count|default:'none' }} dogs and              {{ cl.totals.cats_count|default:'none' }} cats          </strong>          onthis page.      </p>  {% endblock %}

Tip #8: 对某些用户只读的 admin 界面

啥意思?假设你的祖母打算瞅一眼这些可爱的图片,她站在你背后,觉得 Django 的 admin 界面挺有意思。不过你能肯定,她要是使用 admin 界面,恐怕一个按钮的点击就能毁掉整个网站。那么,我们加上 grandma-proof™,这样就支持只读的 admin 界面(就是某人说的“数据浏览”):

class GrandmaProofAdmin(admin.ModelAdmin):      def get_readonly_fields(self, request, obj=None):          if request.user.username == 'granny':              return [f.name for f in self.model._meta.fields]          else:              return super(GrandmaProofAdmin, self).get_readonly_fields(request, obj)    class PictureAdmin(GrandmaProofAdmin):      ...
class GrandmaProofAdmin(admin.ModelAdmin):      defget_readonly_fields(self, request, obj=None):          if request.user.username == 'granny':              return [f.namefor f in self.model._meta.fields]          else:              return super(GrandmaProofAdmin, self).get_readonly_fields(request, obj)     class PictureAdmin(GrandmaProofAdmin):      ...

现在你可以安全的把修改图片的权限放开给你的祖母,这样她就能浏览图片列表。要注意这个方案肯定不能适用于所有使用场景,你还需要处理 更多的情况

Tip #9: 为每个对象自定义 action

有时候你需要在单个对象上执行特定的 action。‘actions’工具当然可以完成这个任务,不过过程会显得很麻烦:点击对象、选择 action、再点击一个按钮……肯定有更便捷的方式,对吧?让我们想办法只点击一次就全部搞定。

这次我们要实现老祖母的另一个宏达的想法。她希望能给某些编辑发 email,告诉他们她喜欢的所有图片。

class PictureAdmin(admin.ModelAdmin):      list_fields = (..., 'mail_link', )        def mail_link(self, obj):          dest = reverse('admin:myapp_pictures_mail_author',                         kwargs={'pk': obj.pk})          return '<a href="{url}">{title}</a>'.format(url=dest, title='send mail')      mail_link.short_description = 'Show some love'      mail_link.allow_tags = True        def get_urls(self):          urls = [              url('^(?P<pk>\d+)/sendaletter/?$',                  self.admin_site.admin_view(self.mail_view),                  name='myapp_pictures_mail_author'),          ]          return urls + super(PictureAdmin, self).get_urls()        def mail_view(self, request, *args, **kwargs):          obj = get_object_or_404(Picture, pk=kwargs['pk'])          send_mail('Feel the granny\'s love', 'Hey, she loves your pet!',                    'granny@yoursite.com', [obj.author.email])          self.message_user(request, 'The letter is on its way')          return redirect(reverse('admin:myapp_picture_changelist'))
class PictureAdmin(admin.ModelAdmin):      list_fields = (..., 'mail_link', )         defmail_link(self, obj):          dest = reverse('admin:myapp_pictures_mail_author',                        kwargs={'pk': obj.pk})          return '<a href="{url}">{title}</a>'.format(url=dest, title='send mail')      mail_link.short_description = 'Show some love'      mail_link.allow_tags = True         defget_urls(self):          urls = [              url('^(?P<pk>\d+)/sendaletter/?$',                  self.admin_site.admin_view(self.mail_view),                  name='myapp_pictures_mail_author'),          ]          return urls + super(PictureAdmin, self).get_urls()         defmail_view(self, request, *args, **kwargs):          obj = get_object_or_404(Picture, pk=kwargs['pk'])          send_mail('Feel the granny\'s love', 'Hey, she loves your pet!',                    'granny@yoursite.com', [obj.author.email])          self.message_user(request, 'The letter is on its way')          return redirect(reverse('admin:myapp_picture_changelist'))

但愿她现在能够满意。现在每个对象字段加上了一个链接,让她点一下就可以发送邮件。

Bonus Tip: 只需为 admin 添加一行代码来减少查询量

Django admin (Django 也是如此) 最常用也是最有用的技巧是 select_related。呃,你已经都知道了?不就是把对象的名字传给 ModelAdmin 的 list_select_related 属性来实现相关对象的预加载嘛。但是,你知道你并没有描述全部的相关对象吗?只需要设置成 True,Django 就可以自动预加载外部对象:

class PictureAdmin(admin.ModelAdmin):      list_select_related = True
class PictureAdmin(admin.ModelAdmin):      list_select_related = True

本文到此就差不多结束了,希望你能觉得有意思。别忘了在评论里分享你的看法,告诉我们对你有帮助的技巧。

</div>