diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 9d65acfb760..99fb1cd2440 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -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, @@ -187,8 +188,38 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context): action['domain'], req.session.evaluation_context( action['context'])) or [] + fix_view_modes(action) return actions +def fix_view_modes(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 Menu(openerpweb.Controller): _cp_path = "/base/menu" @@ -237,7 +268,6 @@ class Menu(openerpweb.Controller): return {"action": actions} - class DataSet(openerpweb.Controller): _cp_path = "/base/dataset" @@ -383,7 +413,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( @@ -408,7 +438,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', diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 531941f85a9..151ec6e214e 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -395,6 +395,11 @@ body.openerp { padding-left: 10px; } +/* View Manager */ +.openerp .views-switchers { + text-align: right; +} + /* Form */ .openerp .required.error { border: 1px solid #900; @@ -413,6 +418,13 @@ body.openerp { border-radius: 3px; background: white; } +.openerp input.field_date { + background: url('../img/ui/field_calendar.png') no-repeat right center; + background-origin: content-box; + -moz-background-origin: content; + -moz-background-origin: content-box; + -webkit-background-origin: content-box; +} /* Widgets */ .openerp .separator { diff --git a/addons/base/static/src/img/ui/field_calendar.png b/addons/base/static/src/img/ui/field_calendar.png new file mode 100644 index 00000000000..99a1f3f9ef1 Binary files /dev/null and b/addons/base/static/src/img/ui/field_calendar.png differ diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 14577c9d0ab..523f2c8a2b7 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -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); } }); diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 2fd4d9f8b6b..ad4028fb155 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -32,8 +32,6 @@ openerp.base.DataSet = openerp.base.Controller.extend( this._super(session); this.model = model; - this._fields = null; - this._ids = []; this._active_ids = null; this._active_id_index = 0; @@ -43,12 +41,6 @@ openerp.base.DataSet = openerp.base.Controller.extend( this._context = {}; }, start: function() { - // TODO: fields_view_get fields selection? - this.rpc("/base/dataset/fields", {"model":this.model}, this.on_fields); - }, - on_fields: function(result) { - this._fields = result.fields; - this.on_ready(); }, /** @@ -63,12 +55,11 @@ openerp.base.DataSet = openerp.base.Controller.extend( * @param {Number} [limit=null] The maximum number of records to return * @returns itself */ - fetch: function (offset, limit) { + fetch: function (fields, offset, limit) { offset = offset || 0; - limit = limit || null; this.rpc('/base/dataset/find', { model: this.model, - fields: this._fields, + fields: fields, domain: this._domain, context: this._context, sort: this._sort, @@ -77,9 +68,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( }, _.bind(function (records) { var data_records = _.map( records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); + return new openerp.base.DataRecord(this.session, this.model, fields, record); }, this); this.on_fetch(data_records, { @@ -116,16 +105,14 @@ openerp.base.DataSet = openerp.base.Controller.extend( * * @returns itself */ - active_ids: function () { + active_ids: function (fields) { this.rpc('/base/dataset/get', { ids: this.get_active_ids(), model: this.model }, _.bind(function (records) { this.on_active_ids(_.map( records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); + return new openerp.base.DataRecord(this.session, this.model, fields, record); }, this)); }, this)); return this; @@ -149,7 +136,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( * * @returns itself */ - active_id: function () { + active_id: function (fields) { this.rpc('/base/dataset/get', { ids: [this.get_active_id()], model: this.model @@ -158,7 +145,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( this.on_active_id( record && new openerp.base.DataRecord( this.session, this.model, - this._fields, record)); + fields, record)); }, this)); return this; }, diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 8310ca752e7..350ecddeee8 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1,10 +1,24 @@ 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(view_manager, session, element_id, dataset, view_id) { this._super(session, element_id); - this.view_manager; + this.view_manager = view_manager; this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -17,7 +31,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; @@ -92,6 +106,8 @@ openerp.base.FormView = openerp.base.Controller.extend({ }, on_saved: function() { // Check response for exceptions, display error + }, + on_action: function (action) { } }); @@ -463,6 +479,10 @@ openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldDate"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('input').datepicker(); } }); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 46402dc6e3e..86b9a4b4ea6 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -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(view_manager, session, element_id, dataset, view_id) { this._super(session, element_id); @@ -9,6 +10,9 @@ openerp.base.ListView = openerp.base.Controller.extend({ this.model = dataset.model; this.view_id = view_id; this.name = ""; + // TODO: default to action.limit + // TODO: decide if limit is a property of DataSet and thus global to all views (calendar ?) + this.limit = 80; this.cols = []; @@ -20,7 +24,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; @@ -81,6 +85,20 @@ openerp.base.ListView = openerp.base.Controller.extend({ return record.values; })); + }, + do_search: function (domains, contexts, groupbys) { + var self = this; + this.rpc('/base/session/eval_domain_and_context', { + domains: domains, + contexts: contexts, + group_by_seq: groupbys + }, function (results) { + // TODO: handle non-empty results.group_by with read_group + self.dataset.set({ + context: results.context, + domain: results.domain + }).fetch(self.fields_view.fields, 0, self.limit); + }); } }); diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 45f60372b25..fa016549b0e 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -15,7 +15,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) @@ -442,122 +448,6 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({ } }); -openerp.base.search.custom_filters = new openerp.base.Registry({ - 'char': 'openerp.base.search.ExtendedSearchProposition.Char', - 'datetime': 'openerp.base.search.ExtendedSearchProposition.DateTime' -}); - -openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({ - template: 'SearchView.extended_search.proposition', - identifier_prefix: 'extended-search-proposition', - init: function (parent, fields) { - this._super(parent); - this.fields = _(fields).chain() - .map(function(val, key) { return _.extend({}, val, {'name': key}); }) - .sortBy(function(field) {return field.string;}) - .value(); - this.attrs = {_: _, fields: this.fields, selected: null}; - this.value = null; - }, - start: function () { - this._super(); - this.select_field(this.fields.length > 0 ? this.fields[0] : null); - var _this = this; - this.$element.find(".searchview_extended_prop_field").change(function() { - _this.changed(); - }); - var delete_btn = this.$element.find('.searchview_extended_delete_prop'); - delete_btn.click(function (e) { - _this.stop(); - e.stopPropagation(); - e.preventDefault(); - }); - }, - changed: function() { - var nval = this.$element.find(".searchview_extended_prop_field").val(); - if(this.attrs.selected == null || nval != this.attrs.selected.name) { - this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); - } - }, - /** - * Selects the provided field object - * - * @param field a field descriptor object (as returned by fields_get, augmented by the field name) - */ - select_field: function(field) { - var _this = this; - if(this.attrs.selected != null) { - this.value.stop(); - this.value = null; - this.$element.find('.searchview_extended_prop_op').html(''); - } - this.attrs.selected = field; - if(field == null) { - return; - } - - try { - this.value = new (openerp.base.search.custom_filters.get_object(field.type)) - (this); - _.each(this.value.operators, function(operator) { - var option = jQuery('