9. Django 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_key=True)name = models.CharField(max_length=20)age = models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称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)# 打印记录时展示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_key=True)name = models.CharField(max_length=20)age = models.IntegerField()# 打印记录时展示用户def __str__(self):return str(self.name)# 定义模型对象的元数据class Meta:# admin中展示的表名称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)# 打印记录时展示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_key=True)name = models.CharField(max_length=20)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_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)# 打印记录时展示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 = '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']# 重写get_readonly_fields函数# 设置超级管理员和普通用户的权限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为例, 将该字段的数据设置为不同的颜色, 实现代码如下:
# index 的 models.py
from django.db import models
from django.utils.html import format_html # 格式化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:# admin中展示的表名称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)# 打印记录时展示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 = 'red'else:color_code = 'blue'return format_html('<span style="color: {}">{}</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 = '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')# 重写get_readonly_fields函数# 设置超级管理员和普通用户的权限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) # 设置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 = '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) # 返回id<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为例, 实现代码如下:
# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
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):# db_field 不是一个普通的Python对象, 而是一个Django模型字段对象,# 它包含了这个字段的类型, 名称, 相关模型, 关联字段等信息.# 通过db_field.name可以获取该外键字段在模型中的名称.# 判断是否为外键字段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来改变表单字段的行为 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过滤下拉框数据, 实现代码如下:
# index 的 admin.py
from django.contrib import admin
from .models import *# 修改title的header
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']# 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 = 'MyDjango'@admin.register(PersonInfo)
class PersonInfoAdmin(admin.ModelAdmin):# 设置显示的字段list_display = ['id', 'name', 'age']@admin.register(Vocation)
class VocationAdmin(admin.ModelAdmin):# 在数据列表页设置显示的模型字段list_display = ['id', 'job', 'title', 'salary']def save_model(self, request, obj, form, change):# 判断是否为修改操作if change:# 获取当前用户名user = request.user.username# 使用模型获取数据(Vocation模型的数据), pk代表具主键属性的字段job = self.model.objects.get(pk=obj.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 = '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"按钮.
# index 的 models.py
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:# admin中展示的表名称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)# 打印记录时展示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'), namespace='index'))
]
# index 的 urls.py
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错误.
# 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, 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. 在模板中显示用户信息.
<!-- templates 的 vacation_detail.html -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>字段列表</title><style>/* 添加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>
<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后台系统的用户登录地址, 实现代码如下:
# 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, 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, 代码如下:
# MyDjango 的 urls.py
from django.contrib import admin
from django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('', include(('index.urls', 'index'), namespace='index'))
]
# index 的 urls.py
from django.urls import path
from .views import login_viewurlpatterns = [# 定义路由path('login.html', login_view, name='login'),
]
# 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(username=u): # 检查用户名是否存在, 如果成功则返回一个用户对象, 否则会Noneuser = authenticate(username=u, password=p) # 使用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 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, 代码如下:
# 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的MyAdminConfig'MyDjango.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中设置一次即可.
相关文章:
9. Django Admin后台系统
9. Admin后台系统 Admin后台系统也称为网站后台管理系统, 主要对网站的信息进行管理, 如文字, 图片, 影音和其他日常使用的文件的发布, 更新, 删除等操作, 也包括功能信息的统计和管理, 如用户信息, 订单信息和访客信息等. 简单来说, 它是对网站数据库和文件进行快速操作和管…...
ELK+kafka日志采集
ElasticSeach(存储日志信息) Logstash(搬运工) Kibana 连接ElasticSeach图形化界面查询日志 ELK采集日志的原理: 在每个服务器上安装LogstashLogstash需要配置固定读取某个日志文件Logstash将日志文件格式化为json的…...
【C++ list所有函数举例如何使用】
C 中的 std::list 是一个双向链表,提供了在列表中添加、删除、访问元素等操作的方法。以下是一些常用的 std::list 函数以及如何使用它们的示例: push_back(const T& value): 在列表的末尾添加一个值为 value 的元素。 std::list<int> mylis…...
HTML5(1)
目录 一.HTML5(超文本(链接)标记(标签<>)语言) 1.开发环境(写代码,看效果) 2.vscode 使用 3.谷歌浏览器使用 4.标签语法 5.HTML基本骨架(网页模板) 6.标签的…...
【LAMMPS学习】八、基础知识(6.2)LAMMPS GitHub 教程
8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…...
专业习惯:避开本地语言,使用通用语言
如果你的目标是走一步看一步,那躺平就得了,学习什么的都没有必要。如果你的目标是远方,那么就需要未雨绸缪。 在工作之中,本地语言及习惯固然可用,但非常局限,随便换一个地方和场景,别人就难以理…...
【Leetcode每日一题】 综合练习 - 逆波兰表达式求值(难度⭐⭐)(73)
1. 题目解析 题目链接:150. 逆波兰表达式求值 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 数据结构选择: 使用栈(stack<int>)来存储操作数,以便进…...
2G 3G LTE 5G的区别
2G、3G、LTE和5G是不同代的移动通信技术,每一代技术都在其前一代的基础上提供了改进的性能、更高的数据速率和新的功能。以下是这些技术的主要区别: ### 2G (第二代移动通信技术): - **数据速率**:较低的数据速率,通常在几百kbps…...
《21天学通C++》(第二十章)STL映射类(map和multimap)
为什么需要map和multimap: 1.查找高效: 映射类允许通过键快速查找对应的值,这对于需要频繁查找特定元素的场景非常适合。 2.自动排序: 会自动根据键的顺序对元素进行排序 3.多级映射: 映射类可以嵌套使用,创…...
5月游戏市场迎来新的体验,网易两款游戏重磅出炉
易采游戏网5月9日消息,随着科技的飞速发展,手机游戏已经成为人们休闲娱乐的重要方式。在这个领域,网易作为国内领先的游戏开发商,一直致力于为玩家带来高品质的游戏体验。近日,网易携手国际大厂Square Enix,…...
15_Scala面向对象编程_访问权限
文章目录 Scala访问权限1.同类中访问2.同包不同类访问3.不同包访问4.子类权限小结 Scala访问权限 知识点概念 private --同类访问private[包名] --包私有; 同类同包下访问protected --同类,或子类 //同包不能访问(default)(public)默认public --公…...
LeetCode|700. Search in Binary Search Tree
题目 You are given the root of a binary search tree (BST) and an integer val. Find the node in the BST that the node’s value equals val and return the subtree rooted with that node. If such a node does not exist, return null. Example 1: Input: root […...
MacOS下载安装JDK8
一、前言 今天给苹果电脑安装JDK环境,后续打算把Mac系统也用起来,也体验一把用苹果系统开发。 JDK就不过多介绍了,大家都是JAVA开发,JDK就是JAVA开发的必要环境。目前已经更新到JDK20了,不过我是不会更新的࿰…...
macOS 如何使用Visual Studio Code 编译C++
在 macOS,则默认系统 C++ 编译器是 Clang。 要使用 Visual Studio Code 在 macOS 上的 Clang 中指定 C++ 版本,可以按如下所示修改tasks.json 文件: 在 Visual Studio Code 中打开您的 C++ 项目。按 Ctrl+Shift+P(或 macOS 上的 Cmd+Shift+P)打开命令面板。在命令面板中键…...
SQLite3简单操作
SQLite命令 文章目录 SQLite命令一、创建数据库二、表的操作1、创建表2、删除表 一、创建数据库 注:使用Ubuntu服务器操作,安装sqlite3 sudo apt update sudo apt install sqlite3 sqlite3 --version1、SQLite主要使用命令sqlite3来创建新的数据库 sq…...
从“制造”到“智造”:“灯塔”经验助力中国制造业转型升级-转载
作者:Karel Eloot,侯文皓,Francisco Betti,Enno de Boer和Yves Giraud 作为中国实体经济的主体,制造业是推动中国经济发展乃至全球制造业持续增长的重要引擎。站在历史与未来交汇的新起点上,中国制造业将背…...
C++ 容器(二)——容器操作
一、容器的修改 容器修改函数 insert():在指定位置插入一个或多个元素erase():删除指定位置或指定范围的元素push_back():将元素添加到容器的末尾pop_back():删除容器的最后一个元素 push_front():将元素添加到容器的开…...
操作系统——进程控制
创建进程 fork fork是一个系统调用函数,用来创建子进程,通过多个执行流完成任务。子进程和父进程共用一份代码,子进程数据使用写时拷贝,即子进程数据在创建的时候和父进程相同,但是当要修改数据的时候,子进…...
Marin说PCB之国产电源芯片方案 ---STC2620Q
随着小米加入的造车大家庭,让这个本来就卷的要死的造车大家庭更加卷了。随之带来的蝴蝶效应就是江湖上各个造成门派都开始了降本方案的浪潮啊,开始打响价格战了。各家的新能源车企也是不得不开始启动了降本方案的计划了,为了应对降价的浪潮。…...
已解决java.lang.StringIndexOutOfBoundsException: 字符串索引越界异常的正确解决方法,亲测有效!!!
已解决java.lang.StringIndexOutOfBoundsException: 字符串索引越界异常的正确解决方法,亲测有效!!! 目录 问题分析 报错原因 解决思路 解决方法 检查索引范围 检查字符串长度 管理循环中的索引 总结 问题分析 java.lan…...
关于实体类注解@Data、@EqualsAndHashCode(callSuper = true)、@Accessors(chain = true)的作用
笔记:都是lombook插件的注解,作用是简化优化代码等,比如getter、setter,一般三者连用能避免一些如继承类的导致的一些坑,比如equal()方法的错误,具体用法可查阅每个注解及属性的作用。 Accessors(chain tr…...
5.9号模拟前端面试10问
5.9号模拟前端面试10问 1.html语义化的理解 HTML语义化是指使用具有明确含义的HTML标签来描述内容,而不仅仅是使用<div>和<span>等通用容器标签。语义化的HTML代码更易于阅读和维护,同时也有助于搜索引擎优化(SEO)。…...
vue3 JSX的使用与警告【JSX 元素隐式具有类型 “any“,因为不存在接口 “JSX.IntrinsicElements“】解决办法
一、安装 pnpm i vitejs/plugin-vue-jsx -D 二、配置 1、tsconfig.json "compilerOptions":{"jsx":"preserve" } 2、vite.config.ts import VueJsx from "vitejs/plugin-vue-jsx"...plugin:[vue(),VueJsx() ] 三、简单使用案例…...
一、计算机基础(Java零基础一)
🌻🌻目录 一、🌻🌻剖析学习Java前的疑问🌻🌻1.1 零基础学习编程1.2 英语不好能学吗?1.3 理解慢能学好吗?1.4 现在学Java晚吗?1.5 Java 和 Python 还有 Go 的选择1.6 Java…...
德国著名自动化公司Festo设计了一款仿生蜜蜂,仅重34g,支持多只蜜蜂编队飞行!...
德国著名的气动元件研发及自动化解决方案供应商Festo公司近日展示了一款仿生蜜蜂(BionicBee),重量只有34g,却完全可以实现自主飞行,还支持多只相同的蜜蜂机器人编队飞行。 BionicBee 重约 34 克,长 22 厘米…...
折腾记:C++用开源库Snap7通过S7协议连接西门子PLC
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 不是教程,是避坑指…...
Android studio 新版本 NewUI toolbar显示快捷按钮
新版本的Android studio 启用新的界面,以前许多快捷按键位置有变化 文章目录 设置始终显示主菜单设置ToolBar快捷按钮显示设置右下角显示分支 设置始终显示主菜单 原本要点击左上角几个横向才显示的菜单 设置始终显示,View -> Appearance -> Mai…...
辛普森公式求函数的近似积分【通用计算】
利用辛普森公式可以近似求出复杂函数的积分值,公式如下: ∫ a b f ( x ) d x ≈ h 3 [ y 0 y 2 n − 1 4 ( ∑ i 1 n − 1 y 2 i − 1 ) ∑ i 1 n − 1 y 2 i ] \int_{a}^{b} f(x) dx \approx \frac{h}{3}\left[ y_0 y_{2n-1} 4(\sum\limits_{i1…...
即插即用 | YOLOv8热力图可视化方法详解,揭秘AI如何「看」世界!【附完整源码】
《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…...
多线程学习D10 收尾了应该
线程安全集合类概述 重点介绍java.util.concurrent.* 下的线程安全集合类,可以发现它们有规律,里面包含三类关键词:Blocking、CopyOnWrite、Concurrent Blocking 大部分实现基于锁,并提供用来阻塞的方法 CopyOnWrite 之类容器修改…...
深圳网站建设工作室/周口网站seo
做 Java 开发的你,一天是不是这样度过的?8:00--9:30 闹铃响了N遍后,匆忙起床洗漱,在拥挤的地铁上刷朋友圈、公众号和技术论坛9:30--10:00 到公司,吃早点,打开电脑收邮件,终终终于准备好状态开始…...
wordpress官网中文版下载/专业网页设计和网站制作公司
微服务的概念源于2014年3月Martin Fowler(马丁福勒)所写的一篇文章“Mieroservices”(微服务)。文中表达了一种观念,微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终…...
网站建设的主要特征/长春网站建设开发
go get github.com/garyburd/redigo/redis import "github.com/garyburd/redigo/redis" 连接 Conn接口是与Redis协作的主要接口,可以使用Dial,DialWithTimeout或者NewConn函数来创建连接,当任务完成时,应用程序必须调用Close函数来…...
深圳建网站兴田德润优秀/北京推广
Adobe Photoshop是目前最流行的平面设计软件之一。可以说,只要你接触平面设计,那么无论早晚,你都要和它打交道。关于Photoshop,要说的实在太多太多,但不论你想让它成为你的左膀右臂,或者仅仅是用它来做一些…...
男女做受视频网站/网络推广途径
文章目录[隐藏]1. 检查当前安装的 PHP2. 更换 RPM 源3. 停止相关服务4. 删除已经安装的 PHP 相关包5. 安装新版本 PHP6. 重新启动相关服务7. 再次检查版本本站使用的是 WordPress 搭建,刚开始搭建的时候吧,没啥经验,网上搜一搜,就…...
wordpress设置缓存/淘宝运营培训班学费大概多少
我们以Android获取TP报点为例,分析poll过程。poll系统调用功能是检测设备是否有可读等对应事件发生时,调用read系统调用实现对设备的无阻塞访问。现在我们来分析poll的基本调用流程。 首先看应用如何使用poll: int main(int argc, char* argv[]) {int …...