From 494f1686c3127658b38c845da3ffe0f6b634fa28 Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Tue, 13 May 2014 17:56:10 +0200 Subject: [PATCH 001/578] Use NamedTemporaryFile instead of file and of deprecated mktemp. That way we ensure 2 files created at the exact same time will have a unique name --- addons/report_webkit/webkit_report.py | 44 +++++++++++---------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index c6a30f5e579..20c71e3acbd 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -105,9 +105,10 @@ class WebKitParser(report_sxw): """Call webkit in order to generate pdf""" if not webkit_header: webkit_header = report_xml.webkit_header - tmp_dir = tempfile.gettempdir() - out_filename = tempfile.mktemp(suffix=".pdf", prefix="webkit.tmp.") - file_to_del = [out_filename] + out_filename = tempfile.NamedTemporaryFile(suffix=".pdf", + prefix="webkit.tmp.", + delete=False) + file_to_del = [out_filename.name] if comm_path: command = [comm_path] else: @@ -117,25 +118,15 @@ class WebKitParser(report_sxw): # default to UTF-8 encoding. Use to override. command.extend(['--encoding', 'utf-8']) if header : - head_file = file( os.path.join( - tmp_dir, - str(time.time()) + '.head.html' - ), - 'w' - ) - head_file.write(self._sanitize_html(header)) - head_file.close() + with tempfile.NamedTemporaryFile(suffix=".head.html", + delete=False) as head_file: + head_file.write(self._sanitize_html(header)) file_to_del.append(head_file.name) command.extend(['--header-html', head_file.name]) if footer : - foot_file = file( os.path.join( - tmp_dir, - str(time.time()) + '.foot.html' - ), - 'w' - ) - foot_file.write(self._sanitize_html(footer)) - foot_file.close() + with tempfile.NamedTemporaryFile(suffix=".foot.html", + delete=False) as foot_file: + foot_file.write(self._sanitize_html(footer)) file_to_del.append(foot_file.name) command.extend(['--footer-html', foot_file.name]) @@ -153,13 +144,13 @@ class WebKitParser(report_sxw): command.extend(['--page-size', str(webkit_header.format).replace(',', '.')]) count = 0 for html in html_list : - html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w') - count += 1 - html_file.write(self._sanitize_html(html)) - html_file.close() + with tempfile.NamedTemporaryFile(suffix="%d.body.html" %count, + delete=False) as html_file: + count += 1 + html_file.write(self._sanitize_html(html)) file_to_del.append(html_file.name) command.append(html_file.name) - command.append(out_filename) + command.append(out_filename.name) stderr_fd, stderr_path = tempfile.mkstemp(text=True) file_to_del.append(stderr_path) try: @@ -176,9 +167,8 @@ class WebKitParser(report_sxw): if status : raise except_osv(_('Webkit error' ), _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message)) - pdf_file = open(out_filename, 'rb') - pdf = pdf_file.read() - pdf_file.close() + with out_filename as pdf_file: + pdf = pdf_file.read() finally: if stderr_fd is not None: os.close(stderr_fd) From 72ab3e81a52dc772e1b8670554870873229d999b Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Tue, 13 May 2014 18:23:48 +0200 Subject: [PATCH 002/578] Make report_webkit thread safe by removing parser_instance attribute, as a WebkitParser instance can be shared by multiple thread and thus we don't want a thread to erase parser_instance of anorther thread. --- addons/report_webkit/webkit_report.py | 41 ++++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index c6a30f5e579..d5d921e84df 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -37,6 +37,7 @@ from openerp import report import tempfile import time import logging +from functools import partial from mako.template import Template from mako.lookup import TemplateLookup @@ -68,7 +69,6 @@ class WebKitParser(report_sxw): """ def __init__(self, name, table, rml=False, parser=False, header=True, store=False): - self.parser_instance = False self.localcontext = {} report_sxw.__init__(self, name, table, rml, parser, header, store) @@ -189,16 +189,16 @@ class WebKitParser(report_sxw): _logger.error('cannot remove file %s: %s', f_to_del, exc) return pdf - def translate_call(self, src): + def translate_call(self, parser_instance, src): """Translate String.""" - ir_translation = self.pool.get('ir.translation') + ir_translation = self.pool['ir.translation'] name = self.tmpl and 'addons/' + self.tmpl or None - res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, - name, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src) + res = ir_translation._get_source(parser_instance.cr, parser_instance.uid, + name, 'report', parser_instance.localcontext.get('lang', 'en_US'), src) if res == src: # no translation defined, fallback on None (backward compatibility) - res = ir_translation._get_source(self.parser_instance.cr, self.parser_instance.uid, - None, 'report', self.parser_instance.localcontext.get('lang', 'en_US'), src) + res = ir_translation._get_source(parser_instance.cr, parser_instance.uid, + None, 'report', parser_instance.localcontext.get('lang', 'en_US'), src) if not res : return src return res @@ -213,14 +213,14 @@ class WebKitParser(report_sxw): if report_xml.report_type != 'webkit': return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context) - self.parser_instance = self.parser(cursor, + parser_instance = self.parser(cursor, uid, self.name2, context=context) self.pool = pooler.get_pool(cursor.dbname) objs = self.getObjects(cursor, uid, ids, context) - self.parser_instance.set_context(objs, data, ids, report_xml.report_type) + parser_instance.set_context(objs, data, ids, report_xml.report_type) template = False @@ -250,17 +250,18 @@ class WebKitParser(report_sxw): if not css : css = '' + translate_call = partial(self.translate_call, parser_instance) #default_filters=['unicode', 'entity'] can be used to set global filter body_mako_tpl = mako_template(template) helper = WebKitHelper(cursor, uid, report_xml.id, context) if report_xml.precise_mode: for obj in objs: - self.parser_instance.localcontext['objects'] = [obj] + parser_instance.localcontext['objects'] = [obj] try : html = body_mako_tpl.render(helper=helper, css=css, - _=self.translate_call, - **self.parser_instance.localcontext) + _=translate_call, + **parser_instance.localcontext) htmls.append(html) except Exception: msg = exceptions.text_error_template().render() @@ -270,8 +271,8 @@ class WebKitParser(report_sxw): try : html = body_mako_tpl.render(helper=helper, css=css, - _=self.translate_call, - **self.parser_instance.localcontext) + _=translate_call, + **parser_instance.localcontext) htmls.append(html) except Exception: msg = exceptions.text_error_template().render() @@ -281,9 +282,9 @@ class WebKitParser(report_sxw): try : head = head_mako_tpl.render(helper=helper, css=css, - _=self.translate_call, + _=translate_call, _debug=False, - **self.parser_instance.localcontext) + **parser_instance.localcontext) except Exception: raise except_osv(_('Webkit render!'), exceptions.text_error_template().render()) @@ -293,8 +294,8 @@ class WebKitParser(report_sxw): try : foot = foot_mako_tpl.render(helper=helper, css=css, - _=self.translate_call, - **self.parser_instance.localcontext) + _=translate_call, + **parser_instance.localcontext) except: msg = exceptions.text_error_template().render() _logger.error(msg) @@ -304,8 +305,8 @@ class WebKitParser(report_sxw): deb = head_mako_tpl.render(helper=helper, css=css, _debug=tools.ustr("\n".join(htmls)), - _=self.translate_call, - **self.parser_instance.localcontext) + _=translate_call, + **parser_instance.localcontext) except Exception: msg = exceptions.text_error_template().render() _logger.error(msg) From e367a54640d579cce998ad33dfd22de8cffe58f9 Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Wed, 14 May 2014 11:50:27 +0200 Subject: [PATCH 003/578] fix indentation --- addons/report_webkit/webkit_report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index d5d921e84df..10a98d30bf3 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -214,9 +214,9 @@ class WebKitParser(report_sxw): return super(WebKitParser,self).create_single_pdf(cursor, uid, ids, data, report_xml, context=context) parser_instance = self.parser(cursor, - uid, - self.name2, - context=context) + uid, + self.name2, + context=context) self.pool = pooler.get_pool(cursor.dbname) objs = self.getObjects(cursor, uid, ids, context) From 4ed343455c3fb396e2a77630ec604cbeee5ce37e Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Wed, 14 May 2014 13:22:42 +0200 Subject: [PATCH 004/578] Fix incompatibility with Windows using tempfile. --- addons/report_webkit/webkit_report.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py index 20c71e3acbd..e6ed123b8ad 100644 --- a/addons/report_webkit/webkit_report.py +++ b/addons/report_webkit/webkit_report.py @@ -105,10 +105,9 @@ class WebKitParser(report_sxw): """Call webkit in order to generate pdf""" if not webkit_header: webkit_header = report_xml.webkit_header - out_filename = tempfile.NamedTemporaryFile(suffix=".pdf", - prefix="webkit.tmp.", - delete=False) - file_to_del = [out_filename.name] + fd, out_filename = tempfile.mkstemp(suffix=".pdf", + prefix="webkit.tmp.") + file_to_del = [out_filename] if comm_path: command = [comm_path] else: @@ -150,7 +149,7 @@ class WebKitParser(report_sxw): html_file.write(self._sanitize_html(html)) file_to_del.append(html_file.name) command.append(html_file.name) - command.append(out_filename.name) + command.append(out_filename) stderr_fd, stderr_path = tempfile.mkstemp(text=True) file_to_del.append(stderr_path) try: @@ -167,8 +166,9 @@ class WebKitParser(report_sxw): if status : raise except_osv(_('Webkit error' ), _("The command 'wkhtmltopdf' failed with error code = %s. Message: %s") % (status, error_message)) - with out_filename as pdf_file: + with open(out_filename, 'rb') as pdf_file: pdf = pdf_file.read() + os.close(fd) finally: if stderr_fd is not None: os.close(stderr_fd) From 5597c25acf0b98b6e73f5e305c268c2a4da88d79 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 16 May 2014 16:43:38 -0400 Subject: [PATCH 005/578] [FIX] Fix account_budget name unicode error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix bug https://bugs.launchpad.net/openobject-addons/+bug/1292245: Invoice budget's warning when crossovered budget lines are not related to any account will cause a UnicodeEncodeError if the account has unicode-only characters such as é. Make sure that the budget name is a unicode to avoid UnicodeEncodeError which happens when budget name contains UTF-8 characters. Signed-off-by: Sandy Carter --- addons/account_budget/account_budget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/account_budget/account_budget.py b/addons/account_budget/account_budget.py index daec4ea0a2f..c20871ed65e 100644 --- a/addons/account_budget/account_budget.py +++ b/addons/account_budget/account_budget.py @@ -22,6 +22,7 @@ import datetime from openerp.osv import fields, osv +from openerp.tools import ustr from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp @@ -116,7 +117,7 @@ class crossovered_budget_lines(osv.osv): for line in self.browse(cr, uid, ids, context=context): acc_ids = [x.id for x in line.general_budget_id.account_ids] if not acc_ids: - raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % str(line.general_budget_id.name)) + raise osv.except_osv(_('Error!'),_("The Budget '%s' has no accounts!") % ustr(line.general_budget_id.name)) date_to = line.date_to date_from = line.date_from if context.has_key('wizard_date_from'): From 7e6d223e9e6e9ba232c9fa2b1e5629563f5e7a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 19 May 2014 14:16:09 +0200 Subject: [PATCH 006/578] [IMP] work on inline search view in web client incomplete work. So far, the search view widget has been split in two widgets: SearchView and SearchViewDrawer. The SearchViewDrawer has been inserted inline, and some preliminary work has been done to improve the layout. --- addons/mail/static/src/js/mail.js | 5 +- addons/mail/static/src/xml/mail.xml | 1 + addons/web/static/src/css/base.css | 5 +- addons/web/static/src/css/base.sass | 4 +- addons/web/static/src/js/search.js | 325 +++++++++++++++------------- addons/web/static/src/js/views.js | 9 +- addons/web/static/src/xml/base.xml | 6 +- 7 files changed, 197 insertions(+), 158 deletions(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 99a7751e15e..9cf33203a45 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1888,7 +1888,10 @@ openerp.mail = function (session) { var self = this; var ds_msg = new session.web.DataSetSearch(this, 'mail.message'); this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false); - this.searchview.appendTo(this.$('.oe_view_manager_view_search')) + this.searchview_drawer = new session.web.SearchViewDrawer(this, this.searchview); + + $.when(this.searchview.appendTo(this.$('.oe_view_manager_view_search')), + this.searchview_drawer.appendTo(this.$('.oe_searchview_drawer_container'))) .then(function () { self.searchview.on('search_data', self, self.do_searchview_search); }); if (this.searchview.has_defaults) { this.searchview.ready.then(this.searchview.do_search); diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index ec7b0fa2911..77aeef0b5b9 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -191,6 +191,7 @@ +
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index f02dc3c7fd7..03066c171b4 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1,4 +1,4 @@ -@charset "utf-8"; +@charset "UTF-8"; @font-face { font-family: "mnmliconsRegular"; src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); @@ -1776,6 +1776,9 @@ margin: 0 0 0 4px; padding: 0; } +.openerp .oe_searchview_drawer_container { + border-bottom: 1px solid #afafb6; +} .openerp .oe_view_nocontent { padding: 15px; margin-top: 0; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c15c90abb7a..e1296924629 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1280,7 +1280,7 @@ $sheet-padding: 16px &.oe_searchview_open_drawer .oe_searchview_drawer display: block - + .oe_searchview_drawer cursor: default position: absolute @@ -1458,6 +1458,8 @@ $sheet-padding: 16px margin: 0 0 0 4px padding: 0 + .oe_searchview_drawer_container + border-bottom: 1px solid $tag-border // }}} // Views Common {{{ diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 832faa4ca62..88cf7531fec 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -340,7 +340,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }, 'click .oe_searchview_unfold_drawer': function (e) { e.stopImmediatePropagation(); - this.$el.toggleClass('oe_searchview_open_drawer'); + if (this.searchview_drawer) + this.searchview_drawer.toggle(); }, 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { switch(e.which) { @@ -383,14 +384,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.defaults = defaults || {}; this.has_defaults = !_.isEmpty(this.defaults); - this.inputs = []; - this.controls = []; - this.headless = this.options.hidden && !this.has_defaults; this.input_subviews = []; this.ready = $.Deferred(); + this.drawer_ready = $.Deferred(); + this.fields_view_get = $.Deferred(); }, start: function() { var self = this; @@ -416,20 +416,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); this.alive($.when(load_view)).then(function (r) { + self.fields_view_get.resolve(r); return self.search_view_loaded(r); }).fail(function () { self.ready.reject.apply(null, arguments); }); } - instance.web.bus.on('click', this, function(ev) { - if ($(ev.target).parents('.oe_searchview').length === 0) { - self.$el.removeClass('oe_searchview_open_drawer'); - } - }); - return $.when(p, this.ready); }, + + set_drawer: function (drawer) { + this.searchview_drawer = drawer; + }, + show: function () { this.$el.show(); }, @@ -462,14 +462,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea .$el.focus(); }, - /** - * Sets up thingie where all the mess is put? - */ - select_for_drawer: function () { - return _(this.inputs).filter(function (input) { - return input.in_drawer(); - }); - }, /** * Sets up search view's view-wide auto-completion widget */ @@ -630,87 +622,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); }, - /** - * Builds a list of widget rows (each row is an array of widgets) - * - * @param {Array} items a list of nodes to convert to widgets - * @param {Object} fields a mapping of field names to (ORM) field attributes - * @param {Object} [group] group to put the new controls in - */ - make_widgets: function (items, fields, group) { - if (!group) { - group = new instance.web.search.Group( - this, 'q', {attrs: {string: _t("Filters")}}); - } - var self = this; - var filters = []; - _.each(items, function (item) { - if (filters.length && item.tag !== 'filter') { - group.push(new instance.web.search.FilterGroup(filters, group)); - filters = []; - } - - switch (item.tag) { - case 'separator': case 'newline': - break; - case 'filter': - filters.push(new instance.web.search.Filter(item, group)); - break; - case 'group': - self.make_widgets(item.children, fields, - new instance.web.search.Group(group, 'w', item)); - break; - case 'field': - var field = this.make_field( - item, fields[item['attrs'].name], group); - group.push(field); - // filters - self.make_widgets(item.children, fields, group); - break; - } - }, this); - - if (filters.length) { - group.push(new instance.web.search.FilterGroup(filters, this)); - } - }, - /** - * Creates a field for the provided field descriptor item (which comes - * from fields_view_get) - * - * @param {Object} item fields_view_get node for the field - * @param {Object} field fields_get result for the field - * @param {Object} [parent] - * @returns instance.web.search.Field - */ - make_field: function (item, field, parent) { - // M2O combined with selection widget is pointless and broken in search views, - // but has been used in the past for unsupported hacks -> ignore it - if (field.type === "many2one" && item.attrs.widget === "selection"){ - item.attrs.widget = undefined; - } - var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); - if(obj) { - return new (obj) (item, field, parent || this); - } else { - console.group('Unknown field type ' + field.type); - console.error('View node', item); - console.info('View field', field); - console.info('In view', this); - console.groupEnd(); - return null; - } - }, - - add_common_inputs: function() { - // add Filters to this.inputs, need view.controls filled - (new instance.web.search.Filters(this)); - // add custom filters to this.inputs - this.custom_filters = new instance.web.search.CustomFilters(this); - // add Advanced to this.inputs - (new instance.web.search.Advanced(this)); - }, - search_view_loaded: function(data) { var self = this; this.fields_view = data; @@ -720,41 +631,28 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea "Got non-search view after asking for a search view: type %s, arch root %s", data.type, data.arch.tag)); } - this.make_widgets( - data['arch'].children, - data.fields); - this.add_common_inputs(); - - // build drawer - var drawer_started = $.when.apply( - null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$('.oe_searchview_drawer'))); - - - // load defaults - var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( - 'facet_for_defaults', this.defaults)) - .then(this.proxy('setup_default_query')); - - return $.when(drawer_started, defaults_fetched) + return this.drawer_ready + .then(this.proxy('setup_default_query')) .then(function () { self.trigger("search_view_loaded", data); self.ready.resolve(); }); }, setup_default_query: function () { + this.ready.resolve(); + return; // Hacky implementation of CustomFilters#facet_for_defaults ensure // CustomFilters will be ready (and CustomFilters#filters will be // correctly filled) by the time this method executes. - var custom_filters = this.custom_filters.filters; + var custom_filters = this.searchview_drawer.custom_filters.filters; if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) { // Check for any is_default custom filter var personal_filter = _(custom_filters).find(function (filter) { return filter.user_id && filter.is_default; }); if (personal_filter) { - this.custom_filters.toggle_filter(personal_filter, true); + this.searchview_drawer.custom_filters.toggle_filter(personal_filter, true); return; } @@ -762,7 +660,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return !filter.user_id && filter.is_default; }); if (global_filter) { - this.custom_filters.toggle_filter(global_filter, true); + this.searchview_drawer.custom_filters.toggle_filter(global_filter, true); return; } } @@ -875,6 +773,146 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }); +instance.web.SearchViewDrawer = instance.web.Widget.extend({ + template: "SearchViewDrawer", + + init: function(parent, searchview) { + this._super(parent); + this.searchview = searchview; + this.searchview.set_drawer(this); + this.ready = searchview.drawer_ready; + this.controls = []; + this.inputs = []; + }, + + toggle: function () { + this.$el.toggle(); + }, + + start: function() { + var filters_ready = this.searchview.fields_view_get + .then(this.proxy('prepare_filters')); + return $.when(this._super(), filters_ready) + .then(this.proxy('notify_searchview')); + }, + prepare_filters: function (data) { + this.make_widgets( + data['arch'].children, + data.fields); + + this.add_common_inputs(); + + // build drawer + var drawer_started = $.when.apply( + null, _(this.select_for_drawer()).invoke( + 'appendTo', this.$el)); + + + // load defaults + // var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( + // 'facet_for_defaults', this.defaults)) + // .then(this.proxy('setup_default_query')); + + + }, + notify_searchview: function () { + this.searchview.drawer_ready.resolve(); + + // with args... + // this.searchview.drawer_ready.resolve(); + }, + /** + * Sets up thingie where all the mess is put? + */ + select_for_drawer: function () { + return _(this.inputs).filter(function (input) { + return input.in_drawer(); + }); + }, + + /** + * Builds a list of widget rows (each row is an array of widgets) + * + * @param {Array} items a list of nodes to convert to widgets + * @param {Object} fields a mapping of field names to (ORM) field attributes + * @param {Object} [group] group to put the new controls in + */ + make_widgets: function (items, fields, group) { + if (!group) { + group = new instance.web.search.Group( + this, 'q', {attrs: {string: _t("Filters")}}); + } + var self = this; + var filters = []; + _.each(items, function (item) { + if (filters.length && item.tag !== 'filter') { + group.push(new instance.web.search.FilterGroup(filters, group)); + filters = []; + } + + switch (item.tag) { + case 'separator': case 'newline': + break; + case 'filter': + filters.push(new instance.web.search.Filter(item, group)); + break; + case 'group': + self.make_widgets(item.children, fields, + new instance.web.search.Group(group, 'w', item)); + break; + case 'field': + var field = this.make_field( + item, fields[item['attrs'].name], group); + group.push(field); + // filters + self.make_widgets(item.children, fields, group); + break; + } + }, this); + + if (filters.length) { + group.push(new instance.web.search.FilterGroup(filters, this)); + } + }, + /** + * Creates a field for the provided field descriptor item (which comes + * from fields_view_get) + * + * @param {Object} item fields_view_get node for the field + * @param {Object} field fields_get result for the field + * @param {Object} [parent] + * @returns instance.web.search.Field + */ + make_field: function (item, field, parent) { + // M2O combined with selection widget is pointless and broken in search views, + // but has been used in the past for unsupported hacks -> ignore it + if (field.type === "many2one" && item.attrs.widget === "selection"){ + item.attrs.widget = undefined; + } + var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); + if(obj) { + return new (obj) (item, field, parent || this); + } else { + console.group('Unknown field type ' + field.type); + console.error('View node', item); + console.info('View field', field); + console.info('In view', this); + console.groupEnd(); + return null; + } + }, + + add_common_inputs: function() { + // add Filters to this.inputs, need view.controls filled + (new instance.web.search.Filters(this)); + // add custom filters to this.inputs + this.custom_filters = new instance.web.search.CustomFilters(this); + // add Advanced to this.inputs + (new instance.web.search.Advanced(this)); + }, + +}); + /** * Registry of search fields, called by :js:class:`instance.web.SearchView` to * find and instantiate its field widgets. @@ -932,9 +970,10 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web this._super(parent); var ancestor = parent; do { - this.view = ancestor; - } while (!(ancestor instanceof instance.web.SearchView) + this.drawer = ancestor; + } while (!(ancestor instanceof instance.web.SearchViewDrawer) && (ancestor = (ancestor.getParent && ancestor.getParent()))); + this.view = this.drawer.searchview; } }); @@ -956,7 +995,7 @@ instance.web.search.Group = instance.web.search.Widget.extend({ this.name = attrs.string; this.children = []; - this.view.controls.push(this); + this.drawer.controls.push(this); }, push: function (input) { this.children.push(input); @@ -977,7 +1016,7 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan init: function (parent) { this._super(parent); this.load_attrs({}); - this.view.inputs.push(this); + this.drawer.inputs.push(this); }, /** * Fetch auto-completion values for the widget. @@ -1814,22 +1853,13 @@ instance.web.search.Filters = instance.web.search.Input.extend({ _in_drawer: true, start: function () { var self = this; - var running_count = 0; - // get total filters count var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; - var visible_filters = _(this.view.controls).chain().reject(function (group) { + var visible_filters = _(this.drawer.controls).chain().reject(function (group) { return _(_(group.children).filter(is_group)).isEmpty() || group.modifiers.invisible; }); - var filters_count = visible_filters - .pluck('children') - .flatten() - .filter(is_group) - .map(function (i) { return i.filters.length; }) - .sum() - .value(); - var col1 = [], col2 = visible_filters.map(function (group) { + var groups = visible_filters.map(function (group) { var filters = _(group.children).filter(is_group); return { name: _.str.sprintf("%s %s", @@ -1840,27 +1870,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({ }; }).value(); - while (col2.length) { - // col1 + group should be smaller than col2 + group - if ((running_count + col2[0].length) <= (filters_count - running_count)) { - running_count += col2[0].length; - col1.push(col2.shift()); - } else { - break; - } - } + var $dl = $('
').appendTo(this.$el); - return $.when( - this.render_column(col1, $('
').appendTo(this.$el)), - this.render_column(col2, $('
').appendTo(this.$el))); + var rendered_lines = _.map(groups, function (group) { + $('
').html(group.name).appendTo($dl); + var $dd = $('
').appendTo($dl); + return $.when.apply(null, _(group.filters).invoke('appendTo', $dd)); + }); + + return $.when.apply(this, rendered_lines); }, - render_column: function (column, $el) { - return $.when.apply(null, _(column).map(function (group) { - $('

').html(group.name).appendTo($el); - return $.when.apply(null, - _(group.filters).invoke('appendTo', $el)); - })); - } }); instance.web.search.Advanced = instance.web.search.Input.extend({ diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 753d52d497a..f954ebd9ad5 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -553,6 +553,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.model = dataset ? dataset.model : undefined; this.dataset = dataset; this.searchview = null; + this.searchview_drawer = null; this.active_view = null; this.views_src = _.map(views, function(x) { if (x instanceof Array) { @@ -800,14 +801,20 @@ instance.web.ViewManager = instance.web.Widget.extend({ if (this.searchview) { this.searchview.destroy(); } + if (this.searchview_drawer) { + this.searchview_drawer.destroy(); + } + var options = { hidden: this.flags.search_view === false, disable_custom_filters: this.flags.search_disable_custom_filters, }; this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, options); + this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview); this.searchview.on('search_data', self, this.do_searchview_search); - return this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")); + return $.when(this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")), + this.searchview_drawer.appendTo(this.$(".oe_searchview_drawer_container"))); }, do_searchview_search: function(domains, contexts, groupbys) { var self = this, diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index f801e6c7c71..22e344f2b3d 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -520,6 +520,8 @@ +
+
@@ -1555,11 +1557,13 @@
-
+ +
Date: Mon, 19 May 2014 15:40:45 +0200 Subject: [PATCH 007/578] [FIX] add support for default filters in search view support for default filters was broken in the previous commit, now it works again. --- addons/web/static/src/css/base.css | 1 + addons/web/static/src/css/base.sass | 3 ++- addons/web/static/src/js/search.js | 28 ++++++++++++++-------------- addons/web/static/src/xml/base.xml | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 03066c171b4..ade49c3b1fe 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1778,6 +1778,7 @@ } .openerp .oe_searchview_drawer_container { border-bottom: 1px solid #afafb6; + overflow: auto; } .openerp .oe_view_nocontent { padding: 15px; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index e1296924629..d85b55d854c 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1280,7 +1280,7 @@ $sheet-padding: 16px &.oe_searchview_open_drawer .oe_searchview_drawer display: block - + .oe_searchview_drawer cursor: default position: absolute @@ -1460,6 +1460,7 @@ $sheet-padding: 16px .oe_searchview_drawer_container border-bottom: 1px solid $tag-border + overflow: auto // }}} // Views Common {{{ diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 88cf7531fec..0bd4c89f002 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -640,8 +640,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); }, setup_default_query: function () { - this.ready.resolve(); - return; // Hacky implementation of CustomFilters#facet_for_defaults ensure // CustomFilters will be ready (and CustomFilters#filters will be // correctly filled) by the time this method executes. @@ -803,23 +801,25 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ this.add_common_inputs(); // build drawer - var drawer_started = $.when.apply( - null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$el)); + var in_drawer = this.select_for_drawer(); - - // load defaults - // var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( - // 'facet_for_defaults', this.defaults)) - // .then(this.proxy('setup_default_query')); + var self = this; + var second_col = $('
'); + var insert_first_col = in_drawer[0].appendTo(this.$el).then(function () { + second_col.appendTo(self.$el); + }).then(function () { + return $.when.apply(null, + _(_.rest(in_drawer)).invoke('appendTo', second_col)); + }); + + return $.when.apply(null, _(this.inputs).invoke( + 'facet_for_defaults', this.searchview.defaults)); }, notify_searchview: function () { - this.searchview.drawer_ready.resolve(); - - // with args... - // this.searchview.drawer_ready.resolve(); + var defaults = arguments[1]; + this.searchview.drawer_ready.resolve.apply(null, defaults); }, /** * Sets up thingie where all the mess is put? diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 22e344f2b3d..542a7408f54 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1717,7 +1717,7 @@
-
+
From c70df56174be6611bda2a18b14b5145fa365dfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 19 May 2014 17:03:05 +0200 Subject: [PATCH 008/578] [IMP] lots of layout work for inline search view (addon web) mainly to correctly style the custom filters list. --- addons/web/static/src/css/base.css | 200 ++++++++++++++++++++++++++++ addons/web/static/src/css/base.sass | 164 +++++++++++++++++++++++ addons/web/static/src/js/search.js | 21 +-- addons/web/static/src/xml/base.xml | 33 ++--- 4 files changed, 392 insertions(+), 26 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index ade49c3b1fe..085e0d5e08e 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1780,6 +1780,206 @@ border-bottom: 1px solid #afafb6; overflow: auto; } +.openerp .oe_searchview_drawer { + cursor: default; + display: none; + overflow: hidden; + border-bottom: 1px solid #afafb6; + border-right: 1px solid #afafb6; + text-align: left; + padding: 8px 0; +} +.openerp .oe_searchview_drawer .badge { + font-size: 12px; + line-height: 12px; +} +.openerp .oe_searchview_drawer > div:first-child { + border: none; + padding-left: 0; +} +.openerp .oe_searchview_drawer > div:first-child li:hover:not(.badge) { + background-color: #f0f0fa; +} +.openerp .oe_searchview_drawer .col-md-5 { + padding-left: 0; +} +.openerp .oe_searchview_drawer dl { + margin-bottom: 10px; +} +.openerp .oe_searchview_drawer dt { + color: #7c7bad; + font-size: 13px; + line-height: 24px; +} +.openerp .oe_searchview_drawer dd { + line-height: 24px; + font-size: 13px; + padding-top: 3px; +} +.openerp .oe_searchview_drawer h4, .openerp .oe_searchview_drawer h4 * { + margin: 0 0 0 2px; + padding-left: 20px; + cursor: pointer; + font-weight: normal; + display: inline-block; +} +.openerp .oe_searchview_drawer h4:hover, .openerp .oe_searchview_drawer h4 *:hover { + background-color: #f0f0fa; +} +.openerp .oe_searchview_drawer h4:before { + content: "▸ "; + color: #a3a3a3; +} +.openerp .oe_searchview_drawer button { + margin: 4px 0; +} +.openerp .oe_searchview_drawer .button { + border: none; + background: transparent; + padding: 0 2px; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} +.openerp .oe_searchview_drawer .oe_searchview_section ul { + margin: 0 8px; + padding: 0; + list-style: none; + display: inline; +} +.openerp .oe_searchview_drawer .oe_searchview_section li { + display: inline-block; + cursor: pointer; + position: relative; + margin-right: 8px; +} +.openerp .oe_searchview_drawer form { + margin-left: 12px; +} +.openerp .oe_searchview_drawer form p { + margin: 4px 0; + line-height: 18px; +} +.openerp .oe_searchview_drawer form button { + margin: 0 0 8px -3px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom dt { + width: 140px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom dd { + margin-left: 145px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom form { + display: none; +} +.openerp .oe_searchview_drawer .oe_searchview_custom li { + cursor: pointer; + position: relative; + line-height: 14px; + margin-right: 0; +} +.openerp .oe_searchview_drawer .oe_searchview_custom li button { + position: absolute; + top: 0; + right: 5px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom li a { + margin-left: 10px; + position: inherit; + visibility: hidden; + display: inline-block; +} +.openerp .oe_searchview_drawer .oe_searchview_custom li span:hover:not(.badge) { + background-color: #f0f0fa; +} +.openerp .oe_searchview_drawer .oe_searchview_custom li:hover a { + visibility: visible; +} +.openerp .oe_searchview_drawer .oe_searchview_custom label { + font-weight: normal; +} +.openerp .oe_searchview_drawer .oe_searchview_dashboard form { + display: none; + margin-top: 2px; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced form { + display: none; + margin-top: 8px; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced button.oe_add_condition:before { + content: "Z"; + font-family: "entypoRegular" !important; + font-size: 24px; + font-weight: 300 !important; + margin-right: 4px; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced ul { + list-style: none; + padding: 0; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced li { + position: relative; + list-style: none; + margin: 0; + white-space: nowrap; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced li:first-child .searchview_extended_prop_or { + visibility: hidden; + margin-left: -14px; +} +.openerp .oe_searchview_drawer .oe_searchview_advanced .searchview_extended_prop_or { + opacity: 0.5; + margin-left: -14px; +} +.openerp .oe_searchview_drawer .oe_opened h4:before { + content: "▾ "; + position: relative; + top: -1px; +} +.openerp .oe_searchview_drawer .oe_opened form { + display: block; +} +.openerp .oe_searchview_drawer .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .searchview_extended_delete_prop { + display: inline-block; + width: 12px; + height: 12px; + line-height: 12px; + padding: 1px; + color: #8786b7; + line-height: 8px; + text-align: center; + font-weight: bold; + text-shadow: 0 1px 1px white; +} +.openerp .oe_searchview_drawer .oe_searchview_custom_delete:hover, .openerp .oe_searchview_drawer .searchview_extended_delete_prop:hover { + text-decoration: none; + color: white; + background: #8786b7; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom_delete { + display: none; + position: absolute; + bottom: 1px; + right: 4px; +} +.openerp .oe_searchview_drawer .oe_searchview_custom_private:hover .oe_searchview_custom_delete, .openerp .oe_searchview_drawer .oe_searchview_custom_public:hover .oe_searchview_custom_delete { + display: inline-block; +} +.openerp .oe_searchview_drawer .oe_searchview_custom_public:after { + content: ","; + font-family: "entypoRegular" !important; + font-size: 22px; + font-weight: 300 !important; + margin: 0 0 0 4px; + padding: 0; +} .openerp .oe_view_nocontent { padding: 15px; margin-top: 0; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index d85b55d854c..9b02b20b82a 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1461,7 +1461,171 @@ $sheet-padding: 16px .oe_searchview_drawer_container border-bottom: 1px solid $tag-border overflow: auto + .oe_searchview_drawer + cursor: default + display: none + overflow: hidden + border-bottom: 1px solid $tag-border + border-right: 1px solid $tag-border + text-align: left + padding: 8px 0 + .badge + font-size: 12px + line-height: 12px + > div:first-child + border: none + padding-left: 0 + li:hover:not(.badge) + background-color: $hover-background + .col-md-5 + padding-left: 0 + dl + margin-bottom: 10px + dt + color: $section-title-color + font-size: 13px + line-height: 24px + dd + line-height: 24px + font-size: 13px + padding-top: 3px + h4, h4 * + margin: 0 0 0 2px + padding-left: 20px + cursor: pointer + font-weight: normal + display: inline-block + &:hover + background-color: $hover-background + h4:before + content: "▸ " + color: #a3a3a3 + button + margin: 4px 0 + .button + border: none + background: transparent + padding: 0 2px + @include box-shadow(none) + @include radius(0) + .oe_searchview_section + ul + margin: 0 8px + padding: 0 + list-style: none + display: inline + li + display: inline-block + cursor: pointer + position: relative + margin-right: 8px + form + margin-left: 12px + p + margin: 4px 0 + line-height: 18px + button + margin: 0 0 8px -3px // Managed margin-left according bootstrap3 + .oe_searchview_custom + dt + width: 140px + dd + margin-left: 145px + form + display: none + li + cursor: pointer + position: relative + line-height: 14px + margin-right: 0 + button + position: absolute + top: 0 + right: 5px + a + margin-left: 10px + position: inherit + visibility: hidden + display: inline-block + span:hover:not(.badge) + background-color: $hover-background + li:hover a + visibility: visible + //Customize for searchview label + label + font-weight: normal + //End of Customize + .oe_searchview_dashboard + form + display: none + margin-top: 2px + .oe_searchview_advanced + form + display: none + margin-top: 8px + button.oe_add_condition:before + content: "Z" + font-family: "entypoRegular" !important + font-size: 24px + font-weight: 300 !important + margin-right: 4px + ul + list-style: none + padding: 0 + li + position: relative + list-style: none + margin: 0 + white-space: nowrap + &:first-child .searchview_extended_prop_or + visibility: hidden + margin-left: -14px + .searchview_extended_prop_or + opacity: 0.5 + margin-left: -14px //Customize 'or' in searchview + .oe_opened + h4:before + content: "▾ " + position: relative + top: -1px + form + display: block + + // delete buttons + .oe_searchview_custom_delete, .searchview_extended_delete_prop + display: inline-block + width: 12px + height: 12px + line-height: 12px + padding: 1px + color: #8786b7 + line-height: 8px + text-align: center + font-weight: bold + text-shadow: 0 1px 1px white + &:hover + text-decoration: none + color: white + background: #8786b7 + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) + @include radius(2px) + .oe_searchview_custom_delete + display: none + position: absolute + bottom: 1px + right: 4px + .oe_searchview_custom_private, .oe_searchview_custom_public + &:hover + .oe_searchview_custom_delete + display: inline-block + .oe_searchview_custom_public:after + content: "," + font-family: "entypoRegular" !important + font-size: 22px + font-weight: 300 !important + margin: 0 0 0 4px + padding: 0 // }}} // Views Common {{{ .oe_view_nocontent diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 0bd4c89f002..fa7cc0ce8c0 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1127,7 +1127,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$('> li').removeClass('oe_selected'); + var $filters = this.$('> li').removeClass('badge'); var facet = this.view.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { @@ -1135,7 +1135,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in if (i === -1) { return; } $filters.filter(function () { return Number($(this).data('index')) === i; - }).addClass('oe_selected'); + }).addClass('badge'); }); }, /** @@ -1747,7 +1747,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }; }, clear_selection: function () { - this.$('li.oe_selected').removeClass('oe_selected'); + this.$('span.badge').removeClass('badge'); }, append_filter: function (filter) { var self = this; @@ -1760,12 +1760,13 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } else { var id = filter.id; this.filters[key] = filter; - $filter = this.$filters[key] = $('
  • ') + $filter = $('
  • ') .appendTo(this.$('.oe_searchview_custom_list')) - .addClass(filter.user_id ? 'oe_searchview_custom_private' - : 'oe_searchview_custom_public') .toggleClass('oe_searchview_custom_default', filter.is_default) - .text(filter.name); + .append(this.$filters[key] = $('').text(filter.name)); + + this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private' + : 'oe_searchview_custom_public') $('x') .click(function (e) { @@ -1782,7 +1783,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ .appendTo($filter); } - $filter.unbind('click').click(function () { + this.$filters[key].unbind('click').click(function () { self.toggle_filter(filter); }); }, @@ -1792,12 +1793,12 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }); if (current) { this.view.query.remove(current); - this.$filters[this.key_for(filter)].removeClass('oe_selected'); + this.$filters[this.key_for(filter)].removeClass('badge'); return; } this.view.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); - this.$filters[this.key_for(filter)].addClass('oe_selected'); + this.$filters[this.key_for(filter)].addClass('badge'); }, set_filters: function (filters) { _(filters).map(_.bind(this.append_filter, this)); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 542a7408f54..93f6ee924f7 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1720,23 +1720,24 @@
    +
    -
    -

    M Custom Filters

    -
      -
      -

      Save current filter

      -
      -

      -

      - - - - -

      - -
      -
      +
      +
      M Custom Filters
      +
        +
        +
        +

        Save current filter

        +
        +

        +

        + + + + +

        + +
      From 72f532a4630fd123d0a3fdff1aa621079ede5ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 20 May 2014 10:54:58 +0200 Subject: [PATCH 009/578] [IMP] hide drawer when switching views (addon web) the drawer wasn't closed when switching views, which might be a problem when the user switches to a form view. Also, renames the variable 'searchview_drawer' to 'drawer'. --- addons/web/static/src/js/search.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index fa7cc0ce8c0..d9f2a3c5c62 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -340,8 +340,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }, 'click .oe_searchview_unfold_drawer': function (e) { e.stopImmediatePropagation(); - if (this.searchview_drawer) - this.searchview_drawer.toggle(); + if (this.drawer) + this.drawer.toggle(); }, 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { switch(e.which) { @@ -423,11 +423,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); } + var view_manager = this.getParent(); + while (!(view_manager instanceof instance.web.ViewManager)) { + view_manager = view_manager.getParent(); + } + view_manager.on('switch_mode', this, function (e) { + self.drawer.hide(); + }) + return $.when(p, this.ready); }, set_drawer: function (drawer) { - this.searchview_drawer = drawer; + this.drawer = drawer; }, show: function () { @@ -522,7 +530,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * @param {Function} resp response callback */ complete_global_search: function (req, resp) { - $.when.apply(null, _(this.inputs).chain() + $.when.apply(null, _(this.drawer.inputs).chain() .filter(function (input) { return input.visible(); }) .invoke('complete', req.term) .value()).then(function () { @@ -643,14 +651,14 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // Hacky implementation of CustomFilters#facet_for_defaults ensure // CustomFilters will be ready (and CustomFilters#filters will be // correctly filled) by the time this method executes. - var custom_filters = this.searchview_drawer.custom_filters.filters; + var custom_filters = this.drawer.custom_filters.filters; if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) { // Check for any is_default custom filter var personal_filter = _(custom_filters).find(function (filter) { return filter.user_id && filter.is_default; }); if (personal_filter) { - this.searchview_drawer.custom_filters.toggle_filter(personal_filter, true); + this.drawer.custom_filters.toggle_filter(personal_filter, true); return; } @@ -658,7 +666,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return !filter.user_id && filter.is_default; }); if (global_filter) { - this.searchview_drawer.custom_filters.toggle_filter(global_filter, true); + this.drawer.custom_filters.toggle_filter(global_filter, true); return; } } @@ -787,6 +795,10 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ this.$el.toggle(); }, + hide: function () { + this.$el.hide(); + }, + start: function() { var filters_ready = this.searchview.fields_view_get .then(this.proxy('prepare_filters')); From 1c32d931159cb420d3f6ff51d6d21beb674d9c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 20 May 2014 11:41:28 +0200 Subject: [PATCH 010/578] [FIX] correctly display the searchview/drawer (addon web) the searchview has been splitted in two widgets: SearchView and SearchViewDrawer. They are dependent of each other, so a drawer has to be created for each searchview. This was not the case in some form views (inline, created by a wizard). --- addons/web/static/src/css/base.css | 1 + addons/web/static/src/css/base.sass | 1 + addons/web/static/src/js/search.js | 2 +- addons/web/static/src/js/view_form.js | 5 +++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 085e0d5e08e..dda3a83c9f8 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1781,6 +1781,7 @@ overflow: auto; } .openerp .oe_searchview_drawer { + width: 100%; cursor: default; display: none; overflow: hidden; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 9b02b20b82a..1069de614f5 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1462,6 +1462,7 @@ $sheet-padding: 16px border-bottom: 1px solid $tag-border overflow: auto .oe_searchview_drawer + width: 100% cursor: default display: none overflow: hidden diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index d9f2a3c5c62..08246ca649a 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -831,7 +831,7 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ }, notify_searchview: function () { var defaults = arguments[1]; - this.searchview.drawer_ready.resolve.apply(null, defaults); + this.ready.resolve.apply(null, defaults); }, /** * Sets up thingie where all the mess is put? diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 4c4ba6f004c..a568f217145 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -5295,8 +5295,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend if (this.searchview) { this.searchview.destroy(); } + if (this.searchview_drawer) { + this.searchview_drawer.destroy(); + } this.searchview = new instance.web.SearchView(this, this.dataset, false, search_defaults); + this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview); this.searchview.on('search_data', self, function(domains, contexts, groupbys) { if (self.initial_ids) { self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]), @@ -5343,6 +5347,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }); this.searchview.appendTo($(".oe_popup_search", self.$el)); + this.searchview_drawer.appendTo($(".oe_popup_search", self.$el)); }, do_search: function(domains, contexts, groupbys) { var self = this; From 2c8dec5daacb68048e08d9c2037412c1acc0dafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 20 May 2014 11:56:06 +0200 Subject: [PATCH 011/578] [FIX] remove an extra border-bottom css rule (addon web) the border-bottom was set on drawer_container and on drawer, resulting in a double border. --- addons/web/static/src/css/base.css | 1 - addons/web/static/src/css/base.sass | 1 - 2 files changed, 2 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index dda3a83c9f8..000a0a0135c 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1777,7 +1777,6 @@ padding: 0; } .openerp .oe_searchview_drawer_container { - border-bottom: 1px solid #afafb6; overflow: auto; } .openerp .oe_searchview_drawer { diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 1069de614f5..9380af85057 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1459,7 +1459,6 @@ $sheet-padding: 16px padding: 0 .oe_searchview_drawer_container - border-bottom: 1px solid $tag-border overflow: auto .oe_searchview_drawer width: 100% From f7fe753d074160e6c21cc5b26f0d21bafb07db82 Mon Sep 17 00:00:00 2001 From: tpa-odoo Date: Tue, 20 May 2014 17:00:15 +0530 Subject: [PATCH 012/578] [IMP] unlink ir.action.todo which are related to actions which will be deleted. --- openerp/addons/base/ir/ir_actions.py | 7 +++++++ openerp/addons/base/ir/ir_ui_menu.py | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 57f24c8864a..3fb42ac75c8 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -57,6 +57,13 @@ class actions(osv.osv): 'usage': lambda *a: False, } + def unlink(self, cr, uid, ids, context=None): + """unlink ir.action.todo which are related to actions which will be deleted. + NOTE: ondelete cascade will not work on ir.actions.actions so we will need to do it manually.""" + todo_obj = self.pool.get('ir.actions.todo') + todo_ids = todo_obj.search(cr, uid, [('action_id', 'in', ids)], context=context) + todo_obj.unlink(cr, uid, todo_ids, context=context) + return super(actions, self).unlink(cr, uid, ids, context=context) class ir_actions_report_xml(osv.osv): diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index f01ce54bfc2..957e0903013 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -312,9 +312,13 @@ class ir_ui_menu(osv.osv): menu_ref = [menu_ref] model_data_obj = self.pool.get('ir.model.data') for menu_data in menu_ref: - model, id = model_data_obj.get_object_reference(cr, uid, menu_data.split('.')[0], menu_data.split('.')[1]) - if (model == 'ir.ui.menu'): - menu_ids.add(id) + try: + model, id = model_data_obj.get_object_reference(cr, uid, menu_data.split('.')[0], menu_data.split('.')[1]) + if (model == 'ir.ui.menu'): + menu_ids.add(id) + except Exception: + pass + menu_ids = list(menu_ids) for menu in self.browse(cr, uid, menu_ids, context=context): From 7c6c720425f2915ded93534171aff265dab049ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 20 May 2014 15:27:44 +0200 Subject: [PATCH 013/578] [IMP] better architecture for searchview/drawer Before, the searchview and the searchview drawer had to be instantiated by the user and appended separately to their correct place. Now, the searchview is responsible for creating/destroying the drawer, and the user is responsible for correctly using the insert method with the node where the searchview/drawer are to be inserted. --- addons/mail/static/src/js/mail.js | 5 ++-- addons/web/static/src/js/search.js | 36 ++++++++++++++++++++++++--- addons/web/static/src/js/view_form.js | 3 +-- addons/web/static/src/js/views.js | 9 ++----- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 9cf33203a45..cb01efae515 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1888,10 +1888,9 @@ openerp.mail = function (session) { var self = this; var ds_msg = new session.web.DataSetSearch(this, 'mail.message'); this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false); - this.searchview_drawer = new session.web.SearchViewDrawer(this, this.searchview); - $.when(this.searchview.appendTo(this.$('.oe_view_manager_view_search')), - this.searchview_drawer.appendTo(this.$('.oe_searchview_drawer_container'))) + this.searchview.insert(this.$('.oe_view_manager_view_search'), + this.$('.oe_searchview_drawer_container')) .then(function () { self.searchview.on('search_data', self, self.do_searchview_search); }); if (this.searchview.has_defaults) { this.searchview.ready.then(this.searchview.do_search); diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 08246ca649a..2c7ef9948b6 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -391,6 +391,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.ready = $.Deferred(); this.drawer_ready = $.Deferred(); this.fields_view_get = $.Deferred(); + this.drawer = new instance.web.SearchViewDrawer(parent, this); + }, start: function() { var self = this; @@ -424,13 +426,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } var view_manager = this.getParent(); - while (!(view_manager instanceof instance.web.ViewManager)) { + while (!(view_manager instanceof instance.web.ViewManager) && + (view_manager !== undefined)) { view_manager = view_manager.getParent(); } - view_manager.on('switch_mode', this, function (e) { - self.drawer.hide(); - }) + if (view_manager) { + view_manager.on('switch_mode', this, function (e) { + self.drawer.hide(); + }); + } return $.when(p, this.ready); }, @@ -776,6 +781,29 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea on_invalid: function (errors) { this.do_notify(_t("Invalid Search"), _t("triggered from search view")); this.trigger('invalid_search', errors); + }, + + // The method appendTo is overwrited to make sure that the search view is not + // appendTo'ed. Instead, the correct way to insert the searchview in the DOM + // is by using insert + appendTo: function () { + throw new Error("SearchView should be inserted, not appended"); + }, + + // This is the correct way to insert the searchview in the dom. First argument + // is for the searchview, and second is the place where the drawer should be + // appended. + insert: function ($searchview_parent, $searchview_drawer_node) { + var parent = this.getParent(), + $searchview_drawer_node = $searchview_drawer_node || $searchview_parent, + insert_searchview = parent.appendTo.call(this, $searchview_parent), + insert_drawer = parent.appendTo.call(this.drawer, $searchview_drawer_node); + return $.when(insert_searchview, insert_drawer); + }, + + destroy: function () { + this.drawer.destroy(); + this.getParent().destroy.call(this); } }); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index a568f217145..797ecd0e807 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -5346,8 +5346,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }); }); - this.searchview.appendTo($(".oe_popup_search", self.$el)); - this.searchview_drawer.appendTo($(".oe_popup_search", self.$el)); + this.searchview.insert(this.$(".oe_popup_search")); }, do_search: function(domains, contexts, groupbys) { var self = this; diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index f954ebd9ad5..8f01c4775c2 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -553,7 +553,6 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.model = dataset ? dataset.model : undefined; this.dataset = dataset; this.searchview = null; - this.searchview_drawer = null; this.active_view = null; this.views_src = _.map(views, function(x) { if (x instanceof Array) { @@ -801,20 +800,16 @@ instance.web.ViewManager = instance.web.Widget.extend({ if (this.searchview) { this.searchview.destroy(); } - if (this.searchview_drawer) { - this.searchview_drawer.destroy(); - } var options = { hidden: this.flags.search_view === false, disable_custom_filters: this.flags.search_disable_custom_filters, }; this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, options); - this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview); this.searchview.on('search_data', self, this.do_searchview_search); - return $.when(this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")), - this.searchview_drawer.appendTo(this.$(".oe_searchview_drawer_container"))); + return this.searchview.insert(this.$(".oe_view_manager_view_search"), + this.$(".oe_searchview_drawer_container")); }, do_searchview_search: function(domains, contexts, groupbys) { var self = this, From cc64d1d3180a56cfef6de073f1889129b82286a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 20 May 2014 16:18:03 +0200 Subject: [PATCH 014/578] [FIX] fix the graphview field selection searchview has been split in two parts, and the inputs are now located in the drawer --- addons/web_graph/static/src/js/graph_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js index d0038452dfb..ae9be9e0844 100644 --- a/addons/web_graph/static/src/js/graph_widget.js +++ b/addons/web_graph/static/src/js/graph_widget.js @@ -86,7 +86,7 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ get_search_fields: function () { var self = this; - var groupbygroups = _(this.search_view.inputs).select(function (g) { + var groupbygroups = _(this.search_view.drawer.inputs).select(function (g) { return g instanceof openerp.web.search.GroupbyGroup; }); From 93238cd1cb68fcfc27fd6be8fd7d9a4eca1f2d0e Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 20 May 2014 20:23:25 +0200 Subject: [PATCH 015/578] [FIX] website: respect max_{width,height} for /website/image route --- addons/website/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 460f64147e8..ab6f6f08564 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -381,7 +381,7 @@ class Website(openerp.addons.web.controllers.main.Home): """ response = werkzeug.wrappers.Response() return request.registry['website']._image( - request.cr, request.uid, model, id, field, response) + request.cr, request.uid, model, id, field, response, max_width, max_height) #------------------------------------------------------ From e184e502c09f0b4864686712664c6e7b1a65d883 Mon Sep 17 00:00:00 2001 From: Mantavya Gajjar Date: Wed, 21 May 2014 13:23:01 +0530 Subject: [PATCH 016/578] [IMP]: change author and module name --- addons/l10n_in/__openerp__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/l10n_in/__openerp__.py b/addons/l10n_in/__openerp__.py index b3dab229201..9c912caa94c 100644 --- a/addons/l10n_in/__openerp__.py +++ b/addons/l10n_in/__openerp__.py @@ -20,15 +20,15 @@ ############################################################################## { - 'name': 'Indian - Accounting', + 'name': 'Indian Chart of Account', 'version': '1.0', 'description': """ -Indian Accounting: Chart of Account. -==================================== +Indian Chart of Account +======================= Indian accounting chart and localization. """, - 'author': ['OpenERP SA', 'Axelor'], + 'author': ['OpenERP SA'], 'category': 'Localization/Account Charts', 'depends': [ 'account', From f1a6ab5070226c1af615079c0b434c8c3b4dcbd3 Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Wed, 21 May 2014 10:00:08 +0200 Subject: [PATCH 017/578] [FIX] website_hr_recruitment: Hide unpublished job offers to portal users and employees --- .../website_hr_recruitment_security.xml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml b/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml index 792995ca606..5d47e69fead 100644 --- a/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml +++ b/addons/website_hr_recruitment/security/website_hr_recruitment_security.xml @@ -11,6 +11,26 @@ + + Job Positions: Portal + + [('website_published', '=', True)] + + + + + + + + Job Positions: HR Officer + + [(1, '=', 1)] + + + + + + Job department: Public From b6a7402fdbddfe817fa36292e6f89e8659f9bdea Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 21 May 2014 11:20:37 +0200 Subject: [PATCH 018/578] [FIX] report: correct page numbering --- openerp/report/render/rml2pdf/trml2pdf.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index 91dfc9dd79b..00b119ac61f 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -104,7 +104,7 @@ class NumberedCanvas(canvas.Canvas): self.setFont("Helvetica", 8) self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40), " %(this)i / %(total)i" % { - 'this': self._pageNumber+1, + 'this': self._pageNumber, 'total': page_count, } ) @@ -1001,8 +1001,6 @@ class _rml_template(object): if story_cnt > 0: fis.append(platypus.PageBreak()) fis += r.render(node_story) - # Reset Page Number with new story tag - fis.append(PageReset()) story_cnt += 1 try: if self.localcontext and self.localcontext.get('internal_header',False): From 35c5e56718e1ce8ca3747f90ba5c06d8f9d20b1c Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 11:57:45 +0200 Subject: [PATCH 019/578] [FIX] address div more to the left on external reports --- addons/account/views/report_invoice.xml | 2 +- addons/account/views/report_overdue.xml | 2 +- addons/account_followup/views/report_followup.xml | 2 +- addons/hr_payroll/views/report_payslip.xml | 2 +- addons/lunch/views/report_lunchorder.xml | 2 +- addons/mrp_repair/views/report_mrprepairorder.xml | 2 +- addons/purchase/views/report_purchaseorder.xml | 2 +- addons/purchase/views/report_purchasequotation.xml | 2 +- addons/report/data/report_paperformat.xml | 2 +- addons/report_intrastat/views/report_intrastatinvoice.xml | 2 +- addons/sale/views/report_saleorder.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 6c4b701c6cf..a250eb9ebf7 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -5,7 +5,7 @@
      -
      +
      diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml index bc522aa8b9d..df3934ff0de 100644 --- a/addons/account/views/report_overdue.xml +++ b/addons/account/views/report_overdue.xml @@ -5,7 +5,7 @@
      -
      +

      diff --git a/addons/account_followup/views/report_followup.xml b/addons/account_followup/views/report_followup.xml index 6c79fe4cc55..c0d98e7bb28 100644 --- a/addons/account_followup/views/report_followup.xml +++ b/addons/account_followup/views/report_followup.xml @@ -7,7 +7,7 @@
      -
      +
      diff --git a/addons/hr_payroll/views/report_payslip.xml b/addons/hr_payroll/views/report_payslip.xml index 5ac9dfa557e..b30cb089d1a 100644 --- a/addons/hr_payroll/views/report_payslip.xml +++ b/addons/hr_payroll/views/report_payslip.xml @@ -19,7 +19,7 @@ Address -
      diff --git a/addons/lunch/views/report_lunchorder.xml b/addons/lunch/views/report_lunchorder.xml index 93d43f6521d..30401bc142b 100644 --- a/addons/lunch/views/report_lunchorder.xml +++ b/addons/lunch/views/report_lunchorder.xml @@ -8,7 +8,7 @@
      -
      +
      diff --git a/addons/mrp_repair/views/report_mrprepairorder.xml b/addons/mrp_repair/views/report_mrprepairorder.xml index 0ea3f22167c..2897fee1fd9 100644 --- a/addons/mrp_repair/views/report_mrprepairorder.xml +++ b/addons/mrp_repair/views/report_mrprepairorder.xml @@ -22,7 +22,7 @@

      VAT:

      -
      +
      diff --git a/addons/purchase/views/report_purchaseorder.xml b/addons/purchase/views/report_purchaseorder.xml index c03e535ff75..bd1f1846cd5 100644 --- a/addons/purchase/views/report_purchaseorder.xml +++ b/addons/purchase/views/report_purchaseorder.xml @@ -23,7 +23,7 @@

      VAT:

      -
      +
      diff --git a/addons/purchase/views/report_purchasequotation.xml b/addons/purchase/views/report_purchasequotation.xml index 68e8995ddd8..7f6c8f5bb78 100644 --- a/addons/purchase/views/report_purchasequotation.xml +++ b/addons/purchase/views/report_purchasequotation.xml @@ -23,7 +23,7 @@

      VAT:

      -
      +
      diff --git a/addons/report/data/report_paperformat.xml b/addons/report/data/report_paperformat.xml index 275d65edd13..f05e0afb6f0 100644 --- a/addons/report/data/report_paperformat.xml +++ b/addons/report/data/report_paperformat.xml @@ -9,7 +9,7 @@ 0 Portrait 40 - 20 + 23 7 7 diff --git a/addons/report_intrastat/views/report_intrastatinvoice.xml b/addons/report_intrastat/views/report_intrastatinvoice.xml index c534f808c95..abf4bdd56ba 100644 --- a/addons/report_intrastat/views/report_intrastatinvoice.xml +++ b/addons/report_intrastat/views/report_intrastatinvoice.xml @@ -5,7 +5,7 @@
      -
      +
      diff --git a/addons/sale/views/report_saleorder.xml b/addons/sale/views/report_saleorder.xml index c23e680e664..5c198c635a3 100644 --- a/addons/sale/views/report_saleorder.xml +++ b/addons/sale/views/report_saleorder.xml @@ -19,7 +19,7 @@

      VAT:

      -
      +
      From 4019b2b3345c044e41492404a2322c169b75a23e Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 12:56:56 +0200 Subject: [PATCH 020/578] [FIX] Report: do not raise when wkhtmltopdf returns a code 1. In most cases, it is due to some http requests which did not success *but* the pdf is still acceptable. Far from the best commit ever, but it avoids to completely crash when just a link to an image is wrong --- addons/report/models/report.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index fed6a2335ee..d744c917343 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -353,8 +353,6 @@ class Report(osv.Model): if paperformat: command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args)) - command_args.extend(['--load-error-handling', 'ignore']) - if landscape and '--orientation' in command_args: command_args_copy = list(command_args) for index, elem in enumerate(command_args_copy): @@ -417,7 +415,7 @@ class Report(osv.Model): if config['workers'] == 1: os.kill(ppid, signal.SIGTTOU) - if process.returncode != 0: + if process.returncode not in [0, 1]: raise osv.except_osv(_('Report (PDF)'), _('wkhtmltopdf failed with error code = %s. ' 'Message: %s') % (str(process.returncode), err)) From 6958d47694df0a5397b07c40b061c41e43046941 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 21 May 2014 12:57:28 +0200 Subject: [PATCH 021/578] [FIX] account_analytic_analysis: recurring invoices prepare and product_id_change product_id_change set the description with the same behavior as sale orders product_id_change set the price unit according to the pricelist _prepare_invoice is split into two sub methods, _prepare_invoice_data and _prepare_invoice_line _prepare_invoice actually prepare the invoice values instead of directly creating the invoice --- .../account_analytic_analysis.py | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 9f110ed78e6..76b68a7b1fa 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -62,22 +62,33 @@ class account_analytic_invoice_line(osv.osv): context = context or {} uom_obj = self.pool.get('product.uom') company_id = company_id or False - context.update({'company_id': company_id, 'force_company': company_id, 'pricelist_id': pricelist_id}) + local_context = dict(context, company_id=company_id, force_company=company_id, pricelist=pricelist_id) if not product: return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}} if partner_id: - part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) + part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=local_context) if part.lang: - context.update({'lang': part.lang}) + local_context.update({'lang': part.lang}) result = {} - res = self.pool.get('product.product').browse(cr, uid, product, context=context) - result.update({'name': name or res.description or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price_unit or res.list_price or 0.0}) + res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) + if price_unit is not False: + price = price_unit + elif pricelist_id: + price = res.price + else: + price = res.list_price + if not name: + name = self.pool.get('product.product').name_get(cr, uid, [res.id], context=local_context)[0][1] + if res.description_sale: + result['name'] += '\n'+res.description_sale + + result.update({'name': name or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price}) res_final = {'value':result} if result['uom_id'] != res.uom_id.id: - selected_uom = uom_obj.browse(cr, uid, result['uom_id'], context=context) + selected_uom = uom_obj.browse(cr, uid, result['uom_id'], context=local_context) new_price = uom_obj._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uom_id']) res_final['value']['price_unit'] = new_price return res_final @@ -645,12 +656,10 @@ class account_analytic_account(osv.osv): 'nodestroy': True, } - def _prepare_invoice(self, cr, uid, contract, context=None): + def _prepare_invoice_data(self, cr, uid, contract, context=None): context = context or {} - inv_obj = self.pool.get('account.invoice') journal_obj = self.pool.get('account.journal') - fpos_obj = self.pool.get('account.fiscal.position') if not contract.partner_id: raise osv.except_osv(_('No Customer Defined!'),_("You must first select a Customer for Contract %s!") % contract.name ) @@ -671,33 +680,36 @@ class account_analytic_account(osv.osv): elif contract.company_id: currency_id = contract.company_id.currency_id.id - inv_data = { - 'reference': contract.code or False, + invoice = { 'account_id': contract.partner_id.property_account_receivable.id, 'type': 'out_invoice', 'partner_id': contract.partner_id.id, 'currency_id': currency_id, 'journal_id': len(journal_ids) and journal_ids[0] or False, 'date_invoice': contract.recurring_next_date, - 'origin': contract.name, + 'origin': contract.code, 'fiscal_position': fpos and fpos.id, 'payment_term': partner_payment_term, 'company_id': contract.company_id.id or False, } - invoice_id = inv_obj.create(cr, uid, inv_data, context=context) + return invoice + def _prepare_invoice_lines(self, cr, uid, contract, fiscal_position_id, context=None): + fpos_obj = self.pool.get('account.fiscal.position') + fiscal_position = fpos_obj.browse(cr, uid, fiscal_position_id, context=context) + invoice_lines = [] for line in contract.recurring_invoice_line_ids: res = line.product_id account_id = res.property_account_income.id if not account_id: account_id = res.categ_id.property_account_income_categ.id - account_id = fpos_obj.map_account(cr, uid, fpos, account_id) + account_id = fpos_obj.map_account(cr, uid, fiscal_position, account_id) taxes = res.taxes_id or False - tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) + tax_id = fpos_obj.map_tax(cr, uid, fiscal_position, taxes) - invoice_line_vals = { + invoice_lines.append((0, 0, { 'name': line.name, 'account_id': account_id, 'account_analytic_id': contract.id, @@ -705,13 +717,14 @@ class account_analytic_account(osv.osv): 'quantity': line.quantity, 'uos_id': line.uom_id.id or False, 'product_id': line.product_id.id or False, - 'invoice_id' : invoice_id, 'invoice_line_tax_id': [(6, 0, tax_id)], - } - self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context) + })) + return invoice_lines - inv_obj.button_compute(cr, uid, [invoice_id], context=context) - return invoice_id + def _prepare_invoice(self, cr, uid, contract, context=None): + invoice = self._prepare_invoice_data(cr, uid, contract, context=context) + invoice['invoice_line'] = self._prepare_invoice_lines(cr, uid, contract, invoice['fiscal_position'], context=context) + return invoice def recurring_create_invoice(self, cr, uid, ids, context=None): return self._recurring_create_invoice(cr, uid, ids, context=context) @@ -721,6 +734,7 @@ class account_analytic_account(osv.osv): def _recurring_create_invoice(self, cr, uid, ids, automatic=False, context=None): context = context or {} + invoice_ids = [] current_date = time.strftime('%Y-%m-%d') if ids: contract_ids = ids @@ -728,8 +742,8 @@ class account_analytic_account(osv.osv): contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) for contract in self.browse(cr, uid, contract_ids, context=context): try: - invoice_id = self._prepare_invoice(cr, uid, contract, context=context) - + invoice_values = self._prepare_invoice(cr, uid, contract, context=context) + invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") interval = contract.recurring_interval if contract.recurring_rule_type == 'daily': @@ -747,7 +761,7 @@ class account_analytic_account(osv.osv): _logger.error(traceback.format_exc()) else: raise - return True + return invoice_ids class account_analytic_account_summary_user(osv.osv): _name = "account_analytic_analysis.summary.user" From 1745c738eeac326a06f31d2fc4ecd0e0f71d5f86 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 12:59:05 +0200 Subject: [PATCH 022/578] [FIX] Report: double the default height/width of barcode image to avoid a blurred/unusable barcode once integrated in a report --- addons/report/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index d3a683cccdc..800c10140b6 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -64,7 +64,7 @@ class ReportController(Controller): # Misc. route utils #------------------------------------------------------ @route(['/report/barcode', '/report/barcode//'], type='http', auth="user") - def report_barcode(self, type, value, width=300, height=50): + def report_barcode(self, type, value, width=600, height=100): """Contoller able to render barcode images thanks to reportlab. Samples: From 226579a80deac960b2345a8a7b47b683994deff8 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 13:01:42 +0200 Subject: [PATCH 023/578] [FIX] Report: removed psutil/signal/openerp worker crap --- addons/report/models/report.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index d744c917343..89979bb7220 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -20,15 +20,11 @@ ############################################################################## from openerp.osv import osv -from openerp.tools import config from openerp.tools.translate import _ from openerp.addons.web.http import request from openerp.tools.safe_eval import safe_eval as eval -import os import time -import psutil -import signal import base64 import logging import tempfile @@ -399,25 +395,15 @@ class Report(osv.Model): content_file.seek(0) try: - # If the server is running with only one worker, ask to create a secund to be able - # to serve the http request of wkhtmltopdf subprocess. - if config['workers'] == 1: - ppid = psutil.Process(os.getpid()).ppid - os.kill(ppid, signal.SIGTTIN) - wkhtmltopdf = command + command_args + command_arg_local wkhtmltopdf += [content_file.name] + [pdfreport.name] - process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() - if config['workers'] == 1: - os.kill(ppid, signal.SIGTTOU) - if process.returncode not in [0, 1]: raise osv.except_osv(_('Report (PDF)'), - _('wkhtmltopdf failed with error code = %s. ' + _('Wkhtmltopdf failed with error code = %s. ' 'Message: %s') % (str(process.returncode), err)) # Save the pdf in attachment if marked From 654df600c1c2ec398506058ae6a0e83fa0555627 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 13:02:59 +0200 Subject: [PATCH 024/578] [FIX] Report: less verbose error message --- addons/report/models/report.py | 40 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 89979bb7220..c76a6b72e0e 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -338,17 +338,20 @@ class Report(osv.Model): command_args = [] tmp_dir = tempfile.gettempdir() - # Passing the cookie to wkhtmltopdf in order to resolve URL. + # Passing the cookie to wkhtmltopdf in order to resolve internal links. try: if request: command_args.extend(['--cookie', 'session_id', request.session.sid]) except AttributeError: pass - # Display arguments + # Wkhtmltopdf arguments + command_args.extend(['--quiet']) # Less verbose error messages if paperformat: + # Convert the paperformat record into arguments command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args)) + # Force the landscape orientation if necessary if landscape and '--orientation' in command_args: command_args_copy = list(command_args) for index, elem in enumerate(command_args_copy): @@ -359,43 +362,38 @@ class Report(osv.Model): elif landscape and not '--orientation' in command_args: command_args.extend(['--orientation', 'landscape']) + # Execute WKhtmltopdf pdfdocuments = [] - # HTML to PDF thanks to WKhtmltopdf for index, reporthtml in enumerate(bodies): - command_arg_local = [] - pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', - mode='w+b') - # Directly load the document if we have it + local_command_args = [] + pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.', mode='w+b') + + # Directly load the document if we already have it if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) pdfreport.seek(0) pdfdocuments.append(pdfreport) continue - # Header stuff + # Wkhtmltopdf handles header/footer as separate pages. Create them if necessary. if headers: - head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', - dir=tmp_dir, mode='w+') + head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+') head_file.write(headers[index]) head_file.seek(0) - command_arg_local.extend(['--header-html', head_file.name]) - - # Footer stuff + local_command_args.extend(['--header-html', head_file.name]) if footers: - foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', - dir=tmp_dir, mode='w+') + foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+') foot_file.write(footers[index]) foot_file.seek(0) - command_arg_local.extend(['--footer-html', foot_file.name]) + local_command_args.extend(['--footer-html', foot_file.name]) # Body stuff - content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', - dir=tmp_dir, mode='w+') + content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+') content_file.write(reporthtml[1]) content_file.seek(0) try: - wkhtmltopdf = command + command_args + command_arg_local + wkhtmltopdf = command + command_args + local_command_args wkhtmltopdf += [content_file.name] + [pdfreport.name] process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -403,7 +401,7 @@ class Report(osv.Model): if process.returncode not in [0, 1]: raise osv.except_osv(_('Report (PDF)'), - _('Wkhtmltopdf failed with error code = %s. ' + _('Wkhtmltopdf failed (error code: %s). ' 'Message: %s') % (str(process.returncode), err)) # Save the pdf in attachment if marked @@ -429,7 +427,7 @@ class Report(osv.Model): except: raise - # Get and return the full pdf + # Return the entire document if len(pdfdocuments) == 1: content = pdfdocuments[0].read() pdfdocuments[0].close() From ff4fe2c05e35ccbf435583474a942512363b4cb5 Mon Sep 17 00:00:00 2001 From: Pariket Trivedi Date: Wed, 21 May 2014 16:34:34 +0530 Subject: [PATCH 025/578] [FIX]: change correct label and fields in Event Analysis. --- addons/event/report/report_event_registration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/event/report/report_event_registration.py b/addons/event/report/report_event_registration.py index 595d3947687..1ce3591a105 100644 --- a/addons/event/report/report_event_registration.py +++ b/addons/event/report/report_event_registration.py @@ -37,7 +37,7 @@ class report_event_registration(osv.osv): 'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'register_max': fields.integer('Maximum Registrations'), - 'nbevent': fields.integer('Number Of Events'), + 'nbevent': fields.integer('Number of Registrations in Events'), 'event_type': fields.many2one('event.type', 'Event Type'), 'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True), 'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True), @@ -68,7 +68,7 @@ class report_event_registration(osv.osv): to_char(e.date_begin, 'YYYY-MM-DD') AS event_date, to_char(e.date_begin, 'YYYY') AS year, to_char(e.date_begin, 'MM') AS month, - count(e.id) AS nbevent, + count(r.id) AS nbevent, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, e.type AS event_type, From 58d3a6f3f7bf295a2328b557f8094811a67c9942 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 13:06:31 +0200 Subject: [PATCH 026/578] [FIX] Report: inform the user that he may not print a pdf report while his openerp is running with only one worker; adapt method to extract wkhtmltopdf version to also work on mac where the ouput of '--version' is different from gnu/linux binary --- addons/report/models/report.py | 23 +++++--- .../report/static/src/js/qwebactionmanager.js | 53 ++++++++++--------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index c76a6b72e0e..32752fa6e33 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.osv import osv +from openerp.tools import config from openerp.tools.translate import _ from openerp.addons.web.http import request from openerp.tools.safe_eval import safe_eval as eval @@ -48,15 +49,23 @@ try: ['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) except OSError: - _logger.error('You need wkhtmltopdf to print a pdf version of the reports.') + _logger.info('You need wkhtmltopdf to print a pdf version of the reports.') else: out, err = process.communicate() - version = out.splitlines()[1].strip() - version = version.split(' ')[1] - if LooseVersion(version) < LooseVersion('0.12.0'): - _logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0') - wkhtmltopdf_state = 'upgrade' - wkhtmltopdf_state = 'ok' + + for line in out.splitlines(): + line = line.strip() + if line.startswith('wkhtmltopdf 0.'): + version = line.split(' ')[1] + if LooseVersion(version) < LooseVersion('0.12.0'): + _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0') + wkhtmltopdf_state = 'upgrade' + else: + wkhtmltopdf_state = 'ok' + + if config['workers'] == 1: + _logger.info('You need to start OpenERP with at least two workers to print a pdf version of the reports.') + wkhtmltopdf_state = 'workers' class Report(osv.Model): diff --git a/addons/report/static/src/js/qwebactionmanager.js b/addons/report/static/src/js/qwebactionmanager.js index 7e55661be86..cb0f3921482 100644 --- a/addons/report/static/src/js/qwebactionmanager.js +++ b/addons/report/static/src/js/qwebactionmanager.js @@ -54,34 +54,35 @@ openerp.report = function(instance) { if (action.report_type == 'qweb-html') { window.open(report_url, '_blank', 'height=900,width=1280'); instance.web.unblockUI(); - } else { + } else if (action.report_type === 'qweb-pdf') { // Trigger the download of the pdf/controller report - - if (action.report_type == 'qweb-pdf') { - (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) { - // Fallback of qweb-pdf if wkhtmltopdf is not installed - if (presence == 'install' && action.report_type == 'qweb-pdf') { - self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \ - system. The report will be shown in html.

      \ - wkhtmltopdf.org'), true); - report_url = report_url.substring(12) - window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); - instance.web.unblockUI(); - return; - } else { - if (presence == 'upgrade') { - self.do_notify(_t('Report'), _t('You should upgrade your version of\ - Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\ - support for table-breaking between pages.

      wkhtmltopdf.org'), true); - } - } - return trigger_download(self.session, response, c); - }); - } - else if (action.report_type == 'controller') { + (wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) { + // Fallback on html if wkhtmltopdf is not installed or if OpenERP is started with one worker + if (presence === 'install') { + self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \ +system. The report will be shown in html.

      \ +wkhtmltopdf.org'), true); + report_url = report_url.substring(12) + window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); + instance.web.unblockUI(); + return; + } else if (presence === 'workers') { + self.do_notify(_t('Report'), _t('You need to start OpenERP with at least two \ +workers to print a pdf version of the reports.'), true); + report_url = report_url.substring(12) + window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); + instance.web.unblockUI(); + return; + } else if (presence === 'upgrade') { + self.do_notify(_t('Report'), _t('You should upgrade your version of\ + Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\ + support for table-breaking between pages.

      wkhtmltopdf.org'), true); + } return trigger_download(self.session, response, c); - } + }); + } else if (action.report_type === 'controller') { + return trigger_download(self.session, response, c); } } else { return self._super(action, options); From b50421e551ef120e0fa77cddec9247fd1ac982ed Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 13:11:27 +0200 Subject: [PATCH 027/578] [FIX] ir_actions: adapt render_report when called in a tests/qweb report context to generate the pdf only if a directory has been provided --- openerp/addons/base/ir/ir_actions.py | 16 ++++++++++------ openerp/tools/test_reports.py | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 4e177959792..8aa968a3b3a 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -41,6 +41,7 @@ import openerp.workflow _logger = logging.getLogger(__name__) + class actions(osv.osv): _name = 'ir.actions.actions' _table = 'ir_actions' @@ -129,9 +130,15 @@ class ir_actions_report_xml(osv.osv): Look up a report definition and render the report for the provided IDs. """ new_report = self._lookup_report(cr, name) - # in order to use current yml test files with qweb reports - if isinstance(new_report, (str, unicode)): - return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf' + + if isinstance(new_report, (str, unicode)): # Qweb report + # The only case where a QWeb report is rendered with this method occurs when running + # yml tests originally written for RML reports. + if openerp.tools.config['test_enable'] and not tools.config['test_report_directory']: + # Only generate the pdf when a destination folder has been provided. + return self.pool['report'].get_html(cr, uid, res_ids, new_report, data=data, context=context), 'html' + else: + return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf' else: return new_report.create(cr, uid, res_ids, data, context) @@ -1170,7 +1177,4 @@ class ir_actions_act_client(osv.osv): } - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/openerp/tools/test_reports.py b/openerp/tools/test_reports.py index c008c20ae29..305d2b0f115 100644 --- a/openerp/tools/test_reports.py +++ b/openerp/tools/test_reports.py @@ -87,8 +87,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None, re if ('[[' in line) or ('[ [' in line): _logger.error("Report %s may have bad expression near: \"%s\".", rname, line[80:]) # TODO more checks, what else can be a sign of a faulty report? - elif res_format == 'foobar': - # TODO + elif res_format == 'html': pass else: _logger.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) From aec3ba08ae6b8ec50c8f1cb57ae510f567556d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 21 May 2014 13:18:30 +0200 Subject: [PATCH 028/578] [IMP] improvements to inline searchview (web) this commit splits the widget CustomFilters in two widgets: CustomReports and SaveFilter. CustomReports is the widget displaying the list of reports, and SaveFilter obviously is the Save Current Filter widget. The goal was to put the custom reports in the first column and the Save Current Filter form in the second. --- addons/web/static/src/css/base.css | 228 +--------------------------- addons/web/static/src/css/base.sass | 186 +---------------------- addons/web/static/src/js/search.js | 61 +++++--- addons/web/static/src/xml/base.xml | 12 +- 4 files changed, 48 insertions(+), 439 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 000a0a0135c..f46a0de5836 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1556,226 +1556,6 @@ -webkit-border-radius: 2px; border-radius: 2px; } -.openerp .oe_searchview.oe_searchview_open_drawer .oe_searchview_drawer { - display: block; -} -.openerp .oe_searchview .oe_searchview_drawer { - cursor: default; - position: absolute; - z-index: 2; - margin-top: 4px; - top: 100%; - right: -1px; - background-color: white; - min-width: 100%; - display: none; - border: 1px solid #afafb6; - text-align: left; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -} -.openerp .oe_searchview .oe_searchview_drawer > div { - border-top: 1px solid #cccccc; - margin: 0; - padding: 8px; -} -.openerp .oe_searchview .oe_searchview_drawer > div:first-child { - border-top: none; - margin: 0; -} -.openerp .oe_searchview .oe_searchview_drawer h3 { - margin: 8px 4px 4px 0px; - color: #7c7bad; - font-size: 13px; -} -.openerp .oe_searchview .oe_searchview_drawer h4, .openerp .oe_searchview .oe_searchview_drawer h4 * { - margin: 0 0 0 2px; - cursor: pointer; - font-weight: normal; - display: inline-block; -} -.openerp .oe_searchview .oe_searchview_drawer h4:hover, .openerp .oe_searchview .oe_searchview_drawer h4 *:hover { - background-color: #f0f0fa; -} -.openerp .oe_searchview .oe_searchview_drawer h4:before { - content: "▸ "; - color: #a3a3a3; -} -.openerp .oe_searchview .oe_searchview_drawer button { - margin: 4px 0; -} -.openerp .oe_searchview .oe_searchview_drawer .button { - border: none; - background: transparent; - padding: 0 2px; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section { - display: table; - width: 100%; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section > div { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - display: table-cell; - width: 50%; - padding-left: 2px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section ul { - margin: 0 8px 8px; - padding: 0; - list-style: none; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section li { - list-style: none; - padding: 2px 4px 2px 20px; - line-height: 14px; - color: inherit; - cursor: pointer; - position: relative; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section li.oe_selected:before { - content: "W"; - font-family: "entypoRegular" !important; - font-size: 24px; - font-weight: 300 !important; - color: #a3a3a3; - position: absolute; - left: 4px; - top: -2px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section li:hover { - background-color: #f0f0fa; -} -.openerp .oe_searchview .oe_searchview_drawer form { - margin-left: 12px; -} -.openerp .oe_searchview .oe_searchview_drawer form p { - margin: 4px 0; - line-height: 18px; -} -.openerp .oe_searchview .oe_searchview_drawer form button { - margin: 0 0 8px -3px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom { - padding: 0 8px 8px 8px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom div { - padding: 0; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom div h4 { - margin: 0; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom form { - display: none; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom li { - cursor: pointer; - position: relative; - line-height: 14px; - padding: 2px 4px 2px 20px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom li:hover { - background-color: #f0f0fa; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom li button { - position: absolute; - top: 0; - right: 5px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom label { - font-weight: normal; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_dashboard form { - display: none; - margin-top: 2px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced form { - display: none; - margin-top: 8px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced button.oe_add_condition:before { - content: "Z"; - font-family: "entypoRegular" !important; - font-size: 24px; - font-weight: 300 !important; - margin-right: 4px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced ul { - list-style: none; - padding: 0; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced li { - position: relative; - list-style: none; - margin: 0; - white-space: nowrap; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced li:first-child .searchview_extended_prop_or { - visibility: hidden; - margin-left: -14px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced .searchview_extended_prop_or { - opacity: 0.5; - margin-left: -14px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_opened h4:before { - content: "▾ "; - position: relative; - top: -1px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_opened form { - display: block; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete, .openerp .oe_searchview .oe_searchview_drawer .searchview_extended_delete_prop { - display: inline-block; - width: 12px; - height: 12px; - line-height: 12px; - padding: 1px; - color: #8786b7; - line-height: 8px; - text-align: center; - font-weight: bold; - text-shadow: 0 1px 1px white; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete:hover, .openerp .oe_searchview .oe_searchview_drawer .searchview_extended_delete_prop:hover { - text-decoration: none; - color: white; - background: #8786b7; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete { - display: none; - position: absolute; - bottom: 1px; - right: 4px; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_private:hover .oe_searchview_custom_delete, .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_public:hover .oe_searchview_custom_delete { - display: inline-block; -} -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_public:after { - content: ","; - font-family: "entypoRegular" !important; - font-size: 22px; - font-weight: 300 !important; - margin: 0 0 0 4px; - padding: 0; -} .openerp .oe_searchview_drawer_container { overflow: auto; } @@ -1804,7 +1584,7 @@ padding-left: 0; } .openerp .oe_searchview_drawer dl { - margin-bottom: 10px; + margin-bottom: 0; } .openerp .oe_searchview_drawer dt { color: #7c7bad; @@ -1866,12 +1646,6 @@ .openerp .oe_searchview_drawer form button { margin: 0 0 8px -3px; } -.openerp .oe_searchview_drawer .oe_searchview_custom dt { - width: 140px; -} -.openerp .oe_searchview_drawer .oe_searchview_custom dd { - margin-left: 145px; -} .openerp .oe_searchview_drawer .oe_searchview_custom form { display: none; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 9380af85057..18da5a7098c 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1277,186 +1277,6 @@ $sheet-padding: 16px background: #8786b7 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) @include radius(2px) - &.oe_searchview_open_drawer - .oe_searchview_drawer - display: block - - .oe_searchview_drawer - cursor: default - position: absolute - z-index: 2 - // detach drawer from field slightly - margin-top: 4px - top: 100% - right: -1px - background-color: white - min-width: 100% - display: none - border: 1px solid $tag-border - text-align: left - @include radius(4px) - @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) - > div - border-top: 1px solid #ccc - margin: 0 - padding: 8px - > div:first-child - border-top: none - margin: 0 - h3 - margin: 8px 4px 4px 0px - color: $section-title-color - font-size: 13px - h4, h4 * - margin: 0 0 0 2px - cursor: pointer - font-weight: normal - display: inline-block - &:hover - background-color: $hover-background - h4:before - content: "▸ " - color: #a3a3a3 - button - margin: 4px 0 - .button - border: none - background: transparent - padding: 0 2px - @include box-shadow(none) - @include radius(0) - .oe_searchview_section - display: table - width: 100% - > div - @include box-sizing(border) - display: table-cell - width: 50% - padding-left: 2px // Managed padding-left according bootstrap3 - ul - margin: 0 8px 8px - padding: 0 - list-style: none - li - list-style: none - padding: 2px 4px 2px 20px - line-height: 14px - color: inherit - cursor: pointer - position: relative - &.oe_selected:before - content: "W" - font-family: "entypoRegular" !important - font-size: 24px - font-weight: 300 !important - color: #a3a3a3 - position: absolute - left: 4px - top: -2px - // after oe_selected so background color is not overridden - &:hover - background-color: $hover-background - form - margin-left: 12px - p - margin: 4px 0 - line-height: 18px - button - margin: 0 0 8px -3px // Managed margin-left according bootstrap3 - .oe_searchview_custom - padding: 0 8px 8px 8px - div - padding: 0 - h4 - margin: 0 - form - display: none - li - cursor: pointer - position: relative - line-height: 14px - padding: 2px 4px 2px 20px - &:hover - background-color: $hover-background - button - position: absolute - top: 0 - right: 5px - //Customize for searchview label - label - font-weight: normal - //End of Customize - .oe_searchview_dashboard - form - display: none - margin-top: 2px - - .oe_searchview_advanced - form - display: none - margin-top: 8px - button.oe_add_condition:before - content: "Z" - font-family: "entypoRegular" !important - font-size: 24px - font-weight: 300 !important - margin-right: 4px - ul - list-style: none - padding: 0 - li - position: relative - list-style: none - margin: 0 - white-space: nowrap - &:first-child .searchview_extended_prop_or - visibility: hidden - margin-left: -14px - .searchview_extended_prop_or - opacity: 0.5 - margin-left: -14px //Customize 'or' in searchview - .oe_opened - h4:before - content: "▾ " - position: relative - top: -1px - form - display: block - - // delete buttons - .oe_searchview_custom_delete, .searchview_extended_delete_prop - display: inline-block - width: 12px - height: 12px - line-height: 12px - padding: 1px - color: #8786b7 - line-height: 8px - text-align: center - font-weight: bold - text-shadow: 0 1px 1px white - &:hover - text-decoration: none - color: white - background: #8786b7 - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) - @include radius(2px) - .oe_searchview_custom_delete - display: none - position: absolute - bottom: 1px - right: 4px - .oe_searchview_custom_private, .oe_searchview_custom_public - &:hover - .oe_searchview_custom_delete - display: inline-block - .oe_searchview_custom_public:after - content: "," - font-family: "entypoRegular" !important - font-size: 22px - font-weight: 300 !important - margin: 0 0 0 4px - padding: 0 .oe_searchview_drawer_container overflow: auto @@ -1480,7 +1300,7 @@ $sheet-padding: 16px .col-md-5 padding-left: 0 dl - margin-bottom: 10px + margin-bottom: 0 dt color: $section-title-color font-size: 13px @@ -1527,10 +1347,6 @@ $sheet-padding: 16px button margin: 0 0 8px -3px // Managed margin-left according bootstrap3 .oe_searchview_custom - dt - width: 140px - dd - margin-left: 145px form display: none li diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 2c7ef9948b6..b64b7f21080 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -843,19 +843,16 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ // build drawer var in_drawer = this.select_for_drawer(); - var self = this; - var second_col = $('
      '); + var $first_col = this.$(".col-md-7"), + $snd_col = this.$(".col-md-5"); - var insert_first_col = in_drawer[0].appendTo(this.$el).then(function () { - second_col.appendTo(self.$el); - }).then(function () { - return $.when.apply(null, - _(_.rest(in_drawer)).invoke('appendTo', second_col)); - }); - - return $.when.apply(null, _(this.inputs).invoke( + var add_custom_reports = in_drawer[0].appendTo($first_col), + add_filters = in_drawer[1].appendTo($first_col), + add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)), + defaults_fetched = $.when.apply(null, _(this.inputs).invoke( 'facet_for_defaults', this.searchview.defaults)); + return $.when(add_custom_reports, add_filters, add_rest, defaults_fetched); }, notify_searchview: function () { var defaults = arguments[1]; @@ -943,10 +940,11 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ }, add_common_inputs: function() { + // add custom filters to this.inputs + this.custom_filters = new instance.web.search.CustomReports(this); // add Filters to this.inputs, need view.controls filled (new instance.web.search.Filters(this)); - // add custom filters to this.inputs - this.custom_filters = new instance.web.search.CustomFilters(this); + (new instance.web.search.SaveFilter(this, this.custom_filters)); // add Advanced to this.inputs (new instance.web.search.Advanced(this)); }, @@ -1707,12 +1705,12 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ } }); -instance.web.search.CustomFilters = instance.web.search.Input.extend({ - template: 'SearchView.CustomFilters', +instance.web.search.CustomReports = instance.web.search.Input.extend({ + template: 'SearchView.CustomReports', _in_drawer: true, init: function () { this.is_ready = $.Deferred(); - this._super.apply(this, arguments); + this._super.apply(this,arguments); }, start: function () { var self = this; @@ -1727,13 +1725,6 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ self.clear_selection(); }) .on('reset', this.proxy('clear_selection')); - this.$el.on('submit', 'form', this.proxy('save_current')); - this.$el.on('click', 'input[type=checkbox]', function() { - $(this).siblings('input[type=checkbox]').prop('checked', false); - }); - this.$el.on('click', 'h4', function () { - self.$el.toggleClass('oe_opened'); - }); return this.model.call('get_filters', [this.view.model]) .then(this.proxy('set_filters')) .done(function () { self.is_ready.resolve(); }) @@ -1843,6 +1834,26 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ set_filters: function (filters) { _(filters).map(_.bind(this.append_filter, this)); }, +}); + +instance.web.search.SaveFilter = instance.web.search.Input.extend({ + template: 'SearchView.SaveFilter', + _in_drawer: true, + init: function (parent, custom_reports) { + this._super(parent); + this.custom_reports = custom_reports; + }, + start: function () { + var self = this; + this.model = new instance.web.Model('ir.filters'); + this.$el.on('submit', 'form', this.proxy('save_current')); + this.$el.on('click', 'input[type=checkbox]', function() { + $(this).siblings('input[type=checkbox]').prop('checked', false); + }); + this.$el.on('click', 'h4', function () { + self.$el.toggleClass('oe_opened'); + }); + }, save_current: function () { var self = this; var $name = this.$('input:first'); @@ -1879,14 +1890,16 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ // FIXME: current context? return self.model.call('create_or_replace', [filter]).done(function (id) { filter.id = id; - self.append_filter(filter); + if (self.custom_reports) { + self.custom_reports.append_filter(filter); + } self.$el .removeClass('oe_opened') .find('form')[0].reset(); }); }); return false; - } + }, }); instance.web.search.Filters = instance.web.search.Input.extend({ diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 93f6ee924f7..e9ade4d14a5 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1562,6 +1562,8 @@
      -
      +
      -
      + +
      -
      M Custom Filters
      +
      M Custom Reports
        +
        + +

        Save current filter

        From eb7f96efbbcedd0f840a7685599cf5344aff1e8b Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 21 May 2014 13:24:39 +0200 Subject: [PATCH 029/578] --addons-path not required anymore --- openerpcommand/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openerpcommand/common.py b/openerpcommand/common.py index 7572bb4efa6..48d1c16eae4 100644 --- a/openerpcommand/common.py +++ b/openerpcommand/common.py @@ -14,8 +14,7 @@ def add_addons_argument(parser): Add a common --addons argument to a parser. """ parser.add_argument('--addons', metavar='ADDONS', - **required_or_default('ADDONS', - 'colon-separated list of paths to addons')) + help='colon-separated list of paths to addons') def set_addons(args): """ Turn args.addons into a list instead of a column-separated strings. From 2e760407e4df1d0f239c1bc3ad3f2517ce5014fd Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Wed, 21 May 2014 13:42:17 +0200 Subject: [PATCH 030/578] [FIX] survey: enable survey access to portal users instead of 404 --- addons/survey/security/ir.model.access.csv | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/survey/security/ir.model.access.csv b/addons/survey/security/ir.model.access.csv index 89c001ce44e..e49c976ea1f 100644 --- a/addons/survey/security/ir.model.access.csv +++ b/addons/survey/security/ir.model.access.csv @@ -1,11 +1,11 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_survey_public,survey.survey public,model_survey_survey,base.group_public,1,0,0,0 -access_survey_page_public,survey.page public,model_survey_page,base.group_public,1,0,0,0 -access_survey_question_public,survey.question public,model_survey_question,base.group_public,1,0,0,0 -access_survey_label_public,survey.label public,model_survey_label,base.group_public,1,0,0,0 -access_survey_user_input_public,survey.user_input public,model_survey_user_input,base.group_public,1,1,1,0 -access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,base.group_public,1,1,1,0 -access_survey_stage_public,survey.stage public,model_survey_stage,base.group_public,1,0,0,0 +access_survey_public,survey.survey public,model_survey_survey,,1,0,0,0 +access_survey_page_public,survey.page public,model_survey_page,,1,0,0,0 +access_survey_question_public,survey.question public,model_survey_question,,1,0,0,0 +access_survey_label_public,survey.label public,model_survey_label,,1,0,0,0 +access_survey_user_input_public,survey.user_input public,model_survey_user_input,,1,1,1,0 +access_survey_user_input_line_public,survey.user_input_line public,model_survey_user_input_line,,1,1,1,0 +access_survey_stage_public,survey.stage public,model_survey_stage,,1,0,0,0 access_survey_user,survey.survey user,model_survey_survey,base.group_survey_user,1,0,0,0 access_survey_page_user,survey.page user,model_survey_page,base.group_survey_user,1,0,0,0 access_survey_question_user,survey.question user,model_survey_question,base.group_survey_user,1,0,0,0 From 1f97c40c9c109165caef58ee1a87f925b5c7d884 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 21 May 2014 14:05:18 +0200 Subject: [PATCH 031/578] [FIX] Report: get the wkhtmltopdf version in a cleaner way with a simple regex --- addons/report/models/report.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 32752fa6e33..6d118021775 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -25,6 +25,7 @@ from openerp.tools.translate import _ from openerp.addons.web.http import request from openerp.tools.safe_eval import safe_eval as eval +import re import time import base64 import logging @@ -52,16 +53,12 @@ except OSError: _logger.info('You need wkhtmltopdf to print a pdf version of the reports.') else: out, err = process.communicate() - - for line in out.splitlines(): - line = line.strip() - if line.startswith('wkhtmltopdf 0.'): - version = line.split(' ')[1] - if LooseVersion(version) < LooseVersion('0.12.0'): - _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0') - wkhtmltopdf_state = 'upgrade' - else: - wkhtmltopdf_state = 'ok' + version = re.search('([0-9.]+)', out).group(0) + if LooseVersion(version) < LooseVersion('0.12.0'): + _logger.info('Upgrade wkhtmltopdf to (at least) 0.12.0') + wkhtmltopdf_state = 'upgrade' + else: + wkhtmltopdf_state = 'ok' if config['workers'] == 1: _logger.info('You need to start OpenERP with at least two workers to print a pdf version of the reports.') From 36471c395596fb40727c7f5fa8e38431a24e6375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 21 May 2014 14:27:17 +0200 Subject: [PATCH 032/578] [FIX] correctly loads default filters (addon web) also, hide/show the custom reports list when it is empty/non empty --- addons/web/static/src/js/search.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index b64b7f21080..48a8ab05f4b 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -852,10 +852,10 @@ instance.web.SearchViewDrawer = instance.web.Widget.extend({ defaults_fetched = $.when.apply(null, _(this.inputs).invoke( 'facet_for_defaults', this.searchview.defaults)); - return $.when(add_custom_reports, add_filters, add_rest, defaults_fetched); + return $.when(defaults_fetched, add_custom_reports, add_filters, add_rest); }, notify_searchview: function () { - var defaults = arguments[1]; + var defaults = arguments[1][0]; this.ready.resolve.apply(null, defaults); }, /** @@ -1809,6 +1809,9 @@ instance.web.search.CustomReports = instance.web.search.Input.extend({ $filter.remove(); delete self.$filters[key]; delete self.filters[key]; + if (!self.filters.length) { + self.hide(); + } }); }) .appendTo($filter); @@ -1817,6 +1820,7 @@ instance.web.search.CustomReports = instance.web.search.Input.extend({ this.$filters[key].unbind('click').click(function () { self.toggle_filter(filter); }); + this.show(); }, toggle_filter: function (filter, preventSearch) { var current = this.view.query.find(function (facet) { @@ -1833,6 +1837,15 @@ instance.web.search.CustomReports = instance.web.search.Input.extend({ }, set_filters: function (filters) { _(filters).map(_.bind(this.append_filter, this)); + if (!filters.length) { + this.hide(); + } + }, + hide: function () { + this.$el.hide(); + }, + show: function () { + this.$el.show(); }, }); From abe5c803a0e36f18aec4cc56abc289f66329a749 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 21 May 2014 15:44:46 +0200 Subject: [PATCH 033/578] [IMP] account: support for partial reconciliation in aged partner balance When computing the aged partner balance, the partial reconciliation was not handled correctly. The reconciled amount should be removed from the original remaining amount instead of displaying two entries in the journal. eg: if invocie of 1000 in period 1 and payment of 300 in period 2, should only display +700 in period 1 instead of two entries --- .../report/account_aged_partner_balance.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 3b000e40592..b7c26120b48 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -159,7 +159,7 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header): dates_query += ' < %s)' args_list += (form[str(i)]['stop'],) args_list += (self.date_from,) - self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit) + self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit), l.reconcile_partial_id FROM account_move_line AS l, account_account, account_move am WHERE (l.account_id = account_account.id) AND (l.move_id=am.id) AND (am.state IN %s) @@ -171,12 +171,24 @@ class aged_trial_report(report_sxw.rml_parse, common_report_header): AND account_account.active AND ''' + dates_query + ''' AND (l.date <= %s) - GROUP BY l.partner_id''', args_list) - t = self.cr.fetchall() - d = {} - for i in t: - d[i[0]] = i[1] - history.append(d) + GROUP BY l.partner_id, l.reconcile_partial_id''', args_list) + partners_partial = self.cr.fetchall() + partners_amount = dict((i[0],0) for i in partners_partial) + for partner_info in partners_partial: + if partner_info[2]: + # in case of partial reconciliation, we want to keep the left amount in the oldest period + self.cr.execute('''SELECT MIN(COALESCE(date_maturity,date)) FROM account_move_line WHERE reconcile_partial_id = %s''', (partner_info[2],)) + date = self.cr.fetchall() + if date and args_list[-3] <= date[0][0] <= args_list[-2]: + # partial reconcilation + self.cr.execute('''SELECT SUM(l.debit-l.credit) + FROM account_move_line AS l + WHERE l.reconcile_partial_id = %s''', (partner_info[2],)) + unreconciled_amount = self.cr.fetchall() + partners_amount[partner_info[0]] += unreconciled_amount[0][0] + else: + partners_amount[partner_info[0]] += partner_info[1] + history.append(partners_amount) for partner in partners: values = {} From df35de22b80df3a1e57dd38e44dc69de82ef4e50 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 21 May 2014 15:53:16 +0200 Subject: [PATCH 034/578] Disable logging during commands discovery --- openerp/cli/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/cli/__init__.py b/openerp/cli/__init__.py index 27428369cbc..574898b1e24 100644 --- a/openerp/cli/__init__.py +++ b/openerp/cli/__init__.py @@ -47,6 +47,7 @@ def main(): # Subcommand discovery if len(args) and not args[0].startswith("-"): + logging.disable(logging.CRITICAL) for m in module.get_modules(): m = 'openerp.addons.' + m __import__(m) @@ -54,6 +55,7 @@ def main(): #except Exception, e: # raise # print e + logging.disable(logging.NOTSET) command = args[0] args = args[1:] From b7c6b1cbca9a3ae0043bed40018bb88a8ebbb184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 21 May 2014 16:27:17 +0200 Subject: [PATCH 035/578] [FIX] website_mail: message description should effectively be limited to the first 30 characters. --- addons/website_mail/models/mail_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py index 1e50611fa00..cff950f2962 100644 --- a/addons/website_mail/models/mail_message.py +++ b/addons/website_mail/models/mail_message.py @@ -34,7 +34,7 @@ class MailMessage(osv.Model): res[message.id] = message.subject else: plaintext_ct = html2plaintext(message.body) - res[message.id] = plaintext_ct + '%s' % (' [...]' if len(plaintext_ct) >= 20 else '') + res[message.id] = plaintext_ct[:30] + '%s' % (' [...]' if len(plaintext_ct) >= 30 else '') return res _columns = { From 52eb9cba89cc0d057f65b3f20e551ba6559226ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 21 May 2014 16:32:31 +0200 Subject: [PATCH 036/578] [IMP] website_mail_group: refactored layout of displayed mailing lists and their archive. Should be a bit more easy to use and understand. Todo: fix number of displayed thread / replies, and probably re-do the layout once somebody decides to change everything. --- addons/website_mail_group/controllers/main.py | 26 +- .../views/website_mail_group.xml | 376 +++++++++++------- 2 files changed, 261 insertions(+), 141 deletions(-) diff --git a/addons/website_mail_group/controllers/main.py b/addons/website_mail_group/controllers/main.py index 88eb1a7e9b7..4449a38f999 100644 --- a/addons/website_mail_group/controllers/main.py +++ b/addons/website_mail_group/controllers/main.py @@ -9,7 +9,8 @@ from openerp.addons.web.http import request class MailGroup(http.Controller): - _thread_per_page = 10 + _thread_per_page = 5 + _replies_per_page = 1 def _get_archives(self, group_id): MailMessage = request.registry['mail.message'] @@ -27,7 +28,7 @@ class MailGroup(http.Controller): def view(self, **post): cr, uid, context = request.cr, request.uid, request.context group_obj = request.registry.get('mail.group') - group_ids = group_obj.search(cr, uid, [], context=context) + group_ids = group_obj.search(cr, uid, [('alias_id', '!=', False), ('alias_id.alias_name', '!=', False)], context=context) values = {'groups': group_obj.browse(cr, uid, group_ids, context)} return request.website.render('website_mail_group.mail_groups', values) @@ -85,7 +86,28 @@ class MailGroup(http.Controller): 'message': message, 'group': group, 'mode': mode, + 'archives': self._get_archives(group.id), 'date_begin': date_begin, 'date_end': date_end, } return request.website.render('website_mail_group.group_message', values) + + @http.route( + '''/groups// - - - -