diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index e23246d7288..394bbe657d9 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -13,6 +13,7 @@ import os import re import simplejson import time +import urllib import urllib2 import zlib from xml.etree import ElementTree @@ -293,9 +294,9 @@ def manifest_list(req, extension, mods=None, db=None): if not req.debug: path = '/web/webclient/' + extension if mods is not None: - path += '?mods=' + mods + path += '?' + urllib.urlencode({'mods': mods}) elif db: - path += '?db=' + db + path += '?' + urllib.urlencode({'db': db}) return [path] files = manifest_glob(req, extension, addons=mods, db=db) i_am_diabetic = req.httprequest.environ["QUERY_STRING"].count("no_sugar") >= 1 or \ diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index 5692139f002..b6327bf4622 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -32,12 +32,12 @@ NOMODULE_TEMPLATE = Template(u""" -""") +""", default_filters=['h']) NOTFOUND = Template(u"""

Unable to find the module [${module}], please check that the module name is correct and the module is on OpenERP's path.

<< Back to tests -""") +""", default_filters=['h']) TESTING = Template(u""" <%def name="to_path(module, p)">/${module}/${p} @@ -51,9 +51,9 @@ TESTING = Template(u""" @@ -83,7 +83,7 @@ TESTING = Template(u""" % endif % endfor -""") +""", default_filters=['h']) class TestRunnerController(http.Controller): _cp_path = '/web/tests' diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6cfee50d6a4..be26b7edd70 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"); @@ -1415,7 +1415,13 @@ display: inline-block; overflow: hidden; } +.openerp .oe_view_manager { + display: table; + height: inherit; + width: 100%; +} .openerp .oe_view_manager .oe_view_manager_body { + display: table-row; height: inherit; } .openerp .oe_view_manager .oe_view_manager_view_kanban { @@ -3089,7 +3095,6 @@ } .kitten-mode-activated { - background-image: url(http://placekitten.com/g/1365/769); background-size: cover; background-attachment: fixed; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index b4665808caf..b308b825775 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1137,7 +1137,11 @@ $sheet-padding: 16px // }}} // ViewManager common {{{ .oe_view_manager + display: table + height: inherit + width: 100% .oe_view_manager_body + display: table-row height: inherit .oe_view_manager_view_kanban height: inherit @@ -2436,7 +2440,6 @@ $sheet-padding: 16px // }}} // Kitten Mode {{{ .kitten-mode-activated - background-image: url(http://placekitten.com/g/1365/769) background-size: cover background-attachment: fixed >* diff --git a/addons/web/static/src/img/back-enable.jpg b/addons/web/static/src/img/back-enable.jpg new file mode 100644 index 00000000000..79a7098f6da Binary files /dev/null and b/addons/web/static/src/img/back-enable.jpg differ diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 86d0a20ed20..6051dcdb633 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1100,6 +1100,9 @@ instance.web.UserMenu = instance.web.Widget.extend({ }; this.update_promise = this.update_promise.then(fct, fct); }, + on_menu_help: function() { + window.open('http://help.openerp.com', '_blank'); + }, on_menu_logout: function() { this.trigger('user_logout'); }, @@ -1213,6 +1216,7 @@ instance.web.WebClient = instance.web.Client.extend({ return $.when(this._super()).then(function() { if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { $("body").addClass("kitten-mode-activated"); + $("body").css("background-image", "url(" + instance.session.origin + "/web/static/src/img/back-enable.jpg" + ")"); if ($.blockUI) { $.blockUI.defaults.message = ''; } @@ -1396,8 +1400,9 @@ instance.web.WebClient = instance.web.Client.extend({ }, on_hashchange: function(event) { var self = this; - var state = event.getState(true); - if (!_.isEqual(this._current_state, state)) { + var stringstate = event.getState(false); + if (!_.isEqual(this._current_state, stringstate)) { + var state = event.getState(true); if(!state.action && state.menu_id) { self.menu.has_been_loaded.done(function() { self.menu.do_reload().done(function() { @@ -1409,13 +1414,13 @@ instance.web.WebClient = instance.web.Client.extend({ this.action_manager.do_load_state(state, !!this._current_state); } } - this._current_state = state; + this._current_state = stringstate; }, do_push_state: function(state) { this.set_title(state.title); delete state.title; var url = '#' + $.param(state); - this._current_state = _.clone(state); + this._current_state = $.deparam($.param(state), false); // stringify all values $.bbq.pushState(url); this.trigger('state_pushed', state); }, @@ -1425,9 +1430,10 @@ instance.web.WebClient = instance.web.Client.extend({ .then(function (result) { return self.action_mutex.exec(function() { if (options.needaction) { - result.context = new instance.web.CompoundContext( - result.context, - {search_default_message_unread: true}); + result.context = new instance.web.CompoundContext(result.context, { + search_default_message_unread: true, + search_disable_custom_filters: true, + }); } var completed = $.Deferred(); $.when(self.action_manager.do_action(result, { diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 12acf4039f4..32f1d31d795 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -690,7 +690,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return filter.user_id && filter.is_default; }); if (personal_filter) { - this.custom_filters.enable_filter(personal_filter, true); + this.custom_filters.toggle_filter(personal_filter, true); return; } @@ -698,7 +698,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.enable_filter(global_filter, true); + this.custom_filters.toggle_filter(global_filter, true); return; } } @@ -1541,6 +1541,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }) .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'); }); @@ -1591,6 +1594,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } }, + _id: filter['id'], is_custom_filter: true, values: [{label: filter.name, value: null}] }; @@ -1632,10 +1636,18 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } $filter.unbind('click').click(function () { - self.enable_filter(filter); + self.toggle_filter(filter); }); }, - enable_filter: function (filter, preventSearch) { + toggle_filter: function (filter, preventSearch) { + var current = this.view.query.find(function (facet) { + return facet.get('_id') === filter.id; + }); + if (current) { + this.view.query.remove(current); + this.$filters[this.key_for(filter)].removeClass('oe_selected'); + return; + } this.view.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); this.$filters[this.key_for(filter)].addClass('oe_selected'); @@ -1800,6 +1812,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ template: 'SearchView.extended_search.proposition', events: { 'change .searchview_extended_prop_field': 'changed', + 'change .searchview_extended_prop_op': 'operator_changed', 'click .searchview_extended_delete_prop': function (e) { e.stopPropagation(); this.getParent().remove_proposition(this); @@ -1831,6 +1844,17 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } }, + operator_changed: function (e) { + var $value = this.$('.searchview_extended_prop_value'); + switch ($(e.target).val()) { + case '∃': + case '∄': + $value.hide(); + break; + default: + $value.show(); + } + }, /** * Selects the provided field object * @@ -1859,7 +1883,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ .text(String(operator.text)) .appendTo(self.$('.searchview_extended_prop_op')); }); - var $value_loc = this.$('.searchview_extended_prop_value').empty(); + var $value_loc = this.$('.searchview_extended_prop_value').show().empty(); this.value.appendTo($value_loc); }, @@ -1867,19 +1891,12 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @ if ( this.attrs.selected == null) return null; var field = this.attrs.selected; - var op = this.$('.searchview_extended_prop_op')[0]; - var operator = op.options[op.selectedIndex]; + var op_select = this.$('.searchview_extended_prop_op')[0]; + var operator = op_select.options[op_select.selectedIndex]; + return { - label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), { - field: field.string, - // According to spec, HTMLOptionElement#label should return - // HTMLOptionElement#text when not defined/empty, but it does - // not in older Webkit (between Safari 5.1.5 and Chrome 17) and - // Gecko (pre Firefox 7) browsers, so we need a manual fallback - // for those - operator: operator.label || operator.text, - value: this.value}), - value: [field.name, operator.value, this.value.get_value()] + label: this.value.get_label(field, operator), + value: this.value.get_domain(field, operator), }; } }); @@ -1889,6 +1906,37 @@ instance.web.search.ExtendedSearchProposition.Field = instance.web.Widget.extend this._super(parent); this.field = field; }, + get_label: function (field, operator) { + var format; + switch (operator.value) { + case '∃': case '∄': format = _t('%(field)s %(operator)s'); break; + default: format = _t('%(field)s %(operator)s "%(value)s"'); break; + } + return this.format_label(format, field, operator); + }, + format_label: function (format, field, operator) { + return _.str.sprintf(format, { + field: field.string, + // According to spec, HTMLOptionElement#label should return + // HTMLOptionElement#text when not defined/empty, but it does + // not in older Webkit (between Safari 5.1.5 and Chrome 17) and + // Gecko (pre Firefox 7) browsers, so we need a manual fallback + // for those + operator: operator.label || operator.text, + value: this + }); + }, + get_domain: function (field, operator) { + switch (operator.value) { + case '∃': return this.make_domain(field.name, '!=', false); + case '∄': return this.make_domain(field.name, '=', false); + default: return this.make_domain( + field.name, operator.value, this.get_value()); + } + }, + make_domain: function (field, operator, value) { + return [field, operator, value]; + }, /** * Returns a human-readable version of the value, in case the "logical" * and the "semantic" values of a field differ (as for selection fields, @@ -1908,7 +1956,9 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende {value: "ilike", text: _lt("contains")}, {value: "not ilike", text: _lt("doesn't contain")}, {value: "=", text: _lt("is equal to")}, - {value: "!=", text: _lt("is not equal to")} + {value: "!=", text: _lt("is not equal to")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], get_value: function() { return this.$el.val(); @@ -1922,7 +1972,9 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], /** * Date widgets live in view_form which is not yet loaded when this is @@ -1956,7 +2008,9 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { return this.$el.val(); @@ -1981,7 +2035,9 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend {value: ">", text: _lt("greater than")}, {value: "<", text: _lt("less than")}, {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "<=", text: _lt("less or equal than")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { return this.$el.val(); @@ -1999,7 +2055,9 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex template: 'SearchView.extended_search.proposition.selection', operators: [ {value: "=", text: _lt("is")}, - {value: "!=", text: _lt("is not")} + {value: "!=", text: _lt("is not")}, + {value: "∃", text: _lt("is set")}, + {value: "∄", text: _lt("is not set")} ], toString: function () { var select = this.$el[0]; @@ -2016,7 +2074,10 @@ instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.Exte {value: "=", text: _lt("is true")}, {value: "!=", text: _lt("is false")} ], - toString: function () { return ''; }, + get_label: function (field, operator) { + return this.format_label( + _t('%(field)s %(operator)s'), field, operator); + }, get_value: function() { return true; } diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 4880b3882ff..a023dd54174 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -251,9 +251,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.dataset.ids.push(state.id); } this.dataset.select_id(state.id); - if (warm) { - this.do_show(); - } + this.do_show({ reload: warm }); } }, /** @@ -511,9 +509,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var on_change = widget.node.attrs.on_change; if (on_change) { var change_spec = self.parse_on_change(on_change, widget); - var id = [self.datarecord.id == null ? [] : [self.datarecord.id]]; + var ids = []; + if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) { + // In case of a o2m virtual id, we should pass an empty ids list + ids.push(self.datarecord.id); + } def = new instance.web.Model(self.dataset.model).call( - change_spec.method, id.concat(change_spec.args)); + change_spec.method, [ids].concat(change_spec.args)); } else { def = $.when({}); } @@ -2376,6 +2378,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({ type_of_date: "datetime", events: { 'change .oe_datepicker_master': 'change_datetime', + 'dragstart img.oe_datepicker_trigger': function () { return false; }, }, init: function(parent) { this._super(parent); @@ -2957,7 +2960,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc case $.ui.keyCode.DOWN: e.stopPropagation(); } - } + }, + 'dragstart .oe_m2o_drop_down_button img': function () { return false; }, + 'dragstart .oe_m2o_cm_button': function () { return false; } }, init: function(field_manager, node) { this._super(field_manager, node); @@ -4450,6 +4455,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ * options: * -readonly: only applicable when not in creation mode, default to false * - alternative_form_view + * - view_id * - write_function * - read_function * - create_function @@ -4516,7 +4522,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ _.extend(options, { $buttons: this.$buttonpane, }); - this.view_form = new instance.web.FormView(this, this.dataset, false, options); + this.view_form = new instance.web.FormView(this, this.dataset, this.options.view_id || false, options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -4545,6 +4551,7 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ }); var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close"); $cbutton.click(function() { + self.view_form.trigger('on_button_cancel'); self.check_exit(); }); self.view_form.do_show(); @@ -5216,6 +5223,8 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false; this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false; this.set({value: false}); + this.field_manager.on("view_content_has_changed", this, this.render_value); + this.selection_mutex = new $.Mutex(); }, start: function() { if (this.options.clickable) { @@ -5234,14 +5243,16 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ }, render_value: function() { var self = this; - self.get_selection().done(function() { - var content = QWeb.render("FieldStatus.content", {widget: self}); - self.$el.html(content); - var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}"); - var color = colors[self.get('value')]; - if (color) { - self.$("oe_active").css("color", color); - } + self.selection_mutex.exec(function() { + return self.get_selection().done(function() { + var content = QWeb.render("FieldStatus.content", {widget: self}); + self.$el.html(content); + var colors = JSON.parse((self.node.attrs || {}).statusbar_colors || "{}"); + var color = colors[self.get('value')]; + if (color) { + self.$("oe_active").css("color", color); + } + }); }); }, /** Get the selection and render it diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index fbbfabcacca..27e2c88daaf 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1186,7 +1186,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. } }); instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{ - passtrough_events: 'action deleted row_link', + passthrough_events: 'action deleted row_link', /** * Grouped display for the ListView. Handles basic DOM events and interacts * with the :js:class:`~DataGroup` bound to it. @@ -1406,7 +1406,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we // can have selections spanning multiple links var selection = self.get_selection(); $this.trigger(e, [selection.ids, selection.records]); - }).bind(this.passtrough_events, function (e) { + }).bind(this.passthrough_events, function (e) { // additional positional parameters are provided to trigger as an // Array, following the event type or event object, but are // provided to the .bind event handler as *args. diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index efc442de2a9..6be83e7af52 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -795,7 +795,7 @@ openerp.web.list_editable = function (instance) { }); instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{ - passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved", + passthrough_events: instance.web.ListView.Groups.prototype.passthrough_events + " edit saved", get_row_for: function (record) { return _(this.children).chain() .invoke('get_row_for', record) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 9fd49367171..31f3df4a746 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -190,6 +190,18 @@ instance.web.ActionManager = instance.web.Widget.extend({ }); state = _.extend(params || {}, state); } + if (this.inner_action.context) { + var active_id = this.inner_action.context.active_id; + if (active_id) { + state["active_id"] = active_id; + } + var active_ids = this.inner_action.context.active_ids; + if (active_ids && !(active_ids.length === 1 && active_ids[0] === active_id)) { + // We don't push active_ids if it's a single element array containing the active_id + // This makes the url shorter in most cases. + state["active_ids"] = this.inner_action.context.active_ids.join(','); + } + } } if(!this.dialog) { this.getParent().do_push_state(state); @@ -212,8 +224,22 @@ instance.web.ActionManager = instance.web.Widget.extend({ } else { var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action; if (run_action) { + var add_context = {}; + if (state.active_id) { + add_context.active_id = state.active_id; + } + if (state.active_ids) { + // The jQuery BBQ plugin does some parsing on values that are valid integers. + // It means that if there's only one item, it will do parseInt() on it, + // otherwise it will keep the comma seperated list as string. + add_context.active_ids = state.active_ids.toString().split(',').map(function(id) { + return parseInt(id, 10) || id; + }); + } else if (state.active_id) { + add_context.active_ids = [state.active_id]; + } this.null_action(); - action_loaded = this.do_action(state.action); + action_loaded = this.do_action(state.action, { additional_context: add_context }); $.when(action_loaded || null).done(function() { instance.webclient.menu.has_been_loaded.done(function() { if (self.inner_action && self.inner_action.id) { @@ -249,12 +275,25 @@ instance.web.ActionManager = instance.web.Widget.extend({ } }); }, + /** + * Execute an OpenERP action + * + * @param {Number|String|Object} Can be either an action id, a client action or an action descriptor. + * @param {Object} [options] + * @param {Boolean} [options.clear_breadcrumbs=false] Clear the breadcrumbs history list + * @param {Function} [options.on_reverse_breadcrumb] Callback to be executed whenever an anterior breadcrumb item is clicked on. + * @param {Function} [options.on_close] Callback to be executed when the dialog is closed (only relevant for target=new actions) + * @param {Function} [options.action_menu_id] Manually set the menu id on the fly. + * @param {Object} [options.additional_context] Additional context to be merged with the action's context. + * @return {jQuery.Deferred} Action loaded + */ do_action: function(action, options) { options = _.defaults(options || {}, { clear_breadcrumbs: false, on_reverse_breadcrumb: function() {}, on_close: function() {}, action_menu_id: null, + additional_context: {}, }); if (action === false) { action = { type: 'ir.actions.act_window_close' }; @@ -269,9 +308,13 @@ instance.web.ActionManager = instance.web.Widget.extend({ } // Ensure context & domain are evaluated and can be manipulated/used - if (action.context) { - action.context = instance.web.pyeval.eval( - 'context', action.context); + var ncontext = new instance.web.CompoundContext(options.additional_context, action.context || {}); + action.context = instance.web.pyeval.eval('context', ncontext); + if (action.context.active_id || action.context.active_ids) { + // Here we assume that when an `active_id` or `active_ids` is used + // in the context, we are in a `related` action, so we disable the + // searchview's default custom filters. + action.context.search_disable_custom_filters = true; } if (action.domain) { action.domain = instance.web.pyeval.eval( @@ -539,7 +582,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; if (controller) { - var container = self.$el.find(".oe_view_manager_view_" + view_name + ":first"); + var container = self.$el.find("> .oe_view_manager_body > .oe_view_manager_view_" + view_name); if (view_name === view_type) { container.show(); controller.do_show(view_options || {}); @@ -582,7 +625,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ controller.on('switch_mode', self, this.switch_mode); controller.on('previous_view', self, this.prev_view); - var container = this.$el.find(".oe_view_manager_view_" + view_type); + var container = this.$el.find("> .oe_view_manager_body > .oe_view_manager_view_" + view_type); var view_promise = controller.appendTo(container); this.views[view_type].controller = controller; this.views[view_type].deferred.resolve(view_type); @@ -597,6 +640,12 @@ instance.web.ViewManager = instance.web.Widget.extend({ self.trigger("controller_inited",view_type,controller); }); }, + /** + * @returns {Number|Boolean} the view id of the given type, false if not found + */ + get_view_id: function(view_type) { + return this.views[view_type] && this.views[view_type].view_id || false; + }, set_title: function(title) { this.$el.find('.oe_view_title_text:first').text(title); }, @@ -953,10 +1002,11 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ }); }, do_create_view: function(view_type) { - var r = this._super.apply(this, arguments); - var view = this.views[view_type].controller; - view.set({ 'title': this.action.name }); - return r; + var self = this; + return this._super.apply(this, arguments).then(function() { + var view = self.views[view_type].controller; + view.set({ 'title': self.action.name }); + }); }, get_action_manager: function() { var cur = this; @@ -1270,11 +1320,6 @@ instance.web.View = instance.web.Widget.extend({ active_ids: [record_id], active_model: dataset.model }); - if (("" + action.context).match(/\bactive_id\b/)) { - // Special case: when the context is evaluted using - // the active_id, we want to disable the custom filters. - ncontext.add({ search_disable_custom_filters: true }); - } } ncontext.add(action.context || {}); action.context = ncontext; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 7604814fe1d..d56e0906270 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -403,8 +403,9 @@ diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 5f177c0a363..292954b1992 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1038,6 +1038,44 @@ openerp.testing.section('saved_filters', { "should not be checked anymore"); }); }); + test('toggling', {asserts: 2}, function (instance, $fix, mock) { + var view = makeSearchView(instance); + mock('ir.filters:get_filters', function () { + return [{name: 'filter name', user_id: 42, id: 1}]; + }); + + return view.appendTo($fix) + .done(function () { + var $row = $fix.find('.oe_searchview_custom li:first').click(); + equal(view.query.length, 1, "should have one facet"); + $row.click(); + equal(view.query.length, 0, "should have removed facet"); + }); + }); + test('replacement', {asserts: 4}, function (instance, $fix, mock) { + var view = makeSearchView(instance); + mock('ir.filters:get_filters', function () { + return [ + {name: 'f', user_id: 42, id: 1, context: {'private': 1}}, + {name: 'f', user_id: false, id: 2, context: {'private': 0}} + ]; + }); + return view.appendTo($fix) + .done(function () { + $fix.find('.oe_searchview_custom li:eq(0)').click(); + equal(view.query.length, 1, "should have one facet"); + deepEqual( + view.query.at(0).get('field').get_context(), + {'private': 1}, + "should have selected first filter"); + $fix.find('.oe_searchview_custom li:eq(1)').click(); + equal(view.query.length, 1, "should have one facet"); + deepEqual( + view.query.at(0).get('field').get_context(), + {'private': 0}, + "should have selected second filter"); + }); + }); }); openerp.testing.section('advanced', { dependencies: ['web.search'], diff --git a/addons/web_calendar/static/src/css/web_calendar.css b/addons/web_calendar/static/src/css/web_calendar.css index d40a7a2940a..214c777dbdc 100644 --- a/addons/web_calendar/static/src/css/web_calendar.css +++ b/addons/web_calendar/static/src/css/web_calendar.css @@ -18,7 +18,7 @@ .openerp .oe_calendar .dhx_cal_select_menu .dhx_menu_icon.icon_edit { display: none; } -.openerp .oe_calendar .dhx_cal_navline { +.openerp .oe_calendar .dhx_cal_navline, .openerp .oe_calendar .dhx_cal_header { z-index: auto; } .openerp .oe_calendar.oe_cal_month .dhx_cal_data table tr td:last-child div.dhx_month_body { diff --git a/addons/web_calendar/static/src/css/web_calendar.sass b/addons/web_calendar/static/src/css/web_calendar.sass index f6b6a1a5eeb..e4986981609 100644 --- a/addons/web_calendar/static/src/css/web_calendar.sass +++ b/addons/web_calendar/static/src/css/web_calendar.sass @@ -20,7 +20,7 @@ // Dhtmlx Scheduler css overrides .dhx_cal_select_menu .dhx_menu_icon.icon_edit display: none - .dhx_cal_navline + .dhx_cal_navline, .dhx_cal_header // dhtmlx sets the index to 3 (in glossy theme) // I didn't found the reason yet but it overlaps the // dropdown menus and I want to avoid a z-index war. diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index 9cac4db09d5..3c9a8a6fa9d 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -414,6 +414,20 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ self.slow_create(event_id, event_obj); }); }, + get_form_popup_infos: function() { + var parent = this.getParent(); + var infos = { + view_id: false, + title: this.name, + }; + if (parent instanceof instance.web.ViewManager) { + infos.view_id = parent.get_view_id('form'); + if (parent instanceof instance.web.ViewManagerAction && parent.action && parent.action.name) { + infos.title = parent.action.name; + } + } + return infos; + }, slow_create: function(event_id, event_obj) { var self = this; if (this.current_mode() === 'month') { @@ -431,9 +445,11 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ }); var something_saved = false; var pop = new instance.web.form.FormOpenPopup(this); + var pop_infos = this.get_form_popup_infos(); pop.show_element(this.dataset.model, null, this.dataset.get_context(defaults), { - title: _t("Create: ") + ' ' + this.name, + title: _.str.sprintf(_t("Create: %s"), pop_infos.title), disable_multiple_selection: true, + view_id: pop_infos.view_id, }); pop.on('closed', self, function() { if (!something_saved) { @@ -465,9 +481,11 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ }); } else { var pop = new instance.web.form.FormOpenPopup(this); + var pop_infos = this.get_form_popup_infos(); var id_from_dataset = this.dataset.ids[index]; // dhtmlx scheduler loses id's type pop.show_element(this.dataset.model, id_from_dataset, this.dataset.get_context(), { - title: _t("Edit: ") + this.name + title: _.str.sprintf(_t("Edit: %s"), pop_infos.title), + view_id: pop_infos.view_id, }); pop.on('write_completed', self, function(){ self.reload_event(id_from_dataset); diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 3083a9e30a5..eaba760292c 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -92,12 +92,21 @@ overflow: hidden; text-overflow: ellipsis; } -.openerp .oe_kanban_view .oe_kanban_group_title .oe_kanban_group_length { +.openerp .oe_kanban_view .oe_kanban_group_length { + text-align: center; + display: none; +} +.openerp .oe_kanban_view .oe_kanban_group_length .oe_tag { + position: relative; + top: +8px; + font-weight: bold; +} +.openerp .oe_kanban_view .oe_fold_column .oe_kanban_group_length { position: absolute; top: -1px; right: -14px; - text-align: center; float: right; + display: block; } .openerp .oe_kanban_view .oe_kanban_header:hover .oe_kanban_group_length { display: none; @@ -141,7 +150,8 @@ .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title, .openerp .oe_kanban_view .oe_kanban_group_folded.oe_kanban_column *, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_aggregates, .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_add { display: none; } -.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical { +.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical, +.openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_length { display: block; } .openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban { @@ -163,7 +173,7 @@ display: none; position: relative; opacity: 0.75; - top: 20px; + top: 26px; } .openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle { margin-left: 4px; @@ -175,6 +185,7 @@ } .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle { top: -2px; + height: 14px; } .openerp .oe_kanban_view .oe_kanban_card, .openerp .oe_kanban_view .oe_dropdown_toggle { cursor: pointer; diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 6802a257556..ec964c8819f 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -54,6 +54,13 @@ &.oe_kanban_grouped .oe_kanban_dummy_cell background: url(/web/static/src/img/form_sheetbg.png) width: 100% + .oe_kanban_group_length + text-align: center + display: none + .oe_tag + position: relative + top: +8px + font-weight: bold .ui-sortable-placeholder border: 1px solid rgba(0,0,0,0.1) visibility: visible !important @@ -118,11 +125,12 @@ white-space: nowrap overflow: hidden text-overflow: ellipsis + .oe_fold_column .oe_kanban_group_length position: absolute top: -1px right: -14px - text-align: center + display: block float: right &.oe_kanban_grouped .oe_kanban_column, .oe_kanban_group_header @@ -159,7 +167,7 @@ .oe_kanban_group_folded .oe_kanban_group_title, &.oe_kanban_column *, .oe_kanban_aggregates, .oe_kanban_add display: none - .oe_kanban_group_title_vertical + .oe_kanban_group_title_vertical, .oe_kanban_group_length display: block .oe_dropdown_kanban left: -5px @@ -178,7 +186,7 @@ display: none position: relative opacity: 0.75 - top: 20px + top: 26px // }}} // KanbanQuickCreate {{{ .oe_kanban_add, .oe_kanban_header .oe_dropdown_toggle @@ -189,6 +197,7 @@ top: -8px .oe_kanban_header .oe_dropdown_toggle top: -2px + height: 14px; .oe_kanban_card, .oe_dropdown_toggle cursor: pointer display: inline-block diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 7d6f88b6def..fc504ae337a 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -134,7 +134,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ switch (node.tag) { case 'field': if (this.fields_view.fields[node.attrs.name].type === 'many2many') { - this.many2manys.push(node.attrs.name); + if (_.indexOf(this.many2manys, node.attrs.name) < 0) { + this.many2manys.push(node.attrs.name); + } node.tag = 'div'; node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags'; } else { @@ -227,14 +229,15 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.search_domain = domain; this.search_context = context; this.search_group_by = group_by; - $.when(this.has_been_loaded).done(function() { + return $.when(this.has_been_loaded).then(function() { self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by; self.group_by_field = self.fields_view.fields[self.group_by] || {}; self.grouped_by_m2o = (self.group_by_field.type === 'many2one'); self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o); self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o); - var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(self.group_by); - $.when(grouping).done(function(groups) { + var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined; + var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(grouping_fields); + return $.when(grouping).done(function(groups) { if (groups) { self.do_process_groups(groups); } else { @@ -635,6 +638,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ self.view.dataset.ids = ids.concat(self.dataset.ids); self.do_add_records(records); self.compute_cards_auto_height(); + self.view.postprocess_m2m_tags(); return records; }); }, diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index 67ee563c14d..f943541d007 100644 --- a/addons/web_kanban/static/src/xml/web_kanban.xml +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -34,16 +34,21 @@
]
-
- í - +
+
+ + 99+ +
+ í + +
@@ -57,7 +62,9 @@
- + + +