summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2013-03-13 10:49:09 +0100
committerXavier Morel <xmo@openerp.com>2013-03-13 10:49:09 +0100
commit94c2c42be6fc0bdf55421b3fe4c1d4c7db1f4bed (patch)
tree395948faff8d6874a5094dc632b238a8eff32b4e /addons/web/static/src
parenta67b8721848083a9336317139e508c338d925f73 (diff)
parentf79f6a5a25a0708e662f2deed4a54c704c6872b5 (diff)
[MERGE] from 7.0
bzr revid: xmo@openerp.com-20130313094909-u21ee88l2lak9p2x
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/css/base.sass13
-rw-r--r--addons/web/static/src/js/chrome.js97
-rw-r--r--addons/web/static/src/js/corelib.js18
-rw-r--r--addons/web/static/src/js/coresetup.js4
-rw-r--r--addons/web/static/src/js/data.js47
-rw-r--r--addons/web/static/src/js/search.js189
-rw-r--r--addons/web/static/src/js/view_form.js170
-rw-r--r--addons/web/static/src/js/view_list.js2
-rw-r--r--addons/web/static/src/js/view_list_editable.js3
-rw-r--r--addons/web/static/src/js/views.js21
-rw-r--r--addons/web/static/src/xml/base.xml62
11 files changed, 411 insertions, 215 deletions
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 550a746b7ad..8544cf0dd3c 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -1436,7 +1436,7 @@ $sheet-padding: 16px
.oe_searchview_drawer
position: absolute
- z-index: 100
+ z-index: 2
// detach drawer from field slightly
margin-top: 4px
top: 100%
@@ -1832,6 +1832,12 @@ $sheet-padding: 16px
.oe_form
.oe_form_field_text
width: 100%
+ .oe_form_text_content
+ text-overflow: ellipsis
+ display: inline-block
+ white-space: pre-wrap
+ overflow-x: hidden
+ width: 100%
.oe_form_field_char input,
.oe_form_field_url input,
.oe_form_field_email input,
@@ -2502,6 +2508,9 @@ div.ui-widget-overlay
// Internet Explorer 9+ specifics {{{
.openerp_ie
+ .placeholder
+ color: $tag-border !important
+ font-style: italic !important
.oe_form_field_boolean input
background: #fff
.db_option_table .oe_form_field_selection
@@ -2517,6 +2526,8 @@ div.ui-widget-overlay
button.oe_highlight
padding-top: 0
padding-bottom: 0
+ .oe_view_manager_view_kanban
+ display: table-cell
.oe_view_manager_buttons
button.oe_write_full
padding-top: 0
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index eaa4bcbfe1c..c3f9f6af357 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -530,7 +530,16 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
'login': 'admin',
'password': form_obj['create_admin_pwd'],
'login_successful': function() {
- self.do_action("reload");
+ var action = {
+ type: "ir.actions.client",
+ tag: 'reload',
+ params: {
+ url_search : {
+ db: form_obj['db_name'],
+ },
+ }
+ };
+ self.do_action(action);
},
},
_push_me: false,
@@ -641,6 +650,11 @@ instance.web.client_actions.add("database_manager", "instance.web.DatabaseManage
instance.web.Login = instance.web.Widget.extend({
template: "Login",
remember_credentials: true,
+ events: {
+ 'change input[name=db],select[name=db]': function(ev) {
+ this.set('database_selector', $(ev.currentTarget).val());
+ },
+ },
init: function(parent, action) {
this._super(parent);
@@ -652,18 +666,18 @@ instance.web.Login = instance.web.Widget.extend({
if (_.isEmpty(this.params)) {
this.params = $.bbq.getState(true);
}
+ if (action && action.params && action.params.db) {
+ this.params.db = action.params.db;
+ } else if ($.deparam.querystring().db) {
+ this.params.db = $.deparam.querystring().db;
+ }
+ if (this.params.db) {
+ this.selected_db = this.params.db;
+ }
if (this.params.login_successful) {
this.on('login_successful', this, this.params.login_successful);
}
-
- if (this.has_local_storage && this.remember_credentials) {
- this.selected_db = localStorage.getItem('last_db_login_success');
- this.selected_login = localStorage.getItem('last_login_login_success');
- if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) {
- this.selected_password = localStorage.getItem('last_password_login_success');
- }
- }
},
start: function() {
var self = this;
@@ -671,10 +685,10 @@ instance.web.Login = instance.web.Widget.extend({
self.$el.find('.oe_login_manage_db').click(function() {
self.do_action("database_manager");
});
+ self.on('change:database_selector', this, function() {
+ this.database_selected(this.get('database_selector'));
+ });
var d = $.when();
- if ($.deparam.querystring().db) {
- self.params.db = $.deparam.querystring().db;
- }
if ($.param.fragment().token) {
self.params.token = $.param.fragment().token;
}
@@ -682,16 +696,44 @@ instance.web.Login = instance.web.Widget.extend({
if (self.params.db && self.params.login && self.params.password) {
d = self.do_login(self.params.db, self.params.login, self.params.password);
} else {
- if (self.params.db) {
- self.on_db_loaded([self.params.db])
- } else {
- d = self.rpc("/web/database/get_list", {}).done(self.on_db_loaded).fail(self.on_db_failed);
- }
+ d = self.rpc("/web/database/get_list", {})
+ .done(self.on_db_loaded)
+ .fail(self.on_db_failed)
+ .always(function() {
+ if (self.selected_db && self.has_local_storage && self.remember_credentials) {
+ self.$("[name=login]").val(localStorage.getItem(self.selected_db + '|last_login') || '');
+ if (self.session.debug) {
+ self.$("[name=password]").val(localStorage.getItem(self.selected_db + '|last_password') || '');
+ }
+ }
+ });
}
return d;
},
+ remember_last_used_database: function(db) {
+ // This cookie will be used server side in order to avoid db reloading on first visit
+ var ttl = 24 * 60 * 60 * 365;
+ document.cookie = [
+ 'last_used_database=' + db,
+ 'path=/',
+ 'max-age=' + ttl,
+ 'expires=' + new Date(new Date().getTime() + ttl * 1000).toGMTString()
+ ].join(';');
+ },
+ database_selected: function(db) {
+ var params = $.deparam.querystring();
+ params.db = db;
+ this.remember_last_used_database(db);
+ this.$('.oe_login_dbpane').empty().text(_t('Loading...'));
+ this.$('[name=login], [name=password]').prop('readonly', true);
+ instance.web.redirect('/?' + $.param(params));
+ },
on_db_loaded: function (result) {
+ var self = this;
this.db_list = result;
+ if (!this.selected_db) {
+ this.selected_db = result[0];
+ }
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
if(this.db_list.length === 0) {
this.do_action("database_manager");
@@ -732,17 +774,11 @@ instance.web.Login = instance.web.Widget.extend({
self.hide_error();
self.$(".oe_login_pane").fadeOut("slow");
return this.session.session_authenticate(db, login, password).then(function() {
- if (self.has_local_storage) {
- if(self.remember_credentials) {
- localStorage.setItem('last_db_login_success', db);
- localStorage.setItem('last_login_login_success', login);
- if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) {
- localStorage.setItem('last_password_login_success', password);
- }
- } else {
- localStorage.setItem('last_db_login_success', '');
- localStorage.setItem('last_login_login_success', '');
- localStorage.setItem('last_password_login_success', '');
+ self.remember_last_used_database(db);
+ if (self.has_local_storage && self.remember_credentials) {
+ localStorage.setItem(db + '|last_login', login);
+ if (self.session.debug) {
+ localStorage.setItem(db + '|last_password', password);
}
}
self.trigger('login_successful');
@@ -800,6 +836,9 @@ instance.web.Reload = function(parent, action) {
var sobj = $.deparam(l.search.substr(1));
sobj.ts = new Date().getTime();
+ if (params.url_search) {
+ sobj = _.extend(sobj, params.url_search);
+ }
var search = '?' + $.param(sobj);
var hash = l.hash;
@@ -1303,7 +1342,7 @@ instance.web.WebClient = instance.web.Client.extend({
},
logo_edit: function(ev) {
var self = this;
- new self.alive(instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
+ self.alive(new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
result.res_id = res['company_id'][0];
result.target = "new";
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 90a0eb0a5a0..c4bb81117f8 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -232,13 +232,17 @@ instance.web.ParentedMixin = {
Utility method to only execute asynchronous actions if the current
object has not been destroyed.
- @param {Promise} promise The promise representing the asynchronous action.
- @param {bool} reject Defaults to false. If true, the returned promise will be
- rejected with no arguments if the current object is destroyed. If false,
- the returned promise will never be resolved nor rejected.
- @returns {Promise} A promise that will mirror the given promise if everything goes
- fine but will either be rejected with no arguments or never resolved if the
- current object is destroyed.
+ @param {$.Deferred} promise The promise representing the asynchronous
+ action.
+ @param {bool} [reject=false] If true, the returned promise will be
+ rejected with no arguments if the current
+ object is destroyed. If false, the
+ returned promise will never be resolved
+ or rejected.
+ @returns {$.Deferred} A promise that will mirror the given promise if
+ everything goes fine but will either be rejected
+ with no arguments or never resolved if the
+ current object is destroyed.
*/
alive: function(promise, reject) {
var def = $.Deferred();
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
index 43d292b66db..dedb4a49710 100644
--- a/addons/web/static/src/js/coresetup.js
+++ b/addons/web/static/src/js/coresetup.js
@@ -89,6 +89,10 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
});
},
session_is_valid: function() {
+ var db = $.deparam.querystring().db;
+ if (db && this.db !== db) {
+ return false;
+ }
return !!this.uid;
},
/**
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index bbff633c687..5f56c4bc5f9 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -481,9 +481,12 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
* Creates a new record in db
*
* @param {Object} data field values to set on the new record
+ * @param {Object} options Dictionary that can contain the following keys:
+ * - readonly_fields: Values from readonly fields that were updated by
+ * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
* @returns {$.Deferred}
*/
- create: function(data) {
+ create: function(data, options) {
return this._model.call('create', [data], {context: this.get_context()});
},
/**
@@ -491,8 +494,10 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
*
* @param {Number|String} id identifier for the record to alter
* @param {Object} data field values to write into the record
- * @param {Function} callback function called with operation result
- * @param {Function} error_callback function called in case of write error
+ * @param {Object} options Dictionary that can contain the following keys:
+ * - context: The context to use in the server-side call.
+ * - readonly_fields: Values from readonly fields that were updated by
+ * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
* @returns {$.Deferred}
*/
write: function (id, data, options) {
@@ -732,10 +737,13 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
self.last_default_get = res;
});
},
- create: function(data) {
- var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
- defaults: this.last_default_get};
- this.to_create.push(_.extend(_.clone(cached), {values: _.clone(cached.values)}));
+ create: function(data, options) {
+ var cached = {
+ id:_.uniqueId(this.virtual_id_prefix),
+ values: _.extend({}, data, (options || {}).readonly_fields || {}),
+ defaults: this.last_default_get
+ };
+ this.to_create.push(_.extend(_.clone(cached), {values: _.clone(data)}));
this.cache.push(cached);
return $.Deferred().resolve(cached.id).promise();
},
@@ -762,7 +770,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
cached = {id: id, values: {}};
this.cache.push(cached);
}
- $.extend(cached.values, record.values);
+ $.extend(cached.values, _.extend({}, record.values, (options || {}).readonly_fields || {}));
if (dirty)
this.trigger("dataset_changed", id, data, options);
return $.Deferred().resolve(true).promise();
@@ -860,8 +868,16 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
}
return completion.promise();
},
- call_button: function (method, args) {
- var id = args[0][0], index;
+ /**
+ * Invalidates caching of a record in the dataset to ensure the next read
+ * of that record will hit the server.
+ *
+ * Of use when an action is going to remote-alter a record which will then
+ * need to be reloaded, e.g. action button.
+ *
+ * @param {Object} id record to remove from the BDS's cache
+ */
+ evict_record: function (id) {
for(var i=0, len=this.cache.length; i<len; ++i) {
var record = this.cache[i];
// if record we call the button upon is in the cache
@@ -871,8 +887,15 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
break;
}
}
+ },
+ call_button: function (method, args) {
+ this.evict_record(args[0][0]);
return this._super(method, args);
},
+ exec_workflow: function (id, signal) {
+ this.evict_record(id);
+ return this._super(id, signal);
+ },
alter_ids: function(n_ids) {
this._super(n_ids);
this.trigger("dataset_changed", n_ids);
@@ -903,9 +926,9 @@ instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({
return this._super.apply(this, arguments);
}
},
- create: function(data) {
+ create: function(data, options) {
if (this.create_function) {
- return this.create_function(data, this._super);
+ return this.create_function(data, options, this._super);
} else {
return this._super.apply(this, arguments);
}
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index edbec1626b0..33e1fba3813 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -324,7 +324,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
e.preventDefault();
break;
}
- }
+ },
+ 'autocompleteopen': function () {
+ this.$el.autocomplete('widget').css('z-index', 3);
+ },
},
/**
* @constructs instance.web.SearchView
@@ -352,7 +355,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
this.has_defaults = !_.isEmpty(this.defaults);
this.inputs = [];
- this.controls = {};
+ this.controls = [];
this.headless = this.options.hidden && !this.has_defaults;
@@ -492,6 +495,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
*/
complete_global_search: function (req, resp) {
$.when.apply(null, _(this.inputs).chain()
+ .filter(function (input) { return input.visible(); })
.invoke('complete', req.term)
.value()).then(function () {
resp(_(_(arguments).compact()).flatten(true));
@@ -580,18 +584,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
*
* @param {Array} items a list of nodes to convert to widgets
* @param {Object} fields a mapping of field names to (ORM) field attributes
- * @param {String} [group_name] name of the group to put the new controls in
+ * @param {Object} [group] group to put the new controls in
*/
- make_widgets: function (items, fields, group_name) {
- group_name = group_name || null;
- if (!(group_name in this.controls)) {
- this.controls[group_name] = [];
+ make_widgets: function (items, fields, group) {
+ if (!group) {
+ group = new instance.web.search.Group(
+ this, 'q', {attrs: {string: _t("Filters")}});
}
- var self = this, group = this.controls[group_name];
+ var self = this;
var filters = [];
_.each(items, function (item) {
if (filters.length && item.tag !== 'filter') {
- group.push(new instance.web.search.FilterGroup(filters, this));
+ group.push(new instance.web.search.FilterGroup(filters, group));
filters = [];
}
@@ -599,15 +603,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
case 'separator': case 'newline':
break;
case 'filter':
- filters.push(new instance.web.search.Filter(item, this));
+ filters.push(new instance.web.search.Filter(item, group));
break;
case 'group':
- self.make_widgets(item.children, fields, item.attrs.string);
+ self.make_widgets(item.children, fields,
+ new instance.web.search.Group(group, 'w', item));
break;
case 'field':
- group.push(this.make_field(item, fields[item['attrs'].name]));
+ var field = this.make_field(
+ item, fields[item['attrs'].name], group);
+ group.push(field);
// filters
- self.make_widgets(item.children, fields, group_name);
+ self.make_widgets(item.children, fields, group);
break;
}
}, this);
@@ -622,12 +629,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
*
* @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) {
+ make_field: function (item, field, parent) {
var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
if(obj) {
- return new (obj) (item, field, this);
+ return new (obj) (item, field, parent || this);
} else {
console.group('Unknown field type ' + field.type);
console.error('View node', item);
@@ -862,13 +870,18 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web
* @constructs instance.web.search.Widget
* @extends instance.web.Widget
*
- * @param view the ancestor view of this widget
+ * @param parent parent of this widget
*/
- init: function (view) {
- this._super(view);
- this.view = view;
+ init: function (parent) {
+ this._super(parent);
+ var ancestor = parent;
+ do {
+ this.view = ancestor;
+ } while (!(ancestor instanceof instance.web.SearchView)
+ && (ancestor = (ancestor.getParent && ancestor.getParent())));
}
});
+
instance.web.search.add_expand_listener = function($root) {
$root.find('a.searchview_group_string').click(function (e) {
$root.toggleClass('folded expanded');
@@ -877,13 +890,24 @@ instance.web.search.add_expand_listener = function($root) {
});
};
instance.web.search.Group = instance.web.search.Widget.extend({
- template: 'SearchView.group',
- init: function (view_section, view, fields) {
- this._super(view);
- this.attrs = view_section.attrs;
- this.lines = view.make_widgets(
- view_section.children, fields);
- }
+ init: function (parent, icon, node) {
+ this._super(parent);
+ var attrs = node.attrs;
+ this.modifiers = attrs.modifiers =
+ attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
+ this.attrs = attrs;
+ this.icon = icon;
+ this.name = attrs.string;
+ this.children = [];
+
+ this.view.controls.push(this);
+ },
+ push: function (input) {
+ this.children.push(input);
+ },
+ visible: function () {
+ return !this.modifiers.invisible;
+ },
});
instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{
@@ -892,12 +916,12 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
* @constructs instance.web.search.Input
* @extends instance.web.search.Widget
*
- * @param view
+ * @param parent
*/
- init: function (view) {
- this._super(view);
+ init: function (parent) {
+ this._super(parent);
+ this.load_attrs({});
this.view.inputs.push(this);
- this.style = undefined;
},
/**
* Fetch auto-completion values for the widget.
@@ -945,15 +969,30 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
"get_domain not implemented for widget " + this.attrs.type);
},
load_attrs: function (attrs) {
- if (attrs.modifiers) {
- attrs.modifiers = JSON.parse(attrs.modifiers);
- attrs.invisible = attrs.modifiers.invisible || false;
- if (attrs.invisible) {
- this.style = 'display: none;'
+ attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
+ this.attrs = attrs;
+ },
+ /**
+ * Returns whether the input is "visible". The default behavior is to
+ * query the ``modifiers.invisible`` flag on the input's description or
+ * view node.
+ *
+ * @returns {Boolean}
+ */
+ visible: function () {
+ if (this.attrs.modifiers.invisible) {
+ return false;
+ }
+ var parent = this;
+ while ((parent = parent.getParent()) &&
+ ( (parent instanceof instance.web.search.Group)
+ || (parent instanceof instance.web.search.Input))) {
+ if (!parent.visible()) {
+ return false;
}
}
- this.attrs = attrs;
- }
+ return true;
+ },
});
instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
template: 'SearchView.filters',
@@ -967,17 +1006,19 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
* @extends instance.web.search.Input
*
* @param {Array<instance.web.search.Filter>} filters elements of the group
- * @param {instance.web.SearchView} view view in which the filters are contained
+ * @param {instance.web.SearchView} parent parent in which the filters are contained
*/
- init: function (filters, view) {
+ init: function (filters, parent) {
// If all filters are group_by and we're not initializing a GroupbyGroup,
// create a GroupbyGroup instead of the current FilterGroup
if (!(this instanceof instance.web.search.GroupbyGroup) &&
_(filters).all(function (f) {
- return f.attrs.context && f.attrs.context.group_by; })) {
- return new instance.web.search.GroupbyGroup(filters, view);
+ if (!f.attrs.context) { return false; }
+ var c = instance.web.pyeval.eval('context', f.attrs.context);
+ return !_.isEmpty(c.group_by);})) {
+ return new instance.web.search.GroupbyGroup(filters, parent);
}
- this._super(view);
+ this._super(parent);
this.filters = filters;
this.view.query.on('add remove change reset', this.proxy('search_change'));
},
@@ -1096,6 +1137,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
var self = this;
item = item.toLowerCase();
var facet_values = _(this.filters).chain()
+ .filter(function (filter) { return filter.visible(); })
.filter(function (filter) {
var at = {
string: filter.attrs.string || '',
@@ -1122,8 +1164,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
icon: 'w',
completion_label: _lt("Group by: %s"),
- init: function (filters, view) {
- this._super(filters, view);
+ init: function (filters, parent) {
+ this._super(filters, parent);
// Not flanders: facet unicity is handled through the
// (category, field) pair of facet attributes. This is all well and
// good for regular filter groups where a group matches a facet, but for
@@ -1131,8 +1173,8 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
// view which proxies to the first GroupbyGroup, so it can be used
// for every GroupbyGroup and still provides the various methods needed
// by the search view. Use weirdo name to avoid risks of conflicts
- if (!this.getParent()._s_groupby) {
- this.getParent()._s_groupby = {
+ if (!this.view._s_groupby) {
+ this.view._s_groupby = {
help: "See GroupbyGroup#init",
get_context: this.proxy('get_context'),
get_domain: this.proxy('get_domain'),
@@ -1141,14 +1183,14 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
}
},
match_facet: function (facet) {
- return facet.get('field') === this.getParent()._s_groupby;
+ return facet.get('field') === this.view._s_groupby;
},
make_facet: function (values) {
return {
category: _t("GroupBy"),
icon: this.icon,
values: values,
- field: this.getParent()._s_groupby
+ field: this.view._s_groupby
};
}
});
@@ -1166,10 +1208,10 @@ instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instanc
* @extends instance.web.search.Input
*
* @param node
- * @param view
+ * @param parent
*/
- init: function (node, view) {
- this._super(view);
+ init: function (node, parent) {
+ this._super(parent);
this.load_attrs(node.attrs);
},
facet_for: function () { return $.when(null); },
@@ -1185,10 +1227,10 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
*
* @param view_section
* @param field
- * @param view
+ * @param parent
*/
- init: function (view_section, field, view) {
- this._super(view);
+ init: function (view_section, field, parent) {
+ this._super(parent);
this.load_attrs(_.extend({}, field, view_section.attrs));
},
facet_for: function (value) {
@@ -1228,7 +1270,7 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
*
* @param {String} name the field's name
* @param {String} operator the field's operator (either attribute-specified or default operator for the field
- * @param {Number|String} value parsed value for the field
+ * @param {Number|String} facet parsed value for the field
* @returns {Array<Array>} domain to include in the resulting search
*/
make_domain: function (name, operator, facet) {
@@ -1460,8 +1502,8 @@ instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @le
});
instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
default_operator: {},
- init: function (view_section, field, view) {
- this._super(view_section, field, view);
+ init: function (view_section, field, parent) {
+ this._super(view_section, field, parent);
this.model = new instance.web.Model(this.attrs.relation);
},
complete: function (needle) {
@@ -1673,6 +1715,13 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
if (!_.isEmpty(results.group_by)) {
results.context.group_by = results.group_by;
}
+ // Don't save user_context keys in the custom filter, otherwise end
+ // up with e.g. wrong uid or lang stored *and used in subsequent
+ // reqs*
+ var ctx = results.context;
+ _(_.keys(instance.session.user_context)).each(function (key) {
+ delete ctx[key];
+ });
var filter = {
name: $name.val(),
user_id: private_filter ? instance.session.uid : false,
@@ -1702,22 +1751,28 @@ instance.web.search.Filters = instance.web.search.Input.extend({
var running_count = 0;
// get total filters count
var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
- var filters_count = _(this.view.controls).chain()
+ var visible_filters = _(this.view.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 = _(this.view.controls).map(function (inputs, group) {
- var filters = _(inputs).filter(is_group);
- return {
- name: group === 'null' ? "<span class='oe_i'>q</span> " + _t("Filters") : "<span class='oe_i'>w</span> " + group,
- filters: filters,
- length: _(filters).chain().map(function (i) {
- return i.filters.length; }).sum().value()
- };
- });
+ var col1 = [], col2 = visible_filters.map(function (group) {
+ var filters = _(group.children).filter(is_group);
+ return {
+ name: _.str.sprintf("<span class='oe_i'>%s</span> %s",
+ group.icon, group.name),
+ filters: filters,
+ length: _(filters).chain().map(function (i) {
+ return i.filters.length; }).sum().value()
+ };
+ }).value();
while (col2.length) {
// col1 + group should be smaller than col2 + group
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index ccfc17a0288..cd27bd68b27 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -810,7 +810,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
try {
var form_invalid = false,
values = {},
- first_invalid_field = null;
+ first_invalid_field = null,
+ readonly_values = {};
for (var f in self.fields) {
if (!self.fields.hasOwnProperty(f)) { continue; }
f = self.fields[f];
@@ -819,11 +820,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) {
+ } else if (f.name !== 'id' && (!self.datarecord.id || f._dirty_flag)) {
// Special case 'id' field, do not save this field
// on 'create' : save all non readonly fields
// on 'edit' : save non readonly modified fields
- values[f.name] = f.get_value();
+ if (!f.get("readonly")) {
+ values[f.name] = f.get_value();
+ } else {
+ readonly_values[f.name] = f.get_value();
+ }
}
}
if (form_invalid) {
@@ -836,7 +841,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var save_deferral;
if (!self.datarecord.id) {
// Creation save
- save_deferral = self.dataset.create(values).then(function(r) {
+ save_deferral = self.dataset.create(values, {readonly_fields: readonly_values}).then(function(r) {
return self.record_created(r, prepend_on_create);
}, null);
} else if (_.isEmpty(values)) {
@@ -844,7 +849,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
save_deferral = $.Deferred().resolve({}).promise();
} else {
// Write save
- save_deferral = self.dataset.write(self.datarecord.id, values, {}).then(function(r) {
+ save_deferral = self.dataset.write(self.datarecord.id, values, {readonly_fields: readonly_values}).then(function(r) {
return self.record_saved(r);
}, null);
}
@@ -1887,10 +1892,11 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
if (this.node.attrs.icon && (! /\//.test(this.node.attrs.icon))) {
this.node.attrs.icon = '/web/static/src/img/icons/' + this.node.attrs.icon + '.png';
}
- this.view.on('view_content_has_changed', this, this.check_disable);
},
start: function() {
this._super.apply(this, arguments);
+ this.view.on('view_content_has_changed', this, this.check_disable);
+ this.check_disable();
this.$el.click(this.on_click);
if (this.node.attrs.help || instance.session.debug) {
this.do_attach_tooltip();
@@ -1916,18 +1922,20 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
modal: true,
buttons: [
{text: _t("Cancel"), click: function() {
- def.resolve();
$(this).dialog("close");
}
},
{text: _t("Ok"), click: function() {
- self.on_confirmed().done(function() {
- def.resolve();
+ var self2 = this;
+ self.on_confirmed().always(function() {
+ $(self2).dialog("close");
});
- $(this).dialog("close");
}
}
- ]
+ ],
+ beforeClose: function() {
+ def.resolve();
+ },
});
return def.promise();
} else {
@@ -2218,6 +2226,18 @@ instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.Reini
},
});
+/**
+ Some hack to make placeholders work in ie9.
+*/
+if ($.browser.msie && $.browser.version === "9.0") {
+ document.addEventListener("DOMNodeInserted",function(event){
+ var nodename = event.target.nodeName.toLowerCase();
+ if ( nodename === "input" || nodename == "textarea" ) {
+ $(event.target).placeholder();
+ }
+ });
+}
+
instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
template: 'FieldChar',
widget_class: 'oe_form_field_char',
@@ -2382,7 +2402,6 @@ 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);
@@ -2546,42 +2565,45 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
},
'change textarea': 'store_dom_value',
},
- init: function (field_manager, node) {
- this._super(field_manager, node);
- },
initialize_content: function() {
var self = this;
- this.$textarea = this.$el.find('textarea');
- this.auto_sized = false;
- this.default_height = this.$textarea.css('height');
- if (this.get("effective_readonly")) {
- this.$textarea.attr('disabled', 'disabled');
+ if (! this.get("effective_readonly")) {
+ this.$textarea = this.$el.find('textarea');
+ this.auto_sized = false;
+ this.default_height = this.$textarea.css('height');
+ if (this.get("effective_readonly")) {
+ this.$textarea.attr('disabled', 'disabled');
+ }
+ this.setupFocus(this.$textarea);
+ } else {
+ this.$textarea = undefined;
}
- this.setupFocus(this.$textarea);
},
commit_value: function () {
- this.store_dom_value();
+ if (! this.get("effective_readonly") && this.$textarea) {
+ this.store_dom_value();
+ }
return this._super();
},
store_dom_value: function () {
- if (!this.get('effective_readonly') && this.$('textarea').length) {
- this.internal_set_value(
- instance.web.parse_value(
- this.$textarea.val(),
- this));
- }
+ this.internal_set_value(instance.web.parse_value(this.$textarea.val(), this));
},
render_value: function() {
- var show_value = instance.web.format_value(this.get('value'), this, '');
- if (show_value === '') {
- this.$textarea.css('height', parseInt(this.default_height)+"px");
- }
- this.$textarea.val(show_value);
- if (! this.auto_sized) {
- this.auto_sized = true;
- this.$textarea.autosize();
+ if (! this.get("effective_readonly")) {
+ var show_value = instance.web.format_value(this.get('value'), this, '');
+ if (show_value === '') {
+ this.$textarea.css('height', parseInt(this.default_height)+"px");
+ }
+ this.$textarea.val(show_value);
+ if (! this.auto_sized) {
+ this.auto_sized = true;
+ this.$textarea.autosize();
+ } else {
+ this.$textarea.trigger("autosize");
+ }
} else {
- this.$textarea.trigger("autosize");
+ var txt = this.get("value");
+ this.$(".oe_form_text_content").text(txt);
}
},
is_syntax_valid: function() {
@@ -2599,14 +2621,18 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
return this.get('value') === '' || this._super();
},
focus: function($el) {
- this.$textarea[0].focus();
+ if (!this.get("effective_readonly") && this.$textarea) {
+ this.$textarea[0].focus();
+ }
},
set_dimensions: function (height, width) {
this._super(height, width);
- this.$textarea.css({
- width: width,
- minHeight: height
- });
+ if (!this.get("effective_readonly") && this.$textarea) {
+ this.$textarea.css({
+ width: width,
+ minHeight: height
+ });
+ }
},
});
@@ -2970,8 +2996,6 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
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);
@@ -3002,6 +3026,26 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
if (!this.get("effective_readonly"))
this.render_editable();
},
+ destroy_content: function () {
+ if (this.$drop_down) {
+ this.$drop_down.off('click');
+ delete this.$drop_down;
+ }
+ if (this.$input) {
+ this.$input.closest(".ui-dialog .ui-dialog-content").off('scroll');
+ this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
+ 'focus focusout change keydown');
+ delete this.$input;
+ }
+ if (this.$follow_button) {
+ this.$follow_button.off('blur focus click');
+ delete this.$follow_button;
+ }
+ },
+ destroy: function () {
+ this.destroy_content();
+ return this._super();
+ },
init_error_displayer: function() {
// nothing
},
@@ -3263,7 +3307,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
},
focus: function () {
if (!this.get('effective_readonly')) {
- this.$input[0].focus();
+ this.$input && this.$input[0].focus();
}
},
_quick_create: function() {
@@ -3682,8 +3726,8 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
var pop = new instance.web.form.FormOpenPopup(this);
pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
title: _t("Open: ") + self.o2m.string,
- create_function: function(data) {
- return self.o2m.dataset.create(data).done(function(r) {
+ create_function: function(data, options) {
+ return self.o2m.dataset.create(data, options).done(function(r) {
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
self.o2m.dataset.trigger("dataset_changed", r);
});
@@ -3742,8 +3786,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
this.o2m.trigger_on_change();
},
is_valid: function () {
- var form = this.editor.form;
-
+ var editor = this.editor;
+ var form = editor.form;
+ // If no edition is pending, the listview can not be invalid (?)
+ if (!editor.record) {
+ return true
+ }
// If the form has not been modified, the view can only be valid
// NB: is_dirty will also be set on defaults/onchanges/whatever?
// oe_form_dirty seems to only be set on actual user actions
@@ -3773,11 +3821,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
title: _t("Create: ") + self.o2m.string,
initial_view: "form",
alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
- create_function: function(data, callback, error_callback) {
- return self.o2m.dataset.create(data).done(function(r) {
+ create_function: function(data, options) {
+ return self.o2m.dataset.create(data, options).done(function(r) {
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
self.o2m.dataset.trigger("dataset_changed", r);
- }).done(callback).fail(error_callback);
+ });
},
read_function: function() {
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
@@ -3990,6 +4038,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
if (this.get("effective_readonly"))
return;
var self = this;
+ var ignore_blur = false;
self.$text = this.$("textarea");
self.$text.textext({
plugins : 'tags arrow autocomplete',
@@ -4008,6 +4057,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
if (data.id) {
self.add_id(data.id);
} else {
+ ignore_blur = true;
data.action();
}
},
@@ -4058,10 +4108,13 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
self.$text
.focusin(function () {
self.trigger('focused');
+ ignore_blur = false;
})
.focusout(function() {
self.$text.trigger("setInputData", "");
- self.trigger('blurred');
+ if (!ignore_blur) {
+ self.trigger('blurred');
+ }
}).keydown(function(e) {
if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
self.$text.textext()[0].autocomplete().selectFromDropdown();
@@ -4268,6 +4321,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
});
}
},
+ is_action_enabled: function () { return true; },
});
instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
@@ -4502,9 +4556,9 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
this.created_elements = [];
this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context);
this.dataset.read_function = this.options.read_function;
- this.dataset.create_function = function(data, sup) {
+ this.dataset.create_function = function(data, options, sup) {
var fct = self.options.create_function || sup;
- return fct.call(this, data).done(function(r) {
+ return fct.call(this, data, options).done(function(r) {
self.trigger('create_completed saved', r);
self.created_elements.push(r);
});
@@ -5216,11 +5270,11 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
this.calc_domain();
this.on("change:value", this, this.get_selection);
this.on("change:evaluated_selection_domain", this, this.get_selection);
- this.get_selection();
this.on("change:selection", this, function() {
this.selection = this.get("selection");
this.render_value();
});
+ this.get_selection();
if (this.options.clickable) {
this.$el.on('click','li',this.on_click_stage);
}
@@ -5339,10 +5393,10 @@ instance.web.form.FieldMonetary = instance.web.form.FieldFloat.extend({
});
},
parse_value: function(val, def) {
- return instance.web.parse_value(val, {type: "float"}, def);
+ return instance.web.parse_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
},
format_value: function(val, def) {
- return instance.web.format_value(val, {type: "float"}, def);
+ return instance.web.format_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
},
});
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index d23bedf1d1c..b69ec6dbdca 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -321,9 +321,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
.appendTo($this.empty())
.click(function (e) {e.stopPropagation();})
.append('<option value="80">80</option>' +
- '<option value="100">100</option>' +
'<option value="200">200</option>' +
'<option value="500">500</option>' +
+ '<option value="2000">2000</option>' +
'<option value="NaN">' + _t("Unlimited") + '</option>')
.change(function () {
var val = parseInt($select.val(), 10);
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index f1bd7c4325e..4e042b60c5d 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -96,7 +96,8 @@ openerp.web.list_editable = function (instance) {
});
},
editable: function () {
- return !this.options.disable_editable_mode
+ return !this.grouped
+ && !this.options.disable_editable_mode
&& (this.fields_view.arch.attrs.editable
|| this._context_editable
|| this.options.editable);
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index f5638004b47..1ba65f7ec18 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -1446,11 +1446,11 @@ instance.web.View = instance.web.Widget.extend({
* Performs a fields_view_get and apply postprocessing.
* return a {$.Deferred} resolved with the fvg
*
- * @param {Object} [args]
+ * @param {Object} args
* @param {String|Object} args.model instance.web.Model instance or string repr of the model
- * @param {null|Object} args.context context if args.model is a string
- * @param {null|Number} args.view_id id of the view to be loaded, default view if null
- * @param {null|String} args.view_type type of view to be loaded if view_id is null
+ * @param {Object} [args.context] context if args.model is a string
+ * @param {Number} [args.view_id] id of the view to be loaded, default view if null
+ * @param {String} [args.view_type] type of view to be loaded if view_id is null
* @param {Boolean} [args.toolbar=false] get the toolbar definition
*/
instance.web.fields_view_get = function(args) {
@@ -1555,12 +1555,17 @@ instance.web.xml_to_str = function(node) {
} else {
throw new Error(_t("Could not serialize XML"));
}
- // Browsers won't deal with self closing tags except br, hr, input, ...
- // http://stackoverflow.com/questions/97522/what-are-all-the-valid-self-closing-elements-in-xhtml-as-implemented-by-the-maj
- //
+ // Browsers won't deal with self closing tags except void elements:
+ // http://www.w3.org/TR/html-markup/syntax.html
+ var void_elements = 'area base br col command embed hr img input keygen link meta param source track wbr'.split(' ');
+
// The following regex is a bit naive but it's ok for the xmlserializer output
str = str.replace(/<([a-z]+)([^<>]*)\s*\/\s*>/g, function(match, tag, attrs) {
- return "<" + tag + attrs + "></" + tag + ">";
+ if (void_elements.indexOf(tag) < 0) {
+ return "<" + tag + attrs + "></" + tag + ">";
+ } else {
+ return match;
+ }
});
return str;
};
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 26b1cc09019..70d74d05747 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -40,7 +40,7 @@
<td>
<p>
<t t-js="d">
- var message = d.message ? d.message : d.error.data.message;
+ var message = d.message ? d.message : d.error.data.fault_code;
d.html_error = context.engine.tools.html_escape(message)
.replace(/\n/g, '<br/>');
</t>
@@ -71,9 +71,9 @@
</div>
<ul>
<li>Username</li>
- <li><input name="login" type="text" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li>
+ <li><input name="login" type="text" value="" autofocus="autofocus"/></li>
<li>Password</li>
- <li><input name="password" type="password" t-att-value="widget.selected_password || ''"/></li>
+ <li><input name="password" type="password" value=""/></li>
<li><button name="submit">Log in</button></li>
</ul>
</form>
@@ -118,7 +118,8 @@
you will be able to install your first application.
</p>
<p class="oe_grey" style="margin: 10px">
- By default, the master password is 'admin' if you did not changed it.
+ By default, the master password is 'admin'. This password
+ is required to created, delete dump or restore databases.
</p>
<table class="db_option_table" style="margin: 10px">
<tr>
@@ -129,7 +130,9 @@
</tr>
<tr>
<td><label for="db_name">Select a database name:</label></td>
- <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true" placeholder="e.g. mycompany"/></td>
+ <td>
+ <input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true" placeholder="e.g. mycompany"/>
+ </td>
</tr>
<tr>
<td><label for="demo_data">Load demonstration data:</label></td>
@@ -1047,17 +1050,22 @@
</t>
<t t-name="FieldText">
<div class="oe_form_field oe_form_field_text" t-att-style="widget.node.attrs.style">
- <textarea rows="6"
- t-att-name="widget.name"
- class="field_text"
- t-att-tabindex="widget.node.attrs.tabindex"
- t-att-autofocus="widget.node.attrs.autofocus"
- t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''"
- t-att-maxlength="widget.field.size"
- ></textarea><img class="oe_field_translate oe_input_icon"
- t-if="widget.field.translate and !widget.get('effective_readonly')"
- t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
- />
+ <t t-if="!widget.get('effective_readonly')">
+ <textarea rows="6"
+ t-att-name="widget.name"
+ class="field_text"
+ t-att-tabindex="widget.node.attrs.tabindex"
+ t-att-autofocus="widget.node.attrs.autofocus"
+ t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''"
+ t-att-maxlength="widget.field.size"
+ ></textarea><img class="oe_field_translate oe_input_icon"
+ t-if="widget.field.translate and !widget.get('effective_readonly')"
+ t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
+ />
+ </t>
+ <t t-if="widget.get('effective_readonly')">
+ <span class="oe_form_text_content"></span>
+ </t>
</div>
</t>
<t t-name="FieldTextHtml">
@@ -1076,8 +1084,9 @@
t-att-name="widget.name"
t-att-placeholder="placeholder"
class="oe_datepicker_master"
- /><img class="oe_input_icon oe_datepicker_trigger" t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
- title="Select date" width="16" height="16" border="0"/>
+ /><img class="oe_input_icon oe_datepicker_trigger" draggable="false"
+ t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
+ title="Select date" width="16" height="16" border="0"/>
</span>
</t>
<t t-name="FieldDate">
@@ -1111,7 +1120,7 @@
</t>
<t t-if="!widget.get('effective_readonly')">
<a t-if="! widget.options.no_open" href="#" tabindex="-1"
- class="oe_m2o_cm_button oe_e">/</a>
+ class="oe_m2o_cm_button oe_e" draggable="false">/</a>
<div>
<input type="text"
t-att-id="widget.id_for_label"
@@ -1120,7 +1129,7 @@
t-att-placeholder="widget.node.attrs.placeholder"
/>
<span class="oe_m2o_drop_down_button">
- <img t-att-src='_s + "/web/static/src/img/down-arrow.png"'/>
+ <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' draggable="false"/>
</span>
</div>
</t>
@@ -1515,7 +1524,7 @@
<t t-esc="attrs.string"/>
</button>
<ul t-name="SearchView.filters">
- <li t-foreach="widget.filters" t-as="filter"
+ <li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()"
t-att-title="filter.attrs.string ? filter.attrs.help : undefined">
<t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
</li>
@@ -1603,15 +1612,6 @@
</div>
</div>
</t>
-<t t-name="SearchView.group">
- <t t-call="SearchView.util.expand">
- <t t-set="expand" t-value="attrs.expand"/>
- <t t-set="label" t-value="attrs.string"/>
- <t t-set="content">
- <t t-call="SearchView.render_lines"/>
- </t>
- </t>
-</t>
<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">
</div>
@@ -1842,7 +1842,7 @@
<tr>
<td t-foreach="records[0]" t-as="column">
<input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe_m2o_drop_down_button">
- <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span>
+ <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' draggable="false"/></span>
</td>
</tr>
<tr t-foreach="records" t-as="record" class="oe_import_grid-row">