[MERGE} from trunk
[odoo/odoo.git] / doc / rpc.rst
1 Outside the box: network interactions
2 =====================================
3
4 Building static displays is all nice and good and allows for neat
5 effects (and sometimes you're given data to display from third parties
6 so you don't have to make any effort), but a point generally comes
7 where you'll want to talk to the world and make some network requests.
8
9 OpenERP Web provides two primary APIs to handle this, a low-level
10 JSON-RPC based API communicating with the Python section of OpenERP
11 Web (and of your addon, if you have a Python part) and a high-level
12 API above that allowing your code to talk directly to the OpenERP
13 server, using familiar-looking calls.
14
15 All networking APIs are :doc:`asynchronous </async>`. As a result, all
16 of them will return :js:class:`Deferred` objects (whether they resolve
17 those with values or not). Understanding how those work before before
18 moving on is probably necessary.
19
20 High-level API: calling into OpenERP models
21 -------------------------------------------
22
23 Access to OpenERP object methods (made available through XML-RPC from
24 the server) is done via the :js:class:`openerp.web.Model` class. This
25 class maps onto the OpenERP server objects via two primary methods,
26 :js:func:`~openerp.web.Model.call` and
27 :js:func:`~openerp.web.Model.query`.
28
29 :js:func:`~openerp.web.Model.call` is a direct mapping to the
30 corresponding method of the OpenERP server object. Its usage is
31 similar to that of the OpenERP Model API, with three differences:
32
33 * The interface is :doc:`asynchronous </async>`, so instead of
34   returning results directly RPC method calls will return
35   :js:class:`Deferred` instances, which will themselves resolve to the
36   result of the matching RPC call.
37
38 * Because ECMAScript 3/Javascript 1.5 doesnt feature any equivalent to
39   ``__getattr__`` or ``method_missing``, there needs to be an explicit
40   method to dispatch RPC methods.
41
42 * No notion of pooler, the model proxy is instantiated where needed,
43   not fetched from an other (somewhat global) object
44
45 .. code-block:: javascript
46
47     var Users = new Model('res.users');
48
49     Users.call('change_password', ['oldpassword', 'newpassword'],
50                       {context: some_context}).then(function (result) {
51         // do something with change_password result
52     });
53
54 :js:func:`~openerp.web.Model.query` is a shortcut for a builder-style
55 interface to searches (``search`` + ``read`` in OpenERP RPC terms). It
56 returns a :js:class:`~openerp.web.Query` object which is immutable but
57 allows building new :js:class:`~openerp.web.Query` instances from the
58 first one, adding new properties or modifiying the parent object's:
59
60 .. code-block:: javascript
61
62     Users.query(['name', 'login', 'user_email', 'signature'])
63          .filter([['active', '=', true], ['company_id', '=', main_company]])
64          .limit(15)
65          .all().then(function (users) {
66         // do work with users records
67     });
68
69 The query is only actually performed when calling one of the query
70 serialization methods, :js:func:`~openerp.web.Query.all` and
71 :js:func:`~openerp.web.Query.first`. These methods will perform a new
72 RPC call every time they are called.
73
74 For that reason, it's actually possible to keep "intermediate" queries
75 around and use them differently/add new specifications on them.
76
77 .. js:class:: openerp.web.Model(name)
78
79     .. js:attribute:: openerp.web.Model.name
80
81         name of the OpenERP model this object is bound to
82
83     .. js:function:: openerp.web.Model.call(method[, args][, kwargs])
84
85          Calls the ``method`` method of the current model, with the
86          provided positional and keyword arguments.
87
88          :param String method: method to call over rpc on the
89                                :js:attr:`~openerp.web.Model.name`
90          :param Array<> args: positional arguments to pass to the
91                               method, optional
92          :param Object<> kwargs: keyword arguments to pass to the
93                                  method, optional
94          :rtype: Deferred<>         
95
96     .. js:function:: openerp.web.Model.query(fields)
97
98          :param Array<String> fields: list of fields to fetch during
99                                       the search
100          :returns: a :js:class:`~openerp.web.Query` object
101                    representing the search to perform
102
103 .. js:class:: openerp.web.Query(fields)
104
105     The first set of methods is the "fetching" methods. They perform
106     RPC queries using the internal data of the object they're called
107     on.
108
109     .. js:function:: openerp.web.Query.all()
110
111         Fetches the result of the current
112         :js:class:`~openerp.web.Query` object's search.
113
114         :rtype: Deferred<Array<>>
115
116     .. js:function:: openerp.web.Query.first()
117
118        Fetches the **first** result of the current
119        :js:class:`~openerp.web.Query`, or ``null`` if the current
120        :js:class:`~openerp.web.Query` does have any result.
121
122        :rtype: Deferred<Object | null>
123
124     .. js:function:: openerp.web.Query.count()
125
126        Fetches the number of records the current
127        :js:class:`~openerp.web.Query` would retrieve.
128
129        :rtype: Deferred<Number>
130
131     .. js:function:: openerp.web.Query.group_by(grouping...)
132
133        Fetches the groups for the query, using the first specified
134        grouping parameter
135
136        :param Array<String> grouping: Lists the levels of grouping
137                                       asked of the server. Grouping
138                                       can actually be an array or
139                                       varargs.
140        :rtype: Deferred<Array<openerp.web.Group>> | null
141
142     The second set of methods is the "mutator" methods, they create a
143     **new** :js:class:`~openerp.web.Query` object with the relevant
144     (internal) attribute either augmented or replaced.
145
146     .. js:function:: openerp.web.Query.context(ctx)
147
148        Adds the provided ``ctx`` to the query, on top of any existing
149        context
150
151     .. js:function:: openerp.web.Query.filter(domain)
152
153        Adds the provided domain to the query, this domain is
154        ``AND``-ed to the existing query domain.
155
156     .. js:function:: opeenrp.web.Query.offset(offset)
157
158        Sets the provided offset on the query. The new offset
159        *replaces* the old one.
160
161     .. js:function:: openerp.web.Query.limit(limit)
162
163        Sets the provided limit on the query. The new limit *replaces*
164        the old one.
165
166     .. js:function:: openerp.web.Query.order_by(fields…)
167
168        Overrides the model's natural order with the provided field
169        specifications. Behaves much like Django's `QuerySet.order_by
170        <https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by>`_:
171
172        * Takes 1..n field names, in order of most to least importance
173          (the first field is the first sorting key). Fields are
174          provided as strings.
175
176        * A field specifies an ascending order, unless it is prefixed
177          with the minus sign "``-``" in which case the field is used
178          in the descending order
179
180        Divergences from Django's sorting include a lack of random sort
181        (``?`` field) and the inability to "drill down" into relations
182        for sorting.
183
184 Aggregation (grouping)
185 ~~~~~~~~~~~~~~~~~~~~~~
186
187 OpenERP has powerful grouping capacities, but they are kind-of strange
188 in that they're recursive, and level n+1 relies on data provided
189 directly by the grouping at level n. As a result, while ``read_group``
190 works it's not a very intuitive API.
191
192 OpenERP Web 6.2 eschews direct calls to ``read_group`` in favor of
193 calling a method of :js:class:`~openerp.web.Query`, `much in the way
194 it is one in SQLAlchemy
195 <http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.group_by>`_ [#]_:
196
197 .. code-block:: javascript
198
199     some_query.group_by(['field1', 'field2']).then(function (groups) {
200         // do things with the fetched groups
201     });
202
203 This method is asynchronous when provided with 1..n fields (to group
204 on) as argument, but it can also be called without any field (empty
205 fields collection or nothing at all). In this case, instead of
206 returning a Deferred object it will return ``null``.
207
208 When grouping criterion come from a third-party and may or may not
209 list fields (e.g. could be an empty list), this provides two ways to
210 test the presence of actual subgroups (versus the need to perform a
211 regular query for records):
212
213 * A check on ``group_by``'s result and two completely separate code
214   paths
215
216   .. code-block:: javascript
217
218       var groups;
219       if (groups = some_query.group_by(gby)) {
220           groups.then(function (gs) {
221               // groups
222           });
223       }
224       // no groups
225
226 * Or a more coherent code path using :js:func:`when`'s ability to
227   coerce values into deferreds:
228
229   .. code-block:: javascript
230
231       $.when(some_query.group_by(gby)).then(function (groups) {
232           if (!groups) {
233               // No grouping
234           } else {
235               // grouping, even if there are no groups (groups
236               // itself could be an empty array)
237           }
238       });
239
240 The result of a (successful) :js:func:`~openerp.web.Query.group_by` is
241 an array of :js:class:`~openerp.web.data.Group`.
242
243 Synchronizing views (provisional)
244 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
245
246 .. note:: this API may not be final, and may not even remain
247
248 While the high-level RPC API is mostly stateless, some objects in
249 OpenERP Web need to share state information. One of those is OpenERP
250 views, especially between "collection-based" views (lists, graphs) and
251 "record-based" views (forms, diagrams), which gets its very own API
252 for traversing collections of records, the aptly-named
253 :js:class:`~openerp.web.Traverser`.
254
255 A :js:class:`~openerp.web.Traverser` is linked to a
256 :js:class:`~openerp.web.Model` and is used to iterate over it
257 asynchronously (and using indexes).
258
259 .. js:class:: openerp.web.Traverser(model)
260
261     .. js:function:: openerp.web.Traverser.model()
262
263         :returns: the :js:class:`~openerp.web.Model` this traverser
264                   instance is bound to
265
266     .. js:function:: openerp.web.Traverser.index([idx])
267
268         If provided with an index parameter, sets that as the new
269         index for the traverser.
270
271         :param Number idx: the new index for the traverser
272         :returns: the current index for the traverser
273
274     .. js:function:: openerp.web.Traverser.current([fields])
275
276         Fetches the traverser's "current" record (that is, the record
277         at the current index of the traverser)
278
279         :param Array<String> fields: fields to return in the record
280         :rtype: Deferred<>
281
282     .. js:function:: openerp.web.Traverser.next([fields])
283
284         Increases the traverser's internal index by one, the fetches
285         the corresponding record. Roughly equivalent to:
286
287         .. code-block:: javascript
288
289             var idx = traverser.index();
290             traverser.index(idx+1);
291             traverser.current();
292
293         :param Array<String> fields: fields to return in the record
294         :rtype: Deferred<>
295
296     .. js:function:: openerp.web.Traverser.previous([fields])
297
298         Similar to :js:func:`~openerp.web.Traverser.next` but iterates
299         the traverser backwards rather than forward.
300
301         :param Array<String> fields: fields to return in the record
302         :rtype: Deferred<>
303
304     .. js:function:: openerp.web.Traverser.size()
305
306         Shortcut to checking the size of the backing model, calling
307         ``traverser.size()`` is equivalent to calling
308         ``traverser.model().query([]).count()``
309
310         :rtype: Deferred<Number>
311
312 Low-level API: RPC calls to Python side
313 ---------------------------------------
314
315 While the previous section is great for calling core OpenERP code
316 (models code), it does not work if you want to call the Python side of
317 OpenERP Web.
318
319 For this, a lower-level API exists on on
320 :js:class:`~openerp.web.Connection` objects (usually available through
321 ``openerp.connection``): the ``rpc`` method.
322
323 This method simply takes an absolute path (which is the combination of
324 the Python controller's ``_cp_path`` attribute and the name of the
325 method you want to call) and a mapping of attributes to values (applied
326 as keyword arguments on the Python method [#]_). This function fetches
327 the return value of the Python methods, converted to JSON.
328
329 For instance, to call the ``eval_domain_and_context`` of the
330 :class:`~web.controllers.main.Session` controller:
331
332 .. code-block:: javascript
333
334     openerp.connection.rpc('/web/session/eval_domain_and_context', {
335         domains: ds,
336         contexts: cs
337     }).then(function (result) {
338         // handle result
339     });
340
341 .. [#] with a small twist: SQLAlchemy's ``orm.query.Query.group_by``
342        is not terminal, it returns a query which can still be altered.
343
344 .. [#] except for ``context``, which is extracted and stored in the
345        request object itself.