diff --git a/addons/account/static/src/js/tour.js b/addons/account/static/src/js/tours/account.js
similarity index 100%
rename from addons/account/static/src/js/tour.js
rename to addons/account/static/src/js/tours/account.js
diff --git a/addons/account/static/src/js/reconciliation/tour_reconciliation.js b/addons/account/static/tests/tours/reconciliation.js
similarity index 100%
rename from addons/account/static/src/js/reconciliation/tour_reconciliation.js
rename to addons/account/static/tests/tours/reconciliation.js
diff --git a/addons/account/views/account.xml b/addons/account/views/account.xml
index 837d27235b3..448363ddec2 100644
--- a/addons/account/views/account.xml
+++ b/addons/account/views/account.xml
@@ -20,14 +20,13 @@
-
-
+
@@ -39,6 +38,12 @@
+
+
The list of connected USB devices as seen by the IoTBox
""" if debug is None: - resp += """(debug version)""" + resp += """(debug version)""" devices = subprocess.check_output("lsusb").decode('utf-8').split('\n') count = 0 resp += "').text(utils.json_node_to_xml(
- self._controller.renderer.arch, true)
- ).appendTo(dialog.$el);
- });
- dialog.open();
+ leave_debug_mode: function () {
+ var qs = $.deparam.querystring();
+ qs.debug = '';
+ window.location.search = '?' + $.param(qs);
},
});
-function make_context(width, height, fn) {
- var canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- // make e.layerX/e.layerY imitate e.offsetX/e.offsetY.
- canvas.style.position = 'relative';
- var ctx = canvas.getContext('2d');
- ctx.imageSmoothingEnabled = false;
- ctx.mozImageSmoothingEnabled = false;
- ctx.oImageSmoothingEnabled = false;
- ctx.webkitImageSmoothingEnabled = false;
- fn && fn(ctx);
- return ctx;
-}
-var RequestsOverlay = Widget.extend({
- template: 'WebClient.DebugManager.RequestsOverlay',
- TRACKS: 8,
- TRACK_WIDTH: 9,
- events: {
- mousemove: function (e) {
- this.$tooltip.hide();
- }
- },
- init: function () {
- this._super.apply(this, arguments);
- this._render = _.throttle(
- this._render.bind(this),
- 1000/15, {leading: false}
- );
- },
- start: function () {
- var _super = this._super();
- this.$tooltip = this.$('div.o_debug_tooltip');
- this.getParent().on('update-stats', this, this._render);
- this._render();
- return _super;
- },
- tooltip: function (text, start, end, x, y) {
- // x and y are hit point with respect to the viewport. To know where
- // this hit point is with respect to the overlay, subtract the offset
- // between viewport and overlay, then add scroll factor of overlay
- // (which isn't taken in account by the viewport).
- //
- // Normally the viewport overlay should sum offsets of all
- // offsetParents until we reach `null` but in this case the overlay
- // should have been added directly to the body, which should have an
- // offset of 0.
-
- var top = y - this.el.offsetTop + this.el.scrollTop + 1;
- var left = x - this.el.offsetLeft + this.el.scrollLeft + 1;
- this.$tooltip.css({top: top, left: left}).show()[0].innerHTML = ['', text, ' (', (end - start), 'ms)', '
'].join('');
- },
-
- _render: function () {
- var $summary = this.$('header'),
- w = $summary[0].clientWidth,
- $requests = this.$('.o_debug_requests');
- $summary.find('canvas').attr('width', w);
- var tracks = document.getElementById('o_debug_requests_summary');
-
- _.invoke(this.getChildren(), 'destroy');
-
- var requests = this.getParent()._events;
- var bounds = this._get_bounds(requests);
- // horizontal scaling factor for summary
- var scale = w / (bounds.high - bounds.low);
-
- // store end-time of "current" requests, to find out which track a
- // request should go in, just look for the first track whose end-time
- // is smaller than the new request's start time.
- var track_ends = _(this.TRACKS).times(_.constant(-Infinity));
-
- var ctx = tracks.getContext('2d');
- ctx.lineWidth = this.TRACK_WIDTH;
- for (var i = 0; i < requests.length; i++) {
- var request = requests[i];
- // FIXME: is it certain that events in the request are sorted by timestamp?
- var rstart = Math.floor(request[0][3] * 1e3);
- var rend = Math.ceil(request[request.length - 1][3] * 1e3);
- // find free track for current request
- for(var track=0; track < track_ends.length; ++track) {
- if (track_ends[track] < rstart) { break; }
- }
- // FIXME: display error message of some sort? Re-render with larger area? Something?
- if (track >= track_ends.length) {
- console.warn("could not find an empty summary track");
- continue;
- }
- // set new track end
- track_ends[track] = rend;
- ctx.save();
- ctx.translate(Math.floor((rstart - bounds.low) * scale), track * (this.TRACK_WIDTH + 1));
- this._draw_request(request, ctx, 0, scale);
- ctx.restore();
- new RequestDetails(this, request, scale).appendTo($requests);
- }
- },
- _draw_request: function (request, to_context, step, hscale, handle_event) {
- // have one draw surface for each event type:
- // * no need to alter context from one event to the next, each surface
- // gets its own color for all its lifetime
- // * surfaces can be blended in a specified order, which means events
- // can be drawn in any order, no need to care about z-index while
- // serializing events to the surfaces
- var surfaces = {
- request: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
- ctx.strokeStyle = 'blue';
- ctx.fillStyle = '#88f';
- ctx.lineJoin = 'round';
- ctx.lineWidth = 1;
- }),
- //func: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
- // ctx.strokeStyle = 'gray';
- // ctx.lineWidth = to_context.lineWidth;
- // ctx.translate(0, initial_offset);
- //}),
- sql: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
- ctx.strokeStyle = 'red';
- ctx.fillStyle = '#f88';
- ctx.lineJoin = 'round';
- ctx.lineWidth = 1;
- }),
- template: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
- ctx.strokeStyle = 'green';
- ctx.fillStyle = '#8f8';
- ctx.lineJoin = 'round';
- ctx.lineWidth = 1;
- })
- };
- // apply scaling manually so zooming in improves display precision
- var stacks = {}, start = Math.floor(request[0][3] * 1e3 * hscale);
- var event_idx = 0;
-
- var rect_width = to_context.lineWidth;
- for (var i = 0; i < request.length; i++) {
- var type, m, event = request[i];
- var tag = event[0], timestamp = Math.floor(event[3] * 1e3 * hscale) - start;
-
- if (m = /(\w+)-start/.exec(tag)) {
- type = m[1];
- if (!(type in stacks)) { stacks[type] = []; }
- handle_event && handle_event(event_idx, timestamp, event);
- stacks[type].push({
- timestamp: timestamp,
- idx: event_idx++
- });
- } else if (m = /(\w+)-end/.exec(tag)) {
- type = m[1];
- var stack = stacks[type];
- var estart = stack.pop(), duration = Math.ceil(timestamp - estart.timestamp);
- handle_event && handle_event(estart.idx, timestamp, event);
-
- var surface = surfaces[type];
- if (!surface) { continue; } // FIXME: support for unknown event types
-
- var y = step * estart.idx;
- // path rectangle for the current event on the relevant surface
- surface.rect(estart.timestamp + 0.5, y + 0.5, duration || 1, rect_width);
- }
- }
- // add each layer to the main canvas
- var keys = ['request', /*'func', */'template', 'sql'];
- for (var j = 0; j < keys.length; ++j) {
- // stroke and fill all rectangles for the relevant surface/context
- var ctx = surfaces[keys[j]];
- ctx.fill();
- ctx.stroke();
- to_context.drawImage(ctx.canvas, 0, 0);
- }
- },
- /**
- * Returns first and last events in milliseconds
- *
- * @param requests
- * @returns {{low: number, high: number}}
- * @private
- */
- _get_bounds: function (requests) {
- var low = +Infinity;
- var high =-+Infinity;
-
- for (var i = 0; i < requests.length; i++) {
- var request = requests[i];
- for (var j = 0; j < request.length; j++) {
- var event = request[j];
- var timestamp = event[3];
- low = Math.min(low, timestamp);
- high = Math.max(high, timestamp);
- }
- }
- return {low: Math.floor(low * 1e3), high: Math.ceil(high * 1e3)};
- }
-});
-var RequestDetails = Widget.extend({
- events: {
- click: function () {
- this._open = !this._open;
- this.render();
- },
- 'mousemove canvas': function (e) {
- e.stopPropagation();
- var y = e.y || e.offsetY || e.layerY;
- if (!y) { return; }
- var event = this._payloads[Math.floor(y / this._REQ_HEIGHT)];
- if (!event) { return; }
-
- this.getParent().tooltip(event.payload, event.start, event.stop, e.clientX, e.clientY);
- }
- },
- init: function (parent, request, scale) {
- this._super.apply(this, arguments);
- this._request = request;
- this._open = false;
- this._scale = scale;
- this._REQ_HEIGHT = 20;
- },
- start: function () {
- this.el.style.borderBottom = '1px solid black';
- this.render();
- return this._super();
- },
- render: function () {
- var request_cell_height = this._REQ_HEIGHT, TITLE_WIDTH = 200;
- var request = this._request;
- var req_start = request[0][3] * 1e3;
- var req_duration = request[request.length - 1][3] * 1e3 - req_start;
- var height = request_cell_height * (this._open ? request.length / 2 : 1);
- var cell_center = request_cell_height / 2;
- var ctx = make_context(210 + Math.ceil(req_duration * this._scale), height, function (ctx) {
- ctx.lineWidth = cell_center;
- });
- this.$el.empty().append(ctx.canvas);
- var payloads = this._payloads = [];
-
- // lazy version: if the render is single-line (!this._open), the extra
- // content will be discarded when the text canvas gets pasted onto the
- // main canvas. An improvement would be to not do text rendering
- // beyond the first event for "closed" requests events… then again
- // that makes for more regular rendering profile?
- var text_ctx = make_context(TITLE_WIDTH, height, function (ctx) {
- ctx.font = '12px sans-serif';
- ctx.textAlign = 'right';
- ctx.textBaseline = 'middle';
- ctx.translate(0, cell_center);
- });
-
- ctx.save();
- ctx.translate(TITLE_WIDTH + 10, ((request_cell_height/4)|0));
-
- this.getParent()._draw_request(request, ctx, this._open ? request_cell_height : 0, this._scale, function (idx, timestamp, event) {
- if (/-start$/g.test(event[0])) {
- payloads.push({
- payload: event[2],
- start: timestamp,
- stop: null
- });
-
- // we want ~200px wide, assume the average character is at
- // least 4px wide => there can be *at most* 49 characters
- var title = event[2];
- title = title.replace(/\s+$/, '');
- title = title.length <= 50 ? title : ('…' + title.slice(-49));
- while (text_ctx.measureText(title).width > 200) {
- title = '…' + title.slice(2);
- }
- text_ctx.fillText(title, TITLE_WIDTH, request_cell_height * idx);
- } else if (/-end$/g.test(event[0])) {
- payloads[idx].stop = timestamp;
- }
- });
- ctx.restore();
- // add the text layer to the main canvas
- ctx.drawImage(text_ctx.canvas, 0, 0);
- }
-});
-
-if (config.debug) {
- SystrayMenu.Items.push(DebugManager);
-
- WebClient.include({
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
-
- /**
- * @override
- */
- current_action_updated: function (action, controller) {
- this._super.apply(this, arguments);
- var debugManager = _.find(this.menu.systray_menu.widgets, function(item) {
- return item instanceof DebugManager;
- });
- debugManager.update('action', action, controller && controller.widget);
- },
- });
-
- ActionManager.include({
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
-
- /**
- * Returns the action of the controller currently opened in a dialog,
- * i.e. a target='new' action, if any.
- *
- * @returns {Object|null}
- */
- getCurrentActionInDialog: function () {
- if (this.currentDialogController) {
- return this.actions[this.currentDialogController.actionID];
- }
- return null;
- },
- /**
- * Returns the controller currently opened in a dialog, if any.
- *
- * @returns {Object|null}
- */
- getCurrentControllerInDialog: function () {
- return this.currentDialogController;
- },
- });
-
- Dialog.include({
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * @override
- */
- open: function() {
- var self = this;
- // if the dialog is opened by the ActionManager, instantiate a
- // DebugManager and insert it into the DOM once the dialog is opened
- // (delay this with a setTimeout(0) to ensure that the internal
- // state, i.e. the current action and controller, of the
- // ActionManager is set to properly update the DebugManager)
- this.opened(function() {
- setTimeout(function () {
- var parent = self.getParent();
- if (parent instanceof ActionManager) {
- var action = parent.getCurrentActionInDialog();
- if (action) {
- var controller = parent.getCurrentControllerInDialog();
- self.debugManager = new DebugManager(self);
- var $header = self.$modal.find('.modal-header:first');
- return self.debugManager.prependTo($header).then(function () {
- self.debugManager.update('action', action, controller.widget);
- });
- }
- }
- }, 0);
- });
-
- return this._super.apply(this, arguments);
- },
- });
-}
return DebugManager;
diff --git a/addons/web/static/src/js/tools/debug_manager_backend.js b/addons/web/static/src/js/tools/debug_manager_backend.js
new file mode 100644
index 00000000000..d8b462e5bd4
--- /dev/null
+++ b/addons/web/static/src/js/tools/debug_manager_backend.js
@@ -0,0 +1,766 @@
+odoo.define('web.DebugManager.Backend', function (require) {
+"use strict";
+
+var ActionManager = require('web.ActionManager');
+var DebugManager = require('web.DebugManager');
+var dialogs = require('web.view_dialogs');
+var startClickEverywhere = require('web.clickEverywhere');
+var config = require('web.config');
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var SystrayMenu = require('web.SystrayMenu');
+var utils = require('web.utils');
+var WebClient = require('web.WebClient');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+/**
+ * DebugManager features depending on backend
+ */
+DebugManager.include({
+ requests_clear: function () {
+ if (!this._events) {
+ return;
+ }
+ this._events = [];
+ this.trigger('update-stats', this._events);
+ },
+ show_timelines: function () {
+ if (this._overlay) {
+ this._overlay.destroy();
+ this._overlay = null;
+ return;
+ }
+ this._overlay = new RequestsOverlay(this);
+ this._overlay.appendTo(document.body);
+ },
+
+ /**
+ * Updates current action (action descriptor) on tag = action,
+ */
+ update: function (tag, descriptor) {
+ return this._super().then(function () {
+ this.$dropdown.find(".o_debug_split_assets").before(QWeb.render('WebClient.DebugManager.Backend', {
+ manager: this,
+ }));
+ }.bind(this));
+ },
+ select_view: function () {
+ var self = this;
+ new dialogs.SelectCreateDialog(this, {
+ res_model: 'ir.ui.view',
+ title: _t('Select a view'),
+ disable_multiple_selection: true,
+ domain: [['type', '!=', 'qweb'], ['type', '!=', 'search']],
+ on_selected: function (records) {
+ self._rpc({
+ model: 'ir.ui.view',
+ method: 'search_read',
+ domain: [['id', '=', records[0].id]],
+ fields: ['name', 'model', 'type'],
+ limit: 1,
+ })
+ .then(function (views) {
+ var view = views[0];
+ view.type = view.type === 'tree' ? 'list' : view.type; // ignore tree view
+ self.do_action({
+ type: 'ir.actions.act_window',
+ name: view.name,
+ res_model: view.model,
+ views: [[view.id, view.type]]
+ });
+ });
+ }
+ }).open();
+ },
+ /**
+ * Runs the JS (desktop) tests
+ */
+ perform_js_tests: function () {
+ this.do_action({
+ name: _t("JS Tests"),
+ target: 'new',
+ type: 'ir.actions.act_url',
+ url: '/web/tests?mod=*'
+ });
+ },
+ /**
+ * Runs the JS mobile tests
+ */
+ perform_js_mobile_tests: function () {
+ this.do_action({
+ name: _t("JS Mobile Tests"),
+ target: 'new',
+ type: 'ir.actions.act_url',
+ url: '/web/tests/mobile?mod=*'
+ });
+ },
+ perform_click_everywhere_test: function () {
+ var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th");
+ $homeMenu.click();
+ startClickEverywhere();
+ },
+});
+
+/**
+ * DebugManager features depending on having an action, and possibly a model
+ * (window action)
+ */
+DebugManager.include({
+ /**
+ * Updates current action (action descriptor) on tag = action,
+ */
+ update: function (tag, descriptor) {
+ if (tag === 'action') {
+ this._action = descriptor;
+ }
+ return this._super().then(function () {
+ this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.Action', {
+ manager: this,
+ action: this._action
+ }));
+ }.bind(this));
+ },
+ edit: function (params, evt) {
+ this.do_action({
+ res_model: params.model,
+ res_id: params.id,
+ name: evt.target.text,
+ type: 'ir.actions.act_window',
+ views: [[false, 'form']],
+ view_mode: 'form',
+ target: 'new',
+ flags: {action_buttons: true, headless: true}
+ });
+ },
+ get_view_fields: function () {
+ var model = this._action.res_model,
+ self = this;
+ this._rpc({
+ model: 'ir.model',
+ method: 'search',
+ args: [[['model', '=', model]]]
+ }).then(function (ids) {
+ self.do_action({
+ res_model: 'ir.model.fields',
+ name: _t('View Fields'),
+ views: [[false, 'list'], [false, 'form']],
+ domain: [['model_id', '=', model]],
+ type: 'ir.actions.act_window',
+ context: {
+ 'default_model_id': ids[0]
+ }
+ });
+ });
+ },
+ manage_filters: function () {
+ this.do_action({
+ res_model: 'ir.filters',
+ name: _t('Manage Filters'),
+ views: [[false, 'list'], [false, 'form']],
+ type: 'ir.actions.act_window',
+ context: {
+ search_default_my_filters: true,
+ search_default_model_id: this._action.res_model
+ }
+ });
+ },
+ translate: function() {
+ this._rpc({
+ model: 'ir.translation',
+ method: 'get_technical_translations',
+ args: [this._action.res_model],
+ })
+ .then(this.do_action);
+ }
+});
+
+/**
+ * DebugManager features depending on having a form view or single record.
+ * These could theoretically be split, but for now they'll be considered one
+ * and the same.
+ */
+DebugManager.include({
+ start: function () {
+ this._can_edit_views = false;
+ return Promise.all([
+ this._super(),
+ this._rpc({
+ model: 'ir.ui.view',
+ method: 'check_access_rights',
+ kwargs: {operation: 'write', raise_exception: false},
+ })
+ .then(function (ar) {
+ this._can_edit_views = ar;
+ }.bind(this))
+ ]
+ );
+ },
+ update: function (tag, descriptor, widget) {
+ if (tag === 'action' || tag === 'view') {
+ this._controller = widget;
+ }
+ return this._super(tag, descriptor).then(function () {
+ this.$dropdown.find(".o_debug_leave_section").before(QWeb.render('WebClient.DebugManager.View', {
+ action: this._action,
+ can_edit: this._can_edit_views,
+ controller: this._controller,
+ controlPanelView: this._controller && this._controller._controlPanel,
+ manager: this,
+ view: this._controller && _.findWhere(this._action.views, {
+ type: this._controller.viewType,
+ }),
+ }));
+ }.bind(this));
+ },
+ get_attachments: function() {
+ var selectedIDs = this._controller.getSelectedIds();
+ if (!selectedIDs.length) {
+ console.warn(_t("No attachment available"));
+ return;
+ }
+ this.do_action({
+ res_model: 'ir.attachment',
+ name: _t('Manage Attachments'),
+ views: [[false, 'list'], [false, 'form']],
+ type: 'ir.actions.act_window',
+ domain: [['res_model', '=', this._action.res_model], ['res_id', '=', selectedIDs[0]]],
+ context: {
+ default_res_model: this._action.res_model,
+ default_res_id: selectedIDs[0],
+ },
+ });
+ },
+ get_metadata: function() {
+ var self = this;
+ var selectedIDs = this._controller.getSelectedIds();
+ if (!selectedIDs.length) {
+ console.warn(_t("No metadata available"));
+ return;
+ }
+ this._rpc({
+ model: this._action.res_model,
+ method: 'get_metadata',
+ args: [selectedIDs],
+ }).then(function(result) {
+ var metadata = result[0];
+ metadata.creator = field_utils.format.many2one(metadata.create_uid);
+ metadata.lastModifiedBy = field_utils.format.many2one(metadata.write_uid);
+ var createDate = field_utils.parse.datetime(metadata.create_date);
+ metadata.create_date = field_utils.format.datetime(createDate);
+ var modificationDate = field_utils.parse.datetime(metadata.write_date);
+ metadata.write_date = field_utils.format.datetime(modificationDate);
+ var dialog = new Dialog(this, {
+ title: _.str.sprintf(_t("Metadata (%s)"), self._action.res_model),
+ size: 'medium',
+ $content: QWeb.render('WebClient.DebugViewLog', {
+ perm : metadata,
+ })
+ });
+ dialog.open().opened(function () {
+ dialog.$el.on('click', 'a[data-action="toggle_noupdate"]', function (ev) {
+ ev.preventDefault();
+ self._rpc({
+ model: 'ir.model.data',
+ method: 'toggle_noupdate',
+ args: [self._action.res_model, metadata.id]
+ }).then(function (res) {
+ dialog.close();
+ self.get_metadata();
+ })
+ });
+ })
+ });
+ },
+ set_defaults: function() {
+ var self = this;
+
+ var display = function (fieldInfo, value) {
+ var displayed = value;
+ if (value && fieldInfo.type === 'many2one') {
+ displayed = value.data.display_name;
+ value = value.data.id;
+ } else if (value && fieldInfo.type === 'selection') {
+ displayed = _.find(fieldInfo.selection, function (option) {
+ return option[0] === value;
+ })[1];
+ }
+ return [value, displayed];
+ };
+
+ var renderer = this._controller.renderer;
+ var state = renderer.state;
+ var fields = state.fields;
+ var fieldsInfo = state.fieldsInfo.form;
+ var fieldNamesInView = state.getFieldNames();
+ var fieldNamesOnlyOnView = ['message_attachment_count'];
+ var fieldsValues = state.data;
+ var modifierDatas = {};
+ _.each(fieldNamesInView, function (fieldName) {
+ modifierDatas[fieldName] = _.find(renderer.allModifiersData, function (modifierdata) {
+ return modifierdata.node.attrs.name === fieldName;
+ });
+ });
+ this.fields = _.chain(fieldNamesInView)
+ .difference(fieldNamesOnlyOnView)
+ .map(function (fieldName) {
+ var modifierData = modifierDatas[fieldName];
+ var invisibleOrReadOnly;
+ if (modifierData) {
+ var evaluatedModifiers = modifierData.evaluatedModifiers[state.id];
+ invisibleOrReadOnly = evaluatedModifiers.invisible || evaluatedModifiers.readonly;
+ }
+ var fieldInfo = fields[fieldName];
+ var valueDisplayed = display(fieldInfo, fieldsValues[fieldName]);
+ var value = valueDisplayed[0];
+ var displayed = valueDisplayed[1];
+ // ignore fields which are empty, invisible, readonly, o2m
+ // or m2m
+ if (!value || invisibleOrReadOnly || fieldInfo.type === 'one2many' ||
+ fieldInfo.type === 'many2many' || fieldInfo.type === 'binary' ||
+ fieldsInfo[fieldName].options.isPassword || !_.isEmpty(fieldInfo.depends)) {
+ return false;
+ }
+ return {
+ name: fieldName,
+ string: fieldInfo.string,
+ value: value,
+ displayed: displayed,
+ };
+ })
+ .compact()
+ .sortBy(function (field) { return field.string; })
+ .value();
+
+ var conditions = _.chain(fieldNamesInView)
+ .filter(function (fieldName) {
+ var fieldInfo = fields[fieldName];
+ return fieldInfo.change_default;
+ })
+ .map(function (fieldName) {
+ var fieldInfo = fields[fieldName];
+ var valueDisplayed = display(fieldInfo, fieldsValues[fieldName]);
+ var value = valueDisplayed[0];
+ var displayed = valueDisplayed[1];
+ return {
+ name: fieldName,
+ string: fieldInfo.string,
+ value: value,
+ displayed: displayed,
+ };
+ })
+ .value();
+ var d = new Dialog(this, {
+ title: _t("Set Default"),
+ buttons: [
+ {text: _t("Close"), close: true},
+ {text: _t("Save default"), click: function () {
+ var $defaults = d.$el.find('#formview_default_fields');
+ var fieldToSet = $defaults.val();
+ if (!fieldToSet) {
+ $defaults.parent().addClass('o_form_invalid');
+ return;
+ }
+ var selfUser = d.$el.find('#formview_default_self').is(':checked');
+ var condition = d.$el.find('#formview_default_conditions').val();
+ var value = _.find(self.fields, function (field) {
+ return field.name === fieldToSet;
+ }).value;
+ self._rpc({
+ model: 'ir.default',
+ method: 'set',
+ args: [
+ self._action.res_model,
+ fieldToSet,
+ value,
+ selfUser,
+ true,
+ condition || false,
+ ],
+ }).then(function () { d.close(); });
+ }}
+ ]
+ });
+ d.args = {
+ fields: this.fields,
+ conditions: conditions,
+ };
+ d.template = 'FormView.set_default';
+ d.open();
+ },
+ fvg: function() {
+ var self = this;
+ var dialog = new Dialog(this, { title: _t("Fields View Get") });
+ dialog.opened().then(function () {
+ $('').text(utils.json_node_to_xml(
+ self._controller.renderer.arch, true)
+ ).appendTo(dialog.$el);
+ });
+ dialog.open();
+ },
+});
+function make_context(width, height, fn) {
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ // make e.layerX/e.layerY imitate e.offsetX/e.offsetY.
+ canvas.style.position = 'relative';
+ var ctx = canvas.getContext('2d');
+ ctx.imageSmoothingEnabled = false;
+ ctx.mozImageSmoothingEnabled = false;
+ ctx.oImageSmoothingEnabled = false;
+ ctx.webkitImageSmoothingEnabled = false;
+ fn && fn(ctx);
+ return ctx;
+}
+var RequestsOverlay = Widget.extend({
+ template: 'WebClient.DebugManager.RequestsOverlay',
+ TRACKS: 8,
+ TRACK_WIDTH: 9,
+ events: {
+ mousemove: function (e) {
+ this.$tooltip.hide();
+ }
+ },
+ init: function () {
+ this._super.apply(this, arguments);
+ this._render = _.throttle(
+ this._render.bind(this),
+ 1000/15, {leading: false}
+ );
+ },
+ start: function () {
+ var _super = this._super();
+ this.$tooltip = this.$('div.o_debug_tooltip');
+ this.getParent().on('update-stats', this, this._render);
+ this._render();
+ return _super;
+ },
+ tooltip: function (text, start, end, x, y) {
+ // x and y are hit point with respect to the viewport. To know where
+ // this hit point is with respect to the overlay, subtract the offset
+ // between viewport and overlay, then add scroll factor of overlay
+ // (which isn't taken in account by the viewport).
+ //
+ // Normally the viewport overlay should sum offsets of all
+ // offsetParents until we reach `null` but in this case the overlay
+ // should have been added directly to the body, which should have an
+ // offset of 0.
+
+ var top = y - this.el.offsetTop + this.el.scrollTop + 1;
+ var left = x - this.el.offsetLeft + this.el.scrollLeft + 1;
+ this.$tooltip.css({top: top, left: left}).show()[0].innerHTML = ['', text, ' (', (end - start), 'ms)', '
'].join('');
+ },
+
+ _render: function () {
+ var $summary = this.$('header'),
+ w = $summary[0].clientWidth,
+ $requests = this.$('.o_debug_requests');
+ $summary.find('canvas').attr('width', w);
+ var tracks = document.getElementById('o_debug_requests_summary');
+
+ _.invoke(this.getChildren(), 'destroy');
+
+ var requests = this.getParent()._events;
+ var bounds = this._get_bounds(requests);
+ // horizontal scaling factor for summary
+ var scale = w / (bounds.high - bounds.low);
+
+ // store end-time of "current" requests, to find out which track a
+ // request should go in, just look for the first track whose end-time
+ // is smaller than the new request's start time.
+ var track_ends = _(this.TRACKS).times(_.constant(-Infinity));
+
+ var ctx = tracks.getContext('2d');
+ ctx.lineWidth = this.TRACK_WIDTH;
+ for (var i = 0; i < requests.length; i++) {
+ var request = requests[i];
+ // FIXME: is it certain that events in the request are sorted by timestamp?
+ var rstart = Math.floor(request[0][3] * 1e3);
+ var rend = Math.ceil(request[request.length - 1][3] * 1e3);
+ // find free track for current request
+ for(var track=0; track < track_ends.length; ++track) {
+ if (track_ends[track] < rstart) { break; }
+ }
+ // FIXME: display error message of some sort? Re-render with larger area? Something?
+ if (track >= track_ends.length) {
+ console.warn("could not find an empty summary track");
+ continue;
+ }
+ // set new track end
+ track_ends[track] = rend;
+ ctx.save();
+ ctx.translate(Math.floor((rstart - bounds.low) * scale), track * (this.TRACK_WIDTH + 1));
+ this._draw_request(request, ctx, 0, scale);
+ ctx.restore();
+ new RequestDetails(this, request, scale).appendTo($requests);
+ }
+ },
+ _draw_request: function (request, to_context, step, hscale, handle_event) {
+ // have one draw surface for each event type:
+ // * no need to alter context from one event to the next, each surface
+ // gets its own color for all its lifetime
+ // * surfaces can be blended in a specified order, which means events
+ // can be drawn in any order, no need to care about z-index while
+ // serializing events to the surfaces
+ var surfaces = {
+ request: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'blue';
+ ctx.fillStyle = '#88f';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ }),
+ //func: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ // ctx.strokeStyle = 'gray';
+ // ctx.lineWidth = to_context.lineWidth;
+ // ctx.translate(0, initial_offset);
+ //}),
+ sql: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'red';
+ ctx.fillStyle = '#f88';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ }),
+ template: make_context(to_context.canvas.width, to_context.canvas.height, function (ctx) {
+ ctx.strokeStyle = 'green';
+ ctx.fillStyle = '#8f8';
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 1;
+ })
+ };
+ // apply scaling manually so zooming in improves display precision
+ var stacks = {}, start = Math.floor(request[0][3] * 1e3 * hscale);
+ var event_idx = 0;
+
+ var rect_width = to_context.lineWidth;
+ for (var i = 0; i < request.length; i++) {
+ var type, m, event = request[i];
+ var tag = event[0], timestamp = Math.floor(event[3] * 1e3 * hscale) - start;
+
+ if (m = /(\w+)-start/.exec(tag)) {
+ type = m[1];
+ if (!(type in stacks)) { stacks[type] = []; }
+ handle_event && handle_event(event_idx, timestamp, event);
+ stacks[type].push({
+ timestamp: timestamp,
+ idx: event_idx++
+ });
+ } else if (m = /(\w+)-end/.exec(tag)) {
+ type = m[1];
+ var stack = stacks[type];
+ var estart = stack.pop(), duration = Math.ceil(timestamp - estart.timestamp);
+ handle_event && handle_event(estart.idx, timestamp, event);
+
+ var surface = surfaces[type];
+ if (!surface) { continue; } // FIXME: support for unknown event types
+
+ var y = step * estart.idx;
+ // path rectangle for the current event on the relevant surface
+ surface.rect(estart.timestamp + 0.5, y + 0.5, duration || 1, rect_width);
+ }
+ }
+ // add each layer to the main canvas
+ var keys = ['request', /*'func', */'template', 'sql'];
+ for (var j = 0; j < keys.length; ++j) {
+ // stroke and fill all rectangles for the relevant surface/context
+ var ctx = surfaces[keys[j]];
+ ctx.fill();
+ ctx.stroke();
+ to_context.drawImage(ctx.canvas, 0, 0);
+ }
+ },
+ /**
+ * Returns first and last events in milliseconds
+ *
+ * @param requests
+ * @returns {{low: number, high: number}}
+ * @private
+ */
+ _get_bounds: function (requests) {
+ var low = +Infinity;
+ var high =-+Infinity;
+
+ for (var i = 0; i < requests.length; i++) {
+ var request = requests[i];
+ for (var j = 0; j < request.length; j++) {
+ var event = request[j];
+ var timestamp = event[3];
+ low = Math.min(low, timestamp);
+ high = Math.max(high, timestamp);
+ }
+ }
+ return {low: Math.floor(low * 1e3), high: Math.ceil(high * 1e3)};
+ }
+});
+var RequestDetails = Widget.extend({
+ events: {
+ click: function () {
+ this._open = !this._open;
+ this.render();
+ },
+ 'mousemove canvas': function (e) {
+ e.stopPropagation();
+ var y = e.y || e.offsetY || e.layerY;
+ if (!y) { return; }
+ var event = this._payloads[Math.floor(y / this._REQ_HEIGHT)];
+ if (!event) { return; }
+
+ this.getParent().tooltip(event.payload, event.start, event.stop, e.clientX, e.clientY);
+ }
+ },
+ init: function (parent, request, scale) {
+ this._super.apply(this, arguments);
+ this._request = request;
+ this._open = false;
+ this._scale = scale;
+ this._REQ_HEIGHT = 20;
+ },
+ start: function () {
+ this.el.style.borderBottom = '1px solid black';
+ this.render();
+ return this._super();
+ },
+ render: function () {
+ var request_cell_height = this._REQ_HEIGHT, TITLE_WIDTH = 200;
+ var request = this._request;
+ var req_start = request[0][3] * 1e3;
+ var req_duration = request[request.length - 1][3] * 1e3 - req_start;
+ var height = request_cell_height * (this._open ? request.length / 2 : 1);
+ var cell_center = request_cell_height / 2;
+ var ctx = make_context(210 + Math.ceil(req_duration * this._scale), height, function (ctx) {
+ ctx.lineWidth = cell_center;
+ });
+ this.$el.empty().append(ctx.canvas);
+ var payloads = this._payloads = [];
+
+ // lazy version: if the render is single-line (!this._open), the extra
+ // content will be discarded when the text canvas gets pasted onto the
+ // main canvas. An improvement would be to not do text rendering
+ // beyond the first event for "closed" requests events… then again
+ // that makes for more regular rendering profile?
+ var text_ctx = make_context(TITLE_WIDTH, height, function (ctx) {
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'right';
+ ctx.textBaseline = 'middle';
+ ctx.translate(0, cell_center);
+ });
+
+ ctx.save();
+ ctx.translate(TITLE_WIDTH + 10, ((request_cell_height/4)|0));
+
+ this.getParent()._draw_request(request, ctx, this._open ? request_cell_height : 0, this._scale, function (idx, timestamp, event) {
+ if (/-start$/g.test(event[0])) {
+ payloads.push({
+ payload: event[2],
+ start: timestamp,
+ stop: null
+ });
+
+ // we want ~200px wide, assume the average character is at
+ // least 4px wide => there can be *at most* 49 characters
+ var title = event[2];
+ title = title.replace(/\s+$/, '');
+ title = title.length <= 50 ? title : ('…' + title.slice(-49));
+ while (text_ctx.measureText(title).width > 200) {
+ title = '…' + title.slice(2);
+ }
+ text_ctx.fillText(title, TITLE_WIDTH, request_cell_height * idx);
+ } else if (/-end$/g.test(event[0])) {
+ payloads[idx].stop = timestamp;
+ }
+ });
+ ctx.restore();
+ // add the text layer to the main canvas
+ ctx.drawImage(text_ctx.canvas, 0, 0);
+ }
+});
+
+if (config.isDebug()) {
+ SystrayMenu.Items.push(DebugManager);
+
+ WebClient.include({
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ current_action_updated: function (action, controller) {
+ this._super.apply(this, arguments);
+ var debugManager = _.find(this.menu.systray_menu.widgets, function(item) {
+ return item instanceof DebugManager;
+ });
+ debugManager.update('action', action, controller && controller.widget);
+ },
+ });
+
+ ActionManager.include({
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * Returns the action of the controller currently opened in a dialog,
+ * i.e. a target='new' action, if any.
+ *
+ * @returns {Object|null}
+ */
+ getCurrentActionInDialog: function () {
+ if (this.currentDialogController) {
+ return this.actions[this.currentDialogController.actionID];
+ }
+ return null;
+ },
+ /**
+ * Returns the controller currently opened in a dialog, if any.
+ *
+ * @returns {Object|null}
+ */
+ getCurrentControllerInDialog: function () {
+ return this.currentDialogController;
+ },
+ });
+
+ Dialog.include({
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ open: function() {
+ var self = this;
+ // if the dialog is opened by the ActionManager, instantiate a
+ // DebugManager and insert it into the DOM once the dialog is opened
+ // (delay this with a setTimeout(0) to ensure that the internal
+ // state, i.e. the current action and controller, of the
+ // ActionManager is set to properly update the DebugManager)
+ this.opened(function() {
+ setTimeout(function () {
+ var parent = self.getParent();
+ if (parent instanceof ActionManager) {
+ var action = parent.getCurrentActionInDialog();
+ if (action) {
+ var controller = parent.getCurrentControllerInDialog();
+ self.debugManager = new DebugManager(self);
+ var $header = self.$modal.find('.modal-header:first');
+ return self.debugManager.prependTo($header).then(function () {
+ self.debugManager.update('action', action, controller.widget);
+ });
+ }
+ }
+ }, 0);
+ });
+
+ return this._super.apply(this, arguments);
+ },
+ });
+}
+
+return DebugManager;
+
+});
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index 24182fa1a16..6ffe35574bd 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -235,7 +235,7 @@ var BasicRenderer = AbstractRenderer.extend({
$node.tooltip({
title: function () {
return qweb.render('WidgetLabel.tooltip', {
- debug: config.debug,
+ debug: config.isDebug(),
widget: widget,
});
}
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index c66b7bcc665..192f8da21fe 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -187,7 +187,7 @@ return AbstractRenderer.extend({
this.color_map = {};
if (params.eventTemplate) {
- this.qweb = new QWeb(session.debug, {_s: session.origin});
+ this.qweb = new QWeb(config.isDebug(), {_s: session.origin});
this.qweb.add_template(utils.json_node_to_xml(params.eventTemplate));
}
},
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index e2249883f96..71cf5ecbd07 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -310,7 +310,7 @@ var FormRenderer = BasicRenderer.extend({
$button.tooltip({
title: function () {
return qweb.render('WidgetButton.tooltip', {
- debug: config.debug,
+ debug: config.isDebug(),
state: self.state,
node: node,
});
@@ -491,7 +491,7 @@ var FormRenderer = BasicRenderer.extend({
this._registerModifiers(node, this.state, $button);
// Display tooltip
- if (config.debug || node.attrs.help) {
+ if (config.isDebug() || node.attrs.help) {
this._addButtonTooltip(node, $button);
}
return $button;
@@ -727,7 +727,7 @@ var FormRenderer = BasicRenderer.extend({
this._registerModifiers(node, this.state, $button);
// Display tooltip
- if (config.debug || node.attrs.help) {
+ if (config.isDebug() || node.attrs.help) {
this._addButtonTooltip(node, $button);
}
@@ -998,7 +998,7 @@ var FormRenderer = BasicRenderer.extend({
var $widgets = self.$('.o_field_widget[name=' + widget.name + ']');
var $label = idForLabel ? self.$('.o_form_label[for=' + idForLabel + ']') : $();
$label = $label.eq($widgets.index(widget.$el));
- if (config.debug || widget.attrs.help || widget.field.help) {
+ if (config.isDebug() || widget.attrs.help || widget.field.help) {
self._addFieldTooltip(widget, $label);
}
if (widget.attrs.widget === 'upgrade_boolean') {
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 70afa09c066..b643b8355b6 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -245,7 +245,7 @@ var KanbanRecord = Widget.extend({
if (Widget) {
widget = self._processWidget($field, field_name, Widget);
self.subWidgets[field_name] = widget;
- } else if (config.debug) {
+ } else if (config.isDebug()) {
// the widget is not implemented
$field.replaceWith($('', {
text: _.str.sprintf(_t('[No widget %s]'), field_widget),
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index 9e86c857085..bd6c00a6802 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -3,6 +3,7 @@ odoo.define('web.KanbanRenderer', function (require) {
var BasicRenderer = require('web.BasicRenderer');
var ColumnQuickCreate = require('web.kanban_column_quick_create');
+var config = require('web.config');
var core = require('web.core');
var KanbanColumn = require('web.KanbanColumn');
var KanbanRecord = require('web.KanbanRecord');
@@ -107,7 +108,7 @@ var KanbanRenderer = BasicRenderer.extend({
this._super.apply(this, arguments);
this.widgets = [];
- this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
+ this.qweb = new QWeb(config.isDebug(), {_s: session.origin}, false);
var templates = findInNode(this.arch, function (n) { return n.tag === 'templates';});
transformQwebTemplate(templates, state.fields);
this.qweb.add_template(utils.json_node_to_xml(templates));
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index f4468b38695..630cca9da91 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -309,7 +309,7 @@ var ListRenderer = BasicRenderer.extend({
return _.map(this.columns, function (column) {
var $cell = $('');
- if (config.debug) {
+ if (config.isDebug()) {
$cell.addClass(column.attrs.name);
}
if (column.attrs.name in aggregateValues) {
@@ -742,7 +742,7 @@ var ListRenderer = BasicRenderer.extend({
$th.css({ textAlign: 'right' });
}
- if (config.debug) {
+ if (config.isDebug()) {
var fieldDescr = {
field: field,
name: name,
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js
index a913fcd0ea2..6903e7fea33 100644
--- a/addons/web/static/src/js/widgets/data_export.js
+++ b/addons/web/static/src/js/widgets/data_export.js
@@ -1,6 +1,7 @@
odoo.define('web.DataExport', function (require) {
"use strict";
+var config = require('web.config');
var core = require('web.core');
var crash_manager = require('web.crash_manager');
var Dialog = require('web.Dialog');
@@ -240,11 +241,11 @@ var DataExport = Dialog.extend({
.find('.o_expand_parent')
.toggleClass('fa-chevron-right fa-chevron-down')
.next()
- .after(QWeb.render('Export.TreeItems', {fields: records, debug: this.getSession().debug}));
+ .after(QWeb.render('Export.TreeItems', {fields: records, debug: config.isDebug()}));
} else {
this.$('.o_left_field_panel').empty().append(
$('').addClass('o_field_tree_structure')
- .append(QWeb.render('Export.TreeItems', {fields: records, debug: this.getSession().debug}))
+ .append(QWeb.render('Export.TreeItems', {fields: records, debug: config.isDebug()}))
);
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index e46c256b851..e34d017b1a4 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -218,109 +218,6 @@
-
-
-
-
-
-
-
-
-
- Run JS Tests
- Run JS Mobile Tests
- Run Click Everywhere Test
- Open View
-
-
- Toggle Timelines
- Clear Events
-
-
- Activate Assets Debugging
- Regenerate Assets Bundles
- Become Superuser
- Leave the Developer Tools
-
-
-
-
- Edit Action
-
- View Fields
- Manage Filters
- Technical Translation
-
-
-
-
-
-
-
- Set Defaults
-
- View Metadata
- Manage Attachments
-
-
- Fields View Get
-
-
- Edit View:
-
- Edit ControlPanelView
-
-
-
-
-
-
- ID:
-
-
-
- XML ID:
-
-
-
- No Update:
-
-
-
- (change)
-
-
-
-
- Creation User:
-
-
-
- Creation Date:
-
-
-
- Latest Modification by:
-
-
-
- Latest Modification Date:
-
-
-
-
-
-
diff --git a/addons/web/static/src/xml/debug.xml b/addons/web/static/src/xml/debug.xml
new file mode 100644
index 00000000000..ff4613f26b6
--- /dev/null
+++ b/addons/web/static/src/xml/debug.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Activate Assets Debugging
+ Activate Tests Assets Debugging
+ Regenerate Assets Bundles
+ Become Superuser
+ Leave the Developer Tools
+
+
+ Run JS Tests
+ Run JS Mobile Tests
+ Run Click Everywhere Test
+ Open View
+
+
+ Toggle Timelines
+ Clear Events
+
+
+
+
+
+
+ Edit Action
+
+ View Fields
+ Manage Filters
+ Technical Translation
+
+
+
+
+
+
+
+ Set Defaults
+
+ View Metadata
+ Manage Attachments
+
+
+ Fields View Get
+
+
+ Edit View:
+
+ Edit ControlPanelView
+
+
+
+
+
+
+ ID:
+
+
+
+ XML ID:
+
+
+
+ No Update:
+
+
+
+ (change)
+
+
+
+
+ Creation User:
+
+
+
+ Creation Date:
+
+
+
+ Latest Modification by:
+
+
+
+ Latest Modification Date:
+
+
+
+
+
+
+
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index f1c67284707..ef890e92e90 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -14,7 +14,7 @@ var ActionManager = require('web.ActionManager');
var config = require('web.config');
var ControlPanelView = require('web.ControlPanelView');
var concurrency = require('web.concurrency');
-var DebugManager = require('web.DebugManager');
+var DebugManager = require('web.DebugManager.Backend');
var dom = require('web.dom');
var testUtilsMock = require('web.test_utils_mock');
var Widget = require('web.Widget');
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index ccb4d20959e..0d452ddd195 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -173,7 +173,7 @@ function addMockEnvironment(widget, params) {
_.extend(config.device, params.config.device);
}
if ('debug' in params.config) {
- config.debug = params.config.debug;
+ odoo.debug = params.config.debug;
}
}
if ('translateParameters' in params) {
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 7a357d9b345..4d8b751cc1a 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -3,7 +3,6 @@ odoo.define('web.form_tests', function (require) {
var BasicModel = require('web.BasicModel');
var concurrency = require('web.concurrency');
-var config = require('web.config');
var core = require('web.core');
var fieldRegistry = require('web.field_registry');
var FormView = require('web.FormView');
@@ -6576,8 +6575,8 @@ QUnit.module('Views', {
QUnit.test('display tooltips for buttons', async function (assert) {
assert.expect(2);
- var initialDebugMode = config.debug;
- config.debug = true;
+ var initialDebugMode = odoo.debug;
+ odoo.debug = true;
var form = await createView({
View: FormView,
@@ -6607,7 +6606,7 @@ QUnit.module('Views', {
"should have rendered a tooltip");
$secondButton.trigger($.Event('mouseleave'));
- config.debug = initialDebugMode;
+ odoo.debug = initialDebugMode;
form.destroy();
});
@@ -6936,8 +6935,8 @@ QUnit.module('Views', {
QUnit.test('proper stringification in debug mode tooltip', async function (assert) {
assert.expect(6);
- var initialDebugMode = config.debug;
- config.debug = true;
+ var initialDebugMode = odoo.debug;
+ odoo.debug = true;
var form = await createView({
View: FormView,
@@ -6970,7 +6969,7 @@ QUnit.module('Views', {
assert.strictEqual($('.oe_tooltip_technical>li[data-item="widget"]')[0].lastChild.wholeText.trim(),
'Many2one (many2one)', "widget description should be correct");
- config.debug = initialDebugMode;
+ odoo.debug = initialDebugMode;
form.destroy();
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index fa62c6f3a25..ddf340f62df 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -3,7 +3,6 @@ odoo.define('web.list_tests', function (require) {
var AbstractStorageService = require('web.AbstractStorageService');
var BasicModel = require('web.BasicModel');
-var config = require('web.config');
var core = require('web.core');
var basicFields = require('web.basic_fields');
var FormView = require('web.FormView');
@@ -1793,8 +1792,8 @@ QUnit.module('Views', {
QUnit.test('display a tooltip on a field', async function (assert) {
assert.expect(4);
- var initialDebugMode = config.debug;
- config.debug = false;
+ var initialDebugMode = odoo.debug;
+ odoo.debug = false;
var list = await createView({
View: ListView,
@@ -1813,7 +1812,7 @@ QUnit.module('Views', {
list.$('th[data-name=foo]').trigger($.Event('mouseenter'));
assert.strictEqual($('.tooltip .oe_tooltip_string').length, 0, "should not have rendered a tooltip");
- config.debug = true;
+ odoo.debug = true;
// it is necessary to rerender the list so tooltips can be properly created
await list.reload();
list.$('th[data-name=foo]').tooltip('show', false);
@@ -1828,7 +1827,7 @@ QUnit.module('Views', {
assert.strictEqual($('.oe_tooltip_technical>li[data-item="widget"]')[0].lastChild.wholeText.trim(),
'Button (toggle_button)', "widget description should be correct");
- config.debug = initialDebugMode;
+ odoo.debug = initialDebugMode;
list.destroy();
});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 5e322ad2ac9..5bc34aad93f 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -154,6 +154,8 @@
+
+
@@ -248,8 +250,8 @@
-
+
@@ -387,6 +389,14 @@
+
+
+
+
+
+
+
+
<!DOCTYPE html>
@@ -400,6 +410,7 @@
@@ -422,6 +433,7 @@
+
@@ -441,6 +453,7 @@
+
Copyright © Company name
@@ -488,7 +501,7 @@
-
-
+
@@ -996,7 +995,7 @@
-
+
@@ -1123,7 +1122,7 @@
-
+
diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/tours/website_blog.js
similarity index 100%
rename from addons/website_blog/static/src/js/website.tour.blog.js
rename to addons/website_blog/static/src/js/tours/website_blog.js
diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml
index cd5534f29c5..d9a22c4b3f4 100644
--- a/addons/website_blog/views/website_blog_templates.xml
+++ b/addons/website_blog/views/website_blog_templates.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/addons/website_crm/static/src/js/website_crm_tour.js b/addons/website_crm/static/tests/tours/website_crm.js
similarity index 100%
rename from addons/website_crm/static/src/js/website_crm_tour.js
rename to addons/website_crm/static/tests/tours/website_crm.js
diff --git a/addons/website_crm/views/website_crm_templates.xml b/addons/website_crm/views/website_crm_templates.xml
index 20ad233996e..688ed312cb2 100644
--- a/addons/website_crm/views/website_crm_templates.xml
+++ b/addons/website_crm/views/website_crm_templates.xml
@@ -57,9 +57,9 @@
-
+
-
+
diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/tours/website_event.js
similarity index 100%
rename from addons/website_event/static/src/js/website.tour.event.js
rename to addons/website_event/static/src/js/tours/website_event.js
diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml
index 374faf60552..4231efb2907 100644
--- a/addons/website_event/views/event_templates.xml
+++ b/addons/website_event/views/event_templates.xml
@@ -14,7 +14,7 @@
-
+
diff --git a/addons/website_event_sale/static/src/js/website.tour.event_sale.js b/addons/website_event_sale/static/tests/tours/website_event_sale.js
similarity index 100%
rename from addons/website_event_sale/static/src/js/website.tour.event_sale.js
rename to addons/website_event_sale/static/tests/tours/website_event_sale.js
diff --git a/addons/website_event_sale/views/event_templates.xml b/addons/website_event_sale/views/event_templates.xml
index 86740e22719..c43cd83e6ac 100644
--- a/addons/website_event_sale/views/event_templates.xml
+++ b/addons/website_event_sale/views/event_templates.xml
@@ -1,10 +1,10 @@
-
-
-
-
+
+
+
+
diff --git a/addons/website_form/static/src/js/website_form_editor_tour.js b/addons/website_form/static/tests/tours/website_form_editor.js
similarity index 100%
rename from addons/website_form/static/src/js/website_form_editor_tour.js
rename to addons/website_form/static/tests/tours/website_form_editor.js
diff --git a/addons/website_form/views/assets.xml b/addons/website_form/views/assets.xml
index 0c463b6151c..a9615d1ae27 100644
--- a/addons/website_form/views/assets.xml
+++ b/addons/website_form/views/assets.xml
@@ -6,7 +6,6 @@
-
@@ -17,5 +16,10 @@
+
+
+
+
+
diff --git a/addons/website_forum/static/src/js/website_tour_forum.js b/addons/website_forum/static/src/js/tours/website_forum.js
similarity index 100%
rename from addons/website_forum/static/src/js/website_tour_forum.js
rename to addons/website_forum/static/src/js/tours/website_forum.js
diff --git a/addons/website_forum/static/src/js/website_tour_forum_question.js b/addons/website_forum/static/tests/tours/website_forum_question.js
similarity index 100%
rename from addons/website_forum/static/src/js/website_tour_forum_question.js
rename to addons/website_forum/static/tests/tours/website_forum_question.js
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 269e766e912..24fdc26feb3 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -5,11 +5,17 @@
-
+
+
+
+
+
+
+
@@ -17,7 +23,6 @@
-
diff --git a/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js b/addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js
similarity index 100%
rename from addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js
rename to addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js
diff --git a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
index a948f511121..67a57615f42 100644
--- a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
+++ b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
@@ -549,9 +549,9 @@
-
+
-
+
diff --git a/addons/website_links/static/src/js/test_ui_flow.js b/addons/website_links/static/tests/tours/website_links.js
similarity index 100%
rename from addons/website_links/static/src/js/test_ui_flow.js
rename to addons/website_links/static/tests/tours/website_links.js
diff --git a/addons/website_links/views/website_links_template.xml b/addons/website_links/views/website_links_template.xml
index 626ec232e53..63d5606092c 100644
--- a/addons/website_links/views/website_links_template.xml
+++ b/addons/website_links/views/website_links_template.xml
@@ -6,11 +6,16 @@
-
+
+
+
+
+
+
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop.js b/addons/website_sale/static/src/js/tours/website_sale_shop.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop.js
rename to addons/website_sale/static/src/js/tours/website_sale_shop.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_backend.js b/addons/website_sale/static/src/js/tours/website_sale_shop_backend.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_backend.js
rename to addons/website_sale/static/src/js/tours/website_sale_shop_backend.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_frontend.js b/addons/website_sale/static/src/js/tours/website_sale_shop_frontend.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_frontend.js
rename to addons/website_sale/static/src/js/tours/website_sale_shop_frontend.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_buy.js b/addons/website_sale/static/tests/tours/website_sale_buy.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_buy.js
rename to addons/website_sale/static/tests/tours/website_sale_buy.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour.js b/addons/website_sale/static/tests/tours/website_sale_complete_flow.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour.js
rename to addons/website_sale/static/tests/tours/website_sale_complete_flow.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_cart_recovery.js b/addons/website_sale/static/tests/tours/website_sale_shop_cart_recovery.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_cart_recovery.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_cart_recovery.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js b/addons/website_sale/static/tests/tours/website_sale_shop_custom_attribute_value.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_custom_attribute_value.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_customize.js b/addons/website_sale/static/tests/tours/website_sale_shop_customize.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_customize.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_customize.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_deleted_archived_variants.js b/addons/website_sale/static/tests/tours/website_sale_shop_deleted_archived_variants.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_deleted_archived_variants.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_deleted_archived_variants.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_dynamic_variants.js b/addons/website_sale/static/tests/tours/website_sale_shop_dynamic_variants.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_dynamic_variants.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_dynamic_variants.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_list_view_b2c.js b/addons/website_sale/static/tests/tours/website_sale_shop_list_view_b2c.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_list_view_b2c.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_list_view_b2c.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_mail.js b/addons/website_sale/static/tests/tours/website_sale_shop_mail.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_mail.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_mail.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_no_variant_attribute.js b/addons/website_sale/static/tests/tours/website_sale_shop_no_variant_attribute.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_no_variant_attribute.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_no_variant_attribute.js
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_zoom.js b/addons/website_sale/static/tests/tours/website_sale_shop_zoom.js
similarity index 100%
rename from addons/website_sale/static/src/js/website_sale_tour_shop_zoom.js
rename to addons/website_sale/static/tests/tours/website_sale_shop_zoom.js
diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py
index 8f1179bedb2..59af832fc74 100644
--- a/addons/website_sale/tests/test_website_sale_pricelist.py
+++ b/addons/website_sale/tests/test_website_sale_pricelist.py
@@ -6,6 +6,7 @@ except ImportError:
from mock import patch
from odoo.tests import tagged
from odoo.tests.common import HttpCase, TransactionCase
+from odoo.tools import DotDict
''' /!\/!\
Calling `get_pricelist_available` after setting `property_product_pricelist` on
@@ -157,13 +158,6 @@ def simulate_frontend_context(self, website_id=1):
self.addCleanup(patcher.stop)
-class DotDict(dict):
- """dot.notation access to dictionary attributes"""
- def __getattr__(*args):
- val = dict.get(*args)
- return DotDict(val) if type(val) is dict else val
-
-
@tagged('post_install', '-at_install')
class TestWebsitePriceListAvailable(TransactionCase):
# This is enough to avoid a mock (request.session/website do not exist during test)
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 0182725b451..cff60a50803 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -12,7 +12,7 @@
-
+
@@ -31,16 +31,7 @@
-
-
-
-
-
-
-
-
-
@@ -50,16 +41,30 @@
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_sale_coupon/static/src/js/website_sale_coupon.test.js b/addons/website_sale_coupon/static/tests/tours/website_sale_coupon.js
similarity index 100%
rename from addons/website_sale_coupon/static/src/js/website_sale_coupon.test.js
rename to addons/website_sale_coupon/static/tests/tours/website_sale_coupon.js
diff --git a/addons/website_sale_coupon/views/website_sale_templates.xml b/addons/website_sale_coupon/views/website_sale_templates.xml
index fa02669ca53..db123dcc504 100644
--- a/addons/website_sale_coupon/views/website_sale_templates.xml
+++ b/addons/website_sale_coupon/views/website_sale_templates.xml
@@ -1,9 +1,9 @@
-
+
-
+
diff --git a/addons/website_sale_product_configurator/static/src/js/website_sale_tour_buy.js b/addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js
similarity index 100%
rename from addons/website_sale_product_configurator/static/src/js/website_sale_tour_buy.js
rename to addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js
diff --git a/addons/website_sale_product_configurator/static/src/js/website_sale_tour_shop_custom_attributes_value.js b/addons/website_sale_product_configurator/static/tests/tours/website_sale_shop_custom_attributes_value.js
similarity index 100%
rename from addons/website_sale_product_configurator/static/src/js/website_sale_tour_shop_custom_attributes_value.js
rename to addons/website_sale_product_configurator/static/tests/tours/website_sale_shop_custom_attributes_value.js
diff --git a/addons/website_sale_product_configurator/views/assets.xml b/addons/website_sale_product_configurator/views/assets.xml
index e6344530e53..95aa13a2dce 100644
--- a/addons/website_sale_product_configurator/views/assets.xml
+++ b/addons/website_sale_product_configurator/views/assets.xml
@@ -11,8 +11,12 @@
-
-
+
+
+
+
+
+
diff --git a/addons/website_sale_wishlist/static/src/js/website_sale_tour_wishlist.js b/addons/website_sale_wishlist/static/tests/tours/website_sale_wishlist.js
similarity index 100%
rename from addons/website_sale_wishlist/static/src/js/website_sale_tour_wishlist.js
rename to addons/website_sale_wishlist/static/tests/tours/website_sale_wishlist.js
diff --git a/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml b/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml
index 76660917f4f..d8f43248403 100644
--- a/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml
+++ b/addons/website_sale_wishlist/views/website_sale_wishlist_template.xml
@@ -5,7 +5,12 @@
-
+
+
+
+
+
+
diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml
index 38d28900d01..d612215cc0a 100644
--- a/addons/website_slides/views/website_slides_templates_course.xml
+++ b/addons/website_slides/views/website_slides_templates_course.xml
@@ -442,7 +442,7 @@
-
@@ -451,7 +451,7 @@
-
@@ -474,11 +474,11 @@
diff --git a/addons/website_slides/views/website_slides_templates_homepage.xml b/addons/website_slides/views/website_slides_templates_homepage.xml
index 0bcf0c0d189..9df61fe9c44 100644
--- a/addons/website_slides/views/website_slides_templates_homepage.xml
+++ b/addons/website_slides/views/website_slides_templates_homepage.xml
@@ -177,11 +177,11 @@