[FIX] web: add missing icon, add warning, fix deferred
[odoo/odoo.git] / doc / api_integration.rst
1 :classes: stripe
2
3 ===========
4 Web Service
5 ===========
6
7 Odoo is mostly extended internally via modules, but much of its features and
8 all of its data is also available from the outside for external analysis or
9 integration with various tools. Part of the :ref:`reference/orm/model` API is
10 easily available over XML-RPC_ and accessible from a variety of languages.
11
12 .. Odoo XML-RPC idiosyncracies:
13    * uses multiple endpoint and a nested call syntax instead of a
14      "hierarchical" server structure (e.g. ``openerp.res.partner.read()``)
15    * uses its own own manual auth system instead of basic auth or sessions
16      (basic is directly supported the Python and Ruby stdlibs as well as
17      ws-xmlrpc, not sure about ripcord)
18    * own auth is inconvenient as (uid, password) have to be explicitly passed
19      into every call. Session would allow db to be stored as well
20    These issues are especially visible in Java, somewhat less so in PHP
21
22 Connection
23 ==========
24
25 .. kinda gross because it duplicates existing bits
26
27 .. only:: html
28
29     .. rst-class:: setupcode hidden
30
31         .. code-block:: python
32
33             import xmlrpclib
34             info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
35             url, db, username, password = \
36                 info['host'], info['database'], info['user'], info['password']
37             common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
38             uid = common.authenticate(db, username, password, {})
39             models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
40
41         .. code-block:: ruby
42
43             require "xmlrpc/client"
44             info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
45             url, db, username, password = \
46                 info['host'], info['database'], info['user'], info['password']
47             common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
48             uid = common.call('authenticate', db, username, password, {})
49             models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
50
51         .. code-block:: php
52
53             require_once('ripcord.php');
54             $info = ripcord::client('https://demo.odoo.com/start')->start();
55             list($url, $db, $username, $password) =
56               array($info['host'], $info['database'], $info['user'], $info['password']);
57             $common = ripcord::client("$url/xmlrpc/2/common");
58             $uid = $common->authenticate($db, $username, $password, array());
59             $models = ripcord::client("$url/xmlrpc/2/object");
60
61         .. code-block:: java
62
63             final XmlRpcClient client = new XmlRpcClient();
64             final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
65             start_config.setServerURL(new URL("https://demo.odoo.com/start"));
66             final Map<String, String> info = (Map<String, String>)client.execute(
67                 start_config, "start", emptyList());
68
69             final String url = info.get("host"),
70                           db = info.get("database"),
71                     username = info.get("user"),
72                     password = info.get("password");
73
74             final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
75             common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
76
77             int uid = (int)client.execute(
78                 common_config, "authenticate", Arrays.asList(
79                     db, username, password, emptyMap()));
80
81             final XmlRpcClient models = new XmlRpcClient() {{
82                 setConfig(new XmlRpcClientConfigImpl() {{
83                     setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
84                 }});
85             }};
86
87 Configuration
88 -------------
89
90 If you already have an Odoo server installed, you can just use its
91 parameters
92
93 .. rst-class:: switchable setup
94
95     .. code-block:: python
96
97         url = <insert server URL>
98         db = <insert database name>
99         username = 'admin'
100         password = <insert password for your admin user (default: admin)>
101
102     .. code-block:: ruby
103
104         url = <insert server URL>
105         db = <insert database name>
106         username = "admin"
107         password = <insert password for your admin user (default: admin)>
108
109     .. code-block:: php
110
111         $url = <insert server URL>;
112         $db = <insert database name>;
113         $username = "admin";
114         $password = <insert password for your admin user (default: admin)>;
115
116     .. code-block:: java
117
118         final String url = <insert server URL>,
119                       db = <insert database name>,
120                 username = "admin",
121                 password = <insert password for your admin user (default: admin)>;
122
123 To make exploration simpler, you can also ask https://demo.odoo.com for a test
124 database:
125
126 .. rst-class:: switchable setup
127
128     .. code-block:: python
129
130         import xmlrpclib
131         info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
132         url, db, username, password = \
133             info['host'], info['database'], info['user'], info['password']
134
135     .. code-block:: ruby
136
137         require "xmlrpc/client"
138         info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
139         url, db, username, password = \
140             info['host'], info['database'], info['user'], info['password']
141
142     .. code-block:: php
143
144         require_once('ripcord.php');
145         $info = ripcord::client('https://demo.odoo.com/start')->start();
146         list($url, $db, $username, $password) =
147           array($info['host'], $info['database'], $info['user'], $info['password']);
148
149     .. code-block:: java
150
151         final XmlRpcClient client = new XmlRpcClient();
152
153         final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
154         start_config.setServerURL(new URL("https://demo.odoo.com/start"));
155         final Map<String, String> info = (Map<String, String>)client.execute(
156             start_config, "start", emptyList());
157
158         final String url = info.get("host"),
159                       db = info.get("database"),
160                 username = info.get("user"),
161                 password = info.get("password");
162
163 .. rst-class:: force-right
164
165     .. note::
166         :class: only-php
167
168         These examples use the `Ripcord <https://code.google.com/p/ripcord/>`_
169         library, which provides a simple XML-RPC API. Ripcord requires that
170         `XML-RPC support be enabled
171         <http://php.net/manual/en/xmlrpc.installation.php>`_ in your PHP
172         installation.
173
174         Since calls are performed over
175         `HTTPS <http://en.wikipedia.org/wiki/HTTP_Secure>`_, it also requires that
176         the `OpenSSL extension
177         <http://php.net/manual/en/openssl.installation.php>`_ be enabled.
178
179     .. note::
180         :class: only-java
181
182         These examples use the `Apache XML-RPC library
183         <https://ws.apache.org/xmlrpc/>`_
184
185         The examples do not include imports as these imports couldn't be
186         pasted in the code.
187
188 Logging in
189 ----------
190
191 Odoo requires users of the API to be authenticated before being able to query
192 much data.
193
194 The ``xmlrpc/2/common`` endpoint provides meta-calls which don't require
195 authentication, such as the authentication itself or fetching version
196 information. To verify if the connection information is correct before trying
197 to authenticate, the simplest call is to ask for the server's version. The
198 authentication itself is done through the ``authenticate`` function and
199 returns a user identifier (``uid``) used in authenticated calls instead of
200 the login.
201
202 .. rst-class:: switchable setup
203
204     .. code-block:: python
205
206         common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
207         common.version()
208
209     .. code-block:: ruby
210
211         common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
212         common.call('version')
213
214     .. code-block:: php
215
216         $common = ripcord::client("$url/xmlrpc/2/common");
217         $common->version();
218
219     .. code-block:: java
220
221         final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
222         common_config.setServerURL(
223             new URL(String.format("%s/xmlrpc/2/common", url)));
224         client.execute(common_config, "version", emptyList());
225
226 .. code-block:: json
227
228     {
229         "server_version": "8.0",
230         "server_version_info": [8, 0, 0, "final", 0],
231         "server_serie": "8.0",
232         "protocol_version": 1,
233     }
234
235 .. rst-class:: switchable setup
236
237     .. code-block:: python
238
239         uid = common.authenticate(db, username, password, {})
240
241     .. code-block:: ruby
242
243         uid = common.call('authenticate', db, username, password, {})
244
245     .. code-block:: php
246
247         $uid = $common->authenticate($db, $username, $password, array());
248
249     .. code-block:: java
250
251         int uid = (int)client.execute(
252             common_config, "authenticate", asList(
253                 db, username, password, emptyMap()));
254
255 Calling methods
256 ===============
257
258 The second endpoint is ``xmlrpc/2/object``, is used to call methods of odoo
259 models via the ``execute_kw`` RPC function.
260
261 Each call to ``execute_kw`` takes the following parameters:
262
263 * the database to use, a string
264 * the user id (retrieved through ``authenticate``), an integer
265 * the user's password, a string
266 * the model name, a string
267 * the method name, a string
268 * an array/list of parameters passed by position
269 * a mapping/dict of parameters to pass by keyword (optional)
270
271 .. rst-class:: force-right
272
273 For instance to see if we can read the ``res.partner`` model we can call
274 ``check_access_rights`` with ``operation`` passed by position and
275 ``raise_exception`` passed by keyword (in order to get a true/false result
276 rather than true/error):
277
278 .. rst-class:: switchable setup
279
280     .. code-block:: python
281
282         models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
283         models.execute_kw(db, uid, password,
284             'res.partner', 'check_access_rights',
285             ['read'], {'raise_exception': False})
286
287     .. code-block:: ruby
288
289         models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
290         models.execute_kw(db, uid, password,
291             'res.partner', 'check_access_rights',
292             ['read'], {raise_exception: false})
293
294     .. code-block:: php
295
296         $models = ripcord::client("$url/xmlrpc/2/object");
297         $models->execute_kw($db, $uid, $password,
298             'res.partner', 'check_access_rights',
299             array('read'), array('raise_exception' => false));
300
301     .. code-block:: java
302
303         final XmlRpcClient models = new XmlRpcClient() {{
304             setConfig(new XmlRpcClientConfigImpl() {{
305                 setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
306             }});
307         }};
308         models.execute("execute_kw", asList(
309             db, uid, password,
310             "res.partner", "check_access_rights",
311             asList("read"),
312             new HashMap() {{ put("raise_exception", false); }}
313         ));
314
315 .. code-block:: json
316
317     true
318
319 .. todo:: this should be runnable and checked
320
321 List records
322 ------------
323
324 Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
325
326 :meth:`~openerp.models.Model.search` takes a mandatory
327 :ref:`domain <reference/orm/domains>` filter (possibly empty), and returns the
328 database identifiers of all records matching the filter. To list customer
329 companies for instance:
330
331 .. rst-class:: switchable
332
333     .. code-block:: python
334
335         models.execute_kw(db, uid, password,
336             'res.partner', 'search',
337             [[['is_company', '=', True], ['customer', '=', True]]])
338
339     .. code-block:: ruby
340
341         models.execute_kw(db, uid, password,
342             'res.partner', 'search',
343             [[['is_company', '=', true], ['customer', '=', true]]])
344
345     .. code-block:: php
346
347         $models->execute_kw($db, $uid, $password,
348             'res.partner', 'search', array(
349                 array(array('is_company', '=', true),
350                       array('customer', '=', true))));
351
352     .. code-block:: java
353
354         asList((Object[])models.execute("execute_kw", asList(
355             db, uid, password,
356             "res.partner", "search",
357             asList(asList(
358                 asList("is_company", "=", true),
359                 asList("customer", "=", true)))
360         )));
361
362 .. code-block:: json
363
364     [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
365
366 Pagination
367 ''''''''''
368
369 By default a research will return the ids of all records matching the
370 condition, which may be a huge number. ``offset`` and ``limit`` parameters are
371 available to only retrieve a subset of all matched records.
372
373 .. rst-class:: switchable
374
375     .. code-block:: python
376
377         models.execute_kw(db, uid, password,
378             'res.partner', 'search',
379             [[['is_company', '=', True], ['customer', '=', True]]],
380             {'offset': 10, 'limit': 5})
381
382     .. code-block:: ruby
383
384         models.execute_kw(db, uid, password,
385             'res.partner', 'search',
386             [[['is_company', '=', true], ['customer', '=', true]]],
387             {offset: 10, limit: 5})
388
389     .. code-block:: php
390
391         $models->execute_kw($db, $uid, $password,
392             'res.partner', 'search',
393             array(array(array('is_company', '=', true),
394                         array('customer', '=', true))),
395             array('offset'=>10, 'limit'=>5));
396
397     .. code-block:: java
398
399         asList((Object[])models.execute("execute_kw", asList(
400             db, uid, password,
401             "res.partner", "search",
402             asList(asList(
403                 asList("is_company", "=", true),
404                 asList("customer", "=", true))),
405             new HashMap() {{ put("offset", 10); put("limit", 5); }}
406         )));
407
408 .. code-block:: json
409
410     [13, 20, 30, 22, 29]
411
412 Count records
413 -------------
414
415 Rather than retrieve a possibly gigantic list of records and count them
416 afterwards, :meth:`~openerp.models.Model.search_count` can be used to retrieve
417 only the number of records matching the query. It takes the same
418 :ref:`domain <reference/orm/domains>` filter as
419 :meth:`~openerp.models.Model.search` and no other parameter.
420
421 .. rst-class:: switchable
422
423     .. code-block:: python
424
425         models.execute_kw(db, uid, password,
426             'res.partner', 'search_count',
427             [[['is_company', '=', True], ['customer', '=', True]]])
428
429     .. code-block:: ruby
430
431         models.execute_kw(db, uid, password,
432             'res.partner', 'search_count',
433             [[['is_company', '=', true], ['customer', '=', true]]])
434
435     .. code-block:: php
436
437         $models->execute_kw($db, $uid, $password,
438             'res.partner', 'search_count',
439             array(array(array('is_company', '=', true),
440                         array('customer', '=', true))));
441
442     .. code-block:: java
443
444         (Integer)models.execute("execute_kw", asList(
445             db, uid, password,
446             "res.partner", "search_count",
447             asList(asList(
448                 asList("is_company", "=", true),
449                 asList("customer", "=", true)))
450         ));
451
452 .. code-block:: json
453
454     19
455
456 .. warning::
457
458     calling ``search`` then ``search_count`` (or the other way around) may not
459     yield coherent results if other users are using the server: stored data
460     could have changed between the calls
461
462 Read records
463 ------------
464
465 Record data is accessible via the :meth:`~openerp.models.Model.read` method,
466 which takes a list of ids (as returned by
467 :meth:`~openerp.models.Model.search`) and optionally a list of fields to
468 fetch. By default, it will fetch all the fields the current user can read,
469 which tends to be a huge amount.
470
471 .. rst-class:: switchable
472
473     .. code-block:: python
474
475         ids = models.execute_kw(db, uid, password,
476             'res.partner', 'search',
477             [[['is_company', '=', True], ['customer', '=', True]]],
478             {'limit': 1})
479         [record] = models.execute_kw(db, uid, password,
480             'res.partner', 'read', [ids])
481         # count the number of fields fetched by default
482         len(record)
483
484     .. code-block:: ruby
485
486         ids = models.execute_kw(db, uid, password,
487             'res.partner', 'search',
488             [[['is_company', '=', true], ['customer', '=', true]]],
489             {limit: 1})
490         record = models.execute_kw(db, uid, password,
491             'res.partner', 'read', [ids]).first
492         # count the number of fields fetched by default
493         record.length
494
495     .. code-block:: php
496
497         $ids = $models->execute_kw($db, $uid, $password,
498             'res.partner', 'search',
499             array(array(array('is_company', '=', true),
500                         array('customer', '=', true))),
501             array('limit'=>1));
502         $records = $models->execute_kw($db, $uid, $password,
503             'res.partner', 'read', array($ids));
504         // count the number of fields fetched by default
505         count($records[0]);
506
507     .. code-block:: java
508
509         final List ids = asList((Object[])models.execute(
510             "execute_kw", asList(
511                 db, uid, password,
512                 "res.partner", "search",
513                 asList(asList(
514                     asList("is_company", "=", true),
515                     asList("customer", "=", true))),
516                 new HashMap() {{ put("limit", 1); }})));
517         final Map record = (Map)((Object[])models.execute(
518             "execute_kw", asList(
519                 db, uid, password,
520                 "res.partner", "read",
521                 asList(ids)
522             )
523         ))[0];
524         // count the number of fields fetched by default
525         record.size();
526
527 .. code-block:: json
528
529     121
530
531 Conversedly, picking only three fields deemed interesting.
532
533 .. rst-class:: switchable
534
535     .. code-block:: python
536
537         models.execute_kw(db, uid, password,
538             'res.partner', 'read',
539             [ids], {'fields': ['name', 'country_id', 'comment']})
540
541     .. code-block:: ruby
542
543         models.execute_kw(db, uid, password,
544             'res.partner', 'read',
545             [ids], {fields: %w(name country_id comment)})
546
547     .. code-block:: php
548
549         $models->execute_kw($db, $uid, $password,
550             'res.partner', 'read',
551             array($ids),
552             array('fields'=>array('name', 'country_id', 'comment')));
553
554     .. code-block:: java
555
556         asList((Object[])models.execute("execute_kw", asList(
557             db, uid, password,
558             "res.partner", "read",
559             asList(ids),
560             new HashMap() {{
561                 put("fields", asList("name", "country_id", "comment"));
562             }}
563         )));
564
565 .. code-block:: json
566
567     [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
568
569 .. note:: even if the ``id`` field is not requested, it is always returned
570
571 Listing record fields
572 ---------------------
573
574 :meth:`~openerp.models.Model.fields_get` can be used to inspect
575 a model's fields and check which ones seem to be of interest.
576
577 Because
578 it returns a great amount of meta-information (it is also used by client
579 programs) it should be filtered before printing, the most interesting items
580 for a human user are ``string`` (the field's label), ``help`` (a help text if
581 available) and ``type`` (to know which values to expect, or to send when
582 updating a record):
583
584 .. rst-class:: switchable
585
586     .. code-block:: python
587
588         models.execute_kw(
589             db, uid, password, 'res.partner', 'fields_get',
590             [], {'attributes': ['string', 'help', 'type']})
591
592     .. code-block:: ruby
593
594         models.execute_kw(
595             db, uid, password, 'res.partner', 'fields_get',
596             [], {attributes: %w(string help type)})
597
598     .. code-block:: php
599
600         $models->execute_kw($db, $uid, $password,
601             'res.partner', 'fields_get',
602             array(), array('attributes' => array('string', 'help', 'type')));
603
604     .. code-block:: java
605
606         (Map<String, Map<String, Object>>)models.execute("execute_kw", asList(
607             db, uid, password,
608             "res.partner", "fields_get",
609             emptyList(),
610             new HashMap() {{
611                 put("attributes", asList("string", "help", "type"));
612             }}
613         ));
614
615 .. code-block:: json
616
617     {
618         "ean13": {
619             "type": "char",
620             "help": "BarCode",
621             "string": "EAN13"
622         },
623         "property_account_position": {
624             "type": "many2one",
625             "help": "The fiscal position will determine taxes and accounts used for the partner.",
626             "string": "Fiscal Position"
627         },
628         "signup_valid": {
629             "type": "boolean",
630             "help": "",
631             "string": "Signup Token is Valid"
632         },
633         "date_localization": {
634             "type": "date",
635             "help": "",
636             "string": "Geo Localization Date"
637         },
638         "ref_companies": {
639             "type": "one2many",
640             "help": "",
641             "string": "Companies that refers to partner"
642         },
643         "sale_order_count": {
644             "type": "integer",
645             "help": "",
646             "string": "# of Sales Order"
647         },
648         "purchase_order_count": {
649             "type": "integer",
650             "help": "",
651             "string": "# of Purchase Order"
652         },
653
654 Search and read
655 ---------------
656
657 Because that is a very common task, Odoo provides a
658 :meth:`~openerp.models.Model.search_read` shortcut which as its name notes is
659 equivalent to a :meth:`~openerp.models.Model.search` followed by a
660 :meth:`~openerp.models.Model.read`, but avoids having to perform two requests
661 and keep ids around.
662
663 Its arguments are similar to :meth:`~openerp.models.Model.search`'s, but it
664 can also take a list of ``fields`` (like :meth:`~openerp.models.Model.read`,
665 if that list is not provided it'll fetch all fields of matched records):
666
667 .. rst-class:: switchable
668
669     .. code-block:: python
670
671         models.execute_kw(db, uid, password,
672             'res.partner', 'search_read',
673             [[['is_company', '=', True], ['customer', '=', True]]],
674             {'fields': ['name', 'country_id', 'comment'], 'limit': 5})
675
676     .. code-block:: ruby
677
678         models.execute_kw(db, uid, password,
679             'res.partner', 'search_read',
680             [[['is_company', '=', true], ['customer', '=', true]]],
681             {fields: %w(name country_id comment), limit: 5})
682
683     .. code-block:: php
684
685         $models->execute_kw($db, $uid, $password,
686             'res.partner', 'search_read',
687             array(array(array('is_company', '=', true),
688                         array('customer', '=', true))),
689             array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
690
691     .. code-block:: java
692
693         asList((Object[])models.execute("execute_kw", asList(
694             db, uid, password,
695             "res.partner", "search_read",
696             asList(asList(
697                 asList("is_company", "=", true),
698                 asList("customer", "=", true))),
699             new HashMap() {{
700                 put("fields", asList("name", "country_id", "comment"));
701                 put("limit", 5);
702             }}
703         )));
704
705 .. code-block:: json
706
707     [
708         {
709             "comment": false,
710             "country_id": [ 21, "Belgium" ],
711             "id": 7,
712             "name": "Agrolait"
713         },
714         {
715             "comment": false,
716             "country_id": [ 76, "France" ],
717             "id": 18,
718             "name": "Axelor"
719         },
720         {
721             "comment": false,
722             "country_id": [ 233, "United Kingdom" ],
723             "id": 12,
724             "name": "Bank Wealthy and sons"
725         },
726         {
727             "comment": false,
728             "country_id": [ 105, "India" ],
729             "id": 14,
730             "name": "Best Designers"
731         },
732         {
733             "comment": false,
734             "country_id": [ 76, "France" ],
735             "id": 17,
736             "name": "Camptocamp"
737         }
738     ]
739
740
741 Create records
742 --------------
743
744 Records of a model are created using :meth:`~openerp.models.Model.create`. The
745 method will create a single record and return its database identifier.
746
747 :meth:`~openerp.models.Model.create` takes a mapping of fields to values, used
748 to initialize the record. For any field which has a default value and is not
749 set through the mapping argument, the default value will be used.
750
751 .. rst-class:: switchable
752
753     .. code-block:: python
754
755         id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
756             'name': "New Partner",
757         }])
758
759     .. code-block:: ruby
760
761         id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
762             name: "New Partner",
763         }])
764
765     .. code-block:: php
766
767         $id = $models->execute_kw($db, $uid, $password,
768             'res.partner', 'create',
769             array(array('name'=>"New Partner")));
770
771     .. code-block:: java
772
773         final Integer id = (Integer)models.execute("execute_kw", asList(
774             db, uid, password,
775             "res.partner", "create",
776             asList(new HashMap() {{ put("name", "New Partner"); }})
777         ));
778
779 .. code-block:: json
780
781     78
782
783 .. warning::
784
785     while most value types are what would be expected (integer for
786     :class:`~openerp.fields.Integer`, string for :class:`~openerp.fields.Char`
787     or :class:`~openerp.fields.Text`),
788
789     * :class:`~openerp.fields.Date`, :class:`~openerp.fields.Datetime` and
790       :class:`~openerp.fields.Binary` fields use string values
791     * :class:`~openerp.fields.One2many` and :class:`~openerp.fields.Many2many`
792       use a special command protocol detailed in :meth:`the documentation to
793       the write method <openerp.models.Model.write>`.
794
795 Update records
796 --------------
797
798 Reccords can be updated using :meth:`~openerp.models.Model.write`, it takes
799 a list of records to update and a mapping of updated fields to values similar
800 to :meth:`~openerp.models.Model.create`.
801
802 Multiple records can be updated simultanously, but they will all get the same
803 values for the fields being set. It is not currently possible to perform
804 "computed" updates (where the value being set depends on an existing value of
805 a record).
806
807 .. rst-class:: switchable
808
809     .. code-block:: python
810
811         models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
812             'name': "Newer partner"
813         }])
814         # get record name after having changed it
815         models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
816
817     .. code-block:: ruby
818
819         models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
820             name: "Newer partner"
821         }])
822         # get record name after having changed it
823         models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
824
825     .. code-block:: php
826
827         $models->execute_kw($db, $uid, $password, 'res.partner', 'write',
828             array(array($id), array('name'=>"Newer partner")));
829         // get record name after having changed it
830         $models->execute_kw($db, $uid, $password,
831             'res.partner', 'name_get', array(array($id)));
832
833     .. code-block:: java
834
835         models.execute("execute_kw", asList(
836             db, uid, password,
837             "res.partner", "write",
838             asList(
839                 asList(id),
840                 new HashMap() {{ put("name", "Newer Partner"); }}
841             )
842         ));
843         // get record name after having changed it
844         asList((Object[])models.execute("execute_kw", asList(
845             db, uid, password,
846             "res.partner", "name_get",
847             asList(asList(id))
848         )));
849
850 .. code-block:: json
851
852     [[78, "Newer partner"]]
853
854 Delete records
855 --------------
856
857 Records can be deleted in bulk by providing the ids of all records to remove
858 to :meth:`~openerp.models.Model.unlink`.
859
860 .. rst-class:: switchable
861
862     .. code-block:: python
863
864         models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
865         # check if the deleted record is still in the database
866         models.execute_kw(db, uid, password,
867             'res.partner', 'search', [[['id', '=', id]]])
868
869     .. code-block:: ruby
870
871         models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
872         # check if the deleted record is still in the database
873         models.execute_kw(db, uid, password,
874             'res.partner', 'search', [[['id', '=', id]]])
875
876     .. code-block:: php
877
878         $models->execute_kw($db, $uid, $password,
879             'res.partner', 'unlink',
880             array(array($id)));
881         // check if the deleted record is still in the database
882         $models->execute_kw($db, $uid, $password,
883             'res.partner', 'search',
884             array(array(array('id', '=', $id))));
885
886     .. code-block:: java
887
888         models.execute("execute_kw", asList(
889             db, uid, password,
890             "res.partner", "unlink",
891             asList(asList(id))));
892         // check if the deleted record is still in the database
893         asList((Object[])models.execute("execute_kw", asList(
894             db, uid, password,
895             "res.partner", "search",
896             asList(asList(asList("id", "=", 78)))
897         )));
898
899 .. code-block:: json
900
901     []
902
903 Inspection and introspection
904 ----------------------------
905
906 .. todo:: ``get_external_id`` is kinda crap and may not return an id: it just
907           gets a random existing xid but won't generate one if there is no
908           xid currently associated with the record. And operating with xids
909           isn't exactly fun in RPC.
910
911 While we previously used :meth:`~openerp.models.Model.fields_get` to query a
912 model's and have been using an arbitrary model from the start, Odoo stores
913 most model metadata inside a few meta-models which allow both querying the
914 system and altering models and fields (with some limitations) on the fly over
915 XML-RPC.
916
917 .. _reference/webservice/inspection/models:
918
919 ``ir.model``
920 ''''''''''''
921
922 Provides informations about Odoo models themselves via its various fields
923
924 ``name``
925     a human-readable description of the model
926 ``model``
927     the name of each model in the system
928 ``state``
929     whether the model was generated in Python code (``base``) or by creating
930     an ``ir.model`` record (``manual``)
931 ``field_id``
932     list of the model's fields through a :class:`~openerp.fields.One2many` to
933     :ref:`reference/webservice/inspection/fields`
934 ``view_ids``
935     :class:`~openerp.fields.One2many` to the :ref:`reference/views` defined
936     for the model
937 ``access_ids``
938     :class:`~openerp.fields.One2many` relation to the
939     :ref:`reference/security/acl` set on the model
940
941 ``ir.model`` can be used to
942
943 * query the system for installed models (as a precondition to operations
944   on the model or to explore the system's content)
945 * get information about a specific model (generally by listing the fields
946   associated with it)
947 * create new models dynamically over RPC
948
949 .. warning::
950
951     * "custom" model names must start with ``x_``
952     * the ``state`` must be provided and ``manual``, otherwise the model will
953       not be loaded
954     * it is not possible to add new *methods* to a custom model, only fields
955
956 .. rst-class:: force-right
957
958     a custom model will initially contain only the "built-in" fields available
959     on all models:
960
961 .. rst-class:: switchable
962
963     .. code-block:: python
964
965         models.execute_kw(db, uid, password, 'ir.model', 'create', [{
966             'name': "Custom Model",
967             'model': "x_custom_model",
968             'state': 'manual',
969         }])
970         models.execute_kw(
971             db, uid, password, 'x_custom_model', 'fields_get',
972             [], {'attributes': ['string', 'help', 'type']})
973
974     .. code-block:: php
975
976         $models->execute_kw(
977             $db, $uid, $password,
978             'ir.model', 'create', array(array(
979                 'name' => "Custom Model",
980                 'model' => 'x_custom_model',
981                 'state' => 'manual'
982             ))
983         );
984         $models->execute_kw(
985             $db, $uid, $password,
986             'x_custom_model', 'fields_get',
987             array(),
988             array('attributes' => array('string', 'help', 'type'))
989         );
990
991     .. code-block:: ruby
992
993         models.execute_kw(
994             db, uid, password,
995             'ir.model', 'create', [{
996                 name: "Custom Model",
997                 model: 'x_custom_model',
998                 state: 'manual'
999             }])
1000         fields = models.execute_kw(
1001             db, uid, password, 'x_custom_model', 'fields_get',
1002             [], {attributes: %w(string help type)})
1003
1004     .. code-block:: java
1005
1006         models.execute(
1007             "execute_kw", asList(
1008                 db, uid, password,
1009                 "ir.model", "create",
1010                 asList(new HashMap<String, Object>() {{
1011                     put("name", "Custom Model");
1012                     put("model", "x_custom_model");
1013                     put("state", "manual");
1014                 }})
1015         ));
1016         final Object fields = models.execute(
1017             "execute_kw", asList(
1018                 db, uid, password,
1019                 "x_custom_model", "fields_get",
1020                 emptyList(),
1021                 new HashMap<String, Object> () {{
1022                     put("attributes", asList(
1023                             "string",
1024                             "help",
1025                             "type"));
1026                 }}
1027         ));
1028
1029 .. code-block:: json
1030
1031     {
1032         "create_uid": {
1033             "type": "many2one",
1034             "string": "Created by"
1035         },
1036         "create_date": {
1037             "type": "datetime",
1038             "string": "Created on"
1039         },
1040         "__last_update": {
1041             "type": "datetime",
1042             "string": "Last Modified on"
1043         },
1044         "write_uid": {
1045             "type": "many2one",
1046             "string": "Last Updated by"
1047         },
1048         "write_date": {
1049             "type": "datetime",
1050             "string": "Last Updated on"
1051         },
1052         "display_name": {
1053             "type": "char",
1054             "string": "Display Name"
1055         },
1056         "id": {
1057             "type": "integer",
1058             "string": "Id"
1059         }
1060     }
1061
1062 .. _reference/webservice/inspection/fields:
1063
1064 ``ir.model.fields``
1065 '''''''''''''''''''
1066
1067 Provides informations about the fields of Odoo models and allows adding
1068 custom fields without using Python code
1069
1070 ``model_id``
1071     :class:`~openerp.fields.Many2one` to
1072     :ref:`reference/webservice/inspection/models` to which the field belongs
1073 ``name``
1074     the field's technical name (used in ``read`` or ``write``)
1075 ``field_description``
1076     the field's user-readable label (e.g. ``string`` in ``fields_get``)
1077 ``ttype``
1078     the :ref:`type <reference/orm/fields>` of field to create
1079 ``state``
1080     whether the field was created via Python code (``base``) or via
1081     ``ir.model.fields`` (``manual``)
1082 ``required``, ``readonly``, ``translate``
1083     enables the corresponding flag on the field
1084 ``groups``
1085     :ref:`field-level access control <reference/security/fields>`, a
1086     :class:`~openerp.fields.Many2many` to ``res.groups``
1087 ``selection``, ``size``, ``on_delete``, ``relation``, ``relation_field``, ``domain``
1088     type-specific properties and customizations, see :ref:`the fields
1089     documentation <reference/orm/fields>` for details
1090
1091 Like custom models, only new fields created with ``state="manual"`` are
1092 activated as actual fields on the model.
1093
1094 .. warning:: computed fields can not be added via ``ir.model.fields``, some
1095              field meta-information (defaults, onchange) can not be set either
1096
1097 .. todo:: maybe new-API fields could store constant ``default`` in a new
1098           column, maybe JSON-encoded?
1099
1100 .. rst-class:: switchable
1101
1102     .. code-block:: python
1103
1104         id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{
1105             'name': "Custom Model",
1106             'model': "x_custom",
1107             'state': 'manual',
1108         }])
1109         models.execute_kw(
1110             db, uid, password,
1111             'ir.model.fields', 'create', [{
1112                 'model_id': id,
1113                 'name': 'x_name',
1114                 'ttype': 'char',
1115                 'state': 'manual',
1116                 'required': True,
1117             }])
1118         record_id = models.execute_kw(
1119             db, uid, password,
1120             'x_custom', 'create', [{
1121                 'x_name': "test record",
1122             }])
1123         models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]])
1124
1125     .. code-block:: php
1126
1127         $id = $models->execute_kw(
1128             $db, $uid, $password,
1129             'ir.model', 'create', array(array(
1130                 'name' => "Custom Model",
1131                 'model' => 'x_custom',
1132                 'state' => 'manual'
1133             ))
1134         );
1135         $models->execute_kw(
1136             $db, $uid, $password,
1137             'ir.model.fields', 'create', array(array(
1138                 'model_id' => $id,
1139                 'name' => 'x_name',
1140                 'ttype' => 'char',
1141                 'state' => 'manual',
1142                 'required' => true
1143             ))
1144         );
1145         $record_id = $models->execute_kw(
1146             $db, $uid, $password,
1147             'x_custom', 'create', array(array(
1148                 'x_name' => "test record"
1149             ))
1150         );
1151         $models->execute_kw(
1152             $db, $uid, $password,
1153             'x_custom', 'read',
1154             array(array($record_id)));
1155
1156     .. code-block:: ruby
1157
1158         id = models.execute_kw(
1159             db, uid, password,
1160             'ir.model', 'create', [{
1161                 name: "Custom Model",
1162                 model: "x_custom",
1163                 state: 'manual'
1164             }])
1165         models.execute_kw(
1166             db, uid, password,
1167             'ir.model.fields', 'create', [{
1168                 model_id: id,
1169                 name: "x_name",
1170                 ttype: "char",
1171                 state: "manual",
1172                 required: true
1173             }])
1174         record_id = models.execute_kw(
1175             db, uid, password,
1176             'x_custom', 'create', [{
1177                 x_name: "test record"
1178             }])
1179         models.execute_kw(
1180             db, uid, password,
1181             'x_custom', 'read', [[record_id]])
1182
1183     .. code-block:: java
1184
1185         final Integer id = (Integer)models.execute(
1186             "execute_kw", asList(
1187                 db, uid, password,
1188                 "ir.model", "create",
1189                 asList(new HashMap<String, Object>() {{
1190                     put("name", "Custom Model");
1191                     put("model", "x_custom");
1192                     put("state", "manual");
1193                 }})
1194         ));
1195         models.execute(
1196             "execute_kw", asList(
1197                 db, uid, password,
1198                 "ir.model.fields", "create",
1199                 asList(new HashMap<String, Object>() {{
1200                     put("model_id", id);
1201                     put("name", "x_name");
1202                     put("ttype", "char");
1203                     put("state", "manual");
1204                     put("required", true);
1205                 }})
1206         ));
1207         final Integer record_id = (Integer)models.execute(
1208             "execute_kw", asList(
1209                 db, uid, password,
1210                 "x_custom", "create",
1211                 asList(new HashMap<String, Object>() {{
1212                     put("x_name", "test record");
1213                 }})
1214         ));
1215
1216         client.execute(
1217             "execute_kw", asList(
1218                 db, uid, password,
1219                 "x_custom", "read",
1220                 asList(asList(record_id))
1221         ));
1222
1223 .. code-block:: json
1224
1225     [
1226         {
1227             "create_uid": [1, "Administrator"],
1228             "x_name": "test record",
1229             "__last_update": "2014-11-12 16:32:13",
1230             "write_uid": [1, "Administrator"],
1231             "write_date": "2014-11-12 16:32:13",
1232             "create_date": "2014-11-12 16:32:13",
1233             "id": 1,
1234             "display_name": "test record"
1235         }
1236     ]
1237
1238 Workflow manipulations
1239 ----------------------
1240
1241 :ref:`reference/workflows` can be moved along by sending them *signals*.
1242 Instead of using the top-level ``execute_kw``, signals are sent using
1243 ``exec_workflow``.
1244
1245 Signals are sent to a specific record, and possibly trigger a transition on
1246 the workflow instance associated with the record.
1247
1248 .. warning:: requires that the ``account`` module be installed
1249     :class: force-right
1250
1251 .. rst-class:: switchable
1252
1253     .. code-block:: python
1254
1255         client = models.execute_kw(
1256             db, uid, password,
1257             'res.partner', 'search_read',
1258             [[('customer', '=', True)]],
1259             {'limit': 1, 'fields': [
1260                 'property_account_receivable',
1261                 'property_payment_term',
1262                 'property_account_position']
1263             })[0]
1264         invoice_id = models.execute_kw(
1265             db, uid, password,
1266             'account.invoice', 'create', [{
1267                 'partner_id': client['id'],
1268                 'account_id': client['property_account_receivable'][0],
1269                 'invoice_line': [(0, False, {'name': "AAA"})]
1270             }])
1271
1272         models.exec_workflow(
1273             db, uid, password, 'account.invoice', 'invoice_open', invoice_id)
1274
1275     .. code-block:: php
1276
1277         $client = $models->execute_kw(
1278             $db, $uid, $password,
1279             'res.partner', 'search_read',
1280             array(array(array('customer', '=', true))),
1281             array(
1282                 'limit' => 1,
1283                 'fields' => array(
1284                     'property_account_receivable',
1285                     'property_payment_term',
1286                     'property_account_position'
1287                 )))[0];
1288         $invoice_id = $models->execute_kw(
1289             $db, $uid, $password,
1290             'account.invoice', 'create', array(array(
1291                 'partner_id' => $client['id'],
1292                 'account_id' => $client['property_account_receivable'][0],
1293                 'invoice_line' => array(array(0, false, array('name' => "AAA")))
1294             )));
1295
1296         $models->exec_workflow(
1297             $db, $uid, $password,
1298             'account.invoice', 'invoice_open',
1299              $invoice_id);
1300
1301     .. code-block:: ruby
1302
1303         client = models.execute_kw(
1304             db, uid, password,
1305             'res.partner', 'search_read',
1306             [[['customer', '=', true]]],
1307             {limit: 1, fields: %w(property_account_receivable property_payment_term property_account_position)}
1308         )[0]
1309         invoice_id = models.execute_kw(
1310             db, uid, password,
1311             'account.invoice', 'create', [{
1312                 partner_id: client['id'],
1313                 account_id: client['property_account_receivable'][0],
1314                 invoice_line: [[0, false, {name: "AAA"}]]
1315             }])
1316
1317         models.exec_workflow(
1318             db, uid, password,
1319             'account.invoice', 'invoice_open', invoice_id)
1320
1321     .. code-block:: java
1322
1323         final Map<String, Object> c = (Map<String, Object>)
1324             ((Object[])models.execute("execute_kw", asList(
1325                     db, uid, password,
1326                     "res.partner", "search_read",
1327                     asList(
1328                         asList(
1329                             asList("customer", "=", true))),
1330                     new HashMap<String, Object>() {{
1331                             put("limit", 1);
1332                             put("fields", asList(
1333                                 "property_account_receivable",
1334                                 "property_payment_term",
1335                                 "property_account_position"
1336                             ));
1337                         }}
1338             )))[0];
1339         final Integer invoice_id = (Integer)models.execute(
1340             "execute_kw", asList(
1341                 db, uid, password,
1342                 "account.invoice", "create",
1343                 asList(new HashMap<String, Object>() {{
1344                     put("partner_id", c.get("id"));
1345                     put("account_id", ((Object[])c.get("property_account_receivable"))[0]);
1346                     put("invoice_line", asList(
1347                         asList(0, false, new HashMap<String, Object>() {{
1348                             put("name", "AAA");
1349                         }})
1350                     ));
1351                 }})
1352         ));
1353
1354         models.execute(
1355             "exec_workflow", asList(
1356                 db, uid, password,
1357                 "account.invoice", "invoice_open", invoice_id));
1358
1359 Report printing
1360 ---------------
1361
1362 Available reports can be listed by searching the ``ir.actions.report.xml``
1363 model, fields of interest being
1364
1365 ``model``
1366     the model on which the report applies, can be used to look for available
1367     reports on a specific model
1368 ``name``
1369     human-readable report name
1370 ``report_name``
1371     the technical name of the report, used to print it
1372
1373 Reports can be printed over RPC with the following information:
1374
1375 * the name of the report (``report_name``)
1376 * the ids of the records to include in the report
1377
1378 .. rst-class:: switchable
1379
1380     .. code-block:: python
1381
1382         invoice_ids = models.execute_kw(
1383             db, uid, password, 'account.invoice', 'search',
1384             [[('type', '=', 'out_invoice'), ('state', '=', 'open')]])
1385         report = xmlrpclib.ServerProxy('{}/xmlrpc/2/report'.format(url))
1386         result = report.render_report(
1387             db, uid, password, 'account.report_invoice', invoice_ids)
1388         report_data = result['result'].decode('base64')
1389
1390     .. code-block:: php
1391
1392         $invoice_ids = $models->execute_kw(
1393             $db, $uid, $password,
1394             'account.invoice', 'search',
1395             array(array(array('type', '=', 'out_invoice'),
1396                         array('state', '=', 'open'))));
1397         $report = ripcord::client("$url/xmlrpc/2/report");
1398         $result = $report->render_report(
1399             $db, $uid, $password,
1400             'account.report_invoice', $invoice_ids);
1401         $report_data = base64_decode($result['result']);
1402
1403     .. code-block:: ruby
1404
1405         require 'base64'
1406         invoice_ids = models.execute_kw(
1407             db, uid, password,
1408             'account.invoice', 'search',
1409             [[['type', '=', 'out_invoice'], ['state', '=', 'open']]])
1410         report = XMLRPC::Client.new2("#{url}/xmlrpc/2/report").proxy
1411         result = report.render_report(
1412             db, uid, password,
1413             'account.report_invoice', invoice_ids)
1414         report_data = Base64.decode64(result['result'])
1415
1416     .. code-block:: java
1417
1418         final Object[] invoice_ids = (Object[])models.execute(
1419             "execute_kw", asList(
1420                 db, uid, password,
1421                 "account.invoice", "search",
1422                 asList(asList(
1423                     asList("type", "=", "out_invoice"),
1424                     asList("state", "=", "open")))
1425         ));
1426         final XmlRpcClientConfigImpl report_config = new XmlRpcClientConfigImpl();
1427         report_config.setServerURL(
1428             new URL(String.format("%s/xmlrpc/2/report", url)));
1429         final Map<String, Object> result = (Map<String, Object>)client.execute(
1430             report_config, "render_report", asList(
1431                 db, uid, password,
1432                 "account.report_invoice",
1433                 invoice_ids));
1434         final byte[] report_data = DatatypeConverter.parseBase64Binary(
1435             (String)result.get("result"));
1436
1437 .. note::
1438     :class: force-right
1439
1440     the report is sent as PDF binary data encoded in base64_, it must be
1441     decoded and may need to be saved to disk before use
1442
1443 .. _PostgreSQL: http://www.postgresql.org
1444 .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC
1445 .. _base64: http://en.wikipedia.org/wiki/Base64