一、问题

  xadmin后台功能很强大,特别在导出的时候格式有xls/xlsx、csv、xml、json。实际常用的还是前面2种。xls格式使用的xlwt,有个缺陷,导出数据过大时,会报ValueError: row index was 65536, not allowed by .xls format ...,使用xlsxwriter导出为xlsx格式,做个分页导出,十几万都不在话下(亲测)。

  在xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html源码中可以看到这5种格式:

后台导出按钮下拉只有4种:,xlsx格式在源码中可以找到答案。

  在xadmin/plugins/export.py中,,只有安装xlsxwriter依赖才会显示这个下拉按钮。

  二、思路

  xadmin目前是在点下载等待所有符合条件数据下载到内存再生成文件交给浏览器下载,如果下载数据太大就会拖死这个请求。所以在下载数据在内存过程中分段下载,前后端配合传参,将参数存到redis(django session是存数据库django_session表中,存session会报错Packet for query is too large,需要修改mysql参数,max_allowed_packet,默认是1兆,所以存储到redis),下载完删除。页数自增,每次后端根据总数和每次下载数确定状态还需不需要下载,最后标记下载完成,生成文件响应。

  三、实现效果图

  四、前后端代码

4-1.xadmin/templates/xadmin/blocks/model_list.top_toolbar.importexport.export.html

{% load i18n %}
<div class="btn-group export">
<a 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}}">
<label class="checkbox">
{% if et.type == "xlsx" %}
<input type="checkbox" name="export_xlsx_header" checked="checked" value="on"> {% trans "Export all data." %}
{% endif %}
{% if et.type == "xls" %}
<input type="checkbox" name="export_xls_header" checked="checked" value="on"> {% trans "Export all data." %}
{% endif %}
{% if et.type == "csv" %}
<input type="checkbox" name="export_csv_header" checked="checked" value="on"> {% trans "Export all data." %}
{% 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>#}
<input id="export_id" name="export_id" type="hidden" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
<button class="btn btn-success" id="export" type="submit" style="display:none;"><i class="fa fa-share"></i> {% trans "Export" %}</button>
<button class="btn btn-success" id="export_new" type="button"><i class="fa fa-share"></i> {% trans "Export" %}</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dalog -->
</div><!-- /.modal -->
{% endfor %} </div>
<script>
var lock = false;
var el = $('#export');
var elNew = $('#export_new');
var text = elNew.text(); callback = function(status){
lock = true;
if(status){
elNew.hide();
el.show();
}else{
elNew.text(text);
} } fetch = function(p,id,queryData){
baseData = {
'ajax': 1,
'_do_':'export',
'p': p,
'export_id': id,
'export_type':'xlsx',
} queryData = queryData||{} $.ajax({
'data': $.extend({},baseData,queryData),
'headers': {
'X-Requested-With':'-'
},
'success': function (res) {
if(res.status===0){
elNew.text('正在生成报表('+res.len+'/'+res.total+')');
fetch(++p,id)
}else{
callback(true)
}
},
'error':function(){
callback(false)
}
});
}; query = function(){
export_type = $("input[name='export_type']").val();
return {
'export_type':export_type,
}
} elNew.click(function(){
if (lock){
return false
}
lock = true;
elNew.text('正在生成报表...');
var id =Math.random();
$('#export_id').val(id);
fetch(0,id,query());
});
</script>

4-2.xadmin/plugins/export.py

    def _down(self,context):
# session中存储export_id {'dic':{'export_id':[]}} session_dic = {'export_id':[]} # params_obj = {'export_id': export_id,
# 'n': n,
# 'ajax': ajax}
from sms.channels import reids_db # if 'dic' in self.request.session:
# session_dic = self.request.session['dic']
# else:
# # 初始化dic
# self.request.session['dic'] = session_dic = {} if not reids_db.get('dic'):
reids_db.set('dic', {}, 24 * 3600)
# redis取出dic值为byte类型 使用eval转为字典
session_dic = eval(reids_db.get('dic')) # 从request对象获取export_id
if hasattr(self.request,'params_obj'):
export_id = self.request.params_obj['export_id']
else:
datas = self._get_datas(context)
return datas,{}
if export_id:
if export_id in session_dic:
# export_id 是否在redis中
global session_list
session_list = session_dic[export_id]
else:
# 初始化export_id
session_dic[export_id] = session_list = []
reids_db.set('dic', session_dic, 24 * 3600) # 初始化传给前端的数据
context_datas = {} if self.request.params_obj['ajax']:
if self.request.params_obj['p'] == '':
datas = self._get_datas(context)
else:
datas = self._get_datas(context)[1:]
session_list += datas
session_dic[export_id] = session_list
reids_db.set('dic', session_dic, 24 * 3600)
# 对比redis中data与总数 1:下载完,0:未下载完
# if len(session_list) + len(datas) >= context['result_count']:
if len(session_list) >= context['result_count']:
context_datas['status'] = 1
else:
context_datas['status'] = 0
# session_list += datas # 返回前端下载总数和当前进度
context_datas['total'] = context['result_count']
context_datas['len'] = len(session_list)
return '', context_datas
# 最后一次返回所有数据下载 # 不是ajax 删除redis中的export_id
if export_id:
datas = session_list
session_dic[export_id] = ''
reids_db.set('dic', session_dic, 24 * 3600)
else:
datas = self._get_datas(context)
return datas,context_datas def get_xlsx_export(self, context):
datas,context_datas = self._down(context)
output = io.BytesIO()
# export_header = (
# self.request.GET.get('export_xlsx_header', 'off') == 'on') model_name = self.opts.verbose_name
book = xlsxwriter.Workbook(output)
sheet = book.add_worksheet(
u"%s %s" % (_(u'Sheet'), force_text(model_name)))
styles = {'datetime': book.add_format({'num_format': 'yyyy-mm-dd hh:mm:ss'}),
'date': book.add_format({'num_format': 'yyyy-mm-dd'}),
'time': book.add_format({'num_format': 'hh:mm:ss'}),
'header': book.add_format({'font': 'name Times New Roman', 'color': 'red', 'bold': 'on', 'num_format': '#,##0.00'}),
'default': book.add_format()} # if not export_header:
# datas = datas[1:]
for rowx, row in enumerate(datas):
for colx, value in enumerate(row):
if rowx == 0:
cell_style = styles['header']
else:
if isinstance(value, datetime.datetime):
cell_style = styles['datetime']
elif isinstance(value, datetime.date):
cell_style = styles['date']
elif isinstance(value, datetime.time):
cell_style = styles['time']
else:
cell_style = styles['default']
sheet.write(rowx, colx, value, cell_style)
book.close() output.seek(0)
return output.getvalue(),context_datas def get_response(self, response, context, *args, **kwargs):
file_type = self.request.GET.get('export_type', 'csv')
content,context_datas = getattr(self, 'get_%s_export' % file_type)(context)
if 'status' in context_datas.keys():
response = HttpResponse(json.dumps(context_datas), content_type="application/json")
else:
response = HttpResponse(
content_type="%s; charset=UTF-8" % self.export_mimes[file_type]) file_name = self.opts.verbose_name.replace(' ', '_')
# response['Content-Disposition'] = ('attachment; filename=%s.%s' % (
# file_name, file_type)).encode('utf-8') # 修复导出时gunicorn报错ascii
from urllib.parse import quote
response["Content-Disposition"] = \
"attachment; " \
"filenane=%s.%s;" \
"filename*=UTF-8''%s.%s" %(
quote(file_name),file_type,
quote(file_name),file_type
) response.write(content)
return response

4-3.自己应用下的adminx.py中要做大量数据导出的model

class SMSLogAdmin(ReadonlyAdmin):
list_display = ['id', 'my_mobile', 'status', 'req_time', 'ret_time', 'account',
'my_tally', 'my_price']
list_filter = ['account', 'status', 'req_time',] @property
def list_per_page(self):
import re
path = self.request.get_full_path()
pattern_res = re.findall('ajax',path) export_id = self.request.GET.get('export_id')
p = self.request.GET.get('p')
ajax = self.request.GET.get('ajax') # 将前端传过来的参数放到request对象中
if not hasattr(self.request,'params_obj'):
self.request.params_obj = {'export_id': export_id,
'p': p,
'ajax': ajax} if pattern_res:
# 分段下载时,才每页显示500条
return 500
else:
return 50 @list_per_page.setter
def list_per_page(self,x):
return x model_icon = 'fa fa-commenting'
show_all_rel_details = False

4-4.xadmin/views/list.py

    @filter_hook
def get_context(self):
"""
Prepare the context for templates.
"""
self.title = _('%s List') % force_text(self.opts.verbose_name)
model_fields = [(f, f.name in self.list_display, self.get_check_field_url(f))
for f in (list(self.opts.fields) + self.get_model_method_fields()) if f.name not in self.list_exclude] new_context = {
'model_name': force_text(self.opts.verbose_name_plural),
'title': self.title,
'cl': self,
'model_fields': model_fields,
'clean_select_field_url': self.get_query_string(remove=[COL_LIST_VAR]),
'has_add_permission': self.has_add_permission(),
'app_label': self.app_label,
'brand_name': self.opts.verbose_name_plural,
'brand_icon': self.get_model_icon(self.model),
'add_url': self.model_admin_url('add'),
'result_headers': self.result_headers(),
'results': self.results(),
'result_count':self.result_count,# 将查询总数携带在context中
}
context = super(ListAdminView, self).get_context()
context.update(new_context)
return context

 

xadmin后台分段导出避免timeout的更多相关文章

  1. xadmin后台导出时gunicorn报错ascii

    django + xadmin + nginx + gunicorn部署后,xadmin后台导出model数据报错,gunicorn日志记录为:UnicodeEncodeError: 'ascii' ...

  2. 解决了好几天的关于django xadmin后台增加链接并执行函数的问题

    由于xadmin后台封装的完整性,想要在后台做一些改动对于新手来说还是有点困难,目前解决的第一个问题: 在admin后台增加链接,使其改变上级签收状态 如图 点击签收按钮之后,改变其状态 代码展示: ...

  3. 第三百九十四节,Django+Xadmin打造上线标准的在线教育平台—Xadmin后台进阶开发配置2,以及目录结构说明

    第三百九十四节,Django+Xadmin打造上线标准的在线教育平台—Xadmin后台进阶开发配置2,以及目录结构说明 设置后台列表页面可以直接修改字段内容 在当前APP里的adminx.py文件里的 ...

  4. 第三百九十三节,Django+Xadmin打造上线标准的在线教育平台—Xadmin后台进阶开发配置

    第三百九十三节,Django+Xadmin打造上线标准的在线教育平台—Xadmin后台进阶开发配置 设置后台某个字段的排序规则 在当前APP里的adminx.py文件里的数据表管理器里设置 order ...

  5. 自定义xadmin后台首页

    登陆xadmin后台,首页默认是空白,可以自己添加小组件,xadmin一切都是那么美好,但是添加小组件遇到了个大坑,快整了2个礼拜,最终实现想要的界面.初始的页面如图: 本机后台显示这个页面正常,do ...

  6. 第三百八十节,Django+Xadmin打造上线标准的在线教育平台—将所有app下的models数据库表注册到xadmin后台管理

    第三百八十节,Django+Xadmin打造上线标准的在线教育平台—将所有app下的models数据库表注册到xadmin后台管理 将一个app下的models数据库表注册到xadmin后台管理 重点 ...

  7. 5.3 将users表添加到xadmin后台

    在users模块中添加adminx.py文件,是xadmin后台管理默认的文件名,内容是: from .models import EmailVerifyRecord, Banner import x ...

  8. 安装xadmin后台管理插件

    django自带的admin后台管理功能太少.使用国人开发的xadmin后台,使用pip install xadmin安装在线包时,会出错,其中的README.rst是utf8格式,我们win7系统默 ...

  9. Xadmin后台管理系统搭建基于Django1.11.11+Python3.6

    安装python及Django百度即可 主要介绍Xadmin安装 访问地址:https://github.com/sshwsfc/xadmin  下载 安装好之后,将xamdin目录复制到项目 我放在 ...

随机推荐

  1. Android开发代码规范总结

    本篇开始总结Android开发中的一些注意事项,提高代码质量(仅供参考): 1.  Activity间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable 的方式,可以考虑 ...

  2. Dreamweaver 1 网页制作

    1.站点 1.1 创建站点 点击菜单栏中站点进行站点创建,输入站点名称,路径 1.2 设置图像文件夹 1.3 站点管理 站点的编辑.复制.删除 2.页面属性栏 2.1 外观 1.设置页面整体的字体.大 ...

  3. Qt--信号槽传递自定义结构体参数

    自定义结构体参数的信号槽连接 (1) 对于自定义的结构体参数,信号槽无法识别参数,导致信号槽连接不起作用.所以需要注册结构体参数.在结构体中声明结束的地方加上结构体注册. struct DealDet ...

  4. sql server auto increment - trace flag 272

    从 sql 2012 开始, 微软为了让 insert 时 auto increment 快一些,做了一个 cache 的机制. 这个机制虽然好,但是也有麻烦的情况,如果你的 sql 突然 resta ...

  5. springmvc如何进行热部署开发

    1.场景还原 在工程量大的情况下,tomcat运行部署一次会花费相当多的时间,这样太 浪费人力以及时间了:今天笔者将 讲解一下如何配置springmvc工程的热部署 2.实现方案 其实很简单! ①在t ...

  6. 雷林鹏分享:C# 判断

    C# 判断 判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的). 下面是大多数编程语言中典型的判断结构的一般形式: 判断语句 C ...

  7. English trip M1 - PC7 Can I Borrow Your Ping-Pong? Teacher:Patrick

    In this lesson you will learn to desribe abilities.  这节课你将学习到描述你的能力 课上内容(Lesson) 三种常见情态动词 can aux. 能 ...

  8. BroadcastReceiver(广播)的静态注册和动态注册 --Android开发

    BroadcastReceiver是安卓四大组件之一,本例通过代码的方式演示静态注册和动态注册. 1.静态注册 静态注册只需要AndroidManifest.xml中进行配置: AndroidMani ...

  9. Wannafly挑战赛26 B 冥土追魂

    首先,证明结果一定是取某些整行,再加上一个多余的一行的前几个. 假如: x1<=x2<=x3<=x4<=x5 y1<=y2<=y3<=y4<=y5 取6 ...

  10. android ------- 运行官方NDK 案例HelloJNI

    下载案例 HelloJNI ,导入工程到Eclipse,  可以直接下载我的案例, 源码下载:https://github.com/DickyQie/android-ndk 目录图 使用命令生成  . ...