[MERGE] trunk improvements

bzr revid: xmo@openerp.com-20110920101957-zl6tiuf49rzc0l2v
This commit is contained in:
Xavier Morel 2011-09-20 12:19:57 +02:00
commit f896a2571e
12 changed files with 199 additions and 44 deletions

View File

@ -9,3 +9,4 @@ RE:^include/
RE:^share/
RE:^man/
RE:^lib/
logging.cfg

View File

@ -5,8 +5,7 @@ import logging
_logger = logging.getLogger(__name__)
try:
def wsgi_postload():
import openerp.wsgi
import os
import tempfile
@ -24,9 +23,6 @@ try:
#import openerp.wsgi
openerp.wsgi.register_wsgi_handler(app)
except ImportError:
_logger.info("standalone mode")
# TODO
# if we detect that we are imported from the openerp server register common.Root() as a wsgi entry point

View File

@ -45,5 +45,5 @@
"static/src/css/base.css",
"static/src/css/data_export.css",
],
'wsgi' : 'app',
'post_load' : 'wsgi_postload',
}

View File

@ -43,30 +43,30 @@ class OpenERPSession(object):
def build_connection(self):
return openerplib.get_connection(hostname=self._server, port=self._port,
database=self._db,
database=self._db, login=self._login,
user_id=self._uid, password=self._password)
def proxy(self, service):
return self.build_connection().get_service(service)
def bind(self, db, uid, password):
def bind(self, db, uid, login, password):
self._db = db
self._uid = uid
self._login = login
self._password = password
def login(self, db, login, password):
uid = self.proxy('common').login(db, login, password)
self.bind(db, uid, password)
self._login = login
self.bind(db, uid, login, password)
if uid: self.get_context()
return uid
def assert_valid(self):
def assert_valid(self, force=False):
"""
Ensures this session is valid (logged into the openerp server)
"""
self.build_connection().check_login(False)
self.build_connection().check_login(force)
def execute(self, model, func, *l, **d):
self.assert_valid()

View File

@ -301,6 +301,7 @@ class Session(openerpweb.Controller):
}
@openerpweb.jsonrequest
def get_session_info(self, req):
req.session.assert_valid(force=True)
return {
"uid": req.session._uid,
"context": req.session.get_context() if req.session._uid else False,

View File

@ -501,6 +501,8 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
self.on_session_valid();
else
self.on_session_invalid();
}, function() {
self.on_session_invalid();
});
},
/**
@ -741,6 +743,59 @@ openerp.web.SessionAware = openerp.web.CallbackEnabled.extend(/** @lends openerp
}
});
/**
* Base class for all visual components. Provides a lot of functionalities helpful
* for the management of a part of the DOM.
*
* Widget handles:
* - Rendering with QWeb.
* - Life-cycle management and parenting (when a parent is destroyed, all its children are
* destroyed too).
* - Insertion in DOM.
*
* Widget also extends SessionAware for ease of use.
*
* Guide to create implementations of the Widget class:
* ==============================================
*
* Here is a sample child class:
*
* MyWidget = openerp.base.Widget.extend({
* // the name of the QWeb template to use for rendering
* template: "MyQWebTemplate",
* // identifier prefix, it is useful to put an obvious one for debugging
* identifier_prefix: 'my-id-prefix-',
*
* init: function(parent) {
* this._super(parent);
* // stuff that you want to init before the rendering
* },
* start: function() {
* this._super();
* // stuff you want to make after the rendering, `this.$element` holds a correct value
* this.$element.find(".my_button").click(/* an example of event binding * /);
*
* // if you have some asynchronous operations, it's a good idea to return
* // a promise in start()
* var promise = this.rpc(...);
* return promise;
* }
* });
*
* Now this class can simply be used with the following syntax:
*
* var my_widget = new MyWidget(this);
* my_widget.appendTo($(".some-div"));
*
* With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
* DOM inside the ".some-div" div and its events were binded.
*
* And of course, when you don't need that widget anymore, just do:
*
* my_widget.stop();
*
* That will kill the widget in a clean way and erase its content from the dom.
*/
openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widget# */{
/**
* The name of the QWeb template that will be used for rendering. Must be

View File

@ -26,7 +26,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.set_default_options(options);
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
this.view_id = view_id || false;
this.fields_view = {};
this.widgets = {};
this.widgets_counter = 0;
@ -315,9 +315,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
if (self.can_be_discarded()) {
self.dataset.default_get(_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
var keys = _.keys(self.fields_view.fields);
if (keys.length) {
self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
def.resolve();
});
} else {
self.on_record_loaded({});
def.resolve();
});
}
}
});
return def.promise();
@ -373,7 +379,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
first_invalid_field.focus();
this.on_invalid();
return false;
} else if (form_dirty) {
} else {
console.log("About to save", values);
if (!this.datarecord.id) {
return this.dataset.create(values, function(r) {
@ -384,11 +390,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
self.on_saved(r, success);
});
}
} else {
setTimeout(function() {
self.on_saved({ result: true }, success);
});
return true;
}
},
do_save_edit: function() {
@ -413,7 +414,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
if (!r.result) {
// should not happen in the server, but may happen for internal purpose
} else {
console.debug(_.sprintf("The record #%s has been saved.", this.datarecord.id));
if (success) {
success(r);
}
@ -986,7 +986,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
return !this.invalid;
},
is_dirty: function() {
return this.dirty;
return this.dirty && !this.readonly;
},
get_on_change_value: function() {
return this.get_value();
@ -2551,7 +2551,7 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
});
openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
template: "FieldStatus",
template: "EmptyComponent",
start: function() {
this._super();
this.selected_value = null;

View File

@ -133,11 +133,19 @@ db.web.ActionManager = db.web.Widget.extend({
(this.client_widget = new ClientWidget(this, action.params)).appendTo(this);
},
ir_actions_report_xml: function(action) {
var self = this;
$.blockUI();
this.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
complete: $.unblockUI
self.rpc("/web/session/eval_domain_and_context", {
contexts: [action.context],
domains: []
}).then(function(res) {
action = _.clone(action);
action.context = res.context;
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
complete: $.unblockUI
});
});
}
});

View File

@ -342,22 +342,12 @@
<li>
<a t-att-href="'/' + widget.qs" title="Home" class="home"><img src="/web/static/src/img/header-home.png" width="16" height="16" border="0"/></a>
</li>
<!--
<li>
<a href="#requests" title="Requests" class="requests"><img src="/web/static/src/img/header-requests.png" width="16" height="16" border="0"/><small>1</small></a>
</li>
-->
<li class="preferences">
<a href="#preferences" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
<a href="javascript:void(0)" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
</li>
<li>
<a href="#about" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
<a href="javascript:void(0)" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
</li>
<!--
<li>
<a href="http://doc.openerp.com/v6.0/book?version=$version" title="Help" target="_blank" class="help"><img src="/web/static/src/img/header-help.png" width="16" height="16" border="0"/></a>
</li>
-->
</ul>
<div class="block">
<a href="#logout" class="logout">LOGOUT</a>
@ -1428,9 +1418,6 @@
</p>
</div>
</t>
<t t-name="FieldStatus">
<div t-att-id="widget.element_id"></div>
</t>
<t t-name="FieldStatus.content">
<ul class="oe-arrow-list">
<t t-set="size" t-value="widget.to_show.length"/>

View File

@ -282,6 +282,18 @@ view managers can correctly communicate with them:
defining e.g. the ``start`` method) or at the instance level (in the
class's ``init``), though you should generally set it on the class.
Frequent development tasks
--------------------------
There are a number of tasks which OpenERP Web developers do or will need to
perform quite regularly. To make these easier, we have written a few guides
to help you get started:
.. toctree::
:maxdepth: 1
guides/client-action.rst
Utility behaviors
-----------------

View File

@ -0,0 +1,92 @@
Creating a new client action
============================
Client actions are the client-side of OpenERP's "Server Actions": instead of
allowing for semi-arbitrary code to be executed in the server, they allow
for execution of client-customized code.
On the server side, a client action is an action of type ``ir.actions.client``,
which has (at most) two properties: a mandatory ``tag``, which is an arbitrary
string by which the client will identify the action, and an optional ``params``
which is simply a map of keys and values sent to the client as-is (this way,
client actions can be made generic and reused in multiple contexts).
General Structure
-----------------
In the OpenERP Web code, a client action only requires two pieces of
information:
* Mapping the action's ``tag`` to an OpenERP Web object
* The OpenERP Web object itself, which must inherit from
:js:class:`openerp.web.Widget`
Our example will be the actual code for the widgets client action (a client
action displaying a ``res.widget`` object, used in the homepage dashboard of
the web client):
.. code-block:: javascript
// Registers the object 'openerp.web_dashboard.Widget' to the client
// action tag 'board.home.widgets'
openerp.web.client_actions.add(
'board.home.widgets', 'openerp.web_dashboard.Widget');
// This object inherits from View, but only Widget is required
openerp.web_dashboard.Widget = openerp.web.View.extend({
template: 'HomeWidget'
});
At this point, the generic ``Widget`` lifecycle takes over, the template is
rendered, inserted in the client DOM, bound on the object's ``$element``
property and the object is started.
If the client action takes parameters, these parameters are passed in as a
second positional parameter to the constructor:
.. code-block:: javascript
init: function (parent, params) {
// execute the Widget's init
this._super(parent);
// board.home.widgets only takes a single param, the identifier of the
// res.widget object it should display. Store it for later
this.widget_id = params.widget_id;
}
More complex initialization (DOM manipulations, RPC requests, ...) should be
performed in the ``start()`` method.
.. note::
As required by ``Widget``'s contract, if ``start`` executes any
asynchronous code it should return a ``$.Deferred`` so callers know when
it's ready for interaction.
Although generally speaking client actions are not really interacted with.
.. code-block:: javascript
start: function () {
return $.when(
this._super(),
// Simply read the res.widget object this action should display
new openerp.web.DataSet(this, 'res.widget').read_ids(
[this.widget_id], ['title'], this.on_widget_loaded));
}
The client action can then behave exactly as it wishes to within its root
(``this.$element``). In this case, it performs further renderings once its
widget's content is retrieved:
.. code-block:: javascript
on_widget_loaded: function (widgets) {
var widget = widgets[0];
var url = _.sprintf(
'/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
this.session.session_id, widget.id);
this.$element.html(QWeb.render('HomeWidget.content', {
widget: widget,
url: url
}));
}

View File

@ -37,6 +37,9 @@ optparser.add_option("--log-level", dest="log_level",
default='debug', help="Log level", metavar="LOG_LEVEL")
optparser.add_option("--log-config", dest="log_config",
default='', help="Log config file", metavar="LOG_CONFIG")
optparser.add_option('--multi-threaded', dest='threaded',
default=True, action='store_true',
help="Use multiple threads to handle requests")
import web.common.dispatch
@ -55,5 +58,5 @@ if __name__ == "__main__":
werkzeug.serving.run_simple(
'0.0.0.0', options.socket_port, app,
use_reloader=options.reloader, threaded=True)
use_reloader=options.reloader, threaded=options.threaded)