Django-入门指南-七-

223 阅读1小时+

Django 入门指南(七)

原文:Beginning Django

协议:CC BY-NC-SA 4.0

十一、Django Admin

Django admin 是一个用户友好的应用,用于管理链接到 Django 项目的关系数据库的内容。尽管 Django admin 在设置方面几乎毫不费力——如第一章所述——但在这一章中你将学到多种配置选项来创建更强大的 Django admin 显示和功能。

在本章中,您将首先学习如何在 Django 管理中注册 Django 模型。接下来,您将学习如何在 Django admin 中显示记录,并使用排序、行内编辑、分页、搜索和操作按钮等技术。此外,您将学习如何定制 Django 管理表单和关系,以便使用 Django 管理轻松地创建、更新和删除模型记录。

接下来,您将学习如何通过配置字段和定制模板定制 Django 管理页面,以及如何添加定制数据和覆盖 Django 管理类方法和字段来创建最灵活的 Django 管理页面。最后,您将学习如何配置和实施 Django 管理权限,以及如何创建多个 Django 管理站点实例。

在 Django 管理中设置 Django 模型

尽管 Django admin 为 Django 项目的数据库提供了一个优秀的管理工具,但是仅仅创建和安装 Django 模型还不足以在 Django admin 中访问它们的数据。

为了访问 Django 管理中的 Django 模型记录,您必须在admin.py文件中注册和配置 Django 模型。当你创建一个应用时,一个admin.py文件会自动放在 Django 应用中,与models.pyviews.py文件放在一起。

虽然从技术上讲,你可以使用一个admin.py来注册和配置所有的 Django 模型——就像你可以使用一个models.py来定义所有的 Django 模型——但是推荐的做法是每个 Django 应用使用自己的admin.py文件来管理在models.py中定义的相应模型。

在 admin.py 文件中为 Django admin 注册 Django 模型有三种方法,所有这些都在清单 11-1 中进行了说明。

from django.contrib import admin
from coffeehouse.stores.models import Store

# Option 1 - Basic
admin.site.register(Store)

# Option 2 - Allows customizing Django admin behavior
class StoreAdmin(admin.ModelAdmin):
      pass

admin.site.register(Store, StoreAdmin)

# Option 3 – Decorator
@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
      pass

Listing 11-1.Register Django models in admin.py file

清单 11-1 中的第一个选项提供了基本的 Django 模型注册,包括声明一个 Django 模型类作为admin.site.register方法的输入。如果您不需要为模型定制默认的 Django 管理行为,这个选项就足够了。

清单 11-1 中的第二个选项利用了一个 Django 管理类,该类从admin.ModelAdmin类继承了它的行为。在这种情况下,你可以看到类是空的,但是可以定制 Django 管理行为,我将在本章中描述。一旦 Django admin 类被声明,它必须使用选项一中的相同的admin.site.register方法注册并与 Django 模型关联,其中第一个参数是 Django 模型,第二个参数是 Django admin 类。

清单 11-1 中的第三个选项利用了 Django admin @admin.register装饰器。选项三和选项二之间的语法差异在于,注册和关联发生在用@admin.register修饰 Django admin 类时,其中修饰者将 Django 模型作为其参数。

值得一提的是,虽然选项二和选项三在功能上是相同的,但是使用@admin.register装饰器——选项三——有一个限制,即您不能在它的__init__()方法中引用 Django admin 类(例如,super(StoreAdmin, self).__init__(*args, **kwargs)),这在 Python 2 和某些设计中是一个问题;如果您处于这种情况,那么您必须使用选项二用admin.site.register方法注册一个模型。

现在您已经知道如何在 Django 管理中注册 Django 模型,我将描述通过 Django 管理类定制 Django 管理行为的各种选项。为了更容易地查找定制选项,我将选项分为两个主要部分:“读取记录选项”和“创建、更新、删除记录选项”,以涵盖 Django admin 中可用的 CRUD 操作选项的整个范围,此外还包括在每个主要部分下分组功能的子部分。

Django 管理读取记录选项

当你在 Django admin 的主页上点击一个 Django 模型时,你会被带到一个页面,显示这个特定模型的记录列表。图 11-1 和 11-2 展示了Store型号的记录列表页面。

A441241_1_En_11_Fig2_HTML.jpg

图 11-2。

Django admin record list page with model str definition

A441241_1_En_11_Fig1_HTML.jpg

图 11-1。

Django admin record list page with no model str definition

如图 11-1 和 11-2 所示,每个 Django 模型记录都用一个字符串显示。默认情况下,这个字符串由 Django 模型的__str__()方法定义生成,如第七章所述。如果 Django 模型中缺少__str__方法,那么 Django 管理员会将类似图 11-1 的记录显示为‘Store object’;否则,它将返回由__str__方法为每条记录生成的结果——在图 11-2 中,这是每条Store记录的名称、城市和州属性。

记录显示:列表显示,格式 html,空值显示

虽然图 11-1 和 11-2 中呈现的基本显示行为是有帮助的,但是对于具有不表达的或复杂的__str__()方法的模型来说可能是非常有限的。Django admin 类可以用list_display选项进行配置,将记录列表与模型的各种字段分开,从而更容易查看和排序记录。清单 11-2 展示了一个带有 list_display 选项的 Django 管理类。

from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','address','city','state']

admin.site.register(Store, StoreAdmin)

Listing 11-2.Django admin list_display option

正如您在清单 11-2 中看到的,Django admin StoreAdmin类用一个值列表定义了list_display选项。该列表对应于 Django 模型字段,在本例中,这些字段来自Store模型。图 11-3 和 11-4 显示了通过添加list_display选项修改后的记录列表布局。

A441241_1_En_11_Fig4_HTML.jpg

图 11-4。

Django admin record list page with model list_display sorted

A441241_1_En_11_Fig3_HTML.jpg

图 11-3。

Django admin record list page with list_display Tip

如果你想在 Django 管理中继续显示 Django 模型通过其__str__方法生成的值,可以将其添加到list_display选项中(例如list_display = ['name','__str__'])。

在图 11-3 中,你可以看到一个更加清晰的记录列表布局,其中 list_display 中声明的每个字段都有自己的列。此外,如果你点击任何代表模型字段的列标题,记录会自动按照该属性排序,这个过程如图 11-4 所示,它极大地提高了记录的可发现性。

除了支持包含 Django 模型字段之外,list_display选项还支持其他变体来生成更复杂的列表布局。例如,如果数据库记录是不同的(例如,混合的大写和小写文本),您可以生成一个可调用的方法来操作记录,并以统一的方式(例如,全部大写)在 Django admin 中显示它们。此外,您还可以创建一个 callable,从数据库中没有明确显示的记录字段(例如,属于电子邮件记录的域名)生成一个复合值,这使得 Django admin 中记录列表的可视化更加强大。清单 11-3 使用几种方法变体展示了这些可调用的例子。

from django.contrib import admin
from coffeehouse.stores.models import Store

# Option 1
# admin.py
def upper_case_city_state(obj):
    return ("%s %s" % (obj.city, obj.state)).upper()
upper_case_city_state.short_description = 'City/State'

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','address',upper_case_city_state]

# Option 2
# admin.py
class StoreAdmin(admin.ModelAdmin):
    list_display = ['name','address','upper_case_city_state']
    def upper_case_city_state(self, obj):
        return ("%s %s" % (obj.city, obj.state)).upper()
    upper_case_city_state.short_description = 'City/State'

# Option 3
# models.py
from django.db import models

class Store(models.Model):
    name = models.CharField(max_length=30)
    email = models.EmailField()
    def email_domain(self):
        return self.email.split("@")[-1]
    email_domain.short_description = 'Email domain'

# admin.py
class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','email_domain']

Listing 11-3.Django admin list_display option with callables

在清单 11-3 中,你可以看到三个可赎回的变体,它们都可以作为list_display期权。清单 11-3 中的选项一是一个在类外声明的可调用函数,然后被用作list_display选项的一部分。选项二将 callable 声明为 Django admin 类的一部分,然后将其用作list_display选项的一部分。最后,选项三将 callable 声明为 Django 模型类的一部分,然后将其用作 Django 管理类中list_display选项的一部分。清单 11-3 中的两种方法都不是“优于”或“劣于”另一种;这些选项只是在用于实现相同结果的语法和参数上有所不同,您可以使用您喜欢的任何方法。

在某些情况下,你可能想在 Django admin 中将 HTML 作为记录列表的一部分(例如,添加粗体标签或彩色标签)。要在这些情况下包含 HTML,您必须使用format_html方法,因为 Django admin 默认情况下会对所有 HTML 输出进行转义——因为它使用 Django 模板。清单 11-4 展示了format_html方法的使用。

# models.py
from django.db import models
from django.utils.html import format_html

class Store(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=30,unique=True)
    city = models.CharField(max_length=30)
    state = models.CharField(max_length=2)
    def full_address(self):
        return format_html('%s - <b>%s,%s</b>' % (self.address,self.city,self.state))

# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','full_address']

Listing 11-4.Django admin list_display option with callable and format_html

当模型字段使用BooleanFieldNullBooleanField数据类型时,Django admin 显示“开”或“关”图标,而不是TrueFalse值。此外,当list_display中某个字段的值为None时,为空字符串,对于list_display中某个字段为空可迭代的情况(如 list),Django 显示破折号-,如图 11-5 所示。

A441241_1_En_11_Fig5_HTML.jpg

图 11-5。

Django admin default display for empty values

可以用图 11-6 所示的empty_value_display选项覆盖最后一个行为。您可以配置empty_value_display选项,使其在所有 Django 管理模型、特定的 Django 管理类或单个 Django 管理字段上生效,如清单 11-5 所示。

A441241_1_En_11_Fig6_HTML.jpg

图 11-6。

Django admin override display for empty values with empty_value_display

# Option 1 - Globally set empty values to ???
# settings.py
from django.contrib import admin
admin.site.empty_value_display = '???'

# Option 2 - Set all fields in a class to 'Unknown Item field'
# admin.py to show "Unknown Item field" instead of '-' for NULL values in all Item fields
# NOTE: Item model in items app

class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','price']
    empty_value_display = 'Unknown Item field'

admin.site.register(Item, ItemAdmin)

# Option 3 - Set individual field in a class to 'No known price'
class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','price_view']
    def price_view(self, obj):
         return obj.price
    price_view.empty_value_display = 'No known price'

Listing 11-5.Django admin empty_value_display option global, class, or field-level configuration

记录顺序:admin_order_field 和排序

当您在list_display中使用定制字段(即,实际上不在数据库中的字段,而是在 Django 中计算的复合字段或帮助字段)时,这样的字段不能用于排序操作,因为排序发生在数据库级别。然而,如果list_display中的一个元素与一个数据库字段相关联,那么可以用admin_order_field选项创建一个关联用于排序目的。清单 11-6 说明了这一过程。

# models.py
from django.db import models
from django.utils.html import format_html

class Store(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=30,unique=True)
    city = models.CharField(max_length=30)
    state = models.CharField(max_length=2)
    def full_address(self):
        return format_html('%s - <b>%s,%s</b>' % (self.address,self.city,self.state))
    full_address.admin_order_field = 'city'

# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','full_address']

Listing 11-6.Django admin with admin_order_field option

正如您在清单 11-6 中看到的,当试图通过复合full_address字段在 Django admin 中执行排序操作时,admin_order_field声明告诉 Django 按照city对模型记录进行排序。注意,也可以给admin_order_field值添加一个前缀来指定降序(例如full_address.admin_order_field = '-city'),就像在标准模型排序操作中一样。

默认情况下,记录列表值按其数据库pk(主键)字段(通常是 id 字段)排序,如图 11-3 所示(即记录pk 1在底部,记录pk 4在顶部)。如果您单击任何标题列,排序顺序就会改变,如图 11-4 所示。

要设置默认的排序行为——不需要点击标题列——您可以使用 Django admin class ordering选项或 Django model meta ordering选项,您将在第七章中了解到。如果在任一类中没有指定排序选项,则进行pk排序。如果在 Django 模型元选项中指定了ordering选项,那么这种排序行为是通用的,如果 Django 模型和 Django 管理类都有ordering选项,那么 Django 管理类定义优先。

ordering选项接受一个字段值列表来指定记录列表的默认顺序。默认情况下,ordering行为是升序(例如,Z 值第一个@bottom,A 值 top@last),但是可以通过在字段值前添加一个-(减号)将此行为改为降序(例如,A 值第一个@bottom,Z 值 top@last)。例如,默认情况下,要生成如图 11-4 所示的记录列表,您可以使用ordering = ['name'],而要生成如图 11-4 所示的反向记录列表(即住宅区在顶部,公司在底部),您可以使用ordering = ['-name']

记录链接和内联编辑:list_display_links 和 list_editable

如果您查看一些过去的数字,您会注意到记录列表中的每一项都有一个生成的链接。例如,在图 11-2 中,您可以看到每条'商店'记录都是一个链接,将您带到一个可以编辑'商店'值的页面,同样,在图 11-4 中,您可以看到'商店'名称字段是一个链接,将您带到一个可以编辑'商店'值的页面,在图 11-6 中,您可以看到每条'

这是一个默认行为,允许您在每个记录上向下钻取,但是您可以通过list_display_links选项定制这个行为,以不生成链接或者包含更多的链接。清单 11-7 说明了list_display_links选项的两种变型,并显示了 11-7 和 11-8 各自的接口。

A441241_1_En_11_Fig8_HTML.jpg

图 11-8。

Django admin multiple links in records list due to list_display_links

A441241_1_En_11_Fig7_HTML.jpg

图 11-7。

Django admin no links in records list due to list_display_links

# Sample 1)
# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','address','city','state']
      list_display_links = None

admin.site.register(Store, StoreAdmin)

# Sample 2)
# admin.py
from django.contrib import admin
from coffeehouse.items.models import Item

class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','price_view']
    list_display_links = ['menu','name']

admin.site.register(Item, ItemAdmin)

Listing 11-7.Django admin with list_display_links option

清单 11-7 中的第一个示例演示了如何用list_display_links = None设置StoreAdmin类,从而得到图 11-7 中所示的缺少链接的页面。清单 11-7 中的第二个示例显示了带有list_display_links = ['menu','name']ItemAdmin类,该类告诉 Django 在menuname字段值上生成链接,并产生图 11-8 中所示的包含多个链接的页面。

如果您需要编辑多个记录,那么单击记录列表上的单个链接来编辑记录的需要会变得令人厌烦。为了简化记录的编辑,list_editable选项允许 Django 在每个记录值上生成内联表单,有效地允许批量编辑记录,而无需离开记录列表屏幕。清单 11-8 说明了 list_editable 选项的使用和图 11-9 各自的界面。

Note

从技术上讲,list_editable 是 Django admin 的更新选项,但是由于更新是在设计用来读取记录的页面上内联完成的,所以包含在这里。

A441241_1_En_11_Fig9_HTML.jpg

图 11-9。

Django admin editable fields due to list_editable

# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      list_display = ['name','address','city','state']
      list_editable = ['address','city','state']

admin.site.register(Store, StoreAdmin)

Listing 11-8.Django admin with list_editable option

在清单 11-8 中,您可以看到list_editable = ['address','city','state']选项,它告诉 Django 管理员允许编辑记录列表中的addresscity.state值。在图 11-9 中,你可以看到记录列表中的每一个字段值是如何被转换成可编辑的形式,并且在页面的底部,Django 管理员生成一个'保存'按钮来保存编辑时所做的更改。

值得一提的是,list_editable选项中声明的任何字段值也必须声明为list_display选项的一部分,因为不可能编辑未显示的字段。此外,list_editable选项中声明的任何字段值都不能是list_display_option的一部分,因为字段不可能既是表单又是链接。

记录分页:list_per_page,list_max_show_all,分页器

当 Django admin 中的记录列表变得太大时,它们会被自动分割成不同的页面。默认情况下,Django admin 会为每 100 条记录生成额外的页面。您可以使用 Django 管理类list_per_page选项来控制这个设置。清单 11-9 说明了 list_per_page 选项的使用,图 11-10 显示了由清单 11-9 中的配置生成的相应记录列表。

A441241_1_En_11_Fig10_HTML.jpg

图 11-10。

Django admin list_per_page option limit to 5

# admin.py
from django.contrib import admin
from coffeehouse.items.models import Item

class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','price']
    list_per_page = 5

admin.site.register(Item, ItemAdmin)

Listing 11-9.Django admin with list_per_page option

正如您在图 11-10 中看到的,由于清单 11-9 中所示的list_per_page = 5选项,九条记录的显示被分成两页。除了图 11-10 左下方的页面图标,请注意这些图标的右侧是一个“显示全部”链接。“显示全部”链接用于生成一个记录列表,在一个页面上显示所有记录。但是请注意,因为这个额外的数据库操作成本很高,所以默认情况下,只有当记录列表不超过 200 项时,才会显示“显示全部”链接。

您可以使用list_max_show_all选项控制“显示全部”链接的显示。如果记录列表总数小于或等于list_max_show_all值,则显示“显示全部”链接,如果记录列表总数大于此数,则不生成“显示全部”链接。例如,如果您对列表 11-9 声明list_max_show_all = 8选项,那么在图 11-10 中不会出现“显示全部”链接,因为记录列表总数是 9。

Django admin 使用django.core.paginator.Paginator类来生成分页序列,但是也可以通过 paginator 选项提供一个定制的分页器类。注意,如果自定义分页器类没有从django.core.paginator.Paginator继承它的行为,那么你也必须为ModelAdmin.get_paginator()方法提供一个实现。

记录搜索:搜索字段、列表过滤器、显示完整结果计数、保留过滤器

Django admin 也支持搜索功能。Django 管理类search_fields选项通过搜索框增加了文本模型字段的搜索功能——关于 Django 模型文本数据类型的列表,参见表 7-1 。清单 11-10 展示了一个带有 search_fields 选项的 Django 管理类,图 11-11 展示了一个搜索框是如何被添加到记录列表的顶部的。

A441241_1_En_11_Fig11_HTML.jpg

图 11-11。

Django admin search box due to search_fields option

from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
      search_fields = ['city','state']

admin.site.register(Store, StoreAdmin)

Listing 11-10.Django admin search_fields option

在清单 11-10 中,城市和州字段被添加到search_fields选项中,它告诉 Django 管理员在这两个字段中执行搜索。请注意,由于 Django 执行这种类型的搜索查询的方式,向search_fields选项添加太多字段会导致搜索结果变慢。表 11-1 显示了不同的search_fields选项和为给定搜索项生成的 SQL。

表 11-1。

Django search_fields options and generated SQL for search term

| 搜索字段选项 | 搜索术语 | 生成的 SQL 条件 | | --- | --- | --- | | search_fields = ['city ',' state'] | 圣迭戈 | 其中(城市我喜欢“%San%”或州我喜欢“%San%”)和(城市我喜欢“%Diego%”或州我喜欢“%Diego%”) | | search _ fields =['^city','^state'] | 圣迭戈 | 其中(城市 ILIKE 'San% '或州 ILIKE 'San% ')和(城市 ILIKE 'Diego% '或州 ILIKE 'Diego% ') | | search _ fields =[' =城市',' =州'] | 圣迭戈 | 其中(市 ILIKE 'San '或州 ILIKE 'San ')和(市 ILIKE 'Diego '或州 ILIKE 'Diego ') | | *search_fields = ['@city ',' @state'] | 圣迭戈 | (全文搜索)其中(城市 I like“% San %”或州 I like“% San %”)和(城市 I like“% Diego %”或州 I like“% Diego %”) |
  • Full-text search option only supported for MySQL database

如表 11-1 所示,search_fields选项通过将提供的搜索字符串拆分成单词来构建查询,并执行不区分大小写的搜索(即SQL ILIKE),其中每个单词必须至少出现在一个 search _ fields 中。此外,注意在表 11-1 中,可以用不同的前缀声明search_fields值来改变搜索查询。

默认情况下,如果您只是向search_fields,提供模型字段名,Django 会生成一个查询,在每个单词的开头和结尾使用 SQL 通配符%,这可能是一个代价非常高的操作,因为它会搜索字段记录中的所有文本。如果您在search_field前面加上一个^——如表 11-1 所示——Django 会生成一个查询,在每个单词的末尾有一个 SQL 通配符%,这使得搜索操作更加有效,因为它仅限于以单词模式开头的文本。如果您给 search_field 加上前缀=——如表 11-1 所示——Django 会生成一个没有 SQL 通配符%的精确匹配查询,这使得搜索操作最有效,因为它仅限于单词模式的精确匹配。最后,如果您使用的是 MySQL 数据库,也可以给search_fields添加前缀@来实现全文搜索。

Power Searches, Non-Text Searches, and Other Back Ends For Django Admin Searches

搜索引擎提供了各种强大的搜索语法来定制搜索查询,但是 Django admin search_fields选项不支持这种语法。例如,在搜索引擎中,可以引用搜索词"San Diego"来对两个单词进行精确搜索,但是如果你尝试用 Django admin 搜索,Django 会尝试分别搜索文字引号:"San""Diego"。要调整默认的 search_fields 行为,您必须使用表 11-1 或ModelAdmin.get_search_results()中的选项。

Django admin 类的默认搜索行为可以通过接受请求的ModelAdmin.get_search_results()方法、应用当前过滤器的 queryset 和用户提供的搜索词定制为任何需求。通过这种方式,您可以生成非文本搜索(例如,在整数上)或依靠其他第三方工具(例如,Solr、Haystack)来生成搜索结果。

list_filter选项提供了对模型字段值的快速访问,并且像预建的搜索链接一样工作。与search_fields选项不同,list_filter选项在它可以处理的数据类型方面更加灵活,并且不仅仅接受文本模型字段(例如,它还支持布尔字段、日期字段等)。).清单 11-11 展示了一个带有list_filter选项的 Django 管理类,图 11-12 展示了在记录列表右侧生成的过滤器列表。

A441241_1_En_11_Fig12_HTML.jpg

图 11-12。

Django admin list filters due to search_fields option

from django.contrib import admin
from coffeehouse.items.models import Item

class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','price']
    list_filter = ['menu','price']

admin.site.register(Item, ItemAdmin)

Listing 11-11.Django admin list_filter option

在清单 11-11 中,list_filter 选项是用菜单和价格字段声明的,它告诉 Django 用这两个字段创建过滤器。正如您在图 11-12 中所看到的,记录列表的右侧是一个带有各种过滤器的列,其中包含了menuprice字段值的所有值。如果您点击任何过滤器链接,Django admin 会在记录列表中显示与过滤器匹配的记录,这个过程如图 11-13 、 11-14 和 11-15 所示。

A441241_1_En_11_Fig15_HTML.jpg

图 11-15。

Django admin list with dual filter

A441241_1_En_11_Fig14_HTML.jpg

图 11-14。

Django admin list with single filter

A441241_1_En_11_Fig13_HTML.jpg

图 11-13。

Django admin list with single filter

在图 11-15 中可以看到的 Django 管理过滤器的一个有趣的方面是,您可以应用多个过滤器,这使得更容易深入到匹配非常具体的标准的记录。

此外,如果您查看图 11-13、11-14 和 11-15,您可以看到过滤器是如何反映为 URL 查询字符串的。例如,在图 11-13 中,?menu__id__exact=2字符串被附加到 URL 上,它告诉 Django admin 显示一个记录列表,带有一个2的菜单id;在图 11-15 中,?menu__id__exact=3&price=3.99字符串告诉 Django admin 显示一个菜单 id 为3和价格值为3.99的记录列表。该 URL 参数语法基于用于进行标准 Django 模型查询的相同语法——在第八章中描述——这有助于“动态”生成更复杂的过滤器,而无需修改或添加底层 Django 管理类的选项。

当您对记录列表应用一个或多个过滤器,并且过滤后的结果多于 99 条记录时,Django 会将初始显示限制为 99 条记录,并且还会添加分页,但此外还会显示与过滤器匹配的对象的完整计数(例如,99 条结果(总共 153 条))。这个额外的计数需要一个额外的查询,这对于大量的记录来说会减慢速度。要禁止生成适用于过滤器使用的额外计数,您可以将show_full_result_count选项设置为False

应用一个或多个过滤器的另一个特点是,当您创建、编辑或删除一个记录并完成操作时,Django 会将您带回过滤列表。虽然这可能是一个期望的行为,但是可以通过preserve_filters选项覆盖这个行为,这样 Django 管理员就可以将您返回到原始记录列表。如果您在 Django admin 类中设置了preserve_filters = False选项,并创建、编辑或删除了一个记录,Django admin 会将您带回到没有过滤器的原始记录列表。

记录日期:日期层次结构

日期和时间在 Django admin UI 中显示为底层 Python datetime值的字符串表示,正如您所期望的那样。但是有一个针对DateFieldDateTimeField模型数据类型的特殊选项,它像一个专门的过滤器一样工作。如果您在 Django 管理类上使用date_hierarchy选项,并为其分配一个为DateFieldDateTimeField的字段(例如date_hierarchy = 'created',其中timestamp是字段的名称),Django 会在记录列表的顶部生成一个智能日期过滤器,如图 11-16 、 11-17 和 11-18 所示。

A441241_1_En_11_Fig18_HTML.jpg

图 11-18。

Django date filter single day with date_hierarchy

A441241_1_En_11_Fig17_HTML.jpg

图 11-17。

Django date filter by day with date_hierarchy

A441241_1_En_11_Fig16_HTML.jpg

图 11-16。

Django date filter by month with date_hierarchy

正如您在图 11-16 、 11-17 和 11-18 中看到的,智能行为来自于这样一个事实,即在加载记录列表时,Django 会生成一个与date_hierarchy字段的值相对应的可用月份或日期的唯一列表。如果您单击这个过滤器列表的任何选项,Django 就会生成一个唯一的记录列表,该列表与过滤器列表中的月或日的值相匹配。

记录动作:顶部动作,底部动作,动作

除了可以点击 Django 管理记录列表中的项目来编辑或删除它之外,在记录列表的顶部还有一个以单词“Action”开头的下拉菜单,你可以在前面的图中看到。默认情况下,“操作”下拉菜单提供“删除选定选项”项,通过选中每条记录左侧的复选框来同时删除多条记录。

如果您希望从记录列表顶部移除“操作”菜单,您可以使用actions_on_top选项并将其设置为False。此外,如果您希望将“动作”菜单添加到记录列表的底部,您可以使用图 11-19 所示的actions_on_bottom = True选项——注意,记录列表页面的底部和顶部都可以有“动作”菜单。

A441241_1_En_11_Fig19_HTML.jpg

图 11-19。

Django admin list with Action menu on bottom due to actions_on_bottom

与“动作”菜单相关的另一个选项是actions_selection_counter,它在“动作”菜单的右侧显示所选记录的数量,也可以在图 11-19 中看到。如果您设置了actions_selection_counter = False,那么 Django 管理员会忽略与“动作”菜单相关的所选记录的数量。

尽管“Action”菜单仅限于一个操作——删除记录——但是可以通过 Django admin 类中的actions选项定义一个操作列表。1

记录关系

在前面的模型章节中描述的 Django 模型关系——一对一、一对多和多对多——在 Django 管理类和 Django 管理的上下文中具有某些行为,这些行为值得在下面的小节中分别描述。

显示:列表显示(续)

当你有一个一对多的关系并声明相关的ForeignKey字段作为list_display选项的一部分时,Django admin 使用相关模型的__str__表示。图 11-5 显示了最后一个行为,其中有一列Item记录,其中Item模型用models.ForeignKey(Menu)定义了menu字段,因此该字段的输出是Menu模型__str__方法。

list_display选项不能直接接受ManyToManyField字段,因为它需要为表中的每一行执行单独的 SQL 语句;然而,通过 Django 管理类中的自定义方法将ManyToManyField集成到list_display中是可能的,这个过程在清单 11-12 和图 11-20 中有所说明。

A441241_1_En_11_Fig20_HTML.jpg

图 11-20。

Django admin list_display option with ManyToManyField field

# models.py
from django.db import models

class Amenity(models.Model):
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)

class Store(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=30)
    city = models.CharField(max_length=30)
    state = models.CharField(max_length=2)
    email = models.EmailField()
    amenities = models.ManyToManyField(Amenity,blank=True)

# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
    list_display = ['name','address','city','state','list_of_amenities']
    def list_of_amenities(self, obj):
        return ("%s" % ','.join([amenity.name for amenity in obj.amenities.all()]))
    list_of_amenities.short_description = 'Store amenities'

admin.site.register(Store, StoreAdmin)

Listing 11-12.Django admin list_display option with ManyToManyField field

在清单 11-12 中,您可以看到Store模型有一个带有Amenity模型的ManyToManyField字段。为了通过list_display显示 Django admin 中的ManyToManyField字段的值,您可以看到有必要创建一个定制方法来对这些记录进行额外的查询。图 11-20 显示了该ManyToManyField字段的 Django 管理记录列表。请注意,这种设计会给数据库带来沉重的负担,因为它需要对每条记录进行额外的查询。

订单:管理订单字段(续)

admin_order_field选项还支持对相关模型中的字段进行排序。例如,在清单 11-13 中,您可以看到admin_order_field选项被应用于一个字段,该字段是具有ForeignKey字段关系的模型的一部分。

# models.py
class Menu(models.Model):
    name = models.CharField(max_length=30)
    creator = models.CharField(max_length=100,default='Coffeehouse Chef')
    def __str__(self):
        return u"%s" % (self.name)

class Item(models.Model):
    menu = models.ForeignKey(Menu)
    name = models.CharField(max_length=30)

# admin.py
from django.contrib import admin
from coffeehouse.stores.models import Store

class ItemAdmin(admin.ModelAdmin):
    list_display = ['menu','name','menu_creator']
    def menu_creator(self, obj):
        return obj.menu.creator
    menu_creator.admin_order_field = 'menu__creator'

admin.site.register(Item, ItemAdmin)

Listing 11-13.Django admin admin_order_field option with ForeignKey field

关于清单 11-13 值得注意的最重要的事情是指定字段menu__creator的双下划线,它告诉 Django 管理员访问相关模型中的字段——注意这个双下划线是在第八章中描述的 Django 模型关系查询中用于执行查询的相同语法。

搜索:search_fields 和 list_filter(续),管理。RelatedOnlyFieldListFilter,list_select_related

另外两个支持相同双下划线语法(也称为“跟随符号”)的 Django 管理类选项是search_fieldslist_filter。这意味着您可以为相关模型(例如search_fields = ['menu__creator'])启用搜索并生成过滤器。

仅适用于模型关系的list_filter选项的变体是admin.RelatedOnlyFieldListFilter。当属于某个关系的模型记录可以跨越单个关系时,可能会导致创建不必要的筛选器。

举个例子,我们来看一个StoreAmenity车型的关系;您可以为Store记录列表上的Amenity值生成 Django 管理过滤器,但是如果Amenity型号记录是通用的并且在Store型号之外使用(例如,EmployeesAmenity值),您将在商店记录列表中看到不适用的过滤器值。使用admin.RelatedOnlyFieldListFilter可以防止这种情况,这个过程在清单 11-14 和图 11-21 和 11-22 中进行了说明。

A441241_1_En_11_Fig22_HTML.jpg

图 11-22。

Django admin list_filter option with RelatedOnlyFieldListFilter

A441241_1_En_11_Fig21_HTML.jpg

图 11-21。

Django admin list_filter option with no RelatedOnlyFieldListFilterDjango

# admin.py
class StoreAdmin(admin.ModelAdmin):
    list_display = ['name','address','city','state','list_of_amenities']
    list_filter = [['amenities',admin.RelatedOnlyFieldListFilter]]
    def list_of_amenities(self, obj):
        return ("%s" % ','.join([amenity.name for amenity in obj.amenities.all()]))
    list_of_amenities.short_description = 'Store amenities'
Listing 11-14.Django admin list_filter option with admin.RelatedOnlyFieldListFilter

在清单 11-14 中,注意生成过滤器的字段——在本例中是amenities——是如何与admin.RelatedOnlyFieldListFilter一起被包装在它自己的列表中的。为了理解使用和不使用admin.RelatedOnlyFieldListFilter的区别,请看图 11-21 和 11-22 。在图 11-21 中,注意列表中的最后一个过滤器是“按摩椅”——一个Amenity记录——但是主列表中没有Store记录有这个舒适性。要从Store记录列表中删除这个不适用的过滤器,您可以使用admin.RelatedOnlyFieldListFilter并从图 11-22 中获得结果,图中仅显示与Store记录相关的Amenity过滤器。

最后,另一个适用于具有模型关系的 Django 管理类的选项是list_select_relatedlist_select_related选项的功能就像在涉及关系的查询中使用的list_select_related选项,以减少涉及关系的数据库查询的数量(例如,它创建单个复杂的查询,而不是以后需要为每个关系发出多个查询)。list_select_related选项可以接受布尔值或列表值。默认情况下,list_select_related选项接收一个False值(即不使用)。在底层,list_select_related选项使用相同的select_related()模型方法来检索相关记录,如第八章所述。

如果list_select_related = True那么select_related()总是被使用。为了对list_select_related进行更细粒度的控制,您可以指定一个列表,注意空列表会阻止 Django 调用select_related(),任何其他列表值都会作为参数直接传递给select_related()

Django admin 创建、更新、删除记录选项

除了 Django admin read record 选项,主要用于修改 Django admin 中每个模型的主页(即每个模型的记录列表),还有其他 Django admin 页面用于创建、更新和删除 Django 模型记录,这些页面也支持一系列选项。

当你点击显示模型记录的每个 Django 管理页面右上方的“添加”按钮时,你会被带到一个类似表格的页面,在这里你可以为一条新记录提供值,如图 11-23 所示;当你点击 Django 管理记录列表中的一条记录时,你也会被带到一个类似表格的页面,在这里你可以编辑或删除该记录的字段值,如图 11-24 所示。

A441241_1_En_11_Fig23_HTML.jpg

图 11-23。

Django 管理页面创建模型记录

A441241_1_En_11_Fig24_HTML.jpg

图 11-24。

Django 管理页面编辑或删除模型记录 Tip

如图 11-23 和 11-24 所示的娱乐场所界面更加友好,参见图 11-37 和 11-38 中的filter_horizontalfilter_vertical选项。

在接下来的小节中,我将描述 Django admin 中用于创建、更新和删除记录的各种选项,值得注意的是,这三个操作都在同一个页面上。

记录表单:字段、只读字段、排除、字段集、表单字段覆盖、表单、预填充字段

默认情况下,Django 管理员为您正在处理的 Django 模型中的所有字段生成一个表单。例如,在图 11-23 和 11-24 中,您可以看到 Django 管理表单中的六个字段,它们对应于Store Django 模型的六个字段定义。在幕后,由于 Django admin 使用填充了模型记录的表单,Django admin 表单的操作和选项几乎与第九章中描述的模型表单相同。

第一个可以改变 Django 管理表单字段数量的选项是fields选项。fields选项允许您改变表单字段出现的顺序,或者创建一个带有模型字段子集的表单。清单 11-15 展示了fields在 Django 管理类中的用法,图 11-25 展示了清单 11-15 生成的 UI。

A441241_1_En_11_Fig25_HTML.jpg

图 11-25。

Django admin fields option for Django admin forms

class StoreAdmin(admin.ModelAdmin):
      fields = ['address','city','state','email']

admin.site.register(Store, StoreAdmin)

Listing 11-15.Django admin fields option

for Django admin forms

在清单 11-15 中,您可以看到fields选项包含四个字段,而原始支持Store Django 模型包含六个字段;在图 11-25 中,你可以确认 Django 管理表单仅仅由四个字段生成。

Django 管理表单的同一个fields选项的另一个变体是将多个表单字段组合到同一个 UI 行中。这很容易通过将字段嵌套在它们自己的元组中来实现。例如,如果定义了fields = ['address',('city','state'),'email']citystate表单字段在表单的同一行生成,如图 11-26 所示。

A441241_1_En_11_Fig26_HTML.jpg

图 11-26。

Django admin fields option with wrapped fields for Django admin forms Tip

list_editable 选项创建一个内嵌表单来编辑记录,而不需要进入如图 11-25 和 11-26 所示的专用表单页面。参见上一节“记录链接和内嵌编辑”。

虽然fields选项很有帮助,但在其他情况下,可能需要显示一个表单域,但不允许它被更改,因为完全忽略一个域可能会导致混乱。要禁止编辑表单域,您可以使用readonly_fields选项。清单 11-16 展示了readonly_fields选项的使用,图 11-27 展示了它的 UI 布局。

A441241_1_En_11_Fig27_HTML.jpg

图 11-27。

Django admin readonly_fields option for Django admin forms

class StoreAdmin(admin.ModelAdmin):
      readonly_fields = ['name','amenities']

admin.site.register(Store, StoreAdmin)

Listing 11-16.Django admin readonly_fields option

for Django admin forms

在清单 11-16 中,您可以看到readonly_fields选项使nameamenities字段变为只读。因为没有使用 fields 选项,所以所有的模型字段都用于生成表单。在图 11-27 中,你可以看到nameamenities字段是如何显示为文本而不是输入表单字段,使它们不可编辑。

仅使用readonly_fields选项的副作用是这些字段定义被放置在表单的底部,如图 11-27 所示。如果您想保持与原始 Django 模型相同的表单字段顺序,那么您需要使用fields选项明确定义表单字段,这样表单字段顺序遵循fields选项,并且readonly_field中的任何字段都显示为只读,考虑到在fields选项中设置的字段位置。

除了支持模型字段名称之外,readonly_fields选项还支持可调用的方法来进一步添加定制行为。清单 11-17 展示了带有可调用的readonly_fields选项的使用,以及图 11-28 的 UI 布局。

A441241_1_En_11_Fig28_HTML.jpg

图 11-28。

Django admin readonly_fields option for Django admin forms

from django.utils.safestring import mark_safe

class StoreAdmin(admin.ModelAdmin):
    fields = ['name','address',('city','state'),'email','custom_amenities_display']
    readonly_fields = ['name','custom_amenities_display']
    def custom_amenities_display(self, obj):
        return mark_safe("Amenities can only be modified by special request, please contact the store manager at %s to create a request" % (obj.email,obj.email))
    custom_amenities_display.short_description = "Amenities"

admin.site.register(Store, StoreAdmin)

Listing 11-17.Django admin readonly_fields option with callable for Django admin forms

在清单 11-17 中,您可以看到readonly_fields选项使用了custom_amenities_display可调用函数来创建一个定制字段。在底部的图 11-28 中,你可以看到这个新的定制字段——代替了原来的amenities字段——它显示了比图 11-27 更友好的消息,并且也是不可编辑的。

Django 管理类中表单的exclude选项是对fields选项的补充。而fields选项要求它显式地创建包含在 Django 管理表单中的字段列表,而exclude提供了相反的行为,要求它显式地列出不属于 Django 管理表单的字段。例如,对于具有字段 a、b、c 的 Django 模型,Django 管理类 fields = ('a ',' b ')选项等同于 exclude = ('c ')选项(即,两个选项生成相同的 Django 管理表单)。

Django 管理类的fieldsets选项提供了对用于在 Django 管理中创建和编辑记录的页面布局的更大控制。不像fields选项可以改变表单字段的顺序,甚至可以在同一行嵌套表单字段——如图 11-26 所示——fieldsets选项与字段选项一起工作,将一个页面分成多个集合。清单 11-18 说明了fieldsets选项的使用,图 11-29 和 11-30 相应的布局。

A441241_1_En_11_Fig30_HTML.jpg

图 11-30。

Django admin fieldsets option for Django admin forms (collapsed)

A441241_1_En_11_Fig29_HTML.jpg

图 11-29。

Django admin fieldsets option for Django admin forms

from django.utils.safestring import mark_safe

class StoreAdmin(admin.ModelAdmin):
    fieldsets = [
        ['Store general information', {
            'fields': ['name', 'email']
        }],
        ['Store location options', {
            'classes': ['collapse'],
            'fields': ['address',('city', 'state')],
        }],
    ]

admin.site.register(Store, StoreAdmin)

Listing 11-18.Django admin fieldsets option

for Django admin forms

在清单 11-18 中,你可以看到fieldsets选项接受由两个列表组成的列表值,其中每个列表代表 Django 管理页面的一部分,如图 11-29 和 11-30 所示。每个内部列表都由第一个参数和第二个参数组成,第一个参数表示部分的标题或头,第二个参数是一个字典。最后一个字典本身包含分配给一个字段的键的值——其功能就像前面描述的fields选项——和一个classes键,该键通过 CSS 类为该部分提供某些行为。在这种情况下,您可以看到清单 11-18 中的第二个部分表示'classes': ['collapse'],它告诉 Django 使该部分可折叠,在图 11-29 和 11-30 中,您可以欣赏这种折叠和未折叠的行为。

除了在fieldsetsclasses键中使用的collapse选项,另一个有用的 CSS 类选项是wide,它在字段之间增加了更多的水平空间。注意,向classes键添加任意数量的 CSS 类都是有效的,可以是 Django admin 中包含的 CSS 类(即collapsewide),甚至是自定义的 CSS 类。

formfield_overrides选项提供了一种方法来覆盖与 Django 管理表单中的 Django 模型字段相关联的默认表单小部件。默认情况下,所有 Django 模型字段都有一个指定的小部件分配给它们,目的是生成一个表单——这个主题在第九章中讨论,具体见表 9-1 。但是,如果您觉得给定模型字段的默认小部件对 Django admin 来说不够用,您可以使用清单 11-19 中所示的formfield_overrides选项。

from django.contrib import admin
from coffeehouse.items.models import Menu

class MenuAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.CharField: {'widget': forms.Textarea}
    }

admin.site.register(Menu, MenuAdmin)

Listing 11-19.Django admin formfield_overrides option

for Django admin forms

清单 11-19 中的formfield_overrides选项告诉 Django 管理员为所有使用 CharField的模型字段使用forms.Textarea小部件——它生成一个标准的 HTML 标签。在图 11-32 中,您可以看到应用清单 11-19 formfield_overrides选项的效果,而在图 11-31 中,您可以看到用于CharField字段的默认小部件,它是一个标准的 HTML 标签。

A441241_1_En_11_Fig32_HTML.jpg

图 11-32。

Django admin custom CharField field display in Django admin form using formfield_overrides

A441241_1_En_11_Fig31_HTML.jpg

图 11-31。

Django admin default CharField field display in Django admin form

虽然前面所有的选项都允许您调整 Django admin 中使用的表单的一部分,但是有时有必要为 Django admin 从头创建一个表单,而不是调整 Django 模型生成的底层表单(例如,如果您需要对 Django admin 表单进行定制验证)。要为 Django 管理类指定定制表单,可以使用form选项。

最后,与 Django 管理表单相关的另一个选项是prepopulated_fields,它特定于需要 slug 字段值的模型。如果您不熟悉术语“slug ”,最简单地说,slug 字段值是字符串的机器友好表示,例如,大写字母被转换为小写字母,空格等特殊字符被转换为破折号。通过prepopulated_fields选项,您可以告诉 Django,当用户为 Django 管理表单中的给定字段键入值时,它会自动用第一个字段的 slug 表示填充表单中的另一个字段。

例如,对于prepopulated_fields = {'address': ['city','state']}选项,如果用户在城市表单字段中键入值圣地亚哥,在州中键入 CA,Django 用值san-diego-ca填充address表单字段。值得一提的是,该功能是通过集成到 Django admin 中的 JavaScript 实现的,并且prepopulated_fields选项不接受DateTimeFieldForeignKeyManyToManyField字段作为支持模型数据类型。

动作、链接和位置:保存在顶部、另存为(克隆记录)、另存为继续和查看现场

在创建、更新和删除 Django 模型记录的每个表单页面的底部都有在页面上执行操作的按钮:“删除”、“保存并添加另一个”、“保存并继续编辑”和“保存”,所有这些都如图 11-33 所示。如果一个表单太大,不向下滚动就很难找到这些动作按钮,所以为了解决这个问题,Django 管理类支持save_on_top选项,它在页面顶部创建相同的动作按钮,如图 11-34 所示。请注意,要生成图 11-34 中的布局,您需要使用save_on_top = True

A441241_1_En_11_Fig34_HTML.jpg

图 11-34。

Django admin save_on_top option on form page

A441241_1_En_11_Fig33_HTML.jpg

图 11-33。

Django admin standard action button on form page

有时候,需要从 Django admin 中预先存在的记录生成一个相同或几乎相同的记录。因为在 Django admin 中将值从一个表单复制粘贴到另一个表单是一个耗时且容易出错的过程,所以 Django admin 类还支持save_as选项来克隆预先存在的模型记录。如果在 Django 管理类上设置了save_as = True选项,Django 会用“另存为新的”按钮替换“保存并添加另一个”按钮,如图 11-35 所示。

A441241_1_En_11_Fig35_HTML.jpg

图 11-35。

Django admin save_as (Clone) option on form page

如果你点击图 11-35 所示的“另存为新的”按钮,Django 会保存一个相同的记录——有效地克隆你在屏幕上看到的记录——使用不同的id值来区分两者。请注意,如果底层 Django 模型禁止这种操作(例如,字段必须是惟一的),则操作不会发生,并且会抛出一个错误来指出原因。

当您使用save_as = True选项并执行克隆记录的动作时(即点击“另存为新的”按钮),Django 管理员会让您保持新克隆记录的形式,以防您想进一步更改它。您可以使用save_as_continue = False选项,告诉 Django 管理员在克隆记录后将您重定向到主模型列表页面。

Django 模型类支持一个名为get_absolute_url()的实例方法,该方法使得通过 Django 模型的字段解析记录的公共 URL 成为可能(例如,URL/store/1//store/2//store/3/符合一个模式,其中每个数字代表一个商店id值,在这种情况下,Store模型的get_absolute_url()方法将返回/store/<store_record_id>。在 Django admin 中,get_absolute_url()方法被直接绑定到一个链接,该链接有助于在公共 URL 目的地查看记录,图 11-36 在右上角显示了这个链接。

A441241_1_En_11_Fig36_HTML.jpg

图 11-36。

Django admin 'View on site' button due to get_absolute_url() Django model method

在图 11-36 中,右上角的“查看站点”按钮基于 Django 模型的get_absolute_url()方法以及在最后一个方法中定义的当前记录的值生成一个链接。通过这种方式,只需单击一下,您就可以在 Django admin 的公共 URL 目的地看到您正在编辑的记录。如果您想禁用这个按钮,您可以将view_on_site = False选项添加到 Django admin 类中。注意,如果底层 Django 模型类没有定义get_absolute_url()方法,那么无论view_on_site值是多少,都不会显示按钮。

关系:filter_horizontal、filter_vertical、radio_fields、raw_id_fields、inlines

与创建、更新和删除操作相关的 Django 模型关系任务在 Django 管理类的上下文中也有某些行为,值得单独描述。

当您在 Django 模型上使用一个ManyToManyField字段并在 Django 管理中访问它时,Django 会生成 HTML / 表单标签来选择 ManyToManyField字段的值——如图 11-23 和 11-24 底部所示。然而,因为这种类型的选择方法对于大型列表来说很麻烦,Django admin 提供了filter_horizontalfilter_vertical选项来生成单独的面板,使得值选择更容易。图 11-37 为filter_horizontal选项布局示意图,图 11-38 为filter_vertical选项布局示意图。

A441241_1_En_11_Fig38_HTML.jpg

图 11-38。

Django admin filter_vertical option for ManyToManyField

A441241_1_En_11_Fig37_HTML.jpg

图 11-37。

Django admin filter_horizontal option for ManyToManyField

在图 11-37 和 11-38 中,你可以看到有两个面板可以选择和取消选择给定ManyToManyField的值,唯一的区别是filter_horizontal水平堆叠面板-在图 11-37 中-而filter_vertical垂直堆叠面板-在图 11-38 中。

假设ManyToManyField字段被命名为amenities,为了实现图 11-37 中的布局,您将声明filter_horizontal = ['amenities'],为了实现图 11-38 中的布局,您将声明filter_vertical = ['amenities']

当您在 Django 模型字段中使用ForeignKey模型数据类型或choices选项,并在 Django 管理中访问它时,Django 还会生成 HTML / 表单标签来选择 ForeignKey字段的值——如图 11-39 顶部所示。

A441241_1_En_11_Fig39_HTML.jpg

图 11-39。

Django admin default select list for ForeignKey or choices option

Django admin 类可以用radio_fields选项改变这个默认布局,生成一个带有 HTML 单选按钮的布局。清单 11-20 展示了 Django 管理类中radio_fields选项的两种选择,以及图 11-40 和 11-41 的 UI 布局。

A441241_1_En_11_Fig41_HTML.jpg

图 11-41。

Django admin vertical radio_fields option for ForeignKey or choices option

A441241_1_En_11_Fig40_HTML.jpg

图 11-40。

Django admin horizontal radio_fields option for ForeignKey or choices option

from django.contrib import admin
from coffeehouse.items.models import Item

# Option 1 (Horizontal)

class ItemAdmin(admin.ModelAdmin):
    radio_fields = {"menu": admin.HORIZONTAL}

admin.site.register(Item, ItemAdmin)

# Option 2 (Vertical)

class ItemAdmin(admin.ModelAdmin):
    radio_fields = {"menu": admin.VERTICAL}

admin.site.register(Item, ItemAdmin)

Listing 11-20.Django admin radio_fields option

for ForeignKey field

在清单 11-20 中,你可以看到选项一用{"menu": admin.HORIZONTAL}字典定义了radio_fields值,其中menu代表ForeignKey字段或带有choices选项的字段,而admin.HORIZONTAL是单选字段的方向——该选项生成了如图 11-40 所示的布局。清单 11-20 中的选项二以类似的方式定义了radio_fields,除了它使用admin.VERTICAL值来告诉 Django admin 为 radio 字段生成一个垂直布局,如图 11-41 所示。

对于ForeignKeyManyToManyField字段的另一个 Django 管理选项是raw_id_fields选项,顾名思义,它依赖于原始的id字段值来分配一个ForeignKey值或ManyToManyField值。图 11-42 展示了一个raw_id_fields选项在 Django 管理中的样子。

A441241_1_En_11_Fig42_HTML.jpg

图 11-42。

Django admin raw_id_fields option for ForeignKey or ManyToManyField option

如图 11-42 所示,Django admin 生成一个基本文本框,您可以在其中分配id值,旁边的放大镜按钮可以帮助您搜索和选择值。假设ForeignKeyManyToManyField字段被命名为menu,要实现图 11-42 中的布局,您需要声明raw_id_fields = ["menu"]。请注意,对于ForeignKey字段,可接受的值是单个id,对于ManyToManyField字段,您还可以引入一个由逗号分隔的 id 列表(即 CSV)。

最后,我们来看 Django 管理类inlines选项,它是为反向模型关系设计的。当您在 Django admin 中创建或编辑带有关系的模型时,如果这是在带有关系字段定义的模型(即ForeignKeyManyToManyField)上完成的,Django admin 会显示相关的模型内联值,或者添加一个“+”按钮来添加新的模型值,如图 11-37–用于ForeignKey menu值–和图 11-39–用于ManyToMany amenities值所示。然而,当您试图编辑或创建逆向模型关系(即没有关系字段的模型)的值时,Django admin 仅显示模型本身,如图 11-31 所示。可以使用inlines选项编辑或创建反向模型关系的相关模型值。

inlines选项首先要求您创建一个 Django admin TabularInlineStackedInline类,这两个类都是InlineModelAdmin类的子类,后者是用于创建标准 Django admin 类的标准admin.ModelAdmin类的更专门化版本。一旦有了TabularInlineStackedInline类,就可以将它声明为inlines选项值的一部分。

清单 11-21 展示了两个 Django 管理示例,它们在具有一对多和多对多关系的模型上使用了TabularInlineStackedInline类。

# admin.py (ForeignKey)
from django.contrib import admin
from coffeehouse.items.models import Item, Menu

class ItemInline(admin.TabularInline):
    model = Item

class MenuAdmin(admin.ModelAdmin):
    list_display = ['name']
    inlines = [
        ItemInline,
    ]

admin.site.register(Menu, MenuAdmin)

# admin.py (ManyToManyField)
from django.contrib import admin
from coffeehouse.stores.models import Store, Amenity

class StoreInline(admin.StackedInline):
    model = Store.amenities.through

class AmenityAdmin(admin.ModelAdmin):
    inlines = [
        StoreInline,
    ]

admin.site.register(Amenity, AmenityAdmin)

Listing 11-21.Django admin inlines option

for ForeignKey and ManyToManyField field

清单 11-21 中的第一个例子展示了ItemInline类,它继承了内置TabularInline类的行为。因为最后一个类被设计用来表示 Django admin 中的一个模型,所以它也用Item模型类声明了model字段。接下来,MenuAdmin类是为Menu模型类设计的标准 Django 管理类,但是请注意它使用了inlines选项和ItemInline类。由于这最后一个配置,当您在 Django admin 中编辑或创建Menu模型记录时,所有与给定Menu模型记录相关的Item模型记录都显示在内联中,如图 11-43 所示。

A441241_1_En_11_Fig43_HTML.jpg

图 11-43。

Django admin inlines option with TabularInline for ForeignKey

如图 11-43 所示,除了用于编辑或创建Menu记录的标准 Django 管理表单,还有一个显示给定Menu记录的所有Item模型记录的表单集;在这种情况下,表单集包含属于Drinks Menu记录的Item记录。还要注意图 11-43 中表单集中每个表单的表单字段都被内联为选项卡,这是由TabularInline类提供的行为。

清单 11-21 的第二部分展示了从内置StackedInline类继承其行为的StoreInline类。因为最后一个类被设计用来表示 Django admin 中的一个模型,它还用Store.amenities.through模型类声明了model字段,这个额外的模型语法——amenities.through——是必要的,因为StoreAmenity模型是多对多的关系。注意through关键字是多对多关系查询的标准,在前面的 Django 模型章节中有描述。

接下来,AmenityAdmin类是一个为Item模型类设计的标准 Django 管理类,但是请注意,它对StoreInline类使用了inlines选项。由于最后一个配置,当您在 Django admin 中编辑或创建一个Amenity模型记录时,所有与给定Amenity模型记录相关的Store模型记录都显示在内联界面上,如图 11-44 所示。

A441241_1_En_11_Fig44_HTML.jpg

图 11-44。

Django admin inlines option with StackedInline for ManyToManyField

如图 11-44 所示,除了用于编辑或创建Amenity记录的标准 Django 管理表单,还有一个显示给定Amenity记录的所有Store模型记录的表单集;在这种情况下,表单集包含具有Amenity Laptop locks记录的Store记录。还要注意图 11-43 中表单集中每个表单的表单字段都是内嵌堆栈,这是由StackedInline类提供的行为。

Tip

InlineModelAdmin类(即TabularInlineStackedInline)除了model选项之外,还支持前面介绍的标准 Django 管理类—admin.ModelAdmin—选项(如formfieldsexclude),以及第六章介绍的标准表单集选项和第九章介绍的模型表单集选项(如formsetextramax_nummin_num)。 2

Django admin 自定义页面布局、数据和行为

除了前面章节中描述的 Django 管理类选项之外,还有多种方法可以定制 Django 管理页面的布局、数据和行为。您可以定制在所有 Django 管理页面上使用的某些全局值,而无需修改任何 Django 模板。但是除此之外,还可以定制 Django 管理页面使用的任何模板——如登录、注销、密码更新、显示记录和创建/更新/删除记录页面——以改变其布局(例如,修改页面中默认的蓝色 CSS 皮肤或组件位置)。

最后,还可以定制传递给 Django 管理页面的数据,以及通过将方法和字段声明为 Django 管理类的一部分来修改 Django 管理页面运行的默认行为(例如 CRUD 操作)。

Django admin 默认模板的自定义全局值

默认情况下,Django admin 被配置为 Django 项目的urls.py文件的一部分,如下面的代码片段所示:

from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

虽然django.contrib包中的admin.site.urls允许您在/admin/ url 上设置 Django 管理,但是同一个django.contrib.admin.site对象也允许您定制所有 Django 管理页面使用的某些值。

清单 11-22 展示了如何通过django.contrib.admin.site对象定制几个 Django 管理字段。

from django.conf.urls import url
from django.contrib import admin

admin.site.site_header = 'Coffeehouse admin'
admin.site.site_title = 'Coffeehouse admin'
admin.site.site_url = 'http://coffeehouse.com/'
admin.site.index_title = 'Coffeehouse administration'
admin.empty_value_display = '**Empty**'

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

Listing 11-22.Django admin django.contrib.admin.site object to customize fields

Tip

也可以在清单 11-22 的settings.py文件中定义定制的管理字段值。只需导入django.contrib.admin并声明管理字段,将它们与settings.py中的其他定制配置集中在一起。

正如您在清单 11-22 中看到的,在将admin.site.urls声明为url语句之前,在admin.site对象上有一系列声明,它们也是django.contrib包的一部分:

A441241_1_En_11_Fig46_HTML.jpg

图 11-46。

Django admin login page with custom global values

A441241_1_En_11_Fig45_HTML.jpg

图 11-45。

Django admin main index with custom global values

  • admin.site.site_header。-定义在所有 Django 管理页面上使用的标题(例如,在深蓝色标题和登录页面中)。参见图 11-45 和 11-46 。
  • admin.site.site_title。-定义所有 Django 管理页面使用的标题,作为 HTML 标题的一部分。参见图 11-45 和 11-46 。
  • admin.site.site_url。-定义作为 Django admin“查看站点”链接的一部分使用的域(例如,coffeehouse.com),以便从 Django admin 轻松访问实时站点。见图 11-45 。
  • admin.site.index_title。-定义 Django 管理主页的标题。见图 11-45 。
  • admin.empty_value_display。-定义 Django 管理模型值为空时显示的默认值。

正如您在图 11-45 和 11-46 中所看到的,通过类似清单 11-22 中的一些简单语句,您可以定制 Django 管理模板内容,而无需与模板或 HTML 交互。

值得注意的是,当记录字段包含空值时,清单 11-22 中描述的admin.empty_value_display选项适用于所有 Django 管理模型。本章前面的“记录显示”部分描述了admin.empty_value_display选项的示例,特别是图 11-5 和 11-6 ,以及清单 11-5 。

Django admin 自定义页面布局和自定义模板

虽然清单 11-22 中的django.contrib.admin.site对象选项提供了一种快速定制 Django 管理页面的方法,但是在面对更复杂的需求时,它们可能会有所不足,在这种情况下,您必须依赖定制模板。

Django 管理员使用的默认模板位于操作系统或虚拟 env Python 环境中 Django 安装的/django/contrib/admin/templates/目录下(例如<virtual_env_directory>/lib/python3.5/site-packages/django/contrib/admin/templates/)。

类似于前面章节中描述的 Django 模板定制技术(例如,Django 表单小部件、Django allauth),您可以创建这些默认模板的副本,并将它们放在您的项目中。通过这种方式,项目中的模板优先于默认的 Django 管理模板,您可以定制项目模板来满足您的需求。

Django admin /django/contrib/admin/templates/目录包含两个模板文件夹:adminregistration。将它们复制到一个项目目录中,该目录是settings.pyTEMPLATES/DIRS变量的DIRS文件夹的一部分。

Tip

请参阅该书附带的源代码,其中包括所有 Django 管理模板的布局。

所有 Django 管理模板都从admin/base_site.html模板继承了它们的行为,而模板本身又从admin/base.html模板继承了它的行为。如果你不熟悉 Django 模板继承是如何工作的,请阅读第三章描述这个主题。

如果您打开admin/base.html模板,您可以看到每个 Django 管理页面背后的核心结构,例如 HTML <head>部分(例如 CSS 文件、meta 标签)、导航标题和消息通知块等等。因此,您可以修改admin/base.html模板来包含定制的 CSS 或 JavaScript 文件,以改变每个 Django 管理页面的“外观”。

除了admin/base.html模板,在adminregistration目录中还有许多其他模板,它们的功能在下面的列表中描述:

  • admin/404.htmladmin/500.html。-分别为 Django admin not found 和 error 页面(即 HTTP 404 和 HTTP 500)定义布局。
  • admin/index.html。-定义主页面上 Django 管理索引页面的布局–如图 11-45 所示–以及显示所有型号的应用索引的布局。
  • admin/change_list.html。-定义用于读取记录的 Django 管理列表页面的布局,如图 11-1 到 11-22 所示。
  • admin/change_form.html。-定义 Django 管理表单页面的布局,这些页面用于创建、更新和删除记录,如图 11-23 到 11-44 所示。
  • admin/login.html。-定义 Django 管理员登录页面,如图 11-46 所示。
  • registration文件夹。-包含各种 Django 管理页面密码更改操作的模板,以及 Django 管理注销页面布局。

Note

此列表中未描述的admin文件夹中的其他页面(例如filter.htmlobject_history.html)是更细粒度的模板,包含在此列表的较大模板中,您可以根据需要进行定制。

正如您所看到的,通过创建 Django 管理模板的副本并将它们放在您的项目中,您可以通过修改其支持模板来微调每个 Django 管理页面的布局。关于 Django admin index.htmlchange_list.htmlchange_form.html模板,一个重要的模块化行为值得一提,那就是它们如何应用于单独的 Django 管理应用或模型。

默认情况下,如果您为admin/index.htmladmin/change_list.htmladmin/change_form.html模板提供自定义布局,这些模板将用于 Django 管理中的所有应用和模型(即全局)。然而,有时可能需要定制 Django 管理索引页面、列表页面或表格页面,仅用于某些应用(例如,stores应用)或包含单个模型(例如,Item模型)。

要为应用中的所有模型定义自定义 Django 管理模板,您可以创建一个 Django 管理模板,并将其放在模板路径admin/<app_name>/下(例如,admin/stores/change_list.html为所有stores应用模型定义一个change_list.html模板)。

要为单个模型定义一个定制的 Django 管理模板,您可以创建一个 Django 管理模板,并将其放在模板路径admin/<app_name>/<model>/下(例如,admin/items/item/change_list.html定义一个change_list.html模板,以便在items应用的Item模型上使用)。

Note

只有模板admin/index.html-admin/app_index.htmlchange_form.htmlchange_list.htmldelete_confirmation.htmlobject_history.html,popup_response.html可以按应用和型号进行定制。

Django 管理自定义静态资源

如果您在项目中使用定制 CSS 或 JavaScript 文件定制 Django admin admin/base.html模板,这些静态资源将在每个 Django 管理页面上生效。虽然在某些情况下这可能是一个理想的效果,但在其他情况下,可能有必要只对某些 Django 管理页面应用自定义静态资源。

Django 管理类支持Media类来定义 CSS 和 JavaScript 文件,并将它们包含在与给定 Django 管理类相关的所有页面上。在 Django 管理类上使用Media类的优点是,您不需要处理模板或 HTML 标记,Django 管理会自动加载静态资源,作为链接到管理类的每个管理页面的一部分。清单 11-23 展示了一个 Django 管理类,它使用了Media类。

from django.contrib import admin
from coffeehouse.items.models Item

class ItemAdmin(admin.ModelAdmin):
      list_per_page = 5
      class Media:
            css = {
                "screen": ("css/items/items.css",)
            }
            js = ("js/items/items.js",)

admin.site.register(Item, ItemAdmin)

Listing 11-23.Django admin class with Media class to define custom static resources

如清单 11-23 所示,Media类支持cssjs字段分别声明 CSS 和 JavaScript 静态文件。在css的情况下,清单 11-23 声明了一个字典,其中键对应于 CSS 媒体类型,值是一个带有 CSS 文件的元组。对于js的情况,清单 11-23 声明了一个指向 JavaScript 文件的元组。所有声明为Media类一部分的文件都会在 Django 的静态文件目录路径中自动搜索——如第五章所述。

清单 11-23 的最终结果是,所有与ItemAdmin管理类相关联的 Django 管理页面(例如index.htmlchange_list.html, change_form.html)都将包含一个额外的 CSS 导入语句(例如<link href="/static/css/items/items.css" type="text/css" media="screen" rel="stylesheet" />),以及一个额外的 JavaScript 导入语句(例如<script type="text/javascript" src="/static/js/items/items.js"></script>)。

值得指出的是,虽然您可以在 Django 管理页面中包含任何第三方 CSS 或 JavaScript 库(例如 Bootstrap、D3),但是 Django 管理页面已经在django.jQuery名称空间下包含了流行的 jQuery 2.2 库来实现某些功能。Django admin 使用一个 jQuery 名称空间,允许您在 Django admin 页面中导入任何其他 jQuery 库版本,而不用担心冲突。如果您想将包含的 Django admin jQuery 库用于您自己的定制 JavaScript,您必须将您的 JavaScript 逻辑包装在这个名称空间中,如下面的代码片段所示:

(function($) {
    // Custom JavaScript logic leveraging the Django admin built-in jQuery libray
    $(document).ready(function() {
       $('.deletelink').on('click',function() {
            if( !confirm('Are you sure you want to delete this record ?')) {
              return false;
           }
       });
    });
})(django.jQuery); // <-- Note wrapping namespace

正如您在最后一个代码片段中看到的,通过将您的定制 JavaScript 逻辑包装在django.jQuery名称空间中,它获得了对 Django admin 内置 jQuery 库的访问权(即,定制 JavaScript 逻辑获得了对 jQuery $范围的访问权)。

Grappelli Project – an Out-Of-Box Django Admin Supplement

如果你想为 Django admin 尝试不同的“外观和感觉”,而不必编写自定义模板或支持 CSS 和 JavaScript 文件,有各种各样的 Django 应用为此而设计。

最受欢迎的应用之一是“Grappelli 项目”。Grappelli 使用“指南针”CSS 创作框架来包含额外的 Django 管理功能,如:自动完成、内嵌可排序的“拖放”和对 jQuery 插件的支持,等等。

Django 管理自定义数据和行为,带有管理类字段和方法

虽然 Django 管理模板的修改允许您生成任何类型的 Django 管理页面布局,但对于需要在 Django 管理页面中包含自定义数据(例如,添加来自另一个模型的数据以供参考)或覆盖 Django 管理页面的默认 CRUD 行为(例如,为删除操作执行自定义审计跟踪)的情况,它仍然存在不足。

Django 管理类,就像你在本章中从清单 11-1 开始编写的那些类,依赖于二十多个字段——所有这些都是你在本章前面的章节中作为 Django 管理读取选项和创建/更新/删除选项探索过的——和三十多个方法 4 来定义 Django 管理页面的默认数据和行为。

与您如何定制 Django 基于类的视图所使用的默认行为和数据非常相似——如第九章所述——Django 管理类也可以定义它们自己的定制字段和方法来覆盖它们的默认数据和行为。

这一章的大部分内容已经涵盖了所有用于定制 Django 管理页面行为的 Django 管理类字段,所以我不再赘述。然而,我将提供最常见的 Django 管理类方法的例子,以说明如何在 Django 管理页面中添加定制数据和覆盖其他默认行为。

清单 11-24 展示了一个 Django admin 类,它使用了changelist_view()方法的自定义实现——在底层 Django admin change_list.html模板中添加了要访问的自定义数据——以及delete_view()方法的自定义实现——在对 Django admin 类执行删除操作时执行自定义逻辑。

from coffeehouse.stores.models import Store

class StoreAdmin(admin.ModelAdmin):
    search_fields = ['city','state']
    def changelist_view(self, request, extra_context=None):
        # Add extra context data to pass to change list template
        extra_context = extra_context or {}
        extra_context['my_store_data'] = {'onsale':['Item 1','Item 2']}
        # Execute default logic from parent class changelist_view()
        return super(StoreAdmin, self).changelist_view(
            request, extra_context=extra_context
        )
    def delete_view(self, request, object_id, extra_context=None):
        # Add custom audit logic here
        #
        # Execute default logic from parent class delete_view()
        return super(StoreAdmin, self).delete_view(
            request, object_id, extra_context=extra_context
        )

admin.site.register(Store, StoreAdmin)

Listing 11-24.Django admin class with custom 
changelist_view() and delete_view() methods

正如您在清单 11-24 中所看到的,changelist_view()delete_view()方法都是用您之前学习的 Django admin search_fields选项内联声明的。在这种情况下,每当您在 Django 管理中访问Store模型的列表视图页面时,清单 11-24 中的changelist_view()方法就会被触发(例如,如图 11-10 所示)。注意,changelist_view()方法向extra_context变量添加了一个自定义值,然后作为响应的一部分返回,在这种情况下,它是一个硬编码的值,但是您同样可以添加任何类型的数据,比如模型查询或第三方 API 调用,以传递给 Django 管理页面。由于这最后一个工作流,Store模型的列表视图页面(即change_list.html模板)可以访问定制数据以显示为部分页面。

清单 11-24 中的delete_view()方法在您删除 Django admin 中的Store模型时被触发。在这种情况下,清单 11-24 中的delete_view()方法只是通过调用父类的delete_view()方法来触发删除记录的默认操作,但是您可以看到,无论何时在 Django admin 中的Store模型上执行删除操作,都可以执行定制逻辑(例如,创建审计跟踪)。

正如我已经提到的,Django 管理类依赖三十多种方法来实现它们的默认行为,您可以定制所有这些方法来满足您的需求。考虑到这些方法可以生成大量的自定义变量,您可以使用清单 11-24 中的示例作为指南,并参考上一页的脚注,了解您可以在 Django admin 类中自定义的其他方法。

Django 管理 CRUD 权限

默认情况下,Django admin 允许拥有超级用户和职员权限的用户访问——如果您从未听说过 Django 超级用户、Django 职员或 Django 权限这些术语,请参阅前面描述 Django 用户管理的章节。

Django 超级用户,顾名思义,意味着它是一个拥有超级权限的用户。通过扩展,这意味着超级用户可以访问 Django admin 中的任何页面,以及创建、读取、更新和删除 Django admin 中任何类型的模型记录的权限。因为 Django 超级用户代表了一个“要么全有,要么全无”的命题,所以 Django admin 也被设计为允许访问 Django staff 用户。

任何标记为 staff 的 Django 用户都可以访问 Django admin,但除此之外没有其他权限,除非明确授予权限,如图 11-47 所示。

A441241_1_En_11_Fig47_HTML.jpg

图 11-47。

Django admin main page for staff user with no permissions

正如你在图 11-47 中看到的,Django 主管理页面是空的。虽然当您在项目的admin.py文件中没有注册的模型时,空的 Django 管理主页的场景也会出现,但是这种情况代表了没有显式模型权限的 staff 用户的场景。

为了让员工用户能够访问 Django 管理页面,必须通过模型权限的方式给予他们明确的权限,因为 Django 管理页面是基于 CRUD 模型操作的(例如,创建模型记录的 Django 管理页面,删除模型记录的 Django 管理页面)。

默认情况下,所有 Django 模型都被赋予了addchange,delete权限,您可以将这些权限分配给 staff 用户。因此,每个模型权限代表一个 Django 管理页面的访问权限。

例如,如果一个 staff 用户被授予了一个模型的delete权限,这意味着他还被授予了在 Django admin 中删除该模型记录的权限。类似地,如果一个 staff 用户被授予了一个模型的add权限,这意味着他被授予了在 Django admin 中访问该模型的 create record 页面的权限。最后,如果一个职员用户被授予了一个模型的change权限,这意味着他被授予了 Django admin 中该模型的编辑记录页面的访问权。

Note

在给定的模型上授予用户delete权限也需要授予change权限来完成 Django admin 中的删除操作。这是因为 Django 管理记录更改页面上提供了 Django 管理删除操作。

从这种行为可以推断,通过使用 staff 用户和模型权限,您可以允许对 Django admin 的不同部分进行非常细粒度的访问,而不是“全有或全无”的 Django admin 超级用户行为。

不过,Django admin staff 用户还有一个重要的缺失行为:允许对 Django admin 中的模型记录进行只读访问的能力。因为 Django 模型默认拥有addchangedelete权限(即 CUD[创建、更新、删除]行为),所以read权限被认为是隐含了change的存在(即如果您能够更改记录,那么您就能够读取它)。因此,要在 Django admin 中获得独立的只读权限,您必须添加一个定制的模型读取权限(即 CRUD 中缺少的 R)。

前一章更详细地描述了定制模型权限,但是我将在清单 11-25 中通过添加一个只读权限来描述 Django admin 上下文的这个过程。

# models.py
from django.db import models

class Menu(models.Model):
    name = models.CharField(max_length=30)
    creator = models.CharField(max_length=100,default='Coffeehouse Chef')

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)
    class Meta:

        permissions = (

            ('read_item','Can read item'),

        )

# admin.py
from django.contrib import admin

from coffeehouse.items.models import Item

class ItemAdmin(admin.ModelAdmin):
      list_per_page = 5
      list_display = ['menu','name','menu_creator']
      def get_readonly_fields(self, request, obj=None):

        if not request.user.is_superuser and request.user.has_perm('items.read_item'):

            return [f.name for f in self.model._meta.fields]

        return super(ItemAdmin, self).get_readonly_fields(

            request, obj=obj

        )

admin.site.register(Item, ItemAdmin)

Listing 11-25.Django model with custom read permission and Django admin class enforcing read permission

清单 11-25 中突出显示的第一步是带有自定义权限的Item模型,该自定义权限名为read_item,友好名称为'Can read item'。在您通过相应的迁移运行清单 11-25 中的Item模型之后,Item模型将获得一个定制的read_item权限。接下来,创建一个 staff 用户,并为其分配Item模型的read_item和内置change权限。一旦 staff 用户被授予这些权限,您必须强制 Django admin 类对Item模型只允许具有这些权限的用户进行读访问。

当用户被授予模型的change权限时,Django 管理员授予用户访问 Django 管理表单页面的权限,该页面用于更新给定模型的记录,如图 11-23 至 11-44 所示。但是因为您想将更新功能限制为只读,所以您必须将这个页面的表单字段设置为只读,这就是清单 11-25 的第二部分中的get_readonly_fields()方法的目的。

通过用自定义的get_readonly_fields()方法定义一个管理类,您可以告诉 Django 管理员在什么情况下您想要将 Django 管理页面的表单字段设置为只读。在这种情况下,您可以看到清单 11-25 中的get_readonly_fields()方法的逻辑使用is_superuser()has_perm()方法来确定调用方是否不是超级用户(即员工)以及用户是否拥有对Item模型的read_item权限。如果最后一条规则成立,那么get_readonly_fields()方法将所有模型表单字段设置为只读,这就是get_readonly_fields()方法的全部目的。如果最后一个规则为假,那么get_readonly_fields()方法返回其默认行为,调用父类的默认get_readonly_fields()方法。

正如您在这个简短的练习中看到的,通过将自定义 Django 管理类方法与标准和自定义 Django 模型权限结合使用,在 Django 管理中没有限制或允许 CRUD 操作。

多个 Django 管理站点

通过这一章,你已经了解到 Django 管理站点是使用django.contrib.admin.site.urls/admin/ url 下激活的。此外,Django 管理站点还要求将 Django 管理类的定义放在 Django 项目的 app admin.py文件中,并使用清单 11-1 (例如django.contrib.admin.site.register(Store,StoreAdmin))中描述的技术之一进行注册

虽然这是几乎所有 Django 管理站点安装的标准做法,但是当您想要或需要设置多个 Django 管理站点时,这个过程可能会有所不同。这当然引发了一个问题'为什么你想要或者需要多个 Django 管理站点?'

多个 Django 管理站点非常有用,因为它们允许您为不同类型的用户或组划分访问位置。例如,您可以设计一个 Django 管理站点,让员工跨不同的模型执行 CRUD 操作,同时设计一个完全不同的 Django 管理站点,让提供者跨另一组模型执行更有限的 CRUD 操作。

如果您维护一个 Django 管理站点,那么为多种类型的用户和模型创建一个解决方案会非常困难,因为您只有强制 CRUD 操作访问的用户权限,如前一节所述。对于多个 Django 管理站点,您可以根据自己的需求在一个或两个管理站点中注册模型。

创建多个 Django 管理站点的第一步是创建多个django.contrib.admin.AdminSite类的实例,如清单 11-26 所示

# urls.py (Main directory)
from django.conf.urls import include, url
from django.views.generic import TemplateView

from django.contrib import admin
admin.site.site_header = 'Coffeehouse general admin'

from coffeehouse.admin import employeeadmin, provideradmin

urlpatterns = [
url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
url(r'^admin/', admin.site.urls),

url(r'^employeeadmin/', employeeadmin.urls),

url(r'^provideradmin/', provideradmin.urls),

]

# admin.py (Main directory)

from django.contrib.admin import AdminSite

class EmployeeAdminSite(AdminSite):

    site_header = 'Coffeehouse Employee admin'

employeeadmin = EmployeeAdminSite(name='employeeadmin')

class ProviderAdminSite(AdminSite):

    site_header = 'Coffeehouse Provider admin'

provideradmin = ProviderAdminSite(name='provideradmin')

Listing 11-26.Django admin multiple sites accessible on different urls

首先,注意清单 11-26 用urls.py中的django.contrib.admin类声明了标准 Django admin,包括一个定制的site_header值,用于在标准/admin/ url 上挂载 Django admin。接下来,两个自定义 Django 管理实例被配置在它们自己的 URL 上——/employeeadmin//provideradmin/——使用一个对应于自定义django.contrib.admin.AdminSite类的urls引用。

定制 Django 管理站点类EmployeeAdminSiteProviderAdminSite都是在项目顶层目录中它们自己的admin.py文件中定义的(即,在主urls.py文件旁边)。注意每个自定义 Django 管理站点类是如何从django.contrib.admin.AdminSite类继承其行为的。接下来,观察清单 11-26 中的AdminSite类的每个实例如何定义site_header字段,以便为每个 Django 管理站点实例定义一个自定义头,就像对标准 Django 管理站点所做的那样。

此时,如果你访问自定义 Django 管理员的/employeeadmin//provideradmin/URL,你将访问如图 11-47 所示的 Django 管理员主页。在这两种情况下,您都会看到一个空的 Django 管理页面,因为每个定制的 Django 管理站点仍然没有注册任何模型。

要向一个定制的 Django 管理站点注册 Django 管理类,您可以使用清单 11-1 中描述的任何技术。不同之处在于,您必须向特定 Django 管理站点的实例(例如employeeadminprovideradmin)注册 Django 管理类,而不是默认的django.contrib.admin类,如清单 11-27 所示。

# admin.py (stores app)
from django.contrib import admin
from coffeehouse.stores.models import Store,Amenity

class StoreAdmin(admin.ModelAdmin):
    search_fields = ['city','state']

# Default model registration on main Django admin
admin.site.register(Store, StoreAdmin)

# Model registration on custom Django admin named provideradmin

from coffeehouse.admin import provideradmin

provideradmin.register(Store, StoreAdmin)

# admin.py (items app)
from django.contrib import admin
from coffeehouse.items.models import Menu

class MenuAdmin(admin.ModelAdmin):
    list_display = ['name','creator']

# Default model registration on main Django admin
admin.site.register(Menu, MenuAdmin)

# Model registration on custom Django admin named provideradmin

from coffeehouse.admin import employeeadmin

employeeadmin.register(Menu, MenuAdmin)

Listing 11-27.Django admin class registration on multiple Django admin sites

注意在清单 11-27 中,除了通过admin.site.register完成的默认 Django 管理类注册之外,Store类注册到自定义provideradmin Django 管理类,Menu类注册到自定义employeeeadmin Django 管理类,这两个都是清单 11-26 中声明的自定义 Django 管理类。

正如您在清单 11-27 中看到的,在多个 Django 管理站点上注册同一个 Django 管理类是完全有效的,这个过程允许您有选择地访问不同 Django 管理站点下的某些模型。

Caution

多个 Django 管理站点不排除用户权限、认证或 url 可见性。默认情况下,所有 Django 用户都可以使用相同的凭证,并在多个 Django 管理站点上被分配相同的权限。这意味着由您决定在所有 Django 管理站点上实施有效的模型权限,限制每个 Django 管理站点的模型注册,以及避免容易猜测的 Django 管理 URL 以避免意外访问。

Footnotes 1

https://docs.djangoproject.com/en/1.11/ref/contrib/admin/actions/

  2

https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#inlinemodeladmin-options

  3

http://grappelliproject.com/

  4

https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#adminsite-methods

十二、Django 的 REST 服务

自 2000 年首次出现以来,表述性状态转移(REST)服务或简单的 RESTful 服务已经成为 web 开发中最流行的技术之一。关于 REST 服务有一个很长的背景故事,我不会在这里深入讨论,REST 服务在 web 开发中的吸引力和爆炸性增长仅仅是因为它们如何以一种简单和可重用的方式解决一个常见的问题。

在这一章中,您将了解到可用 Django 创建 REST 服务的选项,包括普通 Python/Django REST 服务和特定于框架的 REST 服务。此外,您将了解如何使用普通的 Python/Django 包创建 REST 服务,以及它们何时优于使用特定于框架的 REST 服务包。此外,您还将学习如何创建和使用用 Django REST 框架创建的 REST 服务的一些最重要的特性,以及将基本的安全性集成到 Django REST 框架服务中。

Django 的 REST 服务

REST 服务通过端点或互联网 URL 提供对数据的访问,不假设谁使用它(例如,物联网设备、移动电话或桌面浏览器);这意味着访问 REST 服务不需要任何设备或环境,您需要的只是互联网接入。

最重要的是,因为 REST 服务是在互联网 URL 上操作的,所以它们提供了一个非常直观的访问方案。例如,REST 服务 URL /products/可以表示“获取所有产品数据”,而 URL /products/10/20/可以表示获取价格在 10 到 20 之间的产品的所有数据,而 URL /products/drinks/可以获取饮料类别中的所有产品数据。

最后一种方法的强大之处在于,访问解决复杂数据查询的 REST 服务变体没有陡峭的学习曲线(例如,语言语法、复杂的业务逻辑)。由于这些特性和多功能性,REST 服务在许多领域和应用中都是可重用的数据骨干。例如,实际上整个 web API(应用编程接口)世界都是围绕 REST web 服务运行的,因为根据定义,API 必须为客户访问其功能提供一种设备/环境中立的方式。事实上,您访问的大多数网站很有可能以这样或那样的方式使用 REST 服务,这种做法允许网站运营商重用相同的数据,然后在桌面浏览器、物联网设备、移动应用、RSS 提要或任何其他目标(如加速移动页面(AMP))上为用户格式化数据。 2

简要概述了 REST 服务之后,让我们从在 Django 中创建 REST 服务的最简单的选项开始。

设计为 REST 服务的标准视图方法

您实际上可以在 Django 中创建一个 REST 服务,只需要 Django 包和 Python 的核心包,而不需要安装任何第三方包。清单 12-1 展示了一个标准的 Django url 和视图方法,它被设计成 REST 服务的功能。

# urls.py (In stores app)
from coffeehouse.stores import views as stores_views

urlpatterns = [
    url(r'^rest/$',stores_views.rest_store,name="rest_index"),
]

# views.py (In stores app)
from django.http import HttpResponse
from coffeehouse.stores.models import Store
import json

def rest_store(request):
    store_list = Store.objects.all()
    store_names = [{"name":store.name} for store in store_list]
    return HttpResponse(json.dumps(store_names), content_type='application/json')

# Sample output
# [{"name": "Corporate"}, {"name": "Downtown"}, {"name": "Uptown"}, {"name": "Midtown"}]

Listing 12-1.Standard view method designed as REST service

清单 12-1 中的 url 语句定义了对rest目录下的stores应用的访问模式(即最终的 url /stores/rest/)。这最后一个 url 语句连接到rest_store视图方法——也在清单 12-1 中——该方法进行查询以从数据库中获取所有Store记录,然后创建一个列表理解以从结果查询中生成一个商店名称列表,最后使用 Django 的HttpResponse使用 Python 的json包返回一个带有商店名称的 JSON 响应。清单 12-1 中的设计是您可以在 Django 中创建的最简单的 REST 服务之一,并且有一些要点:

  • 序列化。-请注意,Store查询并不直接用于响应,而是首先创建一个列表理解。这样做是为了将数据序列化为适当的格式。由于 Django 查询的构建方式,结果可能无法自然序列化,因此使用列表理解来确保数据是标准的 Python 字典,可以用 json 包转换成 JSON。在接下来的边栏中,我将提供更多的细节来说明为什么序列化是使用 REST 服务的一个重要部分。
  • 响应处理。-注意使用 Django 的低级HttpResponse方法与 Django 更常用的render()方法来生成响应。虽然可以使用 Django 的render()方法,并进一步将数据传递给模板来格式化响应,但对于大多数 REST 服务来说,这是不必要的,因为响应往往是原始数据(例如 JSON、XML),并且可以在没有支持模板的情况下生成。此外,注意清单 12-1 中的HttpResponse使用了content_type参数,该参数添加了 HTTP Content-Type application/json头,告诉请求方响应是 JSON。HTTP Content-Type 响应值很重要,因为它避免了消费者必须猜测如何处理 REST 服务响应(例如,另一个 Content-Type 选项可以是带有 XML 响应的 REST 服务的application/xml)。Django 视图方法的 HTTP 响应处理将在第二章中详细讨论。
  • (否)查询参数。-为了简单起见,清单 12-1 中的 view 方法没有参数,所以它提供了一个非常严格的输出(即所有存储都是 JSON 格式)。

Error: Is Not JSON/XML Serializable

在 Django 中创建 REST 服务时,最容易遇到的错误之一是“不是 JSON/XML 可序列化的”错误。这意味着 Django 不能将源数据序列化(即表示/转换)成所选择的格式——JSON 或 XML。当源数据由可能产生不确定或不明确的序列化结果的对象或数据类型组成时,会发生此错误。

比如你有一个Store带关系的模型类,Django 应该如何序列化这些关系?类似地,如果您有一个 Python datetime实例,Django 应该如何将值序列化为 DD/MM/YYYY、DD-MM-YYYY 或其他值?在任何情况下,Django 从不试图猜测序列化表示,所以除非源数据是自然可序列化的——如清单 12-1 所示——你必须显式指定一个序列化方案——我将在下面解释——否则你会得到“不是 JSON/XML 可序列化的”错误。

现在您已经对 Django 中的一个简单的 REST 服务有了一个简单的了解,让我们重新编写清单 12-1 中的 REST 服务,以利用参数,这样它就可以返回不同的数据结果和数据格式。

# urls.py (In stores app)
from coffeehouse.stores import views as stores_views

urlpatterns = [
    url(r'^rest/$',stores_views.rest_store,name="rest_index"),
    url(r'^(?P<store_id>\d+)/rest/$',stores_views.rest_store,name="rest_detail"),

]

# views.py (In stores app)
from django.http import HttpResponse
from coffeehouse.stores.models import Store

from django.core import serializers

def rest_store(request,store_id=None):
    store_list = Store.objects.all()
    if store_id:

        store_list = store_list.filter(id=store_id)
    if 'type' in request.GET and request.GET['type'] == 'xml':
        serialized_stores = serializers.serialize('xml',store_list)

        return HttpResponse(serialized_stores, content_type='application/xml')
    else:
        serialized_stores = serializers.serialize('json',store_list)

        return HttpResponse(serialized_stores, content_type='application/json')

Listing 12-2.Standard view method as REST service with parameters and different output formats

清单 12-2 中的第一个不同之处是一个额外的 url 语句,它接受带有store_id参数的请求(例如,/stores/1/rest//stores/2/rest/)。在这种情况下,新的 url 由相同的rest_store()视图方法处理,以处理来自清单 12-1 (即/stores/rest/)的商店索引

接下来,清单 12-2 中的视图方法是清单 12-1 中视图方法的修改版本,它接受一个store_id参数。这个修改允许 REST 服务处理所有商店(例如,/stores/rest/)或单个商店(例如,/stores/1/rest/的商店号1)的 url 请求。如果你不熟悉如何用参数设置 Django urls,请参阅第二章,其中描述了 url 参数。

一旦进入 view 方法,就会执行一个查询,从数据库中获取所有的Store记录。如果该方法接收到一个store_id值,将对查询应用一个额外的过滤器,以将结果限制在给定的store_id内。由于 Django 查询的工作方式,最后一个查询逻辑非常有效——如果您不熟悉 Django 查询的行为,请参见第八章“理解查询集”一节。

接下来,对request进行检查,看它是否具有带有xml值的type参数。如果一个请求匹配最后一个规则(例如/stores/rest/?type=xml,那么为 REST 服务生成一个 XML 响应;如果它不符合最后一条规则,那么将为 REST 服务生成一个 JSON 响应(默认)。

虽然清单 12-2 中的例子也使用了HttpResponsecontent_type参数,就像清单 12-1 一样,但是请注意,数据是用 Django 的django.core.serializers包准备的,该包旨在使数据序列化更容易——不像清单 12-1 中的 REST 服务需要用清单理解预处理查询数据,然后使用 Python 的json包来完成这个过程。

清单 12-2 中django.core.serializers的使用非常简单。使用了serialize()方法,该方法期望将序列化类型作为其第一个参数(例如,'xml''json'),将要序列化的数据作为第二个参数(例如,表示查询的store_list引用)。在django.core.serializers package后面有更多的功能(例如过滤器),为了简单起见,我不在这里探究,但是图 12-1 和 12-2 显示了来自清单 12-2 中 REST 服务的两个示例结果。

A441241_1_En_12_Fig2_HTML.jpg

图 12-2。

XML output from Django REST service

A441241_1_En_12_Fig1_HTML.jpg

图 12-1。

JSON output from Django REST serviceXML output from Django REST service

正如您在图 12-1 和 12-2 中看到的,Django 能够序列化一个查询,并以 JSON 或 XML 的形式将其结果输出为 REST 服务响应,只需清单 12-2 中的几行代码。清单 12-2 中的这几行代码可能会带您在 Django 中构建自己的 REST 服务,但是如果您从未做过 REST 服务或计划将 REST 服务作为应用或网站的核心,您应该停下来分析一下您的需求。

因为 REST 服务以一种简单且可重用的方式解决了一个常见的问题,所以它们容易受到范围蔓延的影响,这是一个变化似乎永无止境且功能总是“没有完全完成”的特征。看到这个最初的 Django REST 服务示例后,问自己以下问题:

  • 您是否需要支持多个 Django 模型类型的 REST 服务?(例如,商店、饮料、员工)。
  • 您需要将 JSON/XML 响应定制为不直接映射到 Django 模型记录的东西吗?(例如,使用自定义模式,过滤某些属性)。
  • 是否需要为用户提供一个友好的界面,描述一个 REST 服务做什么,接受什么参数?
  • 您是否需要支持某种身份验证机制,以便 REST 服务不公开可用?
  • 您是否需要 REST 服务来支持不仅仅是显示数据或读取操作?(例如,更新和删除操作)?

如果你对前面的大部分问题回答是肯定的,那么你的 REST 服务已经超出了基本的范围。虽然您可以继续构建清单 12-2 中的例子——使用一个标准的 Django 视图方法和额外的 Python 包——来支持前面所有的场景,但是在 Django 中支持这些更复杂的 REST 服务特性是许多人以前走过的路,甚至有专门的框架来支持这个目的。

Django REST 框架 3

Django REST 框架现在是它的第三个版本。与我刚才描述的从普通 Python/Django 包编写自己的 REST 服务相比,Django REST 框架提供了以下优势:

  • web 可浏览界面。-为所有 REST 服务提供用户友好的描述页面(例如,输入参数、选项)。类似于 Django admin 提供了一个几乎不费力的界面来查看 Django 项目的数据库,Django REST 框架为最终用户提供了一个几乎不费力的界面来发现 Django 项目的 REST 服务。
  • 集成认证机制。-为了限制对 REST 服务的访问并节省集成认证逻辑的时间,Django REST 框架与 OAuth2、HTTP 签名、HTTP 摘要认证、JSON Web Token 认证和 HAWK (HTTP Holder-Of-Key 认证方案)等认证机制紧密集成。
  • 更加灵活和复杂的序列化程序。-为了避免重新发明轮子和不断处理“不可序列化”的错误,Django REST 框架有自己的序列化器,用于处理复杂的数据关系。

这些只是使用 Django REST 框架的一些核心好处。如您所知,如果您计划创建多个 REST 服务,那么与使用普通 Python/Django 包处理部署 REST 服务所必需的搭建代码相比,花时间学习 Django REST 框架是非常值得的。

决哥打字框架

Django Tastypie 框架是作为 Django REST 框架的替代方案出现的。虽然 Django Tastypie 框架是 0.14 版本,但是不要让 1.0 之前的版本号欺骗了你:Django Tastypie 框架从 2010 年就开始开发了。虽然 Django Tastypie 框架和 Django REST 框架可能产生相同的结果,但是 Django Tastypie 框架有以下不同之处:

Tastypie 提供了更多的默认行为,使得配置和设置 REST 服务比使用 Django REST 框架更简单。

Tastypie 仍然是第二个最常用的 Django REST 包5——尽管它获得了 Django REST 框架一半的下载量——所以它仍然是许多 Django 项目的一个有吸引力的选择。

Tastypie 最初是由 Django haystack 的相同创建者开发的,Django haystack 仍然是最受欢迎的 Django 搜索包6——所以 Tastypie 在一些非常坚实的 Python/Django 基础上运行。

尽管 Django Tastypie 框架不像 Django REST 框架那样主流,但是如果您觉得用后者创建 REST 服务有些力不从心,您总是可以尝试前者以获得更快的 REST 解决方案,而不是用普通的 Python/Django 包从头开始构建您的 REST 服务。

Django REST 框架概念和介绍

Django REST 框架以标准 Python 包的形式发布。所以要开始,你需要用命令:pip install djangorestframework安装 Django REST 框架。

一旦安装了 Django REST 框架包,将它添加到 Django 项目的settings.py文件中的INSTALLED_APPS列表变量中,文件名为rest_framework。一旦完成,您就可以开始使用 Django REST 框架了。接下来,让我们使用 Django REST 框架来浏览 REST 服务的核心概念和创建过程。

序列化程序和视图

序列化器是 Django REST 框架的主要构建块之一,用于定义数据记录的表示,通常基于 Django 模型。如前一节 Django REST 服务选项介绍中所述,Python 记录可以有不明确的数据表示(例如,带有datetime值的记录可以表示为 DD/MM/YYYY、DD-MM-YYYY 或 MM-YYYY ),序列化程序消除了如何表示记录的任何不确定性。清单 12-3 展示了一个使用其serializers包之一的 Django REST 框架序列化程序。

# coffeehouse.stores.serializers.py file
from rest_framework import serializers

class StoreSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=200)
    email = serializers.EmailField()

Listing 12-3.Serializer class based on Django REST framework

正如你在清单 12-3 中看到的,Django REST 框架序列化器是一个标准的 Python 类,在这种情况下,它从 Django REST 框架的serializers.Serializer类继承了它的行为。接下来,在 serializer 类中有一组字段,它们使用来自同一个 Django REST 框架的serializers包的数据类型。请注意这个 Django REST 框架序列化程序类和 Django 模型类或 Django 表单类之间的相似结构(即,它们从父类继承它们的行为,并使用不同的字段来表示不同的数据类型,如字符和电子邮件字段)。

清单 12-3 中的例子是最简单的 Django REST 框架序列化类之一,因为它只有两个字段,并且从最基本的serializers.Serializer类继承了它的行为。但是就像 Django 模型和表单一样,随着 Django REST 框架的发展,您会发现自己使用了更高级的数据类型,并且使用比serializers.Serializer更复杂的基类创建了序列化器。我将很快描述一个更高级的串行化器。接下来让我们在 Django REST 框架的上下文中探索一个视图。

序列化程序类本身什么也不做,必须与完成大部分 REST 服务逻辑(即处理传入请求、查询数据库中的数据)的视图集成,然后使用序列化程序来转换数据。在本章的第一节中,您了解了如何将一个常规的 Django 视图方法转换成 REST 服务。虽然完全可以在常规的 Django 视图方法中使用 Django REST 框架序列化程序类——如清单 12-3 所示——但是 Django REST 框架还提供了一个额外的视图语法——如清单 12-4 所示——以使构建 REST 服务变得更加容易。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET','POST','DELETE'])
def rest_store(request):
    if request.method == 'GET':
        stores = Store.objects.all()
        serializer = StoreSerializer(stores, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        ... #logic for HTTP POST  operation
    elif request.method == 'DELETE':
        ... #logic for HTTP DELETE operation

Listing 12-4.Django view method decorated with Django REST framework

首先注意清单 12-4 中的方法是一个常规的 Django 视图方法,但是它使用了 Django REST 框架中的@api_view装饰器。@api_view的参数指示支持哪些 HTTP REST 方法——参见第二章或第六章了解 Django 或维基百科 REST 条目中 HTTP 方法主题的详细信息, 7 因为 HTTP 方法是一个通用的 REST 概念,而不是 Django/REST 主题。接下来,在视图方法内部,执行一系列条件来处理构成 REST 服务的不同 HTTP 请求方法。

如果在 view 方法上发出 GET 请求,就会发出一个查询来获取所有的Store模型记录。然而,注意在清单 12-4 的例子中,它使用清单 12-3 中的StoreSerializer来转换 Django queryset。此外,return语句使用 Django REST 框架Response方法,而不是 Django 的标准HttpResponserender方法。

更重要的是,注意清单 12-4 中的请求和响应逻辑都没有任何 REST 输出格式(例如 JSON、XML)。通过利用 Django REST 框架,您不再需要处理检测或处理输出格式的底层细节——这由 REST 框架直接负责,并且基于您如何向 REST 服务发出请求,我将很快对此进行描述。

接下来,清单 12-4 中的rest_store()视图方法必须被配置成可以通过 url 访问。添加到应用的urls.py文件中的一行类似于url(r'^rest/$',stores_views.rest_store,name="rest_index")的代码解决了这个问题。一旦你这样做了,如果你访问这个 URL,你会看到如图 12-3 所示的结果。

A441241_1_En_12_Fig3_HTML.jpg

图 12-3。

Django REST framework main service response Caution

如果你得到的是错误'TemplateDoesNotExist at /stores/rest/ rest_framework/api.html'而不是图 12-3 中的页面,这意味着你没有将 REST 框架添加到你的项目的INSTALLED_APPS中,如本节开始所述(例如INSTALLED_APPS = ['rest_framework'])。

正如你在图 12-3 中看到的,REST 框架服务响应与 Django 的基本HttpResponse响应相比,信息非常丰富,非常漂亮,而 Django 的基本HttpResponse响应是由本章开始部分创建的 Django REST 服务生成的。

例如,您可以看到 REST 服务提供的不同选项,并通过几次点击来调用它的各种操作。还要注意,由于清单 12-3 中的StoreSerializer定义,REST 服务的输出只有带有两个字段的Store对象。接下来,让我们修改序列化程序类来输出完整的Store记录。清单 12-5 展示了一个基于清单 12-3 的更新后的序列化器类。

from rest_framework import serializers

from coffeehouse.stores.models import Store

class StoreSerializer(serializers.ModelSerializer):
    class Meta:
        model = Store
        fields = '__all__'

Listing 12-5.Serializer class using Django model based on Django REST framework

为了序列化基于 Django Store模型的完整Store记录,清单 12-5 利用 Django REST 框架ModelSerializer类来简化序列化器语法。更重要的是,注意清单 12-5 中的 REST 框架StoreSerializer类如何使用与 Django 模型表单相同的语法,这些表单也是基于 Django 模型的。

在这种情况下,StoreSerializer类使用一个Meta类,其中model选项设置为Store以指定要序列化的 Django 模型,而fields选项设置为__all__以指示所有模型字段都应该序列化——注意,fields选项同样可以声明一个有限的模型字段名称列表,就像在模型表单中所做的那样。

有了清单 12-5 中的这个更加专门化的父序列化器ModelSerializer类——与清单 12-3 中的通用Serializer类相比——使用 Django REST 框架序列化用于 REST 服务的 Django 模型类就是这么简单。

但是,尽管这些 Django REST 框架序列化特性非常强大,而且清单 12-4 中使用的 Django REST 框架视图语法非常有用——减少了底层逻辑并提供了一个用户友好的界面——但是在视图方法中仍然有许多脚手架代码可以进一步削减。

为了进一步简化 REST 服务的构造,Django REST 框架可以利用基于类的视图。

基于类的视图

第二章介绍了 Django 基于类的视图的概念,第八章用基于类的视图扩展了这个主题,这些视图使用 Django 模型来执行 CRUD 操作。因为在 Django 中已经介绍了基于类的视图的原理,所以我假设您对这个主题有一个最低的熟悉程度;如果没有,那么回到其他章节学习基础知识。

清单 12-6 展示了一个基于 REST 框架类的基于类的视图,它简化了早期的标准视图方法——在清单 12-4 中——用 REST 框架中的@api_view修饰。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework.views import APIView
from rest_framework.response import Response

class StoreList(APIView):

    def get(self, request, format=None):
        stores = Store.objects.all()
        serializer = StoreSerializer(stores, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        ...
        #logic for HTTP POST operation

    def delete(self, request, format=None):
        ...
        #logic for HTTP DELETE operation

Listing 12-6.Django REST framework class-based views

注意清单 12-6 中的类是如何从 Django REST 框架APIView类继承其行为的。这使得该类可以包含代表每个 REST 服务的 HTTP 方法(即 GET、POST、DELETE)的各种方法,类似于处理多个 HTTP 方法(如表单处理)的标准 Django 基于类的视图。

正如您所看到的,与清单 12-4 中的常规 Django 视图方法相比,清单 12-6 产生了更具可读性的 REST 服务逻辑,后者要求您手动检查请求并执行条件语句。清单 12-6 中的get方法内部的逻辑使用了与清单 12-4 中相同的 Django REST 框架语法,所以没有什么新的东西。

同样,您必须将一个常规的 Django 视图方法连接到一个 url,您还必须关联一个 Django REST 框架的基于类的视图,以便在某个 url 上可以访问它。清单 12-7 展示了一个urls.py文件,它具有从清单 12-6 中访问 REST 服务基于类的视图的语法。

from django.conf.urls import url
from coffeehouse.store import stores_views

urlpatterns = [
    url(r'^$',stores_views.index,name="index"),
    url(r'^rest/$',stores_views.StoreList.as_view(),name="rest_index"),    ]

Listing 12-7.Django URL definition linked to Django REST framework class-based views

在清单 12-7 中,您可以看到urls.py文件使用as_view()方法声明了r'^rest/$' url 模式被映射到 Django REST 框架StoreList类,这是所有基于 Django 类的视图将它们链接到 url 的主要方式。以这种方式,如果在/stores/rest/ url 上发出 HTTP GET 请求,则由基于类的视图的get()方法处理,如果在同一/stores/rest/上发出 HTTP POST 请求,则由基于类的视图的post()方法处理。

值得一提的是,点击清单 12-6 中由 REST 框架基于类的视图支持的 url 也会产生如图 12-3 所示的相同界面,这是由清单 12-4 中的标准视图方法产生的。

现在,尽管 REST 框架基于类的视图有助于简化 REST 服务逻辑,但基于类的视图仍然需要您编写每个方法背后的所有逻辑。例如,在清单 12-6 中的get()方法中,执行一个查询来获取所有Store记录,然后序列化数据,最后返回一个响应。对于清单 12-6 中的post()方法,您同样需要用提供的数据插入/更新一个Store记录,而对于delete()方法,您需要用提供的数据删除一个Store记录。

一旦您用 Django REST 框架编写了几个基于类的视图,您就会意识到每种类型的视图方法背后都有一个固定的模式(例如,读取一个记录,序列化它,并返回一个响应)。除此之外,您还将认识到 REST 方法(例如 GET、POST 和 DELETE)和它们在 Django 模型上执行的操作(例如创建-读取-更新-删除(CRUD)操作)之间的密切关系。

为了避免为与 REST 服务相关联的不同 Django 对象不断编写相同的 CRUD 操作和样板逻辑,遵循 Django 的 DRY(不要重复自己)原则和 Django 的基于类的模型视图原则的 Django REST 框架提供了另一种构造:mixins。

混合和通用的基于类的视图

mixin 用于封装和重用相同的逻辑,并且能够在基于类的视图中使用它。例如,不是编写清单 12-6 中的相同逻辑——获取所有记录,序列化它们,并生成一个响应——针对不同的 REST 服务(例如,ItemDrink,Store服务)反复进行;可以使用mixins.ListModelMixin类,快速达到同样的效果。

我不会在这里更详细地介绍 mixin 类,主要是因为 mixin 类不像其他 Django REST 框架选项那样被广泛使用,更不用说 Django mixins 已经在第九章使用模型的基于类的视图中描述过了。

对于大多数 Django framework REST 服务,要么使用基于类的视图——以获得对逻辑的完全控制——要么使用基于 mixins 的更简洁的方法,称为“混合通用类视图”。清单 12-8 基于清单 12-6 中基于类的视图展示了一个等价的混合通用类视图。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework import generics

class StoreList(generics.ListCreateAPIView):
    queryset = Store.objects.all()
    serializer_class = StoreSerializer

Listing 12-8.Django mixed-in generic class views in Django REST framework

注意清单 12-8 甚至比之前相同 REST 服务的迭代更加简洁。在这种情况下,通用类名ListCreateAPIView表明了该类产生了什么——基于指定获取所有Store记录的queryset选项和指向清单 12-5 中的StoreSerializer类的serializer选项,生成一个列表的 REST 视图。

就像以前一样,即使您现在有一个由几行代码组成的 REST 服务,Django REST 框架也可以通过使用视图集和路由器来进一步扩展 Django 的 DRY 原则。

查看集合和路由器

清单 12-8 中的通用类视图对于仅仅三行代码来说已经非常强大了,但是它只是一个显示Store记录列表的类。假设您现在需要创建一个 REST 服务来显示特定的Store记录,创建另一个 REST 服务来更新Store记录,创建另一个 REST 服务来删除Store记录。在这种情况下,您将需要再创建三个通用类视图和三个 URL 映射来推出这个基本的 CRUD 功能。但是您可以依赖 Django REST 框架视图集,而不是为每种情况创建单独的视图类。

Django REST 框架视图集,顾名思义,就是一组视图。要创建 Django REST 框架视图集,您需要做的就是创建一个类,该类继承了 Django REST 框架中用于此目的的一个类的行为。清单 12-9 展示了用ModelViewSet类创建的视图集。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework import viewsets

class StoreViewSet(viewsets.ModelViewSet):
    queryset = Store.objects.all()
    serializer_class = StoreSerializer

Listing 12-9.Django viewset class in Django REST framework

清单 12-9 和清单 12-8 中的 REST 服务类一样短,但是除了类名的变化,父类ModelViewSet继承的类给了这个 REST 服务一个全新的维度。单独使用这个类,REST 服务会自动显示一个Store记录列表,以及创建、读取、更新或删除单个Store记录。

因为一个视图集生成多个视图,所以仍然需要将每个视图配置到一个 url,在这种情况下,最简单的方法是使用 Django REST 框架路由器。路由器对于视图集就像 url 语句对于基于类的视图一样:一种连接端点的方式。清单 12-10 展示了用 Django REST 框架路由器设置的urls.py文件。

from django.conf.urls import include, url
from coffeehouse.stores import views as stores_views

from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'stores', stores_views.StoreViewSet)

urlpatterns = [
    url(r'^rest/', include(router.urls,namespace="rest")),
    ]

Listing 12-10.Django URL definition with Django REST framework router for view set

Caution

视图集和路由器的组合会自动创建敏感的 REST 端点(例如,删除和更新),默认情况下,任何人都可以访问这些端点。参见下一节 REST 框架安全性来限制这些服务端点。

清单 12-10 中的第一步是用routers.DefaultRouter()初始化一个路由器,然后用它注册不同的视图集。正如您在清单 12-10 中看到的,router注册过程使用了接受两个参数的router.register方法:第一个参数表示 REST url 前缀——在本例中为stores——第二个参数指定视图集——在本例中为清单 12-9 中的StoreViewSet类。

接下来,你可以看到router是使用 Django 的标准urlinclude方法赋值的。在这种情况下,路由器实例被分配在r'^rest/' url 下,这意味着Store视图集的最终根 url 变成了/rest/stores/,如图 12-4 所示。

A441241_1_En_12_Fig4_HTML.jpg

图 12-4。

Django REST framework view set main page

如图 12-4 所示,REST 框架呈现了一个默认的 Api 根页面。您可以进一步导航到 Api 根页面下的其他 URL(即/stores/rest/)来执行与视图集相关联的其他 CRUD 操作(例如,对/stores/rest/stores/的 HTTP GET 请求以获取所有Store记录的列表,对/stores/rest/stores/1/的 HTTP GET 请求以获取带有id=1,Store记录,或者对/stores/rest/stores/2/的 HTTP DELETE 请求以删除带有id=3Store记录)。

通过对视图集和路由器的描述,我们总结了用 Django REST 框架建立 REST 服务所需的基本概念。现在您已经熟悉了基础知识,在下一节中,您将学习如何保护用 Django REST 框架构建的 REST 服务。

Django REST 框架安全性

虽然 Django REST 框架为创建允许访问应用数据的 REST 服务节省了大量时间,但是您需要小心不要无意中允许访问数据或功能。对 Django REST 框架使用错误的类或配置参数会使您的应用面临安全风险,尽管 Django REST 框架从根本上来说是安全的。

设置 REST 框架服务权限

默认情况下,所有 REST 框架服务都对任何人开放,只要他们知道或发现一个 REST 服务端点(即 url)。虽然这种默认行为很方便,但它也可能是一个严重的安全威胁,特别是当您创建带有敏感操作(例如,更新或删除操作)或视图集的 REST 框架服务时——比如清单 12-9 中的视图集——它们会自动创建支持敏感操作的端点。

默认的 REST 框架权限可以通过setting.py文件中的REST_FRAMEWORK变量,通过DEFAULT_PERMISSION_CLASSES选项来配置。开箱即用,REST 框架将该选项设置为使用rest_framework.permissions.AllowAny类,如下面的代码片段所示:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
    )
}

这意味着除非您在 Django 项目的settings.py文件中定义了一个DEFAULT_PERMISSION_CLASSES选项,否则所有的 REST 框架服务都是对公众开放的。为了禁止公众访问 REST 服务,您可以更改 REST 框架的默认权限策略。

清单 12-11 展示了设置为rest_framework.permissions.IsAuthenticated类的DEFAULT_PERMISSION_CLASSES选项,它强制规定只有通过 Django 的内置用户系统登录的用户——在第十章中描述——才被允许访问 REST 框架服务。

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}
Listing 12-11.Django REST framework set to restrict all services to authenticated users

除了rest_framework.permissions.IsAuthenticated类,REST 框架还支持表 12-1 中的类成为DEFAULT_PERMISSION_CLASSES选项的一部分。

表 12-1。

Django REST framework permission classes

| REST 框架权限类 | 描述 | | --- | --- | | rest _ framework . permissions . allow any | (默认)允许任何人访问。 | | rest _ framework . permissions . is 已验证 | 允许通过 Django 的内置用户系统访问登录的用户。 | | rest _ framework . permissions . isadminuser | 基于 Django 的内置用户系统,允许访问 Django 管理员用户。 | | rest _ framework . permissions . isauthenticedorreadonly | 允许任何人(无论是否登录)进行读取访问,但需要登录才能执行非读取操作。 | | rest _ framework . permissions . djangodelpermissions | 允许登录用户访问,但也要求所述用户具有必要的添加/更改/删除模型权限,REST 服务可以在这些权限上操作。 | | rest _ framework . permissions . djangodelpermissionsoranonreadonly | 就像 DjangoModelPermissions 类一样,但是允许任何人(无论是否登录)进行读取访问。 | | rest _ framework . permissions . djangobjectpermissions | 类似于 DjangoModelPermissions 类,除了它在 REST 服务操作的模型上处理每个对象的权限。 |

如表 12-1 所示,REST 框架提供了各种类来设置项目中所有 REST 服务的默认访问权限。例如,如果您允许公共读取访问项目的 REST 服务,但是想要限制更敏感的 REST 服务操作,那么表 12-1 中的IsAuthenticatedOrReadOnlyDjangoModelPermissionsOrAnonReadOnly类是DEFAULT_PERMISSION_CLASSES选项的很好的替代。

尽管如此,通过依赖于DEFAULT_PERMISSION_CLASSES选项,您给了项目中的每个 REST 服务相同的访问权限。如果想为一两个服务提供更灵活或更严格的权限策略呢?REST 框架还支持在单个 REST 服务上指定更细粒度的权限,使用表 12-1 中的相同类。

清单 12-12 展示了清单 12-4 中 REST 服务的一个修改版本,它使用@permission_classes decorator 来指定一个不同于全局DEFAULT_PERMISSION_CLASSES选项的权限策略。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework.decorators import api_view, permission_classes

from rest_framework.permissions import IsAuthenticated

from rest_framework.response import Response

@api_view(['GET','POST','DELETE'])

@permission_classes((IsAuthenticated, ))

def rest_store(request):
    if request.method == 'GET':
        stores = Store.objects.all()
        serializer = StoreSerializer(stores, many=True)
        return Response(serializer.data)

Listing 12-12.Django view method decorated with Django REST framework and @permission_classes decorator

在清单 12-12 中可以看到,标准视图方法除了用@api_view修饰外,还用@permission_classes修饰。在这种情况下,@permission_classes装饰器被设置为IsAuthenticated类值,确保这个 REST 服务权限策略优先于DEFAULT_PERMISSION_CLASSES选项中的默认服务权限。

清单 12-13 展示了清单 12-9 中 REST 服务的一个修改版本,它使用 permission_classes 字段来指定一个不同于全局DEFAULT_PERMISSION_CLASSES选项的权限策略。

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer

from rest_framework.permissions import IsAuthenticated

from rest_framework import viewsets

class StoreViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    queryset = Store.objects.all()
    serializer_class = StoreSerializer

Listing 12-13.Django viewset class in Django REST framework and permission_classes field

正如您在清单 12-13 中看到的,REST 框架视图集方法利用了permission_classes字段。在这种情况下,permission_classes字段用IsAuthenticated类值设置,确保这个 REST 服务权限策略优先于DEFAULT_PERMISSION_CLASSES选项中的默认服务权限。

设置 REST 框架登录页面

默认情况下,如果用户试图通过浏览器访问 REST 框架,并且没有必要的权限,那么用户会看到一个警告页面,如图 12-5 所示。

A441241_1_En_12_Fig5_HTML.jpg

图 12-5。

Django REST framework access denied page

尽管图 12-5 代表了一个标准的拒绝访问页面,但它有一个明显的遗漏:这是一个没有链接让用户登录的死胡同。如果用户到达图 12-5 中的页面,他需要手动进入应用或 Django 管理中的 Django 登录页面,提供他的凭证,然后返回 REST 服务来访问它。

最后一个工作流给用户造成了不必要的负担,这就是为什么 REST 框架提供了一种集成登录页面的简单方法——包括所有页面上的登录/注销链接——它直接绑定到 Django admin 的相同认证后端。

清单 12-14 展示了 Django 项目的 main urls.py,它声明了 REST 框架的 URL 以自动激活其内置的登录页面和链接。

from django.conf.urls import include, url
urlpatterns = [
    url(r'^rest-auth/', include('rest_framework.urls',namespace='rest_framework')),
]
Listing 12-14.Django REST framework url declaration

to enable log in

在清单 12-14 中,你可以看到rest_framework.urls配置了 Django 的标准include()语句和namespace参数,此外还配置了rest-auth/ url,最后一个可以更改为你选择的任何 url 模式。

通过添加到 Django 项目的主urls.py文件中,所有 REST 框架页面都在右上角生成了一个登录链接,该链接将用户带到 url 配置的登录路径下的一个可访问的登录页面(例如,如果是url(r'^rest-auth/'),则登录页面在/rest-auth/login/可用)。

Enhanced Rest Framework User Interface Options

尽管 REST 框架提供的内置用户界面(UI)提供了比呈现原始数据 REST 服务输出更好的选择,如图 12-3 到 12-5 所示,如图 12-1 和 12-2 所示,但与更现代的 UI web 布局相比,REST 框架 UI 仍然相当初级。

REST 框架 UI 有多种替代方式,它们是专门为使用 REST 框架而设计的。一些比较成熟的项目包括 Django REST Swagger8和 DRF Docs。 9

Footnotes 1

https://en.wikipedia.org/wiki/Representational_state_transfer

  2

https://www.ampproject.org/

  3

http://www.django-rest-framework.org/

  4

http://tastypieapi.org/

  5

https://djangopackages.org/grids/g/rest/

  6

https://djangopackages.org/grids/g/search/

  7

https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods

  8

http://marcgibbons.github.io/django-rest-swagger/

  9

http://drfdocs.com/