43301979d0a282cd1574d3971ef7ce57d92486cd
[odoo/odoo.git] / doc / modules / api_integration.rst
1 :classes: stripe
2
3 ===========
4 Odoo as API
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 and authentication
23 =============================
24
25 .. kinda gross because it duplicates existing bits
26
27 .. rst-class:: setupcode hidden
28
29     .. code-block:: python
30
31         import xmlrpclib
32         info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
33         url, db, username, password = \
34             info['host'], info['database'], info['user'], info['password']
35         common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
36         uid = common.authenticate(db, username, password, {})
37         models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
38
39     .. code-block:: ruby
40
41         require "xmlrpc/client"
42         info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
43         url, db, username, password = \
44             info['host'], info['database'], info['user'], info['password']
45         common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
46         uid = common.authenticate(db, username, password, {})
47         models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
48
49     .. code-block:: php
50
51         require_once('ripcord.php');
52         $info = ripcord::client('https://demo.odoo.com/start')->start();
53         list($url, $db, $username, $password) =
54           array($info['host'], $info['database'], $info['user'], $info['password']);
55         $common = ripcord::client("$url/xmlrpc/2/common");
56         $uid = $common->authenticate($db, $username, $password, array());
57         $models = ripcord::client("$url/xmlrpc/2/object");
58
59     .. code-block:: java
60
61         final XmlRpcClient client = new XmlRpcClient();
62
63         final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
64         start_config.setServerURL(new URL("https://demo.odoo.com/start"));
65         final Map<String, String> info = (Map<String, String>)client.execute(
66             start_config, "start", Collections.emptyList());
67
68         final String url = info.get("host"),
69                       db = info.get("database"),
70                 username = info.get("user"),
71                 password = info.get("password");
72
73         final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
74         common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
75
76         int uid = (int)client.execute(
77             common_config, "authenticate", Arrays.asList(
78                 db, username, password, Collections.emptyMap()));
79
80         final XmlRpcClient models = new XmlRpcClient() {{
81             setConfig(new XmlRpcClientConfigImpl() {{
82                 setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
83             }});
84         }};
85
86 Configuration
87 -------------
88
89 If you already have an Odoo server installed, you can just use its
90 parameters
91
92 .. rst-class:: switchable setup
93
94     .. code-block:: python
95
96         url = <insert server URL>
97         db = <insert database name>
98         username = 'admin'
99         password = <insert password for your admin user (default: admin)>
100
101     .. code-block:: ruby
102
103         url = <insert server URL>
104         db = <insert database name>
105         username = "admin"
106         password = <insert password for your admin user (default: admin)>
107
108     .. code-block:: php
109
110         $url = <insert server URL>;
111         $db = <insert database name>;
112         $username = "admin";
113         $password = <insert password for your admin user (default: admin)>;
114
115     .. code-block:: java
116
117         final String url = <insert server URL>,
118                       db = <insert database name>,
119                 username = "admin",
120                 password = <insert password for your admin user (default: admin)>;
121
122 To make exploration simpler, you can also ask https://demo.odoo.com for a test
123 database:
124
125 .. rst-class:: switchable setup
126
127     .. code-block:: python
128
129         import xmlrpclib
130         info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
131         url, db, username, password = \
132             info['host'], info['database'], info['user'], info['password']
133
134     .. code-block:: ruby
135
136         require "xmlrpc/client"
137         info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start')
138         url, db, username, password = \
139             info['host'], info['database'], info['user'], info['password']
140
141     .. code-block:: php
142
143         require_once('ripcord.php');
144         $info = ripcord::client('https://demo.odoo.com/start')->start();
145         list($url, $db, $username, $password) =
146           array($info['host'], $info['database'], $info['user'], $info['password']);
147
148     .. code-block:: java
149
150         final XmlRpcClient client = new XmlRpcClient();
151
152         final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl();
153         start_config.setServerURL(new URL("https://demo.odoo.com/start"));
154         final Map<String, String> info = (Map<String, String>)client.execute(
155             start_config, "start", Collections.emptyList());
156
157         final String url = info.get("host"),
158                       db = info.get("database"),
159                 username = info.get("user"),
160                 password = info.get("password");
161
162 .. rst-class:: force-right
163
164     .. note::
165         :class: only-php
166
167         These examples use the `Ripcord <https://code.google.com/p/ripcord/>`_
168         library, which provides a simple XML-RPC API. Ripcord requires that
169         `XML-RPC support be enabled
170         <http://php.net/manual/en/xmlrpc.installation.php>`_ in your PHP
171         installation.
172
173         Since calls are performed over
174         `HTTPS <http://en.wikipedia.org/wiki/HTTP_Secure>`_, it also requires that
175         the `OpenSSL extension
176         <http://php.net/manual/en/openssl.installation.php>`_ be enabled.
177
178     .. note::
179         :class: only-java
180
181         These examples use the `Apache XML-RPC library
182         <https://ws.apache.org/xmlrpc/>`_
183
184 Logging in
185 ----------
186
187 Odoo requires users of the API to be authenticated before being able to query
188 much data.
189
190 The ``xmlrpc/2/common`` endpoint provides meta-calls which don't require
191 authentication, such as the authentication itself or fetching version
192 information. To verify if the connection information is correct before trying
193 to authenticate, the simplest call is to ask for the server's version. The
194 authentication itself is done through the ``authenticate`` function and
195 returns a user identifier (``uid``) used in authenticated calls instead of
196 the login.
197
198 .. rst-class:: switchable setup
199
200     .. code-block:: python
201
202         common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
203         common.version()
204
205     .. code-block:: ruby
206
207         common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
208         common.call('version')
209
210     .. code-block:: php
211
212         $common = ripcord::client("$url/xmlrpc/2/common");
213         $common->version();
214
215     .. code-block:: java
216
217         final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
218         common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));
219         client.execute(common_config, "version", Collections.emptyList());
220
221 .. code-block:: json
222
223     {
224         "server_version": "8.0",
225         "server_version_info": [8, 0, 0, "final", 0],
226         "server_serie": "8.0",
227         "protocol_version": 1,
228     }
229
230 .. rst-class:: switchable setup
231
232     .. code-block:: python
233
234         uid = common.authenticate(db, username, password, {})
235
236     .. code-block:: ruby
237
238         uid = common.call('authenticate', db, username, password, {})
239
240     .. code-block:: php
241
242         $uid = $common->authenticate($db, $username, $password, array());
243
244     .. code-block:: java
245
246         int uid = (int)client.execute(
247             common_config, "authenticate", Arrays.asList(
248                 db, username, password, Collections.emptyMap()));
249
250 Calling methods
251 ===============
252
253 The second — and most generally useful — is ``xmlrpc/2/object`` which is used
254 to call methods of odoo models via the ``execute_kw`` RPC function.
255
256 Each call to ``execute_kw`` takes the following parameters:
257
258 * the database to use, a string
259 * the user id (retrieved through ``authenticate``), an integer
260 * the user's password, a string
261 * the model name, a string
262 * the method name, a string
263 * an array/list of parameters passed by position
264 * a mapping/dict of parameters to pass by keyword (optional)
265
266 .. rst-class:: force-right
267
268 For instance to see if we can read the ``res.partner`` model we can call
269 ``check_access_rights`` with ``operation`` passed by position and
270 ``raise_exception`` passed by keyword (in order to get a true/false result
271 rather than true/error):
272
273 .. rst-class:: switchable setup
274
275     .. code-block:: python
276
277         models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
278         models.execute_kw(db, uid, password,
279             'res.partner', 'check_access_rights',
280             ['read'], {'raise_exception': False})
281
282     .. code-block:: ruby
283
284         models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy
285         models.execute_kw(db, uid, password,
286             'res.partner', 'check_access_rights',
287             ['read'], {raise_exception: false})
288
289     .. code-block:: php
290
291         $models = ripcord::client("$url/xmlrpc/2/object");
292         $models->execute_kw($db, $uid, $password,
293             'res.partner', 'check_access_rights',
294             array('read'), array('raise_exception' => false));
295
296     .. code-block:: java
297
298         final XmlRpcClient models = new XmlRpcClient() {{
299             setConfig(new XmlRpcClientConfigImpl() {{
300                 setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
301             }});
302         }};
303         models.execute("execute_kw", Arrays.asList(
304             db, uid, password,
305             "res.partner", "check_access_rights",
306             Arrays.asList("read"),
307             new HashMap() {{ put("raise_exception", false); }}
308         ));
309
310 .. code-block:: json
311
312     true
313
314 .. todo:: this should be runnable and checked
315
316 List records
317 ------------
318
319 Records can be listed and filtered via :meth:`~openerp.models.Model.search`.
320
321 :meth:`~openerp.models.Model.search` takes a mandatory
322 :ref:`domain <reference/orm/domains>` filter (possibly empty), and returns the
323 database identifiers of all records matching the filter. To list customer
324 companies for instance:
325
326 .. rst-class:: switchable
327
328     .. code-block:: python
329
330         models.execute_kw(db, uid, password,
331             'res.partner', 'search',
332             [[['is_company', '=', True], ['customer', '=', True]]])
333
334     .. code-block:: ruby
335
336         models.execute_kw(db, uid, password,
337             'res.partner', 'search',
338             [[['is_company', '=', true], ['customer', '=', true]]])
339
340     .. code-block:: php
341
342         $domain = array(array('is_company', '=', true),
343                         array('customer', '=', true));
344         $models->execute_kw($db, $uid, $password,
345             'res.partner', 'search', array($domain));
346
347     .. code-block:: java
348
349         final List domain = Arrays.asList(
350             Arrays.asList("is_company", "=", true),
351             Arrays.asList("customer", "=", true));
352         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
353             db, uid, password,
354             "res.partner", "search",
355             Arrays.asList(domain)
356         )));
357
358 .. code-block:: json
359
360     [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74]
361
362 Pagination
363 ''''''''''
364
365 By default a research will return the ids of all records matching the
366 condition, which may be a huge number. ``offset`` and ``limit`` parameters are
367 available to only retrieve a subset of all matched records.
368
369 .. rst-class:: switchable
370
371     .. code-block:: python
372
373         models.execute_kw(db, uid, password,
374             'res.partner', 'search',
375             [[['is_company', '=', True], ['customer', '=', True]]],
376             {'offset': 10, 'limit': 5})
377
378     .. code-block:: ruby
379
380         models.execute_kw(db, uid, password,
381             'res.partner', 'search',
382             [[['is_company', '=', true], ['customer', '=', true]]],
383             {offset: 10, limit: 5})
384
385     .. code-block:: php
386
387         $models->execute_kw($db, $uid, $password,
388             'res.partner', 'search',
389             array($domain),
390             array('offset'=>10, 'limit'=>5));
391
392     .. code-block:: java
393
394         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
395             db, uid, password,
396             "res.partner", "search",
397             Arrays.asList(domain),
398             new HashMap() {{ put("offset", 10); put("limit", 5); }}
399         )));
400
401 .. code-block:: json
402
403     [13, 20, 30, 22, 29]
404
405 Count records
406 -------------
407
408 Rather than retrieve a possibly gigantic list of records and count them
409 afterwards, :meth:`~openerp.models.Model.search_count` can be used to retrieve
410 only the number of records matching the query. It takes the same
411 :ref:`domain <reference/orm/domains>` filter as
412 :meth:`~openerp.models.Model.search` and no other parameter.
413
414 .. rst-class:: switchable
415
416     .. code-block:: python
417
418         models.execute_kw(db, uid, password,
419             'res.partner', 'search_count',
420             [[['is_company', '=', True], ['customer', '=', True]]])
421
422     .. code-block:: ruby
423
424         models.execute_kw(db, uid, password,
425             'res.partner', 'search_count',
426             [[['is_company', '=', true], ['customer', '=', true]]])
427
428     .. code-block:: php
429
430         $models->execute_kw($db, $uid, $password,
431             'res.partner', 'search_count',
432             array($domain));
433
434     .. code-block:: java
435
436         (Integer)models.execute("execute_kw", Arrays.asList(
437             db, uid, password,
438             "res.partner", "search_count",
439             Arrays.asList(domain)
440         ));
441
442 .. code-block:: json
443
444     19
445
446 .. warning::
447
448     calling ``search`` then ``search_count`` (or the other way around) may not
449     yield coherent results if other users are using the server: stored data
450     could have changed between the calls
451
452 Read records
453 ------------
454
455 Record data is accessible via the :meth:`~openerp.models.Model.read` method,
456 which takes a list of ids (as returned by
457 :meth:`~openerp.models.Model.search`) and optionally a list of fields to
458 fetch. By default, it will fetch all the fields the current user can read,
459 which tends to be a huge amount.
460
461 .. rst-class:: switchable
462
463     .. code-block:: python
464
465         ids = models.execute_kw(db, uid, password,
466             'res.partner', 'search',
467             [[['is_company', '=', True], ['customer', '=', True]]],
468             {'limit': 1})
469         [record] = models.execute_kw(db, uid, password,
470             'res.partner', 'read', [ids])
471         # count the number of fields fetched by default
472         len(record)
473
474     .. code-block:: ruby
475
476         ids = models.execute_kw(db, uid, password,
477             'res.partner', 'search',
478             [[['is_company', '=', true], ['customer', '=', true]]],
479             {limit: 1})
480         record = models.execute_kw(db, uid, password,
481             'res.partner', 'read', [ids]).first
482         # count the number of fields fetched by default
483         record.length
484
485     .. code-block:: php
486
487         $ids = $models->execute_kw($db, $uid, $password,
488             'res.partner', 'search',
489             array($domain),
490             array('limit'=>1));
491         $records = $models->execute_kw($db, $uid, $password,
492             'res.partner', 'read', array($ids));
493         // count the number of fields fetched by default
494         count($records[0]);
495
496     .. code-block:: java
497
498         final List ids = Arrays.asList((Object[])models.execute(
499             "execute_kw", Arrays.asList(
500                 db, uid, password,
501                 "res.partner", "search",
502                 Arrays.asList(domain),
503                 new HashMap() {{ put("limit", 1); }})));
504         final Map record = (Map)((Object[])models.execute(
505             "execute_kw", Arrays.asList(
506                 db, uid, password,
507                 "res.partner", "read",
508                 Arrays.asList(ids)
509             )
510         ))[0];
511         // count the number of fields fetched by default
512         record.size();
513
514 .. code-block:: json
515
516     121
517
518 Conversedly, picking only three fields deemed interesting.
519
520 .. rst-class:: switchable
521
522     .. code-block:: python
523
524         models.execute_kw(db, uid, password,
525             'res.partner', 'read',
526             [ids], {'fields': ['name', 'country_id', 'comment']})
527
528     .. code-block:: ruby
529
530         models.execute_kw(db, uid, password,
531             'res.partner', 'read',
532             [ids], {fields: %w(name country_id comment)})
533
534     .. code-block:: php
535
536         $models->execute_kw($db, $uid, $password,
537             'res.partner', 'read',
538             array($ids),
539             array('fields'=>array('name', 'country_id', 'comment')));
540
541     .. code-block:: java
542
543         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
544             db, uid, password,
545             "res.partner", "read",
546             Arrays.asList(ids),
547             new HashMap() {{
548                 put("fields", Arrays.asList("name", "country_id", "comment"));
549             }}
550         )));
551
552 .. code-block:: json
553
554     [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}]
555
556 .. note:: even if the ``id`` field is not requested, it is always returned
557
558 Listing record fields
559 ---------------------
560
561 :meth:`~openerp.models.Model.fields_get` can be used to inspect
562 a model's fields and check which ones seem to be of interest.
563
564 Because
565 it returns a great amount of meta-information (it is also used by client
566 programs) it should be filtered before printing, the most interesting items
567 for a human user are ``string`` (the field's label), ``help`` (a help text if
568 available) and ``type`` (to know which values to expect, or to send when
569 updating a record):
570
571 .. rst-class:: switchable
572
573     .. code-block:: python
574
575         fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
576         # filter keys of field attributes for display
577         {field: {
578                     k: v for k, v in attributes.iteritems()
579                     if k in ['string', 'help', 'type']
580                 }
581          for field, attributes in fields.iteritems()}
582
583     .. code-block:: ruby
584
585         fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [])
586         # filter keys of field attributes for display
587         fields.each {|k, v|
588             fields[k] = v.keep_if {|kk, vv| %w(string help type).include? kk}
589         }
590
591     .. code-block:: php
592
593         $fields_full = $models->execute_kw($db, $uid, $password,
594             'res.partner', 'fields_get', array());
595         // filter keys of field attributes for display
596         $allowed = array_flip(array('string', 'help', 'type'));
597         $fields = array();
598         foreach($fields_full as $field => $attributes) {
599           $fields[$field] = array_intersect_key($attributes, $allowed);
600         }
601
602     .. code-block:: java
603
604         final Map<String, Map<String, Object>> fields  =
605             (Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList(
606                 db, uid, password,
607                 "res.partner", "fields_get",
608                 Collections.emptyList()));
609         // filter keys of field attributes for display
610         final List<String> allowed = Arrays.asList("string", "help", "type");
611         new HashMap<String, Map<String, Object>>() {{
612             for(Entry<String, Map<String, Object>> item: fields.entrySet()) {
613                 put(item.getKey(), new HashMap<String, Object>() {{
614                     for(Entry<String, Object> it: item.getValue().entrySet()) {
615                         if (allowed.contains(it.getKey())) {
616                             put(it.getKey(), it.getValue());
617                         }
618                     }
619                 }});
620             }
621         }};
622
623 .. code-block:: json
624
625     {
626         "ean13": {
627             "type": "char",
628             "help": "BarCode",
629             "string": "EAN13"
630         },
631         "property_account_position": {
632             "type": "many2one",
633             "help": "The fiscal position will determine taxes and accounts used for the partner.",
634             "string": "Fiscal Position"
635         },
636         "signup_valid": {
637             "type": "boolean",
638             "help": "",
639             "string": "Signup Token is Valid"
640         },
641         "date_localization": {
642             "type": "date",
643             "help": "",
644             "string": "Geo Localization Date"
645         },
646         "ref_companies": {
647             "type": "one2many",
648             "help": "",
649             "string": "Companies that refers to partner"
650         },
651         "sale_order_count": {
652             "type": "integer",
653             "help": "",
654             "string": "# of Sales Order"
655         },
656         "purchase_order_count": {
657             "type": "integer",
658             "help": "",
659             "string": "# of Purchase Order"
660         },
661
662 Search and read
663 ---------------
664
665 Because that is a very common task, Odoo provides a
666 :meth:`~openerp.models.Model.search_read` shortcut which as its name notes is
667 equivalent to a :meth:`~openerp.models.Model.search` followed by a
668 :meth:`~openerp.models.Model.read`, but avoids having to perform two requests
669 and keep ids around. Its arguments are similar to
670 :meth:`~openerp.models.Model.search`'s, but it can also take a list of
671 ``fields`` (like :meth:`~openerp.models.Model.read`, if that list is not
672 provided it'll fetch all fields of matched records):
673
674 .. rst-class:: switchable
675
676     .. code-block:: python
677
678         models.execute_kw(db, uid, password,
679             'res.partner', 'search_read',
680             [[['is_company', '=', True], ['customer', '=', True]]],
681             {'fields': ['name', 'country_id', 'comment'], 'limit': 5})
682
683     .. code-block:: ruby
684
685         models.execute_kw(db, uid, password,
686             'res.partner', 'search_read',
687             [[['is_company', '=', true], ['customer', '=', true]]],
688             {fields: %w(name country_id comment), limit: 5})
689
690     .. code-block:: php
691
692         $models->execute_kw($db, $uid, $password,
693             'res.partner', 'search_read',
694             array($domain),
695             array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5));
696
697     .. code-block:: java
698
699         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
700             db, uid, password,
701             "res.partner", "search_read",
702             Arrays.asList(domain),
703             new HashMap() {{
704                 put("fields", Arrays.asList("name", "country_id", "comment"));
705                 put("limit", 5);
706             }}
707         )));
708
709 .. code-block:: json
710
711     [
712         {
713             "comment": false,
714             "country_id": [ 21, "Belgium" ],
715             "id": 7,
716             "name": "Agrolait"
717         },
718         {
719             "comment": false,
720             "country_id": [ 76, "France" ],
721             "id": 18,
722             "name": "Axelor"
723         },
724         {
725             "comment": false,
726             "country_id": [ 233, "United Kingdom" ],
727             "id": 12,
728             "name": "Bank Wealthy and sons"
729         },
730         {
731             "comment": false,
732             "country_id": [ 105, "India" ],
733             "id": 14,
734             "name": "Best Designers"
735         },
736         {
737             "comment": false,
738             "country_id": [ 76, "France" ],
739             "id": 17,
740             "name": "Camptocamp"
741         }
742     ]
743
744
745 Create records
746 --------------
747
748 .. rst-class:: switchable
749
750     .. code-block:: python
751
752         id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
753             'name': "New Partner",
754         }])
755
756     .. code-block:: ruby
757
758         id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{
759             name: "New Partner",
760         }])
761
762     .. code-block:: php
763
764         $id = $models->execute_kw($db, $uid, $password,
765             'res.partner', 'create',
766             array(array('name'=>"New Partner")));
767
768     .. code-block:: java
769
770         final Integer id = (Integer)models.execute("execute_kw", Arrays.asList(
771             db, uid, password,
772             "res.partner", "create",
773             Arrays.asList(new HashMap() {{ put("name", "New Partner"); }})
774         ));
775
776 .. code-block:: json
777
778     78
779
780 Update records
781 --------------
782
783 .. rst-class:: switchable
784
785     .. code-block:: python
786
787         models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
788             'name': "Newer partner"
789         }])
790         # get record name after having changed it
791         models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
792
793     .. code-block:: ruby
794
795         models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {
796             name: "Newer partner"
797         }])
798         # get record name after having changed it
799         models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]])
800
801     .. code-block:: php
802
803         $models->execute_kw($db, $uid, $password, 'res.partner', 'write',
804             array(array($id), array('name'=>"Newer partner")));
805         // get record name after having changed it
806         $models->execute_kw($db, $uid, $password,
807             'res.partner', 'name_get', array(array($id)));
808
809     .. code-block:: java
810
811         models.execute("execute_kw", Arrays.asList(
812             db, uid, password,
813             "res.partner", "write",
814             Arrays.asList(
815                 Arrays.asList(id),
816                 new HashMap() {{ put("name", "Newer Partner"); }}
817             )
818         ));
819         // get record name after having changed it
820         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
821             db, uid, password,
822             "res.partner", "name_get",
823             Arrays.asList(Arrays.asList(id))
824         )));
825
826 .. code-block:: json
827
828     [[78, "Newer partner"]]
829
830 Delete records
831 --------------
832
833 .. rst-class:: switchable
834
835     .. code-block:: python
836
837         models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
838         # check if the deleted record is still in the database
839         models.execute_kw(db, uid, password,
840             'res.partner', 'search', [[['id', '=', id]]])
841
842     .. code-block:: ruby
843
844         models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]])
845         # check if the deleted record is still in the database
846         models.execute_kw(db, uid, password,
847             'res.partner', 'search', [[['id', '=', id]]])
848
849     .. code-block:: php
850
851         $models->execute_kw($db, $uid, $password,
852             'res.partner', 'unlink',
853             array(array($id)));
854         // check if the deleted record is still in the database
855         $models->execute_kw($db, $uid, $password,
856             'res.partner', 'search',
857             array(array(array('id', '=', $id))));
858
859     .. code-block:: java
860
861         models.execute("execute_kw", Arrays.asList(
862             db, uid, password,
863             "res.partner", "unlink",
864             Arrays.asList(Arrays.asList(id))));
865         // check if the deleted record is still in the database
866         Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
867             db, uid, password,
868             "res.partner", "search",
869             Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78)))
870         )));
871
872 .. code-block:: json
873
874     []
875
876 .. _PostgreSQL: http://www.postgresql.org
877 .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC