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
-
26report_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
-
17report_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> |
|||
<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" |
|||
string="Demo xml report" |
|||
report_type="qweb-xml" |
|||
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> |
@ -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). |
|||
|
|||
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> |
|||
* `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({ |
|||
_executeReportAction: function (action, options) { |
|||
if (action.report_type === 'qweb-xml') { |
|||
return this._triggerDownload(action, options, 'xml'); |
|||
_executeReportAction: function(action, options) { |
|||
if (action.report_type === "qweb-xml") { |
|||
return this._triggerDownload(action, options, "xml"); |
|||
} |
|||
return this._super(action, options); |
|||
}, |
|||
_makeReportUrls: function (action) { |
|||
_makeReportUrls: function(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; |
|||
}, |
|||
}); |
|||
|
@ -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