Django admin 權威指南(一)

轉載自:https://www.cnblogs.com/kaid/p/7966058.html

 

此部分由官方文檔《6.5.1 The Django admin site》翻譯而來。

6.5.1.1 概覽

默認情況下,使用startproject的時候,admin就被激活了。

如果需要手動啓用,請按下面的需求和要點進行操作:

  1. 將'django.contrib.admin'加入 INSTALLED_APPS。
  2. admin依賴下面四個模塊,請確保它們存在於INSTALLED_APPS。
    django.contrib.auth
    django.contrib.contenttypes
    django.contrib.messages
    and django.contrib.sessions
  3. 在你的settings文件中的TEMPLATES中的'context_processors'選項內添加django.contrib.auth.context_processors.auth和django.contrib.messages.context_processors.messages。
    同樣,將django.contrib.auth.middleware.AuthenticationMiddleware和django.contrib.messages.middleware.MessageMiddleware添加到MIDDLEWARE內。默認情況下,這些都是配置好的。
  4. 決定哪些模型將在admin內進行管理。
  5. 對於每個模型,可以創建一個對應的ModelAdmin類,這個類將封裝對模型的所有自定義設置。
  6. 實例化一個AdminSite,將模型對模型對應的ModelAdmin類傳給它。
  7. 鏈接AdminSite和你的URLconf。

做完上面的步驟,你就可以在瀏覽器中訪問admin站點了,默認地址是....../admin/。但是,想要登錄進去,你首先必須使用python manage.py createsuperuser命令創建管理員賬戶。

一、自定義actions(Admin actions)

通常情況下,admin的工作模式是“選中目標,修改目標”,但在某些列入同時修改大量目標的時候,這種模式就變得重複、繁瑣。

爲此,admin提供了自定義功能函數的手段,可以批量對數據進行修改。admin內置了一個批量刪除對象的操作,如下圖所示(Django內置的django.contrib.auth應用的用戶模型):
1.png-40.6kB

警告:“delete selected objects”使用的是 QuerySet.delete()方法,它不會執行你自定義的模型的delete()方法,請注意。如果你想使用自己的delete()方法,那你必須重寫這個動作,將它覆蓋了。

下面以一個新聞應用的文章模型爲例,介紹一個批量更新的自定義actions,它將選擇的文章由“草稿”狀態更新爲“發佈”狀態:
首先是模型的代碼:

from django.db import models

STATUS_CHOICES = (
    ('d', 'Draft'),
    ('p', 'Published'),
    ('w', 'Withdrawn'),
)

class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

    def __str__(self):              # __unicode__ on Python 2
        return self.title

編寫功能函數

函數必須攜帶三個參數:

  • 當前的ModelAdmin
  • 當前的HttpRequest對象(即request)
  • 被選擇的對象(即QuerySet)

下面是一個例子:
在應用中的admin.py文件中寫入:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

注意:這裏我們使用了queryset的update方法,它能批量操作。但在多數情況下,你要自己遍歷queryset的每個元素,並執行具體的操作。也就是:

for obj in queryset:
    do_something_with(obj)

我們還可以設置一個簡單易懂的簡短描述(可以使用中文),用於代替生硬的函數名:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
# 注意縮進,下面這句不在函數體內。
make_published.short_description = "Mark selected stories as published"

將自定義操作添加到對應的ModelAdmin中

關鍵是其中的actions = [make_published]這句。

from django.contrib import admin
from myapp.models import Article

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'status']
    ordering = ['title']
    actions = [make_published]

admin.site.register(Article, ArticleAdmin)

然後,頁面看起來是下面的樣子(注意下拉框):
2.png-61.5kB

處理錯誤
如果你能夠預知在自定義的操作中可能產生的錯誤,請處理該錯誤,並通過django.contrib.admin.ModelAdmin.message_user()以友好的方式給予用戶提示信息。

將自定義操作定義爲ModelAdmin的方法

上面的make_published函數看起來已經不錯了,但是我們一般會將它作爲ModelAdmin的方法來使用。下面我們把它移到ArticleAdmin類中:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ['make_published']  # 請注意這裏改成字符串引用了
    # 第一個參數變爲self
    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

這樣做的好處是自定義方法可以直接訪問類本身。例如下面使用self引用,爲方法添加提示信息的功能:

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

回到瀏覽器,再試試,你會看到如下圖所示(注意頂部的綠色提示行):
3.png-68.8kB

跳轉到中間頁面

默認情況下,執行完actions後,瀏覽器會返回先前的修改列表頁面。但有時候,一些複雜的action需要返回中間頁面,例如內置的刪除方法,在執行刪除動作之前,會彈出一個刪除確認頁面。

要實現這個功能,只需要在action方法中返回一個HttpResponse(或它的子類)。
例如下面是一個利用Django內置的序列化函數將一個對象保存爲json格式的範例:

from django.http import HttpResponse
from django.core import serializers

def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

多數情況下,我們會使用HttpResponseRedirect跳轉到一箇中間頁面,並在GET方法的url中攜帶別選擇的對象作爲參數傳遞過去,然後在已個新的視圖中接收這個參數,並編寫具體的更加複雜的業務邏輯,如下面的代碼所示:

from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    # 獲得被打鉤的checkbox對應的對象
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    # 獲取對應的模型
    ct = ContentType.objects.get_for_model(queryset.model)
    # 構造訪問的url,使用GET方法,跳轉到相應的頁面
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))

具體的業務views這裏沒有給出,作爲練習,留給大家。

可用於整個admin站點的actions

前面創建的actions基本都是針對某個模型,某個類進行操作。實際上有時候,我們還需要可以對admin站點內所有模型都有效的acitons。上面寫的export_selected_objects函數可以是一個很好的例子。要實現這一功能,你需要使用內置的AdminSite.add_action方法:

AdminSite.add_action(action, name=None)[source]

from django.contrib import admin

admin.site.add_action(export_selected_objects, 'verbose name')

禁用acitons

  • 禁用全站級別的acitons:AdminSite.disable_action(name)[source]

例如,禁用內置的刪除方法:

admin.site.disable_action('delete_selected')
  • 全站禁用,但個別可用:在ModelAdmin.actions中顯示的引用

例如:

# 全站禁用刪除功能
admin.site.disable_action('delete_selected')

# 這個老老實實的被禁了
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# 這個聲明:我還要用
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...
  • 在指定模型中禁用所有actions:設置ModelAdmin.actions爲None
    (這會連帶全局actions一起禁用了。)
    例如:

    class MyModelAdmin(admin.ModelAdmin):
    actions = None

  • 根據條件自動啓用或禁用:ModelAdmin.get_actions(request)[source]
    你還可以根據條件自動選擇性的啓動或禁用某些acitons,你只需要改寫ModelAdmin.get_actions()方法。

該方法將返回一個包含actions的字典。字典的鍵是aciton的名字(也就是前面的'delete_selected', 'a_third_action'之類),值是一個元組,包含(函數、名字、別名)

例如,允許用戶名以“J”開頭的用戶批量刪除對象,但其它用戶不行:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super(MyModelAdmin, self).get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions

二、 admin文檔生成器

Django的admindocs應用可以從模型、視圖、模板標籤等地方獲得文檔內容。

概覽
要激活admindocs,請按下面的步驟操作:

  • 在INSTALLED_APPS內添加django.contrib.admindocs
  • 在urlpatterns內添加url(r'^admin/doc/',include('django.contrib.admindocs.urls'))。確保它處於 r'^admin/'條目之前,原因你懂的。
  • 安裝Python的docutils模塊(http://docutils.sf.net/)
  • 可選:想使用admindocs的書籤小工具,需要安裝django.contrib.admindocs.middleware.XViewMiddleware。

譯者注:在命令行中,使用pip3 install wheel,然後pip3 install docutils就可以迅速安裝好Python的這個包了。

如果上述步驟順利完成,那麼你可以從admin界面訪問doc界面,也可以直接訪問“.../admin/doc”,如下圖:

image_1b3pdb2s81qpfl7f17vo14b41be89.png-12.2kB
它看起來是下面的樣子:
image_1b3pdbns81ku015j3u7r1hse09m.png-109.9kB

下面的這些特殊標記,可幫助你在文檔字符串中,快速創建指向其它組件的鏈接:
image_1b3pdjv5c11216jr5nl3mcr2913.png-53.2kB

模型

在doc頁面的模型部分,列出了所有的模型,點擊可以查看具體的字段等細節信息。信息主要來自字段的help_txt部分和模型方法的docstring部分。如下面圖中展示:

有用的幫助信息看起來是這個樣子的:

class BlogEntry(models.Model):
    """
    Stores a single blog entry, related to :model:`blog.Blog` and
    :model:`auth.User`.
    """
    slug = models.SlugField(help_text="A short label, generally used in URLs.")
    author = models.ForeignKey(
        User,
        models.SET_NULL,
        blank=True, null=True,
    )
    blog = models.ForeignKey(Blog, models.CASCADE)
    ...

    def publish(self):
        """Makes the blog entry live on the site."""
        ...

image_1b3pdte9vsra1ug033l1gjqmg51t.png-104.9kB
image_1b3pdud35rgi1sg137t18kucsv2a.png-117.8kB

視圖
站點內的每個URL都會在doc內享有一個頁面,點擊某個URL將會展示對應的視圖信息。主要包括下面這些信息,請儘量豐富它們:

  • 視圖功能的簡單描述
  • 上下文環境,或者視圖模塊裏的變量列表
  • 視圖內使用的模板

例如:

from django.shortcuts import render

from myapp.models import MyModel

def my_view(request, slug):
    """
    Display an individual :model:`myapp.MyModel`.

    **Context**

    ``mymodel``
        An instance of :model:`myapp.MyModel`.

    **Template:**

    :template:`myapp/my_template.html`
    """
    context = {'mymodel': MyModel.objects.get(slug=slug)}
    return render(request, 'myapp/my_template.html', context)

image_1b3pecjkf6gj16391370n7a1pe82n.png-132.3kB
模板標籤和過濾器

所有Django內置的或者你自定義的或者第三方app提供的標籤和過濾器都將在頁面內展示:
image_1b3peevvf1lbo7cj1iqdeic1rjd34.png-133.1kB
image_1b3pefdp2t8s99m1or4i9212813h.png-140.4kB

6.5.1.2 ModelAdmin類

ModelAdmin類是一個模型在admin頁面裏的展示方法,如果你對默認的admin頁面滿意,那麼你完全不需要定義這個類,直接使用最原始的樣子就好了。通常,它們保存在app的admin.py文件裏。下面是個簡單的例子:

from django.contrib import admin
from myproject.myapp.models import Author

# 創建一個ModelAdmin的子類
class AuthorAdmin(admin.ModelAdmin):
    pass
    
# 註冊的時候,將原模型和ModelAdmin耦合起來
admin.site.register(Author, AuthorAdmin)

裝飾器註冊

register(*models, site=django.admin.sites.site)[source]
除了常用的admin.site.register(Author, AuthorAdmin)方式,你還可以用裝飾器的方式連接模型和ModelAdmin。如下所示:

from django.contrib import admin
from .models import Author

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    pass

這個裝飾器可以接收一些模型類作爲參數,以及一個可選的關鍵字參數site(如果你使用的不是默認的AdminSite)。

from django.contrib import admin
from .models import Author, Reader, Editor
from myproject.admin_site import custom_admin_site

@admin.register(Author, Reader, Editor, site=custom_admin_site)
class PersonAdmin(admin.ModelAdmin):
    pass

注意:在Python2中如果你在類的__init__()方法中引用了模型的admin類,比如super(PersonAdmin, self).init(*args, **kwargs),那麼你必須使用admin.site.register()的方式,而不能使用裝飾器的方式。但是,在Python3中,可以避免這個問題,通過使用super().init(*args, **kwargs)。

搜索admin文件

當你在 INSTALLED_APPS設置中添加了'django.contrib.admin'後,Django將自動在每個應用中搜索admin模塊並導入它。

  • class apps.AdminConfig:admin默認的AppConfig類,當Django啓動時自動調用autodiscover()方法
  • class apps.SimpleAdminConfig:和上面的類似,但不調用autodiscover()
  • autodiscover()[source]:自動搜索admin模塊的方法。在使用自定義的site時,必須禁用這個方法,你應該在INSTALLED_APPS設置中用'django.contrib.admin.apps.SimpleAdminConfig'替代'django.contrib.admin'

ModelAdmin選項

ModelAdmin.actions

一個列表,包含自定義的actions,前面有敘述。


ModelAdmin.actions_on_top

是否在列表上方顯示actions的下拉框,默認爲True


ModelAdmin.actions_on_bottom

是否在列表下方顯示actions的下拉框,默認爲False。和上面的選項一樣,設置布爾值,actions_on_top=Fasle或者actions_on_bottom = True這種,可同時在上下都顯示下拉框。效果看下面的圖片,沒什麼大用途。
image_1b3s30k05bq215jlmv315daeoq9.png-50.1kB


ModelAdmin.actions_selection_counter

是否在actions下拉框右側顯示選中的對象的數量,默認爲True,可改爲False。
image_1b3s34r4h1aj99b61vtn1ac256dm.png-6.6kB


ModelAdmin.date_hierarchy

根據你指定的日期相關的字段,爲頁面創建一個時間導航欄,可通過日期過濾對象。例如:
date_hierarchy = 'pub_date'
它的效果看起來是這樣的:
image_1b3s3pbcocqj1sb97181naeg1v13.png-38.4kB


ModelAdmin.empty_value_display

指定空白顯示的內容。如果你有些字段沒有值(例如None,空字符串等等),默認情況下會顯示破折號“-”。但這個選項可以讓你自定義顯示什麼,如下例就顯示個“-empty-”:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    empty_value_display = '-empty-'

你還可以爲整個admin站點設置默認空白顯示值,通過AdminSite.empty_value_display="xxxxxxx"。甚至爲某個函數設置空白值,如下:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title', 'view_birth_date')

    def view_birth_date(self, obj):
        return obj.birth_date
    # 注意下面這句
    view_birth_date.empty_value_display = '???'

ModelAdmin.exclude

不顯示指定的某些字段。如下例有這麼個模型:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3)
    birth_date = models.DateField(blank=True, null=True)

如果你不希望在頁面內顯示birth_date字段,那麼這麼設置:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    fields = ('name', 'title')

和這麼設置是一樣的:

from django.contrib import admin

class AuthorAdmin(admin.ModelAdmin):
    # 一定注意了,值是元組!一個元素的時候,最後的逗號不能省略。
    exclude = ('birth_date',)

ModelAdmin.fields

按你希望的順序,顯示指定的字段。與exclude相對。但要注意與list_display區分。
這裏有個小技巧,你可以通過組合元組的方式,讓某些字段在同一行內顯示,例如下面的做法“url”和“title”將在一行內,而“content”則在下一行。

class FlatPageAdmin(admin.ModelAdmin):
    fields = (('url', 'title'), 'content')

如果沒有對field或fieldsets選項進行定義,那麼Django將按照模型定義中的順序,每一行顯示一個字段的方式,逐個顯示所有的非AutoField和editable=True的字段。(自動字段,如主鍵,不可編輯字段是不會出現在頁面裏的。)


ModelAdmin.fieldsets

這個功能其實就是根據字段對頁面進行分組顯示或佈局了。fieldsets是一個二元元組的列表。每個二元元組代表一個

,代表整個form的一部分。

 

二元元組的格式爲 (name,field_options),name是一個表示該filedset標題的字符串,field_options是一個包含在該filedset內的字段列表。

下面是一個例子,有助於你理解:

from django.contrib import admin

class FlatPageAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('url', 'title', 'content', 'sites')
        }),
        ('Advanced options', {
            'classes': ('collapse',),
            'fields': ('registration_required', 'template_name'),
        }),
    )

它的頁面看起來像下面的樣子:
image_1b3s63k3c6pd1uoiho51bt11f71g.png-103.9kB

在filed_options字典內,可以使用下面這些關鍵字:
fields:一個必填的元組,包含要在fieldset中顯示的字段。例如:

{
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}

同樣,它也可以像前面那樣通過組合元組,實現多個字段在一行內的效果:

{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
}

fileds可以包含 readonly_fields的值,作爲只讀字段。

classes:一個包含額外的CSS類的元組,例如:

{
'classes': ('wide', 'extrapretty'),
}

兩個比較有用的樣式是collaspe和wide,前者將fieldsets摺疊起來,後者讓它具備更寬的水平空間。

description:一個可選的額外的說明文本,放置在每個fieldset的頂部。但是,這裏並沒有對HTML語法進行轉義,因此可能有時候會造成一些莫名其妙的顯示,要忽略HTML的影響,請使用 django.utils.html.escape()。


ModelAdmin.filter_horizontal

水平擴展多對多字段。默認情況下,ManyTOManyField在admin的頁面中會顯示爲一個select框。在需要選擇大量對象時,這會有點困難。將ManyTOManyField添加到這個屬性列表裏後,頁面就會對字段進行擴展,並提供過濾功能。如下圖:
image_1b3ukkfjquhm1ron1lcs15541fcf9.png-55.9kB


ModelAdmin.filter_vertical

與上面的類似,不過是改成垂直佈置了。


ModelAdmin.form

默認情況下,系統會爲你的模型動態的創建ModelForm,它用於創建你的添加/修改頁面的表單。你可以輕易地提供你自己的ModelForm,在"添加/修改"頁面覆蓋默認的表單行爲。或者,你還可以自定義默認的表單而不是指定一個全新的表單,通過使用ModelAdmin.get_form()方法。具體的例子,參考《Adding custom validation to the admin》。

注意:如果你在一個ModelForm上定義了Meta.model屬性,你也必須同時定義Meta.fields屬性(或者Meta.exclude屬性)。然而,當admin定義有自己的fields,Meta.fields屬性會被忽略。如果ModelForm僅僅被用於admin,最好的解決方案是忽略Meta.model屬性,因爲ModelAdmin會自動選擇正確的模型。或者,你可以設置fields=[]在Meta類中,用於ModelForm的驗證。

注意:如果你的ModelForm和ModelAdmin同時定義了exclude選項,那麼ModelAdmin中的具有優先權,如下例所示,"age"字段將被排除,但是“name”字段將被顯示:

from django import forms
from django.contrib import admin
from myapp.models import Person

class PersonForm(forms.ModelForm):

    class Meta:
        model = Person
        exclude = ['name']

class PersonAdmin(admin.ModelAdmin):
    exclude = ['age']
    form = PersonForm

ModelAdmin.formfield_overrides

這個屬性比較難以理解,通過一個列子來解釋可能會更好一點。設想一下我們自己寫了個RichTextEditorWidget(富文本控件),然後想用它來代替傳統的<textarea>(文本域控件)用於輸入大段文字。我們可以這麼做:

from django.db import models
from django.contrib import admin

# 從對應的目錄導入我們先前寫好的widget和model
from myapp.widgets import RichTextEditorWidget
from myapp.models import MyModel

class MyModelAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.TextField: {'widget': RichTextEditorWidget},
    }

注意在上面的外層字典中的鍵是一個實際的字段類,而不是字符串,對應的值又是一個字典;這些參數將被傳遞給form字段的__init__()方法。

警告:如果你想使用一個帶有關係字段(例如ForeignKey or ManyToManyField)的自定義widget。請確保你沒有在raw_id_fields或radio_fields之中include那些字段的名字。


ModelAdmin.inlines

參閱InlineModelAdmin對象,就像ModelAdmin.get_formsets_with_inlines()一樣


ModelAdmin.list_display

指定顯示在修改頁面上的字段。這是一個很常用的技巧。
例如:

list_display = ('first_name', 'last_name')

如果你不設置這個屬性,admin站點將只顯示一列,內容是每個對象__str__()(unicode() on Python 2)方法返回的內容。

在list_display中,你可以設置四種值:
模型的字段

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name')

一個調用,它接收一個模型實例作爲參數

def upper_case_name(obj):
    return ("%s %s" % (obj.first_name, obj.last_name)).upper()
upper_case_name.short_description = 'Name'

class PersonAdmin(admin.ModelAdmin):
    list_display = (upper_case_name,)

一個表示ModelAdmin的某個屬性的字符串

類似上面的調用,換了種寫法而已,例如:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('upper_case_name',)

    def upper_case_name(self, obj):
        return ("%s %s" % (obj.first_name, obj.last_name)).upper()
    upper_case_name.short_description = 'Name'

一個表示模型的某個屬性的字符串

類似第二種,但是此處的self是模型實例。參考下面的例子:

from django.db import models
from django.contrib import admin

class Person(models.Model):
    name = models.CharField(max_length=50)
    birthday = models.DateField()

    def decade_born_in(self):
        return self.birthday.strftime('%Y')[:3] + "0's"
    decade_born_in.short_description = 'Birth decade'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'decade_born_in')

下面是對list_display屬性的一些特別提醒:

  • 對於Foreignkey字段,顯示的將是__str__() (unicode() on Python 2)。
  • 不支持ManyToMany字段。如果你非要顯示它,請自定義方法。
  • 對於BooleanField或NullBooleanField字段,會用on/off圖標代替True/False。
  • 如果給list_display提供的值是一個模型的、ModelAdmin的或者可調用的方法,默認情況下會自動對返回結果進行HTML轉義,這可能不是你想要的,例如,如果你按下面的代碼:
from django.db import models
from django.contrib import admin
from django.utils.html import format_html

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_name(self):
        # 關鍵是這句!!!!!請自己調整縮進。
        return '<span style="color: #%s;">%s %s</span>'%(
            self.color_code,
            self.first_name,
            self.last_name,
        )

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'colored_name')

實際的效果是什麼樣的?官方沒有給配圖,我自己做了個,內容和圖對不上,但意思你應該能懂:
1.png-88.2kB
很明顯,你是想要有個css和html效果,但Django把它當普通的字符串了。怎麼辦呢?用 format_html()方法!

from django.db import models
from django.contrib import admin
# 需要先導入!
from django.utils.html import format_html

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_name(self):
        # 這裏還是重點,注意調用方式,‘%’變成‘{}’了!
        return format_html(
            '<span style="color: #{};">{} {}</span>',
            self.color_code,
            self.first_name,
            self.last_name,
        )

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'colored_name')

這麼看起來就會是你想要的結果了:
2.png-69.5kB
注意:這個特性,在1.9版本之前可以使用allow_tags方法,但由於它不夠安全,現在被format_html()、format_html_join()或者mark_safe()取代了。

  • 如果某個字段的值爲None或空字符串或空的可迭代對象,那麼默認顯示爲短橫槓“-”,你可以使用AdminSite.empty_value_display在全局改寫這一行爲:
rom django.contrib import admin

admin.site.empty_value_display = '(None)'

或者使用ModelAdmin.empty_value_display只改變某個類的行爲:

class PersonAdmin(admin.ModelAdmin):
    empty_value_display = 'unknown'

或者更細粒度的只改變某個字段的這一行爲:

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'birth_date_view')

    def birth_date_view(self, obj):
         return obj.birth_date

    birth_date_view.empty_value_display = 'unknown'
  • 默認情況下,一個返回布爾值的方法在list_display中是顯示true或者False的:
    image_1b417pa5b1rdd196vd611hkg1kqo1h.png-71.7kB
    但如果你給這個方法添加一個boolean的屬性並賦值爲True,它將顯示爲on/off的圖標,如下圖:
from django.db import models
from django.contrib import admin

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    birthday = models.DateField()

    def born_in_fifties(self):
        return self.birthday.strftime('%Y')[:3] == '195'
    # 關鍵在這裏
    born_in_fifties.boolean = True

class PersonAdmin(admin.ModelAdmin):
    # 官方文檔這裏有錯,將'name'改爲'first_name' 
    list_display = ('first_name', 'born_in_fifties')

image_1b417t94q1dkp1j20c671fqc1tbm1u.png-71kB

  • 你還可以這麼幹:list_display=('str','some_other_field')
  • 通常情況下,在list_display列表裏的元素如果不是數據庫內的某個具體字段,是不能根據它進行排序的。但是如果給這個字段添加一個admin_order_field屬性,並賦值一個具體的數據庫內的字段,則可以按這個字段對原字段進行排序,如下所示:
from django.db import models
from django.contrib import admin
from django.utils.html import format_html

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    color_code = models.CharField(max_length=6)

    def colored_first_name(self):
        return format_html(
            '<span style="color: #{};">{}</span>',
            self.color_code,
            self.first_name,
        )
    # 就是這一句了!
    colored_first_name.admin_order_field = 'first_name'

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'colored_first_name')

本來“colored_first_name”是不能排序的,給它的“admin_order_field”賦值“first_name”後,就依託first_name進行排序了。
要降序的話,使用連字符“-”:

colored_first_name.admin_order_field = '-first_name'

你還可以跨表跨關係引用:

class Blog(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

class BlogAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'author_first_name')

    def author_first_name(self, obj):
        return obj.author.first_name
    # 指定了另一張表的first_name作爲排序的依據
    author_first_name.admin_order_field = 'author__first_name'
  • list_display裏的元素還可以是某個屬性。但是請注意的是,如果使用python的@property方式來構造一個屬性,則不能給它添加short_description描述,只有使用property()函數的方法構造屬性的時候,纔可以添加short_description描述,如下:
class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def my_property(self):
        return self.first_name + ' ' + self.last_name
    my_property.short_description = "Full name of the person"

    full_name = property(my_property)

class PersonAdmin(admin.ModelAdmin):
    list_display = ('full_name',)
  • list_display中的每個字段名在HTML中都將自動生成CSS類屬性,在“th”標籤中以“column-<field_name>”的格式,你可以通過它,對前端進行自定義或調整,例如設置寬度等等。
  • Django將按下面的順序,解釋list_display中的每個元素:
  1. 模型的字段
  2. 調用
  3. ModelAdmin的屬性
  4. 模型的屬性

ModelAdmin.list_display_links

指定用於鏈接修改頁面的字段。
通常情況,list_display列表中的第一個元素被作爲指向目標修改頁面的超級鏈接點。但是,使用list_display_links可以幫你修改這一默認設置:
設置爲None,則根本沒有鏈接了,你無法調到目標的修改頁面。
或者設置爲一個字段的元組或列表(和list_display的格式一樣),這裏面的每一個元素都是一個指向修改頁面的鏈接。你可以指定和list_display一樣多的元素個數,Django不關係它的多少。唯一需要注意的是,如果你要使用list_display_links,你必須先有list_display。
下面這個例子中“first_name”和“last_name”都可以點擊並跳轉到修改頁面。

class PersonAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'birthday')
    list_display_links = ('first_name', 'last_name')

而如果這樣,你將沒有任何鏈接:

class AuditEntryAdmin(admin.ModelAdmin):
    list_display = ('timestamp', 'message')
    list_display_links = None

ModelAdmin.list_editable

這個選項是讓你指定在修改列表頁面中哪些字段可以被編輯。指定的字段將顯示爲編輯框,可修改後直接批量保存,如下圖:

image_1b41c88k49h7vtbddel7uoqm2b.png-79.5kB
在這裏,我將“last_name”設置爲了list_editable。
需要注意的是:一不能將list_display中沒有的元素設置爲list_editable,二是不能將list_display_links中的元素設置爲list_editable。原因很簡單,你不能編輯沒顯示的字段或者作爲超級鏈接的字段。


ModelAdmin.list_filter

設置list_filter屬性後,可以激活修改列表頁面右側邊欄,用於對列表元素進行過濾,如下圖:
3.png-37.3kB

list_filter必須是一個元組或列表,其元素是如下類型之一:

  • 某個字段名,但該字段必須是BooleanField、CharField、DateField、DateTimeField、IntegerField、ForeignKey或者ManyToManyField中的一種。例如:
class PersonAdmin(admin.ModelAdmin):
    list_filter = ('is_staff', 'company')

在這裏,你可以利用雙下劃線進行跨表關聯,如下例:

class PersonAdmin(admin.UserAdmin):
    list_filter = ('company__name',)
  • 一個繼承django.contrib.admin.SimpleListFilter的類。你要給這個類提供title和parameter_name的值,並重寫lookups和queryset方法。例如:
from datetime import date
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _


class DecadeBornListFilter(admin.SimpleListFilter):
    # 提供一個可讀的標題
    title = _('出生年代')

    # 用於URL查詢的參數.
    parameter_name = 'decade'

    def lookups(self, request, model_admin):
        """
        返回一個二維元組。每個元組的第一個元素是用於URL查詢的真實值,
        這個值會被self.value()方法獲取,並作爲queryset方法的選擇條件。
        第二個元素則是可讀的顯示在admin頁面右邊側欄的過濾選項。        
        """
        return (
            ('80s', _('80年代')),
            ('90s', _('90年代')),
        )

    def queryset(self, request, queryset):
        """
        根據self.value()方法獲取的條件值的不同執行具體的查詢操作。
        並返回相應的結果。
        """
        if self.value() == '80s':
            return queryset.filter(birthday__gte=date(1980, 1, 1),
                                    birthday__lte=date(1989, 12, 31))
        if self.value() == '90s':
            return queryset.filter(birthday__gte=date(1990, 1, 1),
                                    birthday__lte=date(1999, 12, 31))


class PersonAdmin(admin.ModelAdmin):
    
    list_display = ('first_name', 'last_name', "colored_first_name",'birthday')

    list_filter = (DecadeBornListFilter,)

其效果如下圖:
image_1b439f5973te198p1lgl1a9b1okdl.png-98.7kB

注意:爲了方便,我們通常會將HttpRequest對象傳遞給lookups和queryset方法,如下所示:

class AuthDecadeBornListFilter(DecadeBornListFilter):

    def lookups(self, request, model_admin):
        if request.user.is_superuser:
            return super(AuthDecadeBornListFilter, self).lookups(request, model_admin)

    def queryset(self, request, queryset):
        if request.user.is_superuser:
            return super(AuthDecadeBornListFilter, self).queryset(request, queryset)

同樣的,我們默認將ModelAdmin對象傳遞給lookups方法。下面的例子根據查詢結果,調整過濾選項,如果某個年代沒有符合的對象,則這個選項不會在右邊的過濾欄中顯示:

class AdvancedDecadeBornListFilter(DecadeBornListFilter):

    def lookups(self, request, model_admin):
        """
        只有存在確切的對象,並且它出生在對應年代時,纔會出現這個過濾選項。
        """
        qs = model_admin.get_queryset(request)
        if qs.filter(birthday__gte=date(1980, 1, 1),
                      birthday__lte=date(1989, 12, 31)).exists():
            yield ('80s', _('in the eighties'))
        if qs.filter(birthday__gte=date(1990, 1, 1),
                      birthday__lte=date(1999, 12, 31)).exists():
            yield ('90s', _('in the nineties'))
  • 也可以是一個元組。它的第一個元素是個字段名,第二個元素則是繼承了django.contrib.admin.FieldListFilter的類。例如:
class PersonAdmin(admin.ModelAdmin):
    list_filter = (
        ('is_staff', admin.BooleanFieldListFilter),
    )

你可以使用RelatedOnlyFieldListFilter限制關聯的對象。假設author是關聯User模型的ForeignKey,下面的用法將只選擇那些出過書的user而不是所有的user:

class BookAdmin(admin.ModelAdmin):
    list_filter = (
        ('author', admin.RelatedOnlyFieldListFilter),
    )

另外,has_output()方法可以控制顯示方式。
template屬性可以指定渲染的模板,如下則指定了一個自定義的模板。(Django默認的模板爲admin/filter.html)

class FilterWithCustomTemplate(admin.SimpleListFilter):
    template = "custom_template.html"

ModelAdmin.list_max_show_all

設置一個數值,當列表元素總數小於這個值的時候,將顯示一個“show all”鏈接,點擊後就能看到一個展示了所有元素的頁面。該值默認爲200.


ModelAdmin.list_per_page

設置每頁顯示多少個列表元素。Django自動幫你分頁。默認爲100。


ModelAdmin.list_select_related

如果設置了list_select_related屬性,Django將會使用select_related()方法查詢數據,這可能會幫助你減少一些數據庫訪問。

屬性的值可以是布爾值、元組或列表,默認爲False。
當值爲True時, 將始終調用select_related()方法;如果值爲False,Django將查看list_display屬性,只對ForeignKey字段調用select_related()方法。

如果你需要更細粒度的控制,請賦值一個元組(或列表)。空元組將阻止select_related()方法,否則元組會被當做參數傳遞給select_related()方法。例如:

class ArticleAdmin(admin.ModelAdmin):
    list_select_related = ('author', 'category')

這將會調用select_related('author', 'category')。
如果你想從request獲取值,進行動態調用,請實現get_list_select_related()方法。


ModelAdmin.ordering

設置排序的方式。
屬性的值必須爲一個元組或列表,格式和模型的ordering參數一樣。
如果不設置這個屬性,Django將按默認方式進行排序。
如果你想進行動態排序,請實現get_ordering()方法。


ModelAdmin.paginator

指定用於分頁的分頁類。默認情況下,分頁類用的是Django自帶的django.core.paginator.Paginator。如果自定義分頁類的構造函數接口和django.core.paginator.Paginator的不一樣,那你還需要自己實現ModelAdmin.get_paginator()方法。


ModelAdmin.prepopulated_fields

設置預填充字段。不接收DateTimeField、ForeignKey和ManyToManyField類型的字段。

class ArticleAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}

ModelAdmin.preserve_filters

默認情況下,當你對目標進行創建、編輯或刪除操作後,頁面會依然保持原來的過濾狀態。將preserve_filters設爲False後,則會返回未過濾狀態。


ModelAdmin.radio_fields

默認情況下,Django使用select標籤顯示ForeignKey或choices集合。如果將這種字段設置爲radio_fields,則會以radio_box標籤的形式展示。下面的例子假設group是Person模型的ForeignKey字段,:

class PersonAdmin(admin.ModelAdmin):
    # 垂直佈局。(肯定也有水平佈局HORIZONTAL的啦)
    radio_fields = {"group": admin.VERTICAL}

注意:不要將ForeignKey或choices集合之外的字段類型設置給這個屬性。
image_1b43tjlci51c8f58k1i8h12uj9.png-90.9kB


ModelAdmin.raw_id_fields

這個屬性會改變默認的ForeignKey和ManyToManyField的展示方式,它會變成一個輸入框,用於輸入關聯對象的主鍵id。對於ManyToManyField,id以逗號分隔。並且再輸入框右側提供一個放大鏡的圖標,你可以點擊進入選擇界面。例如:

class PersonAdmin(admin.ModelAdmin):
    raw_id_fields = ("group",)

image_1b43u00e212geith8gr14s91bjm.png-144.5kB


ModelAdmin.readonly_fields

該屬性包含的字段在頁面內將展示爲不可編輯狀態。它還可以展示模型或者ModelAdmin本身的方法的返回值,類似ModelAdmin.list_display的行爲。參考下面的例子:

from django.contrib import admin
from django.utils.html import format_html_join
from django.utils.safestring import mark_safe

class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ('address_report',)

    def address_report(self, instance):
        # assuming get_full_address() returns a list of strings
        # for each line of the address and you want to separate each
        # line by a linebreak
        return format_html_join(
            mark_safe('<br/>'),
            '{}',
            ((line,) for line in instance.get_full_address()),
        ) or mark_safe("<span class='errors'>I can't determine this address.</span>")

    # short_description functions like a model field's verbose_name
    address_report.short_description = "Address"

ModelAdmin.save_as

默認情況下,它的值爲False。如果設置爲True,那麼右下角的“Save and add another”按鈕將被替換成“Save as new”,意思也變成保存爲一個新的對象。
image_1b43uqhs2dpv16v6bla1uap1oe113.png-95.7kB


ModelAdmin.save_as_continue

Django1.10新特性。默認值爲True。
當save_as=True時,默認在保存新對象後跳轉到該對象的修改頁面。但是如果這時save_as_continue=False,則會跳轉到元素列表頁面。


ModelAdmin.save_on_top

默認爲False。
設爲True時,頁面的頂部會提供同樣的一系列保存按鈕。


ModelAdmin.search_fields

設置這個屬性,可以爲admin的修改列表頁面添加一個搜索框。

被搜索的字段可以是CharField或者TextField文本類型,也可以通過雙下劃線進行ForeignKey或者ManyToManyField的查詢,格式爲search_fields = ['foreign_key__related_fieldname'].
例如:如果作者是博客的ForeignKey字段,下面的方式將通過作者的email地址來查詢對應的博客,也就是email地址是查詢值的作者所寫的所有博客。

search_fields = ['user__email']

當你在搜索框裏輸入一些文本的時候,Django會將文本分割成一個一個的關鍵字,並返回所有包含這些關鍵字的對象,必須注意的是,每個關鍵詞至少得是search_fields其中之一。例如,如果search_fields是['first_name', 'last_name'],當用戶輸入“John lennon”時(注意中間的空格),Django將執行等同於下面的SQL語法WHERE子句:

WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%') AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')

如果要執行更加嚴格的匹配或搜索,可以使用一些元字符,例如“^”。類似正則,它代表從開頭匹配。例如,如果search_fields是['^first_name','^last_name'],當用戶輸入“John lennon”時(注意中間的空格),Django將執行等同於下面的SQL語法WHERE子句:

WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%') AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')

也可以使用“=”,來進行區分大小寫的並絕對相等的嚴格匹配。例如,如果search_fields是['=first_name','=last_name'],當用戶輸入“John lennon”時(注意中間的空格),Django將執行等同於下面的SQL語法WHERE子句:

WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john') AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')

請注意的是,由於使用空格進行字符分割,因此,無法對first_name爲'john winston'(注意空格)進行精確查找,因爲,它會被分成兩個關鍵字,而不是當做一個整體。

在使用MySQL數據庫時,還可以使用@符號進行查找。這將進行全文本匹配,類似使用索引的默認查找方法。

如果你想自定義查找,可以使用ModelAdmin.get_search_results()自己寫查找方法。


ModelAdmin.show_full_result_count

用於設置是否顯示一個過濾後的對象總數的提示信息,例如“99 results (103 total)”。如果它被設置爲False,那麼顯示的將是“ 99 results (Show all)”。
默認情況下,它的值爲True,這將會對整個表進行一個count操作,在表很大的時候,可能會耗費一定的時間和資源。


ModelAdmin.view_on_site

這個屬性可以控制是否在admin頁面顯示“View site”的鏈接。這個鏈接主要用於跳轉到你指定的URL頁面。
image_1b4588dq6303r3u1ai81hkkm071g.png-25.1kB
屬性的值可以是布爾值或某個調用。如果是True(默認值),對象的get_absolute_url()方法將被調用並生成rul。

如果你的模型有一個get_absolute_url()方法,但你不想顯示“View site”鏈接,你只需要將view_on_site屬性設置爲False。

如果屬性的值是一個調用,它將接收一個模型實例作爲參數:

from django.contrib import admin
from django.urls import reverse

class PersonAdmin(admin.ModelAdmin):
    def view_on_site(self, obj):
        url = reverse('person-detail', kwargs={'slug': obj.slug})
        return 'https://example.com' + url

自定義模板選項

在《Overriding admin templates》章節中,詳細介紹瞭如果重寫或擴展默認的admin模板。在ModelAdmin視圖中可以使用下面的選項對默認的模板進行重寫:

ModelAdmin.add_form_template
add_view()方法中需要使用的指向自定義模板的路徑。

ModelAdmin.change_form_template:
change_view()方法中需要使用的指向自定義模板的路徑。

ModelAdmin.change_list_template:
changelist_view()方法中需要使用的指向自定義模板的路徑。

ModelAdmin.delete_confirmation_template:
delete_view()方法中需要使用的指向自定義模板的路徑。該模板是刪除對象時的確認頁面。

ModelAdmin.delete_selected_confirmation_template:
delete_selected動作中需要使用的指向自定義模板的路徑。這也是一個刪除對象的確認頁面。

ModelAdmin.object_history_template:
history_view()方法中需要使用的指向自定義模板的路徑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章