第九章Admin后台系统
第九章 Admin后台系统
默认启用的Admin后台系统
'django.contrib.admin',
创建账户密码之前,确保项目已执行数据迁移,在数据库中创建相应的数据表
中文显示后台
把该中间件位于settings.py中的第三个中间件的位置
'django.middleware.locale.LocaleMiddleware',
注册模型到后台的两种方法
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 = ['name', 'age'] # list_display显示人员信息
# 注册方法二
# admin.site.register(PersonInfo, PersonInfoAdmin)
后台默认会有增加xxx(设置verbose_name即为该字段,否则为数据表名)
源码分析 ModelAdmin
位置:django/contrib/admin/options.py中
ModelAdmin继承自BaseModelAdmin,而父类BaseModelAdmin的元类为MediaDefiningClass,因此Admin系统的属性和方法来及ModelAdmin和BaseModelAdmin
列出日常开发中常用的属性和方法(关于admin后台该展示的字段和方法)
fields 在数据新增或修改的页面设置可编辑的字段
exclude 在数据新增或修改的页面设置不可编辑的字段
fieldsets 改变新增或修改页面的网页布局
# 在数据列表页设置日期选择器
date_hierarchy = 'recordTime'
# 设置可搜索的字段
search_fields = ['job', 'title']
# 为数据列表页的字段id和job设置编辑状态
list_editable = ['job', 'title']
实例models.py
from django.db import models
# Create your models here.
class PersonInfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=20)
age = models.IntegerField()
def __str__(self):
return self.name
class Meta:
verbose_name = '人员信息'
class Vocation(models.Model):
JOB = (
('软件开发', '软件开发'),
('软件测试', '软件测试'),
('需求分析', '需求分析'),
('项目管理', '项目管理'),
)
id = models.AutoField(primary_key=True)
job = models.CharField(max_length=20, choices=JOB)
title = models.CharField(max_length=20)
payment = models.IntegerField(null=True, blank=True)
person = models.ForeignKey(PersonInfo, on_delete=models.CASCADE)
recordTime = models.DateField(auto_now=True, null=True, blank=True)
def __str__(self):
return str(self.id)
class Meta:
verbose_name = '职业信息'
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)
@admin.register(Vocation)
class VocationAdmin(admin.ModelAdmin):
# 在数据新增或修改的页面设置可编辑的字段
# fields = ['job','title','payment','person']
# 在数据新增或修改的页面设置不可编辑的字段
# exclude = []
# 改变新增或修改页面的网页布局(admin后台的新增或修改,修改直接点解id号就可以)
fieldsets = (
('职业信息', {
'fields': ('job', 'title', 'payment')
}),
('人员信息', { # 第一项是显示信息,第二项是隐藏信息
# 设置隐藏与显示
'classes': ('collapse',),
'fields': ('person',),
}),
)
# 将下拉框改为单选按钮
# admin.HORIZONTAL是水平排列
# admin.VERTICAL是垂直排列
radio_fields = {'person': admin.VERTICAL}
# 在数据新增或修改的页面设置可读的字段,不可编辑
# readonly_fields = ['job',]
# 设置排序方式,['id']为升序,降序为['-id']
ordering = ['id']
# 设置数据列表页的每列数据是否可排序显示
sortable_by = ['job', 'title']
# 在数据列表页设置显示的模型字段
list_display = ['id', 'job', 'title', 'payment', 'person']
# 为数据列表页的字段id和job设置路由地址,该路由地址可进入数据修改页
# list_display_links = ['id', 'job']
# 设置过滤器,如有外键,应使用双下画线连接两个模型的字段
list_filter = ['job', 'title', 'person__name']
# 在数据列表页设置每一页显示的数据量
list_per_page = 100
# 在数据列表页设置每一页显示最大上限的数据量
list_max_show_all = 200
# 为数据列表页的字段id和job设置编辑状态
list_editable = ['job', 'title']
# 设置可搜索的字段
search_fields = ['job', 'title']
# 在数据列表页设置日期选择器
date_hierarchy = 'recordTime'
# 在数据修改页添加“另存为”功能
save_as = True
# 设置“动作”栏的位置
actions_on_top = False
actions_on_bottom = True
admin首页设置
修改title和header(登录界面),在admin.py中添加即可
admin.site.site_title = 'MyDjango后台管理' # 这个是显示在url栏目的上方的,像是网页的title
admin.site.site_header = 'MyDjango' # 只有这个显示在登录界面
若想将app应用的英文改为中文(后台管理页面的),也就是修改app在后台的名称,在admin所在文件夹的__init__.py文件夹下设置即可
当然下面方法可以复用到任何一个app下,但主项目文件夹下不可以
只需要复写default_app_config,他的构成是前半部分是应用名,后半部分统一为IndexConfig。所以只需根据apps.py中的name属性的值更改。
还需更改verbose_name,这个为真正的应用名
from django.apps import AppConfig
import os
# 修改App在Admin后台显示的名称
# default_app_config的值的前半部分是应用名,后半部分统一为IndexConfig
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):
name = get_current_app_name(__file__)
verbose_name = 'index应用' # 改应用名字在这里修改
admin后台系统首页的设置
包括:
项目应用的显示名称 :
init.py中
模型的显示名称:
models.py中的 verbose_name和verbose_name_plural
网页标题:
admin.py中设置admin.site.site_title 和
admin.site.site_head属性
如果项目中有多个应用,只需在一个应用下的admin.py设置即可(网页标题)
实现Admin的二次开发
重写ModelAdmin的方法,以下介绍的函数均是
get_readonly_fields
get_readonly_fields函数是由BaseModelAdmin定义的,他获取readonly_fields的属性值,从而将模型字段设为只读属性,通过重写此函数可以自定义模型字段的只读属性,比如根据不同的用户角色来设置模型字段的只读属性。
# 重写get_readonly_fields函数
# 设置超级管理员和普通用户的权限
def get_readonly_fields(self, request, obj=None):
# request是当前的请求对象,obj是模型对象默认值为None,代表当前网页为数据新增页,否则为数据修改页
if request.user.is_superuser: # 判断用户是否为超级管理员
self.readonly_fields = [] # 只读字段为空
else:
self.readonly_fields = ['payment'] # readonly_fields设置为只读字段
return self.readonly_fields
设置字段样式
数据列表页显示的模型字段是由list_display设置的,每个字段的数据都来自数据表,并且数据以固定的字体格式显示在网页上,若要对某些字段的数据进行特殊处理,如设置数据的字体颜色,。
# 自定义函数,设置字体颜色
# 但他不是模型字段,可在admin.py中用list_display.append('colored_name')加入模型的数据列表页
def colored_name(self):
if 'Lucy' in self.person.name: # 获取PersonInfo数据表的字段name,缘由person是外键
color_code = 'red'
else:
color_code = 'blue'
return format_html( # format_html内置的html转义
'<span style="color: {};">{}</span>', # 行内样式
color_code,
self.person.name,
)
# 设置Admin的标题
# short_description属性使该colored_name()函数以字段的形式显示在模型Vocation的数据列表页
colored_name.short_description = '带颜色的姓名' # 带颜色的姓名是标题,
# 捕获的Django内置函数和属性,即不需要导包的
# short_description使该模型类中的函数(colored_name())以字段的形式显示在模型(Vocation)的数据列表页
# format_html()内置的html转义,使用方法类似于format的f用法;f'name:{变量名}',这里变成了逗号分隔,这里是函数所以这样传参
函数get_queryset()
用于查询模型的数据信息,然后再Admin的数据列表页展示。默认情况下,该函数执行全表数据查询,若要改变数据的查询方式,则可重新定义该函数,比如根据不同的用户角色执行不同的数据查询。
# 根据当前用户名设置数据访问权限
def get_queryset(self, request):
qs = super().get_queryset(request) # 调用父类的该方法获取查询对象,用该查询对象查询模型Vocation的全部数据,否则返回模型字段id小于2的数据
if request.user.is_superuser:
return qs
else:
return qs.filter(id__lt=2)
formfield_for_foreignkey
新增或修改数据时,设置外键可选值,就是该字段为下拉框(外键通常都是一对多,一个person,对应着各种名字)
formfield_for_foreignkey只适用于模型一对一或一对多;多对多为formfield_for_manytomany
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'person': # db_field模型的字段对象,因为一个模型可以定义多个外键字段,所以需要对特定的字段做判断
if not request.user.is_superuser: # request为当前用户的请求对象
v = Vocation.objects.filter(id__lt=2)
# 将该模型字段id小于2的数据作为模型PersonInfo的查询条件参数
kwargs['queryset'] = PersonInfo.objects.filter(id__in=v) # 这其实就是外键关联查询吧
# 上述就是将整个查询得到的对象打包成字典对象发给父对象的方法
# 传递形参给父类的方法,由父类的函数从形参kwargs中获取参数queryset,从而实现数据的过滤
return super().formfield_for_foreignkey(db_field, request, **kwargs) # 不用父类试试,报错RecursionError: maximum recursion depth exceeded
formfield_for_choice_field
过滤下拉框数据
# 内置函数formfield_for_choice_field虽然能新增下拉框的选项内容,
# 但在保存数据的过程中,Django会提示新增的选项内容是无效的,因此该函数常用于过滤已存在的选项。
# db_field.choices获取模型字段的属性choices的值 ;这里就是重新定义内置函数formfield_for_choice_field
def formfield_for_choice_field(self, db_field, request, **kwargs):
if db_field.name == 'job': # 过滤下拉框的字段,默认为全部已添加的字段,再models.py中以定义
# 减少字段job可选的选项
kwargs['choices'] = (('软件开发', '软件开发'),
('软件测试', '软件测试'),)
return super().formfield_for_choice_field(db_field, request, **kwargs)
# 如果要对字段的下拉框’新增‘内容,可以重新定义formfield_for_dbfield函数
# Django首先执行formfield_for_dbfield,然后再执行formfield_for_choice_field
# 如果我们都重写了formfield_for_dbfield和formfield_for_choice_field
# 最后下拉框的选项以formfield_for_choice_field为准
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == 'job':
# 必须判断选项内容是否已存在db_field.choices,如果不判断,则会重复新增选项
if ('网页设计', '网页设计') not in db_field.choices:
db_field.choices += (('网页设计', '网页设计'),)
return super().formfield_for_dbfield(db_field, request, **kwargs)
save_model()
在后台页面单击添加(修改)XXX信息,单击保存按钮所触发的功能,该函数主要对输入的数据进行入库或修改处理。
若想加入的是一些特殊功能,可进行复写,如:添加日志数据,把你需要修改的字段写入文件中
def save_model(self, request, obj, form, change):
if change: # change判断当前请求是来自数据修改页还是来自数据新增页,数据修改页则为true
# 获取当前用户名
user = request.user.username
# 使用模型获取数据,’pk代表具有主键属性的字段‘
job = self.model.objects.get(pk=obj.pk).job
# 使用表单获取数据
person = form.cleaned_data['person'].name
# 写入日志文件
f = open('d://log.txt', 'a')
f.write(person + '职位:' + job + ',被' + user + '修改' + '\r\n')
f.close()
else:
pass
# 使用super在继承父类已有的功能下新增自定义功能
# 不调用super()方法的话,程序只执行保存日志,但不执行数据入库和修改处理
super().save_model(request, obj, form, change)
同样的执行数据删除操作也是一样的,Django调用函数delete_model()实现,该函数设有的默认参数为self,request.可以仿照save_model()函数来进行修改。可以实现在你删除数据时候,把删除的字段添加到日志文件中(本地新建个)
数据批量操作
def get_datas(self, request, queryset):
temp = []
for d in queryset: # 从已被勾选的数据对象里获取模型字段的数据内容
t = [d.job, d.title, str(d.payment), d.person.name] # 每行数据以列表t表示
temp.append(t) # 将列表t加入列表
f = open('d://data.txt', 'a')
for t in temp:
f.write(','.join(t) + '\r\n') # 写入数据\r\n \r\n,这样写就不会连在一起了
f.close()
# 设置提示信息
self.message_user(request, '数据导出成功!')
# 设置函数的显示名称
get_datas.short_description = '导出所选数据'
# 添加到“动作”栏
actions = ['get_datas'] # 内置属性actions
自定义Admin模板
源码路径:django/contrib/admin/templates/admin
这里使用继承admin模板,来自定义开发,最好别修改admin初始的模板,缘由是整个django的admin都会被更改,如果你没使用虚拟环境的话。
过程:
在模板文件templates下依次创建文件夹admin和index.
1.文件夹admin代表该文件夹里的模板文件用于Admin后台系统,而且文件夹必须命名为admin
2.文件夹index代表项目应用index,文件夹的命名必须与项目应用的命名一致。文件夹存放模板文件change_form.html,所有在项目应用index中定义的模型都会使用该模板文件生成网页信息。
3.如果将模板文件change_form.html放在admin文件夹下,那么整个Admin后台系统都会使用该模板文件生成网页信息。
重写的change_form.html来自Django内置模板文件admin/change_form.html
该实例是判断用户角色不同,来显示后台对应的页面,当然是在原始admin后台基础上新增了一部分
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %} <!--内置模板admin/change_form.html导入了该模板,所以自定义模板也需要-->
{% 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 on site" %}</a></li>
{% endif %}
{% endblock %}
自定义Admin后台系统
Admin后台系统为每个网页设置了具体的路由地址(啥意思)
重新定义Admin后台系统,比如常见的Xadmin和Django Suit,这些插件都是在Admin后台系统的基础上进行重新定义的
admin后台系统由类AdminSite实例化创建而成的,换句话说,重新定义类AdminSite即可实现Admin后台系统的自定义开发(源码位置:django/contrib/admin/sites.py)
admin后台系统的注册过程
当运行django时,Admin后台系统会随之运行。Admin的系统注册过程在源码apps.py中定义。
django.contrib.admin 中的 apps.py
综上
要实现Admin后台系统的自定义开发,就需要重新定义类AdminSite和改变Admin的系统注册过程。
简单讲解如何更换Admin后台系统的登录页面
在项目更目录下创建static文件,并加入所需的javascript文件和css文件,然后再把templates文件夹中放置登录页面login.html,最后在MyDjango文件夹创建文件myadmin.py和myapps.py.
实例
login.html
<!DOCTYPE html>
<html>
<head>
{% load static %}
<title>MyDjango后台登录</title>
<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 id="Login" name="Login" method="post" action="">
{% csrf_token %}
<li class="login-item">
<span>用户名:</span>
<input type="text" name="username" class="login_input">
<span id="count-msg" class="error"></span>
</li>
<li class="login-item">
<span>密 码:</span>
<input type="password" name="password" class="login_input">
<span id="password-msg" class="error"></span>
</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 : 10
};
CanvasParticle(config);
}
</script>
<script src="{% static "js/canvas-particle.js" %}"></script>
</body>
</html>
myadmin.py
在myadmin.py中定义类MyAdminSite,他继承父类admin.AdminSite并重写方法admin_view()和get_urls(),从而更改admin()后台的登录界面
from django.contrib import admin
from functools import update_wrapper
from django.views.generic import RedirectView
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.http import HttpResponseRedirect
from django.contrib.auth.views import redirect_to_login
from django.urls import include, path, re_path
from django.contrib.contenttypes import views as contenttype_views
class MyAdminSite(admin.AdminSite):
def admin_view(self, view, cacheable=False):
def inner(request, *args, **kwargs):
if not self.has_permission(request):
if request.path == reverse('admin:logout', current_app=self.name):
index_path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(index_path)
# 修改注销后重新登录的路由地址
return redirect_to_login(
request.get_full_path(),
'/login.html'
)
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
return update_wrapper(inner, view)
def get_urls(self):
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return self.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper, view)
urlpatterns = [
path('', wrap(self.index), name='index'),
# 修改登录界面的路由地址
path('login/', RedirectView.as_view(url='/login.html')),
path('logout/', wrap(self.logout), name='logout'),
path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
path(
'password_change/done/',
wrap(self.password_change_done, cacheable=True),
name='password_change_done',
),
path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
path(
'r/<int:content_type_id>/<path:object_id>/',
wrap(contenttype_views.shortcut),
name='view_on_site',
),
]
valid_app_labels = []
for model, model_admin in self._registry.items():
urlpatterns += [
path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
]
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
if valid_app_labels:
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
urlpatterns += [
re_path(regex, wrap(self.app_index), name='app_list'),
]
return urlpatterns
myapps.py
from django.contrib.admin.apps import AdminConfig
# 继承父类AdminConfig
# 重新设置属性default_site的值,使它指向MyAdminSite类
class MyAdminConfig(AdminConfig):
default_site = 'MyDjango.myadmin.MyAdminSite'
在settings.py中配置系统注册类MyAdminConfig.最后在配置静态资源文件static
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 = [os.path.join(BASE_DIR, 'static')]
笔记来源:Django Web应用开发实战