django xadmin开发后台管理系统常见问题及解决方法


原文 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\pluginsinit.py中,

PLUGINS = ( ... 'excel', # 在这个元祖中添加元素,名称与插件文件名同名 ... ) 在这里插入图片描述 3、添加html文件

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

{% load i18n %}

导入数据

在这里插入图片描述 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">×</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]

效果: 在这里插入图片描述