django+openpyxl处理excel

前言

偶然想起了这个号, 太久没有写东西了,还是写一写基础知识。今天整理了个django 对于excel的处理,写成了通用类的形式,这样更方便调用。当然,对于python所有的框架都是适用的。

code

# -*- coding: utf-8 -*-

import tempfile
from zipfile import ZipFile, ZIP_DEFLATED

from django.http import HttpResponse, StreamingHttpResponse
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, Side, Border, PatternFill, Protection
from openpyxl.writer.excel import ExcelWriter


class ExcelUtils:
    """excel处理类"""

    def __init__(self, file_name, header: list = None, data: list = None, sheet_name="Sheet"):
        """
        :param sheet_name: sheet_name
        :param header: 表头
        :param data: 数据
        """
        self.file_name = file_name
        self.sheet_name = sheet_name
        self.header = header
        self.data = data

    def draw(self, header_styles=None, data_styles=None):
        """excel数据、样式设置并返回(导出)"""
        if data_styles is None:
            data_styles = {}
        if header_styles is None:
            header_styles = {}
        wb, sheet = self.excel_data_deal()
        self.excel_header_style(sheet, **header_styles)
        self.excel_data_style(sheet, **data_styles)
        # return self.excel_file_response(save_virtual_workbook(wb), self.file_name)
        return self.excel_stream_response(wb, self.file_name)

    def excel_data_deal(self):
        """创建excel并加入数据"""
        wb = Workbook()
        sheet = wb.create_sheet(self.sheet_name) if self.sheet_name != "Sheet" else wb.active
        if self.header is not None:
            sheet.append(self.header)

        for i, r_data in enumerate(self.data):
            for j, c_data in enumerate(r_data):
                sheet.cell(row=i + 2, column=j + 1, value=c_data)

        return wb, sheet

    def excel_header_style(self, sheet, **kwargs):
        """
        表头样式设置
        rows_width示例:设置E列宽度为30 {"E": 30}
        """
        sheet.row_dimensions[1].height = 20
        for i, r in enumerate(sheet[1]):
            width = len(r.value) * 3
            # 获取列字母
            column_letter = [chr(i + 65) if i <= 25 else 'A' + chr(i - 26 + 65)][0]
            # 列宽, 最大为50
            sheet.column_dimensions['{}'.format(column_letter)].width = width if width < 50 else 50
            self.write_excel_cell(r, **kwargs)

        if kwargs.get("rows_width"):
            for k, v in kwargs["rows_width"].items():
                sheet.column_dimensions[k].width = v

    def excel_data_style(self, sheet, **kwargs):
        """数据样式设置"""
        kwargs["fill"] = kwargs.get("fill", {})
        data_len = len(self.data)
        for i in range(data_len):
            for j, _ in enumerate(sheet.column_dimensions):
                kwargs["fill"]["fill_color"] = "00FFFF00" if j % 2 == 0 else "00FFFFFF"
                self.write_excel_cell(sheet.cell(row=i + 2, column=j + 1), **kwargs)

    @staticmethod
    def write_excel_cell(cell_obj, value=None, **kwargs):
        """
        单元格样式设置
        :param cell_obj: 单元格
        :param value: 单元格内容
        :alignment horizontal: 水平位置 "general", "left", "center", "right",
        :alignment vertical: 垂直位置 "top", "center", "bottom", "justify", "distributed"
        :alignment wrap: 是否换行
        :alignment shrink: 是否缩小填充
        :font size: 字体大小
        :font bold: 字体是否加粗,True/False
        :border side_style: 边框样式 默认thin
        :border side_color: 边框颜色 默认000000
        :fill fill_type: 填充样式 默认solid
        :fill fill_color: 填充颜色 默认无(白色)
        """
        if value is not None:
            cell_obj.value = value
        alignment = kwargs.get("alignment", {})
        font = kwargs.get("font", {})
        border = kwargs.get("border", {})
        fill = kwargs.get("fill", {})
        number_format = kwargs.get("number_format", "General")
        protection = kwargs.get("protection", {})

        # 位置:对齐方式等, 默认居中
        cell_obj.alignment = Alignment(horizontal=alignment.get("horizontal", "center"),
                                       vertical=alignment.get("vertical", "center"),
                                       wrapText=alignment.get("wrap", False),
                                       shrinkToFit=alignment.get("shrink", False))
        # 字体:字号、字体颜色、下划线等, 默认字体大小为10
        cell_obj.font = Font(size=font.get("size", None),
                             bold=font.get("bold", False))
        # 边框样式,默认黑色细线
        side = Side(style=border.get("side_style", "thin"),
                    color=border.get("side_color", "000000"))
        cell_obj.border = Border(left=side, right=side, top=side, bottom=side)
        # 填充:填充色、填充类型等,默认白色填充
        cell_obj.fill = PatternFill(fill_type=fill.get("fill_type", "solid"),
                                    fgColor=fill.get("fill_color", "00FFFFFF"))
        # 数据格式
        cell_obj.number_format = number_format
        # 写保护
        cell_obj.protection = Protection(locked=protection.get("locked", True),
                                         hidden=protection.get("hidden", False))

    @staticmethod
    def excel_file_response(content, file_name):
        """
        导出已处理的excel文件
        :param content: 文件内容
        :param file_name: 文件名
        """
        response = HttpResponse(content, content_type='application/vnd.ms-excel')
        response['Content-Disposition'] = 'attachment;filename=%s' % file_name.encode().decode('latin-1')
        return response

    def excel_stream_response(self, wb, file_name):
        """返回流式数据"""
        response = StreamingHttpResponse(self.wb_iterator(wb), content_type='application/octet-stream')
        response['Content-Disposition'] = f'attachment; filename="{file_name}"'
        return response

    @staticmethod
    def wb_iterator(workbook, chunk_size=512):
        """重写save_virtual_workbook方法,适配文件流"""
        tmp = tempfile.TemporaryFile()
        archive = ZipFile(tmp, 'w', ZIP_DEFLATED, allowZip64=True)

        writer = ExcelWriter(workbook, archive)
        writer.save()

        tmp.seek(0)
        while True:
            virtual_workbook_content = tmp.read(chunk_size)
            if virtual_workbook_content:
                yield virtual_workbook_content
            else:
                tmp.close()
                break

写这个类其实就是加深对openpyxl的理解以及使用,如果要实现excel的导出以及样式处理,直接使用draw方法就行,以我在项目的中的使用示例展示:

        excel = ExcelUtils(file_name, header, data)
        header_styles = dict(font={"size": 12, "bold": True}, fill={"fill_color": "00FF8080"}, rows_width={"E": 30})
        data_styles = dict(alignment={"shrink": True})
        return excel.draw(header_styles=header_styles, data_styles=data_styles)

只需要给表头和数据设置对应的样式即可,而样式设置在write_excel_cell这个函数中已经做了详细说明。
对于返回数据的问题这里我提供了两个函数excel_file_responseexcel_stream_response,从函数名就能知道其意思,如果小文件,一般使用excel_file_response,如果是以文件流形式导出则使用excel_stream_response,这里我重写了save_virtual_workbook方法,通过生成器每次只返回512字节的内容。当然,如果文件比较大建议提高chunk_size。

  • 另外,这是根据我个人理解以及体验来写的,有默认的样式,如果这个样式不符合你的需要也可以在单元格样式设置中改成你喜欢的样式即可。如果有不懂的或者错误之处可以在下面留言,copy代码请注明出处。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容