商城后台管理系统
Admin后台系统也称为网站后台管理系统,主要对网站的信息进行管理,如文字、图片、影音和其他日常使用的文件的发布、更新、删除等操作,也包括数据信息的统计和管理,如用户信息、订单信息和商品信息等。简单来说,它是对网站数据库和文件进行快速操作和管理的系统,以使网页内容能够及时地得到更新和调整。
Admin基本配置
当一个网站上线之后,网站管理员通过网站后台系统对网站进行管理和维护。Django已内置Admin后台系统,在创建Django项目的时候,可以从配置文件settings.py中看到项目已默认启用了Admin后台系统,如图10-1所示。
从图10-1中看到,在INSTALLED_APPS中已配置了Admin后台系统,如果网站不需要Admin后台系统,可以将配置信息删除,这样可以减少程序对系统资源的占用。此外,在babys文件夹的urls.py中也可以看到Admin后台系统的路由信息,只要运行babys并在浏览器上输入127.0.0.1:8000/admin就能访问Admin后台系统,如图10-2所示。
在访问Admin后台系统时,需要用户的账号和密码才能登录后台管理页面。创建用户的账号和密码之前,必须确保项目已执行数据迁移,在数据库中已创建相应的数据表。以babys项目为例,项目的数据
如果Admin后台系统以英文的形式显示,那么我们还需要在项目的settings.py中设置中间件MIDDLEWARE,将后台内容以中文形式显示。添加的中间件是有先后顺序的,具体可回顾2.4.3节,如图10-4所示。
完成上述设置后,下一步创建超级管理员的账号和密码,创建方法由Django的内置指令createsuperuser完成。在PyCharm的Terminal模式下输入创建指令,代码如下:
在创建用户时,用户名和邮箱地址可以为空,如果用户名为空,就默认使用计算机的用户名,而设置用户密码时,输入的密码不会显示在屏幕上。如果密码过短,Django就会提示密码过短并提示是否继续创建。若输入“Y”,则强制创建用户;若输入“N”,则重新输入密码。完成用户创建后,打开数据表auth_user可以看到新增了一条用户信息,如图10-5所示。
在浏览器上再次访问Admin的路由地址,在登录页面上使用刚刚创建的账号和密码登录,即可进入Admin后台系统,如图10-6所示。
在Admin后台系统中可以看到,网页布局分为站点管理、认证和授权、用户和组,分别说明如下:
(1)站点管理是整个Admin后台的主体页面,整个项目的项目应用定义的模型都会在此页面显示。
(2)认证和授权是Django内置的用户认证系统,包括用户信息、权限管理和用户组设置等功能。
(3)认证和授权的用户和组分别对应内置模型User和Group,它们对应数据表auth_user和auth_user_groups。
配置项目应用与模型
我们在项目babys中已简单设置了Admin后台系统,下一步是根据项目babys的目录结构深入设置Admin后台系统。项目babys设有3个项目应用,分别为index、commodity和shopper,其中项目应用commodity和shopper分别定义了模型Types、CommodityInfos和CartInfos、OrderInfos,因此我们将项目应用commodity和shopper配置到Admin后台系统。
因为Admin后台系统是对项目的数据表进行维护和管理,而项目应用commodity和shopper定义了数据模型,必须将它们配置到Admin后台系统,而项目应用index没有定义模型,所以无须配置到Admin后台系统。
在Admin后台系统配置项目应用与模型,只需在项目应用的__init__.py
、apps.py和admin.py文件里编写功能代码即可。我们分别在commodity和shopper的__init__.py
、apps.py和admin.py文件编写以下代码:
在上述代码中,__init__.py
和apps.py文件是设置项目应用在Admin后台系统的名称,admin.py文件是将模型注册并绑定到Admin后台系统,详细的说明如下:
(1)__init__.py
是项目应用的初始化文件,在文件中设置属性default_app_config指向apps.py是定义AppConfig类。
(2)apps.py是定义AppConfig类,通过设置类属性verbose_name用于设置项目应用在Admin后台系统的名称,如图10-7所示。
(3)admin.py是将项目应用定义的模型注册并绑定到Admin后台系统,模型在Admin后台系统显示的名称由模型属性Meta的verbose_name和verbose_name_plural设置,如图10-8所示。如果在模型的Meta属性中分别设置verbose_name和verbose_name_plural,Django就优先显示verbose_name_plural的值。
在admin.py文件使用admin.site.register()方法将模型注册绑定到Admin后台系统,但在实际开发中不建议使用此方法实现,因为功能扩展性太差,无法满足开发需求。除此之外,还可以通过类的继承方式实现模型的注册绑定,我们在项目应用commodity和shopper的admin.py重新编写以下代码:
在项目应用commodity的admin.py分别设置admin.site.site_title、admin.site.site_header和admin.site.index_title,这些属性是设置Admin后台系统的网页标题,网页效果如图10-9所示。
定义的TypesAdmin、CommodityInfosAdmin、CartInfosAdmin和OrderInfosAdmin都是继承Django内置的ModelAdmin,分别对应模型Types、CommodityInfos、CartInfos和OrderInfos。每个模型对应的ModelAdmin类都重写了父类的属性和方法,通过这种方式可以满足各种复杂的开发需求。
以CommodityInfosAdmin为例,它设置了类属性list_display、search_fields和date_hierarchy,并且重新定义了函数方法formfield_for_dbfield()。我们在Admin后台系统的首页单击“商品信息”,进入模型CommodityInfos的数据列表页,CommodityInfosAdmin设置类属性的网页效果如图10-10所示。
在模型CommodityInfos的数据列表页右上方找到“增加商品信息”按钮,单击该按钮可以进入数据新增页,如图10-11所示。
我们知道,模型字段types为字符串类型(models.CharField),但该字段的数据信息是关联模型Types的seconds字段,在设计数据结构的时候,模型字段types和模型字段seconds是一对多的数据关系,但两者并没有使用外键关联,这是为了降低两个模型之间的耦合性。
在模型CommodityInfos的数据新增页中,为了使用模型字段types能关联模型字段seconds,我们重写了ModelAdmin的函数方法formfield_for_dbfield(),函数参数db_field代表模型的每个字段信息,当db_field.name =='types'(即模型字段等于types)的时候,我们对模型字段types设置下拉框,下拉框的数据来自模型Types,并且以二维元组或列表格式表示。
Django内置的ModelAdmin还定义了许多属性和函数方法,通过类的继承和重写可以实现各种复杂的功能需求,下一节将从源码角度深入分析ModelAdmin的底层原理。
分析ModelAdmin的底层原理
ModelAdmin的作用是将模型注册绑定到Admin后台系统,它定义了许多属性和函数方法,开发者通过类的继承和重写可以开发各种复杂的网页功能,我们在PyCharm中打开ModelAdmin的源码文件,如图10-12所示。
从图10-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的属性功能,以项目babys为例,在项目应用commodity的admin.py重新定义CommodityInfosAdmin,代码如下:
CommodityInfosAdmin演示了如何使用ModelAdmin的常用属性。运行项目babys,在浏览器上访问模型CommodityInfos的数据列表页,页面的样式和布局变化如图10-13所示。
在模型CommodityInfos的数据列表页的右上方单击“增加商品信息”按钮,浏览器将访问模型CommodityInfos的数据新增页,该页面的样式和布局的变化情况如图10-14所示。
最后在模型CommodityInfos的数据列表页里单击某行数据的ID字段,由ID字段的链接进入模型CommodityInfos的数据修改页,该页面的样式和布局的变化情况与数据新增页有相同之处,如图10-15所示。
通过10.2节定义的CommodityInfosAdmin与本节定义的CommodityInfosAdmin对比发现,ModelAdmin的属性主要用来设置Admin后台页面的样式和布局,使模型数据以特定的形式展示在Admin后台系统。而在下一节,我们将会讲述如何重写ModelAdmin的方法,实现Admin后台系统的二次开发。
自定义ModelAdmin的函数方法
我们已经掌握了Admin后台系统配置和ModelAdmin的属性设置,但是每个网站的功能和需求并不相同,这导致Admin后台系统的功能有所差异。因此,本节将重写ModelAdmin的函数方法,实现Admin的二次开发,从而满足多方面的开发需求。
为了更好地演示Admin的二次开发所实现的功能,在Admin后台系统里创建非超级管理员账号。在Admin首页的“认证和授权”下单击用户的增加按钮,设置用户名为root,密码为mydjango123,用户密码的长度和内容有一定的规范要求,如果不符合要求就无法创建用户,如图10-16所示。
用户创建后,浏览器将访问用户修改页面,我们需勾选当前用户的职员状态,否则新建的用户无法登录Admin后台系统,如图10-17所示。
除了设置人员状态之外,还需要为当前用户设置相应的访问权限,我们将Admin的所有功能的权限都给予root用户,如图10-18所示,最后单击“保存”按钮,完成用户设置。
已知get_readonly_fields()是由BaseModelAdmin定义的,它获取readonly_fields的属性值,从而将模型字段设为只读属性,通过重写此函数可以自定义模型字段的只读属性,比如根据不同的用户角色来设置模型字段的只读属性。
以项目babys为例,在CommodityInfosAdmin里重写get_readonly_fields()函数,根据当前访问的用户角色设置模型字段的只读属性,代码如下:
函数get_readonly_fields首先判断当前发送请求的用户是否为超级管理员,如果符合判断条件,就将属性readonly_fields设为空列表,使当前用户具有全部字段的编辑权限;如果不符合判断条件,就将模型字段types设为只读状态,使当前用户无法编辑模型字段types(只有只读权限)。
函数参数request是当前用户的请求对象,参数obj是模型对象,默认值为None,代表当前网页为数据新增页,否则为数据修改页。函数必须设置返回值,并且返回值为属性readonly_fields,否则提示异常信息。
运行babys,使用不同的用户角色登录Admin后台系统,在模型CommodityInfos的数据新增页或数据修改页看到,不同的用户角色对模型字段types的操作权限有所不同,比如分别切换用户admin和root进行登录,查看是否对模型字段types具有编辑权限。
设置字段样式
在Admin后台系统预览模型CommodityInfos的数据信息时,数据列表页所显示的模型字段是由属性list_display设置的,每个字段的数据都来自于数据表,并且数据以固定的字体格式显示在网页上。若要对某些字段的数据进行特殊处理,如设置数据的字体颜色,以模型CommodityInfos的字段types为例,将该字段的数据设置为不同的颜色,实现代码如下:
在模型CommodityInfos的定义过程中,我们自定义函数colored_name(),函数实现的功能说明如下:
(1)通过判断模型字段types的值来设置变量color_code,如果字段的值含有关键词“童装”,那么变量color_code等于red,否则为blue。
(2)将变量color_code和模型字段types的值以HTML表示,这是设置模型字段types的数据颜色,函数返回值使用Django内置的format_html方法执行HTML转义处理。
(3)为函数colored_name设置short_description属性,使该函数以字段的形式显示在Admin后台系统的模型CommodityInfos数据列表页。
模型CommodityInfos自定义函数colored_name是作为模型的虚拟字段,它在数据表里没有对应的表字段,数据由模型字段types提供。若将自定义函数colored_name显示在Admin后台系统,则可以在CommodityInfosAdmin的list_display属性中添加函数colored_name,代码如下:
运行babys,在浏览器上访问模型CommodityInfos的数据列表页,发现该页面新增了“带颜色的商品类型”字段,如图10-19所示。
函数get_queryset()用于查询模型的数据信息,然后在Admin的数据列表页展示。默认情况下,该函数执行全表数据查询,若要改变数据的查询方式,则可重新定义该函数,比如根据不同的用户角色执行不同的数据查询,以CommodityInfosAdmin为例,实现代码如下:
分析上述代码可知,自定义函数get_queryset的代码说明如下:
(1)通过super方法获取父类ModelAdmin的函数get_queryset所生成的模型查询对象,该对象用于查询模型CommodityInfos的全部数据。
(2)判断当前用户角色,如果为超级管理员,函数就返回模型CommodityInfos的全部数据,否则返回模型字段id小于2的数据。
运行babys,使用普通用户root登录Admin后台系统,打开模型CommodityInfos的数据列表页,页面上只显示id等于1的数据信息,如图10-20所示。
下拉框设置函数formfield_for_choice_field()如果模型字段设置了参数choices,并且字段类型为CharField,比如模型CommodityInfos的types字段,在Admin后台系统为模型CommodityInfos新增或修改某行数据的时候,模型字段types就以下拉框的形式表示,它根据模型字段的参数choices生成下拉框的数据列表。
若想改变非外键字段的下拉框数据,则可以重写函数formfield_for_choice_field()。以模型CommodityInfos的字段types为例,在Admin后台系统为字段types过滤下拉框数据,实现代码如下:
formfield_for_choice_field()函数设有3个参数,每个参数说明如下:
参数db_field代表当前模型的字段对象,由于一个模型可定义多个字段,因此需要对特定的字段进行判断处理。参数request是当前用户的请求对象,可以从该参数获取当前用户的所有信息。形参**kwargs为空字典,它可以设置参数widget和choices。widget是表单字段的小部件(表单字段的参数widget),能够设置字段的CSS样式;choices是模型字段的参数choices,可以设置字段的下拉框数据。
自定义函数formfield_for_choice_field()判断当前模型字段是否为types,若判断结果为True,则重新设置形参**kwargs的参数choices,并且参数choices有固定的数据格式,最后调用super方法使函数继承并执行父类函数formfield_for_choice_field(),这样能为模型字段types过滤下拉框数据。
运行babys,在Admin后台系统打开模型CommodityInfos的数据新增页或数据修改页,单击打开字段types的下拉框数据,如图10-21所示。
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()为准。
保存函数save_model()
函数save_model()是在新增或修改数据的时候,单击“保存”按钮所触发的功能,该函数主要对输入的数据进行入库或修改处理。若想在这个功能中加入一些特殊功能,则可对函数save_model()进行重写。比如对数据的修改实现日志记录,以CommodityInfosAdmin为例,函数save_model()的实现代码如下:
save_model()函数设有4个参数,每个参数说明如下:
参数request代表当前用户的请求对象。参数obj是模型的数据对象,比如修改模型CommodityInfos的某行数据(称为数据A),参数ojb代表数据A的数据对象,如果为模型CommodityInfos新增数据,参数ojb就为None。参数form代表模型表单,它是Django自动创建的模型表单,比如在模型CommodityInfos里新增或修改数据,Django自动为模型CommodityInfos创建表单CommodityInfosForm。参数change判断当前请求是来自数据修改页还是来自数据新增页,如果来自数据修改页,就代表用户执行数据修改,参数change为True,否则为False。
无论是修改数据还是新增数据,都会调用函数save_model()实现数据保存,因此函数会对当前操作进行判断,如果参数change为True,就说明当前操作为数据修改,否则为新增数据。
如果当前操作是修改数据,就从函数参数request、obj和form里获取当前数据的修改内容,然后将修改内容写入D盘的log.txt文件,最后调用super方法使函数继承并执行父类的函数save_model(),实现数据的入库或修改处理。若不调用super方法,则当执行数据保存时,程序只执行日志记录功能,并不执行数据入库或修改处理。
运行babys,使用超级管理员登录Admin后台系统并打开模型CommodityInfos的数据修改页,单击“保存”按钮实现数据修改,在D盘下打开并查看日志文件log.txt,如图10-22所示。
如果执行数据删除操作,Django就调用函数delete_model()实现,该函数设有参数request和obj,参数的数据类型与函数save_model()的参数相同。若要重新定义函数delete_model(),则定义过程可参考函数save_model(),在此就不再重复讲述。
数据批量处理
模型CommodityInfos的数据列表页设有“动作”栏,单击“动作”栏右侧的下拉框可以看到数据删除操作。只要选中某行数据前面的复选框,在“动作”栏右侧的下拉框选择“删除所选的商品信息”并单击“执行”按钮,即可实现数据删除,如图10-23所示。
从上述的数据删除方式来看,这种操作属于数据批量处理,因为每次可以删除一行或多行数据,若想对数据执行批量操作,则可在“动作”栏里自定义函数,实现数据批量操作。比如实现数据的批量导出功能,以模型CommodityInfos为例,在CommodityInfosAdmin中定义数据批量导出函数,代码如下:
数据批量操作函数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,在“动作”栏生成数据批量处理功能。
运行babys,在模型CommodityInfos的数据列表页全选当前数据,打开“动作”栏右侧的下拉框,选择“导出所选数据”,单击“执行”按钮执行数据导出操作,如图10-24所示。