普通电脑可以做网站服务器吗,广告设计学校,asp 网站图标,网站建设源码下载9. Admin后台系统
Admin后台系统也称为网站后台管理系统, 主要对网站的信息进行管理,
如文字, 图片, 影音和其他日常使用的文件的发布, 更新, 删除等操作,
也包括功能信息的统计和管理, 如用户信息, 订单信息和访客信息等.
简单来说, 它是对网站数据库和文件进行快速操作和管…
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所示.# MyDjango 的 settings.py
# 添加中间件LocaleMiddleware
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.# index 的 models.py
from django.db import models# 定义人员信息类
class PersonInfo(models.Model):id models.AutoField(primary_keyTrue)name models.CharField(max_length20)age models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称verbose_name 人员信息# 定义职业信息表
class Vocation(models.Model):id models.AutoField(primary_keyTrue)job models.CharField(max_length20)title models.CharField(max_length20)salary models.DecimalField(max_digits10, decimal_places2)person_info models.ForeignKey(PersonInfo, on_deletemodels.CASCADE)# 打印记录时展示iddef __str__(self):return str(self.id)class Meta:verbose_name 职业信息 # 执行数据迁移命令:
python manage.py makemigrations
python manage.py migrate若想将index定义的模型展示在Admin后台系统中, 则需要在index的admin.py中编写相关代码, 以模型PersonInfo为例, 代码如下:# index的admin.py
from django.contrib import admin
from .models import *# 方法一:
# 将模型直接注册到admin后台
# admin.site.register(PersonInfo)# 方法二:
# 自定义PersonInfoAdmin类并继承ModelAdmin
# 注册方法一, 使用装饰器将PersonInfoAdmin和Product绑定
admin.register(PersonInfo)
class PersonInfoAdmin(admin.ModelAdmin):# 设置显示的字段list_display [id, name, age]
# 注册方法二
# admin.site.register(PersonInfo, PersonInfoAdmin) 上述代码使用两种方法将数据表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进行重新定义, 代码如下:# index 的 models.py
from django.db import models# 定义人员信息类
class PersonInfo(models.Model):id models.AutoField(primary_keyTrue)name models.CharField(max_length20)age models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称verbose_name 人员信息# 定义职业信息表
class Vocation(models.Model):JOB ((软件开发, 软件开发),(软件测试, 软件测试),(需求分析, 需求分析),(项目管理, 项目管理),)id models.AutoField(primary_keyTrue)job models.CharField(max_length20, choicesJOB)title models.CharField(max_length20)salary models.IntegerField(nullTrue, blankTrue)person_info models.ForeignKey(PersonInfo, on_deletemodels.CASCADE)record_time models.DateField(auto_nowTrue, nullTrue, blankTrue)# 打印记录时展示iddef __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的定义如下:# index 的 models.py
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):# 设置显示的字段# fields [job, title, salary, person_info]# 在数据新增或修改的页面设置不可编辑的字段# exclude []# 改变新增或修改页面的网页布局fieldsets ((职业信息, {fields: (job, title, salary)}),(人员信息, {# 设置隐藏与显示classes: (collapse,),fields: (person_info,),}),)# 将下拉框改为单选按钮# admin.HORIZONTAL 是水平排列# admin.VERTICAL 是垂直排列radio_fields {person_info: admin.HORIZONTAL}# 在数据新增或修改的页面设置可读的字段, 不可编辑readonly_fields [job, ]# 设置排序方式, [id]为升序, [-id]为降序ordering [id]# 设置数据列表页的每列数据是否可排序显示sortable_by [job, title]# 在数据列表页设置显示的模型字段list_display [id, job, title, salary, person_info]# 为数据列表页的字段id和job设置路由地址, 改路由可进入数据修改页# list_display_links [id, job]# 设置过滤器, 若有外键, 则应使用双下划线连接两个模型的字段(现在连接表的name字段信息)list_filter [job, title, person_info__name]# 实在数据列表页设置每一页显示的数据量list_per_page 100# 在数据列表页设置每一页显示最大上限的数据量list_max_show_all 200# 为数据列表页的字段job和title设置可编辑状态list_editable [job, title]# 设置可搜索的字段search_fields [job, title]# 在数据列表页设置日期筛选器date_hierarchy record_time# 在数据修改页面添加另存为功能save_as True# 设置动作栏的设置actions_on_top Falseactions_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中编写以下代码:# index的__init__.py
from django.apps import AppConfig # 导入apps的应用配置
import os# 修改App在Admin后台显示的名称
# default_app_config用于指定默认的AppConfig子类
default_app_config index.IndexConfig# 获取当前App的命名
def get_current_app_name(_file):return os.path.split(os.path.dirname(_file))[-1]# 重写应用配置类IndexConfig
class IndexConfig(AppConfig): # 用于指定该配置类所对应的应用程序的名称(告诉Django这个AppConfig是关联到哪个Django应用的)name get_current_app_name(__file__) # 设置admin后台显示的名称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子类:# index 的 apps.py
from django.apps import AppConfig class IndexConfig(AppConfig): name index
在这种情况下, 只需在INSTALLED_APPS中添加应用的名称index, 而不需要指定IndexConfig:# settings.py
INSTALLED_APPS [ # ... myapp,
]
Django会自动查找myapp/apps.py中的IndexConfig(或任何遵循命名模式的AppConfig)并将其作为该应用的配置类.
如果想要明确地指定使用IndexConfig作为配置类, 可以在INSTALLED_APPS中这样添加: # settings.py
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为例, 代码如下:# index 的 models.py
from django.db import models# 定义人员信息类
class PersonInfo(models.Model):id models.AutoField(primary_keyTrue)name models.CharField(max_length20)age models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称verbose_name 人员信息verbose_name_plural 人员信息# 定义职业信息表
class Vocation(models.Model):JOB ((软件开发, 软件开发),(软件测试, 软件测试),(需求分析, 需求分析),(项目管理, 项目管理),)id models.AutoField(primary_keyTrue)job models.CharField(max_length20, choicesJOB)title models.CharField(max_length20)salary models.IntegerField(nullTrue, blankTrue)person_info models.ForeignKey(PersonInfo, on_deletemodels.CASCADE)record_time models.DateField(auto_nowTrue, nullTrue, blankTrue)# 打印记录时展示iddef __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为例, 设置如下:# index 的 admin.py
# 在末尾面添加...
from django.contrib import admin
# 修改title的hender
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()函数, 根据当前访问的用户角色设置模型字段的只读属性, 代码如下:# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.register(PersonInfo)
class PersonInfoAdmin(admin.ModelAdmin):# 设置显示的字段list_display [id, name, age]admin.register(Vocation)
class VocationAdmin(admin.ModelAdmin):# 在数据列表页设置显示的模型字段list_display [id, job, title, salary]# 重写get_readonly_fields函数# 设置超级管理员和普通用户的权限def get_readonly_fields(self, request, objNone):# 判断用户是否为超级管理员, 来设置只读字段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为例, 将该字段的数据设置为不同的颜色, 实现代码如下:# index 的 models.py
from django.db import models
from django.utils.html import format_html # 格式化HTML字符串模块# 定义人员信息类
class PersonInfo(models.Model):id models.AutoField(primary_keyTrue)name models.CharField(max_length20)age models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称verbose_name 人员信息verbose_name_plural 人员信息# 定义职业信息表
class Vocation(models.Model):JOB ((软件开发, 软件开发),(软件测试, 软件测试),(需求分析, 需求分析),(项目管理, 项目管理),)id models.AutoField(primary_keyTrue)job models.CharField(max_length20, choicesJOB)title models.CharField(max_length20)salary models.IntegerField(nullTrue, blankTrue)person_info models.ForeignKey(PersonInfo, on_deletemodels.CASCADE)record_time models.DateField(auto_nowTrue, nullTrue, blankTrue)# 打印记录时展示iddef __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 redelse:color_code bluereturn format_html(span stylecolor: {}{}/span,color_code,self.person_info)# 设置虚拟字段colored_name在Admin中显示名称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中添加自定义字段colored_name
# colored_name来自于模型Vocation
list_display.append(colored_name)# 完整代码 index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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)# 重写get_readonly_fields函数# 设置超级管理员和普通用户的权限def get_readonly_fields(self, request, objNone):# 判断用户是否为超级管理员, 来设置只读字段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 stylecolor: {}{}/span, color_code, obj.name) # 设置Admin的字段名称 colored_name.short_description 带颜色的姓名 admin.site.register(MyModel, MyModelAdmin)9.4.3 函数get_queryset()
函数get_queryset()用于查询模型的数据信息, 然后在Admin的数据列表页展示.
默认情况下, 该函数执行全表数据查询, 若要改变数据的查询方式, 则可重新定义该函数,
比如根据不同的用户角色执行不同的数据查询, 以VocationAdmin为例, 实现代码如下:# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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__lt2) # 返回id2的数据 分析上述代码可知, 自定义函数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为例, 实现代码如下:# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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):# db_field 不是一个普通的Python对象, 而是一个Django模型字段对象,# 它包含了这个字段的类型, 名称, 相关模型, 关联字段等信息.# 通过db_field.name可以获取该外键字段在模型中的名称.# 判断是否为外键字段if db_field.name person_info:# 判断是否为超级管理员if not request.user.is_superuser:# 过滤下拉框的数据v Vocation.objects.filter(id__lt2)kwargs[queryset] PersonInfo.objects.filter(id__inv)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, requestNone, **kwargs): # 检查是否是特定的多对多字段 if db_field.name my_m2m_field: # 修改kwargs来改变表单字段的行为 kwargs[queryset] db_field.queryset.filter(activeTrue) # 仅显示活动的对象 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过滤下拉框数据, 实现代码如下:# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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]# db_field.choices获取模型字段的属性choices的值def formfield_for_choice_field(self, db_field, request, **kwargs):if db_field.name job:# 减少字段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 *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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# 使用模型获取数据(Vocation模型的数据), pk代表具主键属性的字段job self.model.objects.get(pkobj.pk).job # 获取旧数据中的job信息# 从from表单的清洗数据中获取外键表的name信息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在继承父类已有功能的情况下新增自定义功能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): # 在这里添加删除前的自定义逻辑 # 例如, 可能想要记录日志, 或者执行一些清理工作 # 调用Django的默认删除逻辑 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中定义数据批量导出函数, 代码如下:# index 的 admin
from django.contrib import admin
from .models import *# 修改title的header
admin.site.site_title MyDjango后台管理
admin.site.site_header MyDjangoadmin.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 %} classhistorylink{% trans History %}/a/li{% endif %}{% if has_absolute_url %}lia href{{ absolute_url }} classviewsitelink{% 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 %}
lia href{{ absolute_url }} classviewsitelink{% trans View in site %}/a
/li
{% endif %}* 1. 模型应该有一个get_absolute_url方法, 它返回一个指向该对象在网站上表示形式的URL.定义了get_absolute_url方法后在数据修改页面就会出现View in site按钮.# index 的 models.py
from django.db import models
from django.urls import reverse# 定义人员信息类
class PersonInfo(models.Model):id models.AutoField(primary_keyTrue)name models.CharField(max_length20)age models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称verbose_name 人员信息verbose_name_plural 人员信息# 定义职业信息表
class Vocation(models.Model):JOB ((软件开发, 软件开发),(软件测试, 软件测试),(需求分析, 需求分析),(项目管理, 项目管理),)id models.AutoField(primary_keyTrue)job models.CharField(max_length20, choicesJOB)title models.CharField(max_length20)salary models.IntegerField(nullTrue, blankTrue)person_info models.ForeignKey(PersonInfo, on_deletemodels.CASCADE)record_time models.DateField(auto_nowTrue, nullTrue, blankTrue)# 打印记录时展示iddef __str__(self):return str(self.id)class Meta:verbose_name 职业信息verbose_name_plural 职业信息def get_absolute_url(self):# 反向解析, 生成一个url地址return reverse(index:detail, args[self.id]) * 2. 确保URL配置正确在urls.py文件中, 需要有一个URL模式与get_absolute_url方法返回的URL相匹配.# MyDjango 的 urls.py
from django.contrib import admin
from django.urls import path, includeurlpatterns [path(admin/, admin.site.urls),path(, include((index.urls, index), namespaceindex))
]
# index 的 urls.py
from django.urls import path
from . import views
urlpatterns [path(vacation/int:pk/, views.vacation_detail, namedetail),
] * 3. 编写视图处理请求: vacation_detail视图函数接受一个pk参数, 这个参数的值就是从URL中捕获的整数.然后, 使用get_object_or_404函数来根据这个pk值从Vacation模型中获取相应的对象.如果对象存在, 我们就继续处理; 如果对象不存在, 函数会自动返回一个404错误.# index 的 views.py
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from .models import *def vacation_detail(request, pk):# 使用get_object_or_404来获取对象, 如果对象不存在则返回404页面vacation get_object_or_404(Vocation, pkpk)# 获取模型的所有字段(仅用于展示, 可能需要排除某些字段)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. 在模板中显示用户信息.!-- templates 的 vacation_detail.html --
!DOCTYPE html
html langen
headmeta charsetUTF-8title字段列表/titlestyle/* 添加CSS样式来格式化表格 */table {width: 100%; /* 设置表格宽度为100% */border-collapse: collapse; /* 合并边框 */}th, td {border: 1px solid black; /* 设置单元格边框 */padding: 8px; /* 设置单元格内边距 */text-align: left; /* 文本左对齐 */}th {background-color: #f2f2f2; /* 设置表头背景色 */}/style
/head
body
tabletheadtrth字段名/thth字段值/th/tr/theadtbody{% for field_name, field_value in field_list %}trtd{{ 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.pyAdmin后台系统还有一个系统注册过程, 将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后台系统的用户登录地址, 实现代码如下:# MyDjango 的 myadmin.py
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, cacheableFalse):def inner(request, *args, **kwargs):if not self.has_permission(request):if request.path reverse(admin:logout, current_appself.name):index_path reverse(admin:index, current_appself.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, cacheableFalse):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), nameindex),# 修改登录界面的路由地址path(login/, RedirectView.as_view(url/login.html)),path(logout/, wrap(self.logout), namelogout),path(password_change/, wrap(self.password_change, cacheableTrue), namepassword_change),path(password_change/done/,wrap(self.password_change_done, cacheableTrue),namepassword_change_done,),path(jsi18n/, wrap(self.i18n_javascript, cacheableTrue), namejsi18n),path(r/int:content_type_id/path:object_id/,wrap(contenttype_views.shortcut),nameview_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^(?Papp_label |.join(valid_app_labels) )/$urlpatterns [re_path(regex, wrap(self.app_index), nameapp_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, cacheableFalse):这是MyAdminSite类中的一个方法, 用于装饰管理员视图.这个方法接受两个参数: 一个是要装饰的视图函数view和一个布尔值cacheable(默认为False), 表示这个视图是否可以被缓存.内部函数 inner: def inner(request, *args, **kwargs):这是一个内部函数, 它包装了原始的管理员视图函数view.它首先检查用户是否有权限访问管理员界面(self.has_permission(request)).权限检查: 如果用户没有权限, 并且请求的URL是注销页面的URL(reverse(admin:logout, current_appself.name)),则重定向到管理员首页*reverse(admin:index, current_appself.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, 代码如下:# MyDjango 的 urls.py
from django.contrib import admin
from django.urls import path, includeurlpatterns [path(admin/, admin.site.urls),path(, include((index.urls, index), namespaceindex))
] # index 的 urls.py
from django.urls import path
from .views import login_viewurlpatterns [# 定义路由path(login.html, login_view, namelogin),
] # index 的 views.py
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(usernameu): # 检查用户名是否存在, 如果成功则返回一个用户对象, 否则会Noneuser authenticate(usernameu, passwordp) # 使用authenticate函数验证用户if user:if user.is_active: # 检查用户是否活跃, is_active属性为True, 用户才能被登录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的代码如下:!-- templates 的 login.html --
!DOCTYPE html
html langen
headmeta charsetUTF-8title后台登录/title{% load static %}link relstylesheet href{% static css/reset.css %}link relstylesheet href{% static css/user.css %}script src{% static js/jquery.min.js %}/scriptscript src{% static js/user.js %}/script
/head
body
div classpagediv classloginwarrpdiv classlogo用户登录:/divdiv classlogin_formform action idlogin namelogin methodpost{% csrf_token %}li classlogin-itemlabel forid_username用户名称:/labelinput idid_username typetext nameusername classlogin_inputp idcount-msg classerror plugin-error{{ user_error }}/p/lili classlogin-itemlabel forid_password用户密码:/labelinput idid_password typepassword namepassword classlogin_inputp idpassword-msg classerror{{ pass_error }}/p/lili classlogin-subinput typesubmit nameSubmit value登录/li/form/div/div
/div
{# 画布粒子 #}
script typetext/javascriptwindow.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, 代码如下:# MyDjango 的 myapps.py
from django.contrib.admin.apps import AdminConfig# 继承父类AdminConfig
# 重新设置属性default_site的值使它指向MyAdminSite类
class MyAdminConfig(AdminConfig):default_site MyDjango.myadmin.MyAdminSite 系统注册类MyAdminConfig继承父类AdminConfig并设置父类属性default_site, 使它指向MyAdminSite,
从而由MyAdminSite实例化创建Admin后台系统.
最后在配置文件settings.py中配置系统注册类MyAdminConfig, 此外还需配置静态资源文件夹static, 代码如下:# Django 的 settings.py
# 配置系统注册类MyAdminConfig
INSTALLED_APPS [# 注释原有的admin# django.contrib.admin,# 指向myapps的MyAdminConfigMyDjango.myapps.MyAdminConfig,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.staticfiles,index
]# 配置静态资源文件夹static
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中设置一次即可.