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.
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.
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.
20 High-level API: calling into OpenERP models
21 -------------------------------------------
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`.
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:
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.
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.
42 * No notion of pooler, the model proxy is instantiated where needed,
43 not fetched from an other (somewhat global) object
45 .. code-block:: javascript
47 var Users = new Model('res.users');
49 Users.call('change_password', ['oldpassword', 'newpassword'],
50 {context: some_context}).then(function (result) {
51 // do something with change_password result
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:
60 .. code-block:: javascript
62 Users.query(['name', 'login', 'user_email', 'signature'])
63 .filter([['active', '=', true], ['company_id', '=', main_company]])
65 .all().then(function (users) {
66 // do work with users records
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.
74 For that reason, it's actually possible to keep "intermediate" queries
75 around and use them differently/add new specifications on them.
77 .. js:class:: openerp.web.Model(name)
79 .. js:attribute:: openerp.web.Model.name
81 name of the OpenERP model this object is bound to
83 .. js:function:: openerp.web.Model.call(method[, args][, kwargs])
85 Calls the ``method`` method of the current model, with the
86 provided positional and keyword arguments.
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
92 :param Object<> kwargs: keyword arguments to pass to the
96 .. js:function:: openerp.web.Model.query(fields)
98 :param Array<String> fields: list of fields to fetch during
100 :returns: a :js:class:`~openerp.web.Query` object
101 representing the search to perform
103 .. js:class:: openerp.web.Query(fields)
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
109 .. js:function:: openerp.web.Query.all()
111 Fetches the result of the current
112 :js:class:`~openerp.web.Query` object's search.
114 :rtype: Deferred<Array<>>
116 .. js:function:: openerp.web.Query.first()
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.
122 :rtype: Deferred<Object | null>
124 .. js:function:: openerp.web.Query.count()
126 Fetches the number of records the current
127 :js:class:`~openerp.web.Query` would retrieve.
129 :rtype: Deferred<Number>
131 .. js:function:: openerp.web.Query.group_by(grouping...)
133 Fetches the groups for the query, using the first specified
136 :param Array<String> grouping: Lists the levels of grouping
137 asked of the server. Grouping
138 can actually be an array or
140 :rtype: Deferred<Array<openerp.web.QueryGroup>> | null
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.
146 .. js:function:: openerp.web.Query.context(ctx)
148 Adds the provided ``ctx`` to the query, on top of any existing
151 .. js:function:: openerp.web.Query.filter(domain)
153 Adds the provided domain to the query, this domain is
154 ``AND``-ed to the existing query domain.
156 .. js:function:: opeenrp.web.Query.offset(offset)
158 Sets the provided offset on the query. The new offset
159 *replaces* the old one.
161 .. js:function:: openerp.web.Query.limit(limit)
163 Sets the provided limit on the query. The new limit *replaces*
166 .. js:function:: openerp.web.Query.order_by(fields…)
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>`_:
172 * Takes 1..n field names, in order of most to least importance
173 (the first field is the first sorting key). Fields are
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
180 Divergences from Django's sorting include a lack of random sort
181 (``?`` field) and the inability to "drill down" into relations
184 Aggregation (grouping)
185 ~~~~~~~~~~~~~~~~~~~~~~
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.
192 OpenERP Web 7.0 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>`_ [#]_:
197 .. code-block:: javascript
199 some_query.group_by(['field1', 'field2']).then(function (groups) {
200 // do things with the fetched groups
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``.
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):
213 * A check on ``group_by``'s result and two completely separate code
216 .. code-block:: javascript
219 if (groups = some_query.group_by(gby)) {
220 groups.then(function (gs) {
226 * Or a more coherent code path using :js:func:`when`'s ability to
227 coerce values into deferreds:
229 .. code-block:: javascript
231 $.when(some_query.group_by(gby)).then(function (groups) {
235 // grouping, even if there are no groups (groups
236 // itself could be an empty array)
240 The result of a (successful) :js:func:`~openerp.web.Query.group_by` is
241 an array of :js:class:`~openerp.web.QueryGroup`.
245 Low-level API: RPC calls to Python side
246 ---------------------------------------
248 While the previous section is great for calling core OpenERP code
249 (models code), it does not work if you want to call the Python side of
252 For this, a lower-level API exists on on
253 :js:class:`~openerp.web.Connection` objects (usually available through
254 ``openerp.connection``): the ``rpc`` method.
256 This method simply takes an absolute path (which is the combination of
257 the Python controller's ``_cp_path`` attribute and the name of the
258 method you want to call) and a mapping of attributes to values (applied
259 as keyword arguments on the Python method [#]_). This function fetches
260 the return value of the Python methods, converted to JSON.
262 For instance, to call the ``resequence`` of the
263 :class:`~web.controllers.main.DataSet` controller:
265 .. code-block:: javascript
267 openerp.connection.rpc('/web/dataset/resequence', {
271 }).then(function (result) {
272 // resequenced on server
275 .. [#] with a small twist: SQLAlchemy's ``orm.query.Query.group_by``
276 is not terminal, it returns a query which can still be altered.
278 .. [#] except for ``context``, which is extracted and stored in the
279 request object itself.