diff options
| author | vda (OpenERP) <vda@tinyerp.com> | 2011-04-27 15:54:56 +0530 |
|---|---|---|
| committer | vda (OpenERP) <vda@tinyerp.com> | 2011-04-27 15:54:56 +0530 |
| commit | 023119776de85a8274f88941adeb577a773ac00a (patch) | |
| tree | 681a78071cecae21c72c9817b7d37ac0debd5847 | |
| parent | bf5a922f889e81e1393523c3b9a3837932961b36 (diff) | |
| parent | ceb524e3376d92dde25e68dd7d74b6de0a5c8a6d (diff) | |
[MERGE] Merged latest trunk branch.Improved dashboard using addwidget api.
bzr revid: vda@tinyerp.com-20110427102456-icjhuk24ke8x8cbg
| -rw-r--r-- | addons/base/controllers/main.py | 33 | ||||
| -rw-r--r-- | addons/base/static/src/css/base.css | 16 | ||||
| -rw-r--r-- | addons/base/static/src/js/data.js | 7 | ||||
| -rw-r--r-- | addons/base/static/src/js/form.js | 36 | ||||
| -rw-r--r-- | addons/base/static/src/js/list.js | 110 | ||||
| -rw-r--r-- | addons/base/static/src/js/views.js | 22 | ||||
| -rw-r--r-- | addons/base/static/src/xml/base.xml | 47 | ||||
| -rw-r--r-- | addons/base/static/test/list.js | 36 | ||||
| -rw-r--r-- | addons/base_dashboard/controllers/main.py | 12 | ||||
| -rw-r--r-- | addons/base_dashboard/static/src/js/dashboard.js | 99 | ||||
| -rw-r--r-- | addons/base_dashboard/static/src/xml/base_dashboard.xml | 15 | ||||
| -rw-r--r-- | doc/source/development.rst | 27 | ||||
| -rw-r--r-- | doc/source/project.rst | 6 | ||||
| -rw-r--r-- | openerpweb/openerpweb.py | 41 |
14 files changed, 312 insertions, 195 deletions
diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index bdedb85e63d..8faca685300 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -410,6 +410,11 @@ class DataSet(openerpweb.Controller): return {'result': r} @openerpweb.jsonrequest + def exec_workflow(self, req, model, id, signal): + r = req.session.exec_workflow(model, id, signal) + return {'result': r} + + @openerpweb.jsonrequest def default_get(self, req, model, fields, context={}): m = req.session.model(model) r = m.default_get(fields, context) @@ -573,19 +578,22 @@ class ListView(View): :param int limit: search limit, for pagination :returns: hell if I have any idea yet """ - view = self.fields_view_get(request, model, id) + view = self.fields_view_get(request, model, id, toolbar=True) rows = DataSet().do_search_read(request, model, offset=offset, limit=limit, domain=domain) eval_context = request.session.evaluation_context( request.context) - return [ - {'data': dict((key, {'value': value}) - for key, value in row.iteritems()), - 'color': self.process_colors(view, row, eval_context)} - for row in rows - ] + return { + 'view': view, + 'records': [ + {'data': dict((key, {'value': value}) + for key, value in row.iteritems()), + 'color': self.process_colors(view, row, eval_context)} + for row in rows + ] + } def process_colors(self, view, row, context): colors = view['arch']['attrs'].get('colors') @@ -618,5 +626,12 @@ class Action(openerpweb.Controller): _cp_path = "/base/action" @openerpweb.jsonrequest - def load(self, req, action_id): - return {} + def load(self, req, action_id, context={}): + Actions = req.session.model('ir.actions.actions') + value = False + action_type = Actions.read([action_id], ['type'], context) + if action_type: + action = req.session.model(action_type[0]['type']).read([action_id], False, context) + if action: + value = action[0] + return {'result': value} diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index cf6d0beabd4..1bd004b53f6 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -336,8 +336,9 @@ body.openerp { } /* Main Application */ -.openerp .application { +.openerp .oe-application { padding: 0px; + height: 100%; } .openerp h2.oe_view_title { @@ -486,6 +487,9 @@ body.openerp { vertical-align: middle; } +.openerp .oe-listview .oe-field-cell { + cursor: pointer; +} .openerp .oe-listview .oe-field-cell button { padding: 0; border: none; @@ -636,7 +640,7 @@ body.openerp { .openerp td.required input, .openerp td.required select { background-color: #D2D2FF; } -.openerp td.invalid input, .openerp td.invalid select { +.openerp td.invalid input, .openerp td.invalid select, .openerp td.invalid textarea { background-color: #F66; border: 1px solid #D00; } @@ -673,13 +677,14 @@ body.openerp { } .openerp .view-manager-main-content { + width: 100%; padding: 6px; } .openerp .view-manager-main-sidebar { - width:0; - padding:0; - margin:0; + width: 180px; + padding: 0; + margin: 0; } .openerp .sidebar-main-div { @@ -746,6 +751,7 @@ body.openerp { } .openerp .toggle-sidebar { + cursor: pointer; border-color: #D2CFCF; border-style: solid; border-width: 1px; diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 5ed21be533c..a29f320b79c 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -118,6 +118,13 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. ids: ids, args: args }, callback); + }, + exec_workflow: function (id, signal, callback) { + return this.rpc('/base/dataset/exec_workflow', { + model: this.model, + id: id, + signal: signal + }, callback); } }); diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 9efa3cd01b6..84d7fe0babd 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -457,16 +457,14 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({ } this.add_widget(widget); }, - add_widget: function(w) { - if (!w.invisible) { - var current_row = this.table[this.table.length - 1]; - if (current_row.length && (this.x + w.colspan) > this.columns) { - current_row = this.add_row(); - } - current_row.push(w); - this.x += w.colspan; - } - return w; + add_widget: function(widget) { + var current_row = this.table[this.table.length - 1]; + if (current_row.length && (this.x + widget.colspan) > this.columns) { + current_row = this.add_row(); + } + current_row.push(widget); + this.x += widget.colspan; + return widget; } }); @@ -534,7 +532,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ on_confirmed: function() { var attrs = this.node.attrs; if (attrs.special) { - this.on_button_object({ + this.on_button_action({ result : { type: 'ir.actions.act_window_close' } }); } else { @@ -542,19 +540,20 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ var context = _.extend({}, this.view.dataset.context, attrs.context || {}); switch(type) { case 'object': - return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_object); - break; + return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_action); + case 'action': + return this.rpc('/base/action/load', { action_id: parseInt(attrs.name) }, this.on_button_action); default: - this.log(_.sprintf("Unsupported button type : %s", type)); + return this.view.dataset.exec_workflow(this.view.datarecord.id, attrs.name, this.on_button_action); } } }, - on_button_object: function(r) { - if (r.result === false) { - this.log("Button object returns false"); - } else if (r.result.constructor == Object) { + on_button_action: function(r) { + console.log("Got reesonse button", r) + if (r.result && r.result.constructor == Object) { this.session.action_manager.do_action(r.result); } else { + this.log("Button returned", r.result); this.view.reload(); } } @@ -911,7 +910,6 @@ openerp.base.form.FieldReference = openerp.base.form.Field.extend({ * Registry of form widgets, called by :js:`openerp.base.FormView` */ openerp.base.form.widgets = new openerp.base.Registry({ - 'hpaned': 'openerp.base.form.Hpaned', 'group' : 'openerp.base.form.WidgetFrame', 'notebook' : 'openerp.base.form.WidgetNotebook', 'separator' : 'openerp.base.form.WidgetSeparator', diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 5a08b96ac14..8035616e59a 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -10,9 +10,23 @@ openerp.base.ListView = openerp.base.Controller.extend( // whether the column headers should be displayed 'header': true, // display addition button, with that label - 'addable': "New" + 'addable': "New", + // whether the list view can be sorted, note that once a view has been + // sorted it can not be reordered anymore + 'sortable': true, + // whether the view rows can be reordered (via vertical drag & drop) + 'reorderable': true }, /** + * Core class for list-type displays. + * + * As a view, needs a number of view-related parameters to be correctly + * instantiated, provides options and overridable methods for behavioral + * customization. + * + * See constructor parameters and method documentations for information on + * the default behaviors and possible options for the list view. + * * @constructs * @param view_manager * @param session An OpenERP session object @@ -24,6 +38,8 @@ openerp.base.ListView = openerp.base.Controller.extend( * @param {Boolean} [options.header=true] should the list's header be displayed * @param {Boolean} [options.deletable=true] are the list rows deletable * @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button. + * @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers + * @param {Boolean} [options.reorderable=true] is it possible to reorder list rows */ init: function(view_manager, session, element_id, dataset, view_id, options) { this._super(session, element_id); @@ -37,11 +53,42 @@ openerp.base.ListView = openerp.base.Controller.extend( this.options = _.extend({}, this.defaults, options || {}); }, + /** + * View startup method, the default behavior is to set the ``oe-listview`` + * class on its root element and to perform an RPC load call. + * + * @returns {$.Deferred} loading promise + */ start: function() { this.$element.addClass('oe-listview'); - return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id, - toolbar:!!this.view_manager.sidebar}, this.on_loaded); + return this.rpc("/base/listview/load", { + model: this.model, + view_id: this.view_id, + toolbar: !!this.view_manager.sidebar + }, this.on_loaded); }, + /** + * Called after loading the list view's description, sets up such things + * as the view table's columns, renders the table itself and hooks up the + * various table-level and row-level DOM events (action buttons, deletion + * buttons, selection of records, [New] button, selection of a given + * record, ...) + * + * Sets up the following: + * + * * Processes arch and fields to generate a complete field descriptor for each field + * * Create the table itself and allocate visible columns + * * Hook in the top-level (header) [New|Add] and [Delete] button + * * Sets up showing/hiding the top-level [Delete] button based on records being selected or not + * * Sets up event handlers for action buttons and per-row deletion button + * * Hooks global callback for clicking on a row + * * Sets up its sidebar, if any + * + * @param {Object} data wrapped fields_view_get result + * @param {Object} data.fields_view fields_view_get result (processed) + * @param {Object} data.fields_view.fields mapping of fields for the current model + * @param {Object} data.fields_view.arch current list view descriptor + */ on_loaded: function(data) { var self = this; this.fields_view = data.fields_view; @@ -108,8 +155,9 @@ openerp.base.ListView = openerp.base.Controller.extend( var context = _.extend( {}, self.dataset.context, field.context || {}); - self.dataset.call(action, [row.data.id.value], [context], - self.do_reload); + self.notification.notify( + '[Dummy] Execute action', self.dataset.model + '#' + action + "([" + row.data.id.value + '])'); + // TODO: implement actions correctly once the corresponding manager has been extracted (and filled) e.stopImmediatePropagation(); }); $table.delegate( @@ -127,10 +175,18 @@ openerp.base.ListView = openerp.base.Controller.extend( /** * Fills the table with the provided records after emptying it * - * @param {Array} records the records to fill the list view with + * TODO: should also re-load the table itself, as e.g. columns may have changed + * + * @param {Object} result filling result + * @param {Array} [result.view] the new view (wrapped fields_view_get result) + * @param {Array} result.records records the records to fill the list view with * @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness) */ - do_fill_table: function(records) { + do_fill_table: function(result) { + if (result.view) { + this.on_loaded({fields_view: result.view}); + } + var records = result.records; var $table = this.$element.find('table'); this.rows = records; @@ -188,14 +244,19 @@ openerp.base.ListView = openerp.base.Controller.extend( }); }, /** - * Asks the view manager to switch to a different view, using the provided - * record index (within the current dataset). + * Used to handle a click on a table row, if no other handler caught the + * event. + * + * The default implementation asks the list view's view manager to switch + * to a different view (by calling + * :js:func:`~openerp.base.ViewManager.on_mode_switch`), using the + * provided record index (within the current list view's dataset). * * If the index is null, ``switch_to_record`` asks for the creation of a * new record. * * @param {Number|null} index the record index (in the current dataset) to switch to - * @param {String} [view="form"] the view to switch to + * @param {String} [view="form"] the view type to switch to */ switch_to_record:function (index, view) { view = view || 'form'; @@ -204,6 +265,16 @@ openerp.base.ListView = openerp.base.Controller.extend( this.view_manager.on_mode_switch(view); }, this)); }, + /** + * Base handler for clicking on a row, discovers the index of the record + * corresponding to the clicked row in the list view's dataset. + * + * Should not be overridden, use + * :js:func:`~openerp.base.ListView.switch_to_record` to customize the + * behavior of the list view when clicking on a row instead. + * + * @param {Object} event jQuery DOM event object + */ on_select_row: function (event) { var $target = $(event.currentTarget); if (!$target.parent().is('tbody')) { @@ -216,8 +287,8 @@ openerp.base.ListView = openerp.base.Controller.extend( if (index == undefined || index === -1) { return; } - this.switch_to_record(index); - }, + this.switch_to_record(index); + }, do_show: function () { this.$element.show(); if (this.hidden) { @@ -246,6 +317,15 @@ openerp.base.ListView = openerp.base.Controller.extend( 'domain': this.dataset.domain }, this.do_fill_table); }, + /** + * Event handler for a search, asks for the computation/folding of domains + * and contexts (and group-by), then reloads the view's content. + * + * @param {Array} domains a sequence of literal and non-literal domains + * @param {Array} contexts a sequence of literal and non-literal contexts + * @param {Array} groupbys a sequence of literal and non-literal group-by contexts + * @returns {$.Deferred} fold request evaluation promise + */ do_search: function (domains, contexts, groupbys) { var self = this; return this.rpc('/base/session/eval_domain_and_context', { @@ -266,7 +346,7 @@ openerp.base.ListView = openerp.base.Controller.extend( /** * Handles the signal to delete a line from the DOM * - * @param e + * @param e jQuery event object */ do_delete: function (e) { // don't link to forms @@ -295,7 +375,7 @@ openerp.base.ListView = openerp.base.Controller.extend( }, /** * Gets the ids of all currently selected records, if any - * @returns a list of ids, empty if no record is selected (or the list view is not selectable + * @returns {Array} empty if no record is selected (or the list view is not selectable) */ get_selection: function () { if (!this.options.selectable) { @@ -307,6 +387,8 @@ openerp.base.ListView = openerp.base.Controller.extend( return rows[$(this).prevAll().length].data.id.value; }).get(); } + // TODO: implement sort (click on column headers), if sorted, the list can not be reordered anymore + // TODO: implement reorder (drag and drop rows) }); openerp.base.TreeView = openerp.base.Controller.extend({ diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index f79d0e611eb..8ef14a3edbf 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -19,7 +19,6 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ * Supported actions: act_window */ do_action: function(action) { - this.log(action); var self = this; // instantiate the right controllers by understanding the action switch (action.type) { @@ -36,14 +35,14 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ // When dialog is closed with ESC key or close manually, branch to act_window_close logic self.do_action({ type: 'ir.actions.act_window_close' }); }); - var viewmanager = new openerp.base.ViewManagerAction(this.session ,element_id, action, false); + var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action, false); viewmanager.start(); this.dialog_stack.push(viewmanager); - } else if (action.target == "current") { + } else { if (this.viewmanager) { this.viewmanager.stop(); } - this.viewmanager = new openerp.base.ViewManagerAction(this.session,this.element_id, action, true); + this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, true); this.viewmanager.start(); } break; @@ -211,14 +210,17 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ // init search view var searchview_id = this.action.search_view_id && this.action.search_view_id[0]; - var searchview_loaded = this.setup_search_view( - searchview_id || false, search_defaults); + if (searchview_id) { + var searchview_loaded = this.setup_search_view( + searchview_id, search_defaults); - // schedule auto_search - if (searchview_loaded != null && this.action['auto_search']) { - $.when(searchview_loaded, inital_view_loaded) - .then(this.searchview.do_search); + // schedule auto_search + if (this.action['auto_search']) { + $.when(searchview_loaded, inital_view_loaded) + .then(this.searchview.do_search); + } } + }, stop: function() { // should be replaced by automatic destruction implemented in BaseWidget diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index cbb90ca9e2e..b49ef2c7d6a 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -18,9 +18,6 @@ </div> </div> <div id="oe_login" class="login"></div> - <div style="position: absolute; right: 2px; top: 38px;"> - <button onclick="QWeb.add_template('base.xml'); $('body').css('background-color', '#FFFF9C'); setTimeout(function() { $('body').css('background-color', '#FFF'); }, 500);">Reload QWEB</button> - </div> <table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" t-att-class="'main_table' + (typeof kitten == 'undefined' ? '' : ' kitten-mode-activated')"> <tr> @@ -33,10 +30,7 @@ <td valign="top" id="oe_secondary_menu" class="secondary_menu"> </td> <td valign="top" width="100%" height="100%"> - <div id="oe_main" class="main"> - <div id="oe_app" class="application"></div> - <div id="oe_sidebar"></div> - </div> + <div id="oe_app" class="oe-application"></div> </td> </tr> <tr> @@ -135,23 +129,23 @@ <t t-name="ViewManager"> <table class="view-manager-main-table"> <tr> - <td class="view-manager-main-content"> - <!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" --> - <div class="oe_vm_switch"> - <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"/> - <t t-foreach="views" t-as="view"> - <div t-attf-id="#{prefix}_view_#{view[1]}"/> - </t> - </td> - <td class="view-manager-main-sidebar"> - </td> - </tr> + <td class="view-manager-main-content"> + <!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" --> + <div class="oe_vm_switch"> + <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" t-opentag="true"/> + <t t-foreach="views" t-as="view"> + <div t-attf-id="#{prefix}_view_#{view[1]}"/> + </t> + </td> + <td class="view-manager-main-sidebar"> + </td> + </tr> </table> </t> <table t-name="ListView"> @@ -217,7 +211,8 @@ <t t-set="is_button" t-value="column.tag === 'button'"/> <!-- TODO: get correct widget from form --> <t t-if="!is_button and row['data'][column.id].value"> - <t t-esc="row['data'][column.id].value"/> + <t t-set="value" t-value="row['data'][column.id].value"/> + <t t-esc="value instanceof Array ? value[1] : value"/> </t> <button type="button" t-att-title="column.help" t-if="is_button"> @@ -299,7 +294,7 @@ t-att-ondblclick="'console.log(\'' + widget.element_id + '\', openerp.screen.' + widget.element_id + ')'"> <t t-esc="widget.string"/> <span t-if="widget.help">?</span> - <t t-if="widget.string">:</t> + <t t-if="widget.string and widget.node.tag != 'label'">:</t> </label> </t> <t t-name="FieldChar"> diff --git a/addons/base/static/test/list.js b/addons/base/static/test/list.js index c0a34aa71be..92d0bdd5fd5 100644 --- a/addons/base/static/test/list.js +++ b/addons/base/static/test/list.js @@ -34,15 +34,15 @@ $(document).ready(function () { asyncTest('render selection checkboxes', 2, function () { var listview = new openerp.base.ListView( {}, null, - 'qunit-fixture', {model: null}); + 'qunit-fixture', {model: null, ids: [null, null, null], index: 0}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: null}}}, {data: {id: {value: null}}}, {data: {id: {value: null}}} - ]).then(function () { + ]}).then(function () { ok(are(listview.$element.find('tbody th'), '.oe-record-selector')); ok(are(listview.$element.find('tbody th input'), @@ -53,30 +53,30 @@ $(document).ready(function () { asyncTest('render no checkbox if selectable=false', 1, function () { var listview = new openerp.base.ListView( {}, null, - 'qunit-fixture', {model: null}, false, + 'qunit-fixture', {model: null, ids: [null, null, null], index: 0}, false, {selectable: false}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: null}}}, {data: {id: {value: null}}}, {data: {id: {value: null}}} - ]).then(function () { + ]}).then(function () { equal(listview.$element.find('tbody th').length, 0); start(); }); }); asyncTest('select a bunch of records', 2, function () { var listview = new openerp.base.ListView( - {}, null, 'qunit-fixture', {model: null}); + {}, null, 'qunit-fixture', {model: null, ids: [1, 2, 3], index: 0}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: 1}}}, {data: {id: {value: 2}}}, {data: {id: {value: 3}}} - ]).then(function () { + ]}).then(function () { listview.$element.find('tbody th input:eq(2)') .attr('checked', true); deepEqual(listview.get_selection(), [3]); @@ -88,15 +88,15 @@ $(document).ready(function () { }); asyncTest('render deletion button if list is deletable', 1, function () { var listview = new openerp.base.ListView( - {}, null, 'qunit-fixture', {model: null}); + {}, null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: null}}}, {data: {id: {value: null}}}, {data: {id: {value: null}}} - ]).then(function () { + ]}).then(function () { equal( listview.$element.find('tbody tr td.oe-record-delete button').length, 3); @@ -109,15 +109,15 @@ $(document).ready(function () { var listview = new openerp.base.ListView( {}, null, 'qunit-fixture', {model: null, unlink: function (ids) { deleted = ids; - }}); + }, ids: [1, 2, 3], index: 0}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: 1}}}, {data: {id: {value: 2}}}, {data: {id: {value: 3}}} - ]).then(function () { + ]}).then(function () { listview.$element.find('tbody td.oe-record-delete:eq(2) button').click(); deepEqual(deleted, [3]); listview.$element.find('tbody td.oe-record-delete:eq(0) button').click(); @@ -130,15 +130,15 @@ $(document).ready(function () { var listview = new openerp.base.ListView( {}, null, 'qunit-fixture', {model: null, unlink: function (ids) { deleted = ids; - }}); + }, ids: [1, 2, 3], index: 0}); listview.on_loaded(fvg); - listview.do_fill_table([ + listview.do_fill_table({records: [ {data: {id: {value: 1}}}, {data: {id: {value: 2}}}, {data: {id: {value: 3}}} - ]).then(function () { + ]}).then(function () { listview.$element.find('tbody th input:eq(2)') .attr('checked', true); listview.$element.find('tbody th input:eq(1)') diff --git a/addons/base_dashboard/controllers/main.py b/addons/base_dashboard/controllers/main.py index bb761a82696..72ef9b2464a 100644 --- a/addons/base_dashboard/controllers/main.py +++ b/addons/base_dashboard/controllers/main.py @@ -5,5 +5,13 @@ class Dashboard(View): _cp_path = "/base_dashboard/dashboard" @openerpweb.jsonrequest - def load(self, req, node): - return
\ No newline at end of file + def load(self, req, node_attrs): + + self.action_id = int(node_attrs['name']) + actions = req.session.model('ir.actions.actions') + result = actions.read([self.action_id],['type'], req.session.context) + if not result: + raise _('Action not found!') + self.action = req.session.model(result[0]['type']).read([self.action_id], False, req.session.context)[0] + + return {'action': self.action}
\ No newline at end of file diff --git a/addons/base_dashboard/static/src/js/dashboard.js b/addons/base_dashboard/static/src/js/dashboard.js index 4b09dee21dc..7382e0b064d 100644 --- a/addons/base_dashboard/static/src/js/dashboard.js +++ b/addons/base_dashboard/static/src/js/dashboard.js @@ -1,89 +1,70 @@ -openerp.base_dashboard = function(openerp) { +openerp.base_dashboard = function(openerp){ + QWeb.add_template('/base_dashboard/static/src/xml/base_dashboard.xml'); -openerp.base.form.Hpaned = openerp.base.form.Widget.extend({ - init: function(view, node) { + +openerp.base.form.Board = openerp.base.form.Widget.extend({ + init: function(view, node) { this._super(view, node); - this.template = "Hpaned"; + this.template = "Board"; }, start: function() { this._super.apply(this, arguments); - this.$element.html(QWeb.render(this.template)) + this.$element.html(QWeb.render(this.template)); }, render: function() { var self = this; jQuery('body').append( - jQuery('<div>', {'id': 'dashboard_templates'}).hide() - ); + jQuery('<div>', {'id': 'dashboard_template'}).load('/base_dashboard/static/src/dashboard_template.html',self.on_loaded).hide() + ) - $("#dashboard_templates").load("/base_dashboard/static/src/dashboard_template.html", function(result){ - self.render_dashboard() - }); }, - render_dashboard: function() { - var self = this; - var data = {"layout": "layout2","data": []} - var get_column = ['first', 'second', 'third']; + on_loaded: function() { var children = this.node.children; - - var action = [] - for (child in children) { - action.push({}) - var ch_widget = children[child]['children']; - for (ch in ch_widget) { - if (ch_widget[ch].tag == 'action') { -// var _act = new openerp.base.form.Action(this.view, ch_widget[ch]); -// action[child][ch] = _act.render(); - data['data'].push({ - "title": ch_widget[ch].attrs.string, - "id": ch_widget[ch].attrs.name, - "column": get_column[child], - "url": "/base_dashboard/static/data.html", - "open": true - }) - } - } - } - + var get_column = ['first', 'second', 'third']; var board = jQuery('#dashboard').dashboard({ - layoutClass:'layout', - json_data: data + layoutClass:'layout' }); board.init(); + for(var ch = 0; ch < children.length; ch++) { + var ch_widgets = children[ch].children; + for(var chld = 0; chld < ch_widgets.length; chld++) { + var widget_type = ch_widgets[chld].tag; + var board_element = board.element.find('[id=column-'+get_column[chld]+']'); + var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widgets[chld], board_element); + board.addWidget({ + 'id': ch_widgets[chld].attrs.name, + 'title': ch_widgets[chld].attrs.string, + 'url': widget.start() + }, board_element) + } + } } }); openerp.base.form.Action = openerp.base.form.Widget.extend({ - init: function(view, node) { - this._super(view, node); + init: function(view, node, column) { + this._super(view, node, column); this.template = "Action"; }, start: function() { this._super.apply(this, arguments); + this.rpc('/base_dashboard/dashboard/load',{ + node_attrs: this.node.attrs + }, + this.on_load_action); }, - render: function(){ - return QWeb.render(this.template, {'node': this.node}); + on_load_action: function(result) { + var action = result.action; + action_manager = new openerp.base.ActionManager(this.session, this.$element.attr('id')); + action_manager.start(); + action_manager.do_action(action); } - -}); - -openerp.base.form.Vpaned = openerp.base.form.Widget.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "Vpaned"; - }, - start: function() { - this._super.apply(this, arguments); - }, - - render: function(){ - return QWeb.render(this.template); - } -}); - -} - +}) +openerp.base.form.widgets.add('hpaned', 'openerp.base.form.Board'); +openerp.base.form.widgets.add('action', 'openerp.base.form.Action'); +}
\ No newline at end of file diff --git a/addons/base_dashboard/static/src/xml/base_dashboard.xml b/addons/base_dashboard/static/src/xml/base_dashboard.xml index 8d26197780c..efe0a92c4dc 100644 --- a/addons/base_dashboard/static/src/xml/base_dashboard.xml +++ b/addons/base_dashboard/static/src/xml/base_dashboard.xml @@ -1,17 +1,12 @@ <template> - <t t-name="Hpaned"> - <div>HPANED>>>>>>>>>>>>>>>>>>>>>>>>></div> + <t t-name="Board"> <div id="dashboard" class="dashboard"> + <!-- this HTML covers all layouts. The 5 different layouts are handled by setting another layout classname --> <div class="layout"> - <div class="column first column-first"></div> - <div class="column second column-second"></div> - <div class="column third column-third"></div> + <div id="column-first" class="column first column-first"></div> + <div id="column-second" class="column second column-second"></div> + <div id="column-third" class="column third column-third"></div> </div> </div> </t> - <t t-name="Vpaned"> - </t> - <t t-name="Action"> - <t t-esc="node.attrs.string"/> - </t> </template>
\ No newline at end of file diff --git a/doc/source/development.rst b/doc/source/development.rst index a1d989b46ac..514e094b689 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -55,7 +55,7 @@ Events Event triggered after a user asked for a search. The search view fires this event after collecting all input data (contexts, domains and group_by contexts). Note that the search view does *not* merge - those (or therwise evaluate them), they are returned as provided by + those (or otherwise evaluate them), they are returned as provided by the various inputs within the view. ``on_clear`` @@ -71,7 +71,7 @@ An important concept in the search view is that of input. It is both an informal protocol and an abstract type that can be inherited from. Inputs are widgets which can contain user data (a char widget for -instance, or a selection box). They are able of action and of +instance, or a selection box). They are capable of action and of reaction: .. _views-search-registration: @@ -93,9 +93,9 @@ reaction: * Return a context (an object), this is the "normal" response if the input holds a value. - * Return a falsy value (generally ``null``). This value indicates - the input does not contain any value and will not take part in the - research. + * Return a value that evaluates as false (generally ``null``). This + value indicates the input does not contain any value and will not + affect the results of the search. * Raise :js:class:`openerp.base.search.Invalid` to indicate that it holds a value but this value can not be used in the search @@ -104,7 +104,7 @@ reaction: the search process. :js:class:`~openerp.base.search.Invalid` takes three mandatory - arguments: an indentifier (a name for instance), the invalid value + arguments: an identifier (a name for instance), the invalid value, and a validation message indicating the issue. ``get_domain`` @@ -114,11 +114,11 @@ reaction: The :js:class:`openerp.base.search.Input` type implements registration on its own, but its implementations of ``get_context`` and -``get_domain`` simply raise errors and *have* to be overridden. +``get_domain`` simply raise errors and *must* be overridden. One last action is for filters, as an activation order has to be kept -on them for some controls (establish the correct grouping sequence for -instance). +on them for some controls (to establish the correct grouping sequence, +for instance). To that end, filters can call :js:func:`openerp.base.Search.do_toggle_filter`, providing themselves @@ -137,7 +137,7 @@ Life cycle The search view has a pretty simple and linear life cycle, in three main steps: -:js:class:`init <openerp.base.SearchView>` +:js:class:`~openerp.base.SearchView.init` Nothing interesting happens here @@ -211,7 +211,7 @@ called in this order: Gives the widget the opportunity to unbind its events, remove itself from the DOM and perform any other cleanup task it may have. - Event if the widget does not do anything itself, it is responsible + Even if the widget does not do anything itself, it is responsible for shutting down its children. An abstract type is available and can be inherited from, to simplify @@ -232,7 +232,8 @@ abstract types, used to implement input widgets: <views-search-registration>`. If inherited from, descendant classes should not call its - implementations of ``get_context`` and ``get_domain``. + implementations of :js:func:`~openerp.base.search.Input.get_context` + and :js:func:`~openerp.base.search.Input.get_domain`. * :js:class:`openerp.base.search.Field` is used to implement more "field" widgets (which allow the user to input potentially complex @@ -251,7 +252,7 @@ abstract types, used to implement input widgets: :js:func:`~openerp.base.search.Widget.make_id`. * It sets up a basic (overridable) - :js:attr:`~opererp.base.search.Field.template` attribute, combined + :js:attr:`~openerp.base.search.Field.template` attribute, combined with the previous tasks, this makes subclasses of :js:class:`~openerp.base.search.Field` render themselves "for free". diff --git a/doc/source/project.rst b/doc/source/project.rst index 6f9a06b2f0d..faa15da2163 100644 --- a/doc/source/project.rst +++ b/doc/source/project.rst @@ -31,7 +31,7 @@ Guides and main documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The meat and most important part of all documentation. Should be -written in plain english, using reStructuredText_ and taking advantage +written in plain English, using reStructuredText_ and taking advantage of `Sphinx's extensions`_, especially `cross-references`_. Python API Documentation @@ -65,7 +65,7 @@ RST, using Sphinx's `Python domain`_ [#]_: and ``__new__``), using the `info fields`_ as well. * Attributes (class and instance) should be documented in their - class's docstrin via the ``.. attribute::`` directiveg, following + class's docstring via the ``.. attribute::`` directive, following the class's own documentation. * The relation between modules and module-level attributes is similar: @@ -220,7 +220,7 @@ Roadmap Release notes +++++++++++++ -.. [#] because Python is the default domain, the ``py:`` markup prefix +.. [#] Because Python is the default domain, the ``py:`` markup prefix is optional and should be left out. .. [#] Resig's Class still uses prototypes under the hood, it doesn't diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index 8423dc878ab..d96a9c5ffec 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -116,6 +116,12 @@ class OpenERPSession(object): r = self.proxy('object').execute(self._db, self._uid, self._password, model, func, *l, **d) return r + def exec_workflow(self, model, id, signal): + if not (self._db and self._uid and self._password): + raise OpenERPUnboundException() + r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id) + return r + def model(self, model): """ Get an RPC proxy for the object ``model``, bound to this session. @@ -289,13 +295,34 @@ class JsonRequest(object): * the json string is passed as a form parameter named "request" * method is currently ignored - Sucessful request: - --> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null} - <-- {"jsonrpc": "2.0", "result": { "res1": "val1" }, "id": null} - - Request producing a error: - --> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null} - <-- {"jsonrpc": "2.0", "error": {"code": 1, "message": "End user error message.", "data": {"code": "codestring", "debug": "traceback" } }, "id": null} + Sucessful request:: + + --> {"jsonrpc": "2.0", + "method": "call", + "params": {"session_id": "SID", + "context": {}, + "arg1": "val1" }, + "id": null} + + <-- {"jsonrpc": "2.0", + "result": { "res1": "val1" }, + "id": null} + + Request producing a error:: + + --> {"jsonrpc": "2.0", + "method": "call", + "params": {"session_id": "SID", + "context": {}, + "arg1": "val1" }, + "id": null} + + <-- {"jsonrpc": "2.0", + "error": {"code": 1, + "message": "End user error message.", + "data": {"code": "codestring", + "debug": "traceback" } }, + "id": null} """ |
