Xadmin开发后台管理系统

关注公众号“轻松学编程”了解更多。

添加小头像

https://blog.csdn.net/qq_34964399/article/details/80303544?utm_source=blogxgwz5

导航栏设置

https://www.cnblogs.com/adc8868/p/7506973.html

管理器常用显示设置

https://blog.csdn.net/weixin_33127753/article/details/80897240

添加插件

比如要做一个二级联动过滤查询数据,查找当前用户所办的套餐。

1、添加linkageFilter.py文件

在虚拟环境中找到/Lib/site-packages/xadmin/plugins(windows的在site-packages/xadmin/plugins),在该文件夹下添加一个linkDataFilter.py文件(文件命名随意),内容如下:

from xadmin.views import BaseAdminPlugin, DeleteAdminView
from xadmin.views.edit import CreateAdminView, UpdateAdminView
import xadmin # 点击增加记录时触发
class LinkageAddFilter(BaseAdminPlugin):
# 默认不加载,只在需要加载的options中设置True来加载
is_execute = False def init_request(self,*arg,**kwargs):
# return self.bool(is_execute)
return self.is_execute def get_media(self, media):
# 此处用来加入我们自己的js文件
media = media + self.vendor("xadmin.self.select.js")
return media
# 点击更新记录时触发
class LinkageUpdateFilter(BaseAdminPlugin):
# 默认不加载,只在需要加载的options中设置True来加载
is_execute = False def init_request(self,*arg,**kwargs):
# return self.bool(is_execute) return self.is_execute def get_media(self, media):
# 此处用来加入我们自己的js文件 media = media + self.vendor("xadmin.self.update_select.js")
return media # 点击删除记录时触发
class LinkageDeleteFilter(BaseAdminPlugin):
# 默认不加载,只在需要加载的options中设置True来加载
is_execute = False def init_request(self,*arg,**kwargs):
# return self.bool(is_execute)
return self.is_execute def get_media(self, media):
# 此处用来加入我们自己的js文件
media = media + self.vendor("xadmin.self.delete.js")
return media xadmin.site.register_plugin(LinkageAddFilter, CreateAdminView)
xadmin.site.register_plugin(LinkageUpdateFilter, UpdateAdminView)
xadmin.site.register_plugin(LinkageDeleteFilter, DeleteAdminView)

2、在plugins文件夹下的init.py中PLUGINS = ()元组中添加linkDataFilter

3、写js文件

在虚拟环境中找到/Lib/site-packages/xadmin/static/xadmin/js,在该文件夹下条件xadmin.self.select.js、xadmin.self.update_select.js和xadmin.self.delete_select.js三个文件

其中xadmin.self.select.js内容如下:

(function($){

  function linkage_query(){

        // 获取用户办的套餐
$("#div_id_package").click(function () {
// 从导航栏处获取用户名
var master_name = $('#top-nav').find('strong').text();
master_name = master_name.substring(4);
url = "selectInfo/?master=" + master_name;
      getSecNav(url,'#id_package');
});     function getSecNav(url,id_type){
      $.ajax({
        type:"GET",
        url:url,
        async:true,
        beforeSend:function(xhr){
          xhr.setRequestHeader("X-CSRFToken", $.getCookie("csrftoken"))
        },
        success:function(data){ $(id_type)[0].selectize.clearOptions(); //二级select清空选项
keys = Object.keys(data);//将JSON转换);
for (var i = 0; i < keys.length; i++) {
console.log(data[i]);
var item = data[keys[i]]; var test = {text: item.name, value: item.value, $order: i + 1}; //遍历数据,拼凑出selectize需要的格式
$(id_type)[0].selectize.addOption(test); //添加数据
}         },
        error:function(xhr){
          console.log(xhr);
          
        }
      });
    }
  }
  linkage_query();
})(jQuery)

4、urls.py和views.py处理js发起的请求

5、adminx.py配置

# 用户管理
class UserManageAdmin(object):
list_display = ['id', 'name','addtime','get_UserManage_Taocan' ]
search_fields = ['name']
list_filter = ['phone',]
ordering = ['-id'] # 进入xadmin页面将某个字段倒序排列
readonly_fields = ['addtime'] # 只读字段,不能编辑
# exclude = ['money'] # 不显示的字段
list_editable = ['name', ] # 即使编辑器
relfield_style = 'level' # 带有外键的字段变成搜索格式
model_icon = 'fa fa-user' # 表左边的图标
is_execute = True # 使用js插件
# 列聚合,可用的值:"count","min","max","avg", "sum"
aggregate_fields = {"id": "max"} # 显示统计数据 统计id字段最大的值 # 禁止页面批量删除,重写虚拟环境根目录下\Lib\site-packages\xadmin\views\edit.py中的has_delete_permission方法
def has_delete_permission(self,*args,**kwargs):
if args:
return True
return False # 自动添加管理员,重写虚拟环境根目录下\Lib\site-packages\xadmin\views\edit.py中的save_models方法
def save_models(self):
self.new_obj.user = self.request.user
flag = self.org_obj is None and 'create' or 'change'
if flag == 'create':
# 对密码字段进行加密
self.new_obj.password = encrypt_oracle(self.new_obj.password)
elif flag == 'change':
if 'password' in self.change_message():
self.new_obj.password = encrypt_oracle(self.new_obj.password)
else:
pass
super().save_models() # 设置用户只能查看自己填写的数据
def queryset(self):
qs = super(UserManageAdmin, self).queryset()
if self.request.user.is_superuser: # 超级用户可查看所有数据
return qs
else:
que = qs.filter(user=self.request.user)
return que

6、models.py

from django.db import models
from django.utils.safestring import mark_safe
from xadmin.plugins.auth import User # 用户管理
class UserManage(models.Model):
name = models.CharField(max_length=20, blank=True, null=True, verbose_name='用户名')
phone = models.CharField(max_length=11, unique=True, verbose_name='手机号')
money = models.DecimalField(max_digits=20, decimal_places=2,default=0, verbose_name='余额')
addtime = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
user = models.ForeignKey(User, on_delete=models.CASCADE, editable=False, null=True, verbose_name='管理员') # 显示时增加一列,比如显示用户办的卡
def get_UserManage_Taocan(self):
text = '增加一列</br>'
# mark_safe允许换行
return mark_safe(text) get_UserManage_Taocan.short_description = '用户套餐'

7、xadmin后台集成’导入‘插件,导入excel文件

效果图:

1、添加插件

在虚拟环境根目录\Lib\site-packages\xadmin\plugins中添加excel.py文件,内容如下:

from xadmin.views import BaseAdminPlugin, ListAdminView
from django.template import loader
import xadmin class ListExcelImportPlugin(BaseAdminPlugin):
# 重写init_request
import_excel = False
def init_request(self, *args, **kwargs):
return self.import_excel def block_top_toolbar(self, context, nodes):
# 这里 xadmin/excel/model_list.top_toolbar.import.html 是自己写的html文件
nodes.append(loader.render_to_string("xadmin/excel/model_list.top_toolbar.import.html")) xadmin.site.register_plugin(ListExcelImportPlugin, ListAdminView)

2、注册插件
在虚拟环境根目录\Lib\site-packages\xadmin\plugins__init__.py中,

PLUGINS = (
...
'excel', # 在这个元祖中添加元素,名称与插件文件名同名
...
)


3、添加html文件

在虚拟环境根目录\Lib\site-packages\xadmin\templates\xadmin\中增加文件夹excel,在文件夹中添加model_list.top_toolbar.import.html文件,内容如下:

{% load i18n %}
<div class="btn-group export">
<a class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown" href="#">
<i class="icon-share"></i> 导入数据 <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<li><a data-toggle="modal" data-target="#export-modal-import-excel"><i class="icon-circle-arrow-down"></i> 导入
Excel</a></li>
</ul> <div id="export-modal-import-excel" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="" enctype="multipart/form-data">
<!--{% csrf_token %}-->
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">导入 Excel</h4>
</div>
<div class="modal-body">
<input type="file" onchange="fileChange(this)" name="excel" id="submit_upload">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
<button class="btn btn-success" type="button" id="submit_upload_b"><i class="icon-share"></i> 导入
</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dalog -->
</div><!-- /.modal --> </div> <script type="text/javascript">
function fileChange(target) {
//检测上传文件的类型
var imgName = document.all.submit_upload.value;
var ext, idx;
if (imgName == '') {
document.all.submit_upload_b.disabled = true;
alert("请选择需要上传的 xls 文件!");
return;
} else {
idx = imgName.lastIndexOf(".");
if (idx != -1) {
ext = imgName.substr(idx + 1).toUpperCase();
ext = ext.toLowerCase(); if (ext != 'xls' && ext != 'xlsx') {
document.all.submit_upload_b.disabled = true;
alert("只能上传 .xls 类型的文件!");
return;
}
} else {
document.all.submit_upload_b.disabled = true;
alert("只能上传 .xls 类型的文件!");
return;
}
}
} $(document).ready(function () { $('#submit_upload_b').click(function () {
var form_data = new FormData();
var file_info = $('#submit_upload')[0].files[0];
form_data.append('file', file_info);
form_data.append('file_source', $('.breadcrumb li').eq(1).text().trim());
var url = window.location.protocol + '//' + window.location.host + '/importkdorderno/'
$.ajax({
url: url,
type: 'POST',
data: form_data,
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", $.getCookie("csrftoken"))
},
processData: false, // tell jquery not to process the data
contentType: false, // tell jquery not to set contentType
success: function (res) { alert(res.msg);
window.location.reload();
},
error: function (err) {
}
});
});
})
</script>


4、在views.py处理上传的excel文件

import pandas as pd
from rest_framework.views import APIView class ImportKDOrderNo(APIView): def post(self, request, *args, **kwargs):
file = request.FILES.get('file')
# read = InMemoryUploadedFile().open()
data = pd.read_excel(file) # 使用pandas处理excel文件
file_source = request.POST.get('file_source', '') # 文件来源 if '订单号' and '物流单号' not in data:
return Response(data={'msg': '文件格式有误,第一行第一列应该为【订单号】,第一行第二列应该为【物流单号】'})
ordernos = data['订单号']
logistics = data['物流单号']
for i in range(len(ordernos)):
print('订单号', ordernos[i], '物流单号', logistics[i]) return Response(data={'msg': '上传成功'})

5、在urls.py中添加访问路由

from django.urls import path
from 你的应用名称 import views app_name = '你的应用名称' urlpatterns = [
# 其他路由
...
# 导入物流单号
path('importkdorderno/', views.ImportKDOrderNo.as_view(), name='importkdorderno'),
]

8、xadmin后台用户操作表权限

虚拟环境根目录\Lib\site-packages\xadmin\views\base.py

可以找到:

在项目子应用下的adminx.py中使用

    import xadmin

    from machine.models import Machine

    class MachineAdmin(object):
list_display = ['code', 'user'] # 显示的字段 search_fields = ['code'] # 搜索的字段
list_filter = ['code', 'is_delete'] # 过滤的字段
ordering = ('-id',) # 按id降序排序
list_editable = ['is_delete', ] # 数据即时编辑
readonly_fields = ['user'] # 只读字段
list_per_page = 30 # 每页显示数据数量
model_icon = 'fa fa-cog fa-spin' # 左侧显示的小图标 def has_delete_permission(self, *args, **kwargs):
# 删除权限
if self.request.user.is_superuser: # 管理员才能增加
return True
return False def has_add_permission(self, *args, **kwargs):
if self.request.user.is_superuser: # 管理员才能增加
return True
return False def has_change_permission(self, *args, **kwargs):
if self.request.user.is_superuser: # 管理员才能修改
self.readonly_fields = [] # 设置管理员可以修改所有字段
return True
return False def queryset(self):
"""当前用户只能看到自己的数据"""
user = self.request.user
if user.is_superuser:
# 管理员可以查看所有数据
return self.model._default_manager.get_queryset()
# 当前用户只能查看自己的数据
return self.model.objects.filter(user=user) xadmin.site.register(MallMachine, MallMachineAdmin)

9、xadmin后台发送邮件找回密码

输入你用户绑定的邮箱

想要发送邮件,需要在settings.py中设置邮件发送器

settings.py最下面增加

    # ------------------------邮箱配置-----------------------------------------
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' #把要发送的邮件显示再控制台上,方便调试
EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.qq.com' # 如果是 163 改成 smtp.163.com
EMAIL_PORT = 465
EMAIL_HOST_USER = '邮箱账号' # 帐号
EMAIL_HOST_PASSWORD = '授权码' # 密码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

由于django2与xadmin有些地方不兼容,需要修改源码:

找到虚拟环境根目录\Lib\site-packages\xadmin\plugins\passwords.py

在passwords.py文件中大概79行,修改为:

    return password_reset_confirm(request=request, uidb36=uidb36, token=token,
template_name=self.password_reset_confirm_template,
token_generator=self.password_reset_token_generator,
set_password_form=self.password_reset_set_form,
post_reset_redirect=self.get_admin_url('xadmin_password_reset_complete'),
success_url=self.get_admin_url('xadmin_password_reset_complete'),
current_app=self.admin_site.name, extra_context=context).dispatch(request=request,
uidb64=uidb36,
token=token)

如图:

10、xadmin消息提醒

xadmin 使用

self.message_user(u'错误,','error')
self.message_user(u'警告,','warning')
self.message_user(u'恭喜,','success')
self.message_user(u'信息,','info')

效果

11、xadmin外键下拉框添加过滤


class MallGoodsAdmin(object):
"""商品管理"""
list_display = ['id', 'show_photo', 'nickname', 'merchant', 'goods_class', 'label',]
search_fields = ['nickname']
list_filter = ['goods_class', 'label',]
model_icon = 'fa fa-bars'
list_editable = ['goods_class', ]
# 重写虚拟环境根目录下\Lib\site-packages\xadmin\views\edit.py中的formfield_for_dbfield方法
def formfield_for_dbfield(self, db_field, **kwargs):
# 对MallGoodsClass这个表项的下拉框选择进行过滤
# MallGoods中有一个goods_class商品分类外键MallGoodsClass,过滤掉外键MallGoodsClass中
# master_class为空的值
if db_field.name == "goods_class":
kwargs["queryset"] = MallGoodsClass.objects.filter(master_class__isnull=False)
# 对assigned_recipient这个表项的下拉选择进行过滤
return db_field.formfield(**dict(**kwargs))
return super().formfield_for_dbfield(db_field, **kwargs) xadmin.site.register(models.MallGoods, MallGoodsAdmin)

12、xadmin即时编辑器去掉空标签


虚拟环境根目录下\Lib\site-packages\xadmin\plugins\editable.py,在大概

129行增加:

    form.fields[fields[0]].empty_label = None

13、用户增加的小组件,让其他用户可见

找到虚拟环境根目录\Lib\site-packages\xadmin\views\dashboard.py

在548行、554行

改为:

    @filter_hook
def get_widgets(self): if self.widget_customiz:
portal_pos = UserSettings.objects.filter(
key=self.get_portal_key())
if len(portal_pos):
portal_pos = portal_pos[0].value
widgets = [] if portal_pos:
user_widgets = dict([(uw.id, uw) for uw in UserWidget.objects.filter(page_id=self.get_page_id())])
for col in portal_pos.split('|'):
ws = []
for wid in col.split(','):
try:
widget = user_widgets.get(int(wid))
if widget:
ws.append(self.get_widget(widget))
except Exception as e:
import logging
logging.error(e, exc_info=True)
widgets.append(ws) return widgets return self.get_init_widget()

14 、xadmin导出插件处理,增加导出勾选数据项

常规的导出只有两个选择【导出表头】、【导出全部数据】

现在想要做的是增加一个选择,即【导出表头】、【导出全部数据】、【导出勾选数据】,如下图:

需要修改xadmin源代码,具体如下

1、加载js文件

找到虚拟环境\Lib\site-packages\xadmin\views\list.py,在607行增加’xadmin.plugin.importexport.js’,如下图所示

2、修改export.py,后端处理下载文件

找到虚拟环境\Lib\site-packages\xadmin\plugins\export.py

在84行把rows = context[‘results’]修改成如下函数

    # 新增导出所选数据
# rows = context['results']
rows = []
select_across = self.request.GET.get('_select_across', False) == '1'
selected = self.request.GET.get('_selected_actions', '')
if self.request.GET.get('selected', 'off') == 'on':
if not select_across:
selected_pk = selected.split(',')
for i in context['results']:
if str(i['object'].id) in selected_pk:
rows.append(i)
else:
rows = context['results']
else:
rows = context['results']

3、 修改model_list.top_toolbar.exports.html

找到虚拟环境\Lib\site-packages\xadmin\templates\xadmin\blocks\model_list.top_toolbar.exports.html

使用以下代码覆盖原文件

    {% load i18n %}
<div class="btn-group export">
<a id="export-menu" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown" href="#">
<i class="fa fa-share"></i> {% trans "Export" %} <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
{% for et in export_types %}
<li><a data-toggle="modal" data-target="#export-modal-{{et.type}}"><i class="fa fa-arrow-circle-down">
</i> {% trans "Export" %} {{et.name}}</a></li>
{% endfor %}
</ul> {% for et in export_types %}
<div id="export-modal-{{et.type}}" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<form method="get" action=""> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Export" %} {{et.name}}</h4>
</div>
<div class="modal-body">
{{ form_params|safe }}
<input type="hidden" name="export_type" value="{{et.type}}">
<!-- 增加 导出所选数据 一栏 -->
<input type="hidden" name="_selected_actions" value=""/>
<input type="hidden" name="_select_across" value=""/> <label class="checkbox">
{% if et.type == "xlsx" %}
<input type="checkbox" name="export_xlsx_header" checked="checked" value="on">
{% trans "Export with table header." %}
{% endif %}
{% if et.type == "xls" %}
<input type="checkbox" name="export_xls_header" checked="checked" value="on">
{% trans "Export with table header." %}
{% endif %}
{% if et.type == "csv" %}
<input type="checkbox" name="export_csv_header" checked="checked" value="on">
{% trans "Export with table header." %}
{% endif %}
{% if et.type == "xml" %}
<input type="checkbox" name="export_xml_format" checked="checked" value="on">
{% trans "Export with format." %}
{% endif %}
{% if et.type == "json" %}
<input type="checkbox" name="export_json_format" checked="checked" value="on">
{% trans "Export with format." %}
{% endif %}
</label>
<label class="checkbox">
<input type="checkbox" name="all" value="on"> {% trans "Export all data." %}
</label>
<!-- 增加 导出所选数据 一栏 -->
<label class="checkbox">
<input type="checkbox" name="selected" value="on"> 导出勾选数据
</label> </div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button> <button class="btn btn-success myexport glyphicon glyphicon-export " type="submit"><i
class="fa fa-share"></i> {% trans "Export" %}
</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dalog -->
</div><!-- /.modal -->
{% endfor %} </div> <script type="text/javascript">
// 如果是订单导出,把待出货订单设置成待收货订单
$(document).ready(function () {
$('.myexport').click(function () {
// 当把订单导出时,需要修改订单状态为待收货状态
var url = window.location.protocol + '//' + window.location.host + "/exportorder/";
$("input[name='_select_across']").val($("input[name='select_across']").val());
if ($("input[name='selected']").is(':checked')) {
var arr = [];
$.each($('.action-select'), function () {
if (true == $(this).prop('checked')) {
arr.push($(this).val());
}
});
if(arr.length == 0){
alert('请先勾选导出数据')
return false
}
}else{
var arr = []
var order_type = $('.breadcrumb li').eq(1).text().trim()
$('.grid-item').each(function (index, el) { arr.push($(el).find('td').eq(1).text().trim())
})
}
if (($('.breadcrumb > li').eq(1).text()).indexOf('订单') != -1) {
// 5秒后执行
setTimeout(function () {
$.ajax({
type: "POST",
url: url,
data: {'orderlist': JSON.stringify(arr), 'order_type': order_type,},
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", $.getCookie("csrftoken"))
},
success: function (data) {
window.location.reload();
},
error: function (xhr) {
alert("出现未知错误");
window.location.reload();
}
});
}, 5000);
}
});
})
</script>

15、常见问题及解决方案

1、使用django2.1+xadmin搭建后台有时会出现如下错误:

lib/python3.5/site-packages/django/forms/boundfield.py", line 93, in as_widget
renderer=self.form.renderer, TypeError: render() got an unexpected keyword argument 'renderer'

解决方案:
找到虚拟环境\Lib\site-packages\xadmin\views\dashboard.py ,在36行的render函数增加一个参数renderer=None:

def render(self, name, value, attrs=None, renderer=None)

2、 xadmin后台删除数据出现错误

    get_deleted_objects() takes 3 positional arguments but 5 were given

这是由于Django2.1版本和xadmin不兼容导致的

知道虚拟环境\Lib\site-packages\xadmin\plugins\actions.py

修改93行,

    deletable_objects, model_count, perms_needed, protected = get_deleted_objects(
queryset, self.opts, self.user, self.admin_site, using)

改为

    deletable_objects, model_count, perms_needed, protected = get_deleted_objects(
queryset, self.user, self.admin_site)

如果出现“‘User’ object has no attribute ‘request’”错误,则改为:

deletable_objects, model_count, perms_needed, protected = get_deleted_objects(
queryset, self, self.admin_site)

然后在adminx.py文件中对应的模型类中允许删除权限

    class MaterialAdmin(object):
"""素材库分类"""
list_display = ['id', 'name', 'class_id', 'is_delete', 'addtime'] def has_delete_permission(self, *args, **kwargs):
return True
xadmin.site.register(Material, MaterialAdmin)

3、xadmin后台无法显示下拉框完整内容

解决方案 在根目录中找到/static/xadmin/vendor/selectize/selectize.bootstrap3.css
在331行后加入 position: static;

4、Django 更改超级用户密码

在工程文件目录下敲入:

    python manage.py shell

再在python交互界面输入:

    from django.contrib.auth.models import User
user = User.objects.get(username = 'username')
user.set_password('new_password')
user.save()

5、xadmin后台加载数据慢,解决方案

list_filter: 过滤器要慎用,不要使用类似id这些数据量大的字段

    class MallUserAdmin(object):
"""用户管理""" list_display = ['id', 'tp_icon', 'nickname', 'phone', 'level', 'balance', 'province', 'city', 'quxian'] # 显示字段
search_fields = ['id', 'nickname', 'phone'] # 搜索
list_filter = ['level', 'province', 'city', 'quxian'] # 过滤器
# list_filter = ['id', 'level', 'province', 'city', 'quxian'] # 如果加id,xadmin加载回来的数据就会很慢,所以不要在过滤器上使用id
list_per_page = 30 # 默认每页数量
model_icon = 'fa fa-users' # 左侧图标
ordering = ['-id'] # 排序
readonly_fields = ['subscribe', 'wx_openid', 'phone'] # 只读字段
is_addbalance = True # 加载自定义的插件
relfield_style = 'fk-ajax' # 其他表如果外键到用户表就做ajax搜索查询,不一次性加载数据

6、xadmin使用富文本DjangoUeditor与时间插件产生冲突

现象:

原因:
加载富文本插件时,会覆盖bootstrap-clockpicker.js,bootstrap-datepicker.js,xadmin.widget.datetime.js这三个js文件
解决方案:
做一个时间插件,手动加载这个插件即可。
在adminx.py中添加:

from xadmin.views import BaseAdminPlugin, UpdateAdminView, CreateAdminView
from indent import models
class JkdPtGoodsAdmin(object):
"""商品表"""
list_display = ["shop", "ptgoods_name", 'price', 'pt_price', 'pt_size', 'pt_validhours',
'pt_state', 'is_sale']
list_filter = ['is_sale', 'pt_size', 'start_time', 'pt_size']
list_editable = ['price', 'start_time', 'end_time']
show_detail_fields = ['ptgoods_name']
search_fields = ['ptgoods_name']
style_fields = {"content": "ueditor"} # content字段使用富文本
show_all_rel_details = True
is_datetime = True # 加载自定义的时间插件
model_icon = 'fa fa-ellipsis-h' class DateTimeWidget(BaseAdminPlugin):
"""时间插件"""
# 默认不加载,只在需要加载的options中设置True来加载
is_datetime = False def init_request(self, *arg, **kwargs):
return self.is_datetime def get_media(self, media):
# 此处用来加入我们自己的js文件
media = media + self.vendor("xadmin.self.selectize.js",
"xadmin.self.select2.js",
"xadmin.self.select2_locale_zh-hans.js",
"xadmin.widget.select.js",
"xadmin.plugin.quick-form.js",
"xadmin.self.bootstrap-datepicker.js",
"xadmin.self.bootstrap-clockpicker.js",
"xadmin.widget.datetime.js", )
return media # 注册时间插件
xadmin.site.register_plugin(DateTimeWidget, CreateAdminView)
xadmin.site.register_plugin(DateTimeWidget, UpdateAdminView)
xadmin.site.register(models.JkdPtGoods, JkdPtGoodsAdmin)


xadmin.self.selectize.js、
xadmin.self.select2.js、
xadmin.self.select2_locale_zh-hans.js、
xadmin.self.bootstrap-clockpicker.js、
xadmin.self.bootstrap-datepicker.js
放到项目目录/static/xadmin/js下,
xadmin.self.selectize.js中的内容复制项目根目录/static/xadmin/vendor/selectize/selectize.js中的,
xadmin.self.select2.js中的内容复制项目根目录/static/xadmin/vendor/select2/select2.js中的,
xadmin.self.select2_locale_zh-hans.js中的内容复制项目根目录/static/xadmin/vendor/select2/select2_locale_zh-hans.js中的,
xadmin.self.bootstrap-clockpicker.js中的内容复制项目根目录/static/xadmin/vendor/bootstrap-clockpicker/bootstrap-clockpicker.js中的,
xadmin.self.bootstrap-datepicker.js中的内容复制项目根目录/static/xadmin/vendor/bootstrap-datepicker/js/bootstrap-datepicker.js中的.

xadmin.widget.datetime.js、xadmin.widget.select.js、xadmin.widget.datetime.js已经在项目目录/static/xadmin/js下了

最后在xadmin.main.js文件的最后加上以下代码:

/*动态加载cee*/
var windowPrefix = window.__admin_media_prefix__;
var linkList = [
windowPrefix + "vendor/select2/select2.css",
windowPrefix + "vendor/selectize/selectize.css",
windowPrefix + "vendor/bootstrap-clockpicker/bootstrap-clockpicker.css",
windowPrefix + "vendor/bootstrap-datepicker/css/datepicker.css",
]; for (var i = 0; i < linkList.length; i++) {
$("<link>")
.attr({
rel: "stylesheet",
type: "text/css",
href: linkList[i]
})
.appendTo("head");
}

如图

7、xadmin后台编辑多对多字段

在models.py定义了多对多字段,想要在编辑时可以灵活使用这个字段的话,可以按以下方法设置:
modes.py

class Book(models.Model):
title = models.CharField(verbose_name="书名", max_length=32)
second_title = models.CharField(verbose_name="副标题", max_length=32, blank=True, null=True)
author = models.CharField(verbose_name="作者", max_length=32)
translator = models.CharField(verbose_name="译者", max_length=32, blank=True, null=True)
intro = models.TextField(verbose_name="描述")
pic = models.FileField(verbose_name="封面图片", max_length=64, upload_to='book_cover', null=True, blank=True)
tags = models.ManyToManyField(Tags, verbose_name='书籍标签', blank=True)
prizes = models.ManyToManyField(Prizes, verbose_name='获奖详情', blank=True)
sump = models.IntegerField(verbose_name="收藏人数", default=0)
rate_num = models.IntegerField(verbose_name="评分人数", default=0)
num = models.IntegerField(verbose_name="浏览量", default=0)
published_time = models.DateField(blank=True, null=True, verbose_name='出版时间')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') class Meta:
db_table = 'book'
verbose_name = "图书"
verbose_name_plural = "图书" def __str__(self):
return self.title

adminx.py

# 书籍管理
class BookAdmin(object):
search_fields = ['title', 'author', 'intro'] # 检索字段
list_display = ['id', 'show_pic', 'title', 'second_title', 'author', 'translator', 'published_time', 'intro',
'tags', 'prizes', 'num', 'sump', 'rate_num'] # 要显示的字段
list_filter = ['published_time', 'tags', 'prizes'] # 分组过滤的字段
ordering = ('id',) # 设置默认排序字段,负号表示降序排序
list_per_page = 30 # 默认每页显示多少条记录,默认是100条
model_icon = 'fa fa-book' # 左侧小图标
list_editable = ['title', 'author', 'intro', 'published_time'] # 可编辑字段
style_fields = {'tags': 'm2m_transfer', 'prizes': 'm2m_transfer'} # 控制字段的显示样式
filter_horizontal = ('tags', 'prizes') # 水平选择编辑多对多字段

重点是设置style_fields 和filter_horizontal ,效果:

8、xadmin通过展开方式显示TextField字段类型

由于TextField字段类型内容可能很长,在后台显示时很占屏幕位置,可以通过按钮来控制显示,代码如下:
models.py中定义了一个TextField字段类型:

class Prizes(models.Model):
name = models.CharField(max_length=32, verbose_name="奖项")
intro = models.TextField(blank=True, null=True, verbose_name='简介') class Meta:
db_table = 'prizes'
verbose_name = "奖项"
verbose_name_plural = "奖项" def __str__(self):
return self.name

这里使用xadmin作为后台管理框架,在adminx.py中代码如下:

# 奖项管理
class PrizesAdmin(object):
search_fields = ['name'] # 检索字段
list_display = ['id', 'name', 'show_intro']
list_filter = ['name']
ordering = ('id',) def show_intro(self, obj):
# 显示简介
if not obj.intro:
return mark_safe('')
if len(obj.intro) < 20:
return mark_safe(obj.intro) short_id = f'{obj._meta.db_table}_short_text_{obj.id}'
short_text_len = len(obj.intro) // 4
short_text = obj.intro[:short_text_len] + '......'
detail_id = f'{obj._meta.db_table}_detail_text_{obj.id}'
detail_text = obj.intro text = """<style type="text/css">
#%s,%s {padding:10px;border:1px solid green;}
</style>
<script type="text/javascript"> function openShutManager(oSourceObj,oTargetObj,shutAble,oOpenTip,oShutTip,oShortObj){
var sourceObj = typeof oSourceObj == "string" ? document.getElementById(oSourceObj) : oSourceObj;
var targetObj = typeof oTargetObj == "string" ? document.getElementById(oTargetObj) : oTargetObj;
var shortObj = typeof oShortObj == "string" ? document.getElementById(oShortObj) : oShortObj;
var openTip = oOpenTip || "";
var shutTip = oShutTip || "";
if(targetObj.style.display!="none"){
if(shutAble) return;
targetObj.style.display="none";
shortObj.style.display="block";
if(openTip && shutTip){
sourceObj.innerHTML = shutTip;
}
} else {
targetObj.style.display="block";
shortObj.style.display="none";
if(openTip && shutTip){
sourceObj.innerHTML = openTip;
}
}
}
</script>
<p id="%s">%s</p>
<p><a href="###" οnclick="openShutManager(this,'%s',false,'点击关闭','点击展开','%s')">点击展开</a></p> <p id="%s" style="display:none">
%s
</p>
""" % (short_id, detail_id, short_id, short_text, detail_id, short_id, detail_id, detail_text)
return mark_safe(text) show_intro.short_description = '描述'

注意:复制代码后需要做如下修改:

一开始显示效果:

点击展开效果:

9、xadmin后台批量操作

adminx.py

import xadmin
from django.db.models import Sum
from xadmin.plugins.actions import BaseActionView class MyCountFeeAction(BaseActionView):
"""
用户余额统计
"""
action_name = "countuserfee" #: 相当于这个 Action 的唯一标示, 尽量用比较针对性的名字
description = u'统计用户总余额' #: 描述, 出现在 Action 菜单中, 可以使用 ``%(verbose_name_plural)s`` 代替 Model 的名字.
model_perm = 'view' # 权限 def do_action(self, queryset):
all_balance = MallUser.objects.all().aggregate(Sum('balance'))
return HttpResponse(f'用户总余额{all_balance}')
class UserAdmin(object):
"""用户信息管理"""
list_display = ['username', 'balance', 'status', 'addtime']
search_fields = ['username', ]
list_filter = ['status', 'addtime']
list_per_page = 30 # 默认每页数量
model_icon = 'fa fa-user'
list_editable = ['status']
ordering = ['-addtime']
actions = [ MyCountFeeAction] # 添加批量选择操作

10、xadmin过滤器外键显示特定值(比如只能过滤自己与超级管理员定义的数据)

首先,修改xadmin源码,修改xadmin/filters.py,在401行,做如下修改,

把self.lookup_choices = field.get_choices(include_blank=False)
改为:
# 调用自定义的方法
if hasattr(model_admin, '{field}_choices'.format(field=field.name)):
self.lookup_choices = getattr(model_admin, '{field}_choices'.format(field=field.name))(field, request,params, model,model_admin,field_path)
else:
self.lookup_choices = field.get_choices(include_blank=False)

如图:

然后,在adminx.py中定义过滤的方法:

import xadmin
from django.db.models import Q, Sum
from xadmin.plugins.actions import BaseActionView class MeasurePointAdmin(object):
# search_fields = ['user__name', 'user__account'] # 检索字段
list_display = ['num', 'elevation', 'correct_num', 'cumulative_amount']
list_filter = ['user', 'is_default', 'create_time'] # 分组过滤的字段
list_editable = ['num', 'elevation', 'correct_num', 'cumulative_amount']
ordering = ('id',) # 设置默认排序字段,负号表示降序排序
list_per_page = 30 # 默认每页显示多少条记录,默认是100条
model_icon = 'fa fa-users' # 左侧小图标
readonly_fields = ['user', 'is_default']
import_excel = True
actions = [MyCountFeeAction] # 定义的函数名必须是 字段名_choices
def user_choices(self, field, request, params, model, model_admin, field_path):
# 超级用户不做控制
if self.request.user.is_superuser:
return field.get_choices(include_blank=False) # 过滤器只显示自己与超级管理员
user_lst = field.related_model._default_manager.filter(Q(id=self.request.user.id) | Q(is_superuser=True))
# 返回格式 [('pk','标题'),]
return [(user.id, user.username) for user in user_lst]

效果:

后记

【后记】为了让大家能够轻松学编程,我创建了一个公众号【轻松学编程】,里面有让你快速学会编程的文章,当然也有一些干货提高你的编程水平,也有一些编程项目适合做一些课程设计等课题。

也可加我微信【1257309054】,拉你进群,大家一起交流学习。
如果文章对您有帮助,请我喝杯咖啡吧!

公众号

关注我,我们一起成长~~

xadmin开发后台管理系统常见问题的更多相关文章

  1. vue开发后台管理系统小结

    最近工作需要用vue开发了后台管理系统,由于是第一次开发后台管理系统,中间也遇到了一些坑,想在这里做个总结,也算是对于自己工作的一个肯定.我们金融性质的网站所以就不将代码贴出来哈 一.项目概述 首先工 ...

  2. 使用moy快速开发后台管理系统(一)

    moy是什么? moy 是基于模型框架 kero 和 UI 框架 neoui 实现的应用框架,是前端集成解决方案,为企业级应用开发而生.github地址:iuap-design/tinper-moy ...

  3. C#+Layui开发后台管理系统

    ​我是笑林新记,分享一下我一套C#开发的后台管理系统,希望对大家有帮助!欢迎关注微信公众号:笑林新记   后台开发语言:C# 前端框架:layui   前天用毛笔笔画制作了一个毛笔字效果的Logo,主 ...

  4. vue开发后台管理系统有感

    使用vue开发后台近一个月,今天终于完成得差不多了,期间也遇到很多的问题,所以利用现在的闲暇时间做个总结 使用element-ui基础,这次使用了vue-element-admin(github地址) ...

  5. React开发后台管理系统

    1.基础插件安装,less文件加载配置  安装基础插件 安装React-Router .Axios yarn add react-router-dom axios less-loader(router ...

  6. vue-cli+vue 2.0+element-ui+vue-router+echarts.js开发后台管理系统项目教程

    一.首先使用npm创建vue项目框架: 1.安装vue-cli:    $ npm install --global vue-cli 2.初始化项目:$ npm init webpack  项目名 3 ...

  7. 基于Laravel开发博客应用系列 —— 构建博客后台管理系统

    一个完整的博客应用不能没有后台管理系统.所以在本节中我们将继续完善博客应用 —— 开发后台管理系统. 1.创建路由 在上一节十分钟创建博客项目中,已经设置过了 app/Http/routes.php, ...

  8. Svelte Ui Admin后台管理系统|svelte3+svelteUI中后台前端解决方案

    基于svelte3.x+svelteKit+svelte-ui网页后台管理系统SvelteAdmin. Svelte-Ui-Admin 基于svelte3.x+svelteKit+vite3+echa ...

  9. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

随机推荐

  1. 077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象

    077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象 本文知识点:类和对象 说明:因为时间紧张,本人写博客过程中只是对知识点 ...

  2. sublime text3配置Python2、Python3的编译环境

    由于Python2.Python3使用量都很高,Python3虽然是未来趋势,但是目前个别库还是只支持Python2.所以,很多人会选择在电脑上安装两个版本的Python,那么使用sublime执行代 ...

  3. c++中CString:: Find , ReverseFind, Left, Right

    CString 是在MFC中的头文件 非MFC加上afx.h头文件 直接上代码: // ConsoleApplication1.cpp : Defines the entry point for th ...

  4. 使用 PL/SQL Developer 导入 .sql 文件

    操作系统:Windows 10 x64 PL/SQL Developer Version 12.0.7.1837 (64 bit) 01.226959 第一节:下载 Oracle Database X ...

  5. C语言中time_t数据类型详细介绍

    包含文件:<time.h> #ifndef __TIME_T #define __TIME_T     /* 避免重复定义 time_t */ typedef long     time_ ...

  6. HNOI 2015 【亚瑟王】

    看着洛谷里那一排任务计划,瑟瑟发抖...... 题目大意: 你有n张牌,每一张牌有一个发动的概率和造成的伤害值,游戏一共有r轮.对于每一轮游戏,你只能发动一张牌(在之前回合发动过的牌会被跳过,不予考虑 ...

  7. SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理|前后端分离(下)----筑基后期

    写在前面 在上一篇文章<SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期>当中,我们初步实现了SpringBoot整合Shiro ...

  8. Spring IOC 容器预启动流程源码探析

    Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...

  9. 获取Jetbrain全家桶激活码

    支持正版,本KEY仅用于体验软件 激活码 激活码一: 2GCA2ZHNKP-eyJsaWNlbnNlSWQiOiIyR0NBMlpITktQIiwibGljZW5zZWVOYW1lIjoi5r+A5r ...

  10. ansible-命令使用说明

    1. ansible命令的使用说明 ansible 主机或组-m 模块名-a '模块参数' ansible参数 表示调用什么模块,使用模块的那些参数 • 主机和组,是在/etc/ansible/hos ...