1、源码安装(待补充)
安装方法
复制 xadmin 目录到你的项目(从github下载 https://github.com/sshwsfc/xadmin/tree/django2)
pip install httplib2 django-formtools django-crispy-forms
# setting.py
INSTALLED_APPS = [
...,
'xadmin',
'crispy_forms',
'reversion',
]
# urls.py
import xadmin
xadmin.autodiscover()
from xadmin.plugins import xversion
xversion.register_models()
urlpatterns = [
url(r'^admin/', xadmin.site.urls)
]
2、左边菜单名称的修改,修改apps.py文件
class BlogConfig(AppConfig):
name = 'blog'
verbose_name = u'博客'
还需要在该应用下的init.py下添加
default_app_config = "blog.apps.BlogConfig"
3、页面显示中文
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
4、修改后台显示的网站名称和footer名称,菜单样式
from xadmin import views
class BaseSetting(object):
enable_themes = True #开启主题选择
use_bootswatch = True
class GlobalSettings(object):
site_title = "我的管理系统" #设置左上角title名字
site_footer = "504工作室" #设置底部关于版权信息
#设置菜单缩放
menu_style = "accordion" #左侧导航条修改可折叠
global_models_icon = {
User: "glyphicon glyphicon-user", UserDistrict: "fa fa-cloud"
} # 设置models的全局图标
xadmin.site.register(views.BaseAdminView, BaseSetting)
xadmin.site.register(views.CommAdminView, GlobalSettings)
5、modelview里面可以指定的参数
list_display=[] #要显示的字段
search_fields=[] #搜索的字段
list_filter = [] #过滤器
date_hierarchy =['publication_date'] #添加过滤(这里是过滤日期)
ordering = ['-publication_date',] #排序(这里以日期排序,加‘-’表示降序)
filter_horizontal = ('authors',) #filter_horizontal 从‘多选框’的形式改变为‘过滤器’的方式,水平排列过滤器,必须是一个 ManyToManyField类型,且不能用于 ForeignKey字段,默认地,管理工具使用`` 下拉框`` 来展现`` 外键`` 字段
filter_vertical = ['authors',]#同上filter_horizontal,垂直排列过滤器
raw_id_fields = ['publisher',] #将ForeignKey字段从‘下拉框’改变为‘文本框’显示
list_editable = ['csdevice'] #在列表页可直接编辑的字段
model_icon = 'fa fa-user-secret' #图标样式
style_fields = {'csdevice': 'm2m_transfer','csservice': 'ueditor',} #字段显示样式
refresh_times = [10, 60] #自动刷新时间
show_detail_fields=['ttdsn'] #在指定的字段后添加一个显示数据详情的一个按钮
relfield_style = 'fk-ajax' #涉及到外键下拉的时候使用ajax搜索的方式而不是全部列出的方式,比如在分类下拉很多的情况下,这个功能就很好用
free_query_filter=['字段1','字段2',......]#默认为 True , 指定是否可以自由搜索. 如果开启自由搜索, 用户可以通过 url 参数来进行特定的搜索
exclude=['字段1','字段2',......]#隐藏字段
aggregate_fields = {"expire": "max"}# 列聚合,在list表格下面会增加一行统计的数据,可用的值:"count","min","max","avg", "sum"
# 添加数据时候,一步一步提供数据,分块显示
wizard_form_list = [
("基础信息", ("name", "contact", "telphone", "address")),
("其它信息", ("customer_id", "expire", "description")),
]
grid_layouts = ("table", "thumbnails") #列表的布局方式,是以表格一行一条的方式还是类似于缩略图的方式展示的
list_per_page = 50 # 每页显示数据的条数
list_max_show_all = 200 #每页最大显示数据的条数
list_display_links=[] #指定列表显示的哪列可以点击跳转到详情更新页
preserve_filters=True #详细页面,删除、修改,更新后跳转回列表后,是否保留原搜索条件
save_as = False#详细页面,按钮为“Sava as new” 或 “Sava and add another”
save_as_continue = True#点击保存并继续编辑
save_on_top = False#详细页面,在页面上方是否也显示保存删除等按钮
radio_fields = {"ug": admin.VERTICAL} # 或admin.HORIZONTAL #详细页面时,使用radio显示选项(FK默认使用select)
show_full_result_count = True #列表时,模糊搜索后面显示的数据个数样式
7、自定义显示界面
可分为Main主区域和Side侧边区域,Main或Side中又可通过Fieldset再分多个区域。Fieldset为一个元组,第一个字段为需要设置的名称,其它字段均为模型中的字段名。如下:
class DeviceAdmin(object):
...
form_layout = (
Main(
Fieldset('基础信息',
'site', 'device_name', 'device_id', 'device_type', 'account', 'password'),
Fieldset('EXTRA',
'device_model', 'supplier', 'responsible_by', 'device_ip', 'sn_number'),
),
Side(
Fieldset('其它',
'buy_date', 'expire_date', 'note', 'attachment', 'date'),
)
)
8、根据登录用户过滤数据
需要根据登录用户或组过滤数据时,xadmin.py中改为重写queryset方法即可。如下:
class DeviceAdmin(object):
...
def queryset(self):
"""函数作用:使当前登录的用户只能看到自己负责的设备"""
qs = super(DeviceAdmin, self).queryset()
if self.request.user.is_superuser:
return qs
return qs.filter(area_company=Group.objects.get(user=self.request.user))
9、文件上传
settings.py文件增加以下代码:
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
urls.py文件增加路由设置:
from django.conf.urls import url
from django.conf import settings
from django.views.static import serve
urlpatterns = [
url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
]
然后就可以在models里面定义字段为图片或者文件类型了
class Userinfo(models.Model):
sex_choice={
('male','男'),
('female','女')
}
username=models.CharField("用户名",max_length=100)
sex=models.CharField('性别',max_length=10,choices=sex_choice,default='male')
image=models.ImageField('头像',upload_to='image/%Y%m',default='image/default.png',max_length=254)
10、增加自定义的actions操作,比如现在我需要对选中的用户批量加钱
- 1、创建adminx_actions.py文件,要代码如下
# coding:utf-8
from xadmin.plugins.actions import BaseActionView
from django.http import HttpResponse
class MoneyAddAction(BaseActionView):
# 这里需要填写三个属性
action_name = "money_add_100" #: 相当于这个 Action 的唯一标示, 尽量用比较针对性的名字
description = u'批量增加金钱' #: 描述, 出现在 Action 菜单中, 可以使用 ``%(verbose_name_plural)s`` 代替 Model 的名字.
model_perm = 'change' #: 该 Action 所需权限
# 而后实现 do_action 方法
def do_action(self, queryset):
# queryset 是包含了已经选择的数据的 queryset
for obj in queryset:
# obj 的操作
obj.money+=100
obj.save()
# 返回 HttpResponse
return HttpResponse("修改成功")
这里做药就是在do_action中对选中的queryset进行增删改查操作。
- 2、在adminx.py文件中,导入刚刚的文件并使用
from .adminx_actions import *
class UserAdmin(object):
list_display=['username','sex','image','money']
#list_filter=[]
model_icon='fa fa-address-card'
actions=[MoneyAddAction,] #这里使用刚刚创建的action
xadmin.site.register(Userinfo,UserAdmin)
如下图,刷新之后这里就有新建的action,点击之后则会执行相应的操作
11、图表的使用
在 Model OptionClass 中设定 data_charts 属性, 该属性为 dict 类型, key 是图表的标示名称, value 是图表的具体设置属性. 使用示例:
class RecordAdmin(object):
data_charts = {
"host_service_type_counts": {
'title' : '主机类型统计',
'x-field' : "service_type",
'y-field' : ("service_type"),
'option' : {
"series" : {"bars":{"align":"center", "barWidth": 0.8,"show":True}},
"xaxis" : {"aggregate":"count","mode":"categories"}
},
},
"host_idc_counts" : {
'title' : '机房统计',
'x-field' : "idc",
'y-field' : ("idc",),
'option' : {
"series" : {"bars":{"align":"center", "barWidth": 0.5,"show":True}},
"xaxis" : {"aggregate":"count","mode":"categories"}
}
}
}
图表的主要属性为:
title : 图表的显示名称
x-field : 图表的 X 轴数据列, 一般是日期, 时间等
y-field : 图表的 Y 轴数据列, 该项是一个 list, 可以同时设定多个列, 这样多个列的数据会在同一个图表中显示
order : 排序信息, 如果不写则使用数据列表的排序
在上面的属性中,如果只是这样子的话,默认展示的就是折线图,如果需要用到其它图表类型,可以重写option属性
data_charts = {
"host_service_type_counts": {
'title' : '主机类型统计',
'x-field' : "service_type",
'y-field' : ("service_type"),
'option' : {
"series" : {"bars":{"align":"center", "barWidth": 0.8,"show":True}},
"xaxis" : {"aggregate":"count","mode":"categories"}
},
},
"host_idc_counts" : {
'title' : '机房统计',
'x-field' : "idc",
'y-field' : ("idc",),
'option' : {
"series" : {"bars":{"align":"center", "barWidth": 0.5,"show":True}},
"xaxis" : {"aggregate":"count","mode":"categories"}
}
}
}
但是,到目前为止,仅发现以上的bars柱形图可以用,其它图形的设置没有找到。。。。
顺便提一下,在xadmin\static\xadmin\vendor\flot目录下,可以看到图表是使用jquery.flot.js渲染的,但是我使用里面的option参数去测试饼图的时候并没有成功,不懂要怎么处理。。。
============================2019.11.27更新内容==============
12、adminx.py配置views的时候,可以重定义以下函数实现其它自定义功能。
class JFLogsAdmin(object):
def queryset(self):
"""queryset对应的是列表页显示的数据内容"""
qs = super(JFLogsAdmin, self).queryset()
if not self.request.user.is_superuser:
qs=qs.filter(...) #按用户过滤
return qs
def formfield_for_dbfield(self, db_field, **kwargs):
"""formfield_for_dbfield是指在新增或者编辑内容的时候的数据,比如下面只显示指定部门的员工"""
print(db_field.name)
if not self.request.user.is_superuser:
# 对case这个表项的下拉框选择进行过滤
if db_field.name == "employee":
kwargs["queryset"] = Employee.objects.filter(dept=self.request.user.dept).order_by('id')
# 对assigned_recipient这个表项的下拉选择进行过滤
return db_field.formfield(**dict(**kwargs))
else:
attrs = self.get_field_attrs(db_field, **kwargs)
return db_field.formfield(**dict(attrs, **kwargs))
def save_models(self):
"""新增或者修改保存数据的时候调用,可以在保存之前修改数据内容或者添加其它只读字段的内容"""
self.new_obj.operator=self.request.user
super().save_models()
def post(self, request, *args, **kwargs):
"""在点击提交保存按钮的时候触发,可以对数据进行一个校验"""
#print(request.POST)
if request.POST.get('action') not in ['delete_selected']:
err_tip=''
jf=RulesRecord.objects.filter(id=request.POST.get('jf')).first()
#print(jf.person_limit,jf.date_limit,jf.times_limit,jf.jf_num,jf.jf_num_max)
if jf.person_limit=='每人':
if jf.date_limit=='不限':
exists_count=JFLogs.objects.filter(employee_id=request.POST.get('employee'),jf_id=request.POST.get('jf')).count()
if exists_count>=jf.times_limit:
err_tip="个人奖励次数已到达次数上限"
elif jf.date_limit=='每天':
start_date = datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day,0,0,0)
end_date = datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day,23,59,59)
#print(start_date,end_date)
exists_count=JFLogs.objects.filter(employee_id=request.POST.get('employee'),jf_id=request.POST.get('jf')).filter(inserttime__range=(start_date,end_date)).count()
#print(exists_count)
if exists_count>=jf.times_limit:
err_tip="个人奖励次数已到达每天上限"
elif jf.date_limit=='每周':
monday,sunday=get_current_week()
start_date = datetime.datetime(monday.year, monday.month, monday.day,0,0,0)
end_date = datetime.datetime(sunday.year, sunday.month, sunday.day,23,59,59)
exists_count=JFLogs.objects.filter(employee_id=request.POST.get('employee'),jf_id=request.POST.get('jf')).filter(inserttime__range=(start_date,end_date)).count()
#print(exists_count)
if exists_count>=jf.times_limit:
err_tip="个人奖励次数已到达每周上限"
else:
err_tip='未知错误'
elif jf.person_limit=='每部门':
dept=Employee.objects.filter(id=request.POST.get('employee')).first()
emps=Employee.objects.filter(dept=dept.dept).values("id")
#print(emps)
if jf.date_limit=='不限':
exists_count=JFLogs.objects.filter(employee_id__in=emps,jf_id=request.POST.get('jf')).count()
if exists_count>=jf.times_limit:
err_tip="部门奖励次数已到达上限"
elif jf.date_limit=='每天':
start_date = datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day,0,0,0)
end_date = datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day,23,59,59)
#print(start_date,end_date)
exists_count=JFLogs.objects.filter(employee_id__in=emps,jf_id=request.POST.get('jf')).filter(inserttime__range=(start_date,end_date)).count()
#print(exists_count)
if exists_count>=jf.times_limit:
err_tip="部门奖励次数已到达每天上限"
elif jf.date_limit=='每周':
monday,sunday=get_current_week()
start_date = datetime.datetime(monday.year, monday.month, monday.day,0,0,0)
end_date = datetime.datetime(sunday.year, sunday.month, sunday.day,23,59,59)
exists_count=JFLogs.objects.filter(employee_id__in=emps,jf_id=request.POST.get('jf')).filter(inserttime__range=(start_date,end_date)).count()
#print(exists_count)
if exists_count>=jf.times_limit:
err_tip="部门奖励次数已到达每周上限"
else:
err_tip='未知错误'
if err_tip:
messages.add_message(request, messages.ERROR, err_tip)
return HttpResponseRedirect('/xadmin/frontapp/jflogs/add/')
else:
obj=Employee.objects.filter(id=request.POST.get('employee')).first()
obj.jf_num+=int(request.POST.get('jf_num'))
obj.save()
return super(JFLogsAdmin, self).post(request, args, kwargs)
else:
if request.POST.get('post')=='yes':
print(request.POST)
idx=[int(x) for x in request.POST.getlist('_selected_action')]
print(idx)
jfs=JFLogs.objects.filter(id__in=idx)
for jf in jfs:
obj=Employee.objects.filter(id=jf.employee_id).first()
obj.jf_num-=jf.jf_num
if obj.jf_num<0:obj.jf_num=0
obj.save()
return super(JFLogsAdmin, self).post(request, args, kwargs)
14、书签的使用
以下是官方书签的示例,可以看到大概就是可以设置书签名称、和过滤条件。
class UserAdmin(object):
list_bookmarks = [{
'title': "书签名称", # 书签的名称, 显示在书签菜单中
'query': {'gender': True,'postdate__gte':20200327}, # 过滤参数, 是标准的 queryset 过滤
'order': ('-age','name'), # 排序参数
'cols': ('first_name', 'age', 'phones'), # 显示的列
'search': 'Tom' # 搜索参数, 指定搜索的内容
}, {...}
]
如何一打开就使用默认书签呢?我是这么做的:
1、修改\xadmin\plugins\bookmark.py文件,增加一个selected判断(大概是84行)
#修改前
selected = (current_qs == bk_qs)
#修改后
if not current_qs and 'selected' in bk:
selected=bk['selected']
current_qs = bk_qs
else:
selected = (current_qs == bk_qs)
2、然后配合adminx.py里面的list_bookmarks设置,增加selected参数
list_bookmarks = [{
'title': "书签名称", # 书签的名称, 显示在书签菜单中
'query': {'gender': True,'postdate__gte':20200327}, # 过滤参数, 是标准的 queryset 过滤
'selected':True
},]
3、当然,默认查询的queryset也要增加一个默认过滤条件
class NewsAdmin(object):
model_icon='fa fa-tasks'
list_display=['classify','postdate','title','go_to']
...
list_bookmarks = [{
'title': "今天新闻", # 书签的名称, 显示在书签菜单中
'query': {'postdate__exact': datetime.datetime.now().strftime('%Y%m%d')}, # 过滤参数, 是标准的 queryset 过滤
'order': ('classify','-postdate','orirank'), # 排序参数
'selected':True,
},
{
'title': "近两天新闻", # 书签的名称, 显示在书签菜单中
'query': {'postdate__gte': (datetime.datetime.now()+datetime.timedelta(-1)).strftime('%Y%m%d')}, # 过滤参数, 是标准的 queryset 过滤
'order': ('classify','-postdate','orirank'), # 排序参数
},
]
class Meta:
verbose_name="新闻"
verbose_name_plural=verbose_name
def queryset(self):
fillters={}
for k in self.request.GET.keys():
if '_p_' in k:
fillters[k.replace('_p_','')]=self.request.GET.get(k)
if not fillters:
fillters={'postdate__exact': datetime.datetime.now().strftime('%Y%m%d')} #如果没有选择标签就要增加一个默认的过滤条件
qs = super(NewsAdmin, self).queryset()
qs=qs.filter(**fillters)
return qs
==============2020-04-21更新==============
在导出数据的时候指定导出字段
1、修改extra_apps\xadmin\plugins\export.py的get_result_list方法
def get_result_list(self, __):
if self.request.GET.get('all', 'off') == 'on':
self.admin_view.list_per_page = sys.maxsize
#增加下面这一行
self.admin_view.list_display=getattr(self.admin_view,'list_export_fields', self.admin_view.list_display)
return __()
2、在admin.py或者xadmin.py中增加list_export_fields字段即可