本文源自 Reddit 上對我最近的一個帖子的評論:
“問題是,我問到的每個人都持反對意見,他們認為 admin 只限於超級使用者,很不靈活並且是難以定製。”
—來自 Reddit 的 andybak
我現在要澄清這個誤解。Django 的 admin 絕對是軟體中的亮點,可以有效的加速你的開發。
這裡有一些我能想到的很有用的 Django 的 admin 模組的竅門。
(對於 Django admin 不太熟悉的人,先簡單解釋幾個名詞)
Changeform 是可以編輯物件的頁面。
Changelist 頁面可以列出指定型別的物件。你可以指定過濾物件的條件及對物件的操作。點選 changelist 裡的物件一般會跳轉到物件的 changeform 頁面。
為了讓這些敲門更具可操作性,我們使用了與真實問題幾乎一致的場景。假設我們有一個簡單的網站,訪客可以上傳可愛的動物圖片並進行評論。這是不是很流行呢?
Tip 1:Django admin 後臺不限於用 Django 開發的網站
雖然 Django admin 管理介面可以非常友好的用在 Django 專案的其它部分,它同樣可以很容易用於其它像傳統的資料庫或具有一個可怕的的管理介面的網站。而且這也是評估 Django 是否會滿足您的需求的最佳途徑。
你需要做的僅是:
- 在你的 Django 專案中建立一個新的應用,並確保你已經連線好傳統資料庫 ,通過 settings.py 檔案中的 DATABASES 的設定。
- 將你的資料表定義為 Django 的模型。正如它的名字所表述的,manage.py inspectdb 是一個非常有用的命令:檢測現有的資料庫,並列印出自動生成的 Django 模型。
- 建立 admin.py 檔案,並放在那裡,唉,管理相關的。稍後將詳細說明這個。
說到我們的動物“的網站,是由進屎的腦袋寫出來的,所以管理介面看起來像……你知道的,不是很好。為了解決這個問題,我們通過幾個 Django 模型重構了資料庫結構,實現一個簡單的管理介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 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+ 的帖子的作者。但是,我們如何區分它們?讓我們建立一個過濾器,並把它新增到我們的變更列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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] |
現在,我們可以很容易地選出我們的核心作者。那麼我們如何開始向他們推廣呢?讓我們進入下一部分。
Tip #3:新增動作(操作函式)到 ‘actions’
這可是內容管理者的天賜之物。還記得在每個模型的列表頂部的“動作”工具欄不?我們是不是非常方便的先選擇一些圖片,然後只需單擊一下就“推廣”給作者了?現在讓我們來實現它:
1 2 3 4 5 6 7 |
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' |
就是這樣!不用再一個挨一個的開啟每個表單!另外,它很容易進一步增加我們的動作,例如,新增一個過渡表單。關於這點,Django 文件 有段非常棒的講解。
Tip #4: 搜尋你需要的所有欄位
好吧,過濾器是很酷,但讓我們關注了一下就搜尋工具。在幾乎所有的安裝我見過的搜尋框是用來在一個模型中的欄位搜尋。但是,當你意識到它可以處理關係的 Django 搜尋真正的亮點。因此,假設我們希望它在圖片“的標題,作者姓名和註釋”文字進行搜尋。我們如何做到這一點?
1 2 |
class PictureAdmin(admin.ModelAdmin): search_fields = ('title', 'author__name', 'comments__text', ) |
如果你的資料庫是夠大,不要忘記新增一些全文索引來增加搜尋速度。
Tip #5: “在站點檢視”的簡單實現
在站點檢視一個物件的介面是非常普及的需求,預設情況下,你必須開啟該物件的表單,然後點選按鈕“在站點檢視”。以下程式碼展示如何使此過程更容易一些:
1 2 3 4 5 6 7 8 |
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 |
這段程式碼給列表中每個物件都新增了“在站點檢視”的連結。在此,我們假定你的模型(Model)已經實現了get_absolute_url()方法。如果還沒有 – 那現在就去實現 ,這將為你節省很多時間。你也可能會想將這個片段轉移到一個 mixin,或公用的 admin 基類。
Tip #6: 在列表頁就地編輯欄位
假設我們需要給評論加一個編輯的備註。很自然,我們希望不需要對每條評論都去開啟評論的changeform。要做到這點,我們可以稍微修改一下ModelAdmin:
1 2 3 |
class CommentAdmin(admin.ModelAdmin): list_display_fields = ('picture', 'author', 'editors_note', ) list_editable = ('editors_note', ) |
這樣就搞定了,現在開啟評論列表,可以按照需要進行過濾,還可以在評論上即時新增備註。
Tip #7: 根據需要自定義 total 欄位
每個 changelist 最下方都有一行列出總數(total)。假設我們需要把貓和狗的圖片數量區分開來。這個功能需要的程式碼稍微多一些:我們需要過載 changelist 和 html 模板(更多內容參考模板過載)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 |
模板的內容:
1 2 3 4 5 6 7 8 9 10 11 12 |
{% 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 %} |
Tip #8: 對某些使用者只讀的 admin 介面
啥意思?假設你的祖母打算瞅一眼這些可愛的圖片,她站在你背後,覺得 Django 的 admin 介面挺有意思。不過你能肯定,她要是使用 admin 介面,恐怕一個按鈕的點選就能毀掉整個網站。那麼,我們加上 grandma-proof™,這樣就支援只讀的 admin 介面(就是某人說的“資料瀏覽”):
1 2 3 4 5 6 7 8 9 |
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): ... |
現在你可以安全的把修改圖片的許可權放開給你的祖母,這樣她就能瀏覽圖片列表。要注意這個方案肯定不能適用於所有使用場景,你還需要處理更多的情況。
Tip #9: 為每個物件自定義 action
有時候你需要在單個物件上執行特定的 action。‘actions’工具當然可以完成這個任務,不過過程會顯得很麻煩:點選物件、選擇 action、再點選一個按鈕……肯定有更便捷的方式,對吧?讓我們想辦法只點選一次就全部搞定。
這次我們要實現老祖母的另一個巨集達的想法。她希望能給某些編輯發 email,告訴他們她喜歡的所有圖片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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')) |
但願她現在能夠滿意。現在每個物件欄位加上了一個連結,讓她點一下就可以傳送郵件。
Bonus Tip: 只需為 admin 新增一行程式碼來減少查詢量
Django admin (Django 也是如此) 最常用也是最有用的技巧是 select_related。呃,你已經都知道了?不就是把物件的名字傳給 ModelAdmin 的 list_select_related 屬性來實現相關物件的預載入嘛。但是,你知道你並沒有描述全部的相關物件嗎?只需要設定成 True,Django 就可以自動預載入外部物件:
1 2 |
class PictureAdmin(admin.ModelAdmin): list_select_related = True |
本文到此就差不多結束了,希望你能覺得有意思。別忘了在評論裡分享你的看法,告訴我們對你有幫助的技巧。