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):