zoukankan      html  css  js  c++  java
  • 在Flask/Django中增加下载Excel的功能

    又好久没写博客了,因为公司在做的东西涉及到业务方面的比较多,没法写。
    最近在做下载功能,在网上能找到很多例子,但是都不太好用,自己半研究半照抄,终于搞出来了能用的东西。所以觉得应该记录一下。
     
    下载什么呢?下载Excel。我所维护的几个系统里,有一些数据,需要在页面上导出。以前的做法,我都是用定时任务提前把要下载的Excel生成好,保存在服务器的某个位置,但是这样做似乎太傻了。
    于是我现在用的是这样一种方法:在生成Excel的时候,最后保存为字节流,而不是一个文件;然后,在框架的response中,设置header,使返回的数据直接是下载类型的。
    这样,前端只要直接调用这个接口,就能返回一个可以下载的字节流数据,而下载完成后,保存下来的就是一个Excel了。
     
    那么具体要怎么做呢?
    首先还是生成Excel。这里我使用的是openpyxl。我之前试了一下用xlwt,其实使用起来也是蛮方便的,并不比openpyxl难用,但是有个致命的弱点:生成的xls文件,最大支持的行数为65535行。而我业务上要生成的Excel,动辄就是十万行起(摊手。xlsx格式的Excel是能够支持到一百万行的,所以也就没什么好说的了。
     
    下面我就分成Flask和Django两个版本来介绍一下,下载功能应该怎么做。
     
     
    1、Flask版本
     
    生成Excel的部分:
     
    wb = Workbook()
    ws = wb.active
    # 首行列名写入excel
    for i, t in enumerate(title):
        ws.cell(row=1, column=(i + 1)).value = t[1]
    # 数据部分写入excel
    title_fields = [t[0] for t in title]
    for i, _data in enumerate(data):
        one_row = [_data[t] for t in title_fields]
        for j, d in enumerate(one_row):
            ws.cell(row=(i + 2), column=(j + 1)).value = d
    这里我是做成了一个通用的写Excel的方法:
    data是一个字典,格式  {"key1": "value1", "key2": "value2", ...}
    title是一个二维数组,格式  [("key1": "第一列"), ("key2": "第二列"), ...]
    这样能保证title写入excel的时候保证跟想要的顺序一致。
     
    接下来就到了重点了:一般在保存Excel的时候,我们会用
     
    wb.save(filename)
    而这里我不是这样用的,我是将它保存为一个字节流:
     
    sio = BytesIO()
    wb.save(sio)
    接下来就是将这个流返回到浏览器端下载:
     
    response = Response()
    response.headers.add("Content-Type", "application/vnd.ms-excel")
    response.headers.add('Content-Disposition', 'attachment', filename=filename.encode("utf-8").decode("latin1"))
    sio.seek(0)
    response.data = sio.getvalue()
    return response
    要注意的是,这里面的filename有一个小小的尬点:在flask框架中,header里面的文件名会用latin1编码(flask框架的代码是这么写的):
     
     
    这就有点尴尬,我正常的filename如果有中文,并且不经编码,在这里就会报错——没错,就算是python3,中文编码一样能恶心你。
    而解决方法,就像上面写的那样,先encode成UTF-8编码,然后再decode成latin1。后面的事情就让框架去做吧。
    这样就可以了。当我们去下载的时候,在前端调用这个链接,就能自动下载Excel了,而我们的服务器上,也用不着傻傻地保存一份。
     
     
    完整的代码是这样的:
     
    app.py
    from flask import Flask
    from excel import generate_excel
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    
    @app.route('/excel')
    def download():
        data = [
            {"key1": 1, "key2": 2, "key3": 3},
            {"key1": 11, "key2": 22, "key3": 33},
            {"key1": 111, "key2": 222, "key3": 333},
            {"key1": 1111, "key2": 2222, "key3": 3333},
            {"key1": 11111, "key2": 22222, "key3": 33333},
        ]
        title = [("key1", "第一列"), ("key2", "第二列"), ("key3", "第三列")]
        filename = "测试Excel.xlsx"
        return generate_excel(title, data, filename)
    
    
    if __name__ == '__main__':
        app.run()
    excel.py
    from io import BytesIO
    from openpyxl import Workbook
    from flask import Response
    
    
    def generate_excel(title, data, filename):
        wb = Workbook()
        ws = wb.active
    
        # 首行列名写入excel
        for i, t in enumerate(title):
            ws.cell(row=1, column=(i + 1)).value = t[1]
        # 数据部分写入excel
        title_fields = [t[0] for t in title]
        for i, _data in enumerate(data):
            one_row = [_data[t] for t in title_fields]
            for j, d in enumerate(one_row):
                ws.cell(row=(i + 2), column=(j + 1)).value = d
    
        # 传给save函数的不是保存文件名,而是BytesIO流
        sio = BytesIO()
        wb.save(sio)
    
        response = Response()
        response.headers.add("Content-Type", "application/vnd.ms-excel")
        response.headers.add('Content-Disposition', 'attachment', filename=filename.encode("utf-8").decode("latin1"))
        sio.seek(0)
        response.data = sio.getvalue()
        return response

    2、Django版本

     
    如果用django的话,思路是一致的,只不过在实现方面有点出入。在返回流到浏览器下载这部分,django的写法是这样的:
     
    from django.http import HttpResponse
    from django.utils.encoding import escape_uri_path
     
    # ...从生成excel到wb.save(sio)都是一样的
     
    response = HttpResponse()
    response["Content-Type"] = "application/vnd.ms-excel"
    response["Content-Disposition"] = "attachment; filename*=UTF-8''%s" % escape_uri_path(filename)
    # 保存流
    sio.seek(0)
    response.write(sio.getvalue())
    return response
    其他部分就不贴了。django代码比较繁琐,全贴出来也没什么意义。
    使用django的话,其实还有专门的StreamingHttpResponse和FileResponse模块(FileResponse还是从StreamingHttpResponse继承来的),理论上来说应该能更方便,不过我没有尝试。毕竟我懒。
     
    OK那就这样。
  • 相关阅读:
    马士兵_聊天系统_知识储备库
    最近一些情况
    JAVA坦克大战项目练习日4
    JAVA坦克大战项目练习日3
    JAVA坦克大战项目练习日2
    JAVA坦克大战项目练习日1
    课后实战String的一些细节
    Java从入门到精通之数组篇
    58_自定义标签打包以及使用自定义标签教程
    javaWeb安全篇(1)——防盗链技术的实现
  • 原文地址:https://www.cnblogs.com/anpengapple/p/13195881.html
Copyright © 2011-2022 走看看