From d3b9953791ce98ec7355c4b16475888f14e77ec5 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 1 Jun 2015 12:43:42 +0200 Subject: [PATCH 01/25] Add module report_xml. --- report_xml/README.rst | 82 + report_xml/__init__.py | 19 + report_xml/__openerp__.py | 32 + report_xml/controllers.py | 40 + report_xml/models.py | 70 + report_xml/static/description/icon.png | Bin 0 -> 4328 bytes report_xml/static/description/icon.svg | 3121 ++++++++++++++++++++++++ 7 files changed, 3364 insertions(+) create mode 100644 report_xml/README.rst create mode 100644 report_xml/__init__.py create mode 100644 report_xml/__openerp__.py create mode 100644 report_xml/controllers.py create mode 100644 report_xml/models.py create mode 100644 report_xml/static/description/icon.png create mode 100644 report_xml/static/description/icon.svg diff --git a/report_xml/README.rst b/report_xml/README.rst new file mode 100644 index 00000000..0a3b88ab --- /dev/null +++ b/report_xml/README.rst @@ -0,0 +1,82 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +Qweb XML Reports +================ + +This module was written to extend the functionality of the reporting engine to +support XML reports and allow modules to generate them by code or by QWeb +templates. + +Installation +============ + +To install this module, you need to: + +* Install the repository `reporting-engine`_. + +Configuration +============= + +No manual configuration is needed. + +Usage +===== + +This module is technical, so its usage instructions are intended for module +developers. + +To use this module, you need to: + +* Create a module. +* Make it depend on this one. +* Follow `instructions to create reports`_ having in mind that the + ``report_type`` field in your ``ir.actions.report.xml`` record must be + ``qweb-xml``. + +In case you want to create a `custom report`_, the instructions remain the same +as for HTML reports, and the method that you must override is also called +``render_html``, even when this time you are creating a XML report. + +You can visit http:///report/xml// +to see your XML report online as a web page. + +For further information, please visit: + +* https://www.odoo.com/forum/help-1 +* https://github.com/OCA/reporting-engine + +Known issues / Roadmap +====================== + +None + +Credits +======= + +* Icon taken from http://commons.wikimedia.org/wiki/File:Text-xml.svg. + +Contributors +------------ + +* Jairo Llopis + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. + + +.. _reporting-engine: https://github.com/OCA/reporting-engine +.. _instructions to create reports: https://www.odoo.com/documentation/8.0/reference/reports.html +.. _custom report: https://www.odoo.com/documentation/8.0/reference/reports.html#custom-reports diff --git a/report_xml/__init__.py b/report_xml/__init__.py new file mode 100644 index 00000000..809441f6 --- /dev/null +++ b/report_xml/__init__.py @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- + +# Odoo, Open Source Management Solution +# Copyright (C) 2014-2015 Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from . import controllers, models diff --git a/report_xml/__openerp__.py b/report_xml/__openerp__.py new file mode 100644 index 00000000..ed9a6eeb --- /dev/null +++ b/report_xml/__openerp__.py @@ -0,0 +1,32 @@ +# -*- encoding: utf-8 -*- + +# Odoo, Open Source Management Solution +# Copyright (C) 2014-2015 Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +{ + "name": "Qweb XML Reports", + "version": "1.0", + "category": "Reporting", + "author": "Odoo Community Association (OCA), Grupo ESOC", + "license": "AGPL-3", + "website": "https://odoo-community.org/", + "installable": True, + "application": False, + "summary": "Allow to generate XML reports", + "depends": [ + "report", + ], +} diff --git a/report_xml/controllers.py b/report_xml/controllers.py new file mode 100644 index 00000000..a8ffb04f --- /dev/null +++ b/report_xml/controllers.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- + +# Odoo, Open Source Management Solution +# Copyright (C) 2014-2015 Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from openerp.http import route +from openerp.addons.report.controllers import main as report + + +class ReportController(report.ReportController): + @route() + def report_routes(self, reportname, docids=None, converter=None, **data): + # Trick the main reporter to think we want an HTML report + new_converter = converter if converter != "xml" else "html" + response = super(ReportController, self).report_routes( + reportname, docids, new_converter, **data) + + # If it was an XML report, just download the generated response + if converter == "xml": + # XML header must be before any spaces, and it is a common error, + # so let's fix that here and make developers happier + response.data = response.data.strip() + + # XML files should be downloaded + response.headers.set("Content-Type", "text/xml") + + return response diff --git a/report_xml/models.py b/report_xml/models.py new file mode 100644 index 00000000..168e4de9 --- /dev/null +++ b/report_xml/models.py @@ -0,0 +1,70 @@ +# -*- encoding: utf-8 -*- + +# Odoo, Open Source Management Solution +# Copyright (C) 2014-2015 Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from openerp import api, fields, models + + +class ReportAction(models.Model): + _inherit = "ir.actions.report.xml" + + report_type = fields.Selection(selection_add=[("qweb-xml", "XML")]) + + def _lookup_report(self, cr, name): + """Enable ``qweb-xml`` report lookup.""" + try: + super(ReportAction, self)._lookup_report(cr, name) + except Exception as ex: + # Somebody thought it was a good idea to use standard exceptions + if "qweb-xml" not in ex.message: + raise ex + else: + cr.execute( + "SELECT * FROM ir_act_report_xml WHERE report_name=%s", + (name,)) + return cr.dictfetchone()["report_name"] + + @api.model + def render_report(self, res_ids, name, data): + """Special handling for ``qweb-xml`` reports.""" + if data.get("report_type") == u"qweb-xml": + new_report = self._lookup_report(name) + recs = self.env[self.env.context["active_model"]].browse(res_ids) + result = self.env["report"].get_html(recs, new_report, data=data) + + # XML with spaces before the AS59n3B5_s&=I5v_fjq$RC@1H zLs5iFM3w#VD=Yv2fZfOtVosag{&c2` zv=OtMW=WeEa9|@#CRz(&a*L+znK6d8H~`>6<)3~bo)LeSCKSZ$+u$wG?s$I}EDYf9 z@2>#&M&O_>7?=VY>+v^5^9l{b@dsoH$0K0?JS+e&8h}F9tfa`&Gy;D#y1sZ%G!|fh zb@2{@;}9;Q78tZQ4u#h2 z!=SNv1)QIUwkV)GevxLF|BqokEX?JPRZ&Yg3I;ItMFxs0D=MnnhhPI}5cB^aXiOj$ z?%|0C=;?{-;_+CxtM8v9@)n-7py(pu2$<;qyoLUEn;#5{MxkgN|HKG{ghOF8x;T() zpghh6q@th*a*DKT0{}SXjUc*~0a+W_fl0!Xp(LHnc1lk6?EI|0ospq1Fv{(!bF_@8 zLOjX`A|P|=NfnQD8NZC$r!`pR?dm+o^7Z5R z>0K8M)iKMz`Xgo>0HX?Iu$-%{E%mS5Mj*_*n}R2zR%S#B~;)&;x89X0`sT*)BYBR z>}fzBVEUg8hTYV@#BDNSA^3WGyBh2y0Lfb~=#CDibes&6xxpE~Q18s94SqH}AOb7V|S zTd$|5t?G=?VOrlfZm`TKHC5*N1;wrWm1gy#v|qR0@hsQN z{aVD$BsUDU;Vypk+T4j~7O<(N(0*we6T~ zJ|$bDTgwGIQ&bGSJEzEGM3^2M|ZAme8w8pl9h7i>7ziOy#9s+~FudC_CoQ~qAOSQVj+`Lk9_mUM8d6||2MNlYI zq4|w;dHeo1X|lExD;?PC@tML#3hyruNSmqy2C zyK0I|RwVmeF*vIo_?iA0q&rVO z^Zl-aT#@Id;n;X>5@ozaJ)lrWrzHMSVo1A#{r4B5W)+RSxht}t^v5&{EsvdE>3z4J z$m0zXUrM5^-@6gT4ZxE*@! z)2_f3k^WsxxnaBFK_s2XK$`MqkYR?GR^BUlV52Hb`ez&Ujl--RBK1{W$*a1?lAk|4 zOKNK)X)L6Qm1hHxL3?BOQ=0L+4N=c;?4{3XNi?pM3rjco%`Xx=f)@ewk@QC$FE@{y za)kD{$RvT$Ig;Ck5d6mTBz&QHJh#oU`H=T?cAMalLY6q;9yF}FO09SRls?EnU}NW0 zxuq#w35<}Zdj`F+V+BlQ*%`XFCFY{0;xw|q?M@BP)q<6*22f?bRwhiEV!D}I*9xYW za5D*@RK1fHgjvkx{`YhB{8a(l%cl={!Oou}uDvTSXBT4_ViA5lWZq;UXh0&VVOJDS z4mO||jA$JRdI#clFtl0C*CmuXf|Li#eQAoho;wF_)4EclXFVnocQDrB{wc?CJ;cYO z;>TaXl(~1$x%%mnh}ZA7ZWJNY4&536;gN;+YPiRu8Rj=JWY&n0=A9Sr z8!fD~aDT6#c4FkR7n#ROVMLXqWJs?2zOVDvE!@4zVw8xD{%u1m+mGYL5OEWYZXBns zcRDtIFmi)2+kKtsJPc*9GEu*7=3d2%DKYiA%9Y&d31%$PY0f)GGF)=6dm0S4!$Rv9 z3R1zw5tX;Q`DflJ0*#)smR1e@o5Iti6COzX>$ZROlM20@21TGXyxzKvWtf=W-jMo& zPaOR%3ZC%LH%W!9_DS#T-HMf~1IK-_aAxyMUBW;QbC7B!7f_nNBz1>S$JcowFx+a! zhD~Zeb1N=Q=-hRmYi#O;O+c7f>v^M+#qD<%VoAeO{F5dHWKL_|nHww1x5p}_FDlUo zBc`Q8FXq?cr7f@hRzJ!#%y6jTa2tJ~avLa19TMiz+_>k*;(_a&ZR~^QjfX{uB_#g`;PZ?47%H%bm)E8@n(GAvG=G79U`~WE;uvXEAeCU-AmjqPw8%@ zJag_(EmoC1?{mAHFfml)8AI0lo{i1Wh%&UJl?v6%GDmNSRQ{cRxOumycjw^Eej}u! z+{9Z~p}iDKrx6qCi0&AsUo>6R4u2{(L~2Zz8fhm*P|{6H-9XuRV)} zu6^ZEP4=X*&@^oDt)f5@P_ij`X@c&ANV=&{>ew-b{EpvZBevG^tDHK0teKd5WK9j* zPdFBn;cG6b`Y>*jqTCvh{BCXLqJp$@bG!s0BjmZ~lZlh?_)dhq7`>R;k1)9=^*+>3 z8^T#~FiU%n=Z68^FZ|cazHFH})K?Hi5onhA72<+Nj?1t4f|o>Ip5VxvyX)DD{9i}{ z387~N|Kx{(g1_o@Wn`~i1uttK!Uin2cZkbFMf?(`_K&r5vLWTa)!h77WvX9264^fZ zX~K9@szKD<8MI(RmSbRwS+|YxUG-qily7H3oLwUV;d@=VOYOhs1sE?AFV`Ow*F2sW zN^q7oS^mT)AtBMWJF-wi8XYw=E3er#DViU=D7`OCNjy>FiA?sH?KlX7*z*Gp_FqDB z?ZkXK5BmaCL5CAU305v5kJq$q1&Vv+*0uQ)7VQuYsul+W9Sb~?an0;m!4}F79Ltq? zBAYZQoa}%Hy4Q1k%Jb>1+e&&5P`9d($ZpUMUUHKidJ!I zDIrRab;0N*tM1^{zcZknOj`RFPhvGN-XU^8;=w=&HRrB<(nFBjAVD{K_b~*kTT!(C zEf`oRLNr9$-AmHOou7GyhHhRV7`%C^Gb>d*p1qe99=6|fdmzRPV`%^naQ~pWs~?-t zwnc`9fgiP=NlbwiXOApz!^aV8r= z9nS;ZJ`I6C`H{bKheajU)$-5EMYiye z%An$RO9RQ9m8h$$@Vne&Z3L(Ac**fMOLbagZMCXba{IdUt#a>3ZWg6+do569yVx0-dHuwGMnxGuc$ma3fLte4|- zc>FQB@saU?c8!qflzwhmrS-z)^S>Y6C+tN~yW)l4fO6Fh--($h*kxY@W6y6UP{LU^ zsW~z+Yue$UsTa8%Y!L3=`pPv{>-*lPm8T_W z&@5*rdp^Ok^jYYc#27#8^WM_)MIj7Ep<=oc&OI zF1Xm9oD%yZP5YV3Ltov(?v5qIm?D(0@HpXv4CSB_60c87G4xR!+_GNR9s1V)Vws&i z;!J*ETt|R;*Xi7EYsj6wEjLV7k+0{NmWjRHX|r!<=l1{ZXE6KP{*Fg?tWYEy(l$+H z?;NmliG5v=HY$=59?abB6<6#}x{wjDwO6y`vmJg+FC`LvzKpCM%Q!wHbMo=CJNGBP zCs@@>j3`b%vCSM?)e@#K6n75|Jzz^`f0?j3^#M8uyX+&rp7=`HG=Lc=Xfe}siA84+ zPX}lI*9}5^ce*7hnIGzO&g2_vGy54=!>+farnWZqIwmaE{5yLOk3NLU-!-=D?7rt( z)^TT9#@i!W;#WKGr+-$Gi-OHc0!QkGYXR{e&gz<%-ajH|`4#V`RaYw|UQ;tWXk}#O zs|xUeex7oXd{#ZncP$6MSnF=+BT7hK z{W?n7^f%`v+P6L^h;K4+W<@e3q1iK5eU(7_Z@wCI+lVe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + July 2009 + + + Franziska Sponsel + + + + + Franziska Sponsel + + + + + RRZE + + + + + export + csv + text + + + + + Hendrik Eggers, Beate Kaspar + + + uses <http://ftp.uni-erlangen.de/pub/rrze/tango/rrze-icon-set/tango/scalable/emblems/report.svg> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c7ff1bf38b7347b12a8b78591746f55c7a433fb3 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 12 Jun 2015 11:09:09 +0200 Subject: [PATCH 02/25] Add template utf8_header. Almost any XML must start with this. Let's make it easier. --- report_xml/__openerp__.py | 3 +++ report_xml/views/report_xml_templates.xml | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 report_xml/views/report_xml_templates.xml diff --git a/report_xml/__openerp__.py b/report_xml/__openerp__.py index ed9a6eeb..e2f73bee 100644 --- a/report_xml/__openerp__.py +++ b/report_xml/__openerp__.py @@ -29,4 +29,7 @@ "depends": [ "report", ], + "data": [ + "views/report_xml_templates.xml", + ] } diff --git a/report_xml/views/report_xml_templates.xml b/report_xml/views/report_xml_templates.xml new file mode 100644 index 00000000..8a7c88ab --- /dev/null +++ b/report_xml/views/report_xml_templates.xml @@ -0,0 +1,13 @@ + + + + + + + + From 58d75b9e9907b0bc27fe1d037fa6f434ad74393a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 12 Jun 2015 11:10:00 +0200 Subject: [PATCH 03/25] Easier XSD checking reports. Any report inheriting this AbstractModel can check its XML results against any XSD. --- report_xml/README.rst | 2 ++ report_xml/models.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/report_xml/README.rst b/report_xml/README.rst index 0a3b88ab..b0969641 100644 --- a/report_xml/README.rst +++ b/report_xml/README.rst @@ -13,6 +13,7 @@ Installation To install this module, you need to: +* Install lxml_ in Odoo's ``$PYTHONPATH``. * Install the repository `reporting-engine`_. Configuration @@ -80,3 +81,4 @@ To contribute to this module, please visit http://odoo-community.org. .. _reporting-engine: https://github.com/OCA/reporting-engine .. _instructions to create reports: https://www.odoo.com/documentation/8.0/reference/reports.html .. _custom report: https://www.odoo.com/documentation/8.0/reference/reports.html#custom-reports +.. _lxml: http://lxml.de/ diff --git a/report_xml/models.py b/report_xml/models.py index 168e4de9..47e1b874 100644 --- a/report_xml/models.py +++ b/report_xml/models.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from lxml import etree from openerp import api, fields, models @@ -68,3 +69,33 @@ class ReportGenerator(models.Model): return self.env["ir.actions.report.xml"].search( [("report_type", "=", "qweb-xml"), ("report_name", "=", report_name)])[0] + + +class XSDCheckedReport(models.AbstractModel): + """Check XML report against a XSD schema before downloading it. + + This is an Abstract Model to be inherited by the real report models, which + must implement :meth:`.xsd` and have a ``_name`` in the form + ``report..``. + """ + _name = "report_xml.xsd_checked_report" + _description = "Base model for reports that need XSD checking" + + @api.multi + def xsd(self): + """Return the XSD schema contents.""" + raise NotImplementedError + + @api.multi + def render_html(self, data=None): + """Return the XML report after checking it against an XSD.""" + docargs = {"docs": (self.env[self.env.context["active_model"]] + .browse(self.env.context["active_ids"]))} + xsd = etree.XMLSchema(etree.XML(self.xsd())) + parser = etree.XMLParser(schema=xsd) + result = (self.env["report"] + .render(self._name[len("report."):], docargs) + .strip()) + etree.fromstring(result, parser) + + return result From 8d165525512aa992854af19fc23e07a2bb5653f6 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 12 Jun 2015 11:15:17 +0200 Subject: [PATCH 04/25] Add sample module. --- report_xml/README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/report_xml/README.rst b/report_xml/README.rst index b0969641..e8d2b380 100644 --- a/report_xml/README.rst +++ b/report_xml/README.rst @@ -42,6 +42,9 @@ as for HTML reports, and the method that you must override is also called You can visit http:///report/xml// to see your XML report online as a web page. +If you are a developer and you want a sample module to know how to use this +reporting engine, you can review `l10n_es_training`_. + For further information, please visit: * https://www.odoo.com/forum/help-1 @@ -81,4 +84,5 @@ To contribute to this module, please visit http://odoo-community.org. .. _reporting-engine: https://github.com/OCA/reporting-engine .. _instructions to create reports: https://www.odoo.com/documentation/8.0/reference/reports.html .. _custom report: https://www.odoo.com/documentation/8.0/reference/reports.html#custom-reports +.. _l10n_es_training: https://github.com/grupoesoc/l10n-spain/tree/l10n_es_training/l10n_es_training .. _lxml: http://lxml.de/ From f0ac3309f3425bb944d04d7f3e02a77501cc6ad0 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 12 Jun 2015 11:15:28 +0200 Subject: [PATCH 05/25] Sort links. --- report_xml/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/report_xml/README.rst b/report_xml/README.rst index e8d2b380..6849c1e2 100644 --- a/report_xml/README.rst +++ b/report_xml/README.rst @@ -81,8 +81,8 @@ promote its widespread use. To contribute to this module, please visit http://odoo-community.org. -.. _reporting-engine: https://github.com/OCA/reporting-engine -.. _instructions to create reports: https://www.odoo.com/documentation/8.0/reference/reports.html .. _custom report: https://www.odoo.com/documentation/8.0/reference/reports.html#custom-reports +.. _instructions to create reports: https://www.odoo.com/documentation/8.0/reference/reports.html +.. _reporting-engine: https://github.com/OCA/reporting-engine .. _l10n_es_training: https://github.com/grupoesoc/l10n-spain/tree/l10n_es_training/l10n_es_training .. _lxml: http://lxml.de/ From fb428656102dcf1e4a7fbe6d9a8eb820fe14c692 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 12 Jun 2015 11:39:14 +0200 Subject: [PATCH 06/25] Fix XML tag mismatch. --- report_xml/views/report_xml_templates.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report_xml/views/report_xml_templates.xml b/report_xml/views/report_xml_templates.xml index 8a7c88ab..facfc328 100644 --- a/report_xml/views/report_xml_templates.xml +++ b/report_xml/views/report_xml_templates.xml @@ -9,5 +9,5 @@ - + From 47bac44eb3d14bc4ee206ea24a9e69a563067ca1 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 15 Jun 2015 11:49:43 +0200 Subject: [PATCH 07/25] Allow docargs to be loaded from context. --- report_xml/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/report_xml/models.py b/report_xml/models.py index 47e1b874..498346ad 100644 --- a/report_xml/models.py +++ b/report_xml/models.py @@ -89,8 +89,9 @@ class XSDCheckedReport(models.AbstractModel): @api.multi def render_html(self, data=None): """Return the XML report after checking it against an XSD.""" - docargs = {"docs": (self.env[self.env.context["active_model"]] - .browse(self.env.context["active_ids"]))} + docargs = self.env.context.get("docargs", dict()) + docargs["docs"] = (self.env[self.env.context["active_model"]] + .browse(self.env.context["active_ids"])) xsd = etree.XMLSchema(etree.XML(self.xsd())) parser = etree.XMLParser(schema=xsd) result = (self.env["report"] From 3412893bd2c81bf6ffe9a6fdc34c394e903f78c3 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 15 Jun 2015 12:35:39 +0200 Subject: [PATCH 08/25] Only replace the docs key if it is missing. --- report_xml/models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/report_xml/models.py b/report_xml/models.py index 498346ad..c8234238 100644 --- a/report_xml/models.py +++ b/report_xml/models.py @@ -88,11 +88,16 @@ class XSDCheckedReport(models.AbstractModel): @api.multi def render_html(self, data=None): - """Return the XML report after checking it against an XSD.""" + """Return the XML report after checking it against an XSD. + + If ``context`` contains a dict called ``docargs``, it will be used as + the Qweb context. The special key ``docs`` will be added to ``docargs`` + automatically if missing. docargs = self.env.context.get("docargs", dict()) - docargs["docs"] = (self.env[self.env.context["active_model"]] - .browse(self.env.context["active_ids"])) xsd = etree.XMLSchema(etree.XML(self.xsd())) + if "docs" not in docargs: + docargs["docs"] = (self.env[self.env.context["active_model"]] + .browse(self.env.context["active_ids"])) parser = etree.XMLParser(schema=xsd) result = (self.env["report"] .render(self._name[len("report."):], docargs) From c73f0a8e9c05bc49d1c72a6b88cc78bc71575745 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 15 Jun 2015 12:36:01 +0200 Subject: [PATCH 09/25] Clearer code comments, add logging. --- report_xml/models.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/report_xml/models.py b/report_xml/models.py index c8234238..c83017a7 100644 --- a/report_xml/models.py +++ b/report_xml/models.py @@ -16,10 +16,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging from lxml import etree from openerp import api, fields, models +_logger = logging.getLogger(__name__) + + class ReportAction(models.Model): _inherit = "ir.actions.report.xml" @@ -93,15 +97,29 @@ class XSDCheckedReport(models.AbstractModel): If ``context`` contains a dict called ``docargs``, it will be used as the Qweb context. The special key ``docs`` will be added to ``docargs`` automatically if missing. + """ + # Qweb context docargs = self.env.context.get("docargs", dict()) - xsd = etree.XMLSchema(etree.XML(self.xsd())) if "docs" not in docargs: docargs["docs"] = (self.env[self.env.context["active_model"]] .browse(self.env.context["active_ids"])) + + # Load XSD + xsd = etree.XML(self.xsd()) + _logger.debug("XSD schema contents: %s", etree.tostring(xsd)) + xsd = etree.XMLSchema(xsd) parser = etree.XMLParser(schema=xsd) + + # Generate XML report result = (self.env["report"] .render(self._name[len("report."):], docargs) .strip()) - etree.fromstring(result, parser) + + # Validate XML with XSD + try: + etree.fromstring(result, parser) + except Exception as error: + _logger.error(result) + raise error return result From 519a02e5eb8c16e4063cc2f075562bbfcfad0ba1 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 17 Jun 2015 11:03:40 +0200 Subject: [PATCH 10/25] Oops, this header belongs to other module. --- report_xml/views/report_xml_templates.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/report_xml/views/report_xml_templates.xml b/report_xml/views/report_xml_templates.xml index facfc328..811eddec 100644 --- a/report_xml/views/report_xml_templates.xml +++ b/report_xml/views/report_xml_templates.xml @@ -4,9 +4,7 @@ From 0d933c89968f9430c957322daf50021763c5d532 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 17 Jun 2015 11:09:52 +0200 Subject: [PATCH 11/25] Add module report_xml_sample. --- report_xml/README.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/report_xml/README.rst b/report_xml/README.rst index 6849c1e2..564bedf3 100644 --- a/report_xml/README.rst +++ b/report_xml/README.rst @@ -16,6 +16,9 @@ To install this module, you need to: * Install lxml_ in Odoo's ``$PYTHONPATH``. * Install the repository `reporting-engine`_. +But this module does nothing for the end user by itself, so if you have it +installed it's probably because there is another module that depends on it. + Configuration ============= @@ -24,10 +27,18 @@ No manual configuration is needed. Usage ===== -This module is technical, so its usage instructions are intended for module -developers. +If you are a user +----------------- + +You will be able to download XML reports from the *Print* menu found on form +and list views. + +If you are a developer +---------------------- -To use this module, you need to: +To learn from an example, just check the `sample module`_. + +To develop with this module, you need to: * Create a module. * Make it depend on this one. @@ -39,11 +50,13 @@ In case you want to create a `custom report`_, the instructions remain the same as for HTML reports, and the method that you must override is also called ``render_html``, even when this time you are creating a XML report. -You can visit http:///report/xml// -to see your XML report online as a web page. +You can make your custom report inherit ``report_xml.xsd_checked_report``, name +it like your XML `` - diff --git a/report_xml/models/__init__.py b/report_xml/models/__init__.py index e8588766..c5675429 100644 --- a/report_xml/models/__init__.py +++ b/report_xml/models/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). from . import report_action -from . import report_generator diff --git a/report_xml/models/report_action.py b/report_xml/models/report_action.py index f6e4f3ce..573163f9 100644 --- a/report_xml/models/report_action.py +++ b/report_xml/models/report_action.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (C) 2014-2015 Grupo ESOC -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). import logging @@ -11,39 +11,30 @@ _logger = logging.getLogger(__name__) class ReportAction(models.Model): - _inherit = "ir.actions.report.xml" + _inherit = "ir.actions.report" report_type = fields.Selection(selection_add=[("qweb-xml", "XML")]) - def _lookup_report(self, name): - """Enable ``qweb-xml`` report lookup.""" - try: - return super(ReportAction, self)._lookup_report(name) - except Exception as ex: - # Somebody thought it was a good idea to use standard exceptions - if "qweb-xml" not in ex.message: - raise ex - else: - self._cr.execute( - "SELECT * FROM ir_act_report_xml WHERE report_name=%s", - (name,)) - return self._cr.dictfetchone()["report_name"] + @api.model + def _get_report_from_name(self, report_name): + res = super(ReportAction, self)._get_report_from_name(report_name) + if res: + return res + report_obj = self.env['ir.actions.report'] + qwebtypes = ['qweb-xml'] + conditions = [('report_type', 'in', qwebtypes), + ('report_name', '=', report_name)] + context = self.env['res.users'].context_get() + return report_obj.with_context(context).search(conditions, limit=1) @api.model - def render_report(self, res_ids, name, data): - """Special handling for ``qweb-xml`` reports.""" - xml_report = self.search([('report_name', '=', name), - ('report_type', '=', 'qweb-xml')], limit=1) - if xml_report: - xml_report = xml_report.ensure_one() - result = self.env["report"].get_html(res_ids, - xml_report.report_name, - data=data) - return ( - etree.tostring( - etree.fromstring(result.strip()), - encoding='UTF-8', xml_declaration=True, pretty_print=True - ), "xml") - else: - return super(ReportAction, self).render_report( - res_ids, name, data) + def render_qweb_xml(self, docids, data): + result = self.render_qweb_html(docids, data=data) + return etree.tostring( + etree.fromstring( + str(result[0], 'UTF-8').lstrip('\n').lstrip().encode('UTF-8') + ), + encoding='UTF-8', + xml_declaration=True, + pretty_print=True + ), "xml" diff --git a/report_xml/models/report_generator.py b/report_xml/models/report_generator.py deleted file mode 100644 index af2d9145..00000000 --- a/report_xml/models/report_generator.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2014-2015 Grupo ESOC -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -import logging - -from odoo import api, models - -_logger = logging.getLogger(__name__) - - -class ReportGenerator(models.Model): - _inherit = "report" - - @api.model - def _get_report_from_name(self, report_name): - res = super(ReportGenerator, self)._get_report_from_name(report_name) - if res: - return res - report_obj = self.env['ir.actions.report.xml'] - qwebtypes = ['qweb-xml'] - conditions = [('report_type', 'in', qwebtypes), - ('report_name', '=', report_name)] - context = self.env['res.users'].context_get() - return report_obj.with_context(context).search(conditions, limit=1) diff --git a/report_xml/static/src/js/report/qwebactionmanager.js b/report_xml/static/src/js/report/qwebactionmanager.js new file mode 100644 index 00000000..01dda4c3 --- /dev/null +++ b/report_xml/static/src/js/report/qwebactionmanager.js @@ -0,0 +1,41 @@ +odoo.define('report_xml.report', function(require){ +'use strict'; + +var ActionManager= require('web.ActionManager'); +var crash_manager = require('web.crash_manager'); +var framework = require('web.framework'); + +ActionManager.include({ + ir_actions_report: function (action, options){ + var self = this; + action = _.clone(action); + if (action.report_type === 'qweb-xml') { + framework.blockUI() + var report_xml_url = 'report/xml/' + action.report_name; + if(action.context.active_ids){ + report_xml_url += '/' + action.context.active_ids.join(','); + } + else{ + report_xml_url += '?options=' + encodeURIComponent(JSON.stringify(action.data)); + report_xml_url += '&context=' + encodeURIComponent(JSON.stringify(action.context)); + } + self.getSession().get_file({ + url: report_xml_url, + data: {data: JSON.stringify([ + report_xml_url, + action.report_type, + ])}, + error: crash_manager.rpc_error.bind(crash_manager), + success: function (){ + if(action && options && !action.dialog){ + options.on_close(); + } + }, + }); + framework.unblockUI(); + return + } + return self._super(action, options); + } +}); +}); \ No newline at end of file diff --git a/report_xml/tests/__init__.py b/report_xml/tests/__init__.py index 61e68940..c89ef056 100644 --- a/report_xml/tests/__init__.py +++ b/report_xml/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). from . import test_report_xml diff --git a/report_xml/tests/test_report_xml.py b/report_xml/tests/test_report_xml.py index 7880eb90..8bc8592f 100644 --- a/report_xml/tests/test_report_xml.py +++ b/report_xml/tests/test_report_xml.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2017 Creu Blanca -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl). from lxml import etree from odoo.tests import common @@ -8,17 +8,12 @@ from odoo.tests import common class TestXmlReport(common.TransactionCase): def test_xml(self): - report_object = self.env['ir.actions.report.xml'] + report_object = self.env['ir.actions.report'] report_name = 'report_xml.demo_report_xml_view' - self.assertEqual( - report_name, report_object._lookup_report(report_name)) + report = report_object._get_report_from_name(report_name) docs = self.env['res.company'].search([], limit=1) - rep = report_object.render_report( - docs.ids, report_name, {} - ) + self.assertEqual(report.report_type, 'qweb-xml') + rep = report.render(docs.ids, {}) root = etree.fromstring(rep[0]) el = root.xpath('/root/user/name') - self.assertEqual( - el[0].text, - docs.ensure_one().name - ) + self.assertEqual(el[0].text, docs.ensure_one().name) diff --git a/report_xml/views/report_xml_templates.xml b/report_xml/views/report_xml_templates.xml index ef5aabfa..0d38a56e 100644 --- a/report_xml/views/report_xml_templates.xml +++ b/report_xml/views/report_xml_templates.xml @@ -1,9 +1,9 @@ - + diff --git a/report_xml/views/webclient_templates.xml b/report_xml/views/webclient_templates.xml new file mode 100644 index 00000000..7af6bbf1 --- /dev/null +++ b/report_xml/views/webclient_templates.xml @@ -0,0 +1,9 @@ + + +