[MERGE] xmo viewmanager

bzr revid: al@openerp.com-20110404112535-pa3ss7ymj6mr97r1
This commit is contained in:
Antony Lesuisse 2011-04-04 13:25:35 +02:00
commit 9ad770a2da
10 changed files with 244 additions and 59 deletions

View File

@ -88,6 +88,7 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest
def login(self, req, db, login, password):
req.session.login(db, login, password)
return {
"session_id": req.session_id,
"uid": req.session._uid,
@ -230,8 +231,38 @@ class Menu(openerpweb.Controller):
req.session.evaluation_context(
action['context'])) or []
self.fix_view_modes(action)
return {"action": actions}
def fix_view_modes(self, action):
""" For historical reasons, OpenERP has weird dealings in relation to
view_mode and the view_type attribute (on window actions):
* one of the view modes is ``tree``, which stands for both list views
and tree views
* the choice is made by checking ``view_type``, which is either
``form`` for a list view or ``tree`` for an actual tree view
This methods simply folds the view_type into view_mode by adding a
new view mode ``list`` which is the result of the ``tree`` view_mode
in conjunction with the ``form`` view_type.
TODO: this should go into the doc, some kind of "peculiarities" section
:param dict action: an action descriptor
:returns: nothing, the action is modified in place
"""
if action.pop('view_type') != 'form':
return
action['view_mode'] = ','.join(
mode if mode != 'tree' else 'list'
for mode in action['view_mode'].split(','))
action['views'] = [
[id, mode if mode != 'tree' else 'list']
for id, mode in action['views']
]
class DataSet(openerpweb.Controller):
_cp_path = "/base/dataset"
@ -378,7 +409,7 @@ class View(openerpweb.Controller):
:param session: Current OpenERP session
:type session: openerpweb.openerpweb.OpenERPSession
"""
domain = elem.get(attr_name)
domain = elem.get(attr_name, '').strip()
if domain:
try:
elem.set(
@ -403,7 +434,7 @@ class View(openerpweb.Controller):
"""
self.parse_domain(elem, 'domain', session)
self.parse_domain(elem, 'filter_domain', session)
context_string = elem.get('context')
context_string = elem.get('context', '').strip()
if context_string:
try:
elem.set('context',

View File

@ -396,7 +396,7 @@ body.openerp {
}
/* View Manager */
.openerp .views_switchers {
.openerp .views-switchers {
text-align: right;
}

View File

@ -184,8 +184,19 @@ openerp.base.BasicController = Class.extend(
* Controller start
* event binding, rpc and callback calling required to initialize the
* object can happen here
*
* Returns a promise object letting callers (subclasses and direct callers)
* know when this component is done starting
*
* @returns {jQuery.Deferred}
*/
start: function() {
// returns an already fulfilled promise. Maybe we could return nothing?
// $.when can take non-deferred and in that case it simply considers
// them all as fulfilled promises.
// But in thise case we *have* to ensure callers use $.when and don't
// try to call deferred methods on this return value.
return $.Deferred().done().promise();
},
stop: function() {
},
@ -250,6 +261,19 @@ openerp.base.Session = openerp.base.BasicController.extend(
}
});
},
/**
* Executes an RPC call, registering the provided callbacks.
*
* Registers a default error callback if none is provided, and handles
* setting the correct session id and session context in the parameter
* objects
*
* @param {String} url RPC endpoint
* @param {Object} params call parameters
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
rpc: function(url, params, success_callback, error_callback) {
// Construct a JSON-RPC2 request, method is currently unused
params.session_id = this.session_id;
@ -259,17 +283,22 @@ openerp.base.Session = openerp.base.BasicController.extend(
error_callback = typeof(error_callback) != "undefined" ? error_callback : this.on_rpc_error;
// Call using the rpc_mode
this.rpc_ajax(url, {
return this.rpc_ajax(url, {
jsonrpc: "2.0",
method: "call",
params: params,
id:null
}, success_callback, error_callback);
},
/**
* Raw JSON-RPC call
*
* @returns {jQuery.Deferred} ajax-based deferred object
*/
rpc_ajax: function(url, payload, success_callback, error_callback) {
var self = this;
this.on_rpc_request();
$.ajax({
return $.ajax({
type: "POST",
url: url,
dataType: 'json',
@ -446,9 +475,18 @@ openerp.base.Controller = openerp.base.BasicController.extend(
if(this.session)
this.session.log.apply(this.session,arguments);
},
/**
* Performs a JSON-RPC call
*
* @param {String} url endpoint url
* @param {Object} data RPC parameters
* @param {Function} success RPC call success callback
* @param {Function} error RPC call error callback
* @returns {jQuery.Deferred} deferred object for the RPC call
*/
rpc: function(url, data, success, error) {
// TODO: support additional arguments ?
this.session.rpc(url, data, success, error);
return this.session.rpc(url, data, success, error);
}
});

View File

@ -1,7 +1,21 @@
openerp.base.form = function (openerp) {
openerp.base.FormView = openerp.base.Controller.extend({
openerp.base.views.add('form', 'openerp.base.FormView');
openerp.base.FormView = openerp.base.Controller.extend(
/** @lends openerp.base.FormView# */{
/**
* Indicates that this view is not searchable, and thus that no search
* view should be displayed (if there is one active).
*/
searchable: false,
/**
* @constructs
* @param {openerp.base.Session} session the current openerp session
* @param {String} element_id this view's root element id
* @param {openerp.base.DataSet} dataset the dataset this view will work with
* @param {String} view_id the identifier of the OpenERP view object
*/
init: function(session, element_id, dataset, view_id) {
this._super(session, element_id);
this.dataset = dataset;
@ -16,7 +30,7 @@ openerp.base.FormView = openerp.base.Controller.extend({
},
start: function() {
//this.log('Starting FormView '+this.model+this.view_id)
this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
},
on_loaded: function(data) {
var self = this;

View File

@ -1,6 +1,7 @@
openerp.base.list = function (openerp) {
openerp.base.views.add('list', 'openerp.base.ListView');
openerp.base.ListView = openerp.base.Controller.extend({
init: function(session, element_id, dataset, view_id) {
this._super(session, element_id);
@ -22,7 +23,7 @@ openerp.base.ListView = openerp.base.Controller.extend({
},
start: function() {
//this.log('Starting ListView '+this.model+this.view_id)
this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
},
on_loaded: function(data) {
this.fields_view = data.fields_view;

View File

@ -14,7 +14,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({
},
start: function() {
//this.log('Starting SearchView '+this.model+this.view_id)
this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
return this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
},
show: function () {
this.$element.show();
},
hide: function () {
this.$element.hide();
},
/**
* Builds a list of widget rows (each row is an array of widgets)

View File

@ -17,6 +17,9 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
do_action: function(action) {
// instantiate the right controllers by understanding the action
if(action.type == "ir.actions.act_window") {
if (this.viewmanager) {
this.viewmanager.stop();
}
this.viewmanager = new openerp.base.ViewManager(this.session,this.element_id);
this.viewmanager.do_action_window(action);
this.viewmanager.start();
@ -24,55 +27,58 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
}
});
/**
* Registry for all the main views
*/
openerp.base.views = new openerp.base.Registry();
openerp.base.ViewManager = openerp.base.Controller.extend({
// This will be ViewManager Abstract/Common
// This will be ViewManager Abstract/Common
init: function(session, element_id) {
this._super(session, element_id);
this.action = null;
this.dataset = null;
this.searchview_id = false;
this.searchview = null;
this.search_visible = true;
this.active_view = null;
this.auto_search = false;
// this.views = { "list": { "view_id":1234, "controller": instance} }
this.views = {};
},
start: function() {
},
/**
* Asks the view manager to switch visualization mode.
*
* @param {String} view_type type of view to display
* @returns {jQuery.Deferred} new view loading promise
*/
on_mode_switch: function(view_type) {
var view_promise;
this.active_view = view_type;
var view = this.views[view_type];
if (!view.controller) {
// Lazy loading of views
var controller;
switch (view_type) {
case 'tree':
controller = new openerp.base.ListView(this.session, this.element_id + "_view_tree", this.dataset, view.view_id);
break;
case 'form':
controller = new openerp.base.FormView(this.session, this.element_id + "_view_form", this.dataset, view.view_id);
break;
case 'calendar':
controller = new openerp.base.CalendarView(this.session, this.element_id + "_view_calendar", this.dataset, view.view_id);
break;
case 'gantt':
controller = new openerp.base.GanttView(this.session, this.element_id + "_view_gantt", this.dataset, view.view_id);
break;
}
controller.start();
// controller.on_action.add(this.on_action); ??
var controller = new (openerp.base.views.get_object(view_type))(
this.session, this.element_id + "_view_" + view_type, this.dataset, view.view_id);
view_promise = controller.start();
this.views[view_type].controller = controller;
if (this.auto_search) {
this.searchview.on_loaded.add_last(this.searchview.do_search);
this.auto_search = false;
}
}
if (view.controller.searchable === false) {
this.searchview.hide();
} else {
this.searchview.show();
}
this.$element
.find('.views-switchers button').removeAttr('disabled')
.filter('[data-view-type="' + view_type + '"]')
.attr('disabled', true);
for (var i in this.views) {
if (this.views[i].controller) {
this.views[i].controller.$element.toggle(i === view_type);
}
}
return view_promise;
},
/**
* Extract search view defaults from the current action's context.
@ -91,37 +97,51 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
});
return defaults;
},
/**
* Sets up the current viewmanager's search view.
*
* @param action the action being executed
* @returns {jQuery.Deferred} search view startup deferred
*/
setup_search_view:function (action) {
var self = this;
if (this.searchview) {
this.searchview.stop();
}
var searchview = this.searchview = new openerp.base.SearchView(
this.session, this.element_id + "_search",
this.dataset, action.search_view_id[0] || false,
this.search_defaults());
searchview.on_search.add(function() {
self.views[self.active_view].controller.do_search.apply(self, arguments);
});
return searchview.start();
},
do_action_window: function(action) {
var self = this;
var prefix_id = "#" + this.element_id;
this.action = action;
this.dataset = new openerp.base.DataSet(this.session, action.res_model);
this.dataset.start();
this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: action.views}));
this.searchview_id = false;
if (this.search_visible && action.search_view_id) {
this.searchview_id = action.search_view_id[0];
var searchview = this.searchview = new openerp.base.SearchView(
this.session, this.element_id + "_search",
this.dataset, this.searchview_id,
this.search_defaults());
searchview.on_search.add(function() {
self.views[self.active_view].controller.do_search.apply(self, arguments);
});
searchview.start();
var searchview_loaded = this.setup_search_view(action);
this.auto_search = action.auto_search;
}
this.$element.find('.views_switchers button').click(function() {
this.$element.find('.views-switchers button').click(function() {
self.on_mode_switch($(this).data('view-type'));
});
_.each(action.views, function(view) {
self.views[view[1]] = { view_id: view[0], controller: null };
});
// switch to the first one in sequence
this.on_mode_switch(action.views[0][1]);
var inital_view_loaded = this.on_mode_switch(action.views[0][1]);
if (action['auto_search']) {
$.when(searchview_loaded, inital_view_loaded)
.then(this.searchview.do_search);
}
},
// create when root, also add to parent when o2m
on_create: function() {
@ -235,19 +255,42 @@ openerp.base.BaseWidget = openerp.base.Controller.extend({
}
});
openerp.base.views.add('calendar', 'openerp.base.CalendarView');
openerp.base.CalendarView = openerp.base.Controller.extend({
// Dhtmlx scheduler ?
start: function () {
this._super();
this.$element.append('Calendar view');
}
});
openerp.base.views.add('gantt', 'openerp.base.GanttView');
openerp.base.GanttView = openerp.base.Controller.extend({
// Dhtmlx gantt ?
start: function () {
this._super();
this.$element.append('Gantt view');
}
});
openerp.base.views.add('tree', 'openerp.base.TreeView');
/**
* Genuine tree view (the one displayed as a tree, not the list)
*/
openerp.base.TreeView = openerp.base.Controller.extend({
start: function () {
this._super();
this.$element.append('Tree view');
}
});
openerp.base.DiagramView = openerp.base.Controller.extend({
//
});
openerp.base.views.add('graph', 'openerp.base.GraphView');
openerp.base.GraphView = openerp.base.Controller.extend({
start: function () {
this._super();
this.$element.append('Graph view');
}
});
openerp.base.ProcessView = openerp.base.Controller.extend({

View File

@ -126,16 +126,16 @@
</t>
<t t-name="ViewManager">
<!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" -->
<div class="views_switchers">
<div class="views-switchers">
<t t-foreach="views" t-as="view">
<button type="button" t-att-data-view-type="view[1]">
<t t-esc="view[1]"/>
</button>
</t>
</div>
<div t-attf-id="#{prefix}_search"></div>
<div t-attf-id="#{prefix}_search"/>
<t t-foreach="views" t-as="view">
<div t-attf-id="#{prefix}_view_#{view[1]}"></div>
<div t-attf-id="#{prefix}_view_#{view[1]}"/>
</t>
</t>
<t t-name="ListView">

View File

@ -97,3 +97,55 @@ class LoadTest(unittest2.TestCase):
}]
}]
)
class ActionMungerTest(unittest2.TestCase):
def setUp(self):
self.menu = base.controllers.main.Menu()
def test_actual_treeview(self):
action = {
"views": [[False, "tree"], [False, "form"],
[False, "calendar"]],
"view_type": "tree",
"view_id": False,
"view_mode": "tree,form,calendar"
}
changed = action.copy()
del action['view_type']
self.menu.fix_view_modes(changed)
self.assertEqual(changed, action)
def test_list_view(self):
action = {
"views": [[False, "tree"], [False, "form"],
[False, "calendar"]],
"view_type": "form",
"view_id": False,
"view_mode": "tree,form,calendar"
}
self.menu.fix_view_modes(action)
self.assertEqual(action, {
"views": [[False, "list"], [False, "form"],
[False, "calendar"]],
"view_id": False,
"view_mode": "list,form,calendar"
})
def test_redundant_views(self):
action = {
"views": [[False, "tree"], [False, "form"],
[False, "calendar"], [42, "tree"]],
"view_type": "form",
"view_id": False,
"view_mode": "tree,form,calendar"
}
self.menu.fix_view_modes(action)
self.assertEqual(action, {
"views": [[False, "list"], [False, "form"],
[False, "calendar"], [42, "list"]],
"view_id": False,
"view_mode": "list,form,calendar"
})

View File

@ -32,7 +32,7 @@ class ViewTest(unittest2.TestCase):
def test_convert_literal_domain(self):
e = xml.etree.ElementTree.Element(
'field', domain="[('somefield', '=', 3)]")
'field', domain=" [('somefield', '=', 3)] ")
self.view.parse_domains_and_contexts(e, None)
self.assertEqual(
@ -75,7 +75,7 @@ class ViewTest(unittest2.TestCase):
def test_convert_literal_context(self):
e = xml.etree.ElementTree.Element(
'field', context="{'some_prop': 3}")
'field', context=" {'some_prop': 3} ")
self.view.parse_domains_and_contexts(e, None)
self.assertEqual(