report_docx/report/report_docx.py
# -*- coding: utf-8 -*-
# © 2016 Elico Corp (www.elico-corp.com).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.report.report_sxw import report_sxw
import logging
import random
from docxtpl import DocxTemplate
from odoo.tools import misc
import ooxml
from ooxml import parse, serialize, importer
import codecs
from datetime import datetime
import pdfkit
_logger = logging.getLogger(__name__)
import pytz
from odoo import models
from odoo import fields
from odoo import api
import tempfile
import os
class DataModelProxy(object):
'''使用一个代理类,来转发 model 的属性,用来消除掉属性值为 False 的情况
且支持 selection 字段取到实际的显示值
'''
DEFAULT_TZ = 'Asia/Shanghai'
def __init__(self, data):
self.data = data
def _compute_by_selection(self, field, temp):
if field and field.type == 'selection':
selection = field.selection
if isinstance(selection, basestring):
selection = getattr(self.data, selection)()
elif callable(selection):
selection = selection(self.data)
try:
return [value for _, value in selection if _ == temp][0]
except KeyError:
temp = ''
return temp
def _compute_by_datetime(self, field, temp):
if field and field.type == 'datetime' and temp:
tz = pytz.timezone(
self.data.env.context.get('tz') or self.DEFAULT_TZ)
temp_date = fields.Datetime.from_string(temp) + tz._utcoffset
temp = fields.Datetime.to_string(temp_date)
return temp
def _compute_temp_false(self, field, temp):
if not temp:
if field and field.type in ('integer', 'float'):
return 0
if field.type == 'float' and int(temp) == temp:
temp = int(temp)
return temp or ''
def __getattr__(self, key):
if not self.data:
return ""
temp = getattr(self.data, key)
field = self.data._fields.get(key)
if isinstance(temp, unicode) and ('&' in temp or '<' in temp or '>' in temp):
temp = temp.replace('&', '&').replace('<', '<').replace('>', '>')
if isinstance(temp, models.Model):
return DataModelProxy(temp)
temp = self._compute_by_selection(field, temp)
temp = self._compute_by_datetime(field, temp)
return self._compute_temp_false(field, temp)
def __getitem__(self, index):
'''支持列表取值'''
return DataModelProxy(self.data[index])
def __iter__(self):
'''支持迭代器行为'''
return IterDataModelProxy(self.data)
def __str__(self):
'''支持直接在word 上写 many2one 字段'''
name = ''
if self.data and self.data.display_name:
name = self.data.display_name
if '&' in self.data.display_name:
name = name.replace('&', '&')
if '<' in self.data.display_name:
name = name.replace('<', '<')
if '>' in self.data.display_name:
name = name.replace('>', '>')
return name
class IterDataModelProxy(object):
'''迭代器类,用 next 函数支持 for in 操作'''
def __init__(self, data):
self.data = data
self.length = len(data)
self.current = 0
def next(self):
if self.current >= self.length:
raise StopIteration()
temp = DataModelProxy(self.data[self.current])
self.current += 1
return temp
class ReportDocx(report_sxw):
def create(self, cr, uid, ids, data, context=None):
env = api.Environment(cr, uid, context)
report_obj = env.get('ir.actions.report.xml')
report_ids = report_obj.search([('report_name', '=', self.name[7:])])
self.title = report_ids[0].name
if report_ids[0].report_type == 'docx':
return self.create_source_docx(cr, uid, ids, report_ids[0], context)
return super(ReportDocx, self).create(cr, uid, ids, data, context)
def generate_temp_file(self, tempname, suffix='docx'):
return os.path.join(tempname, 'temp_%s_%s.%s' %
(os.getpid(), random.randint(1, 10000), suffix))
def create_source_docx(self, cr, uid, ids, report, context=None):
data = DataModelProxy(self.get_docx_data(
cr, uid, ids, report, context))
tempname = tempfile.mkdtemp()
temp_out_file = self.generate_temp_file(tempname)
doc = DocxTemplate(misc.file_open(report.template_file).name)
# 2016-11-2 支持了图片
# 1.导入依赖,python3语法
from . import report_helper
# 2. 需要添加一个"tpl"属性获得模版对象
doc.render({'obj': data, 'tpl': doc}, report_helper.get_env())
doc.save(temp_out_file)
if report.output_type == 'pdf':
temp_file = self.render_to_pdf(temp_out_file)
else:
temp_file = temp_out_file
report_stream = ''
with open(temp_file, 'rb') as input_stream:
report_stream = input_stream.read()
os.remove(temp_file)
return report_stream, report.output_type
def render_to_pdf(self, temp_file):
tempname = tempfile.mkdtemp()
temp_out_file_html = self.generate_temp_file(tempname, suffix='html')
temp_out_file_pdf = self.generate_temp_file(tempname, suffix='pdf')
ofile = ooxml.read_from_file(temp_file)
html = """<html style="height: 100%">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
</head>
<body>
"""
html += unicode(serialize.serialize(ofile.document), 'utf-8')
html += "</body></html>"
with codecs.open(temp_out_file_html, 'w', 'utf-8') as f:
f.write(html)
pdfkit.from_file(temp_out_file_html, temp_out_file_pdf)
os.remove(temp_out_file_html)
return temp_out_file_pdf
def get_docx_data(self, cr, uid, ids, report, context):
env = api.Environment(cr, uid, context)
# 打印时, 在消息处显示打印人
message = str((datetime.now()).strftime('%Y-%m-%d %H:%M:%S')) + ' ' + env.user.name + u' 打印了该单据'
env.get(report.model).message_post(body=message)
return env.get(report.model).browse(ids)
def _save_file(self, folder_name, file):
out_stream = open(folder_name, 'wb')
try:
out_stream.writelines(file)
finally:
out_stream.close()