diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index bf9013a43c8..934fc5101d6 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -741,9 +741,6 @@ display: block; color: #4c4c4c; text-decoration: none; - width: 200px; - text-overflow: ellipsis; - overflow: hidden; } .openerp .oe_dropdown_menu > li > a:hover { text-decoration: none; @@ -1455,7 +1452,7 @@ display: table-row; height: inherit; } -.openerp .oe_view_manager .oe_view_manager_view_kanban { +.openerp .oe_view_manager .oe_view_manager_view_kanban:not(:empty) { height: inherit; } .openerp .oe_view_manager table.oe_view_manager_header { diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 42cfe615d21..84f0f401e83 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -632,9 +632,6 @@ $sheet-padding: 16px display: block color: #4c4c4c text-decoration: none - width: 200px - text-overflow: ellipsis - overflow: hidden &:hover text-decoration: none .oe_dropdown_arrow:after @@ -1171,7 +1168,7 @@ $sheet-padding: 16px .oe_view_manager_body display: table-row height: inherit - .oe_view_manager_view_kanban + .oe_view_manager_view_kanban:not(:empty) height: inherit table.oe_view_manager_header diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 679afe77bcd..d497cf25343 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -432,7 +432,7 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, // TODO: reorder results to match ids list return this._model.call('read', [ids, fields || false], - {context: this._model.context(options.context)}); + {context: this.get_context(options.context)}); }, /** * Read a slice of the records represented by this DataSet, based on its @@ -724,7 +724,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend({ }); }, get_domain: function (other_domain) { - this._model.domain(other_domain); + return this._model.domain(other_domain); }, alter_ids: function (ids) { this._super(ids); @@ -761,6 +761,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ this._super.apply(this, arguments); this.reset_ids([]); this.last_default_get = {}; + this.running_reads = []; }, default_get: function(fields, options) { var self = this; @@ -827,6 +828,9 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ this.to_write = []; this.cache = []; this.delete_all = false; + _.each(_.clone(this.running_reads), function(el) { + el.reject(); + }); }, read_ids: function (ids, fields, options) { var self = this; @@ -842,7 +846,6 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ to_get.push(id); } }); - var completion = $.Deferred(); var return_records = function() { var records = _.map(ids, function(id) { return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id}); @@ -877,10 +880,20 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ }, 0); }); } - completion.resolve(records); + return $.when(records); }; if(to_get.length > 0) { - var rpc_promise = this._super(to_get, fields, options).done(function(records) { + var def = $.Deferred(); + self.running_reads.push(def); + def.always(function() { + self.running_reads = _.without(self.running_reads, def); + }); + this._super(to_get, fields, options).then(function() { + def.resolve.apply(def, arguments); + }, function() { + def.reject.apply(def, arguments); + }); + return def.then(function(records) { _.each(records, function(record, index) { var id = to_get[index]; var cached = _.detect(self.cache, function(x) {return x.id === id;}); @@ -891,13 +904,11 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ cached.values = _.defaults(_.clone(cached.values), record); } }); - return_records(); + return return_records(); }); - $.when(rpc_promise).fail(function() {completion.reject();}); } else { - return_records(); + return return_records(); } - return completion.promise(); }, /** * Invalidates caching of a record in the dataset to ensure the next read diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 5d06d75573f..0d66a978311 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1540,7 +1540,7 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ context: context }).then(function (results) { if (_.isEmpty(results)) { return null; } - return [{label: _.escape(self.attrs.string)}].concat( + return [{label: self.attrs.string}].concat( _(results).map(function (result) { return { label: _.escape(result[1]), @@ -1726,7 +1726,10 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ var $name = this.$('input:first'); var private_filter = !this.$('#oe_searchview_custom_public').prop('checked'); var set_as_default = this.$('#oe_searchview_custom_default').prop('checked'); - + if (_.isEmpty($name.val())){ + this.do_warn(_t("Error"), _t("Filter name is required.")); + return false; + } var search = this.view.build_search_data(); instance.web.pyeval.eval_domains_and_contexts({ domains: search.domains, diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 33bca889dc0..12b2e241f76 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3027,7 +3027,7 @@ instance.web.form.CompletionFieldMixin = { values.push({ label: _t("Search More..."), action: function() { - dataset.name_search(search_val, self.build_domain(), 'ilike', false).done(function(data) { + dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(data) { self._search_create_popup("search", data); }); }, @@ -3649,7 +3649,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ _.extend(view.options, { addable: null, selectable: self.multi_selection, - sortable: false, + sortable: true, import_enabled: false, deletable: true }); @@ -4044,7 +4044,13 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ else return $.when(); }).done(function () { - self.handle_button(name, id, callback); + if (!self.o2m.options.reload_on_button) { + self.handle_button(name, id, callback); + }else { + self.handle_button(name, id, function(){ + self.o2m.view.reload(); + }); + } }); }, diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 74c2fc47aee..b9c789cb6a9 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -496,17 +496,21 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * * @returns {$.Deferred} promise to content reloading */ - reload_content: function () { + reload_content: synchronized(function () { var self = this; self.$el.find('.oe_list_record_selector').prop('checked', false); this.records.reset(); var reloaded = $.Deferred(); this.$el.find('.oe_list_content').append( this.groups.render(function () { - if (self.dataset.index == null && self.records.length || - self.dataset.index >= self.records.length) { + if (self.dataset.index == null) { + if (self.records.length) { self.dataset.index = 0; + } + } else if (self.dataset.index >= self.records.length) { + self.dataset.index = 0; } + self.compute_aggregates(); reloaded.resolve(); })); @@ -515,7 +519,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi limit: this._limit }); return reloaded.promise(); - }, + }), reload: function () { return this.reload_content(); }, @@ -1328,6 +1332,9 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we .removeClass('ui-icon-triangle-1-s') .addClass('ui-icon-triangle-1-e'); child.close(); + // force recompute the selection as closing group reset properties + var selection = self.get_selection(); + $(self).trigger('selected', [selection.ids, this.records]); } }); } @@ -1338,22 +1345,29 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we if (group.grouped_on) { var row_data = {}; row_data[group.grouped_on] = group; + var group_label = _t("Undefined"); var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); - if (! group_column) { - throw new Error(_.str.sprintf( - _t("Grouping on field '%s' is not possible because that field does not appear in the list view."), - group.grouped_on)); - } - var group_label; - try { - group_label = group_column.format(row_data, { - value_if_empty: _t("Undefined"), - process_modifiers: false - }); - } catch (e) { - group_label = _.str.escapeHTML(row_data[group_column.id].value); + if (group_column) { + try { + group_label = group_column.format(row_data, { + value_if_empty: _t("Undefined"), + process_modifiers: false + }); + } catch (e) { + group_label = _.str.escapeHTML(row_data[group_column.id].value); + } + } else { + group_label = group.value; + if (group_label instanceof Array) { + group_label = group_label[1]; + } + if (group_label === false) { + group_label = _t('Undefined'); + } + group_label = _.str.escapeHTML(group_label); } + // group_label is html-clean (through format or explicit // escaping if format failed), can inject straight into HTML $group_column.html(_.str.sprintf(_t("%s (%d)"), @@ -1425,14 +1439,13 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var view = this.view, limit = view.limit(), - d = new $.Deferred(), page = this.datagroup.openable ? this.page : view.page; var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name'); var options = { offset: page * limit, limit: limit, context: {bin_size: true} }; //TODO xmo: investigate why we need to put the setTimeout - $.async_when().done(function() { - dataset.read_slice(fields, options).done(function (records) { + return $.async_when().then(function() { + return dataset.read_slice(fields, options).then(function (records) { // FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently if (self.records.length) { self.records.reset(null, {silent: true}); @@ -1464,13 +1477,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we self.records.add(records, {silent: true}); list.render(); - d.resolve(list); if (_.isEmpty(records)) { view.no_result(); } + return list; }); }); - return d.promise(); }, setup_resequence_rows: function (list, dataset) { // drag and drop enabled if list is not sorted and there is a @@ -1550,11 +1562,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we self.render_groups(groups)); if (post_render) { post_render(); } }, function (dataset) { - self.render_dataset(dataset).done(function (list) { + self.render_dataset(dataset).then(function (list) { self.children[null] = list; self.elements = [list.$current.replaceAll($el)[0]]; self.setup_resequence_rows(list, dataset); + }).always(function() { if (post_render) { post_render(); } }); }); @@ -1597,6 +1610,22 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we } }); +/** + * Serializes concurrent calls to this asynchronous method. The method must + * return a deferred or promise. + * + * Current-implementation is class-serialized (the mutex is common to all + * instances of the list view). Can be switched to instance-serialized if + * having concurrent list views becomes possible and common. + */ +function synchronized(fn) { + var fn_mutex = new $.Mutex(); + return function () { + var args = _.toArray(arguments); + args.unshift(this); + return fn_mutex.exec(fn.bind.apply(fn, args)); + }; +} var DataGroup = instance.web.Class.extend({ init: function(parent, model, domain, context, group_by, level) { this.model = new instance.web.Model(model, context, domain); @@ -1608,6 +1637,10 @@ var DataGroup = instance.web.Class.extend({ }, list: function (fields, ifGroups, ifRecords) { var self = this; + if (!_.isEmpty(this.group_by)) { + // ensure group_by fields are read. + fields = _.unique((fields || []).concat(this.group_by)); + } var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by); $.when(query).done(function (querygroups) { // leaf node diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 2f9efad9171..70873f48d6f 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -163,12 +163,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie var is_loaded = 0, $this = $(this), record_id = $this.data('id'), + row_parent_id = $this.data('row-parent-id'), record = self.records[record_id], children_ids = record[self.children_field]; _(children_ids).each(function(childid) { - if (self.$el.find('#treerow_' + childid).length) { - if (self.$el.find('#treerow_' + childid).is(':hidden')) { + if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) { + if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) { is_loaded = -1; } else { is_loaded++; @@ -180,7 +181,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie self.getdata(record_id, children_ids); } } else { - self.showcontent(record_id, is_loaded < 0); + self.showcontent($this, record_id, is_loaded < 0); } }); }, @@ -192,7 +193,6 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie _(records).each(function (record) { self.records[record.id] = record; }); - var $curr_node = self.$el.find('#treerow_' + id); var children_rows = QWeb.render('TreeView.rows', { 'records': records, @@ -201,9 +201,9 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie 'fields': self.fields, 'level': $curr_node.data('level') || 0, 'render': instance.web.format_value, - 'color_for': self.color_for + 'color_for': self.color_for, + 'row_parent_id': id }); - if ($curr_node.length) { $curr_node.addClass('oe_open'); $curr_node.after(children_rows); @@ -243,14 +243,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }, // show & hide the contents - showcontent: function (record_id, show) { - this.$el.find('#treerow_' + record_id) - .toggleClass('oe_open', show); - + showcontent: function (curnode,record_id, show) { + curnode.parent('tr').toggleClass('oe_open', show); _(this.records[record_id][this.children_field]).each(function (child_id) { - var $child_row = this.$el.find('#treerow_' + child_id); + var $child_row = this.$el.find('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']'); if ($child_row.hasClass('oe_open')) { - this.showcontent(child_id, false); + $child_row.toggleClass('oe_open',show); + this.showcontent($child_row, child_id, false); } $child_row.toggle(show); }, this); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 1d44165dcb2..715ed094092 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -684,7 +684,8 @@ + t-att-data-id="record.id" t-att-data-level="level + 1" + t-att-data-row-parent-id="row_parent_id"> @@ -1651,8 +1652,8 @@

    Save current filter

    -
    -

    + +

    diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 73d984506b2..00ce48d2098 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1293,7 +1293,7 @@ openerp.testing.section('search.filters.saved', { return view.appendTo($fix) .then(function () { $fix.find('.oe_searchview_custom input#oe_searchview_custom_input') - .text("filter name") + .val("filter name") .end() .find('.oe_searchview_custom button').click(); return done.promise(); diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 9397c89de41..f00b14b75a9 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -566,8 +566,8 @@ display: inline-block !important; } .openerp_ie .oe_kanban_view .oe_kanban_group_title_vertical { -ms-writing-mode: lr-tb !important; - background: #f0eeee; - top: -5px !important; } + background: #f0eeee;} + .openerp_ie .oe_kanban_view.oe_kanban_grouped .oe_kanban_group_header { height: 1%; } diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index b97eb27315d..4569bb65486 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -584,7 +584,6 @@ .oe_kanban_group_title_vertical -ms-writing-mode: lr-tb !important background: rgb(240, 238, 238) - top: -5px !important &.oe_kanban_grouped .oe_kanban_group_header height: 1% diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 534d78c2801..5c5e6b2bf10 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -235,7 +235,11 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o); self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o); var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined; - var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(grouping_fields); + if (!_.isEmpty(grouping_fields)) { + // ensure group_by fields are read. + self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields)); + } + var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields); return self.alive($.when(grouping)).done(function(groups) { self.remove_no_result(); if (groups) { diff --git a/addons/web_tests_demo/__init__.py b/addons/web_tests_demo/__init__.py index 137d472ec74..7bed44c4154 100644 --- a/addons/web_tests_demo/__init__.py +++ b/addons/web_tests_demo/__init__.py @@ -1,6 +1,6 @@ from openerp.osv import orm, fields -class TestObject(orm.Model): +class TestObject(orm.TransientModel): _name = 'web_tests_demo.model' _columns = { diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py index 09622dadd17..e1522123dcf 100644 --- a/openerp/addons/base/ir/ir_fields.py +++ b/openerp/addons/base/ir/ir_fields.py @@ -253,7 +253,7 @@ class ir_fields_converter(orm.Model): if not isinstance(selection, (tuple, list)): # FIXME: Don't pass context to avoid translations? # Or just copy context & remove lang? - selection = selection(model, cr, uid) + selection = selection(model, cr, uid, context=None) for item, label in selection: labels = self._get_translations( cr, uid, ('selection', 'model', 'code'), label, context=context) diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 345973526c5..3a0131c2a05 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -304,5 +304,47 @@ class test_partner_recursion(common.TransactionCase): cr, uid, p1, p2, p3 = self.cr, self.uid, self.p1, self.p2, self.p3 self.assertTrue(self.res_partner.write(cr, uid, [p1,p2,p3], {'phone': '123456'})) +class test_translation(common.TransactionCase): + + def setUp(self): + super(test_translation, self).setUp() + self.res_category = self.registry('res.partner.category') + self.ir_translation = self.registry('ir.translation') + cr, uid = self.cr, self.uid + self.registry('ir.translation').load(cr, ['base'], ['fr_BE']) + self.cat_id = self.res_category.create(cr, uid, {'name': 'Customers'}) + self.ir_translation.create(cr, uid, {'name': 'res.partner.category,name', 'module':'base', + 'value': 'Clients', 'res_id': self.cat_id, 'lang':'fr_BE', 'state':'translated', 'type': 'model'}) + + def test_101_create_translated_record(self): + cr, uid = self.cr, self.uid + + no_context_cat = self.res_category.browse(cr, uid, self.cat_id) + self.assertEqual(no_context_cat.name, 'Customers', "Error in basic name_get") + + fr_context_cat = self.res_category.browse(cr, uid, self.cat_id, context={'lang':'fr_BE'}) + self.assertEqual(fr_context_cat.name, 'Clients', "Translation not found") + + def test_102_duplicate_record(self): + cr, uid = self.cr, self.uid + self.new_cat_id = self.res_category.copy(cr, uid, self.cat_id, context={'lang':'fr_BE'}) + + no_context_cat = self.res_category.browse(cr, uid, self.new_cat_id) + self.assertEqual(no_context_cat.name, 'Customers', "Duplication did not set untranslated value") + + fr_context_cat = self.res_category.browse(cr, uid, self.new_cat_id, context={'lang':'fr_BE'}) + self.assertEqual(fr_context_cat.name, 'Clients', "Did not found translation for initial value") + + def test_103_duplicate_record_fr(self): + cr, uid = self.cr, self.uid + self.new_fr_cat_id = self.res_category.copy(cr, uid, self.cat_id, default={'name': 'Clients (copie)'}, context={'lang':'fr_BE'}) + + no_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id) + self.assertEqual(no_context_cat.name, 'Clients (copie)', "Duplication with default value not applied") + + fr_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id, context={'lang':'fr_BE'}) + self.assertEqual(fr_context_cat.name, 'Clients', "Did not found translation for initial value") + + if __name__ == '__main__': unittest2.main() diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 005918966e6..f6face163f6 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -5016,7 +5016,6 @@ class BaseModel(object): # TODO it seems fields_get can be replaced by _all_columns (no need for translation) fields = self.fields_get(cr, uid, context=context) - translation_records = [] for field_name, field_def in fields.items(): # we must recursively copy the translations for o2o and o2m if field_def['type'] == 'one2many': @@ -5030,22 +5029,31 @@ class BaseModel(object): target_obj.copy_translations(cr, uid, old_child, new_child, context=context) # and for translatable fields we keep them for copy elif field_def.get('translate'): - trans_name = '' + if field_name in self._columns: trans_name = self._name + "," + field_name + res_id = new_id + elif field_name in self._inherit_fields: trans_name = self._inherit_fields[field_name][0] + "," + field_name - if trans_name: - trans_ids = trans_obj.search(cr, uid, [ - ('name', '=', trans_name), - ('res_id', '=', old_id) - ]) - translation_records.extend(trans_obj.read(cr, uid, trans_ids, context=context)) + # get the id of the parent record to set the translation + inherit_field_name = self._inherit_fields[field_name][1] + res_id = self.read(cr, uid, [new_id], [inherit_field_name], context=context)[0][inherit_field_name][0] - for record in translation_records: - del record['id'] - record['res_id'] = new_id - trans_obj.create(cr, uid, record, context=context) + else: + continue + + trans_ids = trans_obj.search(cr, uid, [ + ('name', '=', trans_name), + ('res_id', '=', old_id) + ]) + records = trans_obj.read(cr, uid, trans_ids, context=context) + for record in records: + del record['id'] + # remove source to avoid triggering _set_src + del record['source'] + record.update({'res_id': res_id}) + trans_obj.create(cr, uid, record, context=context) def copy(self, cr, uid, id, default=None, context=None): diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index 2052b128ad9..6a35e3a3806 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -631,6 +631,18 @@ class _rml_Illustration(platypus.flowables.Flowable): drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title) drw.render(self.canv, None) +# Workaround for issue #15: https://bitbucket.org/rptlab/reportlab/issue/15/infinite-pages-produced-when-splitting +original_pto_split = platypus.flowables.PTOContainer.split +def split(self, availWidth, availHeight): + res = original_pto_split(self, availWidth, availHeight) + if len(res) > 2 and len(self._content) > 0: + header = self._content[0]._ptoinfo.header + trailer = self._content[0]._ptoinfo.trailer + if isinstance(res[-2], platypus.flowables.UseUpSpace) and len(header + trailer) == len(res[:-2]): + return [] + return res +platypus.flowables.PTOContainer.split = split + class _rml_flowable(object): def __init__(self, doc, localcontext, images=None, path='.', title=None, canvas=None): if images is None: @@ -1012,11 +1024,16 @@ class _rml_template(object): # Reset Page Number with new story tag fis.append(PageReset()) story_cnt += 1 - if self.localcontext and self.localcontext.get('internal_header',False): - self.doc_tmpl.afterFlowable(fis) - self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) - else: - self.doc_tmpl.build(fis) + try: + if self.localcontext and self.localcontext.get('internal_header',False): + self.doc_tmpl.afterFlowable(fis) + self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas) + else: + self.doc_tmpl.build(fis) + except platypus.doctemplate.LayoutError, e: + e.name = 'Print Error' + e.value = 'The document you are trying to print contains a table row that does not fit on one page. Please try to split it in smaller rows or contact your administrator.' + raise def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None): node = etree.XML(rml)