zoukankan      html  css  js  c++  java
  • Django admin

    Django admin 权威指南(一)

    版本: Django 1.10

     

    此部分由官方文档《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应用的用户模型):

     

    警告:“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)

     

    然后,页面看起来是下面的样子(注意下拉框):

     

    处理错误
    如果你能够预知在自定义的操作中可能产生的错误,请处理该错误,并通过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)

     

    回到浏览器,再试试,你会看到如下图所示(注意顶部的绿色提示行):

     

    跳转到中间页面

     

    默认情况下,执行完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”,如下图:

     


    它看起来是下面的样子:

     

    下面的这些特殊标记,可帮助你在文档字符串中,快速创建指向其它组件的链接:

     

    模型

     

    在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."""
            ...

     


     

    视图
    站点内的每个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)

     


    模板标签和过滤器

     

    所有Django内置的或者你自定义的或者第三方app提供的标签和过滤器都将在页面内展示:

     

    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这种,可同时在上下都显示下拉框。效果看下面的图片,没什么大用途。

     


     

    ModelAdmin.actions_selection_counter

     

    是否在actions下拉框右侧显示选中的对象的数量,默认为True,可改为False。

     


     

    ModelAdmin.date_hierarchy

     

    根据你指定的日期相关的字段,为页面创建一个时间导航栏,可通过日期过滤对象。例如:
    date_hierarchy = 'pub_date'
    它的效果看起来是这样的:

     


     

    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'),
            }),
        )

    它的页面看起来像下面的样子:

    在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添加到这个属性列表里后,页面就会对字段进行扩展,并提供过滤功能。如下图:


    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')

    实际的效果是什么样的?官方没有给配图,我自己做了个,内容和图对不上,但意思你应该能懂:

    很明显,你是想要有个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')

    这么看起来就会是你想要的结果了:

    注意:这个特性,在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的:

      但如果你给这个方法添加一个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')

    • 你还可以这么干: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-
    • Django将按下面的顺序,解释list_display中的每个元素:
    1. 模型的字段
    2. 调用
    3. ModelAdmin的属性
    4. 模型的属性

    指定用于链接修改页面的字段。
    通常情况,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

    这个选项是让你指定在修改列表页面中哪些字段可以被编辑。指定的字段将显示为编辑框,可修改后直接批量保存,如下图:


    在这里,我将“last_name”设置为了list_editable。
    需要注意的是:一不能将list_display中没有的元素设置为list_editable,二是不能将list_display_links中的元素设置为list_editable。原因很简单,你不能编辑没显示的字段或者作为超级链接的字段。


    ModelAdmin.list_filter

    设置list_filter属性后,可以激活修改列表页面右侧边栏,用于对列表元素进行过滤,如下图:

    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,)

    其效果如下图:

    注意:为了方便,我们通常会将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。


    如果设置了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集合之外的字段类型设置给这个属性。


    ModelAdmin.raw_id_fields

    这个属性会改变默认的ForeignKey和ManyToManyField的展示方式,它会变成一个输入框,用于输入关联对象的主键id。对于ManyToManyField,id以逗号分隔。并且再输入框右侧提供一个放大镜的图标,你可以点击进入选择界面。例如:

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


    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”,意思也变成保存为一个新的对象。


    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页面。

    属性的值可以是布尔值或某个调用。如果是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()方法中需要使用的指向自定义模板的路径。

    未完待续,觉得不错就点赞支持一下吧!

    

     

     

     

    分类: Django

     
     
  • 相关阅读:
    xshell 缺少mfc110u.dll
    notepad++ 常用插件
    java 发送 http post 和 get 请求(利用unirest)
    my.conf配置大全
    md5算法
    sprinboot+redis
    jq下拉插件,chosen
    springboot+idea 热部署
    Jquery 监听浏览器前进后退
    手机自带的表情入库
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6195938.html
Copyright © 2011-2022 走看看