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. usernamejob =  self. model. objects. get( pk= obj. pk) . job  person_info =  form. cleaned_data[ 'person_info' ] . namef =  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,  includeurlpatterns =  [ 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_viewsclass  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 =  selfreturn  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,  includeurlpatterns =  [ path( 'admin/' ,  admin. site. urls) , path( '' ,  include( ( 'index.urls' ,  'index' ) ,  namespace= 'index' ) ) 
]   
 
from  django. urls import  path
from  . views import  login_viewurlpatterns =  [ 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  reversedef  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中设置一次即可 .