python-docx 导出Word写进内存中封装为response
因为公司需要将数据导出为word文件,恰好又是前后端分离开发,所以前端的请求需要携带Token。并不能简单的window.open() 。以及在后端处理Wold的时候用到python-docx模块写起来不是很顺手,就随便封装了一下,然后就是在return这个请求的时候将document对象转化为文件流返回等等吧。
代码如下
# -*- coding:utf-8 -*-
# 项目周报导出模块
# created by xiaofan
from io import BytesIO
from django.http import HttpResponse
from django.utils.http import urlquote
from docx import Document
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT, WD_COLOR_INDEX
from docx.shared import Pt, Cm, RGBColor # 磅数
from docx.oxml.ns import qn # 中文格式
class CreateTable:
"""
table 操作
"""
def __init__(self, rows, cols, width_list, height, document, font_size=10, font_color=(0, 0, 0), floats='LEFT',
font_name=u'仿宋'):
"""
添加rows行cols列表格
:param rows: 行
:param cols: 列
:param font_size: 字体大小
:param font_color: 字体颜色
:param floats: 居中格式
"""
self.rows = rows
self.cols = cols
self.font_name = font_name
self.width_list = width_list
self.height = height
self.table = document.add_table(rows=rows, cols=cols, style='Table Grid')
self.table.style.font.size = Pt(font_size)
self.table.style.font.name = font_name
self.table.style.element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
self.table.style.font.color.rgb = RGBColor(*font_color)
self.table.style.paragraph_format.alignment = getattr(WD_PARAGRAPH_ALIGNMENT, floats)
self.table.alignment = WD_TABLE_ALIGNMENT.CENTER
self.set_row_width()
def set_row_width(self):
for i in range(self.cols):
self.table.cell(0, i).width = Cm(self.width_list[i])
for i in range(self.rows):
self.table.rows[i].height = Cm(self.height)
def merge(self, tuple1, tuple2, text, font_name=u'仿宋', font_size=10, bold=False):
self.table.cell(*tuple1).merge(self.table.cell(*tuple2))
tr = self.table.cell(*tuple1).paragraphs[0].add_run(text)
tr.font.name = font_name
tr.font.size = Pt(font_size)
tr.font.bold = bold
xu = self.table.cell(*tuple1)
xu.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
xu.alignment = WD_TABLE_ALIGNMENT
xt, y = tuple1
self.table.rows[xt].height = Cm(self.height)
def write_row(self, tuple1, tuple2, data_list, font_size=11, bold=False):
LA, LB = tuple1
RA, RB = tuple2
if (LA == RA and RB >= LB) or (LB == RB and RA >= LA):
if LA == RA:
for index, i in enumerate(range(LB, RB)):
self._write((LA, i), data_list[index], font_size, bold)
return
if LB == RB:
for index, i in enumerate(range(LA, RA)):
self._write((i, LB), data_list[index], font_size, bold)
return
def __add_row(self):
return self.table.add_row().cells
def add_row(self, data_list, color=None, font_size=11, bold=False):
"""
:param bold:
:param font_size:
:param data_list: 数据列表
:param color: 字体下标和颜色组成的字典 ps: {0:'RED',2:'YELLOW}
:return:
"""
row_cells = self.__add_row()
for i in range(self.cols):
xa = row_cells[i]
xa.width = Cm(self.width_list[i])
xa.height = Cm(self.height)
xa.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
xx = xa.paragraphs[0].add_run(str(data_list[i]))
xx.font.name = self.font_name
xx.font.size = Pt(font_size)
xx.font.bold = bold
if color and i in color.keys():
xx.font.highlight_color = getattr(WD_COLOR_INDEX, color[i])
def _write(self, tuple1, text, font_size, bold):
xu = self.table.cell(*tuple1)
xu.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
xx = xu.paragraphs[0].add_run(text)
xx.font.name = self.font_name
xx.font.size = Pt(font_size)
xx.font.bold = bold
# 周报导出主方法
def weekreport_download():
def _write(xp, info, font_type, font_size, bold, underline):
xs = xp.add_run(info)
xs.font.name = font_type # 设置西文字体
xs.element.rPr.rFonts.set(qn('w:eastAsia'), font_type) # 设置段中文字体
xs.font.size = Pt(font_size)
xs.font.bold = bold
xs.underline = underline
def continue_P(xp, infos='', font_type=u'仿宋', font_size=15.0, bold=False, underline=False, undLs=None,
defaultundL=(1,)):
if not undLs:
_write(xp, infos, font_type, font_size, bold, underline)
else:
for index, ev in enumerate(undLs):
if index in [*defaultundL]:
_write(xp, ev, font_type, font_size, bold, True)
else:
_write(xp, ev, font_type, font_size, bold, False)
return xp
def add_P(floats='LEFT', infos='', font_type=u'仿宋', font_size=15.0, bold=False, underline=False, jump=0, undLs=None,
defaultundL=(1,)):
"""
:param floats: 对齐方式
:param infos: 写的内容
:param font_type: 字体类型
:param font_size: 字体大小
:param bold: 是否加粗
:param underline: 是否下划线
:param jump: 在句尾加几个换行
:param undLs: 是否是拼接字符串列表
:param defaultundL: 列表中第几个需要加下换线
:return:
"""
xs = document.add_paragraph() # 初始化建立第一个自然段
xs.alignment = eval('WD_PARAGRAPH_ALIGNMENT.' + floats) # 对齐方式为居中,没有这句默认为左对齐
if not undLs:
continue_P(xp=xs, infos=infos, font_type=font_type, font_size=font_size, bold=bold, underline=underline)
else:
for index, ev in enumerate(undLs):
if index in [*defaultundL]:
continue_P(xp=xs, infos=ev, font_type=font_type, font_size=font_size, bold=bold, underline=True)
else:
continue_P(xp=xs, infos=ev, font_type=font_type, font_size=font_size, bold=bold)
if jump:
[document.add_paragraph() for _ in range(jump)]
return xs
def add_section(direction=True, first=False):
"""
:param direction: 纸张方向 true为竖版 false横版
:param first: 是否是第一节
:return:
"""
if not first:
document.add_section()
header = document.sections[-1].header # 获取第一个节的页眉
header.is_linked_to_previous = False # 不继承上一节
paragraph = header.paragraphs[0] # 获取页眉的第一个段落
space_num = 47 if direction else 96
run = paragraph.add_run('编号: XXXX' + ' ' * space_num + 'XXXXX周报') # 添加页面内容
run.font.size = Pt(10)
run.underline = True
section = document.sections[-1]
if direction:
section.page_height = Cm(29.7)
section.page_width = Cm(21)
else:
section.page_width = Cm(29.7)
section.page_height = Cm(21)
document = Document()
document.styles['Normal'].font.name = u'仿宋' # 设置文档的基础字体
document.styles['Normal'].element.rPr.rFonts.set(qn('w:eastAsia'), u'仿宋') # 设置文档的基础中文字体
add_section(first=True)
add_P(infos='编号:2021-000', font_size=14, jump=3)
# 建立第一个自然段
add_P(floats='CENTER', infos='XXXXXX\n项目周报', font_size=36, bold=True, jump=1)
add_P(floats='CENTER', infos='(2021.03.08—2021.03.14)', font_size=16, bold=True, jump=11)
add_P(floats='CENTER', infos='2021年03月14日', font_size=22, bold=True, jump=1)
document.add_page_break()
# 建立第二个自然段
add_P(floats='CENTER', infos='项目周报', font_size=22, bold=True)
add_P(undLs=[' XX', ' XX', ' XX', ' XX', 'XXXX', ' 4.2 ', 'XX', ' XXX ', 'XXXX', ' XXX ',
'XXX', ' XXXX ', 'XX。'], font_size=12, bold=True, defaultundL=(1, 3, 5, 7, 9, 11))
document.add_page_break()
# 增加第二节
add_section(False)
# 插入表格
t1 = CreateTable(1, 13, floats='CENTER',
width_list=[1.04, 2.64, 1.78, 5.56, 1.86, 1.40, 1.40, 1.40, 1.40, 1.40, 1.40, 0.75, 1.74],
height=1.14, document=document)
t1.add_row([' ' for _ in range(13)])
t1.merge((0, 0), (1, 0), 'XX', bold=True)
t1.merge((0, 1), (1, 1), 'XXXXXX', bold=True)
t1.merge((0, 2), (1, 2), 'XXXX', bold=True)
t1.merge((0, 3), (1, 3), 'XX', bold=True)
t1.merge((0, 4), (1, 4), 'XX', bold=True)
t1.merge((0, 5), (0, 9), 'XX', bold=True)
t1.merge((0, 10), (1, 10), 'XX', bold=True)
t1.merge((0, 11), (1, 11), 'XX', bold=True)
t1.merge((0, 12), (1, 12), 'XX', bold=True)
t1.merge((1, 5), (1, 5), 'XX', bold=False)
t1.merge((1, 6), (1, 6), 'XX', bold=False)
t1.merge((1, 7), (1, 7), 'XX', bold=False)
t1.merge((1, 8), (1, 8), 'XX', bold=False)
t1.merge((1, 9), (1, 9), 'XX', bold=False)
t1.add_row(
[1, 'XX', 'XX', 'XX', '6000', '1', '500', '0', '0',
'XX', '0', 'XX', '0.0%'], color={12: 'RED'})
# 这里是将文件保存在内存中
bio = BytesIO()
document.save(bio)
bio.seek(0)
response = HttpResponse(bio, content_type='application/msword')
response['Access-Control-Expose-Headers'] = 'file_name'
response['file_name'] = '周报汇总'
response['Content-Disposition'] = 'attachment;filename=%s.doc' % urlquote('周报汇总')
return response