diff --git a/addons/account/data/payment_receipt_data.xml b/addons/account/data/payment_receipt_data.xml index 2b15ae5427ab984825979e6bf4739a85d3f58cb1..7cfcc760e189dc29f9f60d4e6da6237975ddc4bf 100644 --- a/addons/account/data/payment_receipt_data.xml +++ b/addons/account/data/payment_receipt_data.xml @@ -6,7 +6,7 @@ <field name="model_id" ref="account.model_account_payment" /> <field name="name">Print Receipt</field> <field name="key2">client_action_multi</field> - <field name="value" eval="'ir.actions.report.xml,' +str(ref('action_report_payment_receipt'))" /> + <field name="value" eval="'ir.actions.report,' +str(ref('action_report_payment_receipt'))" /> <field name="key">action</field> <field name="model">account.payment</field> </record> diff --git a/addons/account/test/account_report.yml b/addons/account/test/account_report.yml index 7ea73c1dd7545c4e663c09f888a73de913324f6e..b3ac442aa81a65f86c05e27ab95cb04405f8ea18 100644 --- a/addons/account/test/account_report.yml +++ b/addons/account/test/account_report.yml @@ -14,9 +14,8 @@ - !python {model: account.invoice, id: False}: | import os - import odoo.report from odoo import tools - data, format = odoo.report.render_report(self.env.cr, self.env.uid, [ref('account.account_invoice_customer0')], 'account.report_invoice', {}, {}) + data, format = ref('account.report_invoice').render(ref('account.account_invoice_customer0')) if tools.config['test_report_directory']: file(os.path.join(tools.config['test_report_directory'], 'account-invoice.'+format), 'wb+').write(data) - @@ -24,9 +23,8 @@ - !python {model: res.partner, id: False}: | import os - import odoo.report from odoo import tools - data, format = odoo.report.render_report(self.env.cr, self.env.uid, [ref('base.res_partner_1'),ref('base.res_partner_2'),ref('base.res_partner_12')], 'account.report_overdue', {}, {}) + data, format = ref('account.report_overdue').render([ref('base.res_partner_1'),ref('base.res_partner_2'),ref('base.res_partner_12')]) if tools.config['test_report_directory']: file(os.path.join(tools.config['test_report_directory'], 'account-report_overdue.'+format), 'wb+').write(data) - diff --git a/addons/account/views/account_report.xml b/addons/account/views/account_report.xml index 9d3e9b215c90717b36bb9ffc196ce57cc7429b15..ddf5cb21cb02a14d145c89f72418ae594300b842 100644 --- a/addons/account/views/account_report.xml +++ b/addons/account/views/account_report.xml @@ -13,7 +13,7 @@ attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')" /> - <record id="account_invoices" model="ir.actions.report.xml"> + <record id="account_invoices" model="ir.actions.report"> <field name="print_report_name">(object.type == 'out_invoice' and object.state == 'draft' and 'Draft Invoice' or object.type == 'out_invoice' and object.state in ('open','paid') and 'Invoice'+'-'+(object.number) or object.type == 'out_refund' and object.state == 'draft' and 'Credit Note' or @@ -33,7 +33,7 @@ file="account.report_invoice_duplicate" attachment_use="False" /> - <record id="account_invoice_action_report_duplicate" model="ir.actions.report.xml"> + <record id="account_invoice_action_report_duplicate" model="ir.actions.report"> <field name="print_report_name">(object.type == 'out_invoice' and object.state == 'draft' and 'Duplicate Invoice' or object.type == 'out_invoice' and object.state in ('open','paid') and 'Duplicate Invoice'+'-'+(object.number) or object.type == 'out_refund' and object.state == 'draft' and 'Duplicate Credit Note' or @@ -52,7 +52,7 @@ name="account.report_overdue" file="account.report_overdue" /> - <record id="action_report_print_overdue" model="ir.actions.report.xml"> + <record id="action_report_print_overdue" model="ir.actions.report"> <field name="print_report_name">'Due Payments'+'-'+(object.name)</field> </record> diff --git a/addons/base_import_module/tests/test_module/test.xml b/addons/base_import_module/tests/test_module/test.xml index 8e3df933e2a73ccb3a3fdbe322e6ca9a15c2f631..80e24a4293bf39334752b339d10661a8729ef17a 100644 --- a/addons/base_import_module/tests/test_module/test.xml +++ b/addons/base_import_module/tests/test_module/test.xml @@ -7,7 +7,7 @@ <record id="main_company2" model="res.company"> <field name="name">Hagrid</field> - <field name="rml_header1">My Company Tagline</field> + <field name="report_header">My Company Tagline</field> <field name="currency_id" ref="base.EUR"/> </record> diff --git a/addons/base_setup/models/res_config.py b/addons/base_setup/models/res_config.py index ceeb7c3bdbff919b8f8c94f9112acb6806ea5014..8db758ccc31598c9c3b06febdd8bb7039da3dd99 100644 --- a/addons/base_setup/models/res_config.py +++ b/addons/base_setup/models/res_config.py @@ -28,7 +28,7 @@ class BaseConfigSettings(models.TransientModel): " * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n" " * Unchecked : Each company can see only its partner (partners where company is defined). Partners not related to a company are visible for all companies.") default_custom_report_footer = fields.Boolean("Custom Report Footer") - rml_footer = fields.Text(related="company_id.rml_footer", string='Custom Report Footer', help="Footer text displayed at the bottom of all reports.") + report_footer = fields.Text(related="company_id.report_footer", string='Custom Report Footer', help="Footer text displayed at the bottom of all reports.") group_multi_currency = fields.Boolean(string='Allow multi currencies', implied_group='base.group_multi_currency', help="Allows to work in a multi currency environment") diff --git a/addons/event/report/event_event_reports.xml b/addons/event/report/event_event_reports.xml index 2f81b7b0e3b220961e6a6f75c2e977e9b395f45a..341d69702c23fc05c3095b7c92844c12cac87b77 100644 --- a/addons/event/report/event_event_reports.xml +++ b/addons/event/report/event_event_reports.xml @@ -25,7 +25,7 @@ name="event.event_registration_report_template_badge" file="event.event_registration_report_template_badge" paperformat="event.paperformat_euro_lowmargin"/> - <record id="report_event_registration_badge" model="ir.actions.report.xml"> + <record id="report_event_registration_badge" model="ir.actions.report"> <field name="print_report_name">'Registration Event'+'-'+(object.name)</field> </record> diff --git a/addons/hr_attendance/report/hr_employee_badge.xml b/addons/hr_attendance/report/hr_employee_badge.xml index 8da14a63e8579b7fdb56c081ecfdcdd438a461a4..0ee61507e086c29934922d84cc4bff82263eeea5 100644 --- a/addons/hr_attendance/report/hr_employee_badge.xml +++ b/addons/hr_attendance/report/hr_employee_badge.xml @@ -7,7 +7,7 @@ report_type="qweb-pdf" name="hr_attendance.print_employee_badge" file="hr_attendance.print_employee_badge"/> - <record id="hr_employee_print_badge" model="ir.actions.report.xml"> + <record id="hr_employee_print_badge" model="ir.actions.report"> <field name="print_report_name">'Print Badge'+'-'+(object.name)</field> </record> diff --git a/addons/hr_expense/report/report_expense_sheet.xml b/addons/hr_expense/report/report_expense_sheet.xml index e033d7920c1a5079b8072dd4878530e50b737ca3..6511dcbcf0c1498850ea7e48c8faa8a36caf5544 100644 --- a/addons/hr_expense/report/report_expense_sheet.xml +++ b/addons/hr_expense/report/report_expense_sheet.xml @@ -106,7 +106,7 @@ name="hr_expense.report_expense_sheet" file="hr_expense.report_expense_sheet" /> - <record id="action_report_hr_expense_sheet" model="ir.actions.report.xml"> + <record id="action_report_hr_expense_sheet" model="ir.actions.report"> <field name="print_report_name">'Expenses'+'-'+(object.employee_id.name)+'-'+(object.name)</field> </record> diff --git a/addons/hr_holidays/report/hr_holidays_reports.xml b/addons/hr_holidays/report/hr_holidays_reports.xml index b033dbf3679e71d093cf026ef4f32b402132534d..bea309eb923be7ad80e88b774d420fd3f917e29e 100644 --- a/addons/hr_holidays/report/hr_holidays_reports.xml +++ b/addons/hr_holidays/report/hr_holidays_reports.xml @@ -10,7 +10,7 @@ file="hr_holidays.report_holidayssummary" menu="False"/> - <record id="report_holidays_summary" model="ir.actions.report.xml"> + <record id="report_holidays_summary" model="ir.actions.report"> <field name="paperformat_id" ref="hr_holidays.paperformat_hrsummary"/> </record> diff --git a/addons/hr_payroll/tests/test_payslip_flow.py b/addons/hr_payroll/tests/test_payslip_flow.py index 7ab0fad275a91c1a756a597f2180d7e1fd77707d..54de80bfbba435a64524e940e6a6d1e0a067abe3 100644 --- a/addons/hr_payroll/tests/test_payslip_flow.py +++ b/addons/hr_payroll/tests/test_payslip_flow.py @@ -2,10 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import os -from datetime import datetime -from dateutil.relativedelta import relativedelta -from odoo.report import render_report from odoo.tools import config, test_reports from odoo.addons.hr_payroll.tests.common import TestPayslipBase @@ -72,14 +69,14 @@ class TestPayslipFlow(TestPayslipBase): }) # I print the payslip report - data, format = render_report(self.env.cr, self.env.uid, richard_payslip.ids, 'hr_payroll.report_payslip', {}, {}) + data, data_format = self.env.ref('hr_payroll.action_report_payslip').render(richard_payslip.ids) if config.get('test_report_directory'): - open(os.path.join(config['test_report_directory'], 'hr_payroll-payslip.'+ format), 'wb+').write(data) + open(os.path.join(config['test_report_directory'], 'hr_payroll-payslip.'+ data_format), 'wb+').write(data) # I print the payslip details report - data, format = render_report(self.env.cr, self.env.uid, richard_payslip.ids, 'hr_payroll.report_payslipdetails', {}, {}) + data, data_format = self.env.ref('hr_payroll.payslip_details_report').render(richard_payslip.ids) if config.get('test_report_directory'): - open(os.path.join(config['test_report_directory'], 'hr_payroll-payslipdetails.'+ format), 'wb+').write(data) + open(os.path.join(config['test_report_directory'], 'hr_payroll-payslipdetails.'+ data_format), 'wb+').write(data) # I print the contribution register report context = {'model': 'hr.contribution.register', 'active_ids': [self.ref('hr_payroll.hr_houserent_register')]} diff --git a/addons/hr_payroll/views/hr_payroll_report.xml b/addons/hr_payroll/views/hr_payroll_report.xml index c6e02324f0a64ddda58e2a5348af3a72c3495e89..6b1e8a94637cdeb7046d684ca0c370046ebda5a4 100644 --- a/addons/hr_payroll/views/hr_payroll_report.xml +++ b/addons/hr_payroll/views/hr_payroll_report.xml @@ -17,7 +17,7 @@ name="hr_payroll.report_payslip" file="hr_payroll.report_payslip" /> - <record id="action_report_payslip" model="ir.actions.report.xml"> + <record id="action_report_payslip" model="ir.actions.report"> <field name="print_report_name">(object.employee_id.name)+'-'+'Payslip'</field> </record> <report @@ -28,7 +28,7 @@ name="hr_payroll.report_payslipdetails" file="hr_payroll.report_payslipdetails" /> - <record id="payslip_details_report" model="ir.actions.report.xml"> + <record id="payslip_details_report" model="ir.actions.report"> <field name="print_report_name">(object.name)</field> </record> </odoo> diff --git a/addons/l10n_ca/data/res_company_data.xml b/addons/l10n_ca/data/res_company_data.xml index 11adcbd01d4ae47b15cbaf2c5452c4858a2000aa..fdc19388202847711700cc97e5d35fc4590a00c4 100644 --- a/addons/l10n_ca/data/res_company_data.xml +++ b/addons/l10n_ca/data/res_company_data.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <record model="res.company" id="base.main_company"> - <field name="rml_paper_format">us_letter</field> <field name="paperformat_id" ref="report.paperformat_us"/> </record> </odoo> diff --git a/addons/l10n_ch/report/isr_report.xml b/addons/l10n_ch/report/isr_report.xml index 23ff2f70758818a3d8335f24d11f4dd0e6e08436..1cabbd8c0560b0d0dc9f5c0fc58d3d6a48586b0b 100644 --- a/addons/l10n_ch/report/isr_report.xml +++ b/addons/l10n_ch/report/isr_report.xml @@ -27,7 +27,7 @@ menu="False" /> - <record id="l10n_ch_isr_report" model ="ir.actions.report.xml"> + <record id="l10n_ch_isr_report" model ="ir.actions.report"> <field name="print_report_name"> 'ISR-' + object.number <!--No additional condition on invoice state or type as this diff --git a/addons/l10n_fr/test/l10n_fr_report.yml b/addons/l10n_fr/test/l10n_fr_report.yml index aaff326624d64272faf63b7b578d1ccfa1e03f0e..927d7d4f2c12348a6221b53b481c2b3c086b9ad9 100644 --- a/addons/l10n_fr/test/l10n_fr_report.yml +++ b/addons/l10n_fr/test/l10n_fr_report.yml @@ -3,9 +3,8 @@ - !python {model: account.move.line, id: False}: | import os - import odoo.report from odoo import tools - data, format = odoo.report.render_report(self.env.cr, self.env.uid, [], 'l10n_fr.report_l10nfrbilan', {'model':'account.move.line', 'form':{'fiscalyear_id': ref('account.data_fiscalyear')}}, {}) + data, format = self.env.ref('l10n_fr.report_l10nfrbilan').render([], data={'model':'account.move.line', 'form':{'fiscalyear_id': ref('account.data_fiscalyear')}}) if tools.config['test_report_directory']: file(os.path.join(tools.config['test_report_directory'], 'l10n_fr-bilan_report.'+format), 'wb+').write(data) @@ -14,8 +13,7 @@ - !python {model: account.move.line}: | import os - import odoo.report from odoo import tools - data, format = odoo.report.render_report(self.env.cr, self.env.uid, [], 'l10n_fr.report_l10nfrresultat', {'model':'account.move.line', 'form':{'fiscalyear_id': ref('account.data_fiscalyear')}}, {}) + data, format = self.env.ref('l10n_fr.report_l10nfrresultat').render([], data={'model':'account.move.line', 'form':{'fiscalyear_id': ref('account.data_fiscalyear')}}) if tools.config['test_report_directory']: file(os.path.join(tools.config['test_report_directory'], 'l10n_fr-compute_resultant_report.'+format), 'wb+').write(data) diff --git a/addons/l10n_in_hr_payroll/tests/test_payment_advice.py b/addons/l10n_in_hr_payroll/tests/test_payment_advice.py index d08c0f58b7a3c69a9ad5d3e784adae5c8419c438..7d9417606f39500ff60f6a99a909ee32c30d5c27 100644 --- a/addons/l10n_in_hr_payroll/tests/test_payment_advice.py +++ b/addons/l10n_in_hr_payroll/tests/test_payment_advice.py @@ -3,7 +3,6 @@ import os -from odoo.report import render_report from odoo.tools import config from odoo.addons.l10n_in_hr_payroll.tests.common import TestPaymentAdviceBase @@ -38,6 +37,6 @@ class TestPaymentAdvice(TestPaymentAdviceBase): self.assertEqual(payment_advice.state, 'confirm') # In order to test the PDF report defined on a Payment Advice, we will print a Print Advice Report when NEFT is checked - data, format = render_report(self.env.cr, self.env.uid, payment_advice.ids, 'l10n_in_hr_payroll.report_payrolladvice', {}, {}) + data, data_format = self.env.ref('l10n_in_hr_payroll.payroll_advice').render(payment_advice.ids) if config.get('test_report_directory'): - open(os.path.join(config['test_report_directory'], 'l10n_in_hr_payroll_summary_report' + format), 'wb+').write(data) + open(os.path.join(config['test_report_directory'], 'l10n_in_hr_payroll_summary_report' + data_format), 'wb+').write(data) diff --git a/addons/l10n_us/data/res_company_data.xml b/addons/l10n_us/data/res_company_data.xml index 11adcbd01d4ae47b15cbaf2c5452c4858a2000aa..fdc19388202847711700cc97e5d35fc4590a00c4 100644 --- a/addons/l10n_us/data/res_company_data.xml +++ b/addons/l10n_us/data/res_company_data.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <odoo> <record model="res.company" id="base.main_company"> - <field name="rml_paper_format">us_letter</field> <field name="paperformat_id" ref="report.paperformat_us"/> </record> </odoo> diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 2c989bb7e2e73e8fdb0e17bb288b32eb8cab09b6..568337c75511e1f236ca88e0a4745b57ec05bf75 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -15,7 +15,6 @@ import urlparse from urllib import urlencode, quote as quote from odoo import _, api, fields, models, tools -from odoo import report as odoo_report from odoo.exceptions import UserError from odoo.tools import pycompat @@ -155,7 +154,7 @@ class MailTemplate(models.Model): report_name = fields.Char('Report Filename', translate=True, help="Name to use for the generated report file (may contain placeholders)\n" "The extension can be omitted and will then come from the report type.") - report_template = fields.Many2one('ir.actions.report.xml', 'Optional report to print and attach') + report_template = fields.Many2one('ir.actions.report', 'Optional report to print and attach') ref_ir_act_window = fields.Many2one('ir.actions.act_window', 'Sidebar action', readonly=True, copy=False, help="Sidebar action to make this template available on records " "of the related document model") @@ -508,10 +507,9 @@ class MailTemplate(models.Model): report = template.report_template report_service = report.report_name - if report.report_type in ['qweb-html', 'qweb-pdf']: - result, format = Template.env['report'].get_pdf([res_id], report_service), 'pdf' - else: - result, format = odoo_report.render_report(self._cr, self._uid, [res_id], report_service, {'model': template.model}, Template._context) + if report.report_type not in ['qweb-html', 'qweb-pdf']: + raise UserError(_('Unsupported report type %s found.') % report.report_type) + result, format = Template.env['report'].get_pdf([res_id], report_service), 'pdf' # TODO in trunk, change return format to binary to match message_post expected format result = base64.b64encode(result) diff --git a/addons/marketing_campaign/models/ir_actions.py b/addons/marketing_campaign/models/ir_actions.py index cb9a55db6cca34d423cdef63b1b5e1f44a00df2f..989507a9fa5da154b1aadaab4253f63531718e8b 100644 --- a/addons/marketing_campaign/models/ir_actions.py +++ b/addons/marketing_campaign/models/ir_actions.py @@ -4,8 +4,8 @@ from odoo import api, models -class IrActionsReportXml(models.Model): - _inherit = 'ir.actions.report.xml' +class IrActionsReport(models.Model): + _inherit = 'ir.actions.report' @api.model def search(self, args, offset=0, limit=None, order=None, count=False): @@ -13,4 +13,4 @@ class IrActionsReportXml(models.Model): if model_id: model = self.env['ir.model'].browse(model_id).model args.append(('model', '=', model)) - return super(IrActionsReportXml, self).search(args, offset=offset, limit=limit, order=order, count=count) + return super(IrActionsReport, self).search(args, offset=offset, limit=limit, order=order, count=count) diff --git a/addons/marketing_campaign/models/marketing_campaign.py b/addons/marketing_campaign/models/marketing_campaign.py index de60ed86e4f349091c70e725e4eeb076aa22665c..a6a3acf303e9f11e4a78749b781a1739163e91ee 100644 --- a/addons/marketing_campaign/models/marketing_campaign.py +++ b/addons/marketing_campaign/models/marketing_campaign.py @@ -311,7 +311,7 @@ class MarketingCampaignActivity(models.Model): "- Report: print an existing Report defined on the resource item and save it into a specific directory \n" "- Custom Action: execute a predefined action, e.g. to modify the fields of the resource record") email_template_id = fields.Many2one('mail.template', "Email Template", help='The email to send when this activity is activated') - report_id = fields.Many2one('ir.actions.report.xml', "Report", help='The report to generate when this activity is activated') + report_id = fields.Many2one('ir.actions.report', "Report", help='The report to generate when this activity is activated') server_action_id = fields.Many2one('ir.actions.server', string='Action', help="The action to perform when this activity is activated") to_ids = fields.One2many('marketing.campaign.transition', 'activity_from_id', 'Next Activities') @@ -343,7 +343,7 @@ class MarketingCampaignActivity(models.Model): @api.multi def _process_wi_report(self, workitem): self.ensure_one() - return self.report_id.render_report(workitem.res_id, self.report_id.report_name, None) + return self.report_id.render(workitem.res_id) @api.multi def _process_wi_action(self, workitem): @@ -611,7 +611,7 @@ class MarketingCampaignWorkitem(models.Model): 'model': self.object_id.model } res = { - 'type': 'ir.actions.report.xml', + 'type': 'ir.actions.report', 'report_name': self.activity_id.report_id.report_name, 'datas': datas, } diff --git a/addons/mrp/report/mrp_report_views_main.xml b/addons/mrp/report/mrp_report_views_main.xml index cdc7d40e79e4b230064156f9d8fd8550030af243..86dfc67ecba76bcc67375d031cc47311459d4abe 100644 --- a/addons/mrp/report/mrp_report_views_main.xml +++ b/addons/mrp/report/mrp_report_views_main.xml @@ -9,7 +9,7 @@ file="mrp.report.mrp_bom_templates" report_type="qweb-pdf" /> - <record id="action_report_bom_structure" model="ir.actions.report.xml"> + <record id="action_report_bom_structure" model="ir.actions.report"> <field name="print_report_name">'BOM'+'-'+(object.product_id.name or object.product_tmpl_id.name)</field> </record> @@ -21,7 +21,7 @@ file="mrp.report.mrp_production_templates" report_type="qweb-pdf" /> - <record id="action_report_production_order" model="ir.actions.report.xml"> + <record id="action_report_production_order" model="ir.actions.report"> <field name="print_report_name">'Production Order'+'-'+(object.name)</field> </record> @@ -33,7 +33,7 @@ file="mrp_bom_cost" report_type="qweb-html" /> - <record id="action_report_bom_price" model="ir.actions.report.xml"> + <record id="action_report_bom_price" model="ir.actions.report"> <field name="print_report_name">'BOM Cost'+'-'+(object.product_id.name or object.product_tmpl_id.name)</field> </record> </data> diff --git a/addons/mrp_repair/report/mrp_repair_reports.xml b/addons/mrp_repair/report/mrp_repair_reports.xml index 9963f87d614070e75c2e3b7ae80ce3c85a67d834..3cc755f5b73e80b135289d2719df83b85ff53812 100644 --- a/addons/mrp_repair/report/mrp_repair_reports.xml +++ b/addons/mrp_repair/report/mrp_repair_reports.xml @@ -9,7 +9,7 @@ file="mrp_repair.report_mrprepairorder" report_type="qweb-pdf" /> - <record id="action_report_mrp_repair_order" model="ir.actions.report.xml"> + <record id="action_report_mrp_repair_order" model="ir.actions.report"> <field name="print_report_name">(object.state == 'draft' and 'Repair Quotation'+'-'+(object.name) or 'Repair Order'+'-'+(object.name))</field> </record> </data> diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index 24869ad5881d42f5900bed392e5e6988ed499233..26a13bf2f5f2d02f7e5c316a556f969ca45ad3ec 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -3,9 +3,10 @@ import time import odoo from odoo import fields -from odoo.tools import float_compare, mute_logger +from odoo.tools import float_compare, mute_logger, test_reports from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon + @odoo.tests.common.at_install(False) @odoo.tests.common.post_install(True) class TestPointOfSaleFlow(TestPointOfSaleCommon): diff --git a/addons/point_of_sale/views/point_of_sale_report.xml b/addons/point_of_sale/views/point_of_sale_report.xml index b8e8bafac9cca729f88f9e5567358f4ce2d3035a..d010e7fcfe47b93df5c7f15122d9659053edba06 100644 --- a/addons/point_of_sale/views/point_of_sale_report.xml +++ b/addons/point_of_sale/views/point_of_sale_report.xml @@ -10,7 +10,7 @@ /> <!-- used from POS UI, no need to be in print menu --> - <record id="pos_invoice_report" model="ir.actions.report.xml"> + <record id="pos_invoice_report" model="ir.actions.report"> <field name="name">Invoice</field> <field name="model">pos.order</field> <field name="report_type">qweb-pdf</field> @@ -18,7 +18,7 @@ <field name="print_report_name">'Invoice'+'-'+(object.name)</field> </record> - <record id="sale_details_report" model="ir.actions.report.xml"> + <record id="sale_details_report" model="ir.actions.report"> <field name="name">Sales Details</field> <field name="model">report.point_of_sale.details</field> <field name="report_type">qweb-pdf</field> diff --git a/addons/product/report/product_reports.xml b/addons/product/report/product_reports.xml index 2c7a58336d0460e2dd3f526c8c8df348f10e1ef9..0e48f95aa27151d7ff89c612b2d25ac2250f142a 100644 --- a/addons/product/report/product_reports.xml +++ b/addons/product/report/product_reports.xml @@ -8,7 +8,7 @@ report_type="qweb-pdf" name="product.report_productlabel" file="product.report_productlabel"/> - <record id="report_product_label" model="ir.actions.report.xml"> + <record id="report_product_label" model="ir.actions.report"> <field name="print_report_name">'Products Labels'+'-'+(object.name)</field> </record> @@ -19,7 +19,7 @@ report_type="qweb-pdf" name="product.report_producttemplatelabel" file="product.report_producttemplatelabel"/> - <record id="report_product_template_label" model="ir.actions.report.xml"> + <record id="report_product_template_label" model="ir.actions.report"> <field name="print_report_name">'Products Labels'+'-'+(object.name)</field> </record> diff --git a/addons/purchase/report/purchase_reports.xml b/addons/purchase/report/purchase_reports.xml index a266c4d014704f642a4be371885d67f4e898b606..33524f2342c9982d0ec6270e71db483198809d25 100644 --- a/addons/purchase/report/purchase_reports.xml +++ b/addons/purchase/report/purchase_reports.xml @@ -8,7 +8,7 @@ name="purchase.report_purchaseorder" file="purchase.report_purchaseorder" /> - <record id="action_report_purchase_order" model="ir.actions.report.xml"> + <record id="action_report_purchase_order" model="ir.actions.report"> <field name="print_report_name">(object.state in ('draft', 'sent') and 'Request for Quotation'+'-'+(object.name) or 'Purchase Order'+'-'+(object.name))</field> </record> @@ -20,7 +20,7 @@ name="purchase.report_purchasequotation" file="purchase.report_purchasequotation" /> - <record id="report_purchase_quotation" model="ir.actions.report.xml"> + <record id="report_purchase_quotation" model="ir.actions.report"> <field name="print_report_name">'Request for Quotation'+'-'+(object.name)</field> </record> </odoo> diff --git a/addons/purchase_requisition/report/purchase_requisition_report.xml b/addons/purchase_requisition/report/purchase_requisition_report.xml index 98b73254c115f312be8b43303d0840b4e4e88f1f..0870c38afb0a1fff1280ba57f36abca29139f420 100644 --- a/addons/purchase_requisition/report/purchase_requisition_report.xml +++ b/addons/purchase_requisition/report/purchase_requisition_report.xml @@ -9,7 +9,7 @@ name="purchase_requisition.report_purchaserequisitions" file="purchase_requisition.report.report_purchaserequisitions" /> - <record id="action_report_purchase_requisitions" model="ir.actions.report.xml"> + <record id="action_report_purchase_requisitions" model="ir.actions.report"> <field name="print_report_name">'Tender'+'-'+(object.name)</field> </record> </data> diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 7e0fd9f8123b98dec8f531880b26a460b05572f4..43771252ccb156aa333ec6025e36b8089a9c9673 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -110,11 +110,6 @@ class ReportController(Controller): response.headers.add('Content-Disposition', content_disposition(filename)) response.set_cookie('fileToken', token) return response - elif type == 'controller': - reqheaders = Headers(request.httprequest.headers) - response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders, follow_redirects=True) - response.set_cookie('fileToken', token) - return response else: return except Exception as e: diff --git a/addons/report/models/__init__.py b/addons/report/models/__init__.py index c983832539e0fa04a541f3799d63cfa94d6e6637..5e9033225cfb949145b76dda6b4339c04a62f287 100644 --- a/addons/report/models/__init__.py +++ b/addons/report/models/__init__.py @@ -3,7 +3,7 @@ from . import abstract_report from . import base_config_settings -from . import ir_actions_report_xml +from . import ir_actions_report from . import ir_http from . import ir_qweb from . import report diff --git a/addons/report/models/ir_actions_report_xml.py b/addons/report/models/ir_actions_report.py similarity index 87% rename from addons/report/models/ir_actions_report_xml.py rename to addons/report/models/ir_actions_report.py index 8461815e389f689179572c6c5ad75f8ca4ef6564..cc46523c0b23b8ee6e757008debf6385ce3f1e50 100644 --- a/addons/report/models/ir_actions_report_xml.py +++ b/addons/report/models/ir_actions_report.py @@ -4,7 +4,7 @@ from odoo import api, fields, models class ir_actions_report(models.Model): - _inherit = 'ir.actions.report.xml' + _inherit = 'ir.actions.report' paperformat_id = fields.Many2one('report.paperformat', 'Paper format') print_report_name = fields.Char('Printed Report Name', @@ -12,7 +12,7 @@ class ir_actions_report(models.Model): @api.multi def associated_view(self): - """Used in the ir.actions.report.xml form view in order to search naively after the view(s) + """Used in the ir.actions.report form view in order to search naively after the view(s) used in the rendering. """ self.ensure_one() diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 55abb28a6383d39e73f9ab3cc4a52e77458ee27a..b581bf0a6130d63a5b7185da0901017af03bc2f9 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -186,7 +186,7 @@ class Report(models.Model): html = html.decode('utf-8') # Ensure the current document is utf-8 encoded. - # Get the ir.actions.report.xml record we are working on. + # Get the ir.actions.report record we are working on. report = self._get_report_from_name(report_name) # Check if we have to save the report or if we have to get one from the db. save_in_attachment = self._check_attachment_use(docids, report) @@ -267,7 +267,7 @@ class Report(models.Model): @api.noguess def get_action(self, docids, report_name, data=None, config=True): - """Return an action of type ir.actions.report.xml. + """Return an action of type ir.actions.report. :param docids: id/ids/browserecord of the records to print (if not used, pass an empty list) :param report_name: Name of the template to generate an action for @@ -297,14 +297,14 @@ class Report(models.Model): active_ids = docids context = dict(self.env.context, active_ids=active_ids) - report = self.env['ir.actions.report.xml'].with_context(context).search([('report_name', '=', report_name)]) + report = self.env['ir.actions.report'].with_context(context).search([('report_name', '=', report_name)]) if not report: raise UserError(_("Bad Report Reference") + _("This report is not loaded into the database: %s.") % report_name) return { 'context': context, 'data': data, - 'type': 'ir.actions.report.xml', + 'type': 'ir.actions.report', 'report_name': report.report_name, 'report_type': report.report_type, 'report_file': report.report_file, @@ -515,10 +515,10 @@ class Report(models.Model): @api.model def _get_report_from_name(self, report_name): - """Get the first record of ir.actions.report.xml having the ``report_name`` as value for + """Get the first record of ir.actions.report having the ``report_name`` as value for the field report_name. """ - report_obj = self.env['ir.actions.report.xml'] + report_obj = self.env['ir.actions.report'] qwebtypes = ['qweb-pdf', 'qweb-html'] conditions = [('report_type', 'in', qwebtypes), ('report_name', '=', report_name)] context = self.env['res.users'].context_get() diff --git a/addons/report/models/report_paperformat.py b/addons/report/models/report_paperformat.py index a97a1dfd3e0c5e35ccd2d3ed2d09aea31999dfa9..08235f3060b2897b20a085c678b3bd97e58ea577 100644 --- a/addons/report/models/report_paperformat.py +++ b/addons/report/models/report_paperformat.py @@ -60,7 +60,7 @@ class report_paperformat(models.Model): header_line = fields.Boolean('Display a header line', default=False) header_spacing = fields.Integer('Header spacing', default=35) dpi = fields.Integer('Output DPI', required=True, default=90) - report_ids = fields.One2many('ir.actions.report.xml', 'paperformat_id', 'Associated reports', help="Explicitly associated reports") + report_ids = fields.One2many('ir.actions.report', 'paperformat_id', 'Associated reports', help="Explicitly associated reports") @api.constrains('format') def _check_format_or_page(self): diff --git a/addons/report/models/res_company.py b/addons/report/models/res_company.py index 85683e6edc1dbed03a9dd2c55533d80d06800e7e..61fa63fea8681595c39cf3ccd98b4f9f688dc04e 100644 --- a/addons/report/models/res_company.py +++ b/addons/report/models/res_company.py @@ -29,17 +29,10 @@ class ResCompany(models.Model): @api.model_cr def init(self): - # set a default paperformat based on rml one. for company in self.search([('paperformat_id', '=', False)]): paperformat_euro = self.env.ref('report.paperformat_euro', False) - paperformat_us = self.env.ref('report.paperformat_us', False) - paperformat_id = { - 'a4': paperformat_euro and paperformat_euro.id or False, - 'us_letter': paperformat_us and paperformat_us.id or False, - }.get(company.rml_paper_format) or paperformat_euro - - if paperformat_id: - company.write({'paperformat_id': paperformat_id}) + if paperformat_euro: + company.write({'paperformat_id': paperformat_euro.id}) sup = super(ResCompany, self) if hasattr(sup, 'init'): diff --git a/addons/report/static/src/js/client_action.js b/addons/report/static/src/js/client_action.js index 041a7af383b0c5ef7dc7777b1d3b0434cd2020dc..60265c0a49dce0d74a5a5e8340dff4882d7900f5 100644 --- a/addons/report/static/src/js/client_action.js +++ b/addons/report/static/src/js/client_action.js @@ -180,7 +180,7 @@ var ReportAction = Widget.extend(ControlPanelMixin, { on_click_print: function () { var action = { - 'type': 'ir.actions.report.xml', + 'type': 'ir.actions.report', 'report_type': 'qweb-pdf', 'report_name': this.report_name, 'report_file': this.report_file, diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js index 3f477bf7d16984e70fdbe44d41b4095a595aaf0b..3c8fee859e393ed6d808ffecce013beb04bed420 100644 --- a/addons/report/static/src/js/qwebactionmanager.js +++ b/addons/report/static/src/js/qwebactionmanager.js @@ -44,7 +44,6 @@ var make_report_url = function (action) { var report_urls = { 'qweb-html': '/report/html/' + action.report_name, 'qweb-pdf': '/report/pdf/' + action.report_name, - 'controller': action.report_file, }; // We may have to build a query string with `action.data`. It's the place // were report's using a wizard to customize the output traditionally put @@ -69,7 +68,7 @@ var make_report_url = function (action) { }; ActionManager.include({ - ir_actions_report_xml: function (action, options) { + ir_actions_report: function (action, options) { var self = this; action = _.clone(action); @@ -132,14 +131,6 @@ ActionManager.include({ return self.do_action('report.client_action', client_action_options); } }); - } else if (action.report_type === 'controller') { - framework.blockUI(); - var response = [ - report_urls.controller, - action.report_type, - ]; - var c = crash_manager; - return trigger_download(self.getSession(), response, c, action, options); } else { return self._super(action, options); } diff --git a/addons/report/tests/test_reports.py b/addons/report/tests/test_reports.py index d1e85494f48b726b12ece79bf95ab0140d253c96..9995067fdecda614898ae4493fe73f4252e19ae3 100644 --- a/addons/report/tests/test_reports.py +++ b/addons/report/tests/test_reports.py @@ -14,7 +14,7 @@ _logger = logging.getLogger(__name__) class TestReports(odoo.tests.TransactionCase): def test_reports(self): domain = [('report_type', 'like', 'qweb')] - for report in self.env['ir.actions.report.xml'].search(domain): + for report in self.env['ir.actions.report'].search(domain): report_model = 'report.%s' % report.report_name try: self.env[report_model] diff --git a/addons/report/views/ir_actions_report_views.xml b/addons/report/views/ir_actions_report_views.xml index d0202203b7681f57f9670440e8cfa118f4b413eb..7b52158a0471566531078582323049006c0578f9 100644 --- a/addons/report/views/ir_actions_report_views.xml +++ b/addons/report/views/ir_actions_report_views.xml @@ -2,9 +2,9 @@ <odoo> <!-- Adding a print_report_name field inside to the report action form view --> <record id="act_report_xml_view_inherit_report" model="ir.ui.view"> - <field name="name">ir.actions.report.xml.form.inherit</field> + <field name="name">ir.actions.report.form.inherit</field> <field name="inherit_id" ref="base.act_report_xml_view" /> - <field name="model">ir.actions.report.xml</field> + <field name="model">ir.actions.report</field> <field name="arch" type="xml"> <data> <xpath expr="//field[@name='report_name']" position="after"> @@ -18,7 +18,7 @@ <record id="act_report_xml_view_inherit" model="ir.ui.view"> <field name="name">act_report_xml_view_inherit</field> <field name="inherit_id" ref="base.act_report_xml_view" /> - <field name="model">ir.actions.report.xml</field> + <field name="model">ir.actions.report</field> <field name="arch" type="xml"> <data> <xpath expr="//field[@name='report_type']" position="after"> diff --git a/addons/report/views/report_paperformat_views.xml b/addons/report/views/report_paperformat_views.xml index 433ed8cc3044ae5d2809fd4e6f19d5fd32b7815a..2141f63b91448a106a2116f94be0693a555d2e43 100644 --- a/addons/report/views/report_paperformat_views.xml +++ b/addons/report/views/report_paperformat_views.xml @@ -42,7 +42,7 @@ </record> <record id='reports_action' model='ir.actions.act_window'> <field name="name">Reports</field> - <field name="res_model">ir.actions.report.xml</field> + <field name="res_model">ir.actions.report</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> </record> diff --git a/addons/report/views/templates.xml b/addons/report/views/templates.xml index c9f6f6157b44140626c627c0facb121de58a37eb..667013df74f51ac9abe9d3c3620bdeac8998fe30 100644 --- a/addons/report/views/templates.xml +++ b/addons/report/views/templates.xml @@ -138,7 +138,7 @@ <template id="external_layout_background"> <div class="header o_background_header"> <div class="pull-right"> - <h3 class="mt0 text-right" t-field="company.rml_header1"/> + <h3 class="mt0 text-right" t-field="company.report_header"/> </div> <img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" class="pull-left"/> <div class="pull-left company_address"> @@ -164,7 +164,7 @@ <li t-if="company.website"><i class="fa fa-globe"></i> <span t-field="company.website"/></li> <li t-if="company.vat"><i class="fa fa-building-o"></i> VAT: <span t-field="company.vat"/></li> </ul> - <div t-field="company.rml_footer"/> + <div t-field="company.report_footer"/> <div class="text-muted"> Page: <span class="page"/> @@ -182,7 +182,7 @@ <img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo"/> </div> <div class="col-xs-6 text-right mb4"> - <h4 class="mt0" t-field="company.rml_header1"/> + <h4 class="mt0" t-field="company.report_header"/> <div name="company_address" class="mb4"> <span class="company_address" t-field="company.partner_id" t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'/> @@ -204,7 +204,7 @@ <li t-if="company.website">Web: <span t-field="company.website"/></li> <li t-if="company.vat">VAT: <span t-field="company.vat"/></li> </ul> - <div t-field="company.rml_footer"/> + <div t-field="company.report_footer"/> <div> Page: <span class="page"/> / <span class="topage"/> </div> @@ -238,14 +238,14 @@ <div class="footer o_clean_footer"> <div class="row mt8"> <div class="col-xs-3"> - <span t-field="company.rml_footer"/> + <span t-field="company.report_footer"/> </div> <div class="col-xs-4 text-right"> <span class="company_address" t-field="company.partner_id" t-field-options='{"widget": "contact", "fields": ["address"], "no_marker": true}'/> </div> <div class="col-xs-4"> - <h4 class="mt0 mb0 text-uppercase" t-field="company.rml_header1"/> + <h4 class="mt0 mb0 text-uppercase" t-field="company.report_header"/> </div> <div class="col-xs-1"> <ul class="list-inline pagenumber pull-right text-center"> @@ -262,9 +262,9 @@ <div class="col-xs-3 mb4"> <img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" style="max-height: 45px;"/> </div> - <div class="col-xs-9 text-right" style="margin-top:22px;" t-field="company.rml_header1" name="moto"/> + <div class="col-xs-9 text-right" style="margin-top:22px;" t-field="company.report_header" name="moto"/> </div> - <div t-if="company.logo or company.rml_header1" class="row zero_min_height"> + <div t-if="company.logo or company.report_header" class="row zero_min_height"> <div class="col-xs-12"> <div style="border-bottom: 1px solid black;"></div> </div> @@ -293,7 +293,7 @@ </ul> <div name="financial_infos"> - <span t-field="company.rml_footer"/> + <span t-field="company.report_footer"/> </div> <div class="text-muted"> diff --git a/addons/report_intrastat/report/report_intrastat_report.xml b/addons/report_intrastat/report/report_intrastat_report.xml index 12df16c12c6c6f4604a1cb5bfd8247f12efc583f..126c059b109249f5c5b1f1afd0349ec141f441c4 100644 --- a/addons/report_intrastat/report/report_intrastat_report.xml +++ b/addons/report_intrastat/report/report_intrastat_report.xml @@ -8,7 +8,7 @@ name="report_intrastat.report_intrastatinvoice" file="report_intrastat.report_intrastatinvoice" /> - <record id="account_intrastatinvoices" model="ir.actions.report.xml"> + <record id="account_intrastatinvoices" model="ir.actions.report"> <field name="print_report_name">(object.type == 'out_invoice' and object.state == 'draft' and 'Intrastat Invoice' or object.type == 'out_invoice' and object.state in ('open','paid') and 'Intrastat Invoice'+'-'+(object.number) or object.type == 'out_refund' and object.state == 'draft' and 'Intrastat Credit Note' or diff --git a/addons/report_intrastat/tests/test_report_intrastat.py b/addons/report_intrastat/tests/test_report_intrastat.py index 90d688f922bd084830e22e4a3425fb0e39a982c3..89b03830eb7a78e8cd03c5700bfcd8b3947566f7 100644 --- a/addons/report_intrastat/tests/test_report_intrastat.py +++ b/addons/report_intrastat/tests/test_report_intrastat.py @@ -29,6 +29,6 @@ class RepoortIntrastatTest(common.TransactionCase): }) def test_00_create_pdf(self): - data, report_format = self.env['ir.actions.report.xml'].render_report(self.invoice.ids, 'report_intrastat.report_intrastatinvoice', {}) + data, data_format = self.env.ref('report_intrastat.account_intrastatinvoices').render(self.invoice.ids) if tools.config['test_report_directory']: - open(os.path.join(tools.config['test_report_directory'], 'report_intrastat-intrastat_report.' + report_format), 'wb+').write(data) + open(os.path.join(tools.config['test_report_directory'], 'report_intrastat-intrastat_report.' + data_format), 'wb+').write(data) diff --git a/addons/sale/report/sale_report.xml b/addons/sale/report/sale_report.xml index 3e4996dffe2e05eae12764972e3e0aec1169b981..ff8a1f5f15e4b784398c55a471a1220a307c89f4 100644 --- a/addons/sale/report/sale_report.xml +++ b/addons/sale/report/sale_report.xml @@ -9,7 +9,7 @@ file="sale.report_saleorder" name="sale.report_saleorder" /> - <record id="report_sale_order" model="ir.actions.report.xml"> + <record id="report_sale_order" model="ir.actions.report"> <field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation'+'-'+(object.name) or 'Order'+'-'+(object.name))</field> </record> @@ -21,7 +21,7 @@ name="sale.report_saleproforma" file="sale.report_saleorder" /> - <record id="report_proforma_quotation" model="ir.actions.report.xml"> + <record id="report_proforma_quotation" model="ir.actions.report"> <field name="print_report_name">'Pro-Forma'+'-'+(object.name)</field> </record> </data> diff --git a/addons/stock/report/stock_report_views.xml b/addons/stock/report/stock_report_views.xml index cb9af6d109e35220efd1972cea6bd7036f6a62a2..84988bfdb6adedc5a2bdd0a5118bfdcbed85cc5d 100644 --- a/addons/stock/report/stock_report_views.xml +++ b/addons/stock/report/stock_report_views.xml @@ -9,7 +9,7 @@ name="stock.report_picking" file="stock.report_picking_operations" /> - <record id="action_report_picking" model="ir.actions.report.xml"> + <record id="action_report_picking" model="ir.actions.report"> <field name="print_report_name">'Picking Operations'+'-'+(object.partner_id.name or '')+'-'+(object.name)</field> </record> <report @@ -20,7 +20,7 @@ name="stock.report_deliveryslip" file="stock.report_deliveryslip" /> - <record id="action_report_delivery" model="ir.actions.report.xml"> + <record id="action_report_delivery" model="ir.actions.report"> <field name="print_report_name">'Delivery Slip'+'-'+(object.partner_id.name or '')+'-'+(object.name)</field> </record> <report @@ -31,17 +31,17 @@ name="stock.report_inventory" file="stock.report_inventory" /> - <record id="action_report_inventory" model="ir.actions.report.xml"> + <record id="action_report_inventory" model="ir.actions.report"> <field name="print_report_name">'Inventory'+'-'+(object.name)</field> </record> <report id="action_report_quant_package_barcode" model="stock.quant.package" report_type="qweb-pdf" name="stock.report_package_barcode" string="Package BarCode with Contents" file="stock.report_package_barcode"/> <report id="action_report_quant_package_barcode_small" model="stock.quant.package" report_type="qweb-pdf" name="stock.report_package_barcode_small" string="Package BarCode" file="stock.report_package_barcode"/> <report id="action_report_location_barcode" model="stock.location" report_type="qweb-pdf" name="stock.report_location_barcode" string="Location BarCode" file="stock.report_location_barcode"/> - <record id="action_report_location_barcode" model="ir.actions.report.xml"> + <record id="action_report_location_barcode" model="ir.actions.report"> <field name="print_report_name">(object.name)+'-'+'Location'</field> </record> <report id="action_report_lot_barcode" model="stock.production.lot" report_type="qweb-pdf" name="stock.report_lot_barcode" string="Lot BarCode" file="stock.report_lot_barcode"/> - <record id="action_report_lot_barcode" model="ir.actions.report.xml"> + <record id="action_report_lot_barcode" model="ir.actions.report"> <field name="print_report_name">'Lot/Serial'+'-'+(object.name)</field> </record> </data> diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 700f19d816879a717753fd387a71f55c5b0815ba..1a86ca97823521219761572e57bbce72ab2c2699 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1157,7 +1157,7 @@ class Action(http.Controller): if base_action: ctx = dict(request.context) action_type = base_action[0]['type'] - if action_type == 'ir.actions.report.xml': + if action_type == 'ir.actions.report': ctx.update({'bin_size': True}) if additional_context: ctx.update(additional_context) @@ -1498,7 +1498,7 @@ class Reports(http.Controller): report_struct['format'], 'octet-stream') file_name = action.get('name', 'report') if 'name' not in action: - reports = request.env['ir.actions.report.xml'] + reports = request.env['ir.actions.report'] reports = reports.search([('report_name', '=', action['report_name'])]) if reports: file_name = reports[0].name diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 934473109a3bff4450e0028505ef3cec9e4338c9..b02376ef6b71c4690a4fbec55fafd2f4a20f1175 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -861,7 +861,7 @@ var ActionManager = Widget.extend({ return self.do_action(action, options); }); }, - ir_actions_report_xml: function(action, options) { + ir_actions_report: function(action, options) { var self = this; framework.blockUI(); action = _.clone(action); diff --git a/addons/website_quote/report/sale_order_reports.xml b/addons/website_quote/report/sale_order_reports.xml index 1aeaf7c6e0e66fc015220df45744b57fd112bac6..c3c8078545f4b102c4d259062d311121e0bb87f2 100644 --- a/addons/website_quote/report/sale_order_reports.xml +++ b/addons/website_quote/report/sale_order_reports.xml @@ -9,7 +9,7 @@ name="website_quote.report_quote" menu="False" /> - <record id="report_web_quote" model="ir.actions.report.xml"> + <record id="report_web_quote" model="ir.actions.report"> <field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation'+'-'+(object.name) or 'Order'+'-'+(object.name))</field> </record> </odoo> diff --git a/doc/api_integration.rst b/doc/api_integration.rst index 1345915d7979a83162fb621d2aa5dbd60fbfde14..612e91ed85422cd43cf952266c2c9abea9c3627e 100644 --- a/doc/api_integration.rst +++ b/doc/api_integration.rst @@ -1298,7 +1298,7 @@ activated as actual fields on the model. Report printing --------------- -Available reports can be listed by searching the ``ir.actions.report.xml`` +Available reports can be listed by searching the ``ir.actions.report`` model, fields of interest being ``model`` diff --git a/doc/howtos/backend.rst b/doc/howtos/backend.rst index 4d48a6d7de32d778c6cc7671d1dc925a589a7752..bffebe587bbf40382454734ac3963f7e60c644c1 100644 --- a/doc/howtos/backend.rst +++ b/doc/howtos/backend.rst @@ -1502,7 +1502,7 @@ Odoo 8.0 comes with a new report engine based on :ref:`reference/qweb`, A report is a combination two elements: -* an ``ir.actions.report.xml``, for which a ``<report>`` shortcut element is +* an ``ir.actions.report``, for which a ``<report>`` shortcut element is provided, it sets up various basic parameters for the report (default type, whether the report should be saved to the database after generation,…) diff --git a/doc/reference/actions.rst b/doc/reference/actions.rst index fb9d3255cdbe1f038e0dfcc4be282982817b25c2..904cb89db135dd1537f4f74f31bcb469018f3c42 100644 --- a/doc/reference/actions.rst +++ b/doc/reference/actions.rst @@ -358,8 +358,8 @@ server actions: .. _reference/actions/report: -Report Actions (``ir.actions.report.xml``) -========================================== +Report Actions (``ir.actions.report``) +====================================== Triggers the printing of a report diff --git a/doc/reference/data.rst b/doc/reference/data.rst index c8ebf864a880a5fa72bf6b0c7fb97d24a325a336..fd597fcb4a51a5144067706a6436bd5e0d295e26 100644 --- a/doc/reference/data.rst +++ b/doc/reference/data.rst @@ -218,10 +218,10 @@ section of the view, and allowing a few *optional* attributes: ``report`` ---------- -Creates a ``ir.actions.report.xml`` record with a few default values. +Creates a ``ir.actions.report`` record with a few default values. Mostly just proxies attributes to the corresponding fields on -``ir.actions.report.xml``, but also automatically creates the item in the +``ir.actions.report``, but also automatically creates the item in the :guilabel:`More` menu of the report's ``model``. .. ignored url, act_window and ir_set diff --git a/odoo/__init__.py b/odoo/__init__.py index 4c074c9beb366920ab769019b0dd592dba1e671e..3c3b14fdadc6deee544f202d6162259af9827224 100644 --- a/odoo/__init__.py +++ b/odoo/__init__.py @@ -61,7 +61,6 @@ from . import modules from . import netsvc from . import osv from . import release -from . import report from . import service from . import sql_db from . import tools diff --git a/odoo/addons/base/base_data.xml b/odoo/addons/base/base_data.xml index 322cd3ae3244af72e4f46df60fd38b1de93140c3..7c90d0b96e99b90510ea02de8357b26203014069 100644 --- a/odoo/addons/base/base_data.xml +++ b/odoo/addons/base/base_data.xml @@ -67,26 +67,6 @@ Administrator</span>]]></field> <field eval="10" name="sequence"/> </record> - <!-- Basic fonts family included in PDF standart, will always be in the font list --> - <record model="res.font" id="base.font_helvetica"> - <field name="name">Helvetica</field> - <field name="family">Helvetica</field> - <field name="path">/dev/null</field> - <field name="mode">all</field> - </record> - <record model="res.font" id="base.font_times"> - <field name="name">Times</field> - <field name="family">Times</field> - <field name="path">/dev/null</field> - <field name="mode">all</field> - </record> - <record model="res.font" id="base.font_courier"> - <field name="name">Courier</field> - <field name="family">Courier</field> - <field name="path">/dev/null</field> - <field name="mode">all</field> - </record> - <record id="public_partner" model="res.partner"> <field name="name">Public user</field> <field name="active" eval="False"/> diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index b174b1c45e2c2725e076e606419b61c790024b82..959b9b3f740176b019b8680da5368d4d7842aa23 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -11,7 +11,6 @@ from pytz import timezone import odoo from odoo import api, fields, models, tools, _ from odoo.exceptions import MissingError, UserError, ValidationError -from odoo.report.report_sxw import report_sxw, report_rml from odoo.tools.safe_eval import safe_eval, test_python_expr _logger = logging.getLogger(__name__) @@ -72,24 +71,18 @@ class IrActions(models.Model): } -class IrActionsReportXml(models.Model): - _name = 'ir.actions.report.xml' +class IrActionsReport(models.Model): + _name = 'ir.actions.report' _inherit = 'ir.actions.actions' _table = 'ir_act_report_xml' _sequence = 'ir_actions_id_seq' _order = 'name' name = fields.Char(translate=True) - type = fields.Char(default='ir.actions.report.xml') + type = fields.Char(default='ir.actions.report') model = fields.Char(required=True) - report_type = fields.Selection([('qweb-pdf', 'PDF'), - ('qweb-html', 'HTML'), - ('controller', 'Controller'), - ('pdf', 'RML pdf (deprecated)'), - ('sxw', 'RML sxw (deprecated)'), - ('webkit', 'Webkit (deprecated)')], - required=True, default="pdf", + report_type = fields.Selection([('qweb-pdf', 'PDF'), ('qweb-html', 'HTML')], required=True, default="pdf", help="HTML will open the report directly in your browser, PDF will use wkhtmltopdf to render the HTML into a PDF file and let you download it, Controller allows you to define the url of a custom controller outputting any kind of report.") report_name = fields.Char(string='Template Name', required=True, help="For QWeb reports, name of the template used in the rendering. The method 'render_html' of the model 'report.template_name' will be called (if any) to give the html. For RML reports, this is the LocalService name.") @@ -108,88 +101,9 @@ class IrActionsReportXml(models.Model): parser = fields.Char(string='Parser Class') auto = fields.Boolean(string='Custom Python Parser', default=True) - report_xsl = fields.Char(string='XSL Path') - report_xml = fields.Char(string='XML Path') - - report_rml = fields.Char(string='Main Report File Path/controller', help="The path to the main report file/controller (depending on Report Type) or empty if the content is in another data field") - report_file = fields.Char(related='report_rml', string='Report File', required=False, readonly=False, store=True, + report_file = fields.Char(string='Report File', required=False, readonly=False, store=True, help="The path to the main report file (depending on Report Type) or empty if the content is in another field") - report_sxw = fields.Char(compute='_compute_report_sxw', string='SXW Path') - report_sxw_content_data = fields.Binary(string='SXW Content') - report_rml_content_data = fields.Binary(string='RML Content') - report_sxw_content = fields.Binary(compute='_compute_report_sxw_content', inverse='_inverse_report_sxw_content', string='SXW Content') - report_rml_content = fields.Binary(compute='_compute_report_rml_content', inverse='_inverse_report_rml_content', string='RML Content') - - @api.depends('report_rml') - def _compute_report_sxw(self): - for report in self: - if report.report_rml: - self.report_sxw = report.report_rml.replace('.rml', '.sxw') - - def _report_content(self, name): - data = self[name + '_content_data'] - if not data and self[name]: - try: - with tools.file_open(self[name], mode='rb') as fp: - data = fp.read() - except Exception: - data = False - return data - - @api.depends('report_sxw', 'report_sxw_content_data') - def _compute_report_sxw_content(self): - for report in self: - report.report_sxw_content = report._report_content('report_sxw') - - @api.depends('report_rml', 'report_rml_content_data') - def _compute_report_rml_content(self): - for report in self: - report.report_rml_content = report._report_content('report_rml') - - def _inverse_report_sxw_content(self): - for report in self: - report.report_sxw_content_data = report.report_sxw_content - - def _inverse_report_rml_content(self): - for report in self: - report.report_rml_content_data = report.report_rml_content - - @api.model_cr - def _lookup_report(self, name): - """ - Look up a report definition. - """ - join = os.path.join - - # First lookup in the deprecated place, because if the report definition - # has not been updated, it is more likely the correct definition is there. - # Only reports with custom parser sepcified in Python are still there. - if 'report.' + name in odoo.report.interface.report_int._reports: - return odoo.report.interface.report_int._reports['report.' + name] - - self._cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s", (name,)) - row = self._cr.dictfetchone() - if not row: - raise Exception("Required report does not exist: %s" % name) - - if row['report_type'] in ('qweb-pdf', 'qweb-html'): - return row['report_name'] - elif row['report_rml'] or row['report_rml_content_data']: - kwargs = {} - if row['parser']: - kwargs['parser'] = getattr(odoo.addons, row['parser']) - return report_sxw('report.'+row['report_name'], row['model'], - join('addons', row['report_rml'] or '/'), - header=row['header'], register=False, **kwargs) - elif row['report_xsl'] and row['report_xml']: - return report_rml('report.'+row['report_name'], row['model'], - join('addons', row['report_xml']), - row['report_xsl'] and join('addons', row['report_xsl']), - register=False) - else: - raise Exception("Unhandled report type: %s" % row) - @api.multi def create_action(self): """ Create a contextual action for each report. """ @@ -198,7 +112,7 @@ class IrActionsReportXml(models.Model): 'name': report.name, 'model': report.model, 'key2': 'client_print_multi', - 'value': "ir.actions.report.xml,%s" % report.id, + 'value': "ir.actions.report,%s" % report.id, }) report.write({'ir_values_id': ir_values.id}) return True @@ -216,21 +130,26 @@ class IrActionsReportXml(models.Model): return True @api.model - def render_report(self, res_ids, name, data): - """ - Look up a report definition and render the report for the provided IDs. - """ - report = self._lookup_report(name) - if isinstance(report, basestring): # Qweb report - # The only case where a QWeb report is rendered with this method occurs when running - # yml tests originally written for RML reports. - if tools.config['test_enable'] and not tools.config['test_report_directory']: - # Only generate the pdf when a destination folder has been provided. - return self.env['report'].get_html(res_ids, report, data=data), 'html' - else: - return self.env['report'].get_pdf(res_ids, report, data=data), 'pdf' - else: - return report.create(self._cr, self._uid, res_ids, data, context=self._context) + def render_html(self, res_ids, data=None): + return self.env['report'].get_html(res_ids, self.report_name, data=data), 'html' + + @api.multi + def render_pdf(self, res_ids, data=None): + # In case of test environment without enough workers to perform calls to wkhtmltopdf, + # fallback to render_html. + if tools.config['test_enable'] and not tools.config['test_report_directory']: + return self.render_html(res_ids, data=data) + return self.env['report'].get_pdf(res_ids, self.report_name, data=data), 'pdf' + + @api.multi + def render(self, res_ids, data=None): + report_type = self.report_type.lower() + if report_type.startswith('qweb-'): + report_type = report_type[5:] + render_func = getattr(self, 'render_' + report_type, None) + if not render_func: + return None + return render_func(res_ids, data=data) class IrActionsActWindow(models.Model): diff --git a/odoo/addons/base/ir/ir_actions.xml b/odoo/addons/base/ir/ir_actions.xml index 750ee14efdb29ac4d66874140b817829e67e11c9..1d26521bb56e4da39edd88697cc23a532ad82072 100644 --- a/odoo/addons/base/ir/ir_actions.xml +++ b/odoo/addons/base/ir/ir_actions.xml @@ -45,11 +45,11 @@ <menuitem id="next_id_6" name="Actions" parent="base.menu_custom" sequence="5"/> <menuitem action="ir_sequence_actions" id="menu_ir_sequence_actions" parent="next_id_6"/> - <!-- ir.actions.report.xml --> + <!-- ir.actions.report --> <record id="act_report_xml_view" model="ir.ui.view"> - <field name="name">ir.actions.report.xml</field> - <field name="model">ir.actions.report.xml</field> + <field name="name">ir.actions.report</field> + <field name="model">ir.actions.report</field> <field name="arch" type="xml"> <form string="Report"> <field name="ir_values_id" invisible="1"/> @@ -69,33 +69,18 @@ </group> <group> <field name="model"/> - <field name="report_name" attrs="{'invisible':[('report_type','=', 'controller')]}"/> - <field name="report_rml" attrs="{'invisible':[('report_type','!=', 'controller')]}"/> + <field name="report_name"/> </group> </group> <notebook> <page name="security" string="Security"> <field name="groups_id"/> </page> - <page name='rml' string="RML Configuration" attrs="{'invisible':[('report_type','not in',['pdf','sxw'])]}"> - <group> - <group string="RML Report"> - <field name="header"/> - <field name="report_file"/> - <field name="auto"/> - <field name="parser"/> - </group> - <group string="XML Report"> - <field name="report_xsl"/> - <field name="report_xml"/> - </group> - </group> - </page> <page name='advanced' string="Advanced Properties"> <group> <field name="multi"/> - <field name="attachment_use" attrs="{'invisible':[('report_type','=', 'controller')]}"/> - <field name="attachment" attrs="{'invisible':[('report_type','=', 'controller')]}"/> + <field name="attachment_use"/> + <field name="attachment"/> </group> </page> </notebook> @@ -104,8 +89,8 @@ </field> </record> <record id="act_report_xml_view_tree" model="ir.ui.view"> - <field name="name">ir.actions.report.xml.tree</field> - <field name="model">ir.actions.report.xml</field> + <field name="name">ir.actions.report.tree</field> + <field name="model">ir.actions.report</field> <field name="arch" type="xml"> <tree string="Report xml"> <field name="name"/> @@ -118,8 +103,8 @@ </field> </record> <record id="act_report_xml_search_view" model="ir.ui.view"> - <field name="name">ir.actions.report.xml.search</field> - <field name="model">ir.actions.report.xml</field> + <field name="name">ir.actions.report.search</field> + <field name="model">ir.actions.report</field> <field name="arch" type="xml"> <search string="Report Xml"> <field name="name" @@ -133,15 +118,15 @@ </search> </field> </record> - <record id="ir_action_report_xml" model="ir.actions.act_window"> + <record id="ir_action_report" model="ir.actions.act_window"> <field name="name">Reports</field> <field name="type">ir.actions.act_window</field> - <field name="res_model">ir.actions.report.xml</field> + <field name="res_model">ir.actions.report</field> <field name="view_type">form</field> <field name="view_id" ref="act_report_xml_view_tree"/> <field name="search_view_id" ref="act_report_xml_search_view"/> </record> - <menuitem action="ir_action_report_xml" id="menu_ir_action_report_xml" parent="base.next_id_6"/> + <menuitem action="ir_action_report" id="menu_ir_action_report" parent="base.next_id_6"/> <!-- ir.actions.act_window --> diff --git a/odoo/addons/base/ir/ir_ui_menu.py b/odoo/addons/base/ir/ir_ui_menu.py index c8a393228350f4d70eac14db23dd63fbd7fc5408..b4bebf0e9a89492f0aeb0722323926a66cab623d 100644 --- a/odoo/addons/base/ir/ir_ui_menu.py +++ b/odoo/addons/base/ir/ir_ui_menu.py @@ -37,7 +37,7 @@ class IrUiMenu(models.Model): "If this field is empty, Odoo will compute visibility based on the related object's read access.") complete_name = fields.Char(compute='_compute_complete_name', string='Full Path') web_icon = fields.Char(string='Web Icon File') - action = fields.Reference(selection=[('ir.actions.report.xml', 'ir.actions.report.xml'), + action = fields.Reference(selection=[('ir.actions.report', 'ir.actions.report'), ('ir.actions.act_window', 'ir.actions.act_window'), ('ir.actions.act_url', 'ir.actions.act_url'), ('ir.actions.server', 'ir.actions.server'), @@ -99,7 +99,7 @@ class IrUiMenu(models.Model): access = self.env['ir.model.access'] MODEL_GETTER = { 'ir.actions.act_window': lambda action: action.res_model, - 'ir.actions.report.xml': lambda action: action.model, + 'ir.actions.report': lambda action: action.model, 'ir.actions.server': lambda action: action.model_id.model, } for menu in action_menus: diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py index ccd971e26e401ea67f240ef1b2fc54899bcee6e9..3672d7811c21e4c914c38954bff28486ed53f66b 100644 --- a/odoo/addons/base/ir/ir_values.py +++ b/odoo/addons/base/ir/ir_values.py @@ -10,11 +10,6 @@ from odoo.tools import pickle import logging _logger = logging.getLogger(__name__) - -EXCLUDED_FIELDS = set(('code', - 'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', - 'report_sxw_content_data', 'report_rml_content_data', 'search_view', )) - #: Possible slots to bind an action to with :meth:`~.set_action` ACTION_SLOTS = [ "client_action_multi", # sidebar wizard action @@ -419,14 +414,10 @@ class IrValues(models.Model): # process values and their action results = {} for id, name, action in actions: - fields = [field for field in action._fields if field not in EXCLUDED_FIELDS] # FIXME: needs cleanup try: - action_def = { - field: action._fields[field].convert_to_read(action[field], action) - for field in fields - } - if action._name in ('ir.actions.report.xml', 'ir.actions.act_window'): + action_def = dict([(k, v.convert_to_read(action[k], action)) for k, v in action._fields.items()]) + if action._name in ('ir.actions.report', 'ir.actions.act_window'): if action.groups_id and not action.groups_id & self.env.user.groups_id: if name == 'Menuitem': raise AccessError(_('You do not have the permission to perform this operation!!!')) diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py index f95c2b07dc1fac01935f104933383d0eeb85465b..ae36ccd17277716ed906b266fe1df620d3957150 100644 --- a/odoo/addons/base/module/module.py +++ b/odoo/addons/base/module/module.py @@ -184,7 +184,7 @@ class Module(models.Model): @api.depends('name', 'state') def _get_views(self): IrModelData = self.env['ir.model.data'].with_context(active_test=True) - dmodels = ['ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu'] + dmodels = ['ir.ui.view', 'ir.actions.report', 'ir.ui.menu'] for module in self: # Skip uninstalled modules below, no data to find anyway. @@ -210,7 +210,7 @@ class Module(models.Model): return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type) module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view')))) - module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report.xml')))) + module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report')))) module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu')))) @api.depends('icon') diff --git a/odoo/addons/base/report/__init__.py b/odoo/addons/base/report/__init__.py deleted file mode 100644 index 6ce403b6633706a4dd273b3d18701b3102b693d5..0000000000000000000000000000000000000000 --- a/odoo/addons/base/report/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import preview_report diff --git a/odoo/addons/base/report/corporate_defaults.xml b/odoo/addons/base/report/corporate_defaults.xml index 751693e7e8931e923aa188b56e499be41a9603b7..3e99caf9d48d4c899bfbfb40d99f56ea7d702827 100644 --- a/odoo/addons/base/report/corporate_defaults.xml +++ b/odoo/addons/base/report/corporate_defaults.xml @@ -1,8 +1,8 @@ <?xml version="1.0"?> <corporate-header> <corporation type="zoom" name="company_id"> - <rml_header1 type="field" name="rml_header1"/> - <rml_footer type="field" name="rml_footer"/> + <report_header type="field" name="report_header"/> + <report_footer type="field" name="report_footer"/> <title type="field" name="partner_id.title"/> <name type="field" name="partner_id.name"/> <street type="field" name="street"/> diff --git a/odoo/addons/base/report/corporate_sxw_header.xml b/odoo/addons/base/report/corporate_sxw_header.xml index 6cd8e5a218fbaf5971560a5bc8b90a3b4e230a09..2705f8703725892d85b05ba168036a7a21f68543 100644 --- a/odoo/addons/base/report/corporate_sxw_header.xml +++ b/odoo/addons/base/report/corporate_sxw_header.xml @@ -200,7 +200,7 @@ <text:p text:style-name="P1">[[ company.partner_id.name ]]</text:p> </table:table-cell> <table:table-cell table:style-name="Table2.A1" table:value-type="string"> - <text:p text:style-name="P2">[[ company.rml_header1 ]]</text:p> + <text:p text:style-name="P2">[[ company.report_header ]]</text:p> </table:table-cell> </table:table-row> </table:table> @@ -233,7 +233,7 @@ <table:table-column table:style-name="Table1.A"/> <table:table-row> <table:table-cell table:style-name="Table1.A1" table:value-type="string"> - <text:p text:style-name="P7">[[ company.rml_footer ]]</text:p> + <text:p text:style-name="P7">[[ company.report_footer ]]</text:p> <text:p text:style-name="P7">Contact : [[ user.name ]]</text:p> </table:table-cell> </table:table-row> diff --git a/odoo/addons/base/report/preview_report.py b/odoo/addons/base/report/preview_report.py deleted file mode 100644 index 68f380984210472163fdb3a85759a003d648e3b9..0000000000000000000000000000000000000000 --- a/odoo/addons/base/report/preview_report.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo.report import report_sxw - -class rmlparser(report_sxw.rml_parse): - def set_context(self, objects, data, ids, report_type = None): - super(rmlparser,self).set_context(objects, data, ids, report_type) - self.setCompany(objects[0]) - -report_sxw.report_sxw('report.preview.report', 'res.company', - 'addons/base/report/preview_report.rml', parser=rmlparser, header='external') diff --git a/odoo/addons/base/report/preview_report.rml b/odoo/addons/base/report/preview_report.rml deleted file mode 100644 index f3a54527a13b1df20acb54c85e57cc0c651ea8c7..0000000000000000000000000000000000000000 --- a/odoo/addons/base/report/preview_report.rml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0"?> -<document filename="preview_report.pdf"> - <template title="Preview Report" author="Odoo (sales@odoo.com)" allowSplitting="20"> - <pageTemplate id="first"> - <frame id="first" x1="57.0" y1="57.0" width="481" height="728"/> - </pageTemplate> - </template> - <story> - <para> - </para> - </story> -</document> diff --git a/odoo/addons/base/res/__init__.py b/odoo/addons/base/res/__init__.py index ee904734c09e2bb29e6f909f3c98e49d57c73529..1e10e5ee9cd30f0472de880155eeabd8fb6b4f49 100644 --- a/odoo/addons/base/res/__init__.py +++ b/odoo/addons/base/res/__init__.py @@ -7,7 +7,6 @@ from . import res_partner from . import res_bank from . import res_config from . import res_currency -from . import res_font from . import res_company from . import res_users from . import res_request diff --git a/odoo/addons/base/res/res_company.py b/odoo/addons/base/res/res_company.py index 18fbd6a0a3ea04edea9ffa23960f6b396367852a..b02255db4bdd7a99a283b61191fc315e8148918c 100644 --- a/odoo/addons/base/res/res_company.py +++ b/odoo/addons/base/res/res_company.py @@ -13,98 +13,6 @@ class Company(models.Model): _description = 'Companies' _order = 'sequence, name' - _header = """ -<header> -<pageTemplate> - <frame id="first" x1="28.0" y1="28.0" width="%s" height="%s"/> - <stylesheet> - <!-- Set here the default font to use for all <para> tags --> - <paraStyle name='Normal' fontName="DejaVuSans"/> - </stylesheet> - <pageGraphics> - <fill color="black"/> - <stroke color="black"/> - <setFont name="DejaVuSans" size="8"/> - <drawString x="%s" y="%s"> [[ formatLang(time.strftime("%%Y-%%m-%%d"), date=True) ]] [[ time.strftime("%%H:%%M") ]]</drawString> - <setFont name="DejaVuSans-Bold" size="10"/> - <drawCentredString x="%s" y="%s">[[ company.partner_id.name ]]</drawCentredString> - <stroke color="#000000"/> - <lines>%s</lines> - <!-- Set here the default font to use for all <drawString> tags --> - <!-- don't forget to change the 2 other occurence of <setFont> above if needed --> - <setFont name="DejaVuSans" size="8"/> - </pageGraphics> -</pageTemplate> -</header>""" - - _header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm", "1.0cm 28.1cm 20.1cm 28.1cm") - _header3 = _header % (786, 525, 25, 555, 440, 555, "25 550 818 550") - - _header_main = """ -<header> - <pageTemplate> - <frame id="first" x1="1.3cm" y1="3.0cm" height="%s" width="19.0cm"/> - <stylesheet> - <!-- Set here the default font to use for all <para> tags --> - <paraStyle name='Normal' fontName="DejaVuSans"/> - <paraStyle name="main_footer" fontSize="8.0" alignment="CENTER"/> - <paraStyle name="main_header" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/> - </stylesheet> - <pageGraphics> - <!-- Set here the default font to use for all <drawString> tags --> - <setFont name="DejaVuSans" size="8"/> - <!-- You Logo - Change X,Y,Width and Height --> - <image x="1.3cm" y="%s" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image> - <fill color="black"/> - <stroke color="black"/> - - <!-- page header --> - <lines>1.3cm %s 20cm %s</lines> - <drawRightString x="20cm" y="%s">[[ company.rml_header1 ]]</drawRightString> - <drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString> - <place x="1.3cm" y="%s" height="1.8cm" width="15.0cm"> - <para style="main_header">[[ display_address(company.partner_id) or '' ]]</para> - </place> - <drawString x="1.3cm" y="%s">Phone:</drawString> - <drawRightString x="7cm" y="%s">[[ company.partner_id.phone or '' ]]</drawRightString> - <drawString x="1.3cm" y="%s">Mail:</drawString> - <drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString> - <lines>1.3cm %s 7cm %s</lines> - - <!-- left margin --> - <rotate degrees="90"/> - <fill color="grey"/> - <drawString x="2.65cm" y="-0.4cm">generated by Odoo.com</drawString> - <fill color="black"/> - <rotate degrees="-90"/> - - <!--page bottom--> - <lines>1.2cm 2.65cm 19.9cm 2.65cm</lines> - <place x="1.3cm" y="0cm" height="2.55cm" width="19.0cm"> - <para style="main_footer">[[ company.rml_footer ]]</para> - <para style="main_footer">Contact : [[ user.name ]] - Page: <pageNumber/></para> - </place> - </pageGraphics> - </pageTemplate> -</header>""" - - _header_a4 = _header_main % ('21.7cm', '27.7cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm', '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm') - _header_letter = _header_main % ('20cm', '26.0cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm', '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm') - - def _get_header(self): - try: - header_file = tools.file_open(os.path.join( - 'base', 'report', 'corporate_rml_header.rml')) - try: - return header_file.read() - finally: - header_file.close() - except: - return self._header_a4 - - def _get_font(self): - return self.env['res.font'].search([('family', '=', 'Helvetica'), ('mode', '=', 'all')], limit=1) - def _get_logo(self): return open(os.path.join(tools.config['root_path'], 'addons', 'base', 'res', 'res_company_logo.png'), 'rb') .read().encode('base64') @@ -121,14 +29,8 @@ class Company(models.Model): parent_id = fields.Many2one('res.company', string='Parent Company', index=True) child_ids = fields.One2many('res.company', 'parent_id', string='Child Companies') partner_id = fields.Many2one('res.partner', string='Partner', required=True) - rml_header = fields.Text(required=True, default=_get_header) - rml_header1 = fields.Text(string='Company Tagline', help="Appears by default on the top right corner of your printed documents (report header).") - rml_header2 = fields.Text(string='RML Internal Header', required=True, default=_header2) - rml_header3 = fields.Text(string='RML Internal Header for Landscape Reports', required=True, default=_header3) - rml_footer = fields.Text(string='Report Footer', translate=True, help="Footer text displayed at the bottom of all reports.") - font = fields.Many2one('res.font', string="Font", default=lambda self: self._get_font(), - domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))], - help="Set the font into the report header, it will be used as default font in the RML reports of the user company") + report_header = fields.Text(string='Company Tagline', help="Appears by default on the top right corner of your printed documents (report header).") + report_footer = fields.Text(string='Report Footer', translate=True, help="Footer text displayed at the bottom of all reports.") logo = fields.Binary(related='partner_id.image', default=_get_logo, string="Company Logo") # logo_web: do not store in attachments, since the image is retrieved in SQL for # performance reasons (see addons/web/controllers/main.py, Binary.company_logo) @@ -149,7 +51,6 @@ class Company(models.Model): website = fields.Char(related='partner_id.website') vat = fields.Char(related='partner_id.vat', string="TIN") company_registry = fields.Char() - rml_paper_format = fields.Selection([('a4', 'A4'), ('us_letter', 'US Letter')], string="Paper Format", required=True, default='a4', oldname='paper_format') sequence = fields.Integer(help='Used to order Companies in the company switcher', default=10) _sql_constraints = [ @@ -213,20 +114,6 @@ class Company(models.Model): def _onchange_state(self): self.country_id = self.state_id.country_id - @api.onchange('font') - def _onchange_font_name(self): - """ To change default header style of all <para> and drawstring. """ - def _change_header(header, font): - """ Replace default fontname use in header and setfont tag """ - default_para = re.sub(r'fontName.?=.?".*"', 'fontName="%s"' % font, header) - return re.sub(r'(<setFont.?name.?=.?)(".*?")(.)', '\g<1>"%s"\g<3>' % font, default_para) - - if self.font: - fontname = self.font.name - self.rml_header = _change_header(self.rml_header, fontname) - self.rml_header2 = _change_header(self.rml_header2, fontname) - self.rml_header3 = _change_header(self.rml_header3, fontname) - @api.multi def on_change_country(self, country_id): # This function is called from account/models/chart_template.py, hence decorated with `multi`. @@ -324,18 +211,6 @@ class Company(models.Model): self.clear_caches() return super(Company, self).write(values) - @api.onchange('rml_paper_format') - def _onchange_rml_paper_format(self): - if self.rml_paper_format == 'us_letter': - self.rml_header = self._header_letter - else: - self.rml_header = self._header_a4 - - @api.multi - def act_discover_fonts(self): - self.ensure_one() - return self.env["res.font"].font_scan() - @api.constrains('parent_id') def _check_parent_id(self): if not self._check_recursion(): diff --git a/odoo/addons/base/res/res_company_view.xml b/odoo/addons/base/res/res_company_view.xml index 4746deeb1b105f0a1455ab03b786991da36e1b78..3a7168dc3f8e145e5d60673698d6f1cbe4d27715 100644 --- a/odoo/addons/base/res/res_company_view.xml +++ b/odoo/addons/base/res/res_company_view.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <odoo> <data> - <report id="preview_rml_report" model="res.config.settings" name="preview.report" menu="False" rml="base/report/preview_report.rml" string="Preview RML Report"/> - <record id="view_company_form" model="ir.ui.view"> <field name="name">res.company.form</field> <field name="model">res.company</field> @@ -31,8 +29,8 @@ <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True}'/> </div> <field name="website" widget="url" placeholder="e.g. www.odoo.com"/> - <field name="rml_footer" placeholder="e.g. Your Bank Accounts, one per line"/> - <field name="rml_header1" placeholder="e.g. Global Business Solutions"/> + <field name="report_footer" placeholder="e.g. Your Bank Accounts, one per line"/> + <field name="report_header" placeholder="e.g. Global Business Solutions"/> </group> <group> <field name="phone"/> diff --git a/odoo/addons/base/res/res_font.py b/odoo/addons/base/res/res_font.py deleted file mode 100644 index ab3ad251c6c8aefa06dac0ba04943b35c229e7cd..0000000000000000000000000000000000000000 --- a/odoo/addons/base/res/res_font.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging -from reportlab.pdfbase import ttfonts - -from odoo import api, fields, models -from odoo.report.render.rml2pdf import customfonts - -"""This module allows the mapping of some system-available TTF fonts to -the reportlab engine. - -This file could be customized per distro (although most Linux/Unix ones) -should have the same filenames, only need the code below). - -Due to an awful configuration that ships with reportlab at many Linux -and Ubuntu distros, we have to override the search path, too. -""" -_logger = logging.getLogger(__name__) - -# Alternatives for the [broken] builtin PDF fonts. Default order chosen to match -# the pre-v8 mapping from odoo.report.render.rml2pdf.customfonts.CustomTTFonts. -# Format: [ (BuiltinFontFamily, mode, [AlternativeFontName, ...]), ...] -BUILTIN_ALTERNATIVES = [ - ('Helvetica', "normal", ["DejaVuSans", "LiberationSans"]), - ('Helvetica', "bold", ["DejaVuSans-Bold", "LiberationSans-Bold"]), - ('Helvetica', 'italic', ["DejaVuSans-Oblique", "LiberationSans-Italic"]), - ('Helvetica', 'bolditalic', ["DejaVuSans-BoldOblique", "LiberationSans-BoldItalic"]), - ('Times', 'normal', ["LiberationSerif", "DejaVuSerif"]), - ('Times', 'bold', ["LiberationSerif-Bold", "DejaVuSerif-Bold"]), - ('Times', 'italic', ["LiberationSerif-Italic", "DejaVuSerif-Italic"]), - ('Times', 'bolditalic', ["LiberationSerif-BoldItalic", "DejaVuSerif-BoldItalic"]), - ('Courier', 'normal', ["FreeMono", "DejaVuSansMono"]), - ('Courier', 'bold', ["FreeMonoBold", "DejaVuSansMono-Bold"]), - ('Courier', 'italic', ["FreeMonoOblique", "DejaVuSansMono-Oblique"]), - ('Courier', 'bolditalic', ["FreeMonoBoldOblique", "DejaVuSansMono-BoldOblique"]), -] - - -class ResFont(models.Model): - _name = "res.font" - _description = 'Fonts available' - _order = 'family,name,id' - _rec_name = 'family' - - family = fields.Char(string="Font family", required=True) - name = fields.Char(string="Font Name", required=True) - path = fields.Char(required=True) - mode = fields.Char(required=True) - - _sql_constraints = [ - ('name_font_uniq', 'unique(family, name)', 'You can not register two fonts with the same name'), - ] - - @api.model - def font_scan(self, lazy=False): - """Action of loading fonts - In lazy mode will scan the filesystem only if there is no founts in the database and sync if no font in CustomTTFonts - In not lazy mode will force scan filesystem and sync - """ - if lazy: - # lazy loading, scan only if no fonts in db - fonts = self.search([('path', '!=', '/dev/null')]) - if not fonts: - # no scan yet or no font found on the system, scan the filesystem - self._scan_disk() - elif len(customfonts.CustomTTFonts) == 0: - # CustomTTFonts list is empty - self._sync() - else: - self._scan_disk() - return True - - def _scan_disk(self): - """Scan the file system and register the result in database""" - found_fonts = [] - for font_path in customfonts.list_all_sysfonts(): - try: - font = ttfonts.TTFontFile(font_path) - _logger.debug("Found font %s at %s", font.name, font_path) - found_fonts.append((font.familyName, font.name, font_path, font.styleName)) - except Exception as ex: - _logger.warning("Could not register Font %s: %s", font_path, ex) - - for family, name, path, mode in found_fonts: - if not self.search([('family', '=', family), ('name', '=', name)]): - self.create({'family': family, 'name': name, 'path': path, 'mode': mode}) - - # remove fonts not present on the disk anymore - existing_font_names = [name for (family, name, path, mode) in found_fonts] - # Remove inexistent fonts - self.search([('name', 'not in', existing_font_names), ('path', '!=', '/dev/null')]).unlink() - - self.pool.cache_invalidated = True - return self._sync() - - def _sync(self): - """Set the customfonts.CustomTTFonts list to the content of the database""" - customfonts.CustomTTFonts = [] - local_family_modes = set() - local_font_paths = {} - for font in self.search([('path', '!=', '/dev/null')]): - local_family_modes.add((font.family, font.mode)) - local_font_paths[font.name] = font.path - customfonts.CustomTTFonts.append((font.family, font.name, font.path, font.mode)) - - # Attempt to remap the builtin fonts (Helvetica, Times, Courier) to better alternatives - # if available, because they only support a very small subset of unicode - # (missing 'Ä' for example) - for builtin_font_family, mode, alts in BUILTIN_ALTERNATIVES: - if (builtin_font_family, mode) not in local_family_modes: - # No local font exists with that name, try alternatives - for altern_font in alts: - if local_font_paths.get(altern_font): - altern_def = (builtin_font_family, altern_font, - local_font_paths[altern_font], mode) - customfonts.CustomTTFonts.append(altern_def) - _logger.debug("Builtin remapping %r", altern_def) - break - else: - _logger.warning("No local alternative found for builtin font `%s` (%s mode)." - "Consider installing the DejaVu fonts if you have problems " - "with unicode characters in RML reports", - builtin_font_family, mode) - return True - - @classmethod - def clear_caches(cls): - """Force worker to resync at next report loading by setting an empty font list""" - customfonts.CustomTTFonts = [] - return super(ResFont, cls).clear_caches() diff --git a/odoo/addons/base/security/ir.model.access.csv b/odoo/addons/base/security/ir.model.access.csv index 284fc1549ddc1f40aa2f4f913806c7db64f8b47b..50a0d8ec7622a76ada85154a05dab08d3dddcfa5 100644 --- a/odoo/addons/base/security/ir.model.access.csv +++ b/odoo/addons/base/security/ir.model.access.csv @@ -70,8 +70,8 @@ "access_ir_actions_act_window_system","ir_actions_act_window_system","model_ir_actions_act_window","group_system",1,1,1,1 "access_ir_actions_act_window_close_all","ir_actions_act_window_close_all","model_ir_actions_act_window_close",,1,0,0,0 "access_ir_actions_act_window_close_group_system","ir_actions_act_window_close_group_system","model_ir_actions_act_window_close","group_system",1,1,1,1 -"access_ir_actions_report_xml_all","ir_actions_report_xml","model_ir_actions_report_xml",,1,0,0,0 -"access_ir_actions_report_xml_group_system","ir_actions_report_xml_group_system","model_ir_actions_report_xml","group_system",1,1,1,1 +"access_ir_actions_report_all","ir_actions_report","model_ir_actions_report",,1,0,0,0 +"access_ir_actions_report_group_system","ir_actions_report_group_system","model_ir_actions_report","group_system",1,1,1,1 "access_ir_actions_todo_group_system","ir_actions_todo group system","model_ir_actions_todo","group_system",1,1,1,1 "access_ir_actions_act_window_view_all","ir_actions_act_window_view_all","model_ir_actions_act_window_view",,1,0,0,0 "access_ir_actions_act_window_view_group_system","ir_actions_act_window_view_group_system","model_ir_actions_act_window_view","group_system",1,1,1,1 @@ -91,6 +91,4 @@ "access_ir_config_parameter_system","ir_config_parameter_system","model_ir_config_parameter","group_system",1,1,1,1 "access_ir_mail_server","ir_mail_server","model_ir_mail_server","group_system",1,1,1,1 "access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0 -"access_res_font_all","res_res_font all","model_res_font",,1,0,0,0 -"access_res_font_group_user","res_res_font group_user","model_res_font","group_user",1,1,1,1 "access_ir_logging","ir_logging admin","model_ir_logging","group_erp_manager",1,1,1,1 diff --git a/odoo/addons/base/tests/test_ir_values.py b/odoo/addons/base/tests/test_ir_values.py index 5a10f3537d96814beb18d98e11920e0de36ab3de..a4365f7e0aad10d15e0943fcc20d9f5a937a0f04 100644 --- a/odoo/addons/base/tests/test_ir_values.py +++ b/odoo/addons/base/tests/test_ir_values.py @@ -63,9 +63,9 @@ class TestIrValues(TransactionCase): ir_values.set_action('OnDblClick Action 2', action_slot='tree_but_open', model='res.partner', action='ir.actions.act_window,%d' % act_id_2, res_id=False) ir_values.set_action('Side Wizard', action_slot='client_action_multi', model='res.partner', action='ir.actions.act_window,%d' % act_id_3, res_id=False) - reports = self.env['ir.actions.report.xml'].search([]) + reports = self.env['ir.actions.report'].search([]) report_id = next(report.id for report in reports if not report.groups_id) - ir_values.set_action('Nice Report', action_slot='client_print_multi', model='res.partner', action='ir.actions.report.xml,%d' % report_id, res_id=False) + ir_values.set_action('Nice Report', action_slot='client_print_multi', model='res.partner', action='ir.actions.report,%d' % report_id, res_id=False) # Replace one action binding to set a new name. ir_values.set_action('OnDblClick Action New', action_slot='tree_but_open', model='res.partner', action='ir.actions.act_window,%d' % act_id_1, res_id=False) diff --git a/odoo/conf/__init__.py b/odoo/conf/__init__.py index b9722b9fff602d51f4088e0242ea606d6e1caf45..2216701c5d68f3fefb9e231445ab5ce32bdf5cd9 100644 --- a/odoo/conf/__init__.py +++ b/odoo/conf/__init__.py @@ -15,8 +15,6 @@ must be used. """ -from . import deprecation - # Paths to search for OpenERP addons. addons_paths = [] diff --git a/odoo/conf/deprecation.py b/odoo/conf/deprecation.py deleted file mode 100644 index 3f64def1fd019c39151bead95cd41df3d45083fe..0000000000000000000000000000000000000000 --- a/odoo/conf/deprecation.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -""" Regroup variables for deprecated features. - -To keep the OpenERP server backward compatible with older modules, some -additional code is needed throughout the core library. This module keeps -track of those specific measures by providing variables that can be unset -by the user to check if her code is future proof. - -In a perfect world, all these variables are set to False, the corresponding -code removed, and thus these variables made unnecessary. -""" - -# If True, the Python modules inside the openerp namespace are made available -# without the 'openerp.' prefix. E.g. openerp.osv.osv and osv.osv refer to the -# same module. -# Introduced around 2011.02. -# Change to False around 2013.02. -open_openerp_namespace = False - -# If True, openerp.netsvc.LocalService() can be used to lookup reports or to -# Introduced around 2013.03. -# Among the related code: -# - The openerp.netsvc.LocalService() function. -# - The openerp.report.interface.report_int._reports dictionary. -# - The register attribute in openerp.report.interface.report_int (and in its -# - auto column in ir.actions.report.xml. -# inheriting classes). -allow_local_service = True - -# Applies for the register attribute in openerp.report.interface.report_int. -# See comments for allow_local_service above. -# Introduced around 2013.03. -allow_report_int_registration = True diff --git a/odoo/import_xml.rng b/odoo/import_xml.rng index d85cd05a0bad3a149f1d9be195147c114c0a4039..adcbd19e0333a431f58fdc6aa0a551bf87cecca1 100644 --- a/odoo/import_xml.rng +++ b/odoo/import_xml.rng @@ -77,15 +77,11 @@ <rng:optional><rng:attribute name="multi"/></rng:optional> <rng:optional><rng:attribute name="menu"/></rng:optional> <rng:optional><rng:attribute name="keyword"/></rng:optional> - <rng:optional><rng:attribute name="rml"/></rng:optional><!-- pending deprecation after v6.0 --> <rng:optional><rng:attribute name="file"/></rng:optional> - <rng:optional><rng:attribute name="sxw"/></rng:optional> <rng:optional><rng:attribute name="xml"/></rng:optional> - <rng:optional><rng:attribute name="xsl"/></rng:optional> <rng:optional><rng:attribute name="parser"/></rng:optional> <rng:optional> <rng:attribute name="auto" /> </rng:optional> <rng:optional> <rng:attribute name="header" /> </rng:optional> - <rng:optional> <rng:attribute name="webkit_header" /> </rng:optional> <rng:optional> <rng:attribute name="attachment" /> </rng:optional> <rng:optional> <rng:attribute name="attachment_use" /> </rng:optional> <rng:optional> <rng:attribute name="groups"/> </rng:optional> diff --git a/odoo/models.py b/odoo/models.py index b993596f332aa8b0eff4a2462d48039f2e4f6f8a..a1535123c9db832935fe7e53342f0a293141a983 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -1304,24 +1304,18 @@ class BaseModel(object): # Add related action information if aksed if toolbar: - toclean = ('report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml', 'report_sxw_content_data', 'report_rml_content_data') - def clean(x): - x = x[2] - for key in toclean: - x.pop(key, None) - return x IrValues = self.env['ir.values'] resprint = IrValues.get_actions('client_print_multi', self._name) resaction = IrValues.get_actions('client_action_multi', self._name) resrelate = IrValues.get_actions('client_action_relate', self._name) - resprint = [clean(print_) + resprint = [print_[2] for print_ in resprint if view_type == 'tree' or not print_[2].get('multi')] - resaction = [clean(action) + resaction = [action[2] for action in resaction if view_type == 'tree' or not action[2].get('multi')] #When multi="True" set it will display only in More of the list view - resrelate = [clean(action) + resrelate = [action[2] for action in resrelate if (action[2].get('multi') and view_type == 'tree') or (not action[2].get('multi') and view_type == 'form')] @@ -3981,17 +3975,6 @@ class BaseModel(object): get_xml_id = get_external_id _get_xml_ids = _get_external_ids - @api.multi - def print_report(self, name, data): - """ - Render the report ``name`` for the given IDs. The report must be defined - for this model, not another. - """ - report = self.env['ir.actions.report.xml']._lookup_report(name) - assert self._name == report.table - cr, uid, context = self.env.args - return report.create(cr, uid, self.ids, data, context) - # Transience @classmethod def is_transient(cls): diff --git a/odoo/netsvc.py b/odoo/netsvc.py index 3832ae10cd6f96b0ff675b02bc77e7d879081eef..a44984f391cdd23c8c249f2fcd1de5cbc41ee500 100644 --- a/odoo/netsvc.py +++ b/odoo/netsvc.py @@ -25,26 +25,6 @@ def log(logger, level, prefix, msg, depth=None): logger.log(level, indent+line) indent=indent_after -def LocalService(name): - """ - The odoo.netsvc.LocalService() function is deprecated. It still works - in one case: reports. For reports, odoo.report.render_report() should - be used (methods on the Model should be provided too in the future). - """ - assert odoo.conf.deprecation.allow_local_service - _logger.warning("LocalService() is deprecated since march 2013 (it was called with '%s')." % name) - - if name.startswith('report.'): - report = odoo.report.interface.report_int._reports.get(name) - if report: - return report - else: - dbname = getattr(threading.currentThread(), 'dbname', None) - if dbname: - registry = odoo.registry(dbname) - with registry.cursor() as cr: - return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):]) - path_prefix = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) class PostgreSQLHandler(logging.Handler): diff --git a/odoo/report/__init__.py b/odoo/report/__init__.py deleted file mode 100644 index 1000d4991c65e4c26654724a9c82f83a8c1e02dd..0000000000000000000000000000000000000000 --- a/odoo/report/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import api -from . import custom -from . import int_to_text -from . import interface -from . import print_fnc -from . import print_xml -from . import printscreen -from . import render -from . import report_sxw - -def render_report(cr, uid, ids, name, data, context=None): - """ - Helper to call ``ir.actions.report.xml.render_report()``. - """ - env = api.Environment(cr, uid, context or {}) - return env['ir.actions.report.xml'].render_report(ids, name, data) diff --git a/odoo/report/common.py b/odoo/report/common.py deleted file mode 100644 index dffcffa859cf4d567ee47e96c68407f75a150ff8..0000000000000000000000000000000000000000 --- a/odoo/report/common.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -pageSize = { - 'A4': (210,297), - 'A5': (148.5,105) -} - -odt_namespace = { - "office":"{urn:oasis:names:tc:opendocument:xmlns:office:1.0}", - "style":"{urn:oasis:names:tc:opendocument:xmlns:style:1.0}", - "text":"{urn:oasis:names:tc:opendocument:xmlns:text:1.0}", - "table":"{urn:oasis:names:tc:opendocument:xmlns:table:1.0}", - "draw":"{urn:oasis:names:tc:opendocument:xmlns:drawing:1.0}", - "fo":"{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}", - "xlink":"{http://www.w3.org/1999/xlink}", - "dc":"{http://purl.org/dc/elements/1.1/}", - "meta":"{urn:oasis:names:tc:opendocument:xmlns:meta:1.0}", - "number":"{urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0}", - "svg":"{urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0}", - "chart":"{urn:oasis:names:tc:opendocument:xmlns:chart:1.0}", - "dr3d":"{urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0}", - "math":"{http://www.w3.org/1998/Math/MathML}", - "form":"{urn:oasis:names:tc:opendocument:xmlns:form:1.0}", - "script":"{urn:oasis:names:tc:opendocument:xmlns:script:1.0}", - "ooo":"{http://openoffice.org/2004/office}", - "ooow":"{http://openoffice.org/2004/writer}", - "oooc":"{http://openoffice.org/2004/calc}", - "dom":"{http://www.w3.org/2001/xml-events}" } - -sxw_namespace = { - "office":"{http://openoffice.org/2000/office}", - "style":"{http://openoffice.org/2000/style}", - "text":"{http://openoffice.org/2000/text}", - "table":"{http://openoffice.org/2000/table}", - "draw":"{http://openoffice.org/2000/drawing}", - "fo":"{http://www.w3.org/1999/XSL/Format}", - "xlink":"{http://www.w3.org/1999/xlink}", - "dc":"{http://purl.org/dc/elements/1.1/}", - "meta":"{http://openoffice.org/2000/meta}", - "number":"{http://openoffice.org/2000/datastyle}", - "svg":"{http://www.w3.org/2000/svg}", - "chart":"{http://openoffice.org/2000/chart}", - "dr3d":"{http://openoffice.org/2000/dr3d}", - "math":"{http://www.w3.org/1998/Math/MathML}", - "form":"{http://openoffice.org/2000/form}", - "script":"{http://openoffice.org/2000/script}", - "ooo":"{http://openoffice.org/2004/office}", - "ooow":"{http://openoffice.org/2004/writer}", - "oooc":"{http://openoffice.org/2004/calc}", - "dom":"{http://www.w3.org/2001/xml-events}"} diff --git a/odoo/report/custom.py b/odoo/report/custom.py deleted file mode 100644 index 1eb68f0890fbbcffa99cd7596200f46b3795794e..0000000000000000000000000000000000000000 --- a/odoo/report/custom.py +++ /dev/null @@ -1,587 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import cStringIO -import os -import time - -from lxml import etree -from pychart import area, arrow, axis, bar_plot, canvas, category_coord, \ - fill_style, legend, line_plot, line_style, pie_plot, theme - -import odoo -import odoo.tools as tools -from odoo.exceptions import UserError -from odoo.models import BaseModel -from odoo.tools.safe_eval import safe_eval -from odoo.tools.translate import _ -from . import common -from . import misc -from . import render -from .interface import report_int - - -class external_pdf(render.render): - def __init__(self, pdf): - render.render.__init__(self) - self.pdf = pdf - self.output_type='pdf' - def _render(self): - return self.pdf - -theme.use_color = 1 - - -#TODO: devrait heriter de report_rml a la place de report_int -# -> pourrait overrider que create_xml a la place de tout create -# heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment -# par pychart, et on passe donc pas par du rml -class report_custom(report_int): - def __init__(self, name): - report_int.__init__(self, name) - - # - # PRE: - # fields = [['address','city'],['name'], ['zip']] - # conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields - # row_canvas = ['Rue', None, None] - # POST: - # [ ['ville','name','zip'] ] - # - def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None): - result = [] - for obj in objs: - tobreak = False - for cond in conditions: - if cond and cond[0]: - c = cond[0] - temp = c[0](safe_eval('obj.'+c[1],{'obj': obj})) - if not safe_eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''): - tobreak = True - if tobreak: - break - levels = {} - row = [] - for i in range(len(fields)): - if not fields[i]: - row.append(row_canvas and row_canvas[i]) - if row_canvas[i]: - row_canvas[i]=False - elif len(fields[i])==1: - if obj: - row.append(str(safe_eval('obj.'+fields[i][0],{'obj': obj}))) - else: - row.append(None) - else: - row.append(None) - levels[fields[i][0]]=True - if not levels: - result.append(row) - else: - # Process group_by data first - key = [] - if group_by is not None and fields[group_by] is not None: - if fields[group_by][0] in levels.keys(): - key.append(fields[group_by][0]) - for l in levels.keys(): - if l != fields[group_by][0]: - key.append(l) - else: - key = levels.keys() - for l in key: - objs = safe_eval('obj.'+l,{'obj': obj}) - if not isinstance(objs, (BaseModel, list)): - objs = [objs] - field_new = [] - cond_new = [] - for f in range(len(fields)): - if (fields[f] and fields[f][0])==l: - field_new.append(fields[f][1:]) - cond_new.append(conditions[f][1:]) - else: - field_new.append(None) - cond_new.append(None) - if len(objs): - result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by) - else: - result.append(row) - return result - - def create(self, cr, uid, ids, datas, context=None): - env = odoo.api.Environment(cr, uid, context or {}) - report = env['ir.report.custom'].browse([datas['report_id']]) - datas['model'] = report.model_id.model - if report.menu_id: - ids = env[report.model_id.model].search([]).ids - datas['ids'] = ids - - report = report.read()[0] - fields = env['ir.report.custom.fields'].browse(report['fields_child0']).read() - fields.sort(key=lambda x: x['sequence']) - model_name = env['ir.model'].sudo().browse(report['model_id'][0]).model - - fct = { - 'id': lambda x: x, - 'gety': lambda x: x.split('-')[0], - 'in': lambda x: x.split(',') - } - new_fields = [] - new_cond = [] - for f in fields: - row = [] - cond = [] - for i in range(4): - field_child = f['field_child'+str(i)] - if field_child: - row.append( - env['ir.model.fields'].sudo().browse(field_child[0]).name - ) - if f['fc'+str(i)+'_operande']: - fct_name = 'id' - cond_op = f['fc'+str(i)+'_op'] - if len(f['fc'+str(i)+'_op'].split(',')) == 2: - cond_op = f['fc'+str(i)+'_op'].split(',')[1] - fct_name = f['fc'+str(i)+'_op'].split(',')[0] - cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition'])) - else: - cond.append(None) - new_fields.append(row) - new_cond.append(cond) - objs = env[model_name].browse(ids) - - # Group by - groupby = None - idx = 0 - for f in fields: - if f['groupby']: - groupby = idx - idx += 1 - - - results = [] - if report['field_parent']: - level = [] - def build_tree(obj, level, depth): - res = self._row_get(cr, uid, [obj], new_fields, new_cond) - level.append(depth) - new_obj = safe_eval('obj.'+report['field_parent'][1], {'obj': obj}) - if not isinstance(new_obj, list) : - new_obj = [new_obj] - for o in new_obj: - if o: - res += build_tree(o, level, depth+1) - return res - - for obj in objs: - results += build_tree(obj, level, 0) - else: - results = self._row_get(cr, uid, objs, new_fields, new_cond, group_by=groupby) - - fct = { - 'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0), - 'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0), - 'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0), - 'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0), - 'calc_count': lambda l: len(filter(None, l)), - 'False': lambda l: '\r\n'.join(filter(None, l)), - 'groupby': lambda l: reduce(lambda x,y: x or y, l) - } - new_res = [] - - prev = None - if groupby is not None: - res_dic = {} - for line in results: - if not line[groupby] and prev in res_dic: - res_dic[prev].append(line) - else: - prev = line[groupby] - res_dic.setdefault(line[groupby], []) - res_dic[line[groupby]].append(line) - - #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't - for key in filter(None, [x[groupby] for x in results]): - row = [] - for col in range(len(fields)): - if col == groupby: - row.append(fct['groupby'](map(lambda x: x[col], res_dic[key]))) - else: - row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key]))) - new_res.append(row) - results = new_res - - if report['type']=='table': - if report['field_parent']: - res = self._create_tree(uid, ids, report, fields, level, results, context) - else: - sort_idx = 0 - for idx in range(len(fields)): - if fields[idx]['name'] == report['sortby']: - sort_idx = idx - break - try : - results.sort(key=lambda x: float(x[sort_idx])) - except : - results.sort(key=lambda x: x[sort_idx]) - if report['limitt']: - results = results[:int(report['limitt'])] - res = self._create_table(uid, ids, report, fields, None, results, context) - - elif report['type'] in ('pie','bar', 'line'): - results2 = [] - prev = False - for r in results: - row = [] - for j in range(len(r)): - if j == 0 and not r[j]: - row.append(prev) - elif j == 0 and r[j]: - prev = r[j] - row.append(r[j]) - else: - try: - row.append(float(r[j])) - except Exception: - row.append(r[j]) - results2.append(row) - if report['type']=='pie': - res = self._create_pie(cr,uid, ids, report, fields, results2, context) - elif report['type']=='bar': - res = self._create_bars(cr,uid, ids, report, fields, results2, context) - elif report['type']=='line': - res = self._create_lines(cr,uid, ids, report, fields, results2, context) - - return self.obj.get(), 'pdf' - - def _create_tree(self, uid, ids, report, fields, level, results, context): - pageSize=common.pageSize.get(report['print_format'], [210.0,297.0]) - if report['print_orientation']=='landscape': - pageSize=[pageSize[1],pageSize[0]] - - new_doc = etree.Element('report') - - config = etree.SubElement(new_doc, 'config') - - def _append_node(name, text): - n = etree.SubElement(config, name) - n.text = text - - _append_node('date', time.strftime('%d/%m/%Y')) - _append_node('PageFormat', '%s' % report['print_format']) - _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) - _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) - _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) - - length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0) - count = 0 - for f in fields: - if not f['width']: count+=1 - for f in fields: - if not f['width']: - f['width']=round((float(length)/count)-0.5) - - _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields))) - _append_node('report-header', '%s' % (report['title'],)) - _append_node('report-footer', '%s' % (report['footer'],)) - - header = etree.SubElement(new_doc, 'header') - for f in fields: - field = etree.SubElement(header, 'field') - field.text = f['name'] - - lines = etree.SubElement(new_doc, 'lines') - level.reverse() - for line in results: - shift = level.pop() - node_line = etree.SubElement(lines, 'row') - prefix = '+' - for f in range(len(fields)): - col = etree.SubElement(node_line, 'col') - if f == 0: - col.attrib.update(para='yes', - tree='yes', - space=str(3*shift)+'mm') - if line[f] is not None: - col.text = prefix+str(line[f]) or '' - else: - col.text = '/' - prefix = '' - - transform = etree.XSLT( - etree.parse(os.path.join(tools.config['root_path'], - 'addons/base/report/custom_new.xsl'))) - rml = etree.tostring(transform(new_doc)) - - self.obj = render.rml(rml) - self.obj.render() - return True - - def _create_lines(self, cr, uid, ids, report, fields, results, context): - env = odoo.api.Environment(cr, uid, context or {}) - pdf_string = cStringIO.StringIO() - - can = canvas.init(fname=pdf_string, format='pdf') - can.show(80,380,'/16/H'+report['title']) - - ar = area.T(size=(350,350), - #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0), - x_axis=axis.X(label = fields[0]['name'], format="/a-30{}%s"), - y_axis=axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:])))) - - process_date = { - 'D': lambda x: reduce(lambda xx, yy: xx + '-' + yy, x.split('-')[1:3]), - 'M': lambda x: x.split('-')[1], - 'Y': lambda x: x.split('-')[0] - } - - abscissa = [] - - idx = 0 - date_idx = None - fct = {} - for f in fields: - field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0]) - if field_id: - ttype = env['ir.model.fields'].sudo().browse(field_id).ttype - if ttype == 'date': - date_idx = idx - fct[idx] = process_date[report['frequency']] - else: - fct[idx] = lambda x : x - else: - fct[idx] = lambda x : x - idx+=1 - - # plots are usually displayed year by year - # so we do so if the first field is a date - data_by_year = {} - if date_idx is not None: - for r in results: - key = process_date['Y'](r[date_idx]) - if key not in data_by_year: - data_by_year[key] = [] - for i in range(len(r)): - r[i] = fct[i](r[i]) - data_by_year[key].append(r) - else: - data_by_year[''] = results - - idx0 = 0 - nb_bar = len(data_by_year)*(len(fields)-1) - colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar)) - abscissa = {} - for line in data_by_year.keys(): - fields_bar = [] - # sum data and save it in a list. An item for a fields - for d in data_by_year[line]: - for idx in range(len(fields)-1): - fields_bar.append({}) - if d[0] in fields_bar[idx]: - fields_bar[idx][d[0]] += d[idx+1] - else: - fields_bar[idx][d[0]] = d[idx+1] - for idx in range(len(fields)-1): - data = {} - for k in fields_bar[idx].keys(): - if k in data: - data[k] += fields_bar[idx][k] - else: - data[k] = fields_bar[idx][k] - data_cum = [] - prev = 0.0 - keys = data.keys() - keys.sort() - # cumulate if necessary - for k in keys: - data_cum.append([k, float(data[k])+float(prev)]) - if fields[idx+1]['cumulate']: - prev += data[k] - idx0 = 0 - plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx]) - ar.add_plot(plot) - abscissa.update(fields_bar[idx]) - idx0 += 1 - - abscissa = map(lambda x : [x, None], abscissa) - ar.x_coord = category_coord.T(abscissa,0) - ar.draw(can) - - can.close() - self.obj = external_pdf(pdf_string.getvalue()) - self.obj.render() - pdf_string.close() - return True - - def _create_bars(self, cr, uid, ids, report, fields, results, context): - env = odoo.api.Environment(cr, uid, context or {}) - pdf_string = cStringIO.StringIO() - - can = canvas.init(fname=pdf_string, format='pdf') - can.show(80,380,'/16/H'+report['title']) - - process_date = { - 'D': lambda x: reduce(lambda xx, yy: xx + '-' + yy, x.split('-')[1:3]), - 'M': lambda x: x.split('-')[1], - 'Y': lambda x: x.split('-')[0] - } - - ar = area.T(size=(350,350), - x_axis=axis.X(label = fields[0]['name'], format="/a-30{}%s"), - y_axis=axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:])))) - - idx = 0 - date_idx = None - fct = {} - for f in fields: - field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0]) - if field_id: - ttype = env['ir.model.fields'].sudo().browse(field_id).ttype - if ttype == 'date': - date_idx = idx - fct[idx] = process_date[report['frequency']] - else: - fct[idx] = lambda x : x - else: - fct[idx] = lambda x : x - idx+=1 - - # plot are usually displayed year by year - # so we do so if the first field is a date - data_by_year = {} - if date_idx is not None: - for r in results: - key = process_date['Y'](r[date_idx]) - if key not in data_by_year: - data_by_year[key] = [] - for i in range(len(r)): - r[i] = fct[i](r[i]) - data_by_year[key].append(r) - else: - data_by_year[''] = results - - - nb_bar = len(data_by_year)*(len(fields)-1) - colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar)) - - abscissa = {} - for line in data_by_year.keys(): - fields_bar = [] - # sum data and save it in a list. An item for a fields - for d in data_by_year[line]: - for idx in range(len(fields)-1): - fields_bar.append({}) - if d[0] in fields_bar[idx]: - fields_bar[idx][d[0]] += d[idx+1] - else: - fields_bar[idx][d[0]] = d[idx+1] - for idx in range(len(fields)-1): - data = {} - for k in fields_bar[idx].keys(): - if k in data: - data[k] += fields_bar[idx][k] - else: - data[k] = fields_bar[idx][k] - data_cum = [] - prev = 0.0 - keys = data.keys() - keys.sort() - # cumulate if necessary - for k in keys: - data_cum.append([k, float(data[k])+float(prev)]) - if fields[idx+1]['cumulate']: - prev += data[k] - - idx0 = 0 - plot = bar_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, cluster=(idx0*(len(fields)-1)+idx,nb_bar), fill_style=colors[idx0*(len(fields)-1)+idx]) - ar.add_plot(plot) - abscissa.update(fields_bar[idx]) - idx0 += 1 - abscissa = map(lambda x : [x, None], abscissa) - abscissa.sort() - ar.x_coord = category_coord.T(abscissa,0) - ar.draw(can) - - can.close() - self.obj = external_pdf(pdf_string.getvalue()) - self.obj.render() - pdf_string.close() - return True - - def _create_pie(self, cr, uid, ids, report, fields, results, context): - pdf_string = cStringIO.StringIO() - can = canvas.init(fname=pdf_string, format='pdf') - ar = area.T(size=(350,350), legend=legend.T(), - x_grid_style = None, y_grid_style = None) - colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results))) - - if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0: - raise UserError(_("The sum of the data (2nd field) is null.\nWe can't draw a pie chart !")) - - plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10], - shadow = (2, -2, fill_style.gray50), - label_offset = 25, - arrow_style = arrow.a3, - fill_styles=colors) - ar.add_plot(plot) - ar.draw(can) - can.close() - self.obj = external_pdf(pdf_string.getvalue()) - self.obj.render() - pdf_string.close() - return True - - def _create_table(self, uid, ids, report, fields, tree, results, context): - pageSize=common.pageSize.get(report['print_format'], [210.0,297.0]) - if report['print_orientation']=='landscape': - pageSize=[pageSize[1],pageSize[0]] - - new_doc = etree.Element('report') - config = etree.SubElement(new_doc, 'config') - - def _append_node(name, text): - n = etree.SubElement(config, name) - n.text = text - - _append_node('date', time.strftime('%d/%m/%Y')) - _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) - _append_node('PageFormat', '%s' % report['print_format']) - _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) - _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) - - length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0) - count = 0 - for f in fields: - if not f['width']: count+=1 - for f in fields: - if not f['width']: - f['width']=round((float(length)/count)-0.5) - - _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields))) - _append_node('report-header', '%s' % (report['title'],)) - _append_node('report-footer', '%s' % (report['footer'],)) - - header = etree.SubElement(new_doc, 'header') - for f in fields: - field = etree.SubElement(header, 'field') - field.text = f['name'] - - lines = etree.SubElement(new_doc, 'lines') - for line in results: - node_line = etree.SubElement(lines, 'row') - for f in range(len(fields)): - col = etree.SubElement(node_line, 'col', tree='no') - if line[f] is not None: - col.text = line[f] or '' - else: - col.text = '/' - - transform = etree.XSLT( - etree.parse(os.path.join(tools.config['root_path'], - 'addons/base/report/custom_new.xsl'))) - rml = etree.tostring(transform(new_doc)) - - self.obj = render.rml(rml) - self.obj.render() - return True - -report_custom('report.custom') diff --git a/odoo/report/int_to_text.py b/odoo/report/int_to_text.py deleted file mode 100644 index 96e07f179bb8ccb03e288834a813a764ff263947..0000000000000000000000000000000000000000 --- a/odoo/report/int_to_text.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -unites = { - 0: '', 1:'un', 2:'deux', 3:'trois', 4:'quatre', 5:'cinq', 6:'six', 7:'sept', 8:'huit', 9:'neuf', - 10:'dix', 11:'onze', 12:'douze', 13:'treize', 14:'quatorze', 15:'quinze', 16:'seize', - 21:'vingt et un', 31:'trente et un', 41:'quarante et un', 51:'cinquante et un', 61:'soixante et un', - 71:'septante et un', 91:'nonante et un', 80:'quatre-vingts' -} - -dizaine = { - 1: 'dix', 2:'vingt', 3:'trente',4:'quarante', 5:'cinquante', 6:'soixante', 7:'septante', 8:'quatre-vingt', 9:'nonante' -} - -centaine = { - 0:'', 1: 'cent', 2:'deux cent', 3:'trois cent',4:'quatre cent', 5:'cinq cent', 6:'six cent', 7:'sept cent', 8:'huit cent', 9:'neuf cent' -} - -mille = { - 0:'', 1:'mille' -} - -def _100_to_text(chiffre): - if chiffre in unites: - return unites[chiffre] - else: - if chiffre%10>0: - return dizaine[chiffre / 10]+'-'+unites[chiffre % 10] - else: - return dizaine[chiffre / 10] - -def _1000_to_text(chiffre): - d = _100_to_text(chiffre % 100) - d2 = chiffre/100 - if d2>0 and d: - return centaine[d2]+' '+d - elif d2>1 and not d: - return centaine[d2]+'s' - else: - return centaine[d2] or d - -def _10000_to_text(chiffre): - if chiffre==0: - return 'zero' - part1 = _1000_to_text(chiffre % 1000) - part2 = mille.get(chiffre / 1000, _1000_to_text(chiffre / 1000)+' mille') - if part2 and part1: - part1 = ' '+part1 - return part2+part1 - -def int_to_text(i): - return _10000_to_text(i) - -if __name__=='__main__': - for i in range(1,999999,139): - print int_to_text(i) diff --git a/odoo/report/interface.py b/odoo/report/interface.py deleted file mode 100644 index ef0784fb518c5027e89303e533a78ae87850f9cc..0000000000000000000000000000000000000000 --- a/odoo/report/interface.py +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import os -import re -import urllib - -from lxml import etree - -import odoo -import odoo.tools as tools -from . import print_xml -from . import render -from odoo.modules import get_module_resource - -# -# coerce any type to a unicode string (to preserve non-ascii characters) -# and escape XML entities -# -def toxml(value): - unicode_value = tools.ustr(value) - return unicode_value.replace('&', '&').replace('<','<').replace('>','>') - - -class report_int(object): - - _reports = {} - - def __init__(self, name, register=True): - if register: - assert odoo.conf.deprecation.allow_report_int_registration - assert name.startswith('report.'), 'Report names should start with "report.".' - assert name not in self._reports, 'The report "%s" already exists.' % name - self._reports[name] = self - else: - # The report is instanciated at each use site, which is ok. - pass - - self.__name = name - - self.name = name - self.id = 0 - self.name2 = '.'.join(name.split('.')[1:]) - # TODO the reports have methods with a 'title' kwarg that is redundant with this attribute - self.title = None - - def create(self, cr, uid, ids, datas, context=None): - return False - - -class report_rml(report_int): - """ - Automatically builds a document using the transformation process: - XML -> DATAS -> RML -> PDF -> HTML - using a XSL:RML transformation - """ - def __init__(self, name, table, tmpl, xsl, register=True): - super(report_rml, self).__init__(name, register=register) - self.table = table - self.internal_header=False - self.tmpl = tmpl - self.xsl = xsl - self.bin_datas = {} - self.generators = { - 'pdf': self.create_pdf, - 'html': self.create_html, - 'raw': self.create_raw, - 'sxw': self.create_sxw, - 'txt': self.create_txt, - 'odt': self.create_odt, - 'html2html' : self.create_html2html, - 'makohtml2html' :self.create_makohtml2html, - } - - def create(self, cr, uid, ids, datas, context=None): - env = odoo.api.Environment(cr, uid, context or {}) - xml = self.create_xml(cr, uid, ids, datas, context) - xml = tools.ustr(xml).encode('utf8') - report_type = datas.get('report_type', 'pdf') - if report_type == 'raw': - return xml, report_type - - env['res.font'].sudo().font_scan(lazy=True) - - rml = self.create_rml(cr, xml, uid, context) - reports = env['ir.actions.report.xml'].search([('report_name', '=', self.name[7:])]) - self.title = reports[0].name if reports else 'Odoo Report' - create_doc = self.generators[report_type] - pdf = create_doc(rml, title=self.title) - return pdf, report_type - - def create_xml(self, cr, uid, ids, datas, context=None): - doc = print_xml.document(cr, uid, datas, {}) - self.bin_datas.update( doc.bin_datas or {}) - doc.parse(self.tmpl, ids, self.table, context) - xml = doc.xml_get() - doc.close() - return self.post_process_xml_data(cr, uid, xml, context) - - def post_process_xml_data(self, cr, uid, xml, context=None): - # find the position of the 3rd tag - # (skip the <?xml ...?> and the "root" tag) - iter = re.finditer('<[^>]*>', xml) - i = iter.next() - i = iter.next() - pos_xml = i.end() - - doc = print_xml.document(cr, uid, {}, {}) - tmpl_path = get_module_resource('base', 'report', 'corporate_defaults.xml') - doc.parse(tmpl_path, [uid], 'res.users', context) - corporate_header = doc.xml_get() - doc.close() - - # find the position of the tag after the <?xml ...?> tag - iter = re.finditer('<[^>]*>', corporate_header) - i = iter.next() - pos_header = i.end() - - return xml[:pos_xml] + corporate_header[pos_header:] + xml[pos_xml:] - - # - # TODO: The translation doesn't work for "<tag t="1">textext<tag> tex</tag>text</tag>" - # - def create_rml(self, cr, xml, uid, context=None): - if self.tmpl=='' and not self.internal_header: - self.internal_header=True - env = odoo.api.Environment(cr, uid, context or {}) - Translation = env['ir.translation'] - - # In some case we might not use xsl ... - if not self.xsl: - return xml - - stylesheet_file = tools.file_open(self.xsl) - try: - stylesheet = etree.parse(stylesheet_file) - xsl_path, _ = os.path.split(self.xsl) - for import_child in stylesheet.findall('./import'): - if 'href' in import_child.attrib: - imp_file = import_child.get('href') - _, imp_file = tools.file_open(imp_file, subdir=xsl_path, pathinfo=True) - import_child.set('href', urllib.quote(str(imp_file))) - imp_file.close() - finally: - stylesheet_file.close() - - #TODO: get all the translation in one query. That means we have to: - # * build a list of items to translate, - # * issue the query to translate them, - # * (re)build/update the stylesheet with the translated items - - def translate(doc, lang): - translate_aux(doc, lang, False) - - def translate_aux(doc, lang, t): - for node in doc: - t = t or node.get("t") - if t: - text = None - tail = None - if node.text: - text = node.text.strip().replace('\n',' ') - if node.tail: - tail = node.tail.strip().replace('\n',' ') - if text: - text1 = Translation._get_source(self.name2, 'xsl', lang, text) - if text1: - node.text = node.text.replace(text, text1) - if tail: - tail1 = Translation._get_source(self.name2, 'xsl', lang, tail) - if tail1: - node.tail = node.tail.replace(tail, tail1) - translate_aux(node, lang, t) - - if env.lang: - translate(stylesheet.iter(), env.lang) - - transform = etree.XSLT(stylesheet) - xml = etree.tostring( - transform(etree.fromstring(xml))) - - return xml - - def create_pdf(self, rml, localcontext=None, logo=None, title=None): - if not localcontext: - localcontext = {} - localcontext.update({'internal_header':self.internal_header}) - if logo: - self.bin_datas['logo'] = logo - else: - if 'logo' in self.bin_datas: - del self.bin_datas['logo'] - obj = render.rml(rml, localcontext, self.bin_datas, self._get_path(), title) - obj.render() - return obj.get() - - def create_html(self, rml, localcontext=None, logo=None, title=None): - obj = render.rml2html(rml, localcontext, self.bin_datas) - obj.render() - return obj.get() - - def create_txt(self, rml,localcontext, logo=None, title=None): - obj = render.rml2txt(rml, localcontext, self.bin_datas) - obj.render() - return obj.get().encode('utf-8') - - def create_html2html(self, rml, localcontext=None, logo=None, title=None): - obj = render.html2html(rml, localcontext, self.bin_datas) - obj.render() - return obj.get() - - - def create_raw(self,rml, localcontext=None, logo=None, title=None): - obj = render.odt2odt(etree.XML(rml),localcontext) - obj.render() - return etree.tostring(obj.get()) - - def create_sxw(self,rml,localcontext=None): - obj = render.odt2odt(rml,localcontext) - obj.render() - return obj.get() - - def create_odt(self,rml,localcontext=None): - obj = render.odt2odt(rml,localcontext) - obj.render() - return obj.get() - - def create_makohtml2html(self,html,localcontext=None): - obj = render.makohtml2html(html,localcontext) - obj.render() - return obj.get() - - def _get_path(self): - return [ - self.tmpl.replace(os.path.sep, '/').rsplit('/', 1)[0], - 'addons', - tools.config['root_path'] - ] diff --git a/odoo/report/misc.py b/odoo/report/misc.py deleted file mode 100644 index 0b735abca7c5814fde814d43ccc1277fcf0e1013..0000000000000000000000000000000000000000 --- a/odoo/report/misc.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from pychart import color - -colorline = [color.T(r=((r+3) % 11)/10.0, - g=((g+6) % 11)/10.0, - b=((b+9) % 11)/10.0) - for r in range(11) for g in range(11) for b in range(11)] - -def choice_colors(n): - if n: - return colorline[0:-1:len(colorline)/n] - return [] - -if __name__=='__main__': - print choice_colors(10) diff --git a/odoo/report/preprocess.py b/odoo/report/preprocess.py deleted file mode 100644 index 16f35e1f49ac5ffbf5c96e712733e5eb72a8de72..0000000000000000000000000000000000000000 --- a/odoo/report/preprocess.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import re - -from lxml import etree - -rml_parents = ['tr','story','section'] -html_parents = ['tr','body','div'] -sxw_parents = ['{http://openoffice.org/2000/table}table-row','{http://openoffice.org/2000/office}body','{http://openoffice.org/2000/text}section'] -odt_parents = ['{urn:oasis:names:tc:opendocument:xmlns:office:1.0}body','{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-row','{urn:oasis:names:tc:opendocument:xmlns:text:1.0}section'] - - -class report(object): - def preprocess_rml(self, root_node,type='pdf'): - _regex1 = re.compile("\[\[(.*?)(repeatIn\(.*?\s*,\s*[\'\"].*?[\'\"]\s*(?:,\s*(.*?)\s*)?\s*\))(.*?)\]\]") - _regex11= re.compile("\[\[(.*?)(repeatIn\(.*?\s*\(.*?\s*[\'\"].*?[\'\"]\s*\),[\'\"].*?[\'\"](?:,\s*(.*?)\s*)?\s*\))(.*?)\]\]") - _regex2 = re.compile("\[\[(.*?)(removeParentNode\(\s*(?:['\"](.*?)['\"])\s*\))(.*?)\]\]") - _regex3 = re.compile("\[\[\s*(.*?setTag\(\s*['\"](.*?)['\"]\s*,\s*['\"].*?['\"]\s*(?:,.*?)?\).*?)\s*\]\]") - for node in root_node: - if node.tag == etree.Comment: - continue - if node.text or node.tail: - def _sub3(txt): - n = node - while n.tag != txt.group(2): - n = n.getparent() - n.set('rml_tag', txt.group(1)) - return "[[ '' ]]" - def _sub2(txt): - if txt.group(3): - n = node - try: - while n.tag != txt.group(3): - n = n.getparent() - except Exception: - n = node - else: - n = node.getparent() - n.set('rml_except', txt.group(0)[2:-2]) - return txt.group(0) - def _sub1(txt): - if len(txt.group(4)) > 1: - return " " - match = rml_parents - if type == 'odt': - match = odt_parents - if type == 'sxw': - match = sxw_parents - if type =='html2html': - match = html_parents - if txt.group(3): - group_3 = txt.group(3) - if group_3.startswith("'") or group_3.startswith('"'): - group_3 = group_3[1:-1] - match = [group_3] - n = node - while n.tag not in match: - n = n.getparent() - n.set('rml_loop', txt.group(2)) - return '[['+txt.group(1)+"''"+txt.group(4)+']]' - t = _regex1.sub(_sub1, node.text or node.tail) - if t == " ": - t = _regex11.sub(_sub1, node.text or node.tail) - t = _regex3.sub(_sub3, t) - node.text = _regex2.sub(_sub2, t) - self.preprocess_rml(node,type) - return root_node - -if __name__=='__main__': - node = etree.XML('''<story> - <para>This is a test[[ setTag('para','xpre') ]]</para> - <blockTable> - <tr> - <td><para>Row 1 [[ setTag('tr','tr',{'style':'TrLevel'+str(a['level']), 'paraStyle':('Level'+str(a['level']))}) ]] </para></td> - <td>Row 2 [[ True and removeParentNode('td') ]] </td> - </tr><tr> - <td>Row 1 [[repeatIn(o.order_line,'o')]] </td> - <td>Row 2</td> - </tr> - </blockTable> - <p>This isa test</p> -</story>''') - a = report() - result = a.preprocess_rml(node) - print etree.tostring(result) diff --git a/odoo/report/print_fnc.py b/odoo/report/print_fnc.py deleted file mode 100644 index 80ad11546717a501e8e5e5f8cd0c21de19ff9df9..0000000000000000000000000000000000000000 --- a/odoo/report/print_fnc.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import time - -functions = { - 'today': lambda x: time.strftime('%d/%m/%Y', time.localtime()).decode('latin1') -} - -# -# TODO: call an object internal function too -# -def print_fnc(fnc, arg): - if fnc in functions: - return functions[fnc](arg) - return '' diff --git a/odoo/report/print_xml.py b/odoo/report/print_xml.py deleted file mode 100644 index 3fe9b959f88a0f5fa0415f0ed29e749b709e2adc..0000000000000000000000000000000000000000 --- a/odoo/report/print_xml.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from lxml import etree - -import odoo -import odoo.tools as tools -from . import print_fnc -from odoo.models import BaseModel -from odoo.tools.safe_eval import safe_eval - - -class InheritDict(dict): - # Might be useful when we're doing name lookup for call or eval. - def __init__(self, parent=None): - self.parent = parent - - def __getitem__(self, name): - if name in self: - return super(InheritDict, self).__getitem__(name) - else: - if not self.parent: - raise KeyError - else: - return self.parent[name] - - -def tounicode(val): - if isinstance(val, str): - unicode_val = unicode(val, 'utf-8') - elif isinstance(val, unicode): - unicode_val = val - else: - unicode_val = unicode(val) - return unicode_val - - -class document(object): - def __init__(self, cr, uid, datas, func=False): - # create a new document - self.cr = cr - self.uid = uid - self.datas = datas - self.func = func or {} - self.bin_datas = {} - - def node_attrs_get(self, node): - if len(node.attrib): - return node.attrib - return {} - - def get_value(self, browser, field_path): - fields = field_path.split('.') - - if not len(fields): - return '' - - value = browser - - for f in fields: - if isinstance(value, (BaseModel, list)): - if not value: - return '' - value = value[0] - value = value[f] - - return value or '' - - def get_value2(self, browser, field_path): - value = self.get_value(browser, field_path) - if isinstance(value, BaseModel): - return value.id - else: - return value - - def eval(self, record, expr): -#TODO: support remote variables (eg address.title) in expr -# how to do that: parse the string, find dots, replace those dotted variables by temporary -# "simple ones", fetch the value of those variables and add them (temporarily) to the _data -# dictionary passed to eval - -#FIXME: it wont work if the data hasn't been fetched yet... this could -# happen if the eval node is the first one using this Record -# the next line is a workaround for the problem: it causes the resource to be loaded -#Pinky: Why not this ? eval(expr, browser) ? -# name = browser.name -# data_dict = browser._data[self.get_value(browser, 'id')] - return safe_eval(expr, {}, {'obj': record}) - - def parse_node(self, node, parent, browser, datas=None): - env = odoo.api.Environment(self.cr, self.uid, {}) - attrs = self.node_attrs_get(node) - if 'type' in attrs: - if attrs['type']=='field': - value = self.get_value(browser, attrs['name']) - #TODO: test this - if value == '' and 'default' in attrs: - value = attrs['default'] - el = etree.SubElement(parent, node.tag) - el.text = tounicode(value) - #TODO: test this - for key, value in attrs.iteritems(): - if key not in ('type', 'name', 'default'): - el.set(key, value) - - elif attrs['type']=='attachment': - model = browser._name - value = self.get_value(browser, attrs['name']) - - atts = env['ir.attachment'].search([('res_model','=',model),('res_id','=',int(value))]) - datas = atts.read() - - if len(datas): - # if there are several, pick first - datas = datas[0] - fname = str(datas['datas_fname']) - ext = fname.split('.')[-1].lower() - if ext in ('jpg','jpeg', 'png'): - import base64 - from StringIO import StringIO - dt = base64.decodestring(datas['datas']) - fp = StringIO() - fp.write(dt) - i = str(len(self.bin_datas)) - self.bin_datas[i] = fp - el = etree.SubElement(parent, node.tag) - el.text = i - - elif attrs['type']=='data': - #TODO: test this - txt = self.datas.get('form', {}).get(attrs['name'], '') - el = etree.SubElement(parent, node.tag) - el.text = txt - - elif attrs['type']=='function': - if attrs['name'] in self.func: - txt = self.func[attrs['name']](node) - else: - txt = print_fnc.print_fnc(attrs['name'], node) - el = etree.SubElement(parent, node.tag) - el.text = txt - - elif attrs['type']=='eval': - value = self.eval(browser, attrs['expr']) - el = etree.SubElement(parent, node.tag) - el.text = str(value) - - elif attrs['type']=='fields': - fields = attrs['name'].split(',') - vals = {} - for b in browser: - value = tuple([self.get_value2(b, f) for f in fields]) - if not value in vals: - vals[value]=[] - vals[value].append(b) - keys = vals.keys() - keys.sort() - - if 'order' in attrs and attrs['order']=='desc': - keys.reverse() - - v_list = [vals[k] for k in keys] - for v in v_list: - el = etree.SubElement(parent, node.tag) - for el_cld in node: - self.parse_node(el_cld, el, v) - - elif attrs['type']=='call': - if len(attrs['args']): - #TODO: test this - # fetches the values of the variables which names where passed in the args attribute - args = [self.eval(browser, arg) for arg in attrs['args'].split(',')] - else: - args = [] - # get the object - if 'model' in attrs: - obj = env[attrs['model']] - else: - obj = browser # the record(set) is an instance of the model - - # get the ids - if 'ids' in attrs: - ids = self.eval(browser, attrs['ids']) - else: - ids = browser.ids - - # call the method itself - newdatas = getattr(obj, attrs['name'])(*args) - - def parse_result_tree(node, parent, datas): - if not node.tag == etree.Comment: - el = etree.SubElement(parent, node.tag) - atr = self.node_attrs_get(node) - if 'value' in atr: - if not isinstance(datas[atr['value']], (str, unicode)): - txt = str(datas[atr['value']]) - else: - txt = datas[atr['value']] - el.text = txt - else: - for el_cld in node: - parse_result_tree(el_cld, el, datas) - if not isinstance(newdatas, (BaseModel, list)): - newdatas = [newdatas] - for newdata in newdatas: - parse_result_tree(node, parent, newdata) - - elif attrs['type']=='zoom': - value = self.get_value(browser, attrs['name']) - if value: - if not isinstance(value, (BaseModel, list)): - v_list = [value] - else: - v_list = value - for v in v_list: - el = etree.SubElement(parent, node.tag) - for el_cld in node: - self.parse_node(el_cld, el, v) - else: - # if there is no "type" attribute in the node, copy it to the xml data and parse its children - if not node.tag == etree.Comment: - if node.tag == parent.tag: - el = parent - else: - el = etree.SubElement(parent, node.tag) - for el_cld in node: - self.parse_node(el_cld,el, browser) - - def xml_get(self): - return etree.tostring(self.doc,encoding="utf-8",xml_declaration=True,pretty_print=True) - - def parse_tree(self, ids, model, context=None): - env = odoo.api.Environment(self.cr, self.uid, context or {}) - browser = env[model].browse(ids) - self.parse_node(self.dom, self.doc, browser) - - def parse_string(self, xml, ids, model, context=None): - # parses the xml template to memory - self.dom = etree.XML(xml) - # create the xml data from the xml template - self.parse_tree(ids, model, context) - - def parse(self, filename, ids, model, context=None): - # parses the xml template to memory - src_file = tools.file_open(filename) - try: - self.dom = etree.XML(src_file.read()) - self.doc = etree.Element(self.dom.tag) - self.parse_tree(ids, model, context) - finally: - src_file.close() - - def close(self): - self.doc = None - self.dom = None diff --git a/odoo/report/printscreen/__init__.py b/odoo/report/printscreen/__init__.py deleted file mode 100644 index 5c442c68695740f77dd5a04d0f3ce343c27b0d9d..0000000000000000000000000000000000000000 --- a/odoo/report/printscreen/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import ps_list -from . import ps_form - - -""" A special report, that is automatically formatted to look like the - screen contents of Form/List Views. -""" diff --git a/odoo/report/printscreen/ps_form.py b/odoo/report/printscreen/ps_form.py deleted file mode 100644 index c063d04f3d5a183525e4b10296c74cf40412c642..0000000000000000000000000000000000000000 --- a/odoo/report/printscreen/ps_form.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import os -import time - -from lxml import etree - -import odoo -import odoo.tools as tools -from odoo.report import render -from odoo.report.interface import report_int - - -class report_printscreen_list(report_int): - def _parse_node(self, root_node): - result = [] - for node in root_node: - if node.tag == 'field': - attrsa = node.attrib - attrs = {} - if not attrsa is None: - for key,val in attrsa.items(): - attrs[key] = val - result.append(attrs['name']) - else: - result.extend(self._parse_node(node)) - return result - - def _parse_string(self, view): - dom = etree.XML(view) - return self._parse_node(dom) - - def create(self, cr, uid, ids, datas, context=None): - if not context: - context={} - env = odoo.api.Environment(cr, uid, context) - datas['ids'] = ids - records = env[datas['model']].browse(ids) - # title come from description of model which are specified in py file. - self.title = records._description - result = records.fields_view_get(view_type='form') - - fields_order = self._parse_string(result['arch']) - rows = records.read(list(result['fields'])) - self._create_table(uid, datas['ids'], result['fields'], fields_order, rows, context, records._description) - return self.obj.get(), 'pdf' - - def _create_table(self, uid, ids, fields, fields_order, results, context, title=''): - pageSize=[297.0,210.0] - - new_doc = etree.Element("report") - config = etree.SubElement(new_doc, 'config') - - # build header - def _append_node(name, text): - n = etree.SubElement(config, name) - n.text = text - - _append_node('date', time.strftime('%d/%m/%Y')) - _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) - _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) - _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) - _append_node('report-header', title) - - l = [] - t = 0 - strmax = (pageSize[0]-40) * 2.8346 - for f in fields_order: - s = 0 - if fields[f]['type'] in ('date','time','float','integer'): - s = 60 - strmax -= s - else: - t += fields[f].get('size', 56) / 28 + 1 - l.append(s) - for pos in range(len(l)): - if not l[pos]: - s = fields[fields_order[pos]].get('size', 56) / 28 + 1 - l[pos] = strmax * s / t - _append_node('tableSize', ','.join(map(str,l)) ) - - header = etree.SubElement(new_doc, 'header') - for f in fields_order: - field = etree.SubElement(header, 'field') - field.text = fields[f]['string'] or '' - - lines = etree.SubElement(new_doc, 'lines') - for line in results: - node_line = etree.SubElement(lines, 'row') - for f in fields_order: - if fields[f]['type']=='many2one' and line[f]: - line[f] = line[f][1] - if fields[f]['type'] in ('one2many','many2many') and line[f]: - line[f] = '( '+str(len(line[f])) + ' )' - if fields[f]['type'] == 'float': - precision=(('digits' in fields[f]) and fields[f]['digits'][1]) or 2 - line[f]=round(line[f],precision) - col = etree.SubElement(node_line, 'col', tree='no') - if line[f] is not None: - col.text = tools.ustr(line[f] or '') - else: - col.text = '/' - - transform = etree.XSLT( - etree.parse(os.path.join(tools.config['root_path'], - 'addons/base/report/custom_new.xsl'))) - rml = etree.tostring(transform(new_doc)) - - self.obj = render.rml(rml, self.title) - self.obj.render() - return True - -report_printscreen_list('report.printscreen.form') diff --git a/odoo/report/printscreen/ps_list.py b/odoo/report/printscreen/ps_list.py deleted file mode 100644 index 5ef2ac831428ed97884b9e0ab88eca4d81cdec97..0000000000000000000000000000000000000000 --- a/odoo/report/printscreen/ps_list.py +++ /dev/null @@ -1,250 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import locale -import os -import time -from datetime import datetime - -from lxml import etree - -import odoo -import odoo.tools as tools -from odoo.report import render, report_sxw -from odoo.report.interface import report_int -from odoo.tools.safe_eval import safe_eval - - -class report_printscreen_list(report_int): - def __init__(self, name): - super(report_printscreen_list, self).__init__(name) - self.context = {} - self.groupby = [] - self.cr = '' - - def _parse_node(self, root_node): - result = [] - for node in root_node: - field_name = node.get('name') - if not safe_eval(str(node.attrib.get('invisible',False)),{'context':self.context}): - if node.tag == 'field': - if field_name in self.groupby: - continue - result.append(field_name) - else: - result.extend(self._parse_node(node)) - return result - - def _parse_string(self, view): - try: - dom = etree.XML(view.encode('utf-8')) - except Exception: - dom = etree.XML(view) - return self._parse_node(dom) - - def create(self, cr, uid, ids, datas, context=None): - if not context: - context = {} - self.cr = cr - self.context = context - self.groupby = context.get('group_by',[]) - self.groupby_no_leaf = context.get('group_by_no_leaf', False) - env = odoo.api.Environment(cr, uid, context) - Model = env[datas['model']] - model = env['ir.model']._get(Model._name) - model_desc = model.name or Model._description - self.title = model_desc - datas['ids'] = ids - result = Model.fields_view_get(view_type='tree') - fields_order = self.groupby + self._parse_string(result['arch']) - if self.groupby: - rows = [] - def get_groupby_data(groupby = [], domain = []): - records = Model.read_group(domain, fields_order, groupby, 0, None) - for rec in records: - rec['__group'] = True - rec['__no_leaf'] = self.groupby_no_leaf - rec['__grouped_by'] = groupby[0] if (isinstance(groupby, list) and groupby) else groupby - for f in fields_order: - if f not in rec: - rec.update({f:False}) - elif isinstance(rec[f], tuple): - rec[f] = rec[f][1] - rows.append(rec) - inner_groupby = (rec.get('__context', {})).get('group_by',[]) - inner_domain = rec.get('__domain', []) - if inner_groupby: - get_groupby_data(inner_groupby, inner_domain) - else: - if self.groupby_no_leaf: - continue - children = Model.search(inner_domain) - res = children.read(list(result['fields'])) - res.sort(key=lambda x: ids.index(x['id'])) - rows.extend(res) - dom = [('id','in',ids)] - if self.groupby_no_leaf and len(ids) and not ids[0]: - dom = datas.get('_domain',[]) - get_groupby_data(self.groupby, dom) - else: - rows = Model.browse(ids).read(list(result['fields'])) - if datas['ids'] != rows.ids: # sorted ids were not taken into consideration for print screen - rows_new = [] - for id in datas['ids']: - rows_new += [elem for elem in rows if elem['id'] == id] - rows = rows_new - res = self._create_table(uid, datas['ids'], result['fields'], fields_order, rows, context, model_desc) - return self.obj.get(), 'pdf' - - def _create_table(self, uid, ids, fields, fields_order, results, context, title=''): - pageSize=[297.0, 210.0] - - new_doc = etree.Element("report") - config = etree.SubElement(new_doc, 'config') - - def _append_node(name, text): - n = etree.SubElement(config, name) - n.text = text - - #_append_node('date', time.strftime('%d/%m/%Y')) - _append_node('date', time.strftime(str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')))) - _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize)) - _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,)) - _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,)) - _append_node('report-header', title) - - env = odoo.api.Environment(self.cr, uid, {}) - Users = env['res.users'] - _append_node('company', Users.browse(uid).company_id.name) - rml_obj = report_sxw.rml_parse(self.cr, uid, Users._name, context) - _append_node('header-date', str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M"))) - l = [] - t = 0 - strmax = (pageSize[0]-40) * 2.8346 - temp = [] - tsum = [] - for i in range(0, len(fields_order)): - temp.append(0) - tsum.append(0) - ince = -1 - for f in fields_order: - s = 0 - ince += 1 - if fields[f]['type'] in ('date','time','datetime','float','integer'): - s = 60 - strmax -= s - if fields[f]['type'] in ('float','integer'): - temp[ince] = 1 - else: - t += fields[f].get('size', 80) / 28 + 1 - - l.append(s) - for pos in range(len(l)): - if not l[pos]: - s = fields[fields_order[pos]].get('size', 80) / 28 + 1 - l[pos] = strmax * s / t - - _append_node('tableSize', ','.join(map(str,l)) ) - - header = etree.SubElement(new_doc, 'header') - for f in fields_order: - field = etree.SubElement(header, 'field') - field.text = tools.ustr(fields[f]['string'] or '') - - lines = etree.SubElement(new_doc, 'lines') - for line in results: - node_line = etree.SubElement(lines, 'row') - count = -1 - for f in fields_order: - float_flag = 0 - count += 1 - if fields[f]['type']=='many2one' and line[f]: - if not line.get('__group'): - line[f] = line[f][1] - if fields[f]['type']=='selection' and line[f]: - for key, value in fields[f]['selection']: - if key == line[f]: - line[f] = value - break - if fields[f]['type'] in ('one2many','many2many') and line[f]: - line[f] = '( '+tools.ustr(len(line[f])) + ' )' - if fields[f]['type'] == 'float' and line[f]: - precision=(('digits' in fields[f]) and fields[f]['digits'][1]) or 2 - prec ='%.' + str(precision) +'f' - line[f]=prec%(line[f]) - float_flag = 1 - - if fields[f]['type'] == 'date' and line[f]: - new_d1 = line[f] - if not line.get('__group'): - format = str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')) - d1 = datetime.strptime(line[f],'%Y-%m-%d') - new_d1 = d1.strftime(format) - line[f] = new_d1 - - if fields[f]['type'] == 'time' and line[f]: - new_d1 = line[f] - if not line.get('__group'): - format = str(locale.nl_langinfo(locale.T_FMT)) - d1 = datetime.strptime(line[f], '%H:%M:%S') - new_d1 = d1.strftime(format) - line[f] = new_d1 - - if fields[f]['type'] == 'datetime' and line[f]: - new_d1 = line[f] - if not line.get('__group'): - format = str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))+' '+str(locale.nl_langinfo(locale.T_FMT)) - d1 = datetime.strptime(line[f], '%Y-%m-%d %H:%M:%S') - new_d1 = d1.strftime(format) - line[f] = new_d1 - - - if line.get('__group'): - col = etree.SubElement(node_line, 'col', para='group', tree='no') - else: - col = etree.SubElement(node_line, 'col', para='yes', tree='no') - - # Prevent empty labels in groups - if f == line.get('__grouped_by') and line.get('__group') and not line[f] and not float_flag and not temp[count]: - col.text = line[f] = 'Undefined' - col.set('tree', 'undefined') - - if line[f] is not None: - col.text = tools.ustr(line[f] or '') - if float_flag: - col.set('tree','float') - if line.get('__no_leaf') and temp[count] == 1 and f != 'id' and not line['__context']['group_by']: - tsum[count] = float(tsum[count]) + float(line[f]) - if not line.get('__group') and f != 'id' and temp[count] == 1: - tsum[count] = float(tsum[count]) + float(line[f]) - else: - col.text = '/' - - node_line = etree.SubElement(lines, 'row') - for f in range(0, len(fields_order)): - col = etree.SubElement(node_line, 'col', para='group', tree='no') - col.set('tree', 'float') - if tsum[f] is not None: - if tsum[f] != 0.0: - digits = fields[fields_order[f]].get('digits', (16, 2)) - prec = '%%.%sf' % (digits[1], ) - total = prec % (tsum[f], ) - txt = str(total or '') - else: - txt = str(tsum[f] or '') - else: - txt = '/' - if f == 0: - txt ='Total' - col.set('tree','no') - col.text = tools.ustr(txt or '') - - transform = etree.XSLT( - etree.parse(os.path.join(tools.config['root_path'], - 'addons/base/report/custom_new.xsl'))) - rml = etree.tostring(transform(new_doc)) - self.obj = render.rml(rml, title=self.title) - self.obj.render() - return True - -report_printscreen_list('report.printscreen.list') diff --git a/odoo/report/render/__init__.py b/odoo/report/render/__init__.py deleted file mode 100644 index 228de009bd1b54756f3e3c9696ae192179a7b695..0000000000000000000000000000000000000000 --- a/odoo/report/render/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .simple import simple -from .rml import rml, rml2html, rml2txt, odt2odt , html2html, makohtml2html -from .render import render - -try: - from PIL import Image -except ImportError: - import logging - _logger = logging.getLogger(__name__) - _logger.warning('Python Imaging not installed, you can use only .JPG pictures !') diff --git a/odoo/report/render/html2html/__init__.py b/odoo/report/render/html2html/__init__.py deleted file mode 100644 index 41fa95cf4a0d8e897b2c3a864f9d0dc08fd04a1a..0000000000000000000000000000000000000000 --- a/odoo/report/render/html2html/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .html2html import parseString diff --git a/odoo/report/render/html2html/html2html.py b/odoo/report/render/html2html/html2html.py deleted file mode 100644 index bb6ea8bae0d9ff57b0c1ace009713257565cfb39..0000000000000000000000000000000000000000 --- a/odoo/report/render/html2html/html2html.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import base64 -import copy -import cStringIO -import re - -from reportlab.lib.utils import ImageReader - -from odoo.report.render.rml2pdf import utils - -_regex = re.compile('\[\[(.+?)\]\]') -utils._regex = re.compile('\[\[\s*(.+?)\s*\]\]',re.DOTALL) -class html2html(object): - def __init__(self, html, localcontext): - self.localcontext = localcontext - self.etree = html - self._node = None - - - def render(self): - def process_text(node,new_node): - if new_node.tag in ['story','tr','section']: - new_node.attrib.clear() - for child in utils._child_get(node, self): - new_child = copy.deepcopy(child) - new_node.append(new_child) - if len(child): - for n in new_child: - new_child.text = utils._process_text(self, child.text) - new_child.tail = utils._process_text(self, child.tail) - new_child.remove(n) - process_text(child, new_child) - else: - if new_child.tag=='img' and new_child.get('name'): - if _regex.findall(new_child.get('name')) : - src = utils._process_text(self, new_child.get('name')) - if src : - new_child.set('src','data:image/gif;base64,%s'%src) - output = cStringIO.StringIO(base64.decodestring(src)) - img = ImageReader(output) - (width,height) = img.getSize() - if not new_child.get('width'): - new_child.set('width',str(width)) - if not new_child.get('height') : - new_child.set('height',str(height)) - else : - new_child.getparent().remove(new_child) - new_child.text = utils._process_text(self, child.text) - new_child.tail = utils._process_text(self, child.tail) - self._node = copy.deepcopy(self.etree) - for n in self._node: - self._node.remove(n) - process_text(self.etree, self._node) - return self._node - - def url_modify(self,root): - for n in root: - if (n.text.find('<a ')>=0 or n.text.find('<a')>=0) and n.text.find('href')>=0 and n.text.find('style')<=0 : - node = (n.tag=='span' and n.getparent().tag=='u') and n.getparent().getparent() or ((n.tag=='span') and n.getparent()) or n - style = node.get('color') and "style='color:%s; text-decoration: none;'"%node.get('color') or '' - if n.text.find('<a')>=0: - t = '<a ' - else : - t = '<a ' - href = n.text.split(t)[-1] - n.text = ' '.join([t,style,href]) - self.url_modify(n) - return root - -def parseString(node, localcontext = {}): - r = html2html(node, localcontext) - root = r.render() - root = r.url_modify(root) - return root diff --git a/odoo/report/render/makohtml2html/__init__.py b/odoo/report/render/makohtml2html/__init__.py deleted file mode 100644 index ad0404301ae8a560556512d4389426d737166165..0000000000000000000000000000000000000000 --- a/odoo/report/render/makohtml2html/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .makohtml2html import parseNode diff --git a/odoo/report/render/makohtml2html/makohtml2html.py b/odoo/report/render/makohtml2html/makohtml2html.py deleted file mode 100644 index f044d1d8f36fe3745950ebf5304364f3611cedf6..0000000000000000000000000000000000000000 --- a/odoo/report/render/makohtml2html/makohtml2html.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging -import os - -import mako -from mako.lookup import TemplateLookup -from mako.template import Template -from lxml import etree - -_logger = logging.getLogger(__name__) - -class makohtml2html(object): - def __init__(self, html, localcontext): - self.localcontext = localcontext - self.html = html - - def format_header(self, html): - head = html.findall('head') - header = '' - for node in head: - header += etree.tostring(node) - return header - - def format_footer(self, footer): - html_footer = '' - for node in footer[0].getchildren(): - html_footer += etree.tostring(node) - return html_footer - - def format_body(self, html): - body = html.findall('body') - body_list = [] - footer = self.format_footer(body[-1].getchildren()) - for b in body[:-1]: - body_list.append(etree.tostring(b).replace('\t', '').replace('\n','')) - html_body =''' - <script type="text/javascript"> - - var indexer = 0; - var aryTest = %s ; - function nextData() - { - if(indexer < aryTest.length -1) - { - indexer += 1; - document.forms[0].prev.disabled = false; - document.getElementById("openerp_data").innerHTML=aryTest[indexer]; - document.getElementById("counter").innerHTML= indexer + 1 + ' / ' + aryTest.length; - } - else - { - document.forms[0].next.disabled = true; - } - } - function prevData() - { - if (indexer > 0) - { - indexer -= 1; - document.forms[0].next.disabled = false; - document.getElementById("openerp_data").innerHTML=aryTest[indexer]; - document.getElementById("counter").innerHTML= indexer + 1 + ' / ' + aryTest.length; - } - else - { - document.forms[0].prev.disabled = true; - } - } - </script> - </head> - <body> - <div id="openerp_data"> - %s - </div> - <div> - %s - </div> - <br> - <form> - <table> - <tr> - <td td align="left"> - <input name = "prev" type="button" value="Previous" onclick="prevData();"> - </td> - <td> - <div id = "counter">%s / %s</div> - </td> - <td align="right"> - <input name = "next" type="button" value="Next" onclick="nextData();"> - </td> - </tr> - </table> - </form> - </body></html>'''%(body_list,body_list[0],footer,'1',len(body_list)) - return html_body - - def render(self): - path = os.path.realpath('addons/base/report') - temp_lookup = TemplateLookup(directories=[path],output_encoding='utf-8', encoding_errors='replace') - template = Template(self.html, lookup=temp_lookup) - self.localcontext.update({'css_path':path}) - final_html ='''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> - <html>''' - try: - html = template.render_unicode(**self.localcontext) - etree_obj = etree.HTML(html) - final_html += self.format_header(etree_obj) - final_html += self.format_body(etree_obj) - return final_html - except Exception: - _logger.exception('report :') - -def parseNode(html, localcontext = {}): - r = makohtml2html(html, localcontext) - return r.render() diff --git a/odoo/report/render/odt2odt/__init__.py b/odoo/report/render/odt2odt/__init__.py deleted file mode 100644 index b1d94607dab96ca3a3e8a01e57c03c015bf9b27f..0000000000000000000000000000000000000000 --- a/odoo/report/render/odt2odt/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .odt2odt import parseNode diff --git a/odoo/report/render/odt2odt/odt2odt.py b/odoo/report/render/odt2odt/odt2odt.py deleted file mode 100644 index 8eea37b8fb489465dc73ec71bb51e71795a7598b..0000000000000000000000000000000000000000 --- a/odoo/report/render/odt2odt/odt2odt.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import copy - -from odoo.report.render.rml2pdf import utils - -class odt2odt(object): - def __init__(self, odt, localcontext): - self.localcontext = localcontext - self.etree = odt - self._node = None - - def render(self): - def process_text(node,new_node): - for child in utils._child_get(node, self): - new_child = copy.deepcopy(child) - new_node.append(new_child) - if len(child): - for n in new_child: - new_child.text = utils._process_text(self, child.text) - new_child.tail = utils._process_text(self, child.tail) - new_child.remove(n) - process_text(child, new_child) - else: - new_child.text = utils._process_text(self, child.text) - new_child.tail = utils._process_text(self, child.tail) - self._node = copy.deepcopy(self.etree) - for n in self._node: - self._node.remove(n) - process_text(self.etree, self._node) - return self._node - -def parseNode(node, localcontext = {}): - r = odt2odt(node, localcontext) - return r.render() diff --git a/odoo/report/render/render.py b/odoo/report/render/render.py deleted file mode 100644 index 988c1bbd52cad10654dab76e94d308a3c1b23481..0000000000000000000000000000000000000000 --- a/odoo/report/render/render.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -# Why doing some multi-thread instead of using OSE capabilities ? -# For progress bar. - -# -# Add a transparant multi-thread layer to all report rendering layers -# - -# TODO: method to stock on the disk -class render(object): - """ Represents a report job being rendered. - - @param bin_datas a dictionary of name:<binary content> of images etc. - @param path the path in which binary files can be discovered, useful - for components (images) of the report. It can be: - - a string, relative or absolute path to images - - a list, containing strings of paths. - If a string is absolute path, it will be opened as such, else - it will be passed to tools.file_open() which also considers zip - addons. - - Reporting classes must subclass this class and redefine the __init__ and - _render methods (not the other methods). - - """ - def __init__(self, bin_datas=None, path='.'): - self.done = False - if bin_datas is None: - self.bin_datas = {} - else: - self.bin_datas = bin_datas - self.path = path - - def _render(self): - return None - - def render(self): - self.done = False - self._result = self._render() - self.done = True - return True - - def is_done(self): - return self.done - - def get(self): - if self.is_done(): - return self._result - else: - return None diff --git a/odoo/report/render/rml.py b/odoo/report/render/rml.py deleted file mode 100644 index d87a3a54ae2dfb0afbf462e65715d2434da0d97c..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import render -from . import rml2pdf -from . import rml2html as htmlizer -from . import rml2txt as txtizer -from . import odt2odt as odt -from . import html2html as html -from . import makohtml2html as makohtml - - -class rml(render.render): - def __init__(self, rml, localcontext = None, datas=None, path='.', title=None): - render.render.__init__(self, datas, path) - self.localcontext = localcontext - self.rml = rml - self.output_type = 'pdf' - self.title=title - - def _render(self): - return rml2pdf.parseNode(self.rml, self.localcontext, images=self.bin_datas, path=self.path,title=self.title) - - -class rml2html(render.render): - def __init__(self, rml,localcontext = None, datas=None): - super(rml2html, self).__init__(datas) - self.rml = rml - self.localcontext = localcontext - self.output_type = 'html' - - def _render(self): - return htmlizer.parseString(self.rml,self.localcontext) - - -class rml2txt(render.render): - def __init__(self, rml, localcontext= None, datas=None): - super(rml2txt, self).__init__(datas) - self.rml = rml - self.localcontext = localcontext - self.output_type = 'txt' - - def _render(self): - return txtizer.parseString(self.rml, self.localcontext) - - -class odt2odt(render.render): - def __init__(self, rml, localcontext=None, datas=None): - render.render.__init__(self, datas) - self.rml_dom = rml - self.localcontext = localcontext - self.output_type = 'odt' - - def _render(self): - return odt.parseNode(self.rml_dom,self.localcontext) - - -class html2html(render.render): - def __init__(self, rml, localcontext=None, datas=None): - render.render.__init__(self, datas) - self.rml_dom = rml - self.localcontext = localcontext - self.output_type = 'html' - - def _render(self): - return html.parseString(self.rml_dom,self.localcontext) - - -class makohtml2html(render.render): - def __init__(self, html, localcontext = None): - render.render.__init__(self) - self.html = html - self.localcontext = localcontext - self.output_type = 'html' - - def _render(self): - return makohtml.parseNode(self.html,self.localcontext) diff --git a/odoo/report/render/rml2html/__init__.py b/odoo/report/render/rml2html/__init__.py deleted file mode 100644 index 5f27854c99f0708422329e0bc5f16c001e4d74d8..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2html/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .rml2html import parseString diff --git a/odoo/report/render/rml2html/rml2html.py b/odoo/report/render/rml2html/rml2html.py deleted file mode 100644 index bdf04f7d6c5f9fdcc60b48b0a2cb45b3356cce94..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2html/rml2html.py +++ /dev/null @@ -1,423 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import copy -import cStringIO -import sys - -from lxml import etree - -from odoo.report.render.rml2pdf import utils - -class _flowable(object): - def __init__(self, template, doc, localcontext = None): - self._tags = { - 'title': self._tag_title, - 'spacer': self._tag_spacer, - 'para': self._tag_para, - 'section':self._section, - 'nextFrame': self._tag_next_frame, - 'blockTable': self._tag_table, - 'pageBreak': self._tag_page_break, - 'setNextTemplate': self._tag_next_template, - } - self.template = template - self.doc = doc - self.localcontext = localcontext - self._cache = {} - - def _tag_page_break(self, node): - return '<br/>'*3 - - def _tag_next_template(self, node): - return '' - - def _tag_next_frame(self, node): - result=self.template.frame_stop() - result+='<br/>' - result+=self.template.frame_start() - return result - - def _tag_title(self, node): - node.tag='h1' - return etree.tostring(node) - - def _tag_spacer(self, node): - length = 1+int(utils.unit_get(node.get('length')))/35 - return "<br/>"*length - - def _tag_table(self, node): - new_node = copy.deepcopy(node) - for child in new_node: - new_node.remove(child) - new_node.tag = 'table' - def process(node,new_node): - for child in utils._child_get(node,self): - new_child = copy.deepcopy(child) - new_node.append(new_child) - if len(child): - for n in new_child: - new_child.remove(n) - process(child, new_child) - else: - new_child.text = utils._process_text(self, child.text) - new_child.tag = 'p' - try: - if new_child.get('style').find('terp_tblheader')!= -1: - new_node.tag = 'th' - except Exception: - pass - process(node,new_node) - if new_node.get('colWidths',False): - sizes = map(lambda x: utils.unit_get(x), new_node.get('colWidths').split(',')) - tr = etree.SubElement(new_node, 'tr') - for s in sizes: - etree.SubElement(tr, 'td', width=str(s)) - - return etree.tostring(new_node) - - def _tag_para(self, node): - new_node = copy.deepcopy(node) - new_node.tag = 'p' - if new_node.attrib.get('style',False): - new_node.set('class', new_node.get('style')) - new_node.text = utils._process_text(self, node.text) - return etree.tostring(new_node) - - def _section(self, node): - result = '' - for child in utils._child_get(node, self): - if child.tag in self._tags: - result += self._tags[child.tag](child) - return result - - def render(self, node): - result = self.template.start() - result += self.template.frame_start() - for n in utils._child_get(node, self): - if n.tag in self._tags: - result += self._tags[n.tag](n) - else: - pass - result += self.template.frame_stop() - result += self.template.end() - return result.encode('utf-8').replace('"',"\'").replace('°','°') - -class _rml_tmpl_tag(object): - def __init__(self, *args): - pass - def tag_start(self): - return '' - def tag_end(self): - return False - def tag_stop(self): - return '' - def tag_mergeable(self): - return True - -class _rml_tmpl_frame(_rml_tmpl_tag): - def __init__(self, posx, width): - self.width = width - self.posx = posx - def tag_start(self): - return "<table border=\'0\' width=\'%d\'><tr><td width=\'%d\'> </td><td>" % (self.width+self.posx,self.posx) - def tag_end(self): - return True - def tag_stop(self): - return '</td></tr></table><br/>' - def tag_mergeable(self): - return False - - def merge(self, frame): - pass - -class _rml_tmpl_draw_string(_rml_tmpl_tag): - def __init__(self, node, style,localcontext = {}): - self.localcontext = localcontext - self.posx = utils.unit_get(node.get('x')) - self.posy = utils.unit_get(node.get('y')) - - aligns = { - 'drawString': 'left', - 'drawRightString': 'right', - 'drawCentredString': 'center' - } - align = aligns[node.tag] - self.pos = [(self.posx, self.posy, align, utils._process_text(self, node.text), style.get('td'), style.font_size_get('td'))] - - def tag_start(self): - self.pos.sort() - res = "<table border='0' cellpadding='0' cellspacing='0'><tr>" - posx = 0 - i = 0 - for (x,y,align,txt, style, fs) in self.pos: - if align=="left": - pos2 = len(txt)*fs - res+="<td width=\'%d\'></td><td style=\'%s\' width=\'%d\'>%s</td>" % (x - posx, style, pos2, txt) - posx = x+pos2 - if align=="right": - res+="<td width=\'%d\' align=\'right\' style=\'%s\'>%s</td>" % (x - posx, style, txt) - posx = x - if align=="center": - res+="<td width=\'%d\' align=\'center\' style=\'%s\'>%s</td>" % ((x - posx)*2, style, txt) - posx = 2*x-posx - i+=1 - res+='</tr></table>' - return res - def merge(self, ds): - self.pos+=ds.pos - -class _rml_tmpl_draw_lines(_rml_tmpl_tag): - def __init__(self, node, style, localcontext = {}): - self.localcontext = localcontext - coord = [utils.unit_get(x) for x in utils._process_text(self, node.text).split(' ')] - self.ok = False - self.posx = coord[0] - self.posy = coord[1] - self.width = coord[2]-coord[0] - self.ok = coord[1]==coord[3] - self.style = style - self.style = style.get('hr') - - def tag_start(self): - if self.ok: - return "<table border=\'0\' cellpadding=\'0\' cellspacing=\'0\' width=\'%d\'><tr><td width=\'%d\'></td><td><hr width=\'100%%\' style=\'margin:0px; %s\'></td></tr></table>" % (self.posx+self.width,self.posx,self.style) - else: - return '' - -class _rml_stylesheet(object): - def __init__(self, localcontext, stylesheet, doc): - self.doc = doc - self.localcontext = localcontext - self.attrs = {} - self._tags = { - 'fontSize': lambda x: ('font-size',str(utils.unit_get(x)+5.0)+'px'), - 'alignment': lambda x: ('text-align',str(x)) - } - result = '' - for ps in stylesheet.findall('paraStyle'): - attr = {} - attrs = ps.attrib - for key, val in attrs.items(): - attr[key] = val - attrs = [] - for a in attr: - if a in self._tags: - attrs.append('%s:%s' % self._tags[a](attr[a])) - if len(attrs): - result += 'p.'+attr['name']+' {'+'; '.join(attrs)+'}\n' - self.result = result - - def render(self): - return self.result - -class _rml_draw_style(object): - def __init__(self): - self.style = {} - self._styles = { - 'fill': lambda x: {'td': {'color':x.get('color')}}, - 'setFont': lambda x: {'td': {'font-size':x.get('size')+'px'}}, - 'stroke': lambda x: {'hr': {'color':x.get('color')}}, - } - def update(self, node): - if node.tag in self._styles: - result = self._styles[node.tag](node) - for key in result: - if key in self.style: - self.style[key].update(result[key]) - else: - self.style[key] = result[key] - def font_size_get(self,tag): - size = utils.unit_get(self.style.get('td', {}).get('font-size','16')) - return size - - def get(self,tag): - if not tag in self.style: - return "" - return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()]) - -class _rml_template(object): - def __init__(self, template, localcontext=None): - self.frame_pos = -1 - self.localcontext = localcontext - self.frames = [] - self.template_order = [] - self.page_template = {} - self.loop = 0 - self._tags = { - 'drawString': _rml_tmpl_draw_string, - 'drawRightString': _rml_tmpl_draw_string, - 'drawCentredString': _rml_tmpl_draw_string, - 'lines': _rml_tmpl_draw_lines - } - self.style = _rml_draw_style() - rc = 'data:image/png;base64,' - self.data = '' - for pt in template.findall('pageTemplate'): - frames = {} - id = pt.get('id') - self.template_order.append(id) - for tmpl in pt.findall('frame'): - posy = int(utils.unit_get(tmpl.get('y1'))) - posx = int(utils.unit_get(tmpl.get('x1'))) - frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width'))) - for tmpl in pt.findall('pageGraphics'): - for n in tmpl: - if n.tag == 'image': - self.data = rc + utils._process_text(self, n.text) - if n.tag in self._tags: - t = self._tags[n.tag](n, self.style,self.localcontext) - frames[(t.posy,t.posx,n.tag)] = t - else: - self.style.update(n) - keys = frames.keys() - keys.sort() - keys.reverse() - self.page_template[id] = [] - for key in range(len(keys)): - if key>0 and keys[key-1][0] == keys[key][0]: - if type(self.page_template[id][-1]) == type(frames[keys[key]]): - if self.page_template[id][-1].tag_mergeable(): - self.page_template[id][-1].merge(frames[keys[key]]) - continue - self.page_template[id].append(frames[keys[key]]) - self.template = self.template_order[0] - - def _get_style(self): - return self.style - - def set_next_template(self): - self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order] - self.frame_pos = -1 - - def set_template(self, name): - self.template = name - self.frame_pos = -1 - - def frame_start(self): - result = '' - frames = self.page_template[self.template] - ok = True - while ok: - self.frame_pos += 1 - if self.frame_pos>=len(frames): - self.frame_pos=0 - self.loop=1 - ok = False - continue - f = frames[self.frame_pos] - result+=f.tag_start() - ok = not f.tag_end() - if ok: - result+=f.tag_stop() - return result - - def frame_stop(self): - frames = self.page_template[self.template] - f = frames[self.frame_pos] - result=f.tag_stop() - return result - - def start(self): - return '' - - def end(self): - result = '' - while not self.loop: - result += self.frame_start() - result += self.frame_stop() - return result - -class _rml_doc(object): - def __init__(self, data, localcontext): - self.dom = etree.XML(data) - self.localcontext = localcontext - self.filename = self.dom.get('filename') - self.result = '' - - def render(self, out): - self.result += '''<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 4.0 Frameset//EN"> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <style type="text/css"> - p {margin:0px; font-size:12px;} - td {font-size:14px;} -''' - style = self.dom.findall('stylesheet')[0] - s = _rml_stylesheet(self.localcontext, style, self.dom) - self.result += s.render() - self.result+=''' - </style> -''' - list_story =[] - for story in utils._child_get(self.dom, self, 'story'): - template = _rml_template(self.dom.findall('template')[0], self.localcontext) - f = _flowable(template, self.dom, localcontext = self.localcontext) - story_text = f.render(story) - list_story.append(story_text) - del f - if template.data: - tag = '''<img src = '%s' width=80 height=72/>'''% template.data - else: - tag = '' - self.result +=''' - <script type="text/javascript"> - - var indexer = 0; - var aryTest = %s ; - function nextData() - { - if(indexer < aryTest.length -1) - { - indexer += 1; - document.getElementById("tiny_data").innerHTML=aryTest[indexer]; - } - } - function prevData() - { - if (indexer > 0) - { - indexer -= 1; - document.getElementById("tiny_data").innerHTML=aryTest[indexer]; - } - } - </script> - </head> - <body> - %s - <div id="tiny_data"> - %s - </div> - <br> - <input type="button" value="next" onclick="nextData();"> - <input type="button" value="prev" onclick="prevData();"> - - </body></html>'''%(list_story,tag,list_story[0]) - out.write( self.result) - -def parseString(data,localcontext = {}, fout=None): - r = _rml_doc(data, localcontext) - if fout: - fp = file(fout,'wb') - r.render(fp) - fp.close() - return fout - else: - fp = cStringIO.StringIO() - r.render(fp) - return fp.getvalue() - -def rml2html_help(): - print 'Usage: rml2html input.rml >output.html' - print 'Render the standard input (RML) and output an HTML file' - sys.exit(0) - -if __name__=="__main__": - if len(sys.argv)>1: - if sys.argv[1]=='--help': - rml2html_help() - print parseString(file(sys.argv[1], 'r').read()), - else: - print 'Usage: rml2html input.rml >output.html' - print 'Try \'rml2html --help\' for more information.' diff --git a/odoo/report/render/rml2html/utils.py b/odoo/report/render/rml2html/utils.py deleted file mode 100644 index 5143665efbd1ce4bdfd16a44099bb690bcdcf1eb..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2html/utils.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import re -import reportlab -import reportlab.lib.units - -units = [ - (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch), - (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm), - (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm), - (re.compile('^(-?[0-9\.]+)\s*px$'), 0.7), - (re.compile('^(-?[0-9\.]+)\s*$'), 1) -] - -def unit_get(size): - global units - for unit in units: - res = unit[0].search(size, 0) - if res: - return int(unit[1]*float(res.group(1))*1.3) - return False - -def tuple_int_get(node, attr_name, default=None): - if not node.get(attr_name): - return default - res = [int(x) for x in node.get(attr_name).split(',')] - return res - -def bool_get(value): - return (str(value)=="1") or (value.lower()=='yes') - -def attr_get(node, attrs, dict=None): - if dict is None: - dict = {} - res = {} - for name in attrs: - if node.get(name): - res[name] = unit_get(node.get(name)) - for key in dict: - if node.get(key): - if dict[key]=='str': - res[key] = str(node.get(key)) - elif dict[key]=='bool': - res[key] = bool_get(node.get(key)) - elif dict[key]=='int': - res[key] = int(node.get(key)) - return res diff --git a/odoo/report/render/rml2pdf/__init__.py b/odoo/report/render/rml2pdf/__init__.py deleted file mode 100644 index 166e0f2d9a282fc8180174b5512ff93e6e038666..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2pdf/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .trml2pdf import parseString, parseNode diff --git a/odoo/report/render/rml2pdf/color.py b/odoo/report/render/rml2pdf/color.py deleted file mode 100644 index 60767cdf8abf96fb1193264907106bccee93ebb7..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2pdf/color.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import re - -from reportlab.lib import colors - -allcols = colors.getAllNamedColors() - -regex_t = re.compile('\(([0-9\.]*),([0-9\.]*),([0-9\.]*)\)') -regex_h = re.compile('#([0-9a-zA-Z][0-9a-zA-Z])([0-9a-zA-Z][0-9a-zA-Z])([0-9a-zA-Z][0-9a-zA-Z])') - -def get(col_str): - if col_str is None: - col_str = '' - global allcols - if col_str in allcols.keys(): - return allcols[col_str] - res = regex_t.search(col_str, 0) - if res: - return float(res.group(1)), float(res.group(2)), float(res.group(3)) - res = regex_h.search(col_str, 0) - if res: - return tuple([ float(int(res.group(i),16))/255 for i in range(1,4)]) - return colors.red diff --git a/odoo/report/render/rml2pdf/customfonts.py b/odoo/report/render/rml2pdf/customfonts.py deleted file mode 100644 index c34088ab0855fd957c93b8d371878bfaa515db16..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2pdf/customfonts.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import glob -import logging -import os - -from reportlab import rl_config - -# .apidoc title: TTF Font Table - -"""This module allows the mapping of some system-available TTF fonts to -the reportlab engine. - -This file could be customized per distro (although most Linux/Unix ones) -should have the same filenames, only need the code below). - -Due to an awful configuration that ships with reportlab at many Linux -and Ubuntu distros, we have to override the search path, too. -""" -_logger = logging.getLogger(__name__) - -CustomTTFonts = [] - -# Search path for TTF files, in addition of rl_config.TTFSearchPath -TTFSearchPath = [ - '/usr/share/fonts/truetype', # SuSE - '/usr/share/fonts/dejavu', '/usr/share/fonts/liberation', # Fedora, RHEL - '/usr/share/fonts/truetype/*','/usr/local/share/fonts' # Ubuntu, - '/usr/share/fonts/TTF/*', # Mandriva/Mageia - '/usr/share/fonts/TTF', # Arch Linux - '/usr/lib/openoffice/share/fonts/truetype/', - '~/.fonts', - '~/.local/share/fonts', - - # mac os X - from - # http://developer.apple.com/technotes/tn/tn2024.html - '~/Library/Fonts', - '/Library/Fonts', - '/Network/Library/Fonts', - '/System/Library/Fonts', - - # windows - 'c:/winnt/fonts', - 'c:/windows/fonts' -] - -def list_all_sysfonts(): - """ - This function returns list of font directories of system. - """ - filepath = [] - - # Perform the search for font files ourselves, as reportlab's - # TTFOpenFile is not very good at it. - searchpath = list(set(TTFSearchPath + rl_config.TTFSearchPath)) - for dirname in searchpath: - for filename in glob.glob(os.path.join(os.path.expanduser(dirname), '*.[Tt][Tt][FfCc]')): - filepath.append(filename) - return filepath - -def SetCustomFonts(rmldoc): - """ Map some font names to the corresponding TTF fonts - - The ttf font may not even have the same name, as in - Times -> Liberation Serif. - This function is called once per report, so it should - avoid system-wide processing (cache it, instead). - """ - for family, font, filename, mode in CustomTTFonts: - if os.path.isabs(filename) and os.path.exists(filename): - rmldoc.setTTFontMapping(family, font, filename, mode) - return True diff --git a/odoo/report/render/rml2pdf/trml2pdf.py b/odoo/report/render/rml2pdf/trml2pdf.py deleted file mode 100644 index 47ab8d313a568043381845c6699efecd5f82b4ca..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2pdf/trml2pdf.py +++ /dev/null @@ -1,1067 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import base64 -import copy -import logging -import os -import re -import sys -import traceback -from distutils.version import LooseVersion - -try: - from cStringIO import StringIO - _hush_pyflakes = [ StringIO ] -except ImportError: - from StringIO import StringIO - -import reportlab -from reportlab import platypus -from reportlab.lib.pagesizes import A4, letter -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfgen import canvas -from reportlab.platypus.doctemplate import ActionFlowable -from lxml import etree - -from odoo.tools.misc import file_open -from odoo.tools.safe_eval import safe_eval -from . import color -from . import utils - -try: - from .customfonts import SetCustomFonts -except ImportError: - SetCustomFonts=lambda x:None - -_logger = logging.getLogger(__name__) - -encoding = 'utf-8' - -def select_fontname(fontname, default_fontname): - if fontname not in pdfmetrics.getRegisteredFontNames()\ - or fontname not in pdfmetrics.standardFonts: - # let reportlab attempt to find it - try: - pdfmetrics.getFont(fontname) - except Exception: - addition = "" - if " " in fontname: - addition = ". Your font contains spaces which is not valid in RML." - _logger.warning('Could not locate font %s, substituting default: %s%s', - fontname, default_fontname, addition) - fontname = default_fontname - return fontname - - -def _open_image(filename, path=None): - """Attempt to open a binary file and return the descriptor - """ - if os.path.isfile(filename): - return open(filename, 'rb') - for p in (path or []): - if p and os.path.isabs(p): - fullpath = os.path.join(p, filename) - if os.path.isfile(fullpath): - return open(fullpath, 'rb') - try: - if p: - fullpath = os.path.join(p, filename) - else: - fullpath = filename - return file_open(fullpath) - except IOError: - pass - raise IOError("File %s cannot be found in image path" % filename) - -class NumberedCanvas(canvas.Canvas): - def __init__(self, *args, **kwargs): - canvas.Canvas.__init__(self, *args, **kwargs) - self._saved_page_states = [] - - def showPage(self): - self._startPage() - - def save(self): - """add page info to each page (page x of y)""" - for state in self._saved_page_states: - self.__dict__.update(state) - self.draw_page_number() - canvas.Canvas.showPage(self) - canvas.Canvas.save(self) - - def draw_page_number(self): - page_count = len(self._saved_page_states) - self.setFont("Helvetica", 8) - self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40), - " %(this)i / %(total)i" % { - 'this': self._pageNumber, - 'total': page_count, - } - ) - - -class PageCount(platypus.Flowable): - def __init__(self, story_count=0): - platypus.Flowable.__init__(self) - self.story_count = story_count - - def draw(self): - self.canv.beginForm("pageCount%d" % self.story_count) - self.canv.setFont("Helvetica", utils.unit_get(str(8))) - self.canv.drawString(0, 0, str(self.canv.getPageNumber())) - self.canv.endForm() - -class PageReset(platypus.Flowable): - def draw(self): - """Flag to close current story page numbering and prepare for the next - should be executed after the rendering of the full story""" - self.canv._doPageReset = True - -class _rml_styles(object,): - def __init__(self, nodes, localcontext): - self.localcontext = localcontext - self.styles = {} - self.styles_obj = {} - self.names = {} - self.table_styles = {} - self.default_style = reportlab.lib.styles.getSampleStyleSheet() - - for node in nodes: - for style in node.findall('blockTableStyle'): - self.table_styles[style.get('id')] = self._table_style_get(style) - for style in node.findall('paraStyle'): - sname = style.get('name') - self.styles[sname] = self._para_style_update(style) - if self.default_style.has_key(sname): - for key, value in self.styles[sname].items(): - setattr(self.default_style[sname], key, value) - else: - self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname]) - for variable in node.findall('initialize'): - for name in variable.findall('name'): - self.names[ name.get('id')] = name.get('value') - - def _para_style_update(self, node): - data = {} - for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']: - if node.get(attr): - data[attr] = color.get(node.get(attr)) - for attr in ['bulletFontName', 'fontName']: - if node.get(attr): - fontname= select_fontname(node.get(attr), None) - if fontname is not None: - data['fontName'] = fontname - for attr in ['bulletText']: - if node.get(attr): - data[attr] = node.get(attr) - for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', - 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading', - 'borderWidth','borderPadding','borderRadius']: - if node.get(attr): - data[attr] = utils.unit_get(node.get(attr)) - if node.get('alignment'): - align = { - 'right':reportlab.lib.enums.TA_RIGHT, - 'center':reportlab.lib.enums.TA_CENTER, - 'justify':reportlab.lib.enums.TA_JUSTIFY - } - data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT) - data['splitLongWords'] = 0 - return data - - def _table_style_get(self, style_node): - styles = [] - for node in style_node: - start = utils.tuple_int_get(node, 'start', (0,0) ) - stop = utils.tuple_int_get(node, 'stop', (-1,-1) ) - if node.tag=='blockValign': - styles.append(('VALIGN', start, stop, str(node.get('value')))) - elif node.tag=='blockFont': - styles.append(('FONT', start, stop, str(node.get('name')))) - elif node.tag=='blockTextColor': - styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName'))))) - elif node.tag=='blockLeading': - styles.append(('LEADING', start, stop, utils.unit_get(node.get('length')))) - elif node.tag=='blockAlignment': - styles.append(('ALIGNMENT', start, stop, str(node.get('value')))) - elif node.tag=='blockSpan': - styles.append(('SPAN', start, stop)) - elif node.tag=='blockLeftPadding': - styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length')))) - elif node.tag=='blockRightPadding': - styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length')))) - elif node.tag=='blockTopPadding': - styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length')))) - elif node.tag=='blockBottomPadding': - styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length')))) - elif node.tag=='blockBackground': - styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName')))) - if node.get('size'): - styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size')))) - elif node.tag=='lineStyle': - kind = node.get('kind') - kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ] - assert kind in kind_list - thick = 1 - if node.get('thickness'): - thick = float(node.get('thickness')) - styles.append((kind, start, stop, thick, color.get(node.get('colorName')))) - return platypus.tables.TableStyle(styles) - - def para_style_get(self, node): - style = False - sname = node.get('style') - if sname: - if sname in self.styles_obj: - style = self.styles_obj[sname] - else: - _logger.debug('Warning: style not found, %s - setting default!', node.get('style')) - if not style: - style = self.default_style['Normal'] - para_update = self._para_style_update(node) - if para_update: - # update style only is necessary - style = copy.deepcopy(style) - style.__dict__.update(para_update) - return style - -class _rml_doc(object): - def __init__(self, node, localcontext=None, images=None, path='.', title=None): - if images is None: - images = {} - if localcontext is None: - localcontext = {} - self.localcontext = localcontext - self.etree = node - self.filename = self.etree.get('filename') - self.images = images - self.path = path - self.title = title - - def docinit(self, els): - from reportlab.lib.fonts import addMapping - from reportlab.pdfbase import pdfmetrics - from reportlab.pdfbase.ttfonts import TTFont - - for node in els: - - for font in node.findall('registerFont'): - name = font.get('fontName').encode('ascii') - fname = font.get('fontFile').encode('ascii') - if name not in pdfmetrics._fonts: - pdfmetrics.registerFont(TTFont(name, fname)) - #by default, we map the fontName to each style (bold, italic, bold and italic), so that - #if there isn't any font defined for one of these style (via a font family), the system - #will fallback on the normal font. - addMapping(name, 0, 0, name) #normal - addMapping(name, 0, 1, name) #italic - addMapping(name, 1, 0, name) #bold - addMapping(name, 1, 1, name) #italic and bold - - #if registerFontFamily is defined, we register the mapping of the fontName to use for each style. - for font_family in node.findall('registerFontFamily'): - family_name = font_family.get('normal').encode('ascii') - if font_family.get('italic'): - addMapping(family_name, 0, 1, font_family.get('italic').encode('ascii')) - if font_family.get('bold'): - addMapping(family_name, 1, 0, font_family.get('bold').encode('ascii')) - if font_family.get('boldItalic'): - addMapping(family_name, 1, 1, font_family.get('boldItalic').encode('ascii')) - - def setTTFontMapping(self,face, fontname, filename, mode='all'): - from reportlab.lib.fonts import addMapping - from reportlab.pdfbase.ttfonts import TTFont - - if mode: - mode = mode.lower() - - if fontname not in pdfmetrics._fonts: - pdfmetrics.registerFont(TTFont(fontname, filename)) - if mode == 'all': - addMapping(face, 0, 0, fontname) #normal - addMapping(face, 0, 1, fontname) #italic - addMapping(face, 1, 0, fontname) #bold - addMapping(face, 1, 1, fontname) #italic and bold - elif mode in ['italic', 'oblique']: - addMapping(face, 0, 1, fontname) #italic - elif mode == 'bold': - addMapping(face, 1, 0, fontname) #bold - elif mode in ('bolditalic', 'bold italic','boldoblique', 'bold oblique'): - addMapping(face, 1, 1, fontname) #italic and bold - else: - addMapping(face, 0, 0, fontname) #normal - - def _textual_image(self, node): - rc = '' - for n in node: - rc +=( etree.tostring(n) or '') + n.tail - return base64.decodestring(node.tostring()) - - def _images(self, el): - result = {} - for node in el.findall('.//image'): - rc =( node.text or '') - result[node.get('name')] = base64.decodestring(rc) - return result - - def render(self, out): - el = self.etree.findall('.//docinit') - if el: - self.docinit(el) - - el = self.etree.findall('.//stylesheet') - self.styles = _rml_styles(el,self.localcontext) - - el = self.etree.findall('.//images') - if el: - self.images.update( self._images(el[0]) ) - - el = self.etree.findall('.//template') - if len(el): - pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title) - el = utils._child_get(self.etree, self, 'story') - pt_obj.render(el) - else: - self.canvas = canvas.Canvas(out) - pd = self.etree.find('pageDrawing')[0] - pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title) - pd_obj.render(pd) - - self.canvas.showPage() - self.canvas.save() - -class _rml_canvas(object): - def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images=None, path='.', title=None): - if images is None: - images = {} - self.localcontext = localcontext - self.canvas = canvas - self.styles = doc.styles - self.doc_tmpl = doc_tmpl - self.doc = doc - self.images = images - self.path = path - self.title = title - if self.title: - self.canvas.setTitle(self.title) - - def _textual(self, node, x=0, y=0): - text = node.text and node.text.encode('utf-8') or '' - rc = utils._process_text(self, text) - for n in node: - if n.tag == 'seq': - from reportlab.lib.sequencer import getSequencer - seq = getSequencer() - rc += str(seq.next(n.get('id'))) - if n.tag == 'pageCount': - if x or y: - self.canvas.translate(x,y) - self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,)) - if x or y: - self.canvas.translate(-x,-y) - if n.tag == 'pageNumber': - rc += str(self.canvas.getPageNumber()) - rc += utils._process_text(self, n.tail) - return rc.replace('\n','') - - def _drawString(self, node): - v = utils.attr_get(node, ['x','y']) - text=self._textual(node, **v) - text = utils.xml2str(text) - try: - self.canvas.drawString(text=text, **v) - except TypeError: - _logger.info("Bad RML: <drawString> tag requires attributes 'x' and 'y'!") - raise - - def _drawCenteredString(self, node): - v = utils.attr_get(node, ['x','y']) - text=self._textual(node, **v) - text = utils.xml2str(text) - self.canvas.drawCentredString(text=text, **v) - - def _drawRightString(self, node): - v = utils.attr_get(node, ['x','y']) - text=self._textual(node, **v) - text = utils.xml2str(text) - self.canvas.drawRightString(text=text, **v) - - def _rect(self, node): - if node.get('round'): - self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'})) - else: - self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'})) - - def _ellipse(self, node): - x1 = utils.unit_get(node.get('x')) - x2 = utils.unit_get(node.get('width')) - y1 = utils.unit_get(node.get('y')) - y2 = utils.unit_get(node.get('height')) - - self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) - - def _curves(self, node): - line_str = node.text.split() - lines = [] - while len(line_str)>7: - self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]]) - line_str = line_str[8:] - - def _lines(self, node): - line_str = node.text.split() - lines = [] - while len(line_str)>3: - lines.append([utils.unit_get(l) for l in line_str[0:4]]) - line_str = line_str[4:] - self.canvas.lines(lines) - - def _grid(self, node): - xlist = [utils.unit_get(s) for s in node.get('xs').split(',')] - ylist = [utils.unit_get(s) for s in node.get('ys').split(',')] - - self.canvas.grid(xlist, ylist) - - def _translate(self, node): - dx = utils.unit_get(node.get('dx')) or 0 - dy = utils.unit_get(node.get('dy')) or 0 - self.canvas.translate(dx,dy) - - def _circle(self, node): - self.canvas.circle(x_cen=utils.unit_get(node.get('x')), y_cen=utils.unit_get(node.get('y')), r=utils.unit_get(node.get('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) - - def _place(self, node): - flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title, canvas=self.canvas).render(node) - infos = utils.attr_get(node, ['x','y','width','height']) - - infos['y']+=infos['height'] - for flow in flows: - w,h = flow.wrap(infos['width'], infos['height']) - if w<=infos['width'] and h<=infos['height']: - infos['y']-=h - flow.drawOn(self.canvas,infos['x'],infos['y']) - infos['height']-=h - else: - raise ValueError("Not enough space") - - def _line_mode(self, node): - ljoin = {'round':1, 'mitered':0, 'bevelled':2} - lcap = {'default':0, 'round':1, 'square':2} - - if node.get('width'): - self.canvas.setLineWidth(utils.unit_get(node.get('width'))) - if node.get('join'): - self.canvas.setLineJoin(ljoin[node.get('join')]) - if node.get('cap'): - self.canvas.setLineCap(lcap[node.get('cap')]) - if node.get('miterLimit'): - self.canvas.setDash(utils.unit_get(node.get('miterLimit'))) - if node.get('dash'): - dashes = node.get('dash').split(',') - for x in range(len(dashes)): - dashes[x]=utils.unit_get(dashes[x]) - self.canvas.setDash(node.get('dash').split(',')) - - def _image(self, node): - import urllib - import urlparse - from reportlab.lib.utils import ImageReader - nfile = node.get('file') - if not nfile: - if node.get('name'): - image_data = self.images[node.get('name')] - _logger.debug("Image %s used", node.get('name')) - s = StringIO(image_data) - else: - newtext = node.text - if self.localcontext: - res = utils._regex.findall(newtext) - for key in res: - newtext = safe_eval(key, {}, self.localcontext) or '' - image_data = None - if newtext: - image_data = base64.decodestring(newtext) - if image_data: - s = StringIO(image_data) - else: - _logger.debug("No image data!") - return False - else: - if nfile in self.images: - s = StringIO(self.images[nfile]) - else: - try: - up = urlparse.urlparse(str(nfile)) - except ValueError: - up = False - if up and up.scheme: - # RFC: do we really want to open external URLs? - # Are we safe from cross-site scripting or attacks? - _logger.debug("Retrieve image from %s", nfile) - u = urllib.urlopen(str(nfile)) - s = StringIO(u.read()) - else: - _logger.debug("Open image file %s ", nfile) - s = _open_image(nfile, path=self.path) - try: - img = ImageReader(s) - (sx,sy) = img.getSize() - _logger.debug("Image is %dx%d", sx, sy) - args = { 'x': 0.0, 'y': 0.0, 'mask': 'auto'} - for tag in ('width','height','x','y'): - if node.get(tag): - args[tag] = utils.unit_get(node.get(tag)) - if ('width' in args) and (not 'height' in args): - args['height'] = sy * args['width'] / sx - elif ('height' in args) and (not 'width' in args): - args['width'] = sx * args['height'] / sy - elif ('width' in args) and ('height' in args): - if (float(args['width'])/args['height'])>(float(sx)>sy): - args['width'] = sx * args['height'] / sy - else: - args['height'] = sy * args['width'] / sx - self.canvas.drawImage(img, **args) - finally: - s.close() -# self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas) - - def _path(self, node): - self.path = self.canvas.beginPath() - self.path.moveTo(**utils.attr_get(node, ['x','y'])) - for n in utils._child_get(node, self): - if not n.text : - if n.tag=='moveto': - vals = utils.text_get(n).split() - self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1])) - elif n.tag=='curvesto': - vals = utils.text_get(n).split() - while len(vals)>5: - pos=[] - while len(pos)<6: - pos.append(utils.unit_get(vals.pop(0))) - self.path.curveTo(*pos) - elif n.text: - data = n.text.split() # Not sure if I must merge all TEXT_NODE ? - while len(data)>1: - x = utils.unit_get(data.pop(0)) - y = utils.unit_get(data.pop(0)) - self.path.lineTo(x,y) - if (not node.get('close')) or utils.bool_get(node.get('close')): - self.path.close() - self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) - - def setFont(self, node): - fontname = select_fontname(node.get('name'), self.canvas._fontname) - return self.canvas.setFont(fontname, utils.unit_get(node.get('size'))) - - def render(self, node): - tags = { - 'drawCentredString': self._drawCenteredString, - 'drawRightString': self._drawRightString, - 'drawString': self._drawString, - 'rect': self._rect, - 'ellipse': self._ellipse, - 'lines': self._lines, - 'grid': self._grid, - 'curves': self._curves, - 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))), - 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))), - 'setFont': self.setFont , - 'place': self._place, - 'circle': self._circle, - 'lineMode': self._line_mode, - 'path': self._path, - 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))), - 'translate': self._translate, - 'image': self._image - } - for n in utils._child_get(node, self): - if n.tag in tags: - tags[n.tag](n) - -class _rml_draw(object): - def __init__(self, localcontext, node, styles, images=None, path='.', title=None): - if images is None: - images = {} - self.localcontext = localcontext - self.node = node - self.styles = styles - self.canvas = None - self.images = images - self.path = path - self.canvas_title = title - - def render(self, canvas, doc): - canvas.saveState() - cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title) - cnv.render(self.node) - canvas.restoreState() - -class _rml_Illustration(platypus.flowables.Flowable): - def __init__(self, node, localcontext, styles, self2): - self.localcontext = (localcontext or {}).copy() - self.node = node - self.styles = styles - self.width = utils.unit_get(node.get('width')) - self.height = utils.unit_get(node.get('height')) - self.self2 = self2 - def wrap(self, *args): - return self.width, self.height - def draw(self): - drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title) - drw.render(self.canv, None) - -# Workaround for issue #15: https://bitbucket.org/rptlab/reportlab/issue/15/infinite-pages-produced-when-splitting -original_pto_split = platypus.flowables.PTOContainer.split -def split(self, availWidth, availHeight): - res = original_pto_split(self, availWidth, availHeight) - if len(res) > 2 and len(self._content) > 0: - header = self._content[0]._ptoinfo.header - trailer = self._content[0]._ptoinfo.trailer - if isinstance(res[-2], platypus.flowables.UseUpSpace) and len(header + trailer) == len(res[:-2]): - return [] - return res -platypus.flowables.PTOContainer.split = split - -class _rml_flowable(object): - def __init__(self, doc, localcontext, images=None, path='.', title=None, canvas=None): - if images is None: - images = {} - self.localcontext = localcontext - self.doc = doc - self.styles = doc.styles - self.images = images - self.path = path - self.title = title - self.canvas = canvas - - def _textual(self, node): - rc1 = utils._process_text(self, node.text or '') - for n in utils._child_get(node,self): - txt_n = copy.deepcopy(n) - for key in txt_n.attrib.keys(): - if key in ('rml_except', 'rml_loop', 'rml_tag'): - del txt_n.attrib[key] - if not n.tag == 'bullet': - if n.tag == 'pageNumber': - txt_n.text = self.canvas and str(self.canvas.getPageNumber()) or '' - else: - txt_n.text = utils.xml2str(self._textual(n)) - txt_n.tail = n.tail and utils.xml2str(utils._process_text(self, n.tail.replace('\n',''))) or '' - rc1 += etree.tostring(txt_n) - return rc1 - - def _table(self, node): - children = utils._child_get(node,self,'tr') - if not children: - return None - length = 0 - colwidths = None - rowheights = None - data = [] - styles = [] - posy = 0 - for tr in children: - paraStyle = None - if tr.get('style'): - st = copy.deepcopy(self.styles.table_styles[tr.get('style')]) - for si in range(len(st._cmds)): - s = list(st._cmds[si]) - s[1] = (s[1][0],posy) - s[2] = (s[2][0],posy) - st._cmds[si] = tuple(s) - styles.append(st) - if tr.get('paraStyle'): - paraStyle = self.styles.styles[tr.get('paraStyle')] - data2 = [] - posx = 0 - for td in utils._child_get(tr, self,'td'): - if td.get('style'): - st = copy.deepcopy(self.styles.table_styles[td.get('style')]) - for s in st._cmds: - s[1][1] = posy - s[2][1] = posy - s[1][0] = posx - s[2][0] = posx - styles.append(st) - if td.get('paraStyle'): - # TODO: merge styles - paraStyle = self.styles.styles[td.get('paraStyle')] - posx += 1 - - flow = [] - for n in utils._child_get(td, self): - if n.tag == etree.Comment: - n.text = '' - continue - fl = self._flowable(n, extra_style=paraStyle) - if isinstance(fl,list): - flow += fl - else: - flow.append( fl ) - - if not len(flow): - flow = self._textual(td) - data2.append( flow ) - if len(data2)>length: - length=len(data2) - for ab in data: - while len(ab)<length: - ab.append('') - while len(data2)<length: - data2.append('') - data.append( data2 ) - posy += 1 - - if node.get('colWidths'): - assert length == len(node.get('colWidths').split(',')) - colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')] - if node.get('rowHeights'): - rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')] - if len(rowheights) == 1: - rowheights = rowheights[0] - table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'}))) - if node.get('style'): - table.setStyle(self.styles.table_styles[node.get('style')]) - for s in styles: - table.setStyle(s) - return table - - def _illustration(self, node): - return _rml_Illustration(node, self.localcontext, self.styles, self) - - def _textual_image(self, node): - return base64.decodestring(node.text) - - def _pto(self, node): - sub_story = [] - pto_header = None - pto_trailer = None - for node in utils._child_get(node, self): - if node.tag == etree.Comment: - node.text = '' - continue - elif node.tag=='pto_header': - pto_header = self.render(node) - elif node.tag=='pto_trailer': - pto_trailer = self.render(node) - else: - flow = self._flowable(node) - if flow: - if isinstance(flow,list): - sub_story = sub_story + flow - else: - sub_story.append(flow) - return platypus.flowables.PTOContainer(sub_story, trailer=pto_trailer, header=pto_header) - - def _flowable(self, node, extra_style=None): - if node.tag=='pto': - return self._pto(node) - if node.tag=='para': - style = self.styles.para_style_get(node) - if extra_style: - style.__dict__.update(extra_style) - text_node = self._textual(node).strip().replace('\n\n', '\n').replace('\n', '<br/>') - instance = platypus.Paragraph(text_node, style, **(utils.attr_get(node, [], {'bulletText':'str'}))) - result = [instance] - if LooseVersion(reportlab.Version) > LooseVersion('3.0') and not instance.getPlainText().strip() and instance.text.strip(): - result.append(platypus.Paragraph(' <br/>', style, **(utils.attr_get(node, [], {'bulletText': 'str'})))) - return result - elif node.tag=='barCode': - try: - from reportlab.graphics.barcode import code128 - from reportlab.graphics.barcode import code39 - from reportlab.graphics.barcode import code93 - from reportlab.graphics.barcode import common - from reportlab.graphics.barcode import fourstate - from reportlab.graphics.barcode import usps - from reportlab.graphics.barcode import createBarcodeDrawing - - except ImportError: - _logger.warning("Cannot use barcode renderers:", exc_info=True) - return None - args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'int','quiet':'int','width':'unit','stop':'bool','bearers':'int','barWidth':'float','barHeight':'float'}) - codes = { - 'codabar': lambda x: common.Codabar(x, **args), - 'code11': lambda x: common.Code11(x, **args), - 'code128': lambda x: code128.Code128(str(x), **args), - 'standard39': lambda x: code39.Standard39(str(x), **args), - 'standard93': lambda x: code93.Standard93(str(x), **args), - 'i2of5': lambda x: common.I2of5(x, **args), - 'extended39': lambda x: code39.Extended39(str(x), **args), - 'extended93': lambda x: code93.Extended93(str(x), **args), - 'msi': lambda x: common.MSI(x, **args), - 'fim': lambda x: usps.FIM(x, **args), - 'postnet': lambda x: usps.POSTNET(x, **args), - 'ean13': lambda x: createBarcodeDrawing('EAN13', value=str(x), **args), - 'qrcode': lambda x: createBarcodeDrawing('QR', value=x, **args), - } - code = 'code128' - if node.get('code'): - code = node.get('code').lower() - return codes[code](self._textual(node)) - elif node.tag=='name': - self.styles.names[ node.get('id')] = node.get('value') - return None - elif node.tag=='xpre': - style = self.styles.para_style_get(node) - return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'}))) - elif node.tag=='pre': - style = self.styles.para_style_get(node) - return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'}))) - elif node.tag=='illustration': - return self._illustration(node) - elif node.tag=='blockTable': - return self._table(node) - elif node.tag=='title': - styles = reportlab.lib.styles.getSampleStyleSheet() - style = styles['Title'] - return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) - elif re.match('^h([1-9]+[0-9]*)$', (node.tag or '')): - styles = reportlab.lib.styles.getSampleStyleSheet() - style = styles['Heading'+str(node.tag[1:])] - return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) - elif node.tag=='image': - image_data = False - if not node.get('file'): - if node.get('name'): - if node.get('name') in self.doc.images: - _logger.debug("Image %s read ", node.get('name')) - image_data = self.doc.images[node.get('name')].read() - else: - _logger.warning("Image %s not defined", node.get('name')) - return False - else: - import base64 - newtext = node.text - if self.localcontext: - newtext = utils._process_text(self, node.text or '') - image_data = base64.decodestring(newtext) - if not image_data: - _logger.debug("No inline image data") - return False - image = StringIO(image_data) - else: - _logger.debug("Image get from file %s", node.get('file')) - image = _open_image(node.get('file'), path=self.doc.path) - return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) - elif node.tag=='spacer': - if node.get('width'): - width = utils.unit_get(node.get('width')) - else: - width = utils.unit_get('1cm') - length = utils.unit_get(node.get('length')) - return platypus.Spacer(width=width, height=length) - elif node.tag=='section': - return self.render(node) - elif node.tag == 'pageNumberReset': - return PageReset() - elif node.tag in ('pageBreak', 'nextPage'): - return platypus.PageBreak() - elif node.tag=='condPageBreak': - return platypus.CondPageBreak(**(utils.attr_get(node, ['height']))) - elif node.tag=='setNextTemplate': - return platypus.NextPageTemplate(str(node.get('name'))) - elif node.tag=='nextFrame': - return platypus.CondPageBreak(1000) # TODO: change the 1000 ! - elif node.tag == 'setNextFrame': - from reportlab.platypus.doctemplate import NextFrameFlowable - return NextFrameFlowable(str(node.get('name'))) - elif node.tag == 'currentFrame': - from reportlab.platypus.doctemplate import CurrentFrameFlowable - return CurrentFrameFlowable(str(node.get('name'))) - elif node.tag == 'frameEnd': - return EndFrameFlowable() - elif node.tag == 'hr': - width_hr=node.get('width') or '100%' - color_hr=node.get('color') or 'black' - thickness_hr=node.get('thickness') or 1 - lineCap_hr=node.get('lineCap') or 'round' - return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr)) - else: - sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,)) - return None - - def render(self, node_story): - def process_story(node_story): - sub_story = [] - for node in utils._child_get(node_story, self): - if node.tag == etree.Comment: - node.text = '' - continue - flow = self._flowable(node) - if flow: - if isinstance(flow,list): - sub_story = sub_story + flow - else: - sub_story.append(flow) - return sub_story - return process_story(node_story) - - -class EndFrameFlowable(ActionFlowable): - def __init__(self,resume=0): - ActionFlowable.__init__(self,('frameEnd',resume)) - -class TinyDocTemplate(platypus.BaseDocTemplate): - - def beforeDocument(self): - # Store some useful value directly inside canvas, so it's available - # on flowable drawing (needed for proper PageCount handling) - self.canv._doPageReset = False - self.canv._storyCount = 0 - - def ___handle_pageBegin(self): - self.page += 1 - self.pageTemplate.beforeDrawPage(self.canv,self) - self.pageTemplate.checkPageSize(self.canv,self) - self.pageTemplate.onPage(self.canv,self) - for f in self.pageTemplate.frames: f._reset() - self.beforePage() - self._curPageFlowableCount = 0 - if hasattr(self,'_nextFrameIndex'): - del self._nextFrameIndex - for f in self.pageTemplate.frames: - if f.id == 'first': - self.frame = f - break - self.handle_frameBegin() - - def afterPage(self): - if isinstance(self.canv, NumberedCanvas): - # save current page states before eventual reset - self.canv._saved_page_states.append(dict(self.canv.__dict__)) - if self.canv._doPageReset: - # Following a <pageReset/> tag: - # - we reset page number to 0 - # - we add an new PageCount flowable (relative to the current - # story number), but not for NumeredCanvas at is handle page - # count itself) - # NOTE: _rml_template render() method add a PageReset flowable at end - # of each story, so we're sure to pass here at least once per story. - if not isinstance(self.canv, NumberedCanvas): - self.handle_flowable([ PageCount(story_count=self.canv._storyCount) ]) - self.canv._pageCount = self.page - self.page = 0 - self.canv._flag = True - self.canv._pageNumber = 0 - self.canv._doPageReset = False - self.canv._storyCount += 1 - -class _rml_template(object): - def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None): - if images is None: - images = {} - if not localcontext: - localcontext={'internal_header':True} - self.localcontext = localcontext - self.images= images - self.path = path - self.title = title - - pagesize_map = {'a4': A4, - 'us_letter': letter - } - pageSize = A4 - if self.localcontext.get('company'): - pageSize = pagesize_map.get(self.localcontext.get('company').rml_paper_format, A4) - if node.get('pageSize'): - ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(',')) - pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) ) - - self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','rotation':'int','title':'str','author':'str'})) - self.page_templates = [] - self.styles = doc.styles - self.doc = doc - self.image=[] - pts = node.findall('pageTemplate') - for pt in pts: - frames = [] - for frame_el in pt.findall('frame'): - frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) ) - if utils.attr_get(frame_el, ['last']): - frame.lastFrame = True - frames.append( frame ) - try : - gr = pt.findall('pageGraphics')\ - or pt[1].findall('pageGraphics') - except Exception: # FIXME: be even more specific, perhaps? - gr='' - if len(gr): -# self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext] - drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title) - self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) )) - else: - drw = _rml_draw(self.localcontext,node,self.doc,title=self.title) - self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) )) - self.doc_tmpl.addPageTemplates(self.page_templates) - - def render(self, node_stories): - if self.localcontext and not self.localcontext.get('internal_header',False): - del self.localcontext['internal_header'] - fis = [] - r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title, canvas=None) - story_cnt = 0 - for node_story in node_stories: - if story_cnt > 0: - fis.append(platypus.PageBreak()) - fis += r.render(node_story) - # end of story numbering computation - fis.append(PageReset()) - story_cnt += 1 - try: - if self.localcontext and self.localcontext.get('internal_header',False): - self.doc_tmpl.afterFlowable(fis) - self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) - else: - self.doc_tmpl.build(fis) - except platypus.doctemplate.LayoutError as e: - e.name = 'Print Error' - e.value = 'The document you are trying to print contains a table row that does not fit on one page. Please try to split it in smaller rows or contact your administrator.' - raise - -def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None): - node = etree.XML(rml) - r = _rml_doc(node, localcontext, images, path, title=title) - #try to override some font mappings - try: - SetCustomFonts(r) - except Exception as exc: - _logger.info('Cannot set font mapping: %s', "".join(traceback.format_exception_only(type(exc),exc))) - fp = StringIO() - r.render(fp) - return fp.getvalue() - -def parseString(rml, localcontext=None, fout=None, images=None, path='.', title=None): - node = etree.XML(rml) - r = _rml_doc(node, localcontext, images, path, title=title) - - #try to override some font mappings - try: - SetCustomFonts(r) - except Exception: - pass - - if fout: - fp = file(fout,'wb') - r.render(fp) - fp.close() - return fout - else: - fp = StringIO() - r.render(fp) - return fp.getvalue() - -def trml2pdf_help(): - print 'Usage: trml2pdf input.rml >output.pdf' - print 'Render the standard input (RML) and output a PDF file' - sys.exit(0) - -if __name__=="__main__": - if len(sys.argv)>1: - if sys.argv[1]=='--help': - trml2pdf_help() - print parseString(file(sys.argv[1], 'r').read()), - else: - print 'Usage: trml2pdf input.rml >output.pdf' - print 'Try \'trml2pdf --help\' for more information.' diff --git a/odoo/report/render/rml2pdf/utils.py b/odoo/report/render/rml2pdf/utils.py deleted file mode 100644 index 785e12fdc41f58eb7ef9d38f0e1de4cece1061fe..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2pdf/utils.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import copy -import locale -import logging -import re - -import reportlab - -import odoo.tools as tools -from odoo.tools.safe_eval import safe_eval -from odoo.tools.misc import ustr - -_logger = logging.getLogger(__name__) - -_regex = re.compile('\[\[(.+?)\]\]') - -def str2xml(s): - return (s or '').replace('&', '&').replace('<', '<').replace('>', '>') - -def xml2str(s): - return (s or '').replace('&','&').replace('<','<').replace('>','>') - -def _child_get(node, self=None, tagname=None): - for n in node: - if self and self.localcontext and n.get('rml_loop'): - - for ctx in safe_eval(n.get('rml_loop'),{}, self.localcontext): - self.localcontext.update(ctx) - if (tagname is None) or (n.tag==tagname): - if n.get('rml_except', False): - try: - safe_eval(n.get('rml_except'), {}, self.localcontext) - except GeneratorExit: - continue - except Exception as e: - _logger.info('rml_except: "%s"', n.get('rml_except',''), exc_info=True) - continue - if n.get('rml_tag'): - try: - (tag,attr) = safe_eval(n.get('rml_tag'),{}, self.localcontext) - n2 = copy.deepcopy(n) - n2.tag = tag - n2.attrib.update(attr) - yield n2 - except GeneratorExit: - yield n - except Exception as e: - _logger.info('rml_tag: "%s"', n.get('rml_tag',''), exc_info=True) - yield n - else: - yield n - continue - if self and self.localcontext and n.get('rml_except'): - try: - safe_eval(n.get('rml_except'), {}, self.localcontext) - except GeneratorExit: - continue - except Exception as e: - _logger.info('rml_except: "%s"', n.get('rml_except',''), exc_info=True) - continue - if self and self.localcontext and n.get('rml_tag'): - try: - (tag,attr) = safe_eval(n.get('rml_tag'),{}, self.localcontext) - n2 = copy.deepcopy(n) - n2.tag = tag - n2.attrib.update(attr or {}) - yield n2 - tagname = '' - except GeneratorExit: - pass - except Exception as e: - _logger.info('rml_tag: "%s"', n.get('rml_tag',''), exc_info=True) - pass - if (tagname is None) or (n.tag==tagname): - yield n - -def _process_text(self, txt): - """Translate ``txt`` according to the language in the local context, - replace dynamic ``[[expr]]`` with their real value, then escape - the result for XML. - - :param str txt: original text to translate (must NOT be XML-escaped) - :return: translated text, with dynamic expressions evaluated and - with special XML characters escaped (``&,<,>``). - """ - if not self.localcontext: - return str2xml(txt) - if not txt: - return '' - result = '' - sps = _regex.split(txt) - while sps: - # This is a simple text to translate - to_translate = tools.ustr(sps.pop(0)) - result += tools.ustr(self.localcontext.get('translate', lambda x:x)(to_translate)) - if sps: - txt = None - try: - expr = sps.pop(0) - txt = safe_eval(expr, self.localcontext) - if txt and isinstance(txt, basestring): - txt = tools.ustr(txt) - except Exception: - _logger.info("Failed to evaluate expression [[ %s ]] with context %r while rendering report, ignored.", expr, self.localcontext) - if isinstance(txt, basestring): - result += txt - elif txt and (txt is not None) and (txt is not False): - result += ustr(txt) - return str2xml(result) - -def text_get(node): - return ''.join([ustr(n.text) for n in node]) - -units = [ - (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch), - (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm), - (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm), - (re.compile('^(-?[0-9\.]+)\s*$'), 1) -] - -def unit_get(size): - global units - if size: - if size.find('.') == -1: - decimal_point = '.' - try: - decimal_point = locale.nl_langinfo(locale.RADIXCHAR) - except Exception: - decimal_point = locale.localeconv()['decimal_point'] - - size = size.replace(decimal_point, '.') - - for unit in units: - res = unit[0].search(size, 0) - if res: - return unit[1]*float(res.group(1)) - return False - -def tuple_int_get(node, attr_name, default=None): - if not node.get(attr_name): - return default - return map(int, node.get(attr_name).split(',')) - -def bool_get(value): - return (str(value)=="1") or (value.lower()=='yes') - -def attr_get(node, attrs, dict=None): - if dict is None: - dict = {} - res = {} - for name in attrs: - if node.get(name): - res[name] = unit_get(node.get(name)) - for key in dict: - if node.get(key): - if dict[key]=='str': - res[key] = tools.ustr(node.get(key)) - elif dict[key]=='bool': - res[key] = bool_get(node.get(key)) - elif dict[key]=='int': - res[key] = int(node.get(key)) - elif dict[key]=='unit': - res[key] = unit_get(node.get(key)) - elif dict[key] == 'float' : - res[key] = float(node.get(key)) - return res diff --git a/odoo/report/render/rml2txt/__init__.py b/odoo/report/render/rml2txt/__init__.py deleted file mode 100644 index 0eccc05611119d169a79b42fd1a25bfb15354c90..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2txt/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from .rml2txt import parseString, parseNode - - -""" This engine is the minimalistic renderer of RML documents into text files, - using spaces and newlines to format. - - It was needed in some special applications, where legal reports need to be - printed in special (dot-matrix) printers. -""" diff --git a/odoo/report/render/rml2txt/rml2txt.py b/odoo/report/render/rml2txt/rml2txt.py deleted file mode 100755 index 3a401918ea37014201de5c5db54d6574c9ddd172..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2txt/rml2txt.py +++ /dev/null @@ -1,484 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import sys -import StringIO - -from lxml import etree - -from . import utils - -Font_size= 10.0 - -def verbose(text): - sys.stderr.write(text+"\n") - - -class textbox(object): - """A box containing plain text. - It can have an offset, in chars. - Lines can be either text strings, or textbox'es, recursively. - """ - def __init__(self,x=0, y=0): - self.posx = x - self.posy = y - self.lines = [] - self.curline = '' - self.endspace = False - - def newline(self): - if isinstance(self.curline, textbox): - self.lines.extend(self.curline.renderlines()) - else: - self.lines.append(self.curline) - self.curline = '' - - def fline(self): - if isinstance(self.curline, textbox): - self.lines.extend(self.curline.renderlines()) - elif len(self.curline): - self.lines.append(self.curline) - self.curline = '' - - def appendtxt(self,txt): - """Append some text to the current line. - Mimic the HTML behaviour, where all whitespace evaluates to - a single space """ - if not txt: - return - bs = es = False - if txt[0].isspace(): - bs = True - if txt[len(txt)-1].isspace(): - es = True - if bs and not self.endspace: - self.curline += " " - self.curline += txt.strip().replace("\n"," ").replace("\t"," ") - if es: - self.curline += " " - self.endspace = es - - def rendertxt(self,xoffset=0): - result = '' - lineoff = "" - for i in range(self.posy): - result +="\n" - for i in range(self.posx+xoffset): - lineoff+=" " - for l in self.lines: - result+= lineoff+ l +"\n" - return result - - def renderlines(self,pad=0): - """Returns a list of lines, from the current object - pad: all lines must be at least pad characters. - """ - result = [] - lineoff = "" - for i in range(self.posx): - lineoff+=" " - for l in self.lines: - lpad = "" - if pad and len(l) < pad : - for i in range(pad - len(l)): - lpad += " " - #elif pad and len(l) > pad ? - result.append(lineoff+ l+lpad) - return result - - - def haplines(self,arr,offset,cc= ''): - """ Horizontaly append lines - """ - while len(self.lines) < len(arr): - self.lines.append("") - - for i in range(len(self.lines)): - while len(self.lines[i]) < offset: - self.lines[i] += " " - for i in range(len(arr)): - self.lines[i] += cc +arr[i] - - -class _flowable(object): - def __init__(self, template, doc,localcontext): - self._tags = { - '1title': self._tag_title, - '1spacer': self._tag_spacer, - 'para': self._tag_para, - 'font': self._tag_font, - 'section': self._tag_section, - '1nextFrame': self._tag_next_frame, - 'blockTable': self._tag_table, - '1pageBreak': self._tag_page_break, - '1setNextTemplate': self._tag_next_template, - } - self.template = template - self.doc = doc - self.localcontext = localcontext - self.nitags = [] - self.tbox = None - - def warn_nitag(self,tag): - if tag not in self.nitags: - verbose("Unknown tag \"%s\", please implement it." % tag) - self.nitags.append(tag) - - def _tag_page_break(self, node): - return "\f" - - def _tag_next_template(self, node): - return '' - - def _tag_next_frame(self, node): - result=self.template.frame_stop() - result+='\n' - result+=self.template.frame_start() - return result - - def _tag_title(self, node): - node.tagName='h1' - return node.toxml() - - def _tag_spacer(self, node): - length = 1+int(utils.unit_get(node.get('length')))/35 - return "\n"*length - - def _tag_table(self, node): - self.tb.fline() - saved_tb = self.tb - self.tb = None - sizes = None - if node.get('colWidths'): - sizes = map(lambda x: utils.unit_get(x), node.get('colWidths').split(',')) - trs = [] - for n in utils._child_get(node,self): - if n.tag == 'tr': - tds = [] - for m in utils._child_get(n,self): - if m.tag == 'td': - self.tb = textbox() - self.rec_render_cnodes(m) - tds.append(self.tb) - self.tb = None - if len(tds): - trs.append(tds) - - if not sizes: - verbose("computing table sizes..") - for tds in trs: - trt = textbox() - off=0 - for i in range(len(tds)): - p = int(sizes[i]/Font_size) - trl = tds[i].renderlines(pad=p) - trt.haplines(trl,off) - off += sizes[i]/Font_size - saved_tb.curline = trt - saved_tb.fline() - - self.tb = saved_tb - return - - def _tag_para(self, node): - #TODO: styles - self.rec_render_cnodes(node) - self.tb.newline() - - def _tag_section(self, node): - #TODO: styles - self.rec_render_cnodes(node) - self.tb.newline() - - def _tag_font(self, node): - """We do ignore fonts..""" - self.rec_render_cnodes(node) - - def rec_render_cnodes(self,node): - self.tb.appendtxt(utils._process_text(self, node.text or '')) - for n in utils._child_get(node,self): - self.rec_render(n) - self.tb.appendtxt(utils._process_text(self, node.tail or '')) - - def rec_render(self,node): - """ Recursive render: fill outarr with text of current node - """ - if node.tag is not None: - if node.tag in self._tags: - self._tags[node.tag](node) - else: - self.warn_nitag(node.tag) - - def render(self, node): - self.tb= textbox() - #result = self.template.start() - #result += self.template.frame_start() - self.rec_render_cnodes(node) - #result += self.template.frame_stop() - #result += self.template.end() - result = self.tb.rendertxt() - del self.tb - return result - -class _rml_tmpl_tag(object): - def __init__(self, *args): - pass - def tag_start(self): - return '' - def tag_end(self): - return False - def tag_stop(self): - return '' - def tag_mergeable(self): - return True - -class _rml_tmpl_frame(_rml_tmpl_tag): - def __init__(self, posx, width): - self.width = width - self.posx = posx - def tag_start(self): - return "frame start" - def tag_end(self): - return True - def tag_stop(self): - return "frame stop" - def tag_mergeable(self): - return False - - # An awfull workaround since I don't really understand the semantic behind merge. - def merge(self, frame): - pass - -class _rml_tmpl_draw_string(_rml_tmpl_tag): - def __init__(self, node, style): - self.posx = utils.unit_get(node.get('x')) - self.posy = utils.unit_get(node.get('y')) - aligns = { - 'drawString': 'left', - 'drawRightString': 'right', - 'drawCentredString': 'center' - } - align = aligns[node.localName] - self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))] - - def tag_start(self): - return "draw string \"%s\" @(%d,%d)..\n" %("txt",self.posx,self.posy) - - def merge(self, ds): - self.pos+=ds.pos - -class _rml_tmpl_draw_lines(_rml_tmpl_tag): - def __init__(self, node, style): - coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')] - self.ok = False - self.posx = coord[0] - self.posy = coord[1] - self.width = coord[2]-coord[0] - self.ok = coord[1]==coord[3] - self.style = style - self.style = style.get('hr') - - def tag_start(self): - return "draw lines..\n" - -class _rml_stylesheet(object): - def __init__(self, stylesheet, doc): - self.doc = doc - self.attrs = {} - self._tags = { - 'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'), - 'alignment': lambda x: ('text-align',str(x)) - } - result = '' - for ps in stylesheet.findall('paraStyle'): - attr = {} - attrs = ps.attributes - for i in range(attrs.length): - name = attrs.item(i).localName - attr[name] = ps.get(name) - attrs = [] - for a in attr: - if a in self._tags: - attrs.append("%s:%s" % self._tags[a](attr[a])) - if len(attrs): - result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n" - self.result = result - - def render(self): - return '' - -class _rml_draw_style(object): - def __init__(self): - self.style = {} - self._styles = { - 'fill': lambda x: {'td': {'color':x.get('color')}}, - 'setFont': lambda x: {'td': {'font-size':x.get('size')+'px'}}, - 'stroke': lambda x: {'hr': {'color':x.get('color')}}, - } - def update(self, node): - if node.localName in self._styles: - result = self._styles[node.localName](node) - for key in result: - if key in self.style: - self.style[key].update(result[key]) - else: - self.style[key] = result[key] - def font_size_get(self,tag): - size = utils.unit_get(self.style.get('td', {}).get('font-size','16')) - return size - - def get(self,tag): - if not tag in self.style: - return "" - return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()]) - -class _rml_template(object): - def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None): - self.localcontext = localcontext - self.frame_pos = -1 - self.frames = [] - self.template_order = [] - self.page_template = {} - self.loop = 0 - self._tags = { - 'drawString': _rml_tmpl_draw_string, - 'drawRightString': _rml_tmpl_draw_string, - 'drawCentredString': _rml_tmpl_draw_string, - 'lines': _rml_tmpl_draw_lines - } - self.style = _rml_draw_style() - for pt in node.findall('pageTemplate'): - frames = {} - id = pt.get('id') - self.template_order.append(id) - for tmpl in pt.findall('frame'): - posy = int(utils.unit_get(tmpl.get('y1'))) #+utils.unit_get(tmpl.get('height'))) - posx = int(utils.unit_get(tmpl.get('x1'))) - frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width'))) - for tmpl in node.findall('pageGraphics'): - for n in tmpl.getchildren(): - if n.nodeType==n.ELEMENT_NODE: - if n.localName in self._tags: - t = self._tags[n.localName](n, self.style) - frames[(t.posy,t.posx,n.localName)] = t - else: - self.style.update(n) - keys = frames.keys() - keys.sort() - keys.reverse() - self.page_template[id] = [] - for key in range(len(keys)): - if key>0 and keys[key-1][0] == keys[key][0]: - if type(self.page_template[id][-1]) == type(frames[keys[key]]): - if self.page_template[id][-1].tag_mergeable(): - self.page_template[id][-1].merge(frames[keys[key]]) - continue - self.page_template[id].append(frames[keys[key]]) - self.template = self.template_order[0] - - def _get_style(self): - return self.style - - def set_next_template(self): - self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order] - self.frame_pos = -1 - - def set_template(self, name): - self.template = name - self.frame_pos = -1 - - def frame_start(self): - result = '' - frames = self.page_template[self.template] - ok = True - while ok: - self.frame_pos += 1 - if self.frame_pos>=len(frames): - self.frame_pos=0 - self.loop=1 - ok = False - continue - f = frames[self.frame_pos] - result+=f.tag_start() - ok = not f.tag_end() - if ok: - result+=f.tag_stop() - return result - - def frame_stop(self): - frames = self.page_template[self.template] - f = frames[self.frame_pos] - result=f.tag_stop() - return result - - def start(self): - return '' - - def end(self): - return "template end\n" - -class _rml_doc(object): - def __init__(self, node, localcontext=None, images=None, path='.', title=None): - self.localcontext = {} if localcontext is None else localcontext - self.etree = node - self.filename = self.etree.get('filename') - self.result = '' - - def render(self, out): - #el = self.etree.findall('docinit') - #if el: - #self.docinit(el) - - #el = self.etree.findall('stylesheet') - #self.styles = _rml_styles(el,self.localcontext) - - el = self.etree.findall('template') - self.result ="" - if len(el): - pt_obj = _rml_template(self.localcontext, out, el[0], self) - stories = utils._child_get(self.etree, self, 'story') - for story in stories: - if self.result: - self.result += '\f' - f = _flowable(pt_obj,story,self.localcontext) - self.result += f.render(story) - del f - else: - self.result = "<cannot render w/o template>" - self.result += '\n' - out.write( self.result) - -def parseNode(rml, localcontext=None,fout=None, images=None, path='.',title=None): - node = etree.XML(rml) - r = _rml_doc(node, localcontext, images, path, title=title) - fp = StringIO.StringIO() - r.render(fp) - return fp.getvalue() - -def parseString(rml, localcontext=None,fout=None, images=None, path='.',title=None): - node = etree.XML(rml) - r = _rml_doc(node, localcontext, images, path, title=title) - if fout: - fp = file(fout,'wb') - r.render(fp) - fp.close() - return fout - else: - fp = StringIO.StringIO() - r.render(fp) - return fp.getvalue() - -def trml2pdf_help(): - print 'Usage: rml2txt input.rml >output.html' - print 'Render the standard input (RML) and output an TXT file' - sys.exit(0) - -if __name__=="__main__": - if len(sys.argv)>1: - if sys.argv[1]=='--help': - trml2pdf_help() - print parseString(file(sys.argv[1], 'r').read()).encode('iso8859-7') - else: - print 'Usage: trml2txt input.rml >output.pdf' - print 'Try \'trml2txt --help\' for more information.' diff --git a/odoo/report/render/rml2txt/utils.py b/odoo/report/render/rml2txt/utils.py deleted file mode 100644 index d86380ccfbb0d18198a37cedbe3378a224335fd8..0000000000000000000000000000000000000000 --- a/odoo/report/render/rml2txt/utils.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import copy -import re - -import reportlab -import reportlab.lib.units - -from odoo.tools.safe_eval import safe_eval - -_regex = re.compile('\[\[(.+?)\]\]') - -def _child_get(node, self=None, tagname=None): - for n in node: - if self and self.localcontext and n.get('rml_loop', False): - oldctx = self.localcontext - for ctx in safe_eval(n.get('rml_loop'),{}, self.localcontext): - self.localcontext.update(ctx) - if (tagname is None) or (n.tag==tagname): - if n.get('rml_except', False): - try: - safe_eval(n.get('rml_except'), {}, self.localcontext) - except Exception: - continue - if n.get('rml_tag'): - try: - (tag,attr) = safe_eval(n.get('rml_tag'),{}, self.localcontext) - n2 = copy.copy(n) - n2.tag = tag - n2.attrib.update(attr) - yield n2 - except Exception: - yield n - else: - yield n - self.localcontext = oldctx - continue - if self and self.localcontext and n.get('rml_except', False): - try: - safe_eval(n.get('rml_except'), {}, self.localcontext) - except Exception: - continue - if (tagname is None) or (n.tag==tagname): - yield n - -def _process_text(self, txt): - if not self.localcontext: - return txt - if not txt: - return '' - result = '' - sps = _regex.split(txt) - while sps: - # This is a simple text to translate - result += self.localcontext.get('translate', lambda x:x)(sps.pop(0)) - if sps: - try: - txt2 = safe_eval(sps.pop(0),self.localcontext) - except Exception: - txt2 = '' - if isinstance(txt2, (int, float)): - txt2 = str(txt2) - if isinstance(txt2, basestring): - result += txt2 - return result - -def text_get(node): - rc = '' - for node in node.getchildren(): - rc = rc + node.text - return rc - -units = [ - (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch), - (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm), - (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm), - (re.compile('^(-?[0-9\.]+)\s*$'), 1) -] - -def unit_get(size): - global units - if size: - for unit in units: - res = unit[0].search(size, 0) - if res: - return unit[1]*float(res.group(1)) - return False - -def tuple_int_get(node, attr_name, default=None): - if not node.get(attr_name): - return default - res = [int(x) for x in node.get(attr_name).split(',')] - return res - -def bool_get(value): - return (str(value)=="1") or (value.lower()=='yes') - -def attr_get(node, attrs, dict=None): - if dict is None: - dict = {} - res = {} - for name in attrs: - if node.get(name): - res[name] = unit_get(node.get(name)) - for key in dict: - if node.get(key): - if dict[key]=='str': - res[key] = str(node.get(key)) - elif dict[key]=='bool': - res[key] = bool_get(node.get(key)) - elif dict[key]=='int': - res[key] = int(node.get(key)) - elif dict[key]=='unit': - res[key] = unit_get(node.get(key)) - return res diff --git a/odoo/report/render/simple.py b/odoo/report/render/simple.py deleted file mode 100644 index 07584f11914f356361d1081d3a0ef9dfdd16186a..0000000000000000000000000000000000000000 --- a/odoo/report/render/simple.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import copy -import xml.dom.minidom -from cStringIO import StringIO - -import reportlab.lib -from reportlab.platypus import SimpleDocTemplate, Paragraph -from reportlab.lib.units import mm -from reportlab.lib.pagesizes import A4 - -from . import render - - -class simple(render.render): - def _render(self): - self.result = StringIO() - parser = xml.dom.minidom.parseString(self.xml) - - title = parser.documentElement.tagName - doc = SimpleDocTemplate(self.result, pagesize=A4, title=title, - author='Odoo, Fabien Pinckaers', leftmargin=10*mm, rightmargin=10*mm) - - styles = reportlab.lib.styles.getSampleStyleSheet() - title_style = copy.deepcopy(styles["Heading1"]) - title_style.alignment = reportlab.lib.enums.TA_CENTER - story = [ Paragraph(title, title_style) ] - style_level = {} - nodes = [ (parser.documentElement,0) ] - while len(nodes): - node = nodes.pop(0) - value = '' - n=len(node[0].childNodes)-1 - while n>=0: - if node[0].childNodes[n].nodeType==3: - value += node[0].childNodes[n].nodeValue - else: - nodes.insert( 0, (node[0].childNodes[n], node[1]+1) ) - n-=1 - if not node[1] in style_level: - style = copy.deepcopy(styles["Normal"]) - style.leftIndent=node[1]*6*mm - style.firstLineIndent=-3*mm - style_level[node[1]] = style - story.append( Paragraph('<b>%s</b>: %s' % (node[0].tagName, value), style_level[node[1]])) - doc.build(story) - return self.result.getvalue() - -if __name__=='__main__': - s = simple() - s.xml = '''<test> - <author-list> - <author> - <name>Fabien Pinckaers</name> - <age>23</age> - </author> - <author> - <name>Michel Pinckaers</name> - <age>53</age> - </author> - No other - </author-list> - </test>''' - if s.render(): - print s.get() diff --git a/odoo/report/report_sxw.py b/odoo/report/report_sxw.py deleted file mode 100644 index e54ecad953ed45d785d5bfb75d3c78260b971ce9..0000000000000000000000000000000000000000 --- a/odoo/report/report_sxw.py +++ /dev/null @@ -1,611 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import base64 -import cStringIO -import logging -import os -import re -import StringIO -import time -import zipfile -from datetime import datetime - -from lxml import etree - -import odoo -import odoo.tools as tools -from . import common -from . import preprocess -from .interface import report_rml -from odoo import fields -from odoo.exceptions import AccessError -from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT -from odoo.tools.safe_eval import safe_eval -from odoo.tools.translate import _ - - -_logger = logging.getLogger(__name__) - -rml_parents = { - 'tr':1, - 'li':1, - 'story': 0, - 'section': 0 -} - -rml_tag="para" - -sxw_parents = { - 'table-row': 1, - 'list-item': 1, - 'body': 0, - 'section': 0, -} - -html_parents = { - 'tr' : 1, - 'body' : 0, - 'div' : 0 - } -sxw_tag = "p" - -rml2sxw = { - 'para': 'p', -} - -def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT): - return len((datetime.now()).strftime(date_format)) - - -class rml_parse(object): - def __init__(self, cr, uid, name, parents=rml_parents, tag=rml_tag, context=None): - if not context: - context = {} - self.cr = cr - self.uid = uid - env = odoo.api.Environment(cr, uid, context) - user = env['res.users'].browse(uid) - self.localcontext = { - 'user': user, - 'setCompany': self.setCompany, - 'repeatIn': self.repeatIn, - 'setLang': self.setLang, - 'setTag': self.setTag, - 'removeParentNode': self.removeParentNode, - 'format': self.format, - 'formatLang': self.formatLang, - 'lang' : user.company_id.partner_id.lang, - 'translate' : self._translate, - 'setHtmlImage' : self.set_html_image, - 'strip_name' : self._strip_name, - 'time' : time, - 'display_address': self.display_address, - # more context members are setup in setCompany() below: - # - company_id - # - logo - } - self.setCompany(user.company_id) - self.localcontext.update(context) - self.name = name - self._node = None - self.parents = parents - self.tag = tag - self._lang_cache = {} - self.lang_dict = {} - self.default_lang = {} - self.lang_dict_called = False - self._transl_regex = re.compile(r'(\[\[.+?\]\])') - - def setTag(self, oldtag, newtag, attrs=None): - return newtag, attrs - - def _ellipsis(self, char, size=100, truncation_str='...'): - if not char: - return '' - if len(char) <= size: - return char - return char[:size-len(truncation_str)] + truncation_str - - def setCompany(self, company_id): - if company_id: - self.localcontext['company'] = company_id - self.localcontext['logo'] = company_id.logo - self.rml_header = company_id.rml_header - self.rml_header2 = company_id.rml_header2 - self.rml_header3 = company_id.rml_header3 - self.logo = company_id.logo - - def _strip_name(self, name, maxlen=50): - return self._ellipsis(name, maxlen) - - def format(self, text, oldtag=None): - return text.strip() - - def removeParentNode(self, tag=None): - raise GeneratorExit('Skip') - - def set_html_image(self, id, model=None, field=None, context=None): - if not id: - return '' - if not model: - model = 'ir.attachment' - try: - env = odoo.api.Environment(self.cr, self.uid, {}) - res = env[model].browse(int(id)).read()[0] - if field: - return res[field] - elif model =='ir.attachment': - return res['datas'] - else: - return '' - except Exception: - return '' - - def setLang(self, lang): - self.localcontext['lang'] = lang - self.lang_dict_called = False - # re-evaluate self.objects in a different environment - env = self.objects.env(self.cr, self.uid, self.localcontext) - self.objects = self.objects.with_env(env) - - def _get_lang_dict(self): - env = odoo.api.Environment(self.cr, self.uid, {}) - Lang = env['res.lang'] - lang = self.localcontext.get('lang', 'en_US') or 'en_US' - lang_obj = Lang.search([('code', '=', lang)], limit=1) or \ - Lang.search([('code', '=', 'en_US')]) - self.lang_dict.update({'lang_obj': lang_obj, - 'date_format': lang_obj.date_format, - 'time_format':lang_obj.time_format}) - self.default_lang[lang] = self.lang_dict.copy() - return True - - def digits_fmt(self, obj=None, f=None, dp=None): - digits = self.get_digits(obj, f, dp) - return "%%.%df" % (digits, ) - - def get_digits(self, obj=None, f=None, dp=None): - d = DEFAULT_DIGITS = 2 - if dp: - env = odoo.api.Environment(self.cr, self.uid, {}) - d = env['decimal.precision'].precision_get(dp) - elif obj and f: - res_digits = getattr(obj._fields[f], 'digits', lambda x: ((16, DEFAULT_DIGITS))) - if isinstance(res_digits, tuple): - d = res_digits[1] - else: - d = res_digits(self.cr)[1] - elif (hasattr(obj, '_field') and\ - obj._field.type == 'float' and\ - obj._field.digits): - d = obj._field.digits[1] - if not d and d is not 0: - d = DEFAULT_DIGITS - return d - - def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False): - if digits is None: - if dp: - digits = self.get_digits(dp=dp) - elif currency_obj: - digits = currency_obj.decimal_places - else: - digits = self.get_digits(value) - - if isinstance(value, (str, unicode)) and not value: - return '' - - if not self.lang_dict_called: - self._get_lang_dict() - self.lang_dict_called = True - - if date or date_time: - if not value: - return '' - - date_format = self.lang_dict['date_format'] - parse_format = DEFAULT_SERVER_DATE_FORMAT - if date_time: - value = value.split('.')[0] - date_format = date_format + " " + self.lang_dict['time_format'] - parse_format = DEFAULT_SERVER_DATETIME_FORMAT - if isinstance(value, basestring): - # FIXME: the trimming is probably unreliable if format includes day/month names - # and those would need to be translated anyway. - date = datetime.strptime(value[:get_date_length(parse_format)], parse_format) - elif isinstance(value, time.struct_time): - date = datetime(*value[:6]) - else: - date = datetime(*value.timetuple()[:6]) - if date_time: - # Convert datetime values to the expected client/context timezone - record = self.env['base'].with_context(self.localcontext) - date = fields.Datetime.context_timestamp(record, date) - return date.strftime(date_format.encode('utf-8')) - - res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary) - if currency_obj and currency_obj.symbol: - if currency_obj.position == 'after': - res = u'%s\N{NO-BREAK SPACE}%s' % (res, currency_obj.symbol) - elif currency_obj and currency_obj.position == 'before': - res = u'%s\N{NO-BREAK SPACE}%s' % (currency_obj.symbol, res) - return res - - def display_address(self, address_record, without_company=False): - # FIXME handle `without_company` - return address_record.contact_address - - def repeatIn(self, lst, name,nodes_parent=False): - ret_lst = [] - for id in lst: - ret_lst.append({name:id}) - return ret_lst - - def _translate(self,text): - lang = self.localcontext['lang'] - if lang and text and not text.isspace(): - env = odoo.api.Environment(self.cr, self.uid, {}) - Translation = env['ir.translation'] - piece_list = self._transl_regex.split(text) - for pn in range(len(piece_list)): - if not self._transl_regex.match(piece_list[pn]): - source_string = piece_list[pn].replace('\n', ' ').strip() - if len(source_string): - translated_string = Translation._get_source(self.name, ('report', 'rml'), lang, source_string) - if translated_string: - piece_list[pn] = piece_list[pn].replace(source_string, translated_string) - text = ''.join(piece_list) - return text - - def _add_header(self, rml_dom, header='external'): - if header=='internal': - rml_head = self.rml_header2 - elif header=='internal landscape': - rml_head = self.rml_header3 - else: - rml_head = self.rml_header - - head_dom = etree.XML(rml_head) - for tag in head_dom: - found = rml_dom.find('.//'+tag.tag) - if found is not None and len(found): - if tag.get('position'): - found.append(tag) - else : - found.getparent().replace(found,tag) - return True - - def set_context(self, objects, data, ids, report_type=None): - self.localcontext['data'] = data - self.localcontext['objects'] = objects - self.localcontext['digits_fmt'] = self.digits_fmt - self.localcontext['get_digits'] = self.get_digits - self.datas = data - self.ids = ids - self.objects = objects - if report_type: - if report_type=='odt' : - self.localcontext.update({'name_space' :common.odt_namespace}) - else: - self.localcontext.update({'name_space' :common.sxw_namespace}) - - # WARNING: the object[0].exists() call below is slow but necessary because - # some broken reporting wizards pass incorrect IDs (e.g. ir.ui.menu ids) - if objects and len(objects) == 1 and \ - objects[0].exists() and 'company_id' in objects[0] and objects[0].company_id: - # When we print only one record, we can auto-set the correct - # company in the localcontext. For other cases the report - # will have to call setCompany() inside the main repeatIn loop. - self.setCompany(objects[0].company_id) - - -class report_sxw(report_rml, preprocess.report): - """ - The register=True kwarg has been added to help remove the - odoo.netsvc.LocalService() indirection and the related - odoo.report.interface.report_int._reports dictionary: - report_sxw registered in XML with auto=False are also registered in Python. - In that case, they are registered in the above dictionary. Since - registration is automatically done upon instanciation, and that - instanciation is needed before rendering, a way was needed to - instanciate-without-register a report. In the future, no report - should be registered in the above dictionary and it will be dropped. - """ - def __init__(self, name, table, rml=False, parser=rml_parse, header='external', store=False, register=True): - report_rml.__init__(self, name, table, rml, '', register=register) - self.name = name - self.parser = parser - self.header = header - self.store = store - self.internal_header=False - if header=='internal' or header=='internal landscape': - self.internal_header=True - - def getObjects(self, cr, uid, ids, context): - env = odoo.api.Environment(cr, uid, context or {}) - return env[self.table].browse(ids) - - def create(self, cr, uid, ids, data, context=None): - context = dict(context or {}) - if self.internal_header: - context.update(internal_header=self.internal_header) - - context.update(bin_raw=True) - env = odoo.api.Environment(cr, uid, context) - env['res.font'].sudo().font_scan(lazy=True) - ir_obj = env['ir.actions.report.xml'] - - report_xml = ir_obj.search([('report_name', '=', self.name[7:])], limit=1) - if not report_xml: - title = '' - report_file = tools.file_open(self.tmpl, subdir=None) - try: - rml = report_file.read() - report_type= data.get('report_type', 'pdf') - class a(object): - def __init__(self, *args, **argv): - for key,arg in argv.items(): - setattr(self, key, arg) - report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header) - finally: - report_file.close() - - # We add an attribute on the ir.actions.report.xml instance. - # This attribute 'use_global_header' will be used by - # the create_single_XXX function of the report engine. - # This change has been done to avoid a big change of the API. - setattr(report_xml, 'use_global_header', self.header if report_xml.header else False) - - report_type = report_xml.report_type - if report_type in ['sxw','odt']: - fnct = self.create_source_odt - elif report_type in ['pdf','raw','txt','html']: - fnct = self.create_source_pdf - elif report_type=='html2html': - fnct = self.create_source_html2html - elif report_type=='mako2html': - fnct = self.create_source_mako2html - else: - raise NotImplementedError(_('Unknown report type: %s') % report_type) - fnct_ret = fnct(cr, uid, ids, data, report_xml, context) - if not fnct_ret: - return False, False - return fnct_ret - - def create_source_odt(self, cr, uid, ids, data, report_xml, context=None): - return self.create_single_odt(cr, uid, ids, data, report_xml, context or {}) - - def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None): - return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {}) - - def create_source_mako2html(self, cr, uid, ids, data, report_xml, context=None): - return self.create_single_mako2html(cr, uid, ids, data, report_xml, context or {}) - - def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None): - if not context: - context = {} - env = odoo.api.Environment(cr, uid, context) - attach = report_xml.attachment - if attach: - objs = self.getObjects(cr, uid, ids, context) - results = [] - for obj in objs: - aname = safe_eval(attach, {'object':obj, 'time':time}) - result = False - if report_xml.attachment_use and aname and context.get('attachment_use', True): - att = env['ir.attachment'].search([('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)], limit=1) - if att: - if not att.datas: - continue - d = base64.decodestring(att.datas) - results.append((d,'pdf')) - continue - result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context) - if not result: - return False - if aname: - try: - name = aname+'.'+result[1] - # Remove the default_type entry from the context: this - # is for instance used on the account.account_invoices - # and is thus not intended for the ir.attachment type - # field. - ctx = dict(context) - ctx.pop('default_type', None) - env['ir.attachment'].with_context(ctx).create({ - 'name': aname, - 'datas': base64.encodestring(result[0]), - 'datas_fname': name, - 'res_model': self.table, - 'res_id': obj.id, - }) - except AccessError: - #TODO: should probably raise a proper osv_except instead, shouldn't we? see LP bug #325632 - _logger.info('Could not create saved report attachment', exc_info=True) - results.append(result) - if results: - if results[0][1]=='pdf': - from pyPdf import PdfFileWriter, PdfFileReader - output = PdfFileWriter() - for r in results: - reader = PdfFileReader(cStringIO.StringIO(r[0])) - for page in range(reader.getNumPages()): - output.addPage(reader.getPage(page)) - s = cStringIO.StringIO() - output.write(s) - return s.getvalue(), results[0][1] - return self.create_single_pdf(cr, uid, ids, data, report_xml, context) - - def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None): - if not context: - context={} - logo = None - context = context.copy() - title = report_xml.name - rml = report_xml.report_rml_content - # if no rml file is found - if not rml: - return False - rml_parser = self.parser(cr, uid, self.name2, context=context) - objs = self.getObjects(cr, uid, ids, context) - rml_parser.set_context(objs, data, ids, report_xml.report_type) - processed_rml = etree.XML(rml) - if report_xml.use_global_header: - rml_parser._add_header(processed_rml, self.header) - processed_rml = self.preprocess_rml(processed_rml,report_xml.report_type) - if rml_parser.logo: - logo = base64.decodestring(rml_parser.logo) - create_doc = self.generators[report_xml.report_type] - pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8')) - return pdf, report_xml.report_type - - def create_single_odt(self, cr, uid, ids, data, report_xml, context=None): - context = dict(context or {}) - context['parents'] = sxw_parents - report_type = report_xml.report_type - binary_report_content = report_xml.report_sxw_content - if isinstance(report_xml.report_sxw_content, unicode): - # if binary content was passed as unicode, we must - # re-encode it as a 8-bit string using the pass-through - # 'latin1' encoding, to restore the original byte values. - binary_report_content = report_xml.report_sxw_content.encode("latin1") - - sxw_io = StringIO.StringIO(binary_report_content) - sxw_z = zipfile.ZipFile(sxw_io, mode='r') - rml = sxw_z.read('content.xml') - meta = sxw_z.read('meta.xml') - mime_type = sxw_z.read('mimetype') - if mime_type == 'application/vnd.sun.xml.writer': - mime_type = 'sxw' - else : - mime_type = 'odt' - sxw_z.close() - - rml_parser = self.parser(cr, uid, self.name2, context=context) - rml_parser.parents = sxw_parents - rml_parser.tag = sxw_tag - objs = self.getObjects(cr, uid, ids, context) - rml_parser.set_context(objs, data, ids, mime_type) - - rml_dom_meta = node = etree.XML(meta) - elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined") - for pe in elements: - if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"): - if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3": - pe[0].text=data['id'] - if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4": - pe[0].text=data['model'] - meta = etree.tostring(rml_dom_meta, encoding='utf-8', - xml_declaration=True) - - rml_dom = etree.XML(rml) - elements = [] - key1 = rml_parser.localcontext['name_space']["text"]+"p" - key2 = rml_parser.localcontext['name_space']["text"]+"drop-down" - for n in rml_dom.iterdescendants(): - if n.tag == key1: - elements.append(n) - if mime_type == 'odt': - for pe in elements: - e = pe.findall(key2) - for de in e: - pp=de.getparent() - if de.text or de.tail: - pe.text = de.text or de.tail - for cnd in de: - if cnd.text or cnd.tail: - if pe.text: - pe.text += cnd.text or cnd.tail - else: - pe.text = cnd.text or cnd.tail - pp.remove(de) - else: - for pe in elements: - e = pe.findall(key2) - for de in e: - pp = de.getparent() - if de.text or de.tail: - pe.text = de.text or de.tail - for cnd in de: - text = cnd.get("{http://openoffice.org/2000/text}value",False) - if text: - if pe.text and text.startswith('[['): - pe.text += text - elif text.startswith('[['): - pe.text = text - if de.getparent(): - pp.remove(de) - - rml_dom = self.preprocess_rml(rml_dom, mime_type) - create_doc = self.generators[mime_type] - odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext), - encoding='utf-8', xml_declaration=True) - sxw_contents = {'content.xml':odt, 'meta.xml':meta} - - if report_xml.use_global_header: - #Add corporate header/footer - rml_file = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type)) - try: - rml = rml_file.read() - rml_parser = self.parser(cr, uid, self.name2, context=context) - rml_parser.parents = sxw_parents - rml_parser.tag = sxw_tag - objs = self.getObjects(cr, uid, ids, context) - rml_parser.set_context(objs, data, ids, report_xml.report_type) - rml_dom = self.preprocess_rml(etree.XML(rml),report_type) - create_doc = self.generators[report_type] - odt = create_doc(rml_dom,rml_parser.localcontext) - if report_xml.use_global_header: - rml_parser._add_header(odt) - odt = etree.tostring(odt, encoding='utf-8', - xml_declaration=True) - sxw_contents['styles.xml'] = odt - finally: - rml_file.close() - - #created empty zip writing sxw contents to avoid duplication - sxw_out = StringIO.StringIO() - sxw_out_zip = zipfile.ZipFile(sxw_out, mode='w') - sxw_template_zip = zipfile.ZipFile (sxw_io, 'r') - for item in sxw_template_zip.infolist(): - if item.filename not in sxw_contents: - buffer = sxw_template_zip.read(item.filename) - sxw_out_zip.writestr(item.filename, buffer) - for item_filename, buffer in sxw_contents.iteritems(): - sxw_out_zip.writestr(item_filename, buffer) - sxw_template_zip.close() - sxw_out_zip.close() - final_op = sxw_out.getvalue() - sxw_io.close() - sxw_out.close() - return final_op, mime_type - - def create_single_html2html(self, cr, uid, ids, data, report_xml, context=None): - context = dict(context or {}) - context['parents'] = html_parents - report_type = 'html' - - html = report_xml.report_rml_content - html_parser = self.parser(cr, uid, self.name2, context=context) - html_parser.parents = html_parents - html_parser.tag = sxw_tag - objs = self.getObjects(cr, uid, ids, context) - html_parser.set_context(objs, data, ids, report_type) - - html_dom = etree.HTML(html) - html_dom = self.preprocess_rml(html_dom,'html2html') - - create_doc = self.generators['html2html'] - html = etree.tostring(create_doc(html_dom, html_parser.localcontext)) - - return html.replace('&','&').replace('<', '<').replace('>', '>').replace('</br>',''), report_type - - def create_single_mako2html(self, cr, uid, ids, data, report_xml, context=None): - mako_html = report_xml.report_rml_content - html_parser = self.parser(cr, uid, self.name2, context) - objs = self.getObjects(cr, uid, ids, context) - html_parser.set_context(objs, data, ids, 'html') - create_doc = self.generators['makohtml2html'] - html = create_doc(mako_html,html_parser.localcontext) - return html,'html' diff --git a/odoo/service/__init__.py b/odoo/service/__init__.py index 5d61b35f0e4801b55d01d8e57bf92ab7c2c75339..3b9f417e273b922e88d150107339766971aff9e7 100644 --- a/odoo/service/__init__.py +++ b/odoo/service/__init__.py @@ -4,7 +4,6 @@ from . import common from . import db from . import model -from . import report from . import wsgi_server from . import server diff --git a/odoo/service/report.py b/odoo/service/report.py deleted file mode 100644 index 8229a2396e4bf260fbd50dcd429771b1de3bf1c2..0000000000000000000000000000000000000000 --- a/odoo/service/report.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- - -import base64 -import logging -import sys -import threading - -import odoo -import odoo.report -from odoo import tools -from odoo.exceptions import UserError - -from . import security - -_logger = logging.getLogger(__name__) - -# TODO: set a maximum report number per user to avoid DOS attacks -# -# Report state: -# False -> True - -self_reports = {} -self_id = 0 -self_id_protect = threading.Semaphore() - -def dispatch(method, params): - (db, uid, passwd ) = params[0:3] - threading.current_thread().uid = uid - params = params[3:] - if method not in ['report', 'report_get', 'render_report']: - raise KeyError("Method not supported %s" % method) - security.check(db,uid,passwd) - registry = odoo.registry(db).check_signaling() - fn = globals()['exp_' + method] - with registry.manage_changes(): - res = fn(db, uid, *params) - return res - -def exp_render_report(db, uid, object, ids, datas=None, context=None): - if not datas: - datas={} - if not context: - context={} - - self_id_protect.acquire() - global self_id - self_id += 1 - id = self_id - self_id_protect.release() - - self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} - - cr = odoo.registry(db).cursor() - try: - result, format = odoo.report.render_report(cr, uid, ids, object, datas, context) - if not result: - tb = sys.exc_info() - self_reports[id]['exception'] = odoo.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) - self_reports[id]['result'] = result - self_reports[id]['format'] = format - self_reports[id]['state'] = True - except Exception as exception: - - _logger.exception('Exception: %s\n', exception) - if hasattr(exception, 'name') and hasattr(exception, 'value'): - self_reports[id]['exception'] = odoo.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) - else: - tb = sys.exc_info() - self_reports[id]['exception'] = odoo.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) - self_reports[id]['state'] = True - cr.commit() - cr.close() - - return _check_report(id) - -def exp_report(db, uid, object, ids, datas=None, context=None): - if not datas: - datas={} - if not context: - context={} - - self_id_protect.acquire() - global self_id - self_id += 1 - id = self_id - self_id_protect.release() - - self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} - - def go(id, uid, ids, datas, context): - with odoo.api.Environment.manage(): - cr = odoo.registry(db).cursor() - try: - result, format = odoo.report.render_report(cr, uid, ids, object, datas, context) - if not result: - tb = sys.exc_info() - self_reports[id]['exception'] = odoo.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) - self_reports[id]['result'] = result - self_reports[id]['format'] = format - self_reports[id]['state'] = True - except Exception as exception: - _logger.exception('Exception: %s\n', exception) - if hasattr(exception, 'name') and hasattr(exception, 'value'): - self_reports[id]['exception'] = odoo.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) - else: - tb = sys.exc_info() - self_reports[id]['exception'] = odoo.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) - self_reports[id]['state'] = True - cr.commit() - cr.close() - return True - - threading.Thread(target=go, args=(id, uid, ids, datas, context)).start() - return id - -def _check_report(report_id): - result = self_reports[report_id] - exc = result['exception'] - if exc: - raise UserError('%s: %s' % (exc.message, exc.traceback)) - res = {'state': result['state']} - if res['state']: - if tools.config['reportgz']: - import zlib - res2 = zlib.compress(result['result']) - res['code'] = 'zlib' - else: - #CHECKME: why is this needed??? - if isinstance(result['result'], unicode): - res2 = result['result'].encode('latin1', 'replace') - else: - res2 = result['result'] - if res2: - res['result'] = base64.encodestring(res2) - res['format'] = result['format'] - del self_reports[report_id] - return res - -def exp_report_get(db, uid, report_id): - if report_id in self_reports: - if self_reports[report_id]['uid'] == uid: - return _check_report(report_id) - else: - raise Exception('AccessDenied') - else: - raise Exception('ReportNotFound') diff --git a/odoo/tools/amount_to_text_en.py b/odoo/tools/amount_to_text_en.py index 7a7fb5c2ef3f087877b1021864c284337ad83d38..6a8e7020b4a81e8b2bca56e36f33fa562d0c8145 100644 --- a/odoo/tools/amount_to_text_en.py +++ b/odoo/tools/amount_to_text_en.py @@ -89,11 +89,6 @@ def amount_to_text(nbr, lang='en', currency='euro'): 1654: thousands six cent cinquante-quatre. """ - import odoo.loglevels as loglevels -# if nbr > 10000000: -# _logger.warning(_("Number too large '%d', can not translate it")) -# return str(nbr) - if lang not in _translate_funcs: _logger.warning(_("no translation function found for lang: '%s'"), lang) #TODO: (default should be en) same as above diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index 2dd45699edbeef1233b7a94089a78e476738d19a..815ee9a64f1b60c8f187ece8c40090e9c9e65d6f 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -285,16 +285,12 @@ form: module.record_id""" % (xml_id,) for dest,f in (('name','string'),('model','model'),('report_name','name')): res[dest] = rec.get(f,'').encode('utf8') assert res[dest], "Attribute %s of report is empty !" % (f,) - for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'), - ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage'), - ('report_type', 'report_type'), ('parser', 'parser')): + for field,dest in (('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage'), + ('file', 'report_file'), ('report_type', 'report_type'), ('parser', 'parser')): if rec.get(field): res[dest] = rec.get(field).encode('utf8') if rec.get('auto'): res['auto'] = safe_eval(rec.get('auto','False')) - if rec.get('sxw'): - sxw_content = file_open(rec.get('sxw')).read() - res['report_sxw_content'] = sxw_content if rec.get('header'): res['header'] = safe_eval(rec.get('header','False')) @@ -319,19 +315,19 @@ form: module.record_id""" % (xml_id,) pf_id = self.id_get(pf_name) res['paperformat_id'] = pf_id - id = self.env['ir.model.data']._update("ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) + id = self.env['ir.model.data']._update("ir.actions.report", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) self.idref[xml_id] = int(id) if not rec.get('menu') or safe_eval(rec.get('menu','False')): keyword = str(rec.get('keyword', 'client_print_multi')) - value = 'ir.actions.report.xml,'+str(id) + value = 'ir.actions.report,'+str(id) action = self.env['ir.values'].set_action(res['name'], keyword, res['model'], value) - self.env['ir.actions.report.xml'].browse(id).write({'ir_values_id': action.id}) + self.env['ir.actions.report'].browse(id).write({'ir_values_id': action.id}) elif self.mode=='update' and safe_eval(rec.get('menu','False'))==False: # Special check for report having attribute menu=False on update - value = 'ir.actions.report.xml,'+str(id) + value = 'ir.actions.report,'+str(id) self._remove_ir_values(res['name'], value, res['model']) - self.env['ir.actions.report.xml'].browse(id).write({'ir_values_id': False}) + self.env['ir.actions.report'].browse(id).write({'ir_values_id': False}) return id def _tag_function(self, rec, data_node=None, mode=None): diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 4c570e7cc87bfc3c2c4b853d08da8164d9249068..492be9951e429effc16c5a01c95375dbb8eef379 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -154,7 +154,6 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): >>> file_open('hr/report/timesheer.xsl') >>> file_open('addons/hr/report/timesheet.xsl') - >>> file_open('../../base/report/rml_template.xsl', subdir='addons/hr/report', pathinfo=True) @param name name of the file @param mode file open mode diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py index c1f9af8bb550e6bb07fdfd25befc59489c70c1b3..17065848129b77ef964ecb862cc478c9b72e9c80 100644 --- a/odoo/tools/test_reports.py +++ b/odoo/tools/test_reports.py @@ -8,7 +8,6 @@ """ import odoo -import odoo.report import odoo.tools as tools import logging @@ -24,24 +23,20 @@ _test_logger = logging.getLogger('odoo.tests') def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, report_type=None): """ Try to render a report <rname> with contents of ids - + This function should also check for common pitfalls of reports. """ - if data is None: - data = {} if context is None: context = {} - if rname.startswith('report.'): - rname_s = rname[7:] - else: - rname_s = rname _test_logger.info(" - Trying %s.create(%r)", rname, ids) - res = odoo.report.render_report(cr, uid, ids, rname_s, data, context=context) - if not isinstance(res, tuple): - raise RuntimeError("Result of %s.create() should be a (data,format) tuple, now it is a %s" % \ - (rname, type(res))) - (res_data, res_format) = res + env = odoo.api.Environment(cr, uid, context) + + report_id = env['ir.actions.report'].search([('report_name', '=', rname)], limit=1) + if not report_id: + raise Exception("Required report does not exist: %s" % rname) + + res_data, res_format = report_id.render(ids, data=data) if not res_data: raise ValueError("Report %s produced an empty result!" % rname) @@ -53,7 +48,6 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re if res_format == 'pdf': if res_data[:5] != '%PDF-': raise ValueError("Report %s produced a non-pdf header, %r" % (rname, res_data[:10])) - res_text = False try: fd, rfname = tempfile.mkstemp(suffix=res_format) @@ -256,7 +250,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, action_name, b['string'], b['type']) return res - elif action['type']=='ir.actions.report.xml': + elif action['type']=='ir.actions.report': if 'window' in datas: del datas['window'] if not datas: @@ -267,7 +261,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, ids = datas.get('ids') if 'ids' in datas: del datas['ids'] - res = try_report(cr, uid, 'report.'+action['report_name'], ids, datas, context, our_module=our_module) + res = try_report(cr, uid, action['report_name'], ids, datas, context, our_module=our_module) return res else: raise Exception("Cannot handle action of type %s" % act_model) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 32299d6db14af47b0ceaa668414d6cd8768b225a..dcdb95d09554c66f68d39e8e6c89bf72e59dca57 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -860,24 +860,6 @@ def trans_generate(lang, modules, cr): for dummy, val in field.selection: push_translation(module, 'selection', name, 0, encode(val)) - elif model=='ir.actions.report.xml': - name = encode(record.report_name) - fname = "" - if record.report_rml: - fname = record.report_rml - parse_func = trans_parse_rml - report_type = "report" - elif record.report_xsl: - continue - if fname and record.report_type in ('pdf', 'xsl'): - try: - with file_open(fname) as report_file: - d = etree.parse(report_file) - for t in parse_func(d.iter()): - push_translation(module, report_type, name, 0, t) - except (IOError, etree.XMLSyntaxError): - _logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname) - for field_name, field in record._fields.iteritems(): if field.translate: name = model + "," + field_name diff --git a/odoo/tools/yaml_import.py b/odoo/tools/yaml_import.py index ad9a48e8a84a85d79114deeaa9ce921556b4f509..58e9a154a54af066a16668db3a7747b0df881d8f 100644 --- a/odoo/tools/yaml_import.py +++ b/odoo/tools/yaml_import.py @@ -793,18 +793,11 @@ class YamlInterpreter(object): for dest, f in (('name','string'), ('model','model'), ('report_name','name')): values[dest] = getattr(node, f) assert values[dest], "Attribute %s of report is empty !" % (f,) - for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')): + for field,dest in (('file', 'report_file'), ('attachment','attachment'),('attachment_use','attachment_use')): if getattr(node, field): values[dest] = getattr(node, field) if node.auto: values['auto'] = safe_eval(node.auto) - if node.sxw: - sxw_file = file_open(node.sxw) - try: - sxw_content = sxw_file.read() - values['report_sxw_content'] = sxw_content - finally: - sxw_file.close() if node.header: values['header'] = safe_eval(node.header) values['multi'] = node.multi and safe_eval(node.multi) @@ -813,13 +806,13 @@ class YamlInterpreter(object): self._set_group_values(node, values) - id = self.sudo_env['ir.model.data']._update("ir.actions.report.xml", \ + id = self.sudo_env['ir.model.data']._update("ir.actions.report", \ self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode) self.id_map[xml_id] = int(id) if not node.menu or safe_eval(node.menu): keyword = node.keyword or 'client_print_multi' - value = 'ir.actions.report.xml,%s' % id + value = 'ir.actions.report,%s' % id ir_values = self.env['ir.values'] res_id = False model = values['model'] diff --git a/requirements.txt b/requirements.txt index 05b1e1bf0e0ed1926a3479d7ff9e680ba42df5d4..23abdb04f1904c5ef7bc8243d9ea0dbd929c3611 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,6 @@ pyldap==2.4.28 pyparsing==2.1.10 pyPdf==1.13 pyserial==3.1.1 -Python-Chart==1.39 python-dateutil==2.5.3 python-openid==2.2.5 pytz==2016.7 diff --git a/setup.py b/setup.py index 5fd18a9995155a3518190e6c218c7194186068d3..2fce5faa39d7141b3d21961252612a5e165fd242 100644 --- a/setup.py +++ b/setup.py @@ -150,7 +150,6 @@ setup( 'psutil', # windows binary code.google.com/p/psutil/downloads/list 'psycogreen', 'psycopg2 >= 2.2', - 'python-chart', 'pydot', 'pyldap', # optional 'pyparsing',