Arnaud Pineux
5 years ago
committed by
Stéphane Bidoul (ACSONE)
No known key found for this signature in database
GPG Key ID: BCAB2555446B5B92
11 changed files with 315 additions and 2 deletions
-
10report_xlsx/README.rst
-
6report_xlsx/__manifest__.py
-
1report_xlsx/models/__init__.py
-
85report_xlsx/models/header_footer.py
-
6report_xlsx/models/ir_report.py
-
15report_xlsx/report/report_xlsx.py
-
3report_xlsx/security/ir.model.access.csv
-
5report_xlsx/tests/__init__.py
-
102report_xlsx/tests/test_header_footer.py
-
64report_xlsx/views/header_footer.xml
-
20report_xlsx/views/ir_report.xml
@ -0,0 +1,85 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2019 ACSONE SA/NV (<http://acsone.eu>) |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
|
|||
import io |
|||
import ast |
|||
from odoo import fields, models, api, _ |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class ReportHeaderFooter(models.Model): |
|||
_name = 'report.xlsx.hf' |
|||
|
|||
name = fields.Char(string="Name", required=True) |
|||
hf_type = fields.Selection( |
|||
[('header', 'Header'), ('footer', 'Footer')], |
|||
string="Type", |
|||
required=True) |
|||
value = fields.Char(string="Value") |
|||
manual_options = fields.Char(string="Options") |
|||
image_left = fields.Binary(string='Image left') |
|||
image_left_name = fields.Char('File Name') |
|||
image_center = fields.Binary(string='Image center') |
|||
image_center_name = fields.Char('File Name') |
|||
image_right = fields.Binary(string='Image right') |
|||
image_right_name = fields.Char('File Name') |
|||
header_report_ids = fields.One2many( |
|||
'ir.actions.report.xml', |
|||
'header_id', |
|||
string="Associated report(s)") |
|||
footer_report_ids = fields.One2many( |
|||
'ir.actions.report.xml', |
|||
'footer_id', |
|||
string="Associated report(s)") |
|||
|
|||
@api.multi |
|||
@api.constrains('manual_options') |
|||
def _check_manual_options(self): |
|||
for rec in self: |
|||
if rec.manual_options: |
|||
options = ast.literal_eval(rec.manual_options) |
|||
if not isinstance(options, dict): |
|||
raise ValidationError( |
|||
_('The Header/Footer is not configured properly.\ |
|||
Options must be a dictionary.')) |
|||
|
|||
@api.multi |
|||
@api.constrains('image_left', 'image_center', 'image_right') |
|||
def _check_images(self): |
|||
for rec in self: |
|||
error = "" |
|||
if rec.image_left and ("&L&G" not in rec.value |
|||
and "&L&[Picture]" not in rec.value): |
|||
error += _('You must specify the control character &L&G or \ |
|||
&L&[Picture] in the "Value" when you add an "Image left".\n') |
|||
if rec.image_center and ("&C&G" not in rec.value |
|||
and "&C&[Picture]" not in rec.value): |
|||
error += _('You must specify the control character &C&G or \ |
|||
&C&[Picture] in the "Value" when you add an "Image center".\n') |
|||
if rec.image_right and ("&R&G" not in rec.value |
|||
and "&R&[Picture]" not in rec.value): |
|||
error += _('You must specify the control character &R&G or \ |
|||
&R&[Picture] in the "Value" when you add an "Image right".\n') |
|||
if error: |
|||
raise ValidationError(error) |
|||
|
|||
@api.multi |
|||
def get_options(self): |
|||
self.ensure_one() |
|||
options = {} |
|||
if self.manual_options: |
|||
options = ast.literal_eval(self.manual_options) |
|||
if self.image_left: |
|||
options['image_left'] = self.image_left_name |
|||
options['image_data_left'] = io.BytesIO( |
|||
self.image_left.decode('base64')) |
|||
if self.image_center: |
|||
options['image_center'] = self.image_center_name |
|||
options['image_data_center'] = io.BytesIO( |
|||
self.image_center.decode('base64')) |
|||
if self.image_right: |
|||
options['image_right'] = self.image_right_name |
|||
options['image_data_right'] = io.BytesIO( |
|||
self.image_right.decode('base64')) |
|||
return options |
@ -0,0 +1,3 @@ |
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
|||
"access_report_xlsx_hf_all","report_xlsx_hf","model_report_xlsx_hf",,1,0,0,0 |
|||
"access_report_xlsx_hf_group_system","report_xlsx_hf_group_system","model_report_xlsx_hf","base.group_system",1,1,1,1 |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2019 ACSONE SA/NV (<http://acsone.eu>) |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
|
|||
from . import test_header_footer |
@ -0,0 +1,102 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2019 ACSONE SA/NV (<http://acsone.eu>) |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
|
|||
import odoo.tests.common as common |
|||
from odoo.exceptions import ValidationError |
|||
from cStringIO import StringIO |
|||
from odoo.addons.report_xlsx.report.report_xlsx import ReportXlsx |
|||
|
|||
|
|||
class PartnerXlsx(ReportXlsx): |
|||
|
|||
def generate_xlsx_report(self, workbook, data, partners): |
|||
sheet = workbook.add_worksheet('sheet') |
|||
sheet.write(0, 0, 'test') |
|||
|
|||
|
|||
class TestHeaderFooter(common.TransactionCase): |
|||
|
|||
@classmethod |
|||
def setUpClass(cls): |
|||
super(TestHeaderFooter, cls).setUpClass() |
|||
cls.report = PartnerXlsx('report.res.partner.xlsx', |
|||
'res.partner') |
|||
|
|||
def setUp(self): |
|||
super(TestHeaderFooter, self).setUp() |
|||
|
|||
# Create Header |
|||
self.header_001 = self.env['report.xlsx.hf'].create({ |
|||
'name': 'Header 001', |
|||
'hf_type': 'header', |
|||
'value': '&LPage &P of &N &CFilename: &F &RSheetname: &A', |
|||
}) |
|||
# Create Footer |
|||
self.footer_001 = self.env['report.xlsx.hf'].create({ |
|||
'name': 'Footer 001', |
|||
'hf_type': 'footer', |
|||
'value': '&LCurrent date: &D &RCurrent time: &T', |
|||
}) |
|||
# Create Report |
|||
self.report_xlsx = self.env['ir.actions.report.xml'].create({ |
|||
'report_name': 'res.partner.xlsx', |
|||
'name': 'XLSX report', |
|||
'report_type': 'xlsx', |
|||
'model': 'res.partner', |
|||
'header_id': self.header_001.id, |
|||
'footer_id': self.footer_001.id, |
|||
}) |
|||
|
|||
def test_header_footer(self): |
|||
""" |
|||
Check that the header and footer have been added to the worksheets |
|||
""" |
|||
file_data = StringIO() |
|||
partner = self.env['res.partner'].browse([1]) |
|||
workbook = self.report.create_workbook( |
|||
file_data, {}, partner, self.report_xlsx) |
|||
header = u'&LPage &P of &N &CFilename: &F &RSheetname: &A' |
|||
footer = u'&LCurrent date: &D &RCurrent time: &T' |
|||
|
|||
for sheet in workbook.worksheets(): |
|||
self.assertEqual(header, sheet.header) |
|||
self.assertEqual(footer, sheet.footer) |
|||
|
|||
def test_wrong_options(self): |
|||
""" |
|||
Check that options must be a dict |
|||
""" |
|||
with self.assertRaises(ValidationError): |
|||
self.env['report.xlsx.hf'].create({ |
|||
'name': 'Header ERROR', |
|||
'hf_type': 'header', |
|||
'value': '&LPage &P of &N &CFilename: &F &RSheetname: &A', |
|||
'manual_options': "1234", |
|||
}) |
|||
|
|||
def test_image_options(self): |
|||
""" |
|||
Check that, adding image, modify the options |
|||
""" |
|||
header = self.env['report.xlsx.hf'].create({ |
|||
'name': 'Header IMAGE', |
|||
'hf_type': 'header', |
|||
'value': '&L&G &C&G &R&G', |
|||
'image_left': |
|||
'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', |
|||
'image_left_name': 'image_left.jpg', |
|||
'image_center': |
|||
'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', |
|||
'image_center_name': 'image_center.jpg', |
|||
'image_right': |
|||
'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', |
|||
'image_right_name': 'image_right.jpg', |
|||
}) |
|||
options = header.get_options() |
|||
self.assertEqual(options.get('image_left'), 'image_left.jpg') |
|||
self.assertEqual(options.get('image_center'), 'image_center.jpg') |
|||
self.assertEqual(options.get('image_right'), 'image_right.jpg') |
|||
self.assertTrue(options.get('image_data_left')) |
|||
self.assertTrue(options.get('image_data_center')) |
|||
self.assertTrue(options.get('image_data_right')) |
@ -0,0 +1,64 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
|
|||
<record id="report_xlsx_hf_tree" model="ir.ui.view"> |
|||
<field name="name">report_xlsx_hf Tree</field> |
|||
<field name="model">report.xlsx.hf</field> |
|||
<field name="type">tree</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="name"/> |
|||
<field name="hf_type"/> |
|||
<field name="value"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="report_xlsx_hf_form" model="ir.ui.view"> |
|||
<field name="name">report_xlsx_hf Form</field> |
|||
<field name="model">report.xlsx.hf</field> |
|||
<field name="type">form</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<sheet> |
|||
<group> |
|||
<field name="name"/> |
|||
<field name="hf_type"/> |
|||
</group> |
|||
<div colspan="2"><i class="fa fa-info-circle" aria-hidden="true"/> Search in the <a href="https://xlsxwriter.readthedocs.io/page_setup.html#set_header">documentation</a> for set_header and set_footer to find the proper syntax to fill "Value" and "Options".</div> |
|||
<group> |
|||
<field name="value" placeholder="&LHello &CWorld &R!!!"/> |
|||
<field name="manual_options" placeholder="{'margin': 0.3}"/> |
|||
</group> |
|||
<separator string="Images"/> |
|||
<div><i class="fa fa-info-circle" aria-hidden="true"/> |
|||
If you select an image, you don't have to specify any image options (image_left, image_data_left, ...) but you need to add the control character <span style="background-color: #ebecec;">&G</span> in the value.</div> |
|||
<div> |
|||
If you add the three images, the value must contain <span style="background-color: #ebecec;">&L&G &C&G &R&G</span> or <span style="background-color: #ebecec;">&L&[Picture] &C&[Picture] &R&[Picture]</span>.</div> |
|||
<group> |
|||
<field name="image_left_name" invisible="1"/> |
|||
<field name="image_left" filename="image_left_name"/> |
|||
<field name="image_center_name" invisible="1"/> |
|||
<field name="image_center" filename="image_center_name"/> |
|||
<field name="image_right_name" invisible="1"/> |
|||
<field name="image_right" filename="image_right_name"/> |
|||
</group> |
|||
<separator string="Associated report(s)"/> |
|||
<field name="header_report_ids" attrs="{'invisible': [('hf_type', '!=', 'header')]}" nolabel="1" colspan="2"/> |
|||
<field name="footer_report_ids" attrs="{'invisible': [('hf_type', '!=', 'footer')]}" nolabel="1" colspan="2"/> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="report_xlsx_hf_action" model="ir.actions.act_window"> |
|||
<field name="name">XLSX Header/Footer</field> |
|||
<field name="res_model">report.xlsx.hf</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
</record> |
|||
|
|||
<menuitem id="report_xlsx_hf_menu" parent="report.reporting_menuitem" |
|||
name="XLSX Header/Footer" action="report_xlsx_hf_action" sequence="100"/> |
|||
|
|||
</odoo> |
@ -0,0 +1,20 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
|
|||
<record id="act_report_xml_view_inherit" model="ir.ui.view"> |
|||
<field name="name">ir.actions.report.xml (xlsx header footer)</field> |
|||
<field name="model">ir.actions.report.xml</field> |
|||
<field name="inherit_id" ref="base.act_report_xml_view"/> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="//notebook" position="inside"> |
|||
<page string="Header/Footer" attrs="{'invisible': [('report_type', '!=', 'xlsx')]}"> |
|||
<group> |
|||
<field name="header_id" context="{'default_hf_type': 'header'}"/> |
|||
<field name="footer_id" context="{'default_hf_type': 'footer'}"/> |
|||
</group> |
|||
</page> |
|||
</xpath> |
|||
</field> |
|||
</record> |
|||
|
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue