如何在Python中生成DOCX并将其保存在内存中?

时间:2017-09-02 06:51:12

标签: python python-docx

我的任务是从模板生成DOCX文件,然后通过Flask提供。我使用python-docx-templates只是python-docx的包装器,允许使用jinja模板。

最后他们建议使用StringIO将文件保存在内存中,这就是我的代码的样子:

def report_doc(user_id):
    # Prepare the data...

    from docxtpl import DocxTemplate

    doc = DocxTemplate(app.root_path+'/templates/report.docx')
    doc.render({
        # Pass parameters
    })
    from io import StringIO
    file_stream = StringIO()
    doc.save(file_stream)

    return send_file(file_stream, as_attachment=True, attachment_filename='report_'+user_id+'.docx')

保存时会抛出错误TypeError: string argument expected, got 'bytes'。谷歌搜索后,我发现this answer表示ZipFile期望BytesIO。但是,当我用BytesIO替换StringIO时,它只返回一个空文件,所以它不会抛出任何错误,但绝对不会保存文件。

在这种情况下究竟会起什么作用?如果这里的某些事情完全错误,一般来说这有什么用呢?

谢谢!

UPD:这是完全跟踪save函数调用的例外:

File "/ms/controllers.py", line 1306, in report_doc
    doc.save(file_stream)
  File "/.env/lib/python3.5/site-packages/docx/document.py", line 142, in save
    self._part.save(path_or_stream)
  File "/.env/lib/python3.5/site-packages/docx/parts/document.py", line 129, in save
    self.package.save(path_or_stream)
  File "/.env/lib/python3.5/site-packages/docx/opc/package.py", line 160, in save
    PackageWriter.write(pkg_file, self.rels, self.parts)
  File "/.env/lib/python3.5/site-packages/docx/opc/pkgwriter.py", line 33, in write
    PackageWriter._write_content_types_stream(phys_writer, parts)
  File "/.env/lib/python3.5/site-packages/docx/opc/pkgwriter.py", line 45, in _write_content_types_stream
    phys_writer.write(CONTENT_TYPES_URI, cti.blob)
  File "/.env/lib/python3.5/site-packages/docx/opc/phys_pkg.py", line 155, in write
    self._zipf.writestr(pack_uri.membername, blob)
  File "/usr/lib/python3.5/zipfile.py", line 1581, in writestr
    self.fp.write(zinfo.FileHeader(zip64))
TypeError: string argument expected, got 'bytes'

1 个答案:

答案 0 :(得分:4)

使用BytesIO实例是正确的,但在将其传递给send_file之前,您需要rewind the file pointer

  

确保文件指针位于数据的开头   在调用send_file()之前发送。

所以这应该有效:

import io
from docxtpl import DocxTemplate

def report_doc(user_id):
   # Prepare the data...

   doc = DocxTemplate(app.root_path+'/templates/report.docx')
   doc.render({
        # Pass parameters
   })
   file_stream = io.BytesIO()
   doc.save(file_stream)
   file_stream.seek(0)

   return send_file(file_stream, as_attachment=True, attachment_filename='report_'+user_id+'.docx')

(在Firefox上测试,我发现即使我指定了不同的文件名,浏览器仍然会从缓存中检索文件,因此您可能需要在测试时清除浏览器的缓存,或者如果您的开发工具中禁用缓存浏览器支持此功能,或调整Flask的cache control settings)。