9. Admin后台系统
Admin后台系统也称为网站后台管理系统 , 主要对网站的信息进行管理 ,
如文字 , 图片 , 影音和其他日常使用的文件的发布 , 更新 , 删除等操作 ,
也包括功能信息的统计和管理 , 如用户信息 , 订单信息和访客信息等 .
简单来说 , 它是对网站数据库和文件进行快速操作和管理的系统 , 以使网页内容能够及时得到更新和调整 .
9.1 走进Admin
当一个网站上线之后 , 网站管理员通过网站后台系统对网站进行管理和维护 .
Django已内置Admin后台系统 , 在创建Django项目的时候 ,
可以从配置文件settings . py中看到项目已默认启用Admin后台系统 , 如图 9 - 1 所示 .
图 9 - 1 Admin配置信息
从图 9 - 1 中看到 , 在INSTALLED_APPS中已配置了Admin后台系统 ,
如果网站不需要Admin后台系统 , 就可以将配置信息删除 , 这样可以减少程序对系统资源的占用 .
此外 , 在MyDjango的urls . py中也可以看到Admin后台系统的路由信息 ,
只要运行MyDjango并在浏览器上输入 : 127.0 .0 .1 : 8000 /admin , 就能访问Admin后台系统 , 如图 9 - 2 所示 .
图 9 - 2 Admin登录页面
在访问Admin后台系统时 , 需要用户的账号和密码才能登录后台管理页面 .
创建用户的账号和密码之前 , 必须确保项目已执行数据迁移 , 在数据库中已创建相应的数据表 .
以MyDjango项目为例 , 项目的数据表如图 9 - 3 所示 .
python manage. py makemigrations
python manage. py migrate
图 9 - 3 数据表信息
如果Admin后台系统以英文的形式显示 , 那么我们还需要在项目的settings . py中设置中间件MIDDLEWARE , 将后台内容以中文形式显示 .
添加的中间件是有先后顺序的 , 具体可回顾 2.5 节 , 如图 9 - 4 所示 .
'django.middleware.locale.LocaleMiddleware' ,
图 9 - 4 设置中文显示
完成上述设置后 , 下一步创建超级管理员的账号和密码 , 创建方法由Django的内置指令createsuperuser完成 .
在PyCharm的Terminal模式下输入创建指令 , 代码如下 :
D: \MyDjango> python manage. py createsuperuser
Username ( leave blank to use 'blue' ) : admin
Email address:
Password: 123456
Password ( again) : 123456
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [ y/ N] : y
Superuser created successfully.
在创建用户时 , 用户名和邮箱地址可以为空 , 如果用户名为空 , 就默认使用计算机的用户名 ,
而设置用户密码时 , 输入的密码不会显示在屏幕上 .
如果密码过短 , Django就会提示密码过短并提示是否继续创建 .
若输入 'Y' , 则强制创建用户 ; 若输入 'N' , 则重新输入密码 .
完成用户创建后 , 打开数据表auth_user可以看到新增了一条用户信息 , 如图 9 - 5 所示 .
图 9 - 5 数据表auth_user
在浏览器上再次访问Admin的路由地址 , 在登录页面上使用刚刚创建的账号和密码登录 , 即可进入Admin后台系统 , 如图 9 - 6 所示 .
图 9 - 6 Admin后台系统
在Admin后台系统中可以看到 , 网页布局分为站点管理 , 认证和授权 , 用户和组 , 分别说明如下 :
( 1 ) 站点管理是整个Admin后台的主体页面 , 整个项目的App所定义的模型都会在此页面显示 .
( 2 ) 认证和授权是Django内置的用户认证系统 , 包括用户信息 , 权限管理和用户组设置等功能 .
( 3 ) 用户和组是认证和授权所定义的模型 , 分别对应数据表auth_user和auth_user_groups .
在MyDjango中 , 项目应用index定义模型PersonInfo和Vocation , 分别对应数据表index_personinfo和index_vocation .
from django. db import models
class PersonInfo ( models. Model) :
id = models. AutoField( primary_key= True )
name = models. CharField( max_length= 20 )
age = models. IntegerField( )
def __str__ ( self) :
return str ( self. name)
class Meta :
verbose_name = '人员信息'
class Vocation ( models. Model) :
id = models. AutoField( primary_key= True )
job = models. CharField( max_length= 20 )
title = models. CharField( max_length= 20 )
salary = models. DecimalField( max_digits= 10 , decimal_places= 2 )
person_info = models. ForeignKey( PersonInfo, on_delete= models. CASCADE)
def __str__ ( self) :
return str ( self. id )
class Meta :
verbose_name = '职业信息'
python manage. py makemigrations
python manage. py migrate
若想将index定义的模型展示在Admin后台系统中 , 则需要在index的admin . py中编写相关代码 , 以模型PersonInfo为例 , 代码如下 :
from django. contrib import admin
from . models import *
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
上述代码使用两种方法将数据表index_personinfo注册到Admin后台系统 : 方法一是基本的注册方式 ; 方法二是通过类的继承方式实现注册 .
日常开发普遍采用第二种方法实现 , 实现过程如下 :
( 1 ) 自定义PersonInfoAdmin类 , 使其继承ModelAdmin . ModelAdmin用于设置模型如何展现在Admin后台系统里 .
( 2 ) 将PersonInfoAdmin类注册到Admin后台系统有两种方法 , 两者是将模型PersonInfo和PersonInfoAdmin类绑定并注册到Admin后台系统 .
当刷新Admin后台系统页面 , 看到站点管理出现INDEX , 就代表项目应用index ;
INDEX下的 '人员信息s' 代表模型PersonInfo , 它对应数据表index_personinfo , 如图 9 - 7 所示 .
图 9 - 7 Admin后台系统
单击 '人员信息s' , 浏览器将访问模型PersonInfo的数据列表页 , 模型PersonInfo的所有数据以分页的形式显示 , 每页显示 100 行数据 ;
数据列表页还设置了新增数据 , 修改数据和删除数据的功能,如图 9 - 8 所示。
图 9 - 8 数据列表项
若想在模型PersonInfo里新增数据 , 则可单击模型PersonInfo的数据列表页 '增加人员信息' 或表格信息栏的 '增加' 按钮 ,
浏览器就会进入数据新增页面 , 用户在此页面添加数据并保存即可 , 如图 9 - 9 所示 .
图 9 - 9 数据新增页面
在模型PersonInfo的数据列表页里 , 每行数据的ID字段都设有路由地址 , 单击某行数据的ID字段 ,
浏览器就会进入当前数据的修改页面 , 用户在此页面修改数据并保存即可 , 如图 9 - 10 所示 .
图 9 - 10 数据修改页面
若想在模型PersonInfo里删除数据 , 则可在数据列表页勾选需要删除的记录 ,
然后选择执行的动作为 '删除所有勾选的人员信息s' , 再点击执行 , 这是会进入确认删除页面 , 如图 9 - 11 所示 .
图 9 - 11 数据删除页面
9.2 源码分析ModelAdmin
简单了解Admin后台系统的网页布局后 , 接下来深入了解ModelAdmin的定义过程 , 在PyCharm里打开ModelAdmin的源码文件 , 如图 9 - 12 所示 .
图 9 - 12 ModelAdmin的源码文件
从图 9 - 12 看到 , ModelAdmin继承BaseModelAdmin , 而父类BaseModelAdmin的元类为MediaDefiningClass ,
因此Admin系统的属性和方法来自ModelAdmin和BaseModelAdmin .
由于定义的属性和方法较多 , 因此这里只说明日常开发中常用的属性和方法 .
● fields : 由BaseModelAdmin定义 , 格式为列表或元组 , 在新增或修改模型数据时 , 设置可编辑的字段 .
● exclude : 由BaseModelAdmin定义 , 格式为列表或元组 , 在新增或修改模型数据时 , 隐藏字段 , 使字段不可编辑 ,
同一个字段不能与fields共同使用 , 否则提示异常 .
● fieldsets : 由BaseModelAdmin定义 , 格式为两元的列表或元组 ( 列表或元组的嵌套使用 ) , 改变新增或修改页面的网页布局 ,
不能与fields和exclude共同使用 , 否则提示异常 .
● radio_fields : 由BaseModelAdmin定义 , 格式为字典 , 如果新增或修改的字段数据以下拉框的形式展示 ,
那么该属性可将下拉框改为单选按钮 .
● readonly_fields : 由BaseModelAdmin定义 , 格式为列表或元组 , 在数据新增或修改的页面设置只读的字段 , 使字段不可编辑 .
● ordering : 由BaseModelAdmin定义 , 格式为列表或元组 , 设置排序方式 , 比如以字段id排序 , [ 'id' ] 为升序 , [ '-id' ] 为降序 .
● sortable_by : 由BaseModelAdmin定义 , 格式为列表或元组 , 设置数据列表页的字段是否可排序显示 ,
比如数据列表页显示模型字段id , name和age , 如果单击字段name , 数据就以字段name进行升序 ( 降序 ) 排列 ,
该属性可以设置某些字段是否具有排序功能 .
● formfield_for_choice_field ( ) : 由BaseModelAdmin定义 , 如果模型字段设置choices属性 ,
那么重写此方法可以更改或过滤模型字段的属性choices的值 .
● formfield_for_foreignkey ( ) : 由BaseModelAdmin定义 , 如果模型字段为外键字段 ( 一对一关系或一对多关系 ) ,
那么重写此方法可以更改或过滤模型字段的可选值 ( 下拉框的数据 ) .
● formfield_for_manytomany ( ) : 由BaseModelAdmin定义 , 如果模型字段为外键字段 ( 多对多关系 ) ,
那么重写此方法可以更改或过滤模型字段的可选值 .
● get_queryset ( ) : 由BaseModelAdmin定义 , 重写此方法可自定义数据的查询方式
● get_readonly_fields ( ) : 由BaseModelAdmin定义 , 重写此方法可自定义模型字段的只读属性 ,
比如根据不同的用户角色来设置模型字段的只读属性 .
● list_display : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页设置显示在页面的模型字段 .
● list_display_links : 由ModelAdmin定义 , 格式为列表或元组 , 为模型字段设置路由地址 , 由该路由地址进入数据修改页 .
● list_filter : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页的右侧添加过滤器 , 用于筛选和查找数据 .
● list_per_page : 由ModelAdmin定义 , 格式为整数类型 , 默认值为 100 , 在数据列表页设置每一页显示的数据量 .
● list_max_show_all : 由ModelAdmin定义 , 格式为整数类型 , 默认值为 200 , 在数据列表页设置每一页显示最大上限的数据量 .
● list_editable : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页设置字段的编辑状态 ,
可以在数据列表页直接修改某行数据的字段内容并保存 , 该属性不能与list_display_links共存 , 否则提示异常信息 .
● search_fields : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页的搜索框设置搜索字段 , 根据搜索字段可快速查找相应的数据 .
● date_hierarchy : 由ModelAdmin定义 , 格式为字符类型 , 在数据列表页设置日期选择器 , 只能设置日期类型的模型字段 .
● save_as : 由ModelAdmin定义 , 格式为布尔型 , 默认为False , 若改为True , 则在数据修改页添加 '另存为' 功能按钮 .
● actions : 由ModelAdmin定义 , 格式为列表或元组 , 列表或元组的元素为自定义函数 , 函数在 '动作' 栏生成操作列表 .
● actions_on_top和actions_on_bottom : 由ModelAdmin定义 , 格式为布尔型 , 设置 '动作' 栏的位置 .
● save_model ( ) : 由ModelAdmin定义 , 重写此方法可自定义数据的保存方式 .
● delete_model ( ) : 由ModelAdmin定义 , 重写此方法可自定义数据的删除方式 .
为了更好地说明ModelAdmin的属性功能 , 以MyDjango为例 , 在index的admin . py里定义VocationAdmin .
在定义VocationAdmin之前 , 我们需要将模型Vocation进行重新定义 , 代码如下 :
from django. db import models
class PersonInfo ( models. Model) :
id = models. AutoField( primary_key= True )
name = models. CharField( max_length= 20 )
age = models. IntegerField( )
def __str__ ( self) :
return str ( self. name)
class Meta :
verbose_name = '人员信息'
class Vocation ( models. Model) :
JOB = (
( '软件开发' , '软件开发' ) ,
( '软件测试' , '软件测试' ) ,
( '需求分析' , '需求分析' ) ,
( '项目管理' , '项目管理' ) ,
)
id = models. AutoField( primary_key= True )
job = models. CharField( max_length= 20 , choices= JOB)
title = models. CharField( max_length= 20 )
salary = models. IntegerField( null= True , blank= True )
person_info = models. ForeignKey( PersonInfo, on_delete= models. CASCADE)
record_time = models. DateField( auto_now= True , null= True , blank= True )
def __str__ ( self) :
return str ( self. id )
class Meta :
verbose_name = '职业信息'
JOB表量的内部元组中的第一个值是标签的value值 ( 提交的数据 ) , 第二个值是被html标签包的值 ( 页面展示 ) .
模型Vocation重新定义后 , 在PyCharm的Terminal窗口下执行数据迁移 ,
并在数据表index_personinfo和index_vocation中添加数据 , 如图 9 - 13 所示 .
( 前面使用了index_personinfo表 , 如果有数据自己清空 , 后续添加数据注意自增id . . . )
INSERT INTO index_personinfo VALUES
( 1 , '张三' , 26 ) ,
( 2 , '李四' , 23 ) ,
( 3 , '王五' , 28 ) ,
( 4 , '赵六' , 30 ) ;
INSERT INTO index_vocation VALUES
( 1 , '软件开发' , 'Python开发' , 2 , '2019-01-02' , 10000 ) ,
( 2 , '软件测试' , '自动化测试' , 3 , '2019-03-20' , 8000 ) ,
( 3 , '需求分析' , '需求分析' , 1 , '2019-02-02' , 6000 ) ,
( 4 , '项目管理' , '项目经理' , 4 , '2019-04-04' , 12000 ) ;
图 9 - 12 数据表index_personinfo和index_vocation
完成模型Vocation的定义与数据迁移后 , 下一步在admin . py里定义VocationAdmin , 使模型Vocation的数据显示在Admin后台系统 .
VocationAdmin的定义如下 :
from django. contrib import admin
from . models import *
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
fieldsets = (
( '职业信息' , {
'fields' : ( 'job' , 'title' , 'salary' )
} ) ,
( '人员信息' , {
'classes' : ( 'collapse' , ) ,
'fields' : ( 'person_info' , ) ,
} ) ,
)
radio_fields = { 'person_info' : admin. HORIZONTAL}
readonly_fields = [ 'job' , ]
ordering = [ 'id' ]
sortable_by = [ 'job' , 'title' ]
list_display = [ 'id' , 'job' , 'title' , 'salary' , 'person_info' ]
list_filter = [ 'job' , 'title' , 'person_info__name' ]
list_per_page = 100
list_max_show_all = 200
list_editable = [ 'job' , 'title' ]
search_fields = [ 'job' , 'title' ]
date_hierarchy = 'record_time'
save_as = True
actions_on_top = False
actions_on_bottom = True
VocationAdmin演示了如何使用ModelAdmin的常用属性 .
运行MyDjango项目 , 在浏览器上访问模型Vocation的数据列表页 , 页面的样式和布局变化如图 9 - 14 所示 .
图 9 - 14 模型Vocation的数据列表页
模型Vocation的数据列表页里单击某行数据的ID字段 , 由ID字段的链接进入模型Vocation的数据修改页 ,
该页面的样式和布局的变化情况与数据新增页有相同之处 , 如图 9 - 15 所示 .
图 9 - 15 模型Vocation的数据修改页
最后在模型Vocation的数据列表页的右上方找到并单击 '增加职业信息' ,
浏览器将访问模型Vocation的数据新增页 , 该页面的样式和布局的变化情况如图 9 - 16 所示 .
图 9 - 16 模型Vocation的数据新增页
对比模型PersonInfo与模型Vocation的Admin后台页面发现 ,
ModelAdmin的属性主要设置Admin后台页面的样式和布局 , 使模型数据以特定的形式展示在Admin后台系统 .
而在 9.4 节 , 我们将会讲述如何重写ModelAdmin的方法 , 实现Admin后台系统的二次开发 .
9.3 Admin首页设置
我们将模型PersonInfo和模型Vocation成功展现在Admin后台系统 , 其中Admin首页的INDEX代表项目应用的名称 ,
但对一个不会网站开发的使用者来说 , 可能无法理解INDEX的含义 , 而且使用英文表示会影响整个网页的美观 .
若想将Admin首页的INDEX改为中文内容 , 则在项目应用的初始化文件__init__ . py中设置即可 ,
以MyDjango的index为例 , 在index的__init__ . py中编写以下代码 :
from django. apps import AppConfig
import os
default_app_config = 'index.IndexConfig'
def get_current_app_name ( _file) :
return os. path. split( os. path. dirname( _file) ) [ - 1 ]
class IndexConfig ( AppConfig) :
name = get_current_app_name( __file__)
verbose_name = '网站首页'
上述代码中 , 变量default_app_config指向自定义的IndexConfig类 ,
该类的属性verbose_name用于设置当前项目应用在Admin后台的名称 , 如图 9 - 17 所示 .
图 9 - 17 设置App的后台名称
在Django中 , 当将应用程序添加到INSTALLED_APPS设置时 , 有几种方式来指定该应用程序 .
如果你只想通过应用名称来注册应用 , 并且该应用有一个默认的AppConfig子类 ( 其名称遵循apps . AppConfig的命名模式 ) ,
那么Django会自动加载它
假设有一个名为index的Django应用 , 并且该应用在index / apps . py文件中定义了一个名为IndexConfig的AppConfig子类 :
from django. apps import AppConfig
class IndexConfig ( AppConfig) :
name = 'index'
在这种情况下 , 只需在INSTALLED_APPS中添加应用的名称index , 而不需要指定IndexConfig :
INSTALLED_APPS = [
'myapp' ,
]
Django会自动查找myapp / apps . py中的IndexConfig ( 或任何遵循命名模式的AppConfig ) 并将其作为该应用的配置类 .
如果想要明确地指定使用IndexConfig作为配置类 , 可以在INSTALLED_APPS中这样添加 :
INSTALLED_APPS = [
'index.apps.IndexConfig' ,
]
在这种情况下 , Django将直接加载myapp . apps . IndexConfig作为myapp应用的配置类 .
Django项目的settings . py文件的INSTALLED_APPS中 , 通常只需要列出应用的名称 ( 如 : 'index' ) ,
但如果你想要使用自定义的AppConfig类 , 可以通过点号路径来指定它 .
通常不在 INSTALLED_APPS 中直接指定 AppConfig 的点号路径 , 而是在应用的__init__ . py 文件中设置default_app_config变量 .
想要default_app_config变量生效 , 确保在INSTALLED_APPS设置中 , 应用是这样添加的不带 ".apps.IndexConfig" 后缀 ! ! !
否则他会使用apps中的IndexConfig , 这个类中直接添加 : verbose_name = '网站首页' 也是可行的 .
default_app_config是一个特殊的变量 ,
用于告诉Django当该应用程序被添加到INSTALLED_APPS时 , 应该使用哪个AppConfig子类作为默认配置 .
在这里 , 它被设置为 'index.IndexConfig' , 意味着Django会使用index应用程序中的IndexConfig类作为默认配置 .
__file__ 它表示当前模块 ( 文件 ) 的完整路径 , 目前为 : D : \ MyDjango \ index \ __init__ . py
os . path . dirname ( ) : 它返回指定文件或目录路径的目录名 , D : \ MyDjango \ index \ __init__ . py -- > D : \ MyDjango \ index .
os . path . split ( ) : 返回一个包含两个元素的元组 , 第一个是路径的目录部分 ( 即最后一个目录分隔符之前的所有内容 ) ,
第二个是文件名或子目录名 ( 即最后一个目录分隔符之后的内容 ) .
带 ".apps.IndexConfig" 后缀 , __init__ . py的default_app_config变量是不生效的 ! ! !
当Django加载INSTALLED_APPS列表中的应用程序时 ,
它会查看每个应用程序的__init__ . py文件 , 检查是否存在default_app_config设置 .
如果存在 , Django就会使用指定的AppConfig子类来加载和配置该应用程序 .
如果不存在default_app_config设置 , Django会使用默认的AppConfig
( 如果应用程序遵循Django的命名约定 , 即apps . py文件中有一个名为AppConfig的类 )
从图 9 - 16 看到 , 模型PersonInfo和模型Vocation在Admin后台显示为 '人员信息s' 和 '职业信息s' ,
这是由模型属性Meta的verbose_name设置 , 若想将中文内容的字母s去掉 , 则可以在模型的Meta属性中设置verbose_name_plural ,
以模型PersonInfo为例 , 代码如下 :
from django. db import models
class PersonInfo ( models. Model) :
id = models. AutoField( primary_key= True )
name = models. CharField( max_length= 20 )
age = models. IntegerField( )
def __str__ ( self) :
return str ( self. name)
class Meta :
verbose_name = '人员信息'
verbose_name_plural = '人员信息'
class Vocation ( models. Model) :
JOB = (
( '软件开发' , '软件开发' ) ,
( '软件测试' , '软件测试' ) ,
( '需求分析' , '需求分析' ) ,
( '项目管理' , '项目管理' ) ,
)
id = models. AutoField( primary_key= True )
job = models. CharField( max_length= 20 , choices= JOB)
title = models. CharField( max_length= 20 )
salary = models. IntegerField( null= True , blank= True )
person_info = models. ForeignKey( PersonInfo, on_delete= models. CASCADE)
record_time = models. DateField( auto_now= True , null= True , blank= True )
def __str__ ( self) :
return str ( self. id )
class Meta :
verbose_name = '职业信息'
verbose_name_plural = '职业信息'
如果在模型的Meta属性中分别设置verbose_name和verbose_name_plural ,
Django就优先显示verbose_name_plural的值 .
重新运行MyDjango , 运行结果如图 9 - 18 所示 .
图 9 - 18 设置模型的后台名称
除了在Admin首页设置项目应用和模型的名称之外 , 还可以设置Admin首页的网页标题 ,
实现方法是在项目应用的admin . py中设置Admin的site_title和site_header属性 ,
如果项目有多个项目应用 , 那么只需在某个项目应用的admin . py中设置一次即可 .
以index的admin . py为例 , 设置如下 :
from django. contrib import admin
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
运行MyDjango并访问Admin首页 , 观察网页的标题变化情况 , 如图 9 - 18 所示 .
图 9 - 18 Admin的网页标题
综上所述 , Admin后台系统的首页设置包括 : 项目应用的显示名称 , 模型的显示名称和网页标题 , 三者的设置方式说明如下 :
● 项目应用的显示名称 : 在项目应用的__init__ . py中设置变量default_app_config ,
该变量指向自定义的IndexConfig类 , 由IndexConfig类的verbose_name属性设置项目应用的显示名称 .
● 模型的显示名称 : 在模型属性Meta中设置verbose_name和verbose_name_plural ,
两者的区别在于verbose_name是以复数的形式表示的 , 若在模型中同时设置这两个属性 , 则优先显示verbose_name_plural的值 .
● 网页标题 : 在项目应用的admin . py中设置Admin的site_title和site_header属性 ,
如果项目有多个项目应用 , 那么只需在某个项目应用的admin . py中设置一次即可 .
9.4 Admin的二次开发
我们已经掌握了ModelAdmin的属性设置和Admin的首页设置 , 但是每个网站的功能和需求并不相同 , 这导致Admin后台的功能有所差异 .
因此 , 本节将重写ModelAdmin的方法 , 实现Admin的二次开发 , 从而满足多方面的开发需求 .
为了更好地演示Admin的二次开发所实现的功能 , 以 9.3 节的MyDjango为例 , 在Admin后台系统里创建非超级管理员账号 .
在Admin首页的 '认证和授权' 下单击用户的新增链接 , 设置用户名为root , 密码为mydjango123 ,
用户密码的长度和内容有一定的规范要求 , 如果不符合要求就无法创建用户 , 如图 9 - 20 所示 .
用户创建后 , 浏览器将访问用户修改页面 , 我们需勾选当前用户的职员状态 , 否则新建的用户无法登录Admin后台系统 , 如图 9 - 21 所示 .
图 9 - 20 创建用户
图 9 - 21 设置职员状态
除了设置职员状态之外 , 还需要为当前用户设置相应的访问权限 , 我们将Admin的所有功能的权限都给予root用户 .
如图 9 - 21 所示 , 最后单击 '保存' 按钮 , 完成用户设置 .
图 9 - 22 设置用户权限
9.4.1 函数get_readonly_fields()
已知get_readonly_fields ( ) 是由BaseModelAdmin定义的 , 它获取readonly_fields的属性值 ,
从而将模型字段设为只读属性 , 通过重写此函数可以自定义模型字段的只读属性 , 比如根据不同的用户角色来设置模型字段的只读属性 .
( 根据用户动态为模式设置readonly_fields只读属性的值 . )
以MyDjango为例 , 在VocationAdmin里重写get_readonly_fields ( ) 函数 , 根据当前访问的用户角色设置模型字段的只读属性 , 代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' ]
def get_readonly_fields ( self, request, obj= None ) :
if request. user. is_superuser:
self. readonly_fields = [ ]
else :
self. readonly_fields = [ 'salary' ]
return self. readonly_fields
request . user是一个常用的方式来获取当前请求的用户对象 .
这个对象通常是User模型的一个实例 , 它代表了登录到Django网站的用户 .
request . user . is_superuser是一个布尔值 ( True 或 False ) , 它表示该用户是否是一个超级用户 .
超级用户通常具有网站上的所有权限 , 可以访问和修改所有内容 .
在用户信息设置的权限中勾选了 '超级用户状态' 的用户都是超级用户 .
函数get_readonly_fields首先判断当前发送请求的用户是否为超级管理员 ,
如果符合判断条件 , 就将属性readonly_fields设为空列表 , 使当前用户具有全部字段的编辑权限 ;
如果不符合判断条件 , 就将模型字段salary设为只读状态 , 使当前用户无法编辑模型字段salary ( 只有只读权限 ) .
函数参数request是当前用户的请求对象 , 参数obj是模型对象 , 默认值为None , 代表当前网页为数据新增页 , 否则为数据修改页 .
函数必须设置返回值 , 并且返回值为属性readonly_fields , 否则提示异常信息 .
运行MyDjango , 使用不同的用户角色登录Admin后台系统 , 在模型Vocation的数据新增页或数据修改页看到 ,
不同的用户角色对模型字段salary的操作权限有所不同 , 比如分别切换用户admin和root进行登录 , 查看是否对模型字段salary具有编辑权限 .
现在登入的用户是admin , 是超级用户 , 可以在数据修改中对salary字段进行修改 .
登入root用户 ( 虽然叫root , 可没有勾选超级用户状态 ) , 不可以在数据修改中对salary字段进行修改 .
目前普通用户是可以设置用户权限的 . . .
9.4.2 设置字段样式
在Admin后台系统预览模型Vocation的数据信息时 , 数据列表页所显示的模型字段是由属性list_display设置的 ,
每个字段的数据都来自于数据表 , 并且数据以固定的字体格式显示在网页上 .
若要对某些字段的数据进行特殊处理 , 如设置数据的字体颜色 ,
则以模型Vocation的外键字段person_info为例 , 将该字段的数据设置为不同的颜色 , 实现代码如下 :
from django. db import models
from django. utils. html import format_html
class PersonInfo ( models. Model) :
id = models. AutoField( primary_key= True )
name = models. CharField( max_length= 20 )
age = models. IntegerField( )
def __str__ ( self) :
return str ( self. name)
class Meta :
verbose_name = '人员信息'
verbose_name_plural = '人员信息'
class Vocation ( models. Model) :
JOB = (
( '软件开发' , '软件开发' ) ,
( '软件测试' , '软件测试' ) ,
( '需求分析' , '需求分析' ) ,
( '项目管理' , '项目管理' ) ,
)
id = models. AutoField( primary_key= True )
job = models. CharField( max_length= 20 , choices= JOB)
title = models. CharField( max_length= 20 )
salary = models. IntegerField( null= True , blank= True )
person_info = models. ForeignKey( PersonInfo, on_delete= models. CASCADE)
record_time = models. DateField( auto_now= True , null= True , blank= True )
def __str__ ( self) :
return str ( self. id )
class Meta :
verbose_name = '职业信息'
verbose_name_plural = '职业信息'
def colored_name ( self) :
if '张三' in self. person_info. name:
color_code = 'red'
else :
color_code = 'blue'
return format_html(
'<span style="color: {}">{}</span>' ,
color_code,
self. person_info
)
colored_name. short_description = '带颜色的姓名'
short_description并不是一个通用的Python属性或方法 ,
而是Django为ModelAdmin类或其内联 ( inline ) 的字段定义提供的一个特殊属性 .
这个属性用于自定义在admin页面上显示字段时的简短描述或标题 .
Python的动态性 : Python是一种动态类型的语言 , 它允许你在运行时向对象添加属性 .
函数是Python中的一等公民 , 它们也是对象 , 因此你可以给它们添加属性 .
在模型Vocation的定义过程中 , 我们自定义函数colored_name , 函数实现的功能说明如下 :
( 1 ) 由于模型的外键字段person指向模型PersonInfo , 因此self . person_info . name可以获取模型PersonInfo的字段name .
( 2 ) 通过判断模型字段name的值来设置变量color_code , 如果字段name的值为 '张三' ,那么变量color_code等于red , 否则为blue .
( 3 ) 将变量color_code和模型字段name的值以HTML表示 , 这是设置模型字段name的数据颜色 ,
函数返回值使用Django内置的format_html方法执行HTML转义处理 .
( 4 ) 为函数colored_name设置short_description属性 , 使该函数以字段的形式显示在模型Vocation的数据列表页 .
模型Vocation自定义函数colored_name是作为模型的虚拟字段 , 它在数据表里没有对应的表字段 , 数据由外键字段name提供 .
若将自定义函数colored_name显示在Admin后台系统 , 则可以在VocationAdmin的list_display属性中添加函数colored_name , 代码如下 :
list_display. append( 'colored_name' )
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' ]
list_display. append( 'colored_name' )
def get_readonly_fields ( self, request, obj= None ) :
if request. user. is_superuser:
self. readonly_fields = [ ]
else :
self. readonly_fields = [ 'salary' ]
return self. readonly_fields
运行MyDjango , 在浏览器上访问模型Vocation的数据列表页 , 发现该页面新增 '带颜色的姓名' 字段 , 如图 9 - 23 所示 .
图 9 - 23 新增 '带颜色的姓名' 字段
虚拟字段可以直接定义在admin . py文件中 , 示例如下 :
from django. contrib import admin
from django. utils. html import format_html
from . models import MyModel
class MyModelAdmin ( admin. ModelAdmin) :
list_display = ( 'name' , 'colored_name' )
def colored_name ( self, obj) :
return format_html( '<span style="color: {}">{}</span>' , color_code, obj. name)
colored_name. short_description = '带颜色的姓名'
admin. site. register( MyModel, MyModelAdmin)
9.4.3 函数get_queryset()
函数get_queryset ( ) 用于查询模型的数据信息 , 然后在Admin的数据列表页展示 .
默认情况下 , 该函数执行全表数据查询 , 若要改变数据的查询方式 , 则可重新定义该函数 ,
比如根据不同的用户角色执行不同的数据查询 , 以VocationAdmin为例 , 实现代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' , 'colored_name' ]
def get_queryset ( self, request) :
qs = super ( ) . get_queryset( request)
if request. user. is_superuser:
return qs
else :
return qs. filter ( id__lt= 2 )
分析上述代码可知 , 自定义函数get_queryset的代码说明如下 :
( 1 ) 通过super方法获取父类ModelAdmin的函数get_queryset所生成的模型查询对象 , 该对象用于查询模型Vocation的全部数据 .
( 2 ) 判断当前用户角色 , 如果为超级管理员 , 函数就返回模型Vocation的全部数据 , 否则返回模型字段id小于 2 的数据 .
运行MyDjango , 使用普通用户 ( 9.4 节创建的root用户 ) 登录Admin后台 ,
打开模型Vocation的数据列表页 , 页面上只显示id等于 1 的数据信息 , 如图 9 - 24 所示 .
( 定义 VocationAdmin类并覆盖get_queryset方法时 , 告诉Django admin , 当渲染这个模型的列表页面时 , 应该如何获取数据 .
super ( ) . get_queryset ( request ) 调用父类 ( 即 admin . ModelAdmin ) 中的get_queryset方法 .
默认情况下 , 这个方法会返回模型对应的QuerySet , 该QuerySet包含了模型在数据库中的所有对象 . )
图 9 - 24 模型Vocation的数据列表页
9.4.4 函数formfield_for_foreignkey()
在新增或修改数据的时候 , 如果某个模型字段为外键字段 , 该字段就显示为下拉框控件 , 并且下拉框的数据来自于该字段所指向的另一个模型 .
以模型Vocation的数据新增页为例 , 该模型的外键字段person_info呈现方式如图 9 - 25 所示 .
图 9 - 25 模型Vocation的外键字段person info ( 不会显示下划线 )
如果想要对下拉框的数据实现过滤筛选 , 那么可以对函数formfield_for_foreignkey ( ) 进行重写 ,
如根据用户角色实现数据的过滤筛选 , 以VocationAdmin为例 , 实现代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' , 'colored_name' ]
def formfield_for_foreignkey ( self, db_field, request, ** kwargs) :
if db_field. name == 'person_info' :
if not request. user. is_superuser:
v = Vocation. objects. filter ( id__lt= 2 )
kwargs[ 'queryset' ] = PersonInfo. objects. filter ( id__in= v)
return super ( ) . formfield_for_foreignkey( db_field, request, ** kwargs)
当formfield_for_foreignkey方法被调用时 , Django期望通过kwargs字典中的queryset键来获取QuerySet , 来设置外键值 .
上述代码根据不同的用户角色过滤筛选下拉框的数据内容 , 实现过程如下 :
( 1 ) 参数db_field是模型Vocation的字段对象 , 因为一个模型可以定义多个外键字段 , 所以需要对特定的外键字段进行判断处理 .
( 2 ) 判断当前用户是否为超级管理员 , 参数request是当前用户的请求对象 .
如果当前用户为普通用户 , 就在模型Vocation中查询字段id小于 2 的数据v , 再将数据v作为模型PersonInfo的查询条件 ,
将模型PersonInfo的查询结果传递给参数queryset , 该参数用于设置下拉框的数据 . ( 查询职业id为 { x1 , x2 . . } 的人员信息 ) .
因为外键字段person的数据主要来自模型PersonInfo , 所以参数queryset的值应以模型PersonInfo的查询结果为准 .
( 3 ) 将形参kwargs传递给父类的函数formfield_for_foreignkey ( ) ,
由父类的函数从形参kwargs里获取参数queryset的值 , 从而实现数据的过滤筛选 .
运行MyDjango , 使用普通用户 ( 9.4 节创建的root用户 ) 登录Admin后台 ,
打开模型Vocation的数据新增页或数据修改页 , 外键字段person的数据如图 9 - 26 所示 .
( 修改职业表id为 4 的数据页面中 , persin info字段绑定的是赵六 , 可赵六现在被过滤了 , 就显示为----空值 ,
查看页面受formfield_for_foreignkey ( ) 函数的影响 , 能正常显示外键 . )
图 9 - 26 外键字段person的数据列表
函数formfield_for_foreignkey ( ) 只适用于一对一或一对多的数据关系 , 如果是多对多的数据关系 ,
就可重写函数formfield_for_manytomany ( ) , 两者的重写过程非常相似 , 这里不再重复讲述 .
def formfield_for_manytomany ( self, db_field, request= None , ** kwargs) :
if db_field. name == 'my_m2m_field' :
kwargs[ 'queryset' ] = db_field. queryset. filter ( active= True )
return super ( ) . formfield_for_manytomany( db_field, request, ** kwargs)
9.4.5 函数formfield_for_choice_field()
如果模型字段设置了参数choices , 并且字段类型为CharField , 比如模型Vocation的job字段 ,
在Admin后台系统为模型Vocation新增或修改某行数据的时候 , 模型字段job就以下拉框的形式表示 ,
它根据模型字段的参数choices生成下拉框的数据列表 .
若想改变非外键字段的下拉框数据 , 则可以重写函数formfield_for_choice_field ( ) .
以模型Vocation的字段job为例 , 在Admin后台系统为字段job过滤下拉框数据 , 实现代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' , 'colored_name' ]
def formfield_for_choice_field ( self, db_field, request, ** kwargs) :
if db_field. name == 'job' :
kwargs[ 'choices' ] = ( ( '软件开发' , '软件开发' ) ,
( '软件测试' , '软件测试' ) , )
return super ( ) . formfield_for_choice_field( db_field, request, ** kwargs)
formfield_for_choice_field ( ) 函数设有 3 个参数 , 每个参数说明如下 :
● 参数db_field代表当前模型的字段对象 , 由于一个模型可定义多个字段 , 因此需要对特定的字段进行判断处理 .
● 参数request是当前用户的请求对象 , 可以从该参数获取当前用户的所有信息 .
● 形参 * * kwargs为空字典 , 它可以设置参数widget和choices .
widget是表单字段的小部件 ( 表单字段的参数widget ) , 能够设置字段的CSS样式 ;
choices是模型字段的参数choices , 可以设置字段的下拉框数据 .
自定义函数formfield_for_choice_field ( ) 判断当前模型字段是否为job , 若判断结果为True , 则重新设置形参 * * kwargs的参数choices ,
并且参数choices有固定的数据格式 , 最后调用super方法使函数继承并执行父类的函数formfield_for_choice_field ( ) ,
这样能为模型字段job过滤下拉框数据 .
运行MyDjango , 在Admin后台系统打开模型Vocation的数据新增页或数据修改页 , 单击打开字段job的下拉框数据 , 如图 9 - 27 所示 .
图 9 - 26 字段job的下拉框数据
formfield_for_choice_field ( ) 只能过滤已存在的下拉框数据 ,
如果要对字段的下拉框新增数据内容 , 只能自定义内置函数formfield_for_dbfield ( ) ,
如果在admin . py都重写了formfield_for_dbfield ( ) 和formfield_for_choice_field ( ) ,
Django优先执行函数formfield_for_dbfield ( ) , 然后再执行函数formfield_for_choice_field ( ) ,
所以字段的下拉框数据最终应以formfield_for_choice_field ( ) 为准 .
9.4.6 函数save_model()
函数save_model ( ) 是在新增或修改数据的时候 , 单击 '保存' 按钮所触发的功能 , 该函数主要对输入的数据进行入库或修改处理 .
若想在这个功能中加入一些特殊功能 , 则可对函数save_model ( ) 进行重写 .
比如对数据的修改实现日志记录 , 以VocationAdmin为例 , 函数save_model ( ) 的实现代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' ]
def save_model ( self, request, obj, form, change) :
if change:
user = request. user. username
job = self. model. objects. get( pk= obj. pk) . job
person_info = form. cleaned_data[ 'person_info' ] . name
f = open ( 'd://log.tat' , 'a' )
f. write( person_info + '职位:' + job + ', 被' + user + '修改' + '\r\n' )
f. close( )
else :
pass
super ( ) . save_model( request, obj, form, change)
form . cleaned_data [ 'person_info' ] 在Django表单处理中不会是一个外键值 ( 比如一个整数ID ) 而是一个模型对象实例 .
这是 Django 表单系统如何处理外键字段的一个关键特性 .
普通表单只是保存外键对象的ID , 而Django Admin则会自动查询数据库 , 将ID转换为完整的模型对象实例 .
obj . person_info . age 能获取到外键表的对象 .
通常不需要直接从form . cleaned_data中获取数据 ,
因为obj已经包含了表单中的所有数据 ( 这些数据在表单验证后已经设置到了obj的属性上 ) .
save_model ( ) 函数设有 4 个参数 , 每个参数说明如下 :
● 参数request代表当前用户的请求对象 .
● 参数obj是模型的数据对象 , 比如修改模型Vocation的某行数据 ( 称为数据A ) ,
参数ojb代表数据A的数据对象 , 如果为模型Vocation新增数据 , 参数ojb就为None .
● 参数form代表模型表单 , 它是Django自动创建的模型表单 ,
比如在模型Vocation里新增或修改数据 , Django自动为模型Vocation创建表单VocationForm .
● 参数change判断当前请求是来自数据修改页还是来自数据新增页 ,
如果来自数据修改页 , 就代表用户执行数据修改 , 参数change为True , 否则为False .
无论是修改数据还是新增数据 , 都会调用函数save_model ( ) 实现数据保存 , 因此函数会对当前操作进行判断 ,
如果参数change为True , 就说明当前操作为数据修改 , 否则为新增数据 .
如果当前操作是修改数据 , 就从函数参数request , obj和form里获取当前数据的修改内容 , 然后将修改内容写入D盘的log . txt文件 ,
最后调用super方法使函数继承并执行父类的函数save_model ( ) , 实现数据的入库或修改处理 .
若不调用super方法 , 则当执行数据保存时 , 程序只执行日志记录功能 , 并不执行数据入库或修改处理 .
运行MyDjango , 使用超级管理员登录Admin后台并打开模型Vocation的数据修改页 ,
单击 '保存' 按钮实现数据修改 , 在D盘下打开并查看日志文件log . txt , 如图 9 - 28 所示 .
图 9 - 28 日志文件log . txt
如果执行数据删除操作 , Django就调用函数delete_model ( ) 实现 ,
该函数设有参数request和obj , 参数的数据类型与函数save_model ( ) 的参数相同 .
若要重新定义函数delete_model ( ) , 则定义过程可参考函数save_model ( ) , 在此就简单讲述 .
from django. contrib import admin
from . models import MyModel
class MyModelAdmin ( admin. ModelAdmin) :
def delete_model ( self, request, obj) :
super ( ) . delete_model( request, obj)
admin. site. register( MyModel, MyModelAdmin)
在上面的例子中 , delete_model方法首先执行一些自定义的逻辑 ( 如果有的话 ) ,
然后调用父类 ( admin . ModelAdmin ) 的delete_model方法来执行实际的删除操作 .
之后 , 可以再添加一些删除后的逻辑 .
注意 , 虽然可以阻止默认的删除逻辑 ( 即不调用 super ( ) . delete_model ( request , obj ) ) ,
但这通常不是个好主意 , 因为这样做会绕过Django的ORM系统 , 可能会导致数据不一致或其他问题 .
此外 , 如果在 delete_model 中抛出了异常 , Django Admin的删除操作将会失败 , 并显示一个错误消息给用户 .
这可以用于实现一些自定义的验证逻辑 , 确保在删除之前满足某些条件 .
9.4.7 数据批量操作
模型Vocation的数据列表页设有 '动作' 栏 , 单击 '动作' 栏右侧的下拉框可以看到数据删除操作 .
只要选中某行数据前面的复选框 , 在 '动作' 栏右侧的下拉框选择 '删除所选的职业信息' 并单击 '执行' 按钮 , 即可实现数据删除 , 如图 9 - 29 所示 .
图 9 - 29 删除数据
从上述的数据删除方式来看 , 这种操作属于数据批量处理 , 因为每次可以删除一行或多行数据 ,
若想对数据执行批量操作 , 则可在 '动作' 栏里自定义函数 , 实现数据批量操作 .
比如实现数据的批量导出功能 , 以模型Vocation为例 , 在VocationAdmin中定义数据批量导出函数 , 代码如下 :
from django. contrib import admin
from . models import *
admin. site. site_title = 'MyDjango后台管理'
admin. site. site_header = 'MyDjango'
@admin. register ( PersonInfo)
class PersonInfoAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'name' , 'age' ]
@admin. register ( Vocation)
class VocationAdmin ( admin. ModelAdmin) :
list_display = [ 'id' , 'job' , 'title' , 'salary' ]
def get_datas ( self, request, queryset) :
temp = [ ]
for d in queryset:
t = [ d. job, d. title, str ( d. salary) , d. person_info. name]
temp. append( t)
f = open ( 'd:/data.txt' , 'a' )
for t in temp:
f. write( ',' . join( t) + '\r\n' )
f. close( )
self. message_user( request, '数据导出成功!' )
get_datas. short_description = '导出数据'
actions = [ 'get_datas' ]
数据批量操作函数get_datas可自行命名函数名 , 参数request代表当前用户的请求对象 , 参数queryset代表已被勾选的数据对象 .
函数实现的功能说明如下 :
( 1 ) 遍历参数queryset , 从已被勾选的数据对象里获取模型字段的数据内容 , 每行数据以列表t表示 , 并且将列表t写入列表temp .
( 2 ) 在D盘下创建data . txt文件 , 并遍历列表temp , 将每次遍历的数据写入data . txt文件 ,
最后调用内置方法message_user提示数据导出成功 .
( 3 ) 为函数get_datas设置short_description属性 , 该属性用于设置 '动作' 栏右侧的下拉框的数据内容 .
( 4 ) 将函数get_datas绑定到ModelAdmin的内置属性actions , 在 '动作' 栏生成数据批量处理功能 .
运行MyDjango , 在模型Vocation的数据列表页全选当前数据 , 打开 '动作' 栏右侧的下拉框 ,
选择 '导出所选数据' , 单击 '执行' 按钮执行数据导出操作 , 如图 9 - 30 所示 .
图 9 - 30 数据批量导出
在D盘下打开并查看导出的数据文件data . txt .
9.4.8 自定义Admin模板
Admin后台系统的模板文件是由Django提供的 ,
在Django的源码目录下可以找到Admin模板文件所在的路径 ( django \ contrib \ admin \ templates \ admin ) .
如果想对Admin模板文件进行自定义更改 , 那么可以直接修改Django内置的Admin模板文件 , 但不提倡这种方法 .
如果一台计算机同时开发多个Django项目 , 就会影响其他项目的使用 .
除了这种方法之外 , 还可以利用模板继承的方法实现自定义模板开发 .
我们对MyDjango的目录架构进行调整 , 如图 9 - 31 所示 .
图 9 - 31 MyDjango的目录架构
在模板文件夹templates下依次创建文件夹admin和index , 文件夹的作用说明如下 :
● 文件夹admin代表该文件夹里的模板文件用于Admin后台系统 , 而且文件夹必须命名为admin .
● 文件夹index代表项目应用index , 文件夹的命名必须与项目应用的命名一致 .
文件夹存放模板文件change_form . html , 所有在项目应用index中定义的模型都会使用该模板文件生成网页信息 .
● 如果将模板文件change_form . html放在admin文件夹下 , 那么整个Admin后台系统都会使用该模板文件生成网页信息 .
MyDjango的模板文件change_form . html来自Django内置模板文件 , 我们根据内置模板文件的代码进行重写 ,
MyDjango的change_form . html代码如下 :
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %}
{% block object-tools-items %}
{# 判断当前用户角色 #}
{% if request.user.is_superuser %}
< li>
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
< a href = " {% add_preserved_filters history_url %}" class = " historylink" >
{% trans "History" %}
</ a>
</ li>
{% endif %}
{% if has_absolute_url %}
< li>
< a href = " {{ absolute_url }}" class = " viewsitelink" >
{% trans "View in site" %}
</ a>
</ li>
{% endif %}
{% endblock %}
从上述代码可以看到 , 自定义模板文件change_form . html的代码说明如下 :
( 1 ) 自定义模板文件change_form . html继承内置模板文件change_form . html , 并且自定义模板文件名必须与内置模板文件名一致 .
( 2 ) 由于内置模板文件admin / change_form . html导入了标签 { % load i18n admin_urlsstatic admin_modify % } ,
因此自定义模板文件change_form . html也要导入该模板标签 , 否则提示异常信息 .
( 3 ) 使用block标签实现内置模板文件的代码重写 .
查看内置模板文件的代码发现 , 模板代码以 { % block xxx % } 形式分块处理 , 将网页上不同的功能以块的形式划分 .
因此 , 在自定义模板中使用block标签对某个功能进行自定义开发 .
下面是对这段代码的详细解释 :
* 1. 模板继承 : { % extends "admin/change_form.html" % }
这一行表示这个模板继承了admin / change_form . html .
在Django admin中 , change_form . html是用于显示对象编辑表单的模板 .
通过继承 , 这个新模板可以覆盖或添加一些块 ( block ) 到基础模板中 .
* 2. 加载标签库 : { % load i18n admin_urls static admin_modify % }
这一行加载了几个Django模板标签库:
i18n : 用于国际化 ( Internationalization ) 的支持 .
admin_urls : 用于生成Django admin中的URL .
static : 用于加载静态文件 ( CSS , JavaScript , 图片等 ) .
admin_modify : 可能是一个自定义的或第三方提供的标签库 , 用于在Django admin的修改视图中执行某些操作 .
* 3. 覆盖对象工具块 :
{ % block object-tools-items % }
. . .
{ % endblock % }
这个块覆盖了admin / change_form . html中的object-tools-items块 ,
该块通常用于显示对象上方的工具 ( 如历史记录 , 在网站上查看等 ) .
* 4. 添加工具项 :
{ % if request . user . is_superuser % }
. . .
{ % endif % }
这个条件判断当前用户是否是超级用户 . 如果是 , 它会添加一个指向对象历史记录的链接 .
这个链接是通过admin_urls标签库中的admin_urlname和admin_urlquote过滤器以及Django的url模板标签生成的 .
{ % if has_absolute_url % }
. . .
{ % endif % }
这个条件判断当前对象是否有一个绝对URL ( 即是否可以在网站上直接查看该对象 ) .
如果有 , 它会显示一个链接和一个文本 ( 通过trans标签进行国际化 ) , 告诉用户可以在网站上查看该对象 .
这个模板片段为Django admin的修改表单页面添加了额外的对象工具 ,
这些工具根据当前用户的角色和对象是否具有绝对URL来决定是否显示 .
运行MyDjango , 当访问Admin后台系统的时候 ,
Django优先查找admin文件夹的模板文件 , 找不到相应的模板文件时 , 再从Django的内置Admin模板文件中查找 .
我们使用超级管理员和普通用户分别访问职业信息的数据修改页 , 不同的用户角色所返回的页面会有所差异 , 如图 9 - 32 所示 .
图 9 - 32 自定义模板文件
要在Django admin中显示一个链接 , 允许用户直接查看某个对象在网站上的表示形式 , 需要确保以下几点 :
* 0. 在admin模板中添加链接 :
在Django admin模板中 ( admin / index / change_form . html ) , 添加类似以下的代码来显示链接 ( 上面已经完成 ) .
{% if has_absolute_url %}
< li>
< a href = " {{ absolute_url }}" class = " viewsitelink" >
{% trans "View in site" %}
</ a>
</ li>
{% endif %}
* 1. 模型应该有一个get_absolute_url方法 , 它返回一个指向该对象在网站上表示形式的URL .
定义了get_absolute_url方法后在数据修改页面就会出现 "View in site" 按钮 .
from django. db import models
from django. urls import reverse
class PersonInfo ( models. Model) :
id = models. AutoField( primary_key= True )
name = models. CharField( max_length= 20 )
age = models. IntegerField( )
def __str__ ( self) :
return str ( self. name)
class Meta :
verbose_name = '人员信息'
verbose_name_plural = '人员信息'
class Vocation ( models. Model) :
JOB = (
( '软件开发' , '软件开发' ) ,
( '软件测试' , '软件测试' ) ,
( '需求分析' , '需求分析' ) ,
( '项目管理' , '项目管理' ) ,
)
id = models. AutoField( primary_key= True )
job = models. CharField( max_length= 20 , choices= JOB)
title = models. CharField( max_length= 20 )
salary = models. IntegerField( null= True , blank= True )
person_info = models. ForeignKey( PersonInfo, on_delete= models. CASCADE)
record_time = models. DateField( auto_now= True , null= True , blank= True )
def __str__ ( self) :
return str ( self. id )
class Meta :
verbose_name = '职业信息'
verbose_name_plural = '职业信息'
def get_absolute_url ( self) :
return reverse( 'index:detail' , args= [ self. id ] )
* 2. 确保URL配置正确在urls . py文件中 , 需要有一个URL模式与get_absolute_url方法返回的URL相匹配 .
from django. contrib import admin
from django. urls import path, include
urlpatterns = [
path( 'admin/' , admin. site. urls) ,
path( '' , include( ( 'index.urls' , 'index' ) , namespace= 'index' ) )
]
from django. urls import path
from . import views
urlpatterns = [
path( 'vacation/<int:pk>/' , views. vacation_detail, name= 'detail' ) ,
]
* 3. 编写视图处理请求 : vacation_detail视图函数接受一个pk参数 , 这个参数的值就是从URL中捕获的整数 .
然后 , 使用get_object_or_404函数来根据这个pk值从Vacation模型中获取相应的对象 .
如果对象存在 , 我们就继续处理 ; 如果对象不存在 , 函数会自动返回一个 404 错误 .
from django. shortcuts import render
from django. shortcuts import get_object_or_404
from . models import *
def vacation_detail ( request, pk) :
vacation = get_object_or_404( Vocation, pk= pk)
field_list = [ ( field. name, field. value_from_object( vacation) ) for field in Vocation. _meta. fields]
return render( request, 'vacation_detail.html' , { 'vacation' : vacation, 'field_list' : field_list} )
* 4. 在模板中显示用户信息 .
<! DOCTYPE html >
< html lang = " en" >
< head>
< meta charset = " UTF-8" >
< title> 字段列表</ title>
< style>
table {
width : 100%;
border-collapse : collapse;
}
th, td {
border : 1px solid black;
padding : 8px;
text-align : left;
}
th {
background-color : #f2f2f2;
}
</ style>
</ head>
< body>
< table>
< thead>
< tr>
< th> 字段名</ th>
< th> 字段值</ th>
</ tr>
</ thead>
< tbody>
{% for field_name, field_value in field_list %}
< tr>
< td> {{ field_name }}</ td>
< td> {{ field_value }}</ td>
</ tr>
{% endfor %}
</ tbody>
</ table>
</ body>
</ html>
启动程序访问数据修改页面 , 会显示 'VIEW IN SET' 标签 .
点击会跳转到数据详情页面 .
9.4.9 自定义Admin后台系统
Admin后台系统为每个网页设置了具体的路由地址 , 每个路由的响应内容是调用内置模板文件生成的 .
若想改变整个Admin后台系统的网页布局和功能 , 则可重新定义Admin后台系统 , 比如常见的第三方插件Xadmin和Django Suit ,
这些插件都是在Admin后台系统的基础上进行重新定义的 .
重新定义Admin后台系统需要对源码结构有一定的了解 , 我们可以从路由信息进行分析 .
以MyDjango为例 , 在MyDjango的urls . py中查看Admin后台系统的路由信息 , 如图 9 - 33 所示 .
图 9 - 33 Admin后台系统的路由信息
长按键盘上的Ctrl键 , 在PyCharm里单击图 9 - 32 中的site即可打开源码文件sites . py ,
将该文件的代码注释进行翻译得知 , Admin后台系统是由类AdminSite实例化创建而成的 ,
换句话说 , 只要重新定义类AdminSite即可实现Admin后台系统的自定义开发 , 如图 9 - 34 所示 .
图 9 - 34 源码文件sites . py
Admin后台系统还有一个系统注册过程 , 将Admin后台系统绑定到Django , 当运行Django时 , Admin后台系统会随之运行 .
Admin的系统注册过程在源码文件apps . py里定义 , 如图 9 - 35 所示 .
图 9 - 35 源码文件apps . py
综上所述 , 如果要实现Admin后台系统的自定义开发 , 就需要重新定义类AdminSite和改变Admin的系统注册过程 .
下一步通过简单的实例来讲述如何自定义开发Admin后台系统 , 我们将会更换Admin后台系统的登录页面 .
以MyDjango为例 , 在项目的根目录创建static文件并放置登录页面所需的JavaScript脚本文件和CSS样式文件 ;
然后在模板文件夹templates中放置登录页面login . html ; 最后在MyDjango文件夹创建myadmin . py和myapps . py文件 .
项目的目录结构如图 9 - 36 所示 .
图 9 - 36 目录结构 ( static的文件在配套资源中 )
下一步在MyDjango的myadmin . py中定义类MyAdminSite , 它继承父类AdminSite并重写方法admin_view ( ) 和get_urls ( ) ,
从而更改Admin后台系统的用户登录地址 , 实现代码如下 :
from django. contrib import admin
from functools import update_wrapper
from django. views. generic import RedirectView
from django. urls import reverse
from django. views. decorators. cache import never_cache
from django. views. decorators. csrf import csrf_protect
from django. http import HttpResponseRedirect
from django. contrib. auth. views import redirect_to_login
from django. urls import include, path, re_path
from django. contrib. contenttypes import views as contenttype_views
class MyAdminSite ( admin. AdminSite) :
def admin_view ( self, view, cacheable= False ) :
def inner ( request, * args, ** kwargs) :
if not self. has_permission( request) :
if request. path == reverse( 'admin:logout' , current_app= self. name) :
index_path = reverse( 'admin:index' , current_app= self. name)
return HttpResponseRedirect( index_path)
return redirect_to_login(
request. get_full_path( ) ,
'/login.html'
)
return view( request, * args, ** kwargs)
if not cacheable:
inner = never_cache( inner)
if not getattr ( view, 'csrf_exempt' , False ) :
inner = csrf_protect( inner)
return update_wrapper( inner, view)
def get_urls ( self) :
def wrap ( view, cacheable= False ) :
def wrapper ( * args, ** kwargs) :
return self. admin_view( view, cacheable) ( * args, ** kwargs)
wrapper. admin_site = self
return update_wrapper( wrapper, view)
urlpatterns = [
path( '' , wrap( self. index) , name= 'index' ) ,
path( 'login/' , RedirectView. as_view( url= '/login.html' ) ) ,
path( 'logout/' , wrap( self. logout) , name= 'logout' ) ,
path( 'password_change/' , wrap( self. password_change, cacheable= True ) , name= 'password_change' ) ,
path(
'password_change/done/' ,
wrap( self. password_change_done, cacheable= True ) ,
name= 'password_change_done' ,
) ,
path( 'jsi18n/' , wrap( self. i18n_javascript, cacheable= True ) , name= 'jsi18n' ) ,
path(
'r/<int:content_type_id>/<path:object_id>/' ,
wrap( contenttype_views. shortcut) ,
name= 'view_on_site' ,
) ,
]
valid_app_labels = [ ]
for model, model_admin in self. _registry. items( ) :
urlpatterns += [
path( '%s/%s/' % ( model. _meta. app_label, model. _meta. model_name) , include( model_admin. urls) ) ,
]
if model. _meta. app_label not in valid_app_labels:
valid_app_labels. append( model. _meta. app_label)
if valid_app_labels:
regex = r'^(?P<app_label>' + '|' . join( valid_app_labels) + ')/$'
urlpatterns += [
re_path( regex, wrap( self. app_index) , name= 'app_list' ) ,
]
return urlpatterns
这段代码定义了一个名为MyAdminSite的类 , 该类继承了Django的admin . AdminSite .
admin . AdminSite是Django管理后台 ( admin site ) 的主要入口点 , 用于注册模型 , 处理URL配置等 .
通过继承并修改admin_view方法 , 可以自定义管理员视图的权限检查和缓存策略 .
下面是对代码的详细解释 :
* 1. 类定义 : class MyAdminSite ( admin . AdminSite ) :
这定义了一个名为MyAdminSite的类 , 该类继承自Django的admin . AdminSite .
* 2. admin_view 方法 : def admin_view ( self , view , cacheable = False ) :
这是MyAdminSite类中的一个方法 , 用于装饰管理员视图 .
这个方法接受两个参数 : 一个是要装饰的视图函数view和一个布尔值cacheable ( 默认为False ) , 表示这个视图是否可以被缓存 .
内部函数 inner : def inner ( request , * args , * * kwargs ) :
这是一个内部函数 , 它包装了原始的管理员视图函数view .
它首先检查用户是否有权限访问管理员界面 ( self . has_permission ( request ) ) .
权限检查 : 如果用户没有权限 , 并且请求的URL是注销页面的URL ( reverse ( 'admin:logout' , current_app = self . name ) ) ,
则重定向到管理员首页 * reverse ( 'admin:index' , current_app = self . name ) ) .
如果用户没有权限且请求的URL不是注销页面的URL , 则使用redirect_to_login函数将用户重定向到登录页面 .
缓存和CSRF保护 : 如果cacheable参数为False ( 即视图不应该被缓存 ) , 则使用never_cache装饰器来装饰inner函数 .
如果view函数没有设置为csrf_exempt ( 即它应该受到CSRF保护 ) , 则使用csrf_protect装饰器来装饰inner函数 .
更新包装器 : return update_wrapper ( inner , view )
使用update_wrapper函数来更新inner函数的__name__ , __module__ , __doc__等属性 , 使其与原始的view函数保持一致 .
这样 , 当在Django的URL配置或模板中使用这个视图时 , 它仍然会表现得像原始的view函数一样 .
不同的URL输入如何影响代码的执行 :
1. 用户访问 / admin / ( 尝试访问管理后台 ) :
self . has_permission ( request ) 被调用 , 检查用户是否有权限访问管理后台 .
如果用户有权限 , view ( request , * args , * * kwargs ) 被调用 , 执行admin_view视图函数 .
如果用户没有权限 , 并且用户不是在尝试注销 ( 因为URL不是 / admin / logout / ) , 用户将被重定向到管理后台的首页 ( / admin / ) .
但由于已经在首页 , 实际上可能不会发生重定向 , 或者根据具体实现 , 可能会显示一个权限不足的提示 .
2. 用户访问 / admin / logout / ( 尝试注销 ) :
self . has_permission ( request ) 被调用 , 检查用户是否有权限访问注销页面 ( 尽管这通常不需要特定权限 , 但假设代码如此 ) .
无论用户是否有权限 , 由于用户正在尝试注销 , 代码会执行redirect_to_login ( request . get_full_path ( ) , '/login.html' ) .
* 3. wrap 函数 : wrap函数是一个闭包函数 , 它接受一个视图函数view和一个布尔值cacheable作为参数 .
在wrap函数内部 , 定义了另一个函数wrapper , 该函数会调用self . admin_view来处理view函数 , 并将cacheable参数传递给它 .
wrapper函数还设置了admin_site属性为self , 以便在需要时可以从内部访问AdminSite的实例 .
最后使用update_wrapper函数来更新wrapper的元信息 ( 如__name__ , __doc__ 等 ) , 使其与原始 view 函数保持一致 .
urlpatterns列表 : 这是一个用于存储URL模式的列表 .
列表中的每个元素都是一个path或re_path对象 , 它们定义了URL的模式和对应的视图函数 .
这里 , 使用前面定义的wrap函数来包装 ( 即装饰 ) 视图函数 , 以确保它们具有适当的权限检查 , 缓存和CSRF保护 .
注册模型的URL模式 : 通过遍历self . _registry ( 这是AdminSite类用于存储已注册模型和它们的管理类的字典 ) ,
代码为每个已注册的模型添加了一个URL模式 .
这些URL模式的路径由模型的app_label和model_name组成 , 并包含该模型的管理类提供的URL配置 ( 通过include ( model_admin . urls ) ) .
应用列表URL : 如果存在有效的应用标签 ( 即至少有一个模型已注册 ) , 则生成一个正则表达式模式来匹配这些应用标签 .
使用re_path和wrap ( self . app_index ) 创建一个URL模式 , 当用户访问某个应用的根路径时 , 会调用app_index视图函数 .
返回值 : 最后 , get_urls方法返回urlpatterns列表 , 这个列表包含了Django管理界面的所有URL模式 .
上述代码将父类AdminSite的方法admin_view ( ) 和get_urls ( ) 进行局部的修改 , 修改的代码已标有注释说明 , 其他代码无须修改 .
从修改的代码看到 , Admin后台系统的用户登录页面的路由地址设为 / login . html , 因此还要定义路由地址 / login . html .
分别在MyDjango的urls . py , index的urls . py和views . py中定义路由login及其视图函数loginView , 代码如下 :
from django. contrib import admin
from django. urls import path, include
urlpatterns = [
path( 'admin/' , admin. site. urls) ,
path( '' , include( ( 'index.urls' , 'index' ) , namespace= 'index' ) )
]
from django. urls import path
from . views import login_view
urlpatterns = [
path( 'login.html' , login_view, name= 'login' ) ,
]
from django. shortcuts import render, redirect
from django. contrib. auth import login, authenticate
from django. contrib. auth. models import User
from django. urls import reverse
def login_view ( request) :
if request. method == 'POST' :
u = request. POST. get( 'username' , '' )
p = request. POST. get( 'password' , '' )
if User. objects. filter ( username= u) :
user = authenticate( username= u, password= p)
if user:
if user. is_active:
login( request, user)
return redirect( reverse( 'index:login' ) )
else :
pass_error = '账号密码错误, 请重新输入!'
else :
user_error = '用户不存在, 请注册!'
else :
if request. user. username:
return redirect( reverse( 'admin:index' ) )
return render( request, 'login.html' , locals ( ) )
视图函数loginView用于实现用户登录 , 它由Django内置的Auth认证系统实现登录过程 .
用户登录页面由模板文件夹templates的login . html生成 .
模板文件login . html的代码如下 :
<! DOCTYPE html >
< html lang = " en" >
< head>
< meta charset = " UTF-8" >
< title> 后台登录</ title>
{% load static %}
<link rel="stylesheet" href="{% static "css/reset.css" %}">
<link rel="stylesheet" href="{% static "css/user.css" %}">
<script src="{% static "js/jquery.min.js" %}"></ script>
<script src="{% static "js/user.js" %}"></ script>
</ head>
< body>
< div class = " page" >
< div class = " loginwarrp" >
< div class = " logo" > 用户登录:</ div>
< div class = " login_form" >
< form action = " " id = " login" name = " login" method = " post" >
{% csrf_token %}
< li class = " login-item" >
< label for = " id_username" > 用户名称:</ label>
< input id = " id_username" type = " text" name = " username" class = " login_input" >
< p id = " count-msg" class = " error plugin-error" > {{ user_error }}</ p>
</ li>
< li class = " login-item" >
< label for = " id_password" > 用户密码:</ label>
< input id = " id_password" type = " password" name = " password" class = " login_input" >
< p id = " password-msg" class = " error" > {{ pass_error }}</ p>
</ li>
< li class = " login-sub" >
< input type = " submit" name = " Submit" value = " 登录" >
</ li>
</ form>
</ div>
</ div>
</ div>
{# 画布粒子 #}
< script type = " text/javascript" >
window. onload = function ( ) {
var config = {
vx : 4 ,
vy : 4 ,
height : 2 ,
width : 2 ,
count : 100 ,
color : "121, 162, 185" ,
stroke : '100, 200, 180' ,
dist : 6000 ,
e_dist : 20000 ,
max_conn : 100
} ;
CanvasParticle ( config) ;
}
</ script>
{# 画布粒子文件 #}
< script src = " {% static 'js/canvas-particle.js' %}" > </ script>
</ body>
</ html>
完成MyAdminSite和路由login的定义后 , 将自定义的MyAdminSite进行系统注册过程 , 由MyAdminSite实例化创建Admin后台系统 .
在MyDjango文件夹的myapps . py中定义系统注册类MyAdminConfig , 代码如下 :
from django. contrib. admin. apps import AdminConfig
class MyAdminConfig ( AdminConfig) :
default_site = 'MyDjango.myadmin.MyAdminSite'
系统注册类MyAdminConfig继承父类AdminConfig并设置父类属性default_site , 使它指向MyAdminSite ,
从而由MyAdminSite实例化创建Admin后台系统 .
最后在配置文件settings . py中配置系统注册类MyAdminConfig , 此外还需配置静态资源文件夹static , 代码如下 :
INSTALLED_APPS = [
'MyDjango.myapps.MyAdminConfig' ,
'django.contrib.auth' ,
'django.contrib.contenttypes' ,
'django.contrib.sessions' ,
'django.contrib.messages' ,
'django.contrib.staticfiles' ,
'index'
]
STATIC_URL = '/static/'
STATICFILES_DIRS = [ BASE_DIR / 'static' ]
完成上述开发后 , 运行MyDjango , 在浏览器上清除Cookie信息 , 确保Admin后台系统处于未登录状态 ,
访问 : 127.0 .0 .1 : 8000 /admin 就能自动跳转到我们定义的用户登录页面 , 如图 9 - 37 所示 .
9.5 本章小结
Admin后台系统也称为网站后台管理系统 , 主要对网站的信息进行管理 ,
如文字 , 图片 , 影音和其他日常使用文件的发布 , 更新 , 删除等操作 , 也包括功能信息的统计和管理 , 如用户信息 , 订单信息和访客信息等 .
简单来说 , 它是对网站数据库和文件进行快速操作和管理的系统 , 以使网页内容能够及时得到更新和调整 .
ModelAdmin继承BaseModelAdmin , BaseModelAdmin的元类为MediaDefiningClass ,
因此Admin系统的属性和方法来自ModelAdmin和BaseModelAdmin .
由于定义的属性和方法较多 , 因此这里只说明日常开发中常用的属性和方法 .
● fields : 由BaseModelAdmin定义 , 格式为列表或元组 , 在新增或修改模型数据时 , 设置可编辑的字段 .
● exclude : 由BaseModelAdmin定义 , 格式为列表或元组 , 在新增或修改模型数据时 ,
隐藏字段 , 使字段不可编辑 , 同一个字段不能与fields共同使用 , 否则提示异常 .
● fieldsets : 由BaseModelAdmin定义 , 格式为两元的列表或元组 ( 列表或元组的嵌套使用 ) ,
改变新增或修改页面的网页布局 , 不能与fields和exclude共同使用 , 否则提示异常 .
● radio_fields : 由BaseModelAdmin定义 , 格式为字典 , 如果新增或修改的字段数据以下拉框的形式展示 ,
那么该属性可将下拉框改为单选按钮 .
● readonly_fields : 由BaseModelAdmin定义 , 格式为列表或元组 , 在数据新增或修改的页面设置只读的字段 , 使字段不可编辑 .
● ordering : 由BaseModelAdmin定义 , 格式为列表或元组 , 设置排序方式 , 比如以字段id排序 , [ 'id' ] 为升序 , [ '-id' ] 为降序 .
● sortable_by : 由BaseModelAdmin定义 , 格式为列表或元组 , 设置数据列表页的字段是否可排序显示 ,
比如数据列表页显示模型字段id , name和age , 如果单击字段name , 数据就以字段name进行升序 ( 降序 ) 排列 ,
该属性可以设置某些字段是否具有排序功能 .
● formfield_for_choice_field ( ) : 由BaseModelAdmin定义 , 如果模型字段设置choices属性 ,
那么重写此方法可以更改或过滤模型字段的属性choices的值 .
● formfield_for_foreignkey ( ) : 由BaseModelAdmin定义 , 如果模型字段为外键字段 ( 一对一关系或一对多关系 ) ,
那么重写此方法可以更改或过滤模型字段的可选值 ( 下拉框的数据 ) .
● formfield_for_manytomany ( ) : 由BaseModelAdmin定义 , 如果模型字段为外键字段 ( 多对多关系 ) ,
那么重写此方法可以更改或过滤模型字段的可选值 .
● get_queryset ( ) : 由BaseModelAdmin定义 , 重写此方法可自定义数据的查询方式 .
● get_readonly_fields ( ) : 由BaseModelAdmin定义 , 重写此方法可自定义模型字段的只读属性 ,
比如根据不同的用户角色来设置模型字段的只读属性 .
● list_display : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页设置显示的模型字段 .
● list_display_links : 由ModelAdmin定义 , 格式为列表或元组 , 为模型字段设置路由地址 , 由该路由地址进入数据修改页 .
● list_filter : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页的右侧添加过滤器 , 用于筛选和查找数据 .
● list_per_page : 由ModelAdmin定义 , 格式为整数类型 , 默认值为 100 , 在数据列表页设置每一页显示的数据量 .
● list_max_show_all : 由ModelAdmin定义 , 格式为整数类型 , 默认值为 200 , 在数据列表页设置每一页显示最大上限的数据量 .
● list_editable : 由ModelAdmin定义 , 格式为列表或元组 ,
在数据列表页设置字段的编辑状态 , 可以在数据列表页直接修改某行数据的字段内容并保存 ,
该属性不能与list_display_links共存 , 否则提示异常信息 .
● search_fields : 由ModelAdmin定义 , 格式为列表或元组 , 在数据列表页的搜索框设置搜索字段 , 根据搜索字段可快速查找相应的数据 .
● date_hierarchy : 由ModelAdmin定义 , 格式为字符类型 , 在数据列表页设置日期选择器 , 只能设置日期类型的模型字段 .
● save_as : 由ModelAdmin定义 , 格式为布尔型 , 默认为False , 若改为True , 则在数据修改页添加 '另存为' 功能按钮 .
● actions : 由ModelAdmin定义 , 格式为列表或元组 , 列表或元组的元素为自定义函数 , 函数在 '动作' 栏生成操作列表 .
● actions_on_top和actions_on_bottom : 由ModelAdmin定义 , 格式为布尔型 , 设置 '动作' 栏的位置 .
● save_model ( ) : 由ModelAdmin定义 , 重写此方法可自定义数据的保存方式 .
● delete_model ( ) : 由ModelAdmin定义 , 重写此方法可自定义数据的删除方式 .
Admin后台系统的首页设置包括 : 项目应用的显示名称 , 模型的显示名称和网页标题 , 三者的设置方式说明如下 :
● 项目应用的显示名称 : 在项目应用的__init__ . py中设置变量default_app_config ,
该变量指向自定义的IndexConfig类 , 由IndexConfig类的verbose_name属性设置项目应用的显示名称 .
● 模型的显示名称 : 在模型属性Meta中设置verbose_name和verbose_name_plural ,
两者的区别在于verbose_name是以复数的形式表示的 , 若在模型中同时设置这两个属性 , 则优先显示verbose_name_plural的值 .
● 网页标题 : 在项目应用的admin . py中设置Admin的site_title和site_header属性 ,
如果项目有多个项目应用 , 那么只需在某个项目应用的admin . py中设置一次即可 .