[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:
Laurent Smet
2017-05-08 09:22:56 +02:00
parent 9898b125ad
commit 3425752eac
116 changed files with 174 additions and 6069 deletions
+1 -1
View File
@@ -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>
+2 -4
View File
@@ -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)
-
+3 -3
View File
@@ -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>
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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>
+4 -7
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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
+2 -4
View File
@@ -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
View File
@@ -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>
+4 -6
View File
@@ -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,
}
+3 -3
View File
@@ -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>
+2 -2
View File
@@ -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>
+2 -2
View File
@@ -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>
-5
View File
@@ -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:
+1 -1
View File
@@ -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
@@ -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()
+6 -6
View File
@@ -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()
+1 -1
View File
@@ -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):
+2 -9
View File
@@ -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'):
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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>
+9 -9
View File
@@ -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)
+2 -2
View File
@@ -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>
+5 -5
View File
@@ -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>
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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``
+1 -1
View File
@@ -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,…)
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
-1
View File
@@ -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
-20
View File
@@ -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"/>
+26 -107
View File
@@ -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):
+13 -28
View File
@@ -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 -->
+2 -2
View File
@@ -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:
+2 -11
View File
@@ -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!!!'))
+2 -2
View File
@@ -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
View File
@@ -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>
-12
View File
@@ -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>
-1
View File
@@ -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
+2 -127
View File
@@ -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():
+2 -4
View File
@@ -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"/>
-131
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
70 access_ir_actions_act_window_system ir_actions_act_window_system model_ir_actions_act_window group_system 1 1 1 1
71 access_ir_actions_act_window_close_all ir_actions_act_window_close_all model_ir_actions_act_window_close 1 0 0 0
72 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
73 access_ir_actions_report_xml_all access_ir_actions_report_all ir_actions_report_xml ir_actions_report model_ir_actions_report_xml model_ir_actions_report 1 0 0 0
74 access_ir_actions_report_xml_group_system access_ir_actions_report_group_system ir_actions_report_xml_group_system ir_actions_report_group_system model_ir_actions_report_xml model_ir_actions_report group_system 1 1 1 1
75 access_ir_actions_todo_group_system ir_actions_todo group system model_ir_actions_todo group_system 1 1 1 1
76 access_ir_actions_act_window_view_all ir_actions_act_window_view_all model_ir_actions_act_window_view 1 0 0 0
77 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 access_ir_config_parameter_system ir_config_parameter_system model_ir_config_parameter group_system 1 1 1 1
92 access_ir_mail_server ir_mail_server model_ir_mail_server group_system 1 1 1 1
93 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
94 access_ir_logging ir_logging admin model_ir_logging group_erp_manager 1 1 1 1
+2 -2
View File
@@ -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)
-2
View File
@@ -15,8 +15,6 @@ must be used.
"""
from . import deprecation
# Paths to search for OpenERP addons.
addons_paths = []
-35
View File
@@ -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
-4
View File
@@ -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
View File
@@ -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):
-20
View File
@@ -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):
-19
View File
@@ -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)
-51
View File
@@ -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}"}
-587
View File
@@ -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')
-56
View File
@@ -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)
-238
View File
@@ -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('&', '&amp;').replace('<','&lt;').replace('>','&gt;')
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']
]
-17
View File
@@ -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)
-86
View File
@@ -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)
-16
View File
@@ -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 ''
-255
View File
@@ -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
-10
View File
@@ -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.
"""
-114
View File
@@ -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')
-250
View File
@@ -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')
-13
View File
@@ -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 !')
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .html2html import parseString
-76
View File
@@ -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('&lt;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('&lt;a')>=0:
t = '&lt;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()
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .odt2odt import parseNode
-36
View File
@@ -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()
-52
View File
@@ -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
-77
View File
@@ -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)
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .rml2html import parseString
-423
View File
@@ -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('°','&deg;')
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\'>&nbsp;</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.'
-48
View File
@@ -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
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .trml2pdf import parseString, parseNode
-25
View File
@@ -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
-73
View File
@@ -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