lims_report_html, lims_diagnosis: report template refactoring
This commit is contained in:
parent
34b4375a78
commit
cdaba55c14
|
@ -24,7 +24,7 @@ def register():
|
|||
html_template.DiagnosisStateImage,
|
||||
html_template.DiagnosisTemplate,
|
||||
html_template.DiagnosisTemplateState,
|
||||
html_template.ReportTemplate,
|
||||
html_template.ResultsReportTemplate,
|
||||
sample.Fraction,
|
||||
sample.Sample,
|
||||
sample.CreateSampleStart,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
from trytond.model import ModelSQL, ModelView, fields, DictSchemaMixin
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
|
||||
|
||||
class DiagnosisTemplate(ModelSQL, ModelView):
|
||||
|
@ -51,10 +52,14 @@ class DiagnosisTemplateState(ModelSQL):
|
|||
required=True, ondelete='CASCADE', select=True)
|
||||
|
||||
|
||||
class ReportTemplate(metaclass=PoolMeta):
|
||||
__name__ = 'lims.result_report.template'
|
||||
class ResultsReportTemplate(metaclass=PoolMeta):
|
||||
__name__ = 'lims.report.template'
|
||||
|
||||
diagnosis_template = fields.Many2One('lims.diagnosis.template',
|
||||
'Diagnosis Template')
|
||||
'Diagnosis Template',
|
||||
states={'invisible': Eval('type') != 'base'},
|
||||
depends=['type'])
|
||||
diagnosis_length = fields.Integer('Diagnosis Length',
|
||||
states={'invisible': Eval('type') != 'base'},
|
||||
depends=['type'],
|
||||
help='Maximum number of characters in diagnosis')
|
||||
|
|
|
@ -86,10 +86,10 @@
|
|||
|
||||
<!-- Results Report Template -->
|
||||
|
||||
<record model="ir.ui.view" id="template_view_form">
|
||||
<field name="model">lims.result_report.template</field>
|
||||
<field name="inherit" ref="lims_report_html.template_view_form"/>
|
||||
<field name="name">template_form</field>
|
||||
<record model="ir.ui.view" id="result_template_view_form">
|
||||
<field name="model">lims.report.template</field>
|
||||
<field name="inherit" ref="lims_report_html.result_template_view_form"/>
|
||||
<field name="name">result_template_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -110,11 +110,11 @@ msgctxt "field:lims.product.type,diagnostician:"
|
|||
msgid "Diagnostician"
|
||||
msgstr "Diagnosticador"
|
||||
|
||||
msgctxt "field:lims.result_report.template,diagnosis_length:"
|
||||
msgctxt "field:lims.report.template,diagnosis_length:"
|
||||
msgid "Diagnosis Length"
|
||||
msgstr "Longitud del diagnóstico"
|
||||
|
||||
msgctxt "field:lims.result_report.template,diagnosis_template:"
|
||||
msgctxt "field:lims.report.template,diagnosis_template:"
|
||||
msgid "Diagnosis Template"
|
||||
msgstr "Plantilla de Diagnóstico"
|
||||
|
||||
|
@ -267,7 +267,7 @@ msgctxt "help:lims.notebook.repeat_analysis.start,notify_acceptance:"
|
|||
msgid "Notify when analysis is ready"
|
||||
msgstr "Notificar cuando el análisis esté terminado"
|
||||
|
||||
msgctxt "help:lims.result_report.template,diagnosis_length:"
|
||||
msgctxt "help:lims.report.template,diagnosis_length:"
|
||||
msgid "Maximum number of characters in diagnosis"
|
||||
msgstr "Cantidad máxima de caracteres en diagnóstico"
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ class ResultsReportVersionDetail(metaclass=PoolMeta):
|
|||
def _get_fields_from_samples(cls, samples, generate_report_form=None):
|
||||
pool = Pool()
|
||||
Notebook = pool.get('lims.notebook')
|
||||
ReportTemplate = pool.get('lims.result_report.template')
|
||||
ReportTemplate = pool.get('lims.report.template')
|
||||
|
||||
detail_default = super()._get_fields_from_samples(samples,
|
||||
generate_report_form)
|
||||
|
@ -476,7 +476,8 @@ class SamplesComparatorLine(ModelSQL, ModelView):
|
|||
result[name] = {}
|
||||
if name == 'result':
|
||||
for l in lines:
|
||||
result[name][l.id] = l.notebook_line.formated_result
|
||||
result[name][l.id] = (
|
||||
l.notebook_line.get_formated_result())
|
||||
elif name == 'converted_result':
|
||||
for l in lines:
|
||||
result[name][l.id] = (
|
||||
|
@ -514,7 +515,7 @@ class SamplesComparatorLine(ModelSQL, ModelView):
|
|||
])
|
||||
if not notebook_line:
|
||||
return None
|
||||
return notebook_line[0].formated_result
|
||||
return notebook_line[0].get_formated_result()
|
||||
|
||||
|
||||
class Cron(metaclass=PoolMeta):
|
||||
|
@ -533,8 +534,8 @@ class ResultReport(metaclass=PoolMeta):
|
|||
__name__ = 'lims.result_report'
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, records, header, data):
|
||||
report_context = super().get_context(records, header, data)
|
||||
def get_context(cls, records, data):
|
||||
report_context = super().get_context(records, data)
|
||||
report_context['state_image'] = cls.get_state_image
|
||||
return report_context
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/field[@name='report']" position="after">
|
||||
<xpath expr="/form/field[@name='page_orientation']" position="after">
|
||||
<label name="diagnosis_template"/>
|
||||
<field name="diagnosis_template"/>
|
||||
<group id="diagnosis_length" colspan="2">
|
|
@ -20,7 +20,8 @@ def register():
|
|||
html_template.ReportTemplate,
|
||||
html_template.ReportTemplateTranslation,
|
||||
html_template.ReportTemplateSection,
|
||||
html_template.ReportTemplateTrendChart,
|
||||
html_template.ResultsReportTemplate,
|
||||
html_template.ResultsReportTemplateTrendChart,
|
||||
configuration.Configuration,
|
||||
laboratory.Laboratory,
|
||||
party.Party,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class ActionReport(metaclass=PoolMeta):
|
||||
|
@ -11,13 +12,21 @@ class ActionReport(metaclass=PoolMeta):
|
|||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
results_option = ('results', 'Results Report')
|
||||
if results_option not in cls.template_extension.selection:
|
||||
cls.template_extension.selection.append(results_option)
|
||||
lims_option = ('lims', 'Lims Report')
|
||||
if lims_option not in cls.template_extension.selection:
|
||||
cls.template_extension.selection.append(lims_option)
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().connection.cursor()
|
||||
table = cls.__table__()
|
||||
super().__register__(module_name)
|
||||
cursor.execute(*table.update([table.template_extension], ['lims'],
|
||||
where=(table.template_extension == 'results')))
|
||||
|
||||
|
||||
class ReportTranslationSet(metaclass=PoolMeta):
|
||||
__name__ = 'ir.translation.set'
|
||||
|
||||
def extract_report_results(self, content):
|
||||
def extract_report_lims(self, content):
|
||||
return []
|
||||
|
|
|
@ -10,7 +10,10 @@ from trytond.pyson import Eval
|
|||
class Analysis(metaclass=PoolMeta):
|
||||
__name__ = 'lims.analysis'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])],
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
],
|
||||
states={'readonly': Eval('type') != 'group'},
|
||||
depends=['type'])
|
||||
|
|
|
@ -9,5 +9,8 @@ from trytond.pool import PoolMeta
|
|||
class Configuration(metaclass=PoolMeta):
|
||||
__name__ = 'lims.configuration'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Default Report Template', domain=[('type', 'in', [None, 'base'])])
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Default Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
])
|
||||
|
|
|
@ -1,78 +1,123 @@
|
|||
# This file is part of lims_report_html module for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains
|
||||
# the full copyright notices and license terms.
|
||||
import os
|
||||
import operator
|
||||
from io import BytesIO
|
||||
from decimal import Decimal
|
||||
from datetime import date, datetime
|
||||
from binascii import b2a_base64
|
||||
from functools import partial
|
||||
from PyPDF2 import PdfFileMerger
|
||||
from PyPDF2.utils import PdfReadError
|
||||
from jinja2 import contextfilter, Markup
|
||||
from jinja2 import Environment, FunctionLoader
|
||||
from lxml import html as lxml_html
|
||||
from base64 import b64encode
|
||||
from babel.support import Translations as BabelTranslations
|
||||
from mimetypes import guess_type as mime_guess_type
|
||||
from sql import Literal
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.report import Report
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.pyson import Eval, Bool, Or
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.cache import Cache
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
from trytond.tools import file_open
|
||||
from trytond import backend
|
||||
from .generator import PdfGenerator
|
||||
|
||||
|
||||
class ReportTemplate(ModelSQL, ModelView):
|
||||
'Results Report Template'
|
||||
__name__ = 'lims.result_report.template'
|
||||
_history = True
|
||||
'Report Template'
|
||||
__name__ = 'lims.report.template'
|
||||
|
||||
report_name = fields.Char('Internal Name', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
report = fields.Many2One('ir.action.report', 'Report',
|
||||
domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('template_extension', '!=', 'results'),
|
||||
],
|
||||
states={'required': ~Eval('type')}, depends=['type'])
|
||||
type = fields.Selection([
|
||||
(None, ''),
|
||||
('base', 'HTML'),
|
||||
('header', 'HTML - Header'),
|
||||
('footer', 'HTML - Footer'),
|
||||
], 'Type')
|
||||
report = fields.Many2One('ir.action.report', 'Report',
|
||||
domain=[
|
||||
('report_name', '=', Eval('report_name')),
|
||||
('template_extension', '!=', 'lims'),
|
||||
],
|
||||
states={
|
||||
'required': ~Eval('type'),
|
||||
'invisible': Bool(Eval('type')),
|
||||
},
|
||||
depends=['report_name', 'type'])
|
||||
content = fields.Text('Content',
|
||||
states={'required': Bool(Eval('type'))}, depends=['type'])
|
||||
header = fields.Many2One('lims.result_report.template', 'Header',
|
||||
domain=[('type', '=', 'header')])
|
||||
footer = fields.Many2One('lims.result_report.template', 'Footer',
|
||||
domain=[('type', '=', 'footer')])
|
||||
translations = fields.One2Many('lims.result_report.template.translation',
|
||||
header = fields.Many2One('lims.report.template', 'Header',
|
||||
domain=[
|
||||
('report_name', '=', Eval('report_name')),
|
||||
('type', '=', 'header'),
|
||||
],
|
||||
depends=['report_name'])
|
||||
footer = fields.Many2One('lims.report.template', 'Footer',
|
||||
domain=[
|
||||
('report_name', '=', Eval('report_name')),
|
||||
('type', '=', 'footer'),
|
||||
],
|
||||
depends=['report_name'])
|
||||
translations = fields.One2Many('lims.report.template.translation',
|
||||
'template', 'Translations')
|
||||
_translation_cache = Cache('lims.result_report.template.translation',
|
||||
_translation_cache = Cache('lims.report.template.translation',
|
||||
size_limit=10240, context=False)
|
||||
sections = fields.One2Many('lims.result_report.template.section',
|
||||
sections = fields.One2Many('lims.report.template.section',
|
||||
'template', 'Sections')
|
||||
previous_sections = fields.Function(fields.One2Many(
|
||||
'lims.result_report.template.section', 'template',
|
||||
'lims.report.template.section', 'template',
|
||||
'Previous Sections', domain=[('position', '=', 'previous')]),
|
||||
'get_previous_sections', setter='set_previous_sections')
|
||||
following_sections = fields.Function(fields.One2Many(
|
||||
'lims.result_report.template.section', 'template',
|
||||
'lims.report.template.section', 'template',
|
||||
'Following Sections', domain=[('position', '=', 'following')]),
|
||||
'get_following_sections', setter='set_following_sections')
|
||||
trend_charts = fields.One2Many('lims.result_report.template.trend.chart',
|
||||
'template', 'Trend Charts')
|
||||
charts_x_row = fields.Selection([
|
||||
('1', '1'),
|
||||
('2', '2'),
|
||||
], 'Charts per Row')
|
||||
page_orientation = fields.Selection([
|
||||
('portrait', 'Portrait'),
|
||||
('landscape', 'Landscape'),
|
||||
], 'Page orientation', sort=False)
|
||||
resultrange_origin = fields.Many2One('lims.range.type', 'Comparison range',
|
||||
domain=[('use', '=', 'result_range')])
|
||||
], 'Page orientation', sort=False,
|
||||
states={'invisible': Eval('type') != 'base'},
|
||||
depends=['type'])
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().connection.cursor()
|
||||
TableHandler = backend.TableHandler
|
||||
sql_table = cls.__table__()
|
||||
|
||||
old_table_exist = TableHandler.table_exist(
|
||||
'lims_result_report_template')
|
||||
if old_table_exist:
|
||||
cursor.execute('ALTER TABLE '
|
||||
'lims_result_report_template '
|
||||
'RENAME TO lims_report_template')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_pkey '
|
||||
'RENAME TO lims_report_template_pkey')
|
||||
cursor.execute('ALTER SEQUENCE '
|
||||
'lims_result_report_template_id_seq '
|
||||
'RENAME TO lims_report_template_id_seq')
|
||||
|
||||
super().__register__(module_name)
|
||||
|
||||
if old_table_exist:
|
||||
cursor.execute(*sql_table.update(
|
||||
[sql_table.report_name], ['lims.result_report'],
|
||||
where=Literal(True)))
|
||||
|
||||
@staticmethod
|
||||
def default_type():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def default_charts_x_row():
|
||||
return '1'
|
||||
|
||||
@staticmethod
|
||||
def default_page_orientation():
|
||||
return 'portrait'
|
||||
|
@ -80,7 +125,7 @@ class ReportTemplate(ModelSQL, ModelView):
|
|||
@classmethod
|
||||
def view_attributes(cls):
|
||||
return super().view_attributes() + [
|
||||
('//page[@id="content"]', 'states', {
|
||||
('//page[@name="content"]', 'states', {
|
||||
'invisible': ~Bool(Eval('type')),
|
||||
}),
|
||||
('//page[@id="header_footer"]', 'states', {
|
||||
|
@ -92,15 +137,12 @@ class ReportTemplate(ModelSQL, ModelView):
|
|||
('//page[@name="sections"]', 'states', {
|
||||
'invisible': Eval('type') != 'base',
|
||||
}),
|
||||
('//page[@name="trend_charts"]', 'states', {
|
||||
'invisible': Eval('type') != 'base',
|
||||
}),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def gettext(cls, *args, **variables):
|
||||
ReportTemplateTranslation = Pool().get(
|
||||
'lims.result_report.template.translation')
|
||||
'lims.report.template.translation')
|
||||
template, src, lang = args
|
||||
key = (template, src, lang)
|
||||
text = cls._translation_cache.get(key)
|
||||
|
@ -143,17 +185,40 @@ class ReportTemplate(ModelSQL, ModelView):
|
|||
|
||||
|
||||
class ReportTemplateTranslation(ModelSQL, ModelView):
|
||||
'Results Report Template Translation'
|
||||
__name__ = 'lims.result_report.template.translation'
|
||||
'Report Template Translation'
|
||||
__name__ = 'lims.report.template.translation'
|
||||
_order_name = 'src'
|
||||
|
||||
template = fields.Many2One('lims.result_report.template', 'Template',
|
||||
template = fields.Many2One('lims.report.template', 'Template',
|
||||
ondelete='CASCADE', select=True, required=True)
|
||||
src = fields.Text('Source', required=True)
|
||||
value = fields.Text('Translation Value', required=True)
|
||||
lang = fields.Selection('get_language', string='Language', required=True)
|
||||
_get_language_cache = Cache(
|
||||
'lims.result_report.template.translation.get_language')
|
||||
'lims.report.template.translation.get_language')
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().connection.cursor()
|
||||
TableHandler = backend.TableHandler
|
||||
|
||||
old_table_exist = TableHandler.table_exist(
|
||||
'lims_result_report_template_translation')
|
||||
if old_table_exist:
|
||||
cursor.execute('ALTER TABLE '
|
||||
'lims_result_report_template_translation '
|
||||
'RENAME TO lims_report_template_translation')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_translation_pkey '
|
||||
'RENAME TO lims_report_template_translation_pkey')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_translation_template_index '
|
||||
'RENAME TO lims_report_template_translation_template_index')
|
||||
cursor.execute('ALTER SEQUENCE '
|
||||
'lims_result_report_template_translation_id_seq '
|
||||
'RENAME TO lims_report_template_translation_id_seq')
|
||||
|
||||
super().__register__(module_name)
|
||||
|
||||
@staticmethod
|
||||
def default_lang():
|
||||
|
@ -171,29 +236,29 @@ class ReportTemplateTranslation(ModelSQL, ModelView):
|
|||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
Template = Pool().get('lims.result_report.template')
|
||||
Template = Pool().get('lims.report.template')
|
||||
Template._translation_cache.clear()
|
||||
return super().create(vlist)
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
Template = Pool().get('lims.result_report.template')
|
||||
Template = Pool().get('lims.report.template')
|
||||
Template._translation_cache.clear()
|
||||
return super().write(*args)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, translations):
|
||||
Template = Pool().get('lims.result_report.template')
|
||||
Template = Pool().get('lims.report.template')
|
||||
Template._translation_cache.clear()
|
||||
return super().delete(translations)
|
||||
|
||||
|
||||
class ReportTemplateSection(ModelSQL, ModelView):
|
||||
'Results Report Template Section'
|
||||
__name__ = 'lims.result_report.template.section'
|
||||
'Report Template Section'
|
||||
__name__ = 'lims.report.template.section'
|
||||
_order_name = 'order'
|
||||
|
||||
template = fields.Many2One('lims.result_report.template', 'Template',
|
||||
template = fields.Many2One('lims.report.template', 'Template',
|
||||
ondelete='CASCADE', select=True, required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
data = fields.Binary('File', filename='name', required=True,
|
||||
|
@ -210,6 +275,29 @@ class ReportTemplateSection(ModelSQL, ModelView):
|
|||
super().__setup__()
|
||||
cls._order.insert(0, ('order', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().connection.cursor()
|
||||
TableHandler = backend.TableHandler
|
||||
|
||||
old_table_exist = TableHandler.table_exist(
|
||||
'lims_result_report_template_section')
|
||||
if old_table_exist:
|
||||
cursor.execute('ALTER TABLE '
|
||||
'lims_result_report_template_section '
|
||||
'RENAME TO lims_report_template_section ')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_section_pkey '
|
||||
'RENAME TO lims_report_template_section_pkey')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_section_template_index '
|
||||
'RENAME TO lims_report_template_section_template_index')
|
||||
cursor.execute('ALTER SEQUENCE '
|
||||
'lims_result_report_template_section_id_seq '
|
||||
'RENAME TO lims_report_template_section_id_seq')
|
||||
|
||||
super().__register__(module_name)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, sections):
|
||||
super().validate(sections)
|
||||
|
@ -222,13 +310,398 @@ class ReportTemplateSection(ModelSQL, ModelView):
|
|||
raise UserError(gettext('lims_report_html.msg_section_pdf'))
|
||||
|
||||
|
||||
class ReportTemplateTrendChart(ModelSQL, ModelView):
|
||||
class LimsReport(Report):
|
||||
|
||||
@classmethod
|
||||
def execute_custom_lims_report(cls, ids, data):
|
||||
pool = Pool()
|
||||
ActionReport = pool.get('ir.action.report')
|
||||
cls.check_access()
|
||||
|
||||
action_id = data.get('action_id')
|
||||
if action_id is None:
|
||||
action_reports = ActionReport.search([
|
||||
('report_name', '=', cls.__name__),
|
||||
('template_extension', '!=', 'lims'),
|
||||
])
|
||||
assert action_reports, '%s not found' % cls
|
||||
action = action_reports[0]
|
||||
else:
|
||||
action = ActionReport(action_id)
|
||||
|
||||
records = []
|
||||
model = action.model or data.get('model')
|
||||
if model:
|
||||
records = cls._get_records(ids, model, data)
|
||||
oext, content = cls._execute(records, data, action)
|
||||
if not isinstance(content, str):
|
||||
content = bytearray(content) if bytes == str else bytes(content)
|
||||
return (oext, content, action.direct_print, action.name)
|
||||
|
||||
@classmethod
|
||||
def execute_html_lims_report(cls, ids, data):
|
||||
pool = Pool()
|
||||
ActionReport = pool.get('ir.action.report')
|
||||
cls.check_access()
|
||||
|
||||
action_reports = ActionReport.search([
|
||||
('report_name', '=', cls.__name__),
|
||||
('template_extension', '=', 'lims'),
|
||||
])
|
||||
assert action_reports, '%s not found' % cls
|
||||
action = action_reports[0]
|
||||
|
||||
records = []
|
||||
model = action.model or data.get('model')
|
||||
if model:
|
||||
records = cls._get_records(ids, model, data)
|
||||
oext, content = cls._execute_html_lims_report(records, data, action)
|
||||
if not isinstance(content, str):
|
||||
content = bytearray(content) if bytes == str else bytes(content)
|
||||
return (oext, content, action.direct_print, action.name)
|
||||
|
||||
@classmethod
|
||||
def _execute_html_lims_report(cls, records, data, action):
|
||||
record = records[0]
|
||||
template_id, tcontent, theader, tfooter = (
|
||||
cls.get_lims_template(action, record))
|
||||
context = Transaction().context
|
||||
context['template'] = template_id
|
||||
if not template_id:
|
||||
context['default_translations'] = os.path.join(
|
||||
os.path.dirname(__file__), 'report', 'translations')
|
||||
with Transaction().set_context(**context):
|
||||
content = cls.render_lims_template(action,
|
||||
tcontent, record=record, records=[record],
|
||||
data=data)
|
||||
header = theader and cls.render_lims_template(action,
|
||||
theader, record=record, records=[record],
|
||||
data=data)
|
||||
footer = tfooter and cls.render_lims_template(action,
|
||||
tfooter, record=record, records=[record],
|
||||
data=data)
|
||||
|
||||
stylesheets = cls.parse_stylesheets(tcontent)
|
||||
if theader:
|
||||
stylesheets += cls.parse_stylesheets(theader)
|
||||
if tfooter:
|
||||
stylesheets += cls.parse_stylesheets(tfooter)
|
||||
|
||||
page_orientation = (record.template and
|
||||
record.template.page_orientation or 'portrait')
|
||||
|
||||
document = PdfGenerator(content,
|
||||
header_html=header, footer_html=footer,
|
||||
side_margin=1, extra_vertical_margin=30,
|
||||
stylesheets=stylesheets,
|
||||
page_orientation=page_orientation).render_html().write_pdf()
|
||||
|
||||
if record.previous_sections or record.following_sections:
|
||||
merger = PdfFileMerger(strict=False)
|
||||
# Previous Sections
|
||||
for section in record.previous_sections:
|
||||
filedata = BytesIO(section.data)
|
||||
merger.append(filedata)
|
||||
# Main Report
|
||||
filedata = BytesIO(document)
|
||||
merger.append(filedata)
|
||||
# Following Sections
|
||||
for section in record.following_sections:
|
||||
filedata = BytesIO(section.data)
|
||||
merger.append(filedata)
|
||||
output = BytesIO()
|
||||
merger.write(output)
|
||||
document = output.getvalue()
|
||||
|
||||
return 'pdf', document
|
||||
|
||||
@classmethod
|
||||
def get_lims_template(cls, action, record):
|
||||
template_id, content, header, footer = None, None, None, None
|
||||
if record.template:
|
||||
template_id = record.template
|
||||
content = '<body>%s</body>' % record.template.content
|
||||
header = (record.template.header and
|
||||
'<header id="header">%s</header>' %
|
||||
record.template.header.content)
|
||||
footer = (record.template.footer and
|
||||
'<footer id="footer">%s</footer>' %
|
||||
record.template.footer.content)
|
||||
if not content:
|
||||
content = (action.report_content and
|
||||
action.report_content.decode('utf-8'))
|
||||
if not content:
|
||||
raise UserError(gettext('lims_report_html.msg_no_template'))
|
||||
return template_id, content, header, footer
|
||||
|
||||
@classmethod
|
||||
def render_lims_template(cls, action, template_string,
|
||||
record=None, records=None, data=None):
|
||||
User = Pool().get('res.user')
|
||||
user = User(Transaction().user)
|
||||
|
||||
if data and data.get('alt_lang'):
|
||||
locale = data['alt_lang']
|
||||
elif user.language:
|
||||
locale = user.language.code
|
||||
else:
|
||||
locale = Transaction().language
|
||||
with Transaction().set_context(locale=locale):
|
||||
env = cls.get_lims_environment()
|
||||
|
||||
report_template = env.from_string(template_string)
|
||||
context = cls.get_context(records, data)
|
||||
context.update({
|
||||
'report': action,
|
||||
'get_image': cls.get_image,
|
||||
'operation': cls.operation,
|
||||
})
|
||||
res = report_template.render(**context)
|
||||
res = cls.parse_images(res)
|
||||
# print('TEMPLATE:\n', res)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_lims_environment(cls):
|
||||
extensions = ['jinja2.ext.i18n', 'jinja2.ext.autoescape',
|
||||
'jinja2.ext.with_', 'jinja2.ext.loopcontrols', 'jinja2.ext.do']
|
||||
env = Environment(extensions=extensions,
|
||||
loader=FunctionLoader(lambda name: ''))
|
||||
|
||||
env.filters.update(cls.get_lims_filters())
|
||||
|
||||
locale = Transaction().context.get('locale').split('_')[0]
|
||||
translations = TemplateTranslations(locale)
|
||||
env.install_gettext_translations(translations)
|
||||
return env
|
||||
|
||||
@classmethod
|
||||
def get_lims_filters(cls):
|
||||
Lang = Pool().get('ir.lang')
|
||||
|
||||
def module_path(name):
|
||||
module, path = name.split('/', 1)
|
||||
with file_open(os.path.join(module, path)) as f:
|
||||
return 'file://%s' % f.name
|
||||
|
||||
def render(value, digits=2, lang=None, filename=None):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if isinstance(value, (float, Decimal)):
|
||||
return lang.format('%.*f', (digits, value), grouping=True)
|
||||
|
||||
if isinstance(value, int):
|
||||
return lang.format('%d', value, grouping=True)
|
||||
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
return gettext('lims_report_html.msg_yes')
|
||||
return gettext('lims_report_html.msg_no')
|
||||
|
||||
if hasattr(value, 'rec_name'):
|
||||
return value.rec_name
|
||||
|
||||
if isinstance(value, date):
|
||||
return lang.strftime(value)
|
||||
|
||||
if isinstance(value, datetime):
|
||||
return '%s %s' % (lang.strftime(value),
|
||||
value.strftime('%H:%M:%S'))
|
||||
|
||||
if isinstance(value, str):
|
||||
return value.replace('\n', '<br/>')
|
||||
|
||||
if isinstance(value, bytes):
|
||||
b64_value = b2a_base64(value).decode('ascii')
|
||||
mimetype = 'image/png'
|
||||
if filename:
|
||||
mimetype = mime_guess_type(filename)[0]
|
||||
return ('data:%s;base64,%s' % (mimetype, b64_value)).strip()
|
||||
return value
|
||||
|
||||
@contextfilter
|
||||
def subrender(context, value, subobj=None):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
_template = context.eval_ctx.environment.from_string(value)
|
||||
if subobj:
|
||||
new_context = {'subobj': subobj}
|
||||
new_context.update(context)
|
||||
else:
|
||||
new_context = context
|
||||
result = _template.render(**new_context)
|
||||
if context.eval_ctx.autoescape:
|
||||
result = Markup(result)
|
||||
return result
|
||||
|
||||
locale = Transaction().context.get('locale').split('_')[0]
|
||||
lang, = Lang.search([('code', '=', locale or 'en')])
|
||||
|
||||
return {
|
||||
'modulepath': module_path,
|
||||
'render': partial(render, lang=lang),
|
||||
'subrender': subrender,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parse_images(cls, template_string):
|
||||
Attachment = Pool().get('ir.attachment')
|
||||
root = lxml_html.fromstring(template_string)
|
||||
for elem in root.iter('img'):
|
||||
# get image from attachments
|
||||
if 'id' in elem.attrib:
|
||||
img = Attachment.search([('id', '=', int(elem.attrib['id']))])
|
||||
if img:
|
||||
elem.attrib['src'] = cls.get_image(img[0].data)
|
||||
# get image from TinyMCE widget
|
||||
elif 'data-mce-src' in elem.attrib:
|
||||
elem.attrib['src'] = elem.attrib['data-mce-src']
|
||||
del elem.attrib['data-mce-src']
|
||||
# set width and height in style attribute
|
||||
style = elem.attrib.get('style', '')
|
||||
if 'width' in elem.attrib:
|
||||
style += ' width: %spx;' % str(elem.attrib['width'])
|
||||
if 'height' in elem.attrib:
|
||||
style += ' height: %spx;' % str(elem.attrib['height'])
|
||||
elem.attrib['style'] = style
|
||||
return lxml_html.tostring(root).decode()
|
||||
|
||||
@classmethod
|
||||
def get_image(cls, image):
|
||||
if not image:
|
||||
return ''
|
||||
b64_image = b64encode(image).decode()
|
||||
return 'data:image/png;base64,%s' % b64_image
|
||||
|
||||
@classmethod
|
||||
def operation(cls, function, value1, value2):
|
||||
return getattr(operator, function)(value1, value2)
|
||||
|
||||
@classmethod
|
||||
def parse_stylesheets(cls, template_string):
|
||||
Attachment = Pool().get('ir.attachment')
|
||||
root = lxml_html.fromstring(template_string)
|
||||
res = []
|
||||
# get stylesheets from attachments
|
||||
elems = root.xpath("//div[@id='tryton_styles_container']/div")
|
||||
for elem in elems:
|
||||
css = Attachment.search([('id', '=', int(elem.attrib['id']))])
|
||||
if not css:
|
||||
continue
|
||||
res.append(css[0].data)
|
||||
return res
|
||||
|
||||
|
||||
class TemplateTranslations:
|
||||
|
||||
def __init__(self, lang='en'):
|
||||
self.cache = {}
|
||||
self.env = None
|
||||
self.current = None
|
||||
self.language = lang
|
||||
self.template = None
|
||||
self.set_language(lang)
|
||||
|
||||
def set_language(self, lang='en'):
|
||||
self.language = lang
|
||||
if lang in self.cache:
|
||||
self.current = self.cache[lang]
|
||||
return
|
||||
context = Transaction().context
|
||||
if context.get('default_translations'):
|
||||
default_translations = context['default_translations']
|
||||
if os.path.isdir(default_translations):
|
||||
self.current = BabelTranslations.load(
|
||||
dirname=default_translations, locales=[lang])
|
||||
self.cache[lang] = self.current
|
||||
else:
|
||||
self.template = context.get('template', -1)
|
||||
|
||||
def ugettext(self, message):
|
||||
ReportTemplate = Pool().get('lims.report.template')
|
||||
if self.current:
|
||||
return self.current.ugettext(message)
|
||||
elif self.template:
|
||||
return ReportTemplate.gettext(self.template, message,
|
||||
self.language)
|
||||
return message
|
||||
|
||||
def ngettext(self, singular, plural, n):
|
||||
ReportTemplate = Pool().get('lims.report.template')
|
||||
if self.current:
|
||||
return self.current.ugettext(singular, plural, n)
|
||||
elif self.template:
|
||||
return ReportTemplate.gettext(self.template, singular,
|
||||
self.language)
|
||||
return singular
|
||||
|
||||
|
||||
class ResultsReportTemplate(ReportTemplate):
|
||||
__name__ = 'lims.report.template'
|
||||
|
||||
trend_charts = fields.One2Many('lims.report.template.trend.chart',
|
||||
'template', 'Trend Charts')
|
||||
charts_x_row = fields.Selection([
|
||||
('1', '1'),
|
||||
('2', '2'),
|
||||
], 'Charts per Row')
|
||||
resultrange_origin = fields.Many2One('lims.range.type', 'Comparison range',
|
||||
domain=[('use', '=', 'result_range')],
|
||||
states={
|
||||
'invisible': Or(
|
||||
Eval('type') != 'base',
|
||||
Eval('report_name') != 'lims.result_report',
|
||||
),
|
||||
},
|
||||
depends=['type'])
|
||||
|
||||
@staticmethod
|
||||
def default_charts_x_row():
|
||||
return '1'
|
||||
|
||||
@classmethod
|
||||
def view_attributes(cls):
|
||||
return super().view_attributes() + [
|
||||
('//page[@name="trend_charts"]', 'states', {
|
||||
'invisible': Or(
|
||||
Eval('type') != 'base',
|
||||
Eval('report_name') != 'lims.result_report',
|
||||
)
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
class ResultsReportTemplateTrendChart(ModelSQL, ModelView):
|
||||
'Results Report Template Trend Chart'
|
||||
__name__ = 'lims.result_report.template.trend.chart'
|
||||
__name__ = 'lims.report.template.trend.chart'
|
||||
_order_name = 'order'
|
||||
|
||||
template = fields.Many2One('lims.result_report.template', 'Template',
|
||||
template = fields.Many2One('lims.report.template', 'Template',
|
||||
ondelete='CASCADE', select=True, required=True)
|
||||
chart = fields.Many2One('lims.trend.chart', 'Trend Chart',
|
||||
required=True, domain=[('active', '=', True)])
|
||||
order = fields.Integer('Order')
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().connection.cursor()
|
||||
TableHandler = backend.TableHandler
|
||||
|
||||
old_table_exist = TableHandler.table_exist(
|
||||
'lims_result_report_template_trend_chart')
|
||||
if old_table_exist:
|
||||
cursor.execute('ALTER TABLE '
|
||||
'lims_result_report_template_trend_chart '
|
||||
'RENAME TO lims_report_template_trend_chart ')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_trend_chart_pkey '
|
||||
'RENAME TO lims_report_template_trend_chart_pkey')
|
||||
cursor.execute('ALTER INDEX '
|
||||
'lims_result_report_template_trend_chart_template_index '
|
||||
'RENAME TO lims_report_template_trend_chart_template_index')
|
||||
cursor.execute('ALTER SEQUENCE '
|
||||
'lims_result_report_template_trend_chart_id_seq '
|
||||
'RENAME TO lims_report_template_trend_chart_id_seq')
|
||||
|
||||
super().__register__(module_name)
|
||||
|
|
|
@ -2,73 +2,76 @@
|
|||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Results Report Template -->
|
||||
|
||||
<record model="ir.ui.view" id="template_view_form">
|
||||
<field name="model">lims.result_report.template</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_view_list">
|
||||
<field name="model">lims.result_report.template</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">template_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_html_template_list">
|
||||
<field name="name">Results Report Templates</field>
|
||||
<field name="res_model">lims.result_report.template</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_html_template_view_list">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="template_view_list"/>
|
||||
<field name="act_window" ref="act_html_template_list"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_html_template_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="template_view_form"/>
|
||||
<field name="act_window" ref="act_html_template_list"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="act_html_template_list"
|
||||
id="menu_html_template_list"
|
||||
parent="lims.lims_config_report" sequence="10"/>
|
||||
|
||||
<!-- Results Report Template Translation -->
|
||||
<!-- Report Template Translation -->
|
||||
|
||||
<record model="ir.ui.view" id="template_translation_view_form">
|
||||
<field name="model">lims.result_report.template.translation</field>
|
||||
<field name="model">lims.report.template.translation</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_translation_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_translation_view_list">
|
||||
<field name="model">lims.result_report.template.translation</field>
|
||||
<field name="model">lims.report.template.translation</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">template_translation_list</field>
|
||||
</record>
|
||||
|
||||
<!-- Results Report Template Section -->
|
||||
<!-- Report Template Section -->
|
||||
|
||||
<record model="ir.ui.view" id="template_section_view_form">
|
||||
<field name="model">lims.result_report.template.section</field>
|
||||
<field name="model">lims.report.template.section</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_section_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_section_view_list">
|
||||
<field name="model">lims.result_report.template.section</field>
|
||||
<field name="model">lims.report.template.section</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">template_section_list</field>
|
||||
</record>
|
||||
|
||||
<!-- Results Report Template -->
|
||||
|
||||
<record model="ir.ui.view" id="result_template_view_form">
|
||||
<field name="model">lims.report.template</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">result_template_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="result_template_view_list">
|
||||
<field name="model">lims.report.template</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">result_template_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_result_template_list">
|
||||
<field name="name">Results Report Templates</field>
|
||||
<field name="res_model">lims.report.template</field>
|
||||
<field name="domain" pyson="1"
|
||||
eval="[('report_name', '=', 'lims.result_report')]"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_result_template_view_list">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="result_template_view_list"/>
|
||||
<field name="act_window" ref="act_result_template_list"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_result_template_view_form">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="result_template_view_form"/>
|
||||
<field name="act_window" ref="act_result_template_list"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="act_result_template_list"
|
||||
id="menu_result_template_list"
|
||||
parent="lims.lims_config_report" sequence="10"
|
||||
icon="tryton-list"/>
|
||||
|
||||
<!-- Results Report Template Trend Chart -->
|
||||
|
||||
<record model="ir.ui.view" id="template_trend_chart_view_form">
|
||||
<field name="model">lims.result_report.template.trend.chart</field>
|
||||
<field name="model">lims.report.template.trend.chart</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_trend_chart_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_trend_chart_view_list">
|
||||
<field name="model">lims.result_report.template.trend.chart</field>
|
||||
<field name="model">lims.report.template.trend.chart</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">template_trend_chart_list</field>
|
||||
</record>
|
||||
|
|
|
@ -9,5 +9,8 @@ from trytond.pool import PoolMeta
|
|||
class Laboratory(metaclass=PoolMeta):
|
||||
__name__ = 'lims.laboratory'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])])
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
])
|
||||
|
|
|
@ -38,111 +38,115 @@ msgctxt "field:lims.notebook.generate_results_report.start,template:"
|
|||
msgid "Report Template"
|
||||
msgstr "Plantilla de Informe"
|
||||
|
||||
msgctxt "field:lims.result_report.template,charts_x_row:"
|
||||
msgctxt "field:lims.report.template,charts_x_row:"
|
||||
msgid "Charts per Row"
|
||||
msgstr "Gráficos por fila"
|
||||
|
||||
msgctxt "field:lims.result_report.template,content:"
|
||||
msgctxt "field:lims.report.template,content:"
|
||||
msgid "Content"
|
||||
msgstr "Contenido"
|
||||
|
||||
msgctxt "field:lims.result_report.template,following_sections:"
|
||||
msgctxt "field:lims.report.template,following_sections:"
|
||||
msgid "Following Sections"
|
||||
msgstr "Secciones siguientes"
|
||||
|
||||
msgctxt "field:lims.result_report.template,footer:"
|
||||
msgctxt "field:lims.report.template,footer:"
|
||||
msgid "Footer"
|
||||
msgstr "Pie de página"
|
||||
|
||||
msgctxt "field:lims.result_report.template,header:"
|
||||
msgctxt "field:lims.report.template,header:"
|
||||
msgid "Header"
|
||||
msgstr "Encabezado"
|
||||
|
||||
msgctxt "field:lims.result_report.template,name:"
|
||||
msgctxt "field:lims.report.template,name:"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
msgctxt "field:lims.result_report.template,page_orientation:"
|
||||
msgctxt "field:lims.report.template,page_orientation:"
|
||||
msgid "Page orientation"
|
||||
msgstr "Orientación de la página"
|
||||
|
||||
msgctxt "field:lims.result_report.template,previous_sections:"
|
||||
msgctxt "field:lims.report.template,previous_sections:"
|
||||
msgid "Previous Sections"
|
||||
msgstr "Secciones anteriores"
|
||||
|
||||
msgctxt "field:lims.result_report.template,report:"
|
||||
msgctxt "field:lims.report.template,report:"
|
||||
msgid "Report"
|
||||
msgstr "Informe"
|
||||
|
||||
msgctxt "field:lims.result_report.template,resultrange_origin:"
|
||||
msgctxt "field:lims.report.template,report_name:"
|
||||
msgid "Internal Name"
|
||||
msgstr "Nombre interno"
|
||||
|
||||
msgctxt "field:lims.report.template,resultrange_origin:"
|
||||
msgid "Comparison range"
|
||||
msgstr "Rango de comparación"
|
||||
|
||||
msgctxt "field:lims.result_report.template,sections:"
|
||||
msgctxt "field:lims.report.template,sections:"
|
||||
msgid "Sections"
|
||||
msgstr "Secciones"
|
||||
|
||||
msgctxt "field:lims.result_report.template,translations:"
|
||||
msgctxt "field:lims.report.template,translations:"
|
||||
msgid "Translations"
|
||||
msgstr "Traducciones"
|
||||
|
||||
msgctxt "field:lims.result_report.template,trend_charts:"
|
||||
msgctxt "field:lims.report.template,trend_charts:"
|
||||
msgid "Trend Charts"
|
||||
msgstr "Gráficos de tendencia"
|
||||
|
||||
msgctxt "field:lims.result_report.template,type:"
|
||||
msgctxt "field:lims.report.template,type:"
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,data:"
|
||||
msgctxt "field:lims.report.template.section,data:"
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,data_id:"
|
||||
msgctxt "field:lims.report.template.section,data_id:"
|
||||
msgid "File ID"
|
||||
msgstr "ID Archivo"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,name:"
|
||||
msgctxt "field:lims.report.template.section,name:"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,order:"
|
||||
msgctxt "field:lims.report.template.section,order:"
|
||||
msgid "Order"
|
||||
msgstr "Orden"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,position:"
|
||||
msgctxt "field:lims.report.template.section,position:"
|
||||
msgid "Position"
|
||||
msgstr "Posición"
|
||||
|
||||
msgctxt "field:lims.result_report.template.section,template:"
|
||||
msgctxt "field:lims.report.template.section,template:"
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
|
||||
msgctxt "field:lims.result_report.template.translation,lang:"
|
||||
msgctxt "field:lims.report.template.translation,lang:"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
msgctxt "field:lims.result_report.template.translation,src:"
|
||||
msgctxt "field:lims.report.template.translation,src:"
|
||||
msgid "Source"
|
||||
msgstr "Original"
|
||||
|
||||
msgctxt "field:lims.result_report.template.translation,template:"
|
||||
msgctxt "field:lims.report.template.translation,template:"
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
|
||||
msgctxt "field:lims.result_report.template.translation,value:"
|
||||
msgctxt "field:lims.report.template.translation,value:"
|
||||
msgid "Translation Value"
|
||||
msgstr "Traducción"
|
||||
|
||||
msgctxt "field:lims.result_report.template.trend.chart,chart:"
|
||||
msgctxt "field:lims.report.template.trend.chart,chart:"
|
||||
msgid "Trend Chart"
|
||||
msgstr "Gráfico de tendencia"
|
||||
|
||||
msgctxt "field:lims.result_report.template.trend.chart,order:"
|
||||
msgctxt "field:lims.report.template.trend.chart,order:"
|
||||
msgid "Order"
|
||||
msgstr "Orden"
|
||||
|
||||
msgctxt "field:lims.result_report.template.trend.chart,template:"
|
||||
msgctxt "field:lims.report.template.trend.chart,template:"
|
||||
msgid "Template"
|
||||
msgstr "Plantilla"
|
||||
|
||||
|
@ -150,7 +154,7 @@ msgctxt "field:lims.results_report.version.detail,charts_x_row:"
|
|||
msgid "Charts per Row"
|
||||
msgstr "Gráficos por fila"
|
||||
|
||||
msgctxt "field:lims.results_report.version.detail,comments_html:"
|
||||
msgctxt "field:lims.results_report.version.detail,comments_plain:"
|
||||
msgid "Comments"
|
||||
msgstr "Observaciones"
|
||||
|
||||
|
@ -234,11 +238,11 @@ msgctxt "field:party.party,result_template:"
|
|||
msgid "Report Template"
|
||||
msgstr "Plantilla de Informe"
|
||||
|
||||
msgctxt "model:ir.action,name:act_html_template_list"
|
||||
msgctxt "model:ir.action,name:act_result_template_list"
|
||||
msgid "Results Report Templates"
|
||||
msgstr "Plantillas de Informe de resultados"
|
||||
|
||||
msgctxt "model:ir.action,name:report_result_report"
|
||||
msgctxt "model:ir.action,name:report_result_report_html"
|
||||
msgid "Results Report"
|
||||
msgstr "Informe de resultados"
|
||||
|
||||
|
@ -250,6 +254,10 @@ msgctxt "model:ir.message,text:msg_no_template"
|
|||
msgid "The report has no template"
|
||||
msgstr "El informe no tiene ninguna plantilla"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_print_multiple_record"
|
||||
msgid "Please, select only one record to print"
|
||||
msgstr "Por favor, seleccione un solo registro para imprimir"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_section_pdf"
|
||||
msgid "Section files must be in PDF format"
|
||||
msgstr "Los archivos de secciones deben ser PDF"
|
||||
|
@ -258,23 +266,23 @@ msgctxt "model:ir.message,text:msg_yes"
|
|||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
msgctxt "model:ir.ui.menu,name:menu_html_template_list"
|
||||
msgctxt "model:ir.ui.menu,name:menu_result_template_list"
|
||||
msgid "Results Report Templates"
|
||||
msgstr "Plantillas de Informe"
|
||||
|
||||
msgctxt "model:lims.result_report.template,name:"
|
||||
msgid "Results Report Template"
|
||||
msgstr "Plantilla de Informe de resultados"
|
||||
msgctxt "model:lims.report.template,name:"
|
||||
msgid "Report Template"
|
||||
msgstr "Plantilla de Informe"
|
||||
|
||||
msgctxt "model:lims.result_report.template.section,name:"
|
||||
msgid "Results Report Template Section"
|
||||
msgstr "Sección de Plantilla de Informe de resultados"
|
||||
msgctxt "model:lims.report.template.section,name:"
|
||||
msgid "Report Template Section"
|
||||
msgstr "Sección de Plantilla de Informe"
|
||||
|
||||
msgctxt "model:lims.result_report.template.translation,name:"
|
||||
msgid "Results Report Template Translation"
|
||||
msgstr "Traducción de Plantilla de Informe de resultados"
|
||||
msgctxt "model:lims.report.template.translation,name:"
|
||||
msgid "Report Template Translation"
|
||||
msgstr "Traducción de Plantilla de Informe"
|
||||
|
||||
msgctxt "model:lims.result_report.template.trend.chart,name:"
|
||||
msgctxt "model:lims.report.template.trend.chart,name:"
|
||||
msgid "Results Report Template Trend Chart"
|
||||
msgstr "Gráfico de tendencia de Plantilla de Informe de resultados"
|
||||
|
||||
|
@ -286,39 +294,39 @@ msgctxt "model:lims.results_report.version.detail.trend.chart,name:"
|
|||
msgid "Results Report Version Detail Trend Chart"
|
||||
msgstr "Gráfico de tendencia de Detalle de versión de Informe de resultados"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,charts_x_row:"
|
||||
msgctxt "selection:lims.report.template,charts_x_row:"
|
||||
msgid "1"
|
||||
msgstr "1"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,charts_x_row:"
|
||||
msgctxt "selection:lims.report.template,charts_x_row:"
|
||||
msgid "2"
|
||||
msgstr "2"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,page_orientation:"
|
||||
msgctxt "selection:lims.report.template,page_orientation:"
|
||||
msgid "Landscape"
|
||||
msgstr "Horizontal"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,page_orientation:"
|
||||
msgctxt "selection:lims.report.template,page_orientation:"
|
||||
msgid "Portrait"
|
||||
msgstr "Vertical"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,type:"
|
||||
msgctxt "selection:lims.report.template,type:"
|
||||
msgid "HTML"
|
||||
msgstr "HTML"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,type:"
|
||||
msgctxt "selection:lims.report.template,type:"
|
||||
msgid "HTML - Footer"
|
||||
msgstr "HTML - Pie de página"
|
||||
|
||||
msgctxt "selection:lims.result_report.template,type:"
|
||||
msgctxt "selection:lims.report.template,type:"
|
||||
msgid "HTML - Header"
|
||||
msgstr "HTML - Encabezado"
|
||||
|
||||
msgctxt "selection:lims.result_report.template.section,position:"
|
||||
msgctxt "selection:lims.report.template.section,position:"
|
||||
msgid "Following"
|
||||
msgstr "Siguiente"
|
||||
|
||||
msgctxt "selection:lims.result_report.template.section,position:"
|
||||
msgctxt "selection:lims.report.template.section,position:"
|
||||
msgid "Previous"
|
||||
msgstr "Anterior"
|
||||
|
||||
|
@ -354,7 +362,7 @@ msgctxt "view:lims.analysis:"
|
|||
msgid "Report"
|
||||
msgstr "Informe"
|
||||
|
||||
msgctxt "view:lims.result_report.template:"
|
||||
msgctxt "view:lims.report.template:"
|
||||
msgid "Header and Footer"
|
||||
msgstr "Encabezado y Pie de página"
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data grouped="1">
|
||||
<record model="ir.message" id="msg_print_multiple_record">
|
||||
<field name="text">Please, select only one record to print</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_no_template">
|
||||
<field name="text">The report has no template</field>
|
||||
</record>
|
||||
|
|
|
@ -10,7 +10,7 @@ class Notebook(metaclass=PoolMeta):
|
|||
__name__ = 'lims.notebook'
|
||||
|
||||
result_template = fields.Function(fields.Many2One(
|
||||
'lims.result_report.template', 'Report Template'), 'get_sample_field')
|
||||
'lims.report.template', 'Report Template'), 'get_sample_field')
|
||||
resultrange_origin = fields.Function(fields.Many2One('lims.range.type',
|
||||
'Comparison range'), 'get_sample_field')
|
||||
|
||||
|
|
|
@ -9,5 +9,8 @@ from trytond.pool import PoolMeta
|
|||
class Party(metaclass=PoolMeta):
|
||||
__name__ = 'party.party'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])])
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
])
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
# This file is part of lims_report_html module for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains
|
||||
# the full copyright notices and license terms.
|
||||
import os
|
||||
import operator
|
||||
from mimetypes import guess_type as mime_guess_type
|
||||
from binascii import b2a_base64
|
||||
from functools import partial
|
||||
from decimal import Decimal
|
||||
from datetime import date, datetime
|
||||
from lxml import html as lxml_html
|
||||
from base64 import b64encode
|
||||
from babel.support import Translations as BabelTranslations
|
||||
from jinja2 import contextfilter, Markup
|
||||
from jinja2 import Environment, FunctionLoader
|
||||
from io import BytesIO
|
||||
from PyPDF2 import PdfFileMerger
|
||||
from PyPDF2.utils import PdfReadError
|
||||
|
@ -23,16 +11,19 @@ from trytond.pyson import Eval, Not, Bool
|
|||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
from trytond.tools import file_open
|
||||
from .generator import PdfGenerator
|
||||
from .html_template import LimsReport
|
||||
|
||||
|
||||
class ResultsReportVersionDetail(metaclass=PoolMeta):
|
||||
__name__ = 'lims.results_report.version.detail'
|
||||
|
||||
template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])],
|
||||
states={'readonly': Eval('state') != 'draft'}, depends=['state'])
|
||||
template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
],
|
||||
states={'readonly': Eval('state') != 'draft'},
|
||||
depends=['state'])
|
||||
template_type = fields.Function(fields.Selection([
|
||||
(None, ''),
|
||||
('base', 'HTML'),
|
||||
|
@ -56,9 +47,8 @@ class ResultsReportVersionDetail(metaclass=PoolMeta):
|
|||
('1', '1'),
|
||||
('2', '2'),
|
||||
], 'Charts per Row')
|
||||
comments_html = fields.Function(fields.Text('Comments',
|
||||
states={'readonly': ~Eval('state').in_(['draft', 'revised'])},
|
||||
depends=['state']), 'get_comments', setter='set_comments')
|
||||
comments_plain = fields.Function(fields.Text('Comments', translate=True),
|
||||
'get_comments_plain', setter='set_comments_plain')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
|
@ -71,10 +61,10 @@ class ResultsReportVersionDetail(metaclass=PoolMeta):
|
|||
@classmethod
|
||||
def view_attributes(cls):
|
||||
return super().view_attributes() + [
|
||||
('//page[@id="comments_html"]', 'states', {
|
||||
('//page[@id="comments"]', 'states', {
|
||||
'invisible': Not(Bool(Eval('template_type'))),
|
||||
}),
|
||||
('//page[@id="comments"]', 'states', {
|
||||
('//page[@id="comments_plain"]', 'states', {
|
||||
'invisible': Eval('template_type') == 'base',
|
||||
}),
|
||||
]
|
||||
|
@ -244,6 +234,13 @@ class ResultsReportVersionDetail(metaclass=PoolMeta):
|
|||
} for s in detail.sections])]
|
||||
return detail_default
|
||||
|
||||
def get_comments_plain(self, name):
|
||||
return self.comments
|
||||
|
||||
@classmethod
|
||||
def set_comments_plain(cls, records, name, value):
|
||||
cls.write(records, {'comments': value})
|
||||
|
||||
|
||||
class ResultsReportVersionDetailSection(ModelSQL, ModelView):
|
||||
'Results Report Version Detail Section'
|
||||
|
@ -426,18 +423,19 @@ class ResultsReportVersionDetailSample(metaclass=PoolMeta):
|
|||
return content
|
||||
|
||||
|
||||
class ResultReport(metaclass=PoolMeta):
|
||||
class ResultReport(LimsReport, metaclass=PoolMeta):
|
||||
__name__ = 'lims.result_report'
|
||||
|
||||
@classmethod
|
||||
def execute(cls, ids, data):
|
||||
if len(ids) > 1:
|
||||
raise UserError(gettext('lims.msg_multiple_reports'))
|
||||
|
||||
pool = Pool()
|
||||
ResultsDetail = pool.get('lims.results_report.version.detail')
|
||||
CachedReport = pool.get('lims.results_report.cached_report')
|
||||
|
||||
if len(ids) > 1:
|
||||
raise UserError(gettext(
|
||||
'lims_report_html.msg_print_multiple_record'))
|
||||
|
||||
results_report = ResultsDetail(ids[0])
|
||||
if results_report.state == 'annulled':
|
||||
raise UserError(gettext('lims.msg_annulled_report'))
|
||||
|
@ -449,12 +447,12 @@ class ResultReport(metaclass=PoolMeta):
|
|||
|
||||
template = results_report.template
|
||||
if template and template.type == 'base': # HTML
|
||||
result = cls.execute_html_results_report(ids, current_data)
|
||||
result = cls.execute_html_lims_report(ids, current_data)
|
||||
else:
|
||||
current_data['action_id'] = None
|
||||
if template and template.report:
|
||||
current_data['action_id'] = template.report.id
|
||||
result = cls.execute_custom_results_report(ids, current_data)
|
||||
result = cls.execute_custom_lims_report(ids, current_data)
|
||||
|
||||
cached_reports = CachedReport.search([
|
||||
('version_detail', '=', results_report.id),
|
||||
|
@ -489,338 +487,15 @@ class ResultReport(metaclass=PoolMeta):
|
|||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def execute_custom_results_report(cls, ids, data):
|
||||
pool = Pool()
|
||||
ActionReport = pool.get('ir.action.report')
|
||||
cls.check_access()
|
||||
|
||||
action_id = data.get('action_id')
|
||||
if action_id is None:
|
||||
action_reports = ActionReport.search([
|
||||
('report_name', '=', cls.__name__),
|
||||
('template_extension', '!=', 'results'),
|
||||
])
|
||||
assert action_reports, '%s not found' % cls
|
||||
action = action_reports[0]
|
||||
else:
|
||||
action = ActionReport(action_id)
|
||||
|
||||
records = []
|
||||
model = action.model or data.get('model')
|
||||
if model:
|
||||
records = cls._get_records(ids, model, data)
|
||||
oext, content = cls._execute(records, [], data, action)
|
||||
if not isinstance(content, str):
|
||||
content = bytearray(content) if bytes == str else bytes(content)
|
||||
return (oext, content, action.direct_print, action.name)
|
||||
|
||||
@classmethod
|
||||
def execute_html_results_report(cls, ids, data):
|
||||
pool = Pool()
|
||||
ActionReport = pool.get('ir.action.report')
|
||||
cls.check_access()
|
||||
|
||||
action_reports = ActionReport.search([
|
||||
('report_name', '=', cls.__name__),
|
||||
('template_extension', '=', 'results'),
|
||||
])
|
||||
assert action_reports, '%s not found' % cls
|
||||
action = action_reports[0]
|
||||
|
||||
records = []
|
||||
model = action.model or data.get('model')
|
||||
if model:
|
||||
records = cls._get_records(ids, model, data)
|
||||
oext, content = cls._execute_html_results_report(records, data, action)
|
||||
if not isinstance(content, str):
|
||||
content = bytearray(content) if bytes == str else bytes(content)
|
||||
return (oext, content, action.direct_print, action.name)
|
||||
|
||||
@classmethod
|
||||
def _execute_html_results_report(cls, records, data, action):
|
||||
record = records[0]
|
||||
template_id, tcontent, theader, tfooter = (
|
||||
cls.get_results_report_template(action, record.id))
|
||||
context = Transaction().context
|
||||
context['template'] = template_id
|
||||
if not template_id:
|
||||
context['default_translations'] = os.path.join(
|
||||
os.path.dirname(__file__), 'report', 'translations')
|
||||
with Transaction().set_context(**context):
|
||||
content = cls.render_results_report_template(action,
|
||||
tcontent, record=record, records=[record],
|
||||
data=data)
|
||||
header = theader and cls.render_results_report_template(action,
|
||||
theader, record=record, records=[record],
|
||||
data=data)
|
||||
footer = tfooter and cls.render_results_report_template(action,
|
||||
tfooter, record=record, records=[record],
|
||||
data=data)
|
||||
|
||||
stylesheets = cls.parse_stylesheets(tcontent)
|
||||
if theader:
|
||||
stylesheets += cls.parse_stylesheets(theader)
|
||||
if tfooter:
|
||||
stylesheets += cls.parse_stylesheets(tfooter)
|
||||
|
||||
page_orientation = (record.template and
|
||||
record.template.page_orientation or 'portrait')
|
||||
|
||||
document = PdfGenerator(content,
|
||||
header_html=header, footer_html=footer,
|
||||
side_margin=1, extra_vertical_margin=30,
|
||||
stylesheets=stylesheets,
|
||||
page_orientation=page_orientation).render_html().write_pdf()
|
||||
|
||||
if record.previous_sections or record.following_sections:
|
||||
merger = PdfFileMerger(strict=False)
|
||||
# Previous Sections
|
||||
for section in record.previous_sections:
|
||||
filedata = BytesIO(section.data)
|
||||
merger.append(filedata)
|
||||
# Results Report
|
||||
filedata = BytesIO(document)
|
||||
merger.append(filedata)
|
||||
# Following Sections
|
||||
for section in record.following_sections:
|
||||
filedata = BytesIO(section.data)
|
||||
merger.append(filedata)
|
||||
output = BytesIO()
|
||||
merger.write(output)
|
||||
document = output.getvalue()
|
||||
|
||||
return 'pdf', document
|
||||
|
||||
@classmethod
|
||||
def get_results_report_template(cls, action, detail_id):
|
||||
ResultsDetail = Pool().get('lims.results_report.version.detail')
|
||||
template_id, content, header, footer = None, None, None, None
|
||||
detail = ResultsDetail(detail_id)
|
||||
if detail.template:
|
||||
template_id = detail.template
|
||||
content = '<body>%s</body>' % detail.template.content
|
||||
header = (detail.template.header and
|
||||
'<header id="header">%s</header>' %
|
||||
detail.template.header.content)
|
||||
footer = (detail.template.footer and
|
||||
'<footer id="footer">%s</footer>' %
|
||||
detail.template.footer.content)
|
||||
if not content:
|
||||
content = (action.report_content and
|
||||
action.report_content.decode('utf-8'))
|
||||
if not content:
|
||||
raise UserError(gettext('lims_report_html.msg_no_template'))
|
||||
return template_id, content, header, footer
|
||||
|
||||
@classmethod
|
||||
def render_results_report_template(cls, action, template_string,
|
||||
record=None, records=None, data=None):
|
||||
User = Pool().get('res.user')
|
||||
user = User(Transaction().user)
|
||||
|
||||
if data and data.get('alt_lang'):
|
||||
locale = data['alt_lang']
|
||||
elif user.language:
|
||||
locale = user.language.code
|
||||
else:
|
||||
locale = Transaction().language
|
||||
with Transaction().set_context(locale=locale):
|
||||
env = cls.get_results_report_environment()
|
||||
|
||||
report_template = env.from_string(template_string)
|
||||
context = cls.get_context(records, [], data)
|
||||
context.update({
|
||||
'report': action,
|
||||
'get_image': cls.get_image,
|
||||
'operation': cls.operation,
|
||||
})
|
||||
res = report_template.render(**context)
|
||||
res = cls.parse_images(res)
|
||||
# print('TEMPLATE:\n', res)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_results_report_environment(cls):
|
||||
extensions = ['jinja2.ext.i18n', 'jinja2.ext.autoescape',
|
||||
'jinja2.ext.with_', 'jinja2.ext.loopcontrols', 'jinja2.ext.do']
|
||||
env = Environment(extensions=extensions,
|
||||
loader=FunctionLoader(lambda name: ''))
|
||||
|
||||
env.filters.update(cls.get_results_report_filters())
|
||||
|
||||
locale = Transaction().context.get('locale').split('_')[0]
|
||||
translations = TemplateTranslations(locale)
|
||||
env.install_gettext_translations(translations)
|
||||
return env
|
||||
|
||||
@classmethod
|
||||
def get_results_report_filters(cls):
|
||||
Lang = Pool().get('ir.lang')
|
||||
|
||||
def module_path(name):
|
||||
module, path = name.split('/', 1)
|
||||
with file_open(os.path.join(module, path)) as f:
|
||||
return 'file://%s' % f.name
|
||||
|
||||
def render(value, digits=2, lang=None, filename=None):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if isinstance(value, (float, Decimal)):
|
||||
return lang.format('%.*f', (digits, value), grouping=True)
|
||||
|
||||
if isinstance(value, int):
|
||||
return lang.format('%d', value, grouping=True)
|
||||
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
return gettext('lims_report_html.msg_yes')
|
||||
return gettext('lims_report_html.msg_no')
|
||||
|
||||
if hasattr(value, 'rec_name'):
|
||||
return value.rec_name
|
||||
|
||||
if isinstance(value, date):
|
||||
return lang.strftime(value)
|
||||
|
||||
if isinstance(value, datetime):
|
||||
return '%s %s' % (lang.strftime(value),
|
||||
value.strftime('%H:%M:%S'))
|
||||
|
||||
if isinstance(value, str):
|
||||
return value.replace('\n', '<br/>')
|
||||
|
||||
if isinstance(value, bytes):
|
||||
b64_value = b2a_base64(value).decode('ascii')
|
||||
mimetype = 'image/png'
|
||||
if filename:
|
||||
mimetype = mime_guess_type(filename)[0]
|
||||
return ('data:%s;base64,%s' % (mimetype, b64_value)).strip()
|
||||
return value
|
||||
|
||||
@contextfilter
|
||||
def subrender(context, value, subobj=None):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
_template = context.eval_ctx.environment.from_string(value)
|
||||
if subobj:
|
||||
new_context = {'subobj': subobj}
|
||||
new_context.update(context)
|
||||
else:
|
||||
new_context = context
|
||||
result = _template.render(**new_context)
|
||||
if context.eval_ctx.autoescape:
|
||||
result = Markup(result)
|
||||
return result
|
||||
|
||||
locale = Transaction().context.get('locale').split('_')[0]
|
||||
lang, = Lang.search([('code', '=', locale or 'en')])
|
||||
|
||||
return {
|
||||
'modulepath': module_path,
|
||||
'render': partial(render, lang=lang),
|
||||
'subrender': subrender,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parse_images(cls, template_string):
|
||||
Attachment = Pool().get('ir.attachment')
|
||||
root = lxml_html.fromstring(template_string)
|
||||
for elem in root.iter('img'):
|
||||
# get image from attachments
|
||||
if 'id' in elem.attrib:
|
||||
img = Attachment.search([('id', '=', int(elem.attrib['id']))])
|
||||
if img:
|
||||
elem.attrib['src'] = cls.get_image(img[0].data)
|
||||
# get image from TinyMCE widget
|
||||
elif 'data-mce-src' in elem.attrib:
|
||||
elem.attrib['src'] = elem.attrib['data-mce-src']
|
||||
del elem.attrib['data-mce-src']
|
||||
# set width and height in style attribute
|
||||
style = elem.attrib.get('style', '')
|
||||
if 'width' in elem.attrib:
|
||||
style += ' width: %spx;' % str(elem.attrib['width'])
|
||||
if 'height' in elem.attrib:
|
||||
style += ' height: %spx;' % str(elem.attrib['height'])
|
||||
elem.attrib['style'] = style
|
||||
return lxml_html.tostring(root).decode()
|
||||
|
||||
@classmethod
|
||||
def get_image(cls, image):
|
||||
if not image:
|
||||
return ''
|
||||
b64_image = b64encode(image).decode()
|
||||
return 'data:image/png;base64,%s' % b64_image
|
||||
|
||||
@classmethod
|
||||
def operation(cls, function, value1, value2):
|
||||
return getattr(operator, function)(value1, value2)
|
||||
|
||||
@classmethod
|
||||
def parse_stylesheets(cls, template_string):
|
||||
Attachment = Pool().get('ir.attachment')
|
||||
root = lxml_html.fromstring(template_string)
|
||||
res = []
|
||||
# get stylesheets from attachments
|
||||
elems = root.xpath("//div[@id='tryton_styles_container']/div")
|
||||
for elem in elems:
|
||||
css = Attachment.search([('id', '=', int(elem.attrib['id']))])
|
||||
if not css:
|
||||
continue
|
||||
res.append(css[0].data)
|
||||
return res
|
||||
|
||||
|
||||
class TemplateTranslations:
|
||||
|
||||
def __init__(self, lang='en'):
|
||||
self.cache = {}
|
||||
self.env = None
|
||||
self.current = None
|
||||
self.language = lang
|
||||
self.template = None
|
||||
self.set_language(lang)
|
||||
|
||||
def set_language(self, lang='en'):
|
||||
self.language = lang
|
||||
if lang in self.cache:
|
||||
self.current = self.cache[lang]
|
||||
return
|
||||
context = Transaction().context
|
||||
if context.get('default_translations'):
|
||||
default_translations = context['default_translations']
|
||||
if os.path.isdir(default_translations):
|
||||
self.current = BabelTranslations.load(
|
||||
dirname=default_translations, locales=[lang])
|
||||
self.cache[lang] = self.current
|
||||
else:
|
||||
self.template = context.get('template', -1)
|
||||
|
||||
def ugettext(self, message):
|
||||
ReportTemplate = Pool().get('lims.result_report.template')
|
||||
if self.current:
|
||||
return self.current.ugettext(message)
|
||||
elif self.template:
|
||||
return ReportTemplate.gettext(self.template, message,
|
||||
self.language)
|
||||
return message
|
||||
|
||||
def ngettext(self, singular, plural, n):
|
||||
ReportTemplate = Pool().get('lims.result_report.template')
|
||||
if self.current:
|
||||
return self.current.ugettext(singular, plural, n)
|
||||
elif self.template:
|
||||
return ReportTemplate.gettext(self.template, singular,
|
||||
self.language)
|
||||
return singular
|
||||
|
||||
|
||||
class GenerateReportStart(metaclass=PoolMeta):
|
||||
__name__ = 'lims.notebook.generate_results_report.start'
|
||||
|
||||
template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])],
|
||||
template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
],
|
||||
states={'readonly': Bool(Eval('report'))},
|
||||
depends=['report'])
|
||||
|
||||
|
|
|
@ -43,13 +43,13 @@
|
|||
|
||||
<!-- Results Report -->
|
||||
|
||||
<record model="ir.action.report" id="report_result_report">
|
||||
<record model="ir.action.report" id="report_result_report_html">
|
||||
<field name="name">Results Report</field>
|
||||
<field name="model">lims.results_report.version.detail</field>
|
||||
<field name="report_name">lims.result_report</field>
|
||||
<field name="report">lims_report_html/report/results_report.html</field>
|
||||
<field name="extension">pdf</field>
|
||||
<field name="template_extension">results</field>
|
||||
<field name="template_extension">lims</field>
|
||||
</record>
|
||||
|
||||
<!-- Wizard Generate Results Report -->
|
||||
|
|
|
@ -10,7 +10,7 @@ class Fraction(metaclass=PoolMeta):
|
|||
__name__ = 'lims.fraction'
|
||||
|
||||
result_template = fields.Function(fields.Many2One(
|
||||
'lims.result_report.template', 'Report Template'), 'get_sample_field')
|
||||
'lims.report.template', 'Report Template'), 'get_sample_field')
|
||||
|
||||
def _order_sample_field(name):
|
||||
def order_field(tables):
|
||||
|
@ -32,8 +32,11 @@ class Fraction(metaclass=PoolMeta):
|
|||
class Sample(metaclass=PoolMeta):
|
||||
__name__ = 'lims.sample'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])])
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
])
|
||||
resultrange_origin = fields.Many2One('lims.range.type', 'Comparison range',
|
||||
domain=[('use', '=', 'result_range')])
|
||||
|
||||
|
@ -47,8 +50,11 @@ class Sample(metaclass=PoolMeta):
|
|||
class CreateSampleStart(metaclass=PoolMeta):
|
||||
__name__ = 'lims.create_sample.start'
|
||||
|
||||
result_template = fields.Many2One('lims.result_report.template',
|
||||
'Report Template', domain=[('type', 'in', [None, 'base'])])
|
||||
result_template = fields.Many2One('lims.report.template',
|
||||
'Report Template', domain=[
|
||||
('report_name', '=', 'lims.result_report'),
|
||||
('type', 'in', [None, 'base']),
|
||||
])
|
||||
resultrange_origin = fields.Many2One('lims.range.type', 'Comparison range',
|
||||
domain=[('use', '=', 'result_range')])
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<xpath expr="/form/notebook/page[@id='times']" position="after">
|
||||
<page id="report" string="Report">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
expr="/form/notebook/page[@id='general']/field[@name='results_report_language']"
|
||||
position="after">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
expr="/form/notebook/page[@id='report']/separator[@id='report_comments']"
|
||||
position="before">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
<label name="resultrange_origin"/>
|
||||
<field name="resultrange_origin"/>
|
||||
</xpath>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<data>
|
||||
<xpath expr="/form/field[@name='headquarters']" position="after">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<data>
|
||||
<xpath expr="/form/group[@id='append_samples']" position="after">
|
||||
<label name="template"/>
|
||||
<field name="template"/>
|
||||
<field name="template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
expr="/form/notebook/page[@id='lims']/field[@name='report_language']"
|
||||
position="after">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -6,19 +6,21 @@
|
|||
<field name="type"/>
|
||||
<label name="report"/>
|
||||
<field name="report"/>
|
||||
<label name="resultrange_origin"/>
|
||||
<field name="resultrange_origin"/>
|
||||
<label name="page_orientation"/>
|
||||
<field name="page_orientation"/>
|
||||
<label name="resultrange_origin"/>
|
||||
<field name="resultrange_origin"/>
|
||||
<notebook>
|
||||
<page name="content">
|
||||
<field name="content" colspan="4" widget="html"/>
|
||||
</page>
|
||||
<page id="header_footer" string="Header and Footer">
|
||||
<label name="header"/>
|
||||
<field name="header"/>
|
||||
<field name="header"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
<label name="footer"/>
|
||||
<field name="footer"/>
|
||||
<field name="footer"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</page>
|
||||
<page name="translations">
|
||||
<field name="translations" colspan="4"/>
|
|
@ -12,15 +12,16 @@
|
|||
<xpath expr="/form/field[@name='report_result_type']" position="replace"/>
|
||||
<xpath expr="/form/label[@name='resultrange_origin']" position="before">
|
||||
<label name="template"/>
|
||||
<field name="template"/>
|
||||
<field name="template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/notebook/page[@name='comments']" position="replace">
|
||||
<page id="comments" string="Comments">
|
||||
<field name="comments" colspan="4" widget="text"/>
|
||||
<page id="comments_plain" string="Comments">
|
||||
<field name="comments_plain" colspan="4" widget="text"/>
|
||||
<field name="template_type" colspan="4" invisible="1"/>
|
||||
</page>
|
||||
<page id="comments_html" string="Comments">
|
||||
<field name="comments_html" colspan="4" widget="html"/>
|
||||
<page id="comments" string="Comments">
|
||||
<field name="comments" colspan="4" widget="html"/>
|
||||
<field name="template_type" colspan="4" invisible="1"/>
|
||||
</page>
|
||||
<page name="sections">
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
expr="/form/notebook/page[@id='report']/separator[@id='report_comments']"
|
||||
position="before">
|
||||
<label name="result_template"/>
|
||||
<field name="result_template"/>
|
||||
<field name="result_template"
|
||||
view_ids="lims_report_html.result_template_view_list,lims_report_html.result_template_view_form"/>
|
||||
<label name="resultrange_origin"/>
|
||||
<field name="resultrange_origin"/>
|
||||
</xpath>
|
||||
|
|
Loading…
Reference in New Issue