diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py index c3046dda015..4528ed0a0ea 100644 --- a/odoo/addons/base/models/ir_qweb.py +++ b/odoo/addons/base/models/ir_qweb.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import print_function import ast +import copy import json import logging from collections import OrderedDict @@ -54,7 +55,38 @@ class IrQWeb(models.AbstractModel, QWeb): context = dict(self.env.context, dev_mode='qweb' in tools.config['dev_mode']) context.update(options) - return super(IrQWeb, self).render(id_or_xml_id, values=values, **context) + result = super(IrQWeb, self).render(id_or_xml_id, values=values, **context) + + if b'data-pagebreak=' not in result: + return result + + fragments = html.fragments_fromstring(result) + + for fragment in fragments: + for row in fragment.iterfind('.//tr[@data-pagebreak]'): + table = next(row.iterancestors('table')) + newtable = html.Element('table', attrib=dict(table.attrib)) + thead = table.find('thead') + if thead: + newtable.append(copy.deepcopy(thead)) + # TODO: copy caption & tfoot as well? + # TODO: move rows in a tbody if row.getparent() is one? + + pos = row.get('data-pagebreak') + assert pos in ('before', 'after') + for sibling in row.getparent().iterchildren('tr'): + if sibling is row: + if pos == 'after': + newtable.append(sibling) + break + newtable.append(sibling) + + table.addprevious(newtable) + table.addprevious(html.Element('div', attrib={ + 'style': 'page-break-after: always' + })) + + return b''.join(html.tostring(f) for f in fragments) def default_values(self): """ attributes add to the values for each computed template diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index 310c5220897..35f07840073 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -7,7 +7,8 @@ import os.path import re import sys -from lxml import etree +from lxml import etree, html +from lxml.builder import E from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase @@ -555,6 +556,81 @@ class TestQWeb(TransactionCase): template ) +class TestPageSplit(TransactionCase): + # need to explicitly assertTreesEqual because I guess it's registered for + # equality between _Element *or* HtmlElement but we're comparing a parsed + # HtmlElement and a convenience _Element + def test_split_before(self): + t = self.env['ir.ui.view'].create({ + 'name': 'test', + 'type': 'qweb', + 'arch_db': ''' +
+ + + + +
+
+
+ ''' + }) + rendered = html.fromstring(self.env['ir.qweb'].render(t.id)) + ref = E.div( + E.table(E.tr()), + E.div({'style': 'page-break-after: always'}), + E.table(E.tr({'data-pagebreak': 'before'}), E.tr()) + ) + print(etree.tostring(rendered)) + print(etree.tostring(ref)) + self.assertTreesEqual(rendered, ref) + + def test_split_after(self): + t = self.env['ir.ui.view'].create({ + 'name': 'test', + 'type': 'qweb', + 'arch_db': ''' +
+ + + + +
+
+
+ ''' + }) + rendered = html.fromstring(self.env['ir.qweb'].render(t.id)) + self.assertTreesEqual( + rendered, + E.div( + E.table(E.tr(), E.tr({'data-pagebreak': 'after'})), + E.div({'style': 'page-break-after: always'}), + E.table(E.tr()) + ) + ) + + def test_dontsplit(self): + t = self.env['ir.ui.view'].create({ + 'name': 'test', + 'type': 'qweb', + 'arch_db': ''' +
+ + + + +
+
+
+ ''' + }) + rendered = html.fromstring(self.env['ir.qweb'].render(t.id)) + self.assertTreesEqual( + rendered, + E.div(E.table(E.tr(), E.tr(), E.tr())) + ) + def load_tests(loader, suite, _): # can't override TestQWeb.__dir__ because dir() called on *class* not # instance diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 8cebf4d272e..b26c509b4bb 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -107,6 +107,8 @@ class TreeCase(unittest.TestCase): self.addTypeEqualityFunc(html.HtmlElement, self.assertTreesEqual) def assertTreesEqual(self, n1, n2, msg=None): + self.assertIsNotNone(n1, msg) + self.assertIsNotNone(n2, msg) self.assertEqual(n1.tag, n2.tag, msg) # Because lxml.attrib is an ordereddict for which order is important # to equality, even though *we* don't care @@ -116,7 +118,7 @@ class TreeCase(unittest.TestCase): self.assertEqual((n1.tail or u'').strip(), (n2.tail or u'').strip(), msg) for c1, c2 in izip_longest(n1, n2): - self.assertEqual(c1, c2, msg) + self.assertTreesEqual(c1, c2, msg) class MetaCase(type):