osbzr/gooderp_addons

View on GitHub
report_docx/report/report_helper.py

Summary

Maintainability
A
3 hrs
Test Coverage
# -*- coding: utf-8 -*-
# © 2016 cole
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from docxtpl import DocxTemplate

import docx

import jinja2

"""
使用一个独立的文件来封装需要支持图片等功能,避免污染report_docx.py
"""


def calc_length(s):
    """
    把字符串,数字类型的参数转化为docx的长度对象,如:
    12 => Pt(12)
    '12' => Pt(12)
    '12pt' => Pt(12)  单位为point
    '12cm' => Cm(12)  单位为厘米
    '12mm' => Mm(12)   单位为毫米
    '12inchs' => Inchs(12)  单位为英寸
    '12emu' => Emu(12)
    '12twips' => Twips(12)
    """
    if not isinstance(s, str):
        # 默认为像素
        return docx.shared.Pt(s)

    if s.endswith('cm'):
        return docx.shared.Cm(float(s[:-2]))
    elif s.endswith('mm'):
        return docx.shared.Mm(float(s[:-2]))
    elif s.endswith('inchs'):
        return docx.shared.Inches(float(s[:-5]))
    elif s.endswith('pt') or s.endswith('px'):
        return docx.shared.Pt(float(s[:-2]))
    elif s.endswith('emu'):
        return docx.shared.Emu(float(s[:-3]))
    elif s.endswith('twips'):
        return docx.shared.Twips(float(s[:-5]))
    else:
        # 默认为像素
        return docx.shared.Pt(float(s))


def calc_alignment(s):
    """
    把字符串转换为对齐的常量
    """
    A = docx.enum.text.WD_ALIGN_PARAGRAPH
    if s == 'center':
        return A.CENTER
    elif s == 'left':
        return A.LEFT
    elif s == 'right':
        return A.RIGHT
    else:
        return A.LEFT


@jinja2.contextfilter
def picture(ctx, data, width=None, height=None, align=None):
    """
    把图片的二进制数据(使用了base64编码)转化为一个docx.Document对象

    data:图片的二进制数据(使用了base64编码)
    width:图片的宽度,可以为:'12cm','12mm','12pt' 等,参考前面的 calc_length()
    height:图片的长度,如果没有设置,根据长度自动缩放
    align:图片的位置,'left','center','right'
    """

    if not data:
        return None

    # 转化为file-like对象
    # 在python2.7中,bytes==str,可以直接使用
    # 在python3.5中,bytes和str是不同的类型,需要使用base64这个库

    # data使用了base64编码,所以这里需要解码
    data = data.decode('base64')

    import io
    data = io.BytesIO(data)

    tpl = ctx['tpl']
    doc = tpl.new_subdoc()

    if width:
        width = calc_length(width)
    if height:
        height = calc_length(height)

    p = doc.add_paragraph()
    p.alignment = calc_alignment(align)
    p.add_run().add_picture(data, width=width, height=height)
    return doc


def get_env():
    """
    创建一个jinja的enviroment,然后添加一个过滤器 
    """
    jinja_env = jinja2.Environment()
    jinja_env.filters['picture'] = picture
    return jinja_env


def test():
    """
    演示了如何使用,可以直接执行该文件,但是需要使用自己写的docx模版,和图片
    """
    tpl = DocxTemplate("tpls/test_tpl.docx")
    # 读取图片的数据且使用base64编码
    data = open('tpls/python_logo.png', 'rb').read().encode('base64')
    obj = {'logo': data}
    # 需要添加模版对象
    ctx = {'obj': obj, 'tpl': tpl}
    jinja_env = get_env()
    tpl.render(ctx, jinja_env)

    tpl.save('tpls/test.docx')


def main():
    test()


if __name__ == '__main__':
    main()