掌握Django(六)

本文介绍Django的管理站点的配置. 通过配置, 可以打造一个功能完善的管理网站. 包括模型的注册、自定义列表页面、添加计算列、加载关联模型、自定义并覆盖基本查询集、添加搜索框、添加过滤栏、链接到其他页面、添加自定义操作、自定义表单、显示子项、添加数据验证、添加通用的关联, 以及介绍如何扩展可插拨应用. 内容丰富基本函盖Django管理站点的所有知识点.


设置Django管理站点

Django管理站点需要加后缀admin

http://127.0.0.1:8000/admin

为了能管理网站还要创建超用户, 以及设置密码

python manage.py createsuperuser

python manage.py changepassword admin

修改管理站点标题, 进入 storefront–urls.py, 添加:

admin.site.site_header = ‘Storefront Admin’

admin.site.index_title = ‘Admin’


注册模型

每个应用都有一个admin模块: admin.py, 这是自定义应用程序管理面板的地方.

首先注册模型 以使在管理站点可以管理. 先注册 Collection

store–admin.py, 添加:

from . improt models

admin.site.register(models.Collection)

再修改对象的显示字符串,避免显示 object(n) 这样的难懂的.

store–models.py, 修改 Collectiong类:

def __str__(self):

    return self.title

class Meta:

    ordering = [‘title’]

同样的, Product模型也作同样的注册和显示. 这里不重复了.


自定义列表页面

前面的列表页只有一列, 显示对象的方法 __str__ 返回的字符串, 很多时候我们希望能显示多列, 并且有些列可以在列表页面进行修改.

store–admin.py, 添加:

class ProductAdmin(admin.ModelAdmin):

    list_display = [‘title’, ‘unit_price’]

我们添加一个新管理类ProductAdmin, 按照约定一般命名为 “类名+Admin”, 我们设置要显示的列为 title 和 unit_price.

再修改注册:

admin.site.register(models.Product, ProductAdmin)

将管理类也注册进去.

还有一种更简短的表示方法.

@admin.register(models.Product)

class ProductAdmin(admin.ModelAdmin):

    list_display = [‘title’, ‘unit_price’]

除了list_display属性, 还有其他

list_editable = [‘unit_price’]

list_per_page = 10

list_editable 表示可编辑的列, list_per_page表示每页显示的数量.

可以搜索google关键字 “Django ModelAdmin”查看完整选项.

类似地, 对Customer的列表页自定义:

  • 有三列显示,为 first_name, last_name, membership
  • 可以更改会员资格 membership
  • 每页显示10个, 默认排序是 first_name, 第二列排序是 last_name.

很简单, Customer管理页面的修改代码就不列了.


添加计算列

在Product列表页面, 我们添加一列显示库存状态, 当小于10时, 显示 Low.

class ProductAdmin(admin.ModelAdmin):

    list_display = [‘title’, ‘unit_price’, ‘inventory_status’]

    …

    @admin.display(ordering=’inventory’)

    def inventory_status(self, product):

        if product.inventory<10:

            return ‘Low’

        return ‘OK’

我们添加了一列 inventory_status, 该列是类的方法名称, 根据该方法计算返回结果, 注意该方法的第2个参数为product.

另外, 由于该列是计算列, 加上修饰符  @admin.display(ordering=’inventory’)  表示在列表页面可以点该列标题通过 inventory 进行排序


加载关联对象

我们希望在Product列表页面添加一列显示Collection, 直接添加 collection 就好.

class ProductAdmin(admin.ModelAdmin):

    list_display = [‘title’, ‘unit_price’, ‘inventory_status’, ‘collection’]

    …

Django自动关联collection, 并用collection的title 显示, 因为在前面,Collection已重写了 __str__ 方法.

如果要显示关联对象的其他字段的话, 以前文章讲的加双划线方法如 …,’collection__title’ 是不可以的. 可以通过上一节添加计算列的方法:

… 

list_display = […,’collection_title’]

list_select_related = [‘collection’]

def collection_title(self, product):

    return product.collection.title

这里的 list_select_related_related属性不能省略, 否则每一行都会向数据库发起请求, 大大降低性能.

我们再来做自定义 order 列表, 可以在列表页面看到订单的客户.

store–admin.py添加:

class OrderAdmin(admin.ModelAdmin):

    list_display = [‘id’, ‘placed_at’, ‘customer’]

    …

store–models.py 修改 Customer类:

class Customer(models.Model):

    def __str__(self):

        return f'{self.first_name}  {self.last_name}’

    class Meta:

        ordering = [‘first_name’, ‘last_name’]


覆盖基本查询集

我们在Collection列表页面添加产品数量的列, 修改CollectionAdmin类:

@admin.register(models.Collection)

class CollectionAdmin(admin.ModelAdmin):

    list_display = [‘title’, ‘products_count’]

添加的 products_count 列用计算列的方式:

    …

    def products_count(self, collection):

        return collection.products_count

    …

显然, collection没有products_count列, 我们可以在基本查询集上增加一列. 覆盖原先的查询集.

    …

    def get_queryset(self, request):

        return super().get_queryset(request).annotate(products_count=Count(‘product’))

    …

每个模型都有一个 get_queryset 方法, 可以覆盖修改, 这里我们增加了products_count列. 注意要导入Count类:

from django.db.models.aggregates import Count

另外增加的这列不能在列表页面点标头排序, 可以在计算列上增加排序字段:

    …

    @admin.display(ordering=’products_count’)

    def products_count(self, collection):

        …


链接到其他页面

如果我们希望在点击Collection列表页面的products_count列可以链接到具体的产品, 这也是很常见的操作, 该如何做呢?

这个需要格式化 html ,先导入 format_html:

from django.utils.html improt format_html

再修改Collection页面的计算列 products_count:

    …

    def products_count(self, collection):

        return format_html(‘<a>{}</a>’, collection.products_count)

format_html函数的第一个参数里的 {} 是占位符, 后面的参数代入.

我们先让他链接到百度看看是否正常.

        …

        return format_html(‘<a href=”https://www.baidu.com”>{}</a>’, collection.products_count)

        …

刷新页面, 点击products_count列的某一值已能链接到百度, 下面我们修改使之能链到Product列表页面, 需要用到reverse 函数. 先导入:

from django.urls import reverse

然后, 修改计算列 products_count:

    …

    def products_count(self, collection):

        return format_html(‘<a href=”{}”>{}</a>’, reverse(‘admin:store_product_changelist’), collection.products_count)

这是Django的格式, admin:应用_模型_changelist.

刷新页面, 点击products_count列的某一值已能链接Product列表页, 但它没有筛选, 我们要加上. url查询筛选是“ ?key=value”, 所以先加个问号 ? ,然后再加筛选参数, 构造筛选参数需要用到 urlencode, 先导入:

from django.utils.html import urlencode

然后修改成这样:

    …

def products_count(self, collection):

    url = reverse(‘admin:store_product_changelist’)+’?’ + \

        urlencode({‘collection__id’: str(collection.id)})

    return format_html(‘<a href=”{}”>{}</a>’, url, collection.products_count)

练习:

在Customer列表页面添加 orders_count, 以查看每个客户的订单.


添加搜索到列表页面

添加搜索到Customer列表页面.

    … 

    search_fields = [‘first_name’, ‘last_name’]

列表页面出现搜索框, 键入的搜索内容在这两个字段中查找是否有匹配. 也可以查询以什么开头.

    search_fields = [‘first_name__startswith’, ‘last_name__istartswith’]

XXX__startswith 表示以什么开头, 而 XXX___istartswith 加上 i 表示不区分大小写.


添加过滤列表页面

我们在Product列表页面添加过滤器, 很简单, 只需在管理类加上list_filter属性即可.

    …

    list_filter = [‘collection’, ‘last_update’]

也可以创建自定义的过滤器.

class InventoryFilter(admin.SimpleListFilter):

    title = ‘inventory’  # 过滤标题

    parameter_name = ‘inventory’  # 在地址栏上显示的查询参数,也可以设成其他任何词    

    def lookups(self, request, model_admin):

        return [(‘<10’, ‘Low’)]

    # 显示的过滤条目, 每个列表项是一个元组,对应filter条目的值和显示项

    def queryset(self, request, queryset):

    # 实际过滤的逻辑

        if self.value() == ‘<10’:

            return queryset.filter(inventory__lt=10)

然后, 在 ProductAdmin类的 list_filter属性中加入 InventoryFilter

    …

    list_filter = [‘collection’, ‘last_update’, InventoryFilter]


创建定制的操作

我们可以创建定制的操作, 比如创建一个批量设置所有Product 库存为 0 的操作. 在 ProductAdmin类中添加方法:

    …

    @admin.action(description=’clear inventory’)

    def clear_inventory(self, request, queryset):

        update_count = queryset.update(inventory=0)

        self.message_user(

            request, f'{update_count} products were successfully updated.’)

再在ProductAdmin类中添加actions属性:

    …   

    actions = [‘clear_inventory’]

刷新网页, 就可以在顶部操作区看到 clear_inventory 操作了.

每一个管理类都有message_user方法, 用于显示提示消息. 前面我们用了两个参数, 还可以设置第三个参数, 表示消息类型.

先导入 message:

from django.contrib import admin, messages

然后添加第3个参数:messages.ERROR

    …

    self.message_user(

    request, f'{update_count} products were successfully updated.’, messages.ERROR)

这样提示消息就变为红叉的错误警示.


自定义表单

添加一条记录, 需要用到表单. 我们是可以自定义表单的.

以Product为例, 我们在ProductAdmin类中添加:

    …

    fields = [‘title’, ‘slug’]

    # 设定显示的列

    exclude = [‘promotions’]

    # 排除某些列

    # readonly_fields = [‘title’]

    # 只读字段

    prepopulated_fields = {

        # 表示是预填,这里是用 title预填slug

        ‘slug’: [‘title’],

        }

下拉列表框太长的话, 是不好选的. 有些时候改成自动完成字段比较好.  对Product表单, collection是默认用下拉列表框的, 我们在ProductAdmin添加一行:

    …

    autocomplete_fields = [‘collection’]

另外, 还需要在CollectionAdmin类里添加 search_fields:

    …

    search_fields = [‘title’]

刷新网页, 添加一个Product记录, 在collection框里输入会自动完成.

详细查看, 可以谷歌搜索关键字 “Django ModelAdmin”—Admin actions

练习:

修改Order表单, 只显示 payment_status 和 customer,不显示 placed_at, 因为Django会自动添加的. 将 customer 改成自动完成.


添加数据验证

我们在定义模型时, 已有基本的验证, 比如不能为空等. 我们在新增一条记录时, 比如Product时, 页面会提示哪些不能为空等信息. 我们将Product模型的description修改下,添加null=True.

description = models.TextField(null=True)

然后再migrate到数据库:

python manage.py makemigrations

python manage.py migrate

再进行新增一条 Product记录, 在 description框为空时, Django仍然提示不能为空. 为什么?

null=true 只是数据库的验证允许为空, 页面还是会验证不能为空, 所以还得加上 blank=True , 这样页面才允许description为空.

description = models.TextField(null=True, blank=True)

我们添加Product时, unit_price 设置为0 甚至负数 -1 时, 页面也不会提示错误, 我们需要添加数据验证. 这里用到的是MinValueValidator.

from django.core.validators import MinValueValidator

    unit_price = models.DecimalField(

        max_digits=6,

        decimal_places=2,

        validators=[MinValueValidator(1)]

    )

MinValueValidator还有第2个参数message, 用于提示信息,这里暂不用.

搜索谷歌关键字 “django validators”, 可以查到很多实用的验证模块方法.

练习:

Product的 inventory 作同样设置, promotions 加上 blank=True, 页面编辑时可选.


编辑子项用Inlines

目前OrderItem未管理, 它属于Order的子项. 可以在OrderAdmin里添加 inlines属性进行管理.

具体做法是:

先在admin.py里创建TabularInline或StackedInline的子类.

class OrderItemInline(admin.TabularInline):

    model = models.OrderItem

TabularInline表示表格内联, 然后在 OrderAdmin类添加属性inlines.

    …

    inlines = [OrderItemInline]

    …

OrderItemInline 间接继承于 ModelAdmin, 所以ModelAdmin的属性也可以用. 所以我们可以添加给OrderItemInline添加一个属性:

    autocomplete_fields = [‘product’]

默认的内联表格位占符是3个,  有点丑, 我们可以取消掉, 设为0 即可. 另外也可以设置 子项的数量. 如下:

class OrderItemInline(admin.TabularInline):

    model = models.OrderItem

    autocomplete_fields = [‘product’]

    extra = 0  # 占位符

    min_num = 1 # 界面上子项最小数量

    max_num = 10  # 界面上子项最大数量

顺便可以试一下让OrderItemInline 继承 admin.StackedInline 看看效果, 说实话, 这个界面本人接受不了: ) 一般就用TabularInline就好.


使用通用关联

storefront 项目有通用的 tags应用. 我们要在产品中增加一个 tag 管理, 该如何做?

先在 tags–admin.py里注册 Tag

from .models import Tag

admin.site.register(Tag)

在tags—models.py里添加 Tag 的 __str__ 方法:

def __str__(self):

    return self.label

回到store应用, admin.py 添加 TagInline类:

from django.contrib.contenttypes.admin import GenericTabularInline

from tags.models import TaggedItem

class TagInline(GenericTabularInline):

    model = TaggedItem

在ProductAdmin类中添加 inlines 属性:

    …

    inlines = [TagInline]

类似地, 在TagInline类中, 添加自动完成列

    …

    autocomplete_fields = [‘tag’]

    …

再回到 tags 应用创建TagAdmin 类, 设置 search_fields 

@admin.register(Tag)

class TagAdmin(admin.ModelAdmin):

    search_fields = [‘label’]


扩展可插拨应用

上节 store 应用需要导入TaggedItem, 对 tags应用产生了依赖. 而store 与 tags 是互相独立的应用, 不应该有此依赖. 我们需要进行解耦.

可以创建一个独立的应用, 比如命名为 store_custom, 它知道 store 和 TaggedItem, 也仅用于 store ,其他应用不会用.

python manage.py startapp store_custom

再进入 store_custom的 admin.py, 将原在 store–admin.py 的 TagInline剪切过来.

class TagInline(GenericTabularInline):

    model = TaggedItem

    autocomplete_fields = [‘tag’]

创建类 CustomProductAdmin, 继承于ProductAdmin

class CustomProductAdmin(ProductAdmin):

inlines = [TagInline]

再重新注册到 CustomProductAdmin

admin.site.unregister(Product)

admin.site.register(Product, CustomProductAdmin)

最后一步:

在项目的 storefront — settings.py的 INSTALL_APPS 添加 ‘store_custom’

当然别忘在 ProductAdmin 中删除 inlines = [TagInline], 同时删除 import TaggedItem ,使之不再依赖于 tags 应用.

最重要的一点是,  如果我们在 INSTALL_APPS 删除 store_custom, 则 Product 表单就不显示 TagInline 表单, 加上则显示. 完全解耦, 非常漂亮!!


小结

本文介绍Django的管理站点的配置.. 内容基本函盖Django管理站点的所有知识点.

下一篇开始介绍Django中创建RESTful API的知识.

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注