[REM] odoo/report: remove deprecated stuff
- RML Reports
- Webkit Reports (most part already removed by 13b9982c62)
- LocalService in netsvc.py
- rename attributes like rml_% to report_%
- rename ir.actions.report.xml to ir.actions.report
- allow rendering directly on an ir.actions.report by calling render method
- remove 'controller' report_type
- remove unused res.font stuff
- remove print_report method in models.py (not used)
- restore removed call to pdftotext process in test_reports
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
-
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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')]}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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``
|
||||
|
||||
@@ -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,…)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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!!!'))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from . import preview_report
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
@@ -15,8 +15,6 @@ must be used.
|
||||
|
||||
"""
|
||||
|
||||
from . import deprecation
|
||||
|
||||
# Paths to search for OpenERP addons.
|
||||
addons_paths = []
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
+3
-20
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
@@ -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}"}
|
||||
@@ -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')
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 ''
|
||||
@@ -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
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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 !')
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .html2html import parseString
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .makohtml2html import parseNode
|
||||
@@ -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()
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .odt2odt import parseNode
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .rml2html import parseString
|
||||
@@ -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.'
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .trml2pdf import parseString, parseNode
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user