[ADD] openerp,web,web_tour: new web_tour addon
Join work with ged
This commit is contained in:
@@ -291,7 +291,7 @@
|
||||
<t t-foreach="menu_data['children']" t-as="menu">
|
||||
<div style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu['id']">
|
||||
<t t-foreach="menu['children']" t-as="menu">
|
||||
<div class="oe_secondary_menu_section">
|
||||
<div class="oe_secondary_menu_section" t-att-data-menu-xmlid="menu.get('xmlid')">
|
||||
<t t-if="menu['children']"><t t-esc="menu['name']"/></t>
|
||||
<t t-if="not menu['children']"><t t-call="web.menu_link"/></t>
|
||||
</div>
|
||||
@@ -317,6 +317,7 @@
|
||||
<a t-att-href="'/web%s#menu_id=%s&action=%s' % (debug_param, menu['id'], menu['action'] and menu['action'].split(',')[1] or '')"
|
||||
t-att-class="'oe_menu_toggler' if menu.get('children') else 'oe_menu_leaf'"
|
||||
t-att-data-menu="menu['id']"
|
||||
t-att-data-menu-xmlid="menu.get('xmlid')"
|
||||
t-att-data-action-model="menu['action'] and menu['action'].split(',')[0] or None"
|
||||
t-att-data-action-id="menu['action'] and menu['action'].split(',')[1] or None">
|
||||
<span class="oe_menu_text">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import models
|
||||
@@ -0,0 +1,20 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': 'Tours',
|
||||
'category': 'Usability',
|
||||
'description': """
|
||||
Odoo Web tours.
|
||||
========================
|
||||
|
||||
""",
|
||||
'version': '0.1',
|
||||
'depends': ['web'],
|
||||
'data': [
|
||||
'assets.xml',
|
||||
'views/tour.xml'
|
||||
],
|
||||
'qweb': [
|
||||
"static/src/xml/*.xml",
|
||||
],
|
||||
'auto_install': True
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="assets_common" name="tours assets" inherit_id="web.assets_common">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/web_tour/static/src/less/tip.less"/>
|
||||
<link rel="stylesheet" href="/web_tour/static/src/less/keyframes.less"/>
|
||||
<script type="text/javascript" src="/web_tour/static/src/js/tip.js"></script>
|
||||
<script type="text/javascript" src="/web_tour/static/src/js/tour_manager.js"></script>
|
||||
<script type="text/javascript" src="/web_tour/static/src/js/tour_service.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,3 @@
|
||||
import ir_http
|
||||
import ir_ui_menu
|
||||
import tour
|
||||
@@ -0,0 +1,13 @@
|
||||
from openerp import models
|
||||
from openerp.http import request
|
||||
|
||||
|
||||
class Http(models.Model):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
result = super(Http, self).session_info()
|
||||
if result['is_admin']:
|
||||
consumed_tours = request.env['web_tour.tour'].search([('user_id', '=', request.env.uid)])
|
||||
result['web_tours'] = [t.name for t in consumed_tours]
|
||||
return result
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class IrUiMenu(models.Model):
|
||||
_inherit = 'ir.ui.menu'
|
||||
|
||||
load_xmlid = fields.Boolean(default=False)
|
||||
|
||||
@api.model
|
||||
@tools.ormcache_context('self._uid', 'debug', keys=('lang',))
|
||||
def load_menus(self, debug):
|
||||
""" Extends load_menus to include requested xmlids """
|
||||
menu_root = super(IrUiMenu, self).load_menus(debug)
|
||||
|
||||
menu_ids = [menu.id for menu in self.browse(menu_root['all_menu_ids']) if menu.load_xmlid]
|
||||
xmlids = {
|
||||
d.res_id: d.module + "." + d.name
|
||||
for d in self.env['ir.model.data'].search([('res_id', 'in', menu_ids), ('model', '=', 'ir.ui.menu')])
|
||||
}
|
||||
|
||||
def _find_subtree(tree, node_id):
|
||||
# Returns the subtree whose id is node_id
|
||||
if tree['id'] == node_id:
|
||||
return tree
|
||||
else:
|
||||
for child in tree['children']:
|
||||
subtree = _find_subtree(child, node_id)
|
||||
if subtree:
|
||||
return subtree
|
||||
|
||||
for menu_id, menu_xmlid in xmlids.iteritems():
|
||||
_find_subtree(menu_root, menu_id)['xmlid'] = menu_xmlid
|
||||
|
||||
return menu_root
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Tour(models.Model):
|
||||
|
||||
_name = "web_tour.tour"
|
||||
_description = "Tours"
|
||||
|
||||
name = fields.Char(string="Tour name", required=True)
|
||||
user_id = fields.Many2one('res.users', string='Consumed by')
|
||||
|
||||
@api.model
|
||||
def consume(self, name):
|
||||
""" Sets tour 'name' as consumed for the current user, meaning that
|
||||
this tour won't be active anymore for that user """
|
||||
self.create({'name': name, 'user_id': self.env.uid})
|
||||
@@ -0,0 +1,77 @@
|
||||
odoo.define('web_tour.Tip', function(require) {
|
||||
"use strict";
|
||||
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
return Widget.extend({
|
||||
template: "Tip",
|
||||
init: function(parent, $anchor, info) {
|
||||
this._super(parent);
|
||||
this.$anchor = $anchor;
|
||||
this.info = _.defaults(info, {
|
||||
position: 'right',
|
||||
});
|
||||
this.consumed = false;
|
||||
},
|
||||
start: function() {
|
||||
this.$breathing = this.$('.o_breathing');
|
||||
this.$breathing.on('mouseenter', this._to_info_mode.bind(this));
|
||||
this._bind_anchor_events();
|
||||
this._reposition();
|
||||
},
|
||||
update: function($anchor) {
|
||||
if (!$anchor.is(this.$anchor)) {
|
||||
this.$anchor.off();
|
||||
this.$anchor = $anchor;
|
||||
this._bind_anchor_events();
|
||||
}
|
||||
this._reposition();
|
||||
},
|
||||
_reposition: function() {
|
||||
this.$breathing.position({
|
||||
my: this._invert_position(this.info.position),
|
||||
at: this.info.position,
|
||||
of: this.$anchor,
|
||||
collision: 'fit',
|
||||
});
|
||||
},
|
||||
_bind_anchor_events: function () {
|
||||
var self = this;
|
||||
this.$anchor.on('mouseenter', this._to_info_mode.bind(this));
|
||||
this.$anchor.on('mouseleave', this._to_bubble_mode.bind(this));
|
||||
this.$anchor.on(this.$anchor.is('input,textarea') ? 'change' : 'mousedown', function () {
|
||||
if (self.consumed) return;
|
||||
self.consumed = true;
|
||||
self.trigger('tip_consumed');
|
||||
});
|
||||
},
|
||||
_invert_position: function (position) {
|
||||
if (position === "right") return "left";
|
||||
if (position === "left") return "right";
|
||||
if (position === "bottom") return "top";
|
||||
return "bottom";
|
||||
},
|
||||
_to_info_mode: function() {
|
||||
this.$breathing.fadeOut(300);
|
||||
this.$popover = this.$popover || this.$anchor.popover({
|
||||
content: this.info.content + (this.info.extra_content || ''),
|
||||
html: true,
|
||||
animation: false,
|
||||
container: this.$el,
|
||||
placement: this.info.position,
|
||||
});
|
||||
this.$popover.popover('show');
|
||||
this.$('.popover').on('mouseleave.to_bubble_mode', this._to_bubble_mode.bind(this));
|
||||
this.$('.popover').on('click', this.trigger.bind(this, 'popover_clicked'));
|
||||
},
|
||||
_to_bubble_mode: function () {
|
||||
this.$breathing.fadeIn(300);
|
||||
if (this.$popover) {
|
||||
this.$popover.popover('hide');
|
||||
this.$('.popover').off('mouseleave.to_bubble_mode');
|
||||
this.$('.popover').off('click');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
odoo.define('web_tour.TourManager', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var local_storage = require('web.local_storage');
|
||||
var Model = require('web.Model');
|
||||
var Tip = require('web_tour.Tip');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var RUNNING_TOUR_TIMEOUT = 3000;
|
||||
|
||||
function getStepKey(name) {
|
||||
return 'tour_' + name + '_step';
|
||||
}
|
||||
function getRunningKey() {
|
||||
return 'running_tour';
|
||||
}
|
||||
|
||||
return core.Class.extend({
|
||||
init: function(consumed_tours) {
|
||||
this.active_tooltips = {};
|
||||
this.tours = {};
|
||||
this.consumed_tours = consumed_tours;
|
||||
this.running_tour = local_storage.getItem(getRunningKey());
|
||||
this.TourModel = new Model('web_tour.tour');
|
||||
},
|
||||
/**
|
||||
* Registers a tour described by the following arguments (in order)
|
||||
* @param [String] tour's name
|
||||
* @param [Object] dict of options (optional), available options are:
|
||||
* test [Boolean] true if the tour is dedicated to tests (it won't be enabled by default)
|
||||
* skip_enabled [Boolean] true to add a link to consume the whole tour in its tips
|
||||
* url [String] the url to load when manually running the tour
|
||||
* @param [Array] dict of steps, each step being a dict containing a tip description
|
||||
*/
|
||||
register: function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var last_arg = args[args.length - 1];
|
||||
var name = args[0];
|
||||
if (this.tours[name]) {
|
||||
console.warn(_.str.sprintf(_t("Tour %s is already defined"), name));
|
||||
return;
|
||||
}
|
||||
var options = args.length === 2 ? {} : args[1];
|
||||
var steps = last_arg instanceof Array ? last_arg : [last_arg];
|
||||
var tour = {
|
||||
name: name,
|
||||
current_step: parseInt(local_storage.getItem(getStepKey(name))) || 0,
|
||||
skip_enabled: options.skip_enabled,
|
||||
steps: steps,
|
||||
url: options.url,
|
||||
test: options.test,
|
||||
};
|
||||
this.tours[name] = tour;
|
||||
if (name === this.running_tour || (!tour.test && !_.contains(this.consumed_tours, name))) {
|
||||
this.active_tooltips[name] = steps[tour.current_step];
|
||||
}
|
||||
},
|
||||
run: function(tour_name) {
|
||||
if (this.running_tour) {
|
||||
console.warn(_.str.sprintf(_t("Killing tour %s"), tour_name));
|
||||
this._deactivate_tip(this.active_tooltips[tour_name]);
|
||||
this._consume_tour(tour_name);
|
||||
return;
|
||||
}
|
||||
var tour = this.tours[tour_name];
|
||||
if (!tour) {
|
||||
console.warn(_.str.sprintf(_t("Unknown Tour %s"), name));
|
||||
return;
|
||||
}
|
||||
console.log(_.str.sprintf(_t("Running tour %s"), tour_name));
|
||||
local_storage.setItem(getRunningKey(), tour_name);
|
||||
if (tour.url) {
|
||||
window.location = tour.url;
|
||||
}
|
||||
this.running_tour = tour_name;
|
||||
this.active_tooltips[tour_name] = tour.steps[0];
|
||||
this._set_running_tour_timeout(tour_name, tour.steps[0]);
|
||||
this.update();
|
||||
},
|
||||
/**
|
||||
* Checks for tooltips to activate (only from the running tour if there is one, from all
|
||||
* active tours otherwise). Should be called each time the DOM changes.
|
||||
*/
|
||||
update: function() {
|
||||
if (this.running_tour) {
|
||||
this._check_for_tooltip(this.active_tooltips[this.running_tour], this.running_tour);
|
||||
} else {
|
||||
_.each(this.active_tooltips, this._check_for_tooltip.bind(this));
|
||||
}
|
||||
},
|
||||
_check_for_tooltip: function (tip, tour_name) {
|
||||
var $trigger = $(tip.trigger).filter(':visible').first();
|
||||
var extra_trigger = tip.extra_trigger ? $(tip.extra_trigger).filter(':visible').length : true;
|
||||
var triggered = $trigger.length && extra_trigger;
|
||||
if (triggered) {
|
||||
if (!tip.widget) {
|
||||
this._activate_tip(tip, tour_name, $trigger);
|
||||
} else {
|
||||
tip.widget.update($trigger);
|
||||
}
|
||||
} else {
|
||||
this._deactivate_tip(tip);
|
||||
}
|
||||
},
|
||||
_activate_tip: function(tip, tour_name, $anchor) {
|
||||
var skip_enabled = this.tours[tour_name].skip_enabled;
|
||||
if (skip_enabled && !tip.extra_content) {
|
||||
// FIXME: not pretty but a cleaner solution will be easy when we'll stop using jquery's popover
|
||||
tip.extra_content = '<br/><span class="o_skip_tour">' + _t('Skip tour') + '</span>';
|
||||
}
|
||||
tip.widget = new Tip(this, $anchor, tip);
|
||||
tip.widget.appendTo(document.body);
|
||||
tip.widget.on('tip_consumed', this, this._consume_tip.bind(this, tip, tour_name));
|
||||
if (skip_enabled) {
|
||||
tip.widget.on('popover_clicked', this, function (event) {
|
||||
if (event.target.className === 'o_skip_tour') {
|
||||
this._deactivate_tip(tip);
|
||||
this._consume_tour(tour_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.running_tour === tour_name) {
|
||||
clearTimeout(this.running_tour_timeout);
|
||||
if (tip.run) {
|
||||
this._consume_tip(tip, tour_name);
|
||||
tip.run.apply(tip);
|
||||
}
|
||||
}
|
||||
},
|
||||
_deactivate_tip: function(tip) {
|
||||
if (tip.widget) {
|
||||
tip.widget.destroy();
|
||||
delete tip.widget;
|
||||
}
|
||||
},
|
||||
_consume_tip: function(tip, tour_name) {
|
||||
this._deactivate_tip(tip);
|
||||
var tour = this.tours[tour_name];
|
||||
if (tour.current_step < tour.steps.length - 1) {
|
||||
tour.current_step = tour.current_step + 1;
|
||||
this.active_tooltips[tour_name] = tour.steps[tour.current_step];
|
||||
local_storage.setItem(getStepKey(tour_name), tour.current_step);
|
||||
if (this.running_tour === tour_name) {
|
||||
this._set_running_tour_timeout(tour_name, this.active_tooltips[tour_name]);
|
||||
}
|
||||
} else {
|
||||
this._consume_tour(tour_name);
|
||||
}
|
||||
},
|
||||
_consume_tour: function(tour_name) {
|
||||
delete this.active_tooltips[tour_name];
|
||||
this.tours[tour_name].current_step = 0;
|
||||
local_storage.removeItem(getStepKey(tour_name));
|
||||
if (this.running_tour === tour_name) {
|
||||
local_storage.removeItem(getRunningKey());
|
||||
this.running_tour = undefined;
|
||||
clearTimeout(this.running_tour_timeout);
|
||||
} else {
|
||||
this.TourModel.call('consume', [tour_name]);
|
||||
}
|
||||
},
|
||||
_set_running_tour_timeout: function(tour_name, step) {
|
||||
if (!step.run) { return; } // don't set a timeout if the current step requires a manual action
|
||||
var self = this;
|
||||
this.running_tour_timeout = setTimeout(function() {
|
||||
console.error(_.str.sprintf(_t("Tour %s failed at step %s"), tour_name, step.trigger));
|
||||
self._consume_tour(tour_name);
|
||||
}, RUNNING_TOUR_TIMEOUT);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
odoo.define('web_tour.tour', function(require) {
|
||||
"use strict";
|
||||
|
||||
var config = require('web.config');
|
||||
var session = require('web.session');
|
||||
var TourManager = require('web_tour.TourManager');
|
||||
|
||||
if (config.device.size_class <= config.device.SIZES.XS) { return; }
|
||||
|
||||
return session.is_bound.then(function () {
|
||||
var tour = new TourManager(session.web_tours);
|
||||
|
||||
// Use a MutationObserver to detect DOM changes
|
||||
var untracked_classnames = ['o_tooltip', 'o_breathing'];
|
||||
var check_tooltip = _.throttle(function (records) {
|
||||
var update = _.find(records, function (record) {
|
||||
var record_class = record.target.className;
|
||||
return !_.isString(record_class) ||
|
||||
_.intersection(record_class.split(' '), untracked_classnames).length === 0;
|
||||
});
|
||||
if (update) { // ignore mutations in the tooltip itself
|
||||
tour.update();
|
||||
}
|
||||
}, 500, {leading: false});
|
||||
var observer = new MutationObserver(check_tooltip);
|
||||
var observe = observer.observe.bind(observer, document.body, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
// Enable the MutationObserver for the admin or if a tour is running, when the DOM is ready
|
||||
if (session.is_admin || tour.running_tour) {
|
||||
$(observe);
|
||||
}
|
||||
// Override the TourManager so that it enables/disables the observer when necessary
|
||||
if (!session.is_admin) {
|
||||
var run = tour.run;
|
||||
tour.run = function () {
|
||||
run.apply(this, arguments);
|
||||
if (this.running_tour) { observe(); }
|
||||
};
|
||||
var _consume_tour = tour._consume_tour;
|
||||
tour._consume_tour = function () {
|
||||
_consume_tour.apply(this, arguments);
|
||||
observer.disconnect();
|
||||
};
|
||||
}
|
||||
|
||||
return tour;
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
@-webkit-keyframes move-left-right {
|
||||
0% {
|
||||
.translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
.translate(5px, 0);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes move-left-right {
|
||||
0% {
|
||||
.translate(0px, 0px);
|
||||
}
|
||||
100% {
|
||||
.translate(5px, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes move-bottom-top {
|
||||
0% {
|
||||
.translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
.translate(0, 5px);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes move-bottom-top {
|
||||
0% {
|
||||
.translate(0px, 0px);
|
||||
}
|
||||
100% {
|
||||
.translate(0, 5px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
@o-tip-background-color: #875a7b;
|
||||
|
||||
.o_tooltip {
|
||||
.o_breathing {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
z-index: @zindex-modal+1;
|
||||
background-color: @o-tip-background-color;
|
||||
.animation-duration(0.5s);
|
||||
.animation-timing-function(ease-in);
|
||||
.animation-iteration-count(infinite);
|
||||
.animation-direction(alternate);
|
||||
&.right, &.left {
|
||||
.animation-name(move-left-right);
|
||||
}
|
||||
&.top, &.bottom {
|
||||
.animation-name(move-bottom-top);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
&.right::after {
|
||||
.o-position-absolute(@left: -4px, @top: 5px);
|
||||
border-right: 10px solid @o-tip-background-color;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
}
|
||||
&.top::after {
|
||||
.o-position-absolute(@bottom: -4px, @left: 5px);
|
||||
border-top: 10px solid @o-tip-background-color;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
}
|
||||
&.left::after {
|
||||
.o-position-absolute(@right: -4px, @bottom: 5px);
|
||||
border-left: 10px solid @o-tip-background-color;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
}
|
||||
&.bottom::after {
|
||||
.o-position-absolute(@top: -4px, @right: 5px);
|
||||
border-bottom: 10px solid @o-tip-background-color;
|
||||
border-right: 10px solid transparent;
|
||||
border-left: 10px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
.o-position-absolute(@top: 10px, @left: 10px);
|
||||
content: '?';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
line-height: 0.6;
|
||||
color: lightgray;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
border: 2px solid @odoo-brand-primary;
|
||||
&.right > .arrow {
|
||||
border-right-color: @odoo-brand-primary;
|
||||
left: -13px;
|
||||
&::after {
|
||||
border-left-width: 2px;
|
||||
}
|
||||
}
|
||||
&.bottom > .arrow {
|
||||
border-bottom-color: @odoo-brand-primary;
|
||||
top: -13px;
|
||||
&::after {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
}
|
||||
.popover-content {
|
||||
color: black;
|
||||
text-align: justify;
|
||||
.o_skip_tour {
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
cursor: pointer;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="Tip">
|
||||
<div class="o_tooltip">
|
||||
<div t-attf-class="o_breathing #{widget.info.position}"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="edit_tour_list" model="ir.ui.view">
|
||||
<field name="model">web_tour.tour</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Menu" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="edit_tour_search" model="ir.ui.view">
|
||||
<field name="name">tour.search</field>
|
||||
<field name="model">web_tour.tour</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Tip">
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="edit_tour_action" model="ir.actions.act_window">
|
||||
<field name="name">Tours</field>
|
||||
<field name="res_model">web_tour.tour</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="edit_tour_list"/>
|
||||
<field name="search_view_id" ref="edit_tour_search"/>
|
||||
</record>
|
||||
<menuitem action="edit_tour_action" id="menu_tour_action" parent="base.next_id_2" sequence="5"/>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -242,6 +242,9 @@
|
||||
<rng:optional> <rng:attribute name="action"/> </rng:optional>
|
||||
<rng:optional> <rng:attribute name="sequence"/> </rng:optional>
|
||||
<rng:optional> <rng:attribute name="groups"/> </rng:optional>
|
||||
<rng:optional>
|
||||
<rng:attribute name="load_xmlid"><rng:value>True</rng:value></rng:attribute>
|
||||
</rng:optional>
|
||||
<!-- TODO: remove icon stuff -->
|
||||
<rng:optional> <rng:attribute name="icon"/> </rng:optional>
|
||||
<rng:optional> <rng:attribute name="web_icon"/> </rng:optional>
|
||||
|
||||
@@ -532,6 +532,8 @@ form: module.record_id""" % (xml_id,)
|
||||
# ensure menu has a name
|
||||
values['name'] = rec_id or '?'
|
||||
|
||||
if rec.get('load_xmlid'):
|
||||
values['load_xmlid'] = True
|
||||
if rec.get('sequence'):
|
||||
values['sequence'] = int(rec.get('sequence'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user