Tatiana Deribina
5 years ago
committed by
Enric Tobella
21 changed files with 428 additions and 125 deletions
-
38report_xml/README.rst
-
2report_xml/__init__.py
-
20report_xml/__manifest__.py
-
10report_xml/controllers/main.py
-
12report_xml/demo/demo_report.xml
-
23report_xml/demo/demo_report.xsd
-
24report_xml/demo/report.xml
-
47report_xml/hooks.py
-
2report_xml/models/__init__.py
-
62report_xml/models/ir_actions_report.py
-
31report_xml/models/report_action.py
-
5report_xml/readme/CONTRIBUTORS.rst
-
22report_xml/readme/USAGE.rst
-
3report_xml/reports/__init__.py
-
123report_xml/reports/report_report_xml_abstract.py
-
44report_xml/static/description/index.html
-
13report_xml/static/src/js/report/action_manager_report.js
-
6report_xml/tests/test_report_xml.py
-
32report_xml/views/ir_actions_report_view.xml
-
13report_xml/views/ir_actions_views.xml
-
5report_xml/views/webclient_templates.xml
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<template id="demo_report_xml_view"> |
||||
|
<root> |
||||
|
<user t-foreach="docs" t-as="doc"> |
||||
|
<id t-esc="doc.id" /> |
||||
|
<name t-esc="doc.name" /> |
||||
|
<vat t-esc="doc.vat" /> |
||||
|
</user> |
||||
|
</root> |
||||
|
</template> |
||||
|
</odoo> |
@ -0,0 +1,23 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
|
||||
|
<xs:element name="root" type="root_type"/> |
||||
|
|
||||
|
<xs:complexType name="root_type"> |
||||
|
<xs:sequence> |
||||
|
<xs:element name="user" |
||||
|
type="user_type" |
||||
|
minOccurs="0" |
||||
|
maxOccurs="unbounded"/> |
||||
|
</xs:sequence> |
||||
|
</xs:complexType> |
||||
|
|
||||
|
<xs:complexType name="user_type"> |
||||
|
<xs:sequence> |
||||
|
<xs:element name="id" type="xs:int"/> |
||||
|
<xs:element name="name" type="xs:string"/> |
||||
|
<xs:element name="vat" type="xs:string" minOccurs="0"/> |
||||
|
</xs:sequence> |
||||
|
</xs:complexType> |
||||
|
|
||||
|
</xs:schema> |
@ -1,19 +1,19 @@ |
|||||
<?xml version="1.0" encoding="UTF-8" ?> |
<?xml version="1.0" encoding="UTF-8" ?> |
||||
<odoo> |
<odoo> |
||||
<template id="demo_report_xml_view"> |
|
||||
<root> |
|
||||
<user t-foreach="docs" t-as="doc"> |
|
||||
<name t-esc="doc.name"/> |
|
||||
<vat t-esc="doc.vat"/> |
|
||||
</user> |
|
||||
</root> |
|
||||
</template> |
|
||||
|
|
||||
<report id="demo_xml_report" |
|
||||
|
<report |
||||
|
id="demo_xml_report" |
||||
name="report_xml.demo_report_xml_view" |
name="report_xml.demo_report_xml_view" |
||||
string="Demo xml report" |
string="Demo xml report" |
||||
report_type="qweb-xml" |
report_type="qweb-xml" |
||||
print_report_name="'Demo xml report'" |
print_report_name="'Demo xml report'" |
||||
file="report_xml.xml" |
|
||||
model="res.company"/> |
|
||||
|
model="res.company" |
||||
|
/> |
||||
|
<!-- |
||||
|
In case of demo data next definition will not work. So it just example |
||||
|
how it should look. If report is a part of demo data you will need |
||||
|
add file to report instance via `post_install_hook` |
||||
|
--> |
||||
|
<record id="demo_xml_report" model="ir.actions.report"> |
||||
|
<field name="xsd_schema" type="base64" file="report_xml/demo/demo_report.xsd" /> |
||||
|
</record> |
||||
</odoo> |
</odoo> |
@ -0,0 +1,47 @@ |
|||||
|
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
||||
|
|
||||
|
import os |
||||
|
|
||||
|
from odoo import SUPERUSER_ID, api |
||||
|
|
||||
|
|
||||
|
def post_init_hook(cr, registry): |
||||
|
""" |
||||
|
Loaded after installing this module, and before the next module starts |
||||
|
installing. |
||||
|
|
||||
|
Add XSD Validation Schema for a demo report if it's in the system. |
||||
|
Demo data records are always created with `noupdate == True` and render of |
||||
|
tag `report` doesn't support new `ir.actions.report` field `xsd_schema`. |
||||
|
Thus it is impossible to define `xsd_schema` in the demo definition or add |
||||
|
schema after that via xml update record. Therefore it possible to add value |
||||
|
to `xsd_schema` field for demo record only via hook. |
||||
|
|
||||
|
Args: |
||||
|
* cr(odoo.sql_db.Cursor) - database cursor. |
||||
|
* registry(odoo.modules.registry.RegistryManager) - a mapping between |
||||
|
model names and model classes. |
||||
|
""" |
||||
|
with api.Environment.manage(): |
||||
|
env = api.Environment(cr, SUPERUSER_ID, {}) |
||||
|
report_domain = [ |
||||
|
("report_name", "=", "report_xml.demo_report_xml_view") # report tech name |
||||
|
] |
||||
|
demo_report = env["ir.actions.report"].search(report_domain, limit=1) |
||||
|
if demo_report: |
||||
|
dir_path = os.path.dirname(__file__) |
||||
|
xsd_file_relative_path = "demo/demo_report.xsd" |
||||
|
xsd_file_full_path = os.path.join(dir_path, xsd_file_relative_path) |
||||
|
|
||||
|
with open(xsd_file_full_path, "r") as xsd: |
||||
|
# `xsd_schema` is binary fields with an attribute |
||||
|
# `attachment=True` so XSD Schema will be added as attachment |
||||
|
attach_vals = { |
||||
|
"name": "Demo Report.xsd", |
||||
|
"datas": xsd.read(), |
||||
|
"res_model": "ir.actions.report", |
||||
|
"res_id": demo_report.id, |
||||
|
"res_field": "xsd_schema", |
||||
|
"type": "binary", |
||||
|
} |
||||
|
env["ir.attachment"].create(attach_vals) |
@ -1,3 +1,3 @@ |
|||||
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
||||
|
|
||||
from . import report_action |
|
||||
|
from . import ir_actions_report |
@ -0,0 +1,62 @@ |
|||||
|
# Copyright (C) 2014-2015 Grupo ESOC <www.grupoesoc.es> |
||||
|
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class IrActionsReport(models.Model): |
||||
|
_inherit = "ir.actions.report" |
||||
|
|
||||
|
report_type = fields.Selection(selection_add=[("qweb-xml", "XML")]) |
||||
|
xsd_schema = fields.Binary( |
||||
|
string="XSD Validation Schema", |
||||
|
attachment=True, |
||||
|
help=( |
||||
|
"File with XSD Schema for checking content of result report. " |
||||
|
"Can be empty if validation is not required." |
||||
|
), |
||||
|
) |
||||
|
xml_encoding = fields.Selection( |
||||
|
selection=[ |
||||
|
("UTF-8", "UTF-8") # will be used as default even if nothing is selected |
||||
|
], |
||||
|
string="XML Encoding", |
||||
|
help=( |
||||
|
"Encoding for XML reports. If nothing is selected, " |
||||
|
"then UTF-8 will be applied." |
||||
|
), |
||||
|
) |
||||
|
xml_declaration = fields.Boolean( |
||||
|
string="XML Declaration", |
||||
|
help=( |
||||
|
"""Add `<?xml encoding="..." version="..."?>` at the start """ |
||||
|
"""of final report file.""" |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
def render_qweb_xml(self, docids, data=None): |
||||
|
""" |
||||
|
Call `generate_report` method of report abstract class |
||||
|
`report.<report technical name>` or of standard class for XML report |
||||
|
rendering - `report.report_xml.abstract` |
||||
|
|
||||
|
Args: |
||||
|
* docids(list) - IDs of instances for those report will be generated |
||||
|
* data(dict, None) - variables for report rendering |
||||
|
|
||||
|
Returns: |
||||
|
* str - result content of report |
||||
|
* str - type of result content |
||||
|
""" |
||||
|
report_model_name = "report.{}".format(self.report_name) |
||||
|
|
||||
|
report_model = self.env.get(report_model_name) |
||||
|
if report_model is None: |
||||
|
report_model = self.env["report.report_xml.abstract"] |
||||
|
|
||||
|
content, ttype = report_model.generate_report( |
||||
|
ir_report=self, # will be used to get settings of report |
||||
|
docids=docids, |
||||
|
data=data, |
||||
|
) |
||||
|
return content, ttype |
@ -1,31 +0,0 @@ |
|||||
# Copyright (C) 2014-2015 Grupo ESOC <www.grupoesoc.es> |
|
||||
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
|
||||
|
|
||||
from lxml import etree |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
|
|
||||
|
|
||||
class ReportAction(models.Model): |
|
||||
_inherit = "ir.actions.report" |
|
||||
|
|
||||
report_type = fields.Selection(selection_add=[("qweb-xml", "XML")]) |
|
||||
|
|
||||
@api.model |
|
||||
def render_qweb_xml(self, docids, data=None): |
|
||||
if not data: |
|
||||
data = {} |
|
||||
data.setdefault("report_type", "text") |
|
||||
data = self._get_rendering_context(docids, data) |
|
||||
result = self.render_template(self.report_name, data) |
|
||||
return ( |
|
||||
etree.tostring( |
|
||||
etree.fromstring( |
|
||||
str(result, "UTF-8").lstrip("\n").lstrip().encode("UTF-8") |
|
||||
), |
|
||||
encoding="UTF-8", |
|
||||
xml_declaration=True, |
|
||||
pretty_print=True, |
|
||||
), |
|
||||
"xml", |
|
||||
) |
|
@ -1,4 +1,5 @@ |
|||||
* Enric Tobella <etobella@creublanca.es> |
* Enric Tobella <etobella@creublanca.es> |
||||
* `Tecnativa <https://www.tecnativa.com>`_: |
* `Tecnativa <https://www.tecnativa.com>`_: |
||||
|
|
||||
Jairo Llopis |
|
||||
|
* Jairo Llopis |
||||
|
* `Avoin.Systems <https://avoin.systems/>`_: |
||||
|
* Tatiana Deribina |
@ -0,0 +1,3 @@ |
|||||
|
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
||||
|
|
||||
|
from . import report_report_xml_abstract |
@ -0,0 +1,123 @@ |
|||||
|
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). |
||||
|
|
||||
|
from base64 import b64decode |
||||
|
from xml.dom import minidom |
||||
|
|
||||
|
from lxml import etree |
||||
|
|
||||
|
from odoo import api, models |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
|
class ReportXmlAbstract(models.AbstractModel): |
||||
|
""" |
||||
|
Model `report.report_xml.abstract`. |
||||
|
|
||||
|
This class provide basic methods for rendering XML report and it's |
||||
|
validation by XSD schema. |
||||
|
""" |
||||
|
|
||||
|
_name = "report.report_xml.abstract" |
||||
|
_description = "Abstract XML Report" |
||||
|
|
||||
|
@api.model |
||||
|
def generate_report(self, ir_report, docids, data=None): |
||||
|
""" |
||||
|
Generate and validate XML report. Use incoming `ir_report` settings |
||||
|
to setup encoding and XMl declaration for result `xml`. |
||||
|
|
||||
|
Methods: |
||||
|
* `_get_rendering_context` `ir.actions.report` - get report variables. |
||||
|
It will call `_get_report_values` of report's class if it's exist. |
||||
|
* `render_template` of `ir.actions.report` - get report content |
||||
|
* `validate_report` - check result content |
||||
|
|
||||
|
Args: |
||||
|
* ir_report(`ir.actions.report`) - report definition instance in Odoo |
||||
|
* docids(list) - IDs of instances for those report will be generated |
||||
|
* data(dict, None) - variables for report rendering |
||||
|
|
||||
|
Returns: |
||||
|
* str - result content of report |
||||
|
* str - `"xml"` |
||||
|
|
||||
|
Extra Info: |
||||
|
* Default encoding is `UTF-8` |
||||
|
""" |
||||
|
# collect variable for rendering environment |
||||
|
if not data: |
||||
|
data = {} |
||||
|
data.setdefault("report_type", "text") |
||||
|
data = ir_report._get_rendering_context(docids, data) |
||||
|
|
||||
|
# render template |
||||
|
result_bin = ir_report.render_template(ir_report.report_name, data) |
||||
|
|
||||
|
# prettify result content |
||||
|
# normalize indents |
||||
|
parsed_result_bin = minidom.parseString(result_bin) |
||||
|
result = parsed_result_bin.toprettyxml(indent=" " * 4) |
||||
|
|
||||
|
# remove empty lines |
||||
|
utf8 = "UTF-8" |
||||
|
result = "\n".join( |
||||
|
line for line in result.splitlines() if line and not line.isspace() |
||||
|
).encode(utf8) |
||||
|
|
||||
|
content = etree.tostring( |
||||
|
etree.fromstring(result), |
||||
|
encoding=ir_report.xml_encoding or utf8, |
||||
|
xml_declaration=ir_report.xml_declaration, |
||||
|
pretty_print=True, |
||||
|
) |
||||
|
|
||||
|
# validate content |
||||
|
xsd_schema_doc = ir_report.xsd_schema |
||||
|
self.validate_report(xsd_schema_doc, content) |
||||
|
return content, "xml" |
||||
|
|
||||
|
@api.model |
||||
|
def validate_report(self, xsd_schema_doc, content): |
||||
|
""" |
||||
|
Validate final report content against value of `xsd_schema` field |
||||
|
("XSD Validation Schema") of `ir.actions.report` via `etree` lib. |
||||
|
|
||||
|
Args: |
||||
|
* xsd_schema_doc(byte-string) - report validation schema |
||||
|
* content(str) - report content for validation |
||||
|
|
||||
|
Raises: |
||||
|
* odoo.exceptions.ValidationError - Syntax of final report is wrong |
||||
|
|
||||
|
Returns: |
||||
|
* bool - True |
||||
|
""" |
||||
|
if xsd_schema_doc: |
||||
|
# create validation parser |
||||
|
decoded_xsd_schema_doc = b64decode(xsd_schema_doc) |
||||
|
parsed_xsd_schema = etree.XML(decoded_xsd_schema_doc) |
||||
|
xsd_schema = etree.XMLSchema(parsed_xsd_schema) |
||||
|
parser = etree.XMLParser(schema=xsd_schema) |
||||
|
|
||||
|
try: |
||||
|
# check content |
||||
|
etree.fromstring(content, parser) |
||||
|
except etree.XMLSyntaxError as error: |
||||
|
raise ValidationError(error.msg) |
||||
|
return True |
||||
|
|
||||
|
@api.model |
||||
|
def _get_report_values(self, docids, data=None): |
||||
|
""" |
||||
|
Allow to generate extra variables for report environment. |
||||
|
|
||||
|
Args: |
||||
|
* docids(list) - IDs of instances for those report will be generated |
||||
|
* data(dict, None) - variables for report rendering |
||||
|
|
||||
|
Returns: |
||||
|
* dict - extra variables for report render |
||||
|
""" |
||||
|
if not data: |
||||
|
data = {} |
||||
|
return data |
@ -1,19 +1,18 @@ |
|||||
odoo.define('report_xml.ReportActionManager', function (require) { |
|
||||
'use strict'; |
|
||||
|
odoo.define("report_xml.ReportActionManager", function(require) { |
||||
|
"use strict"; |
||||
|
|
||||
var ActionManager = require('web.ActionManager'); |
|
||||
|
var ActionManager = require("web.ActionManager"); |
||||
|
|
||||
ActionManager.include({ |
ActionManager.include({ |
||||
_executeReportAction: function(action, options) { |
_executeReportAction: function(action, options) { |
||||
if (action.report_type === 'qweb-xml') { |
|
||||
return this._triggerDownload(action, options, 'xml'); |
|
||||
|
if (action.report_type === "qweb-xml") { |
||||
|
return this._triggerDownload(action, options, "xml"); |
||||
} |
} |
||||
return this._super(action, options); |
return this._super(action, options); |
||||
}, |
}, |
||||
_makeReportUrls: function(action) { |
_makeReportUrls: function(action) { |
||||
var reportUrls = this._super(action); |
var reportUrls = this._super(action); |
||||
reportUrls.xml = reportUrls.text.replace( |
|
||||
'/report/text/', '/report/xml/'); |
|
||||
|
reportUrls.xml = reportUrls.text.replace("/report/text/", "/report/xml/"); |
||||
return reportUrls; |
return reportUrls; |
||||
}, |
}, |
||||
}); |
}); |
||||
|
@ -0,0 +1,32 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<odoo> |
||||
|
<record id="ir_actions_report_view_form_report_xml" model="ir.ui.view"> |
||||
|
<field name="name">ir.actions.report.view.form.report.xml</field> |
||||
|
<field name="model">ir.actions.report</field> |
||||
|
<field name="inherit_id" ref="base.act_report_xml_view" /> |
||||
|
<field name="arch" type="xml"> |
||||
|
<button name="associated_view" position="attributes"> |
||||
|
<attribute name="attrs">{ |
||||
|
'invisible': [ |
||||
|
('report_type', 'not in', ('qweb-pdf', 'qweb-html', 'qweb-text', 'qweb-xml')), |
||||
|
], |
||||
|
}</attribute> |
||||
|
</button> |
||||
|
<xpath expr="//page[@name='advanced']/group" position="after"> |
||||
|
<group |
||||
|
name="xml_reports" |
||||
|
string="XML Rreport Settings" |
||||
|
attrs="{ |
||||
|
'invisible': [ |
||||
|
('report_type', '!=', 'qweb-xml') |
||||
|
], |
||||
|
}" |
||||
|
> |
||||
|
<field name="xsd_schema" /> |
||||
|
<field name="xml_encoding" /> |
||||
|
<field name="xml_declaration" /> |
||||
|
</group> |
||||
|
</xpath> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -1,13 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
<record id="act_report_xml_view" model="ir.ui.view"> |
|
||||
<field name="name">ir.actions.report.report.xml</field> |
|
||||
<field name="model">ir.actions.report</field> |
|
||||
<field name="inherit_id" ref="base.act_report_xml_view"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<button name="associated_view" position="attributes"> |
|
||||
<attribute name="attrs">{'invisible':[('report_type', 'not in', ['qweb-pdf', 'qweb-html', 'qweb-text', 'qweb-xml'])]}</attribute> |
|
||||
</button> |
|
||||
</field> |
|
||||
</record> |
|
||||
</odoo> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue