From c546931ab3398c7fdea7590ff7272083b1c892f8 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 9 Jul 2023 17:11:58 +0100 Subject: [PATCH 1/8] Go Examples for External API v16 The code examples in Go have been added in the same places as existing examples for other programming languages. All Go examples are based on existing examples, but respecting the specificities of the Go language. All examples have been tested with a local installation of odoo v16 and work fine, except the odoo test database which always returns an error. The Go code for testing can be found here: https://gist.github.com/mrhdias/f58106caa1e148c925cbe0b24635ef75 X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 434 +++++++++++++++++++ 1 file changed, 434 insertions(+) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index 93355536fa..b92d565616 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -72,6 +72,15 @@ If you already have an Odoo server installed, you can just use its parameters. username = "admin", password = ; + .. code-tab:: go + + const ( + url = + db = + username = "admin" + password = + ) + .. _api/external_api/keys: API Keys @@ -180,6 +189,41 @@ database: The examples do not include imports as these imports couldn't be pasted in the code. + .. group-tab:: Go + + .. code-block:: go + + client, err := xmlrpc.NewClient("https://demo.odoo.com/start", nil) + if err != nil { + log.Fatalln(err) + } + + info := struct { + Url string `xmlrpc:"url"` + Db string `xmlrpc:"db"` + Username string `xmlrpc:"username"` + Password string `xmlrpc:"password"` + }{} + + if err := client.Call("start", nil, &info); err != nil { + log.Fatalln(err) + } + + url := info.Url + db := info.Db + username := info.Username + password := info.Password + + if err := client.Close(); err != nil { + log.Fatalln(err) + } + + .. note:: + These examples use the `github.com/kolo/xmlrpc library `_. + + The examples do not include imports as these imports couldn't be + pasted in the code. + Logging in ---------- @@ -217,6 +261,34 @@ the login. common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); client.execute(common_config, "version", emptyList()); + .. code-tab:: go + + client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) + if err != nil { + log.Fatalln(err) + } + + common := struct { + ProtocolVersion int `xmlrpc:"protocol_version"` + ServerSerie string `xmlrpc:"server_serie"` + ServerVersion string `xmlrpc:"server_version"` + ServerVersionInfo []any `xmlrpc:"server_version_info"` + }{} + + if err := client.Call("version", nil, &common); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(common, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) + + if err := client.Close(); err != nil { + log.Fatalln(err) + } + Result: .. code-block:: json @@ -246,6 +318,22 @@ Result: .. code-tab:: java int uid = (int)client.execute(common_config, "authenticate", asList(db, username, password, emptyMap())); + + .. code-tab:: go + + var uid int64 + + if err := client.Call("authenticate", []any{ + db, username, password, + []any{}, + }, &uid); err != nil { + log.Fatalln(err) + } + fmt.Println(uid) + + if err := client.Close(); err != nil { + log.Fatalln(err) + } .. _api/external_api/calling_methods: @@ -302,6 +390,30 @@ Each call to ``execute_kw`` takes the following parameters: asList("read"), new HashMap() {{ put("raise_exception", false); }} )); + + .. code-tab:: go + + models, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) + if err != nil { + log.Fatalln(err) + } + + var result bool + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "check_access_rights", + []string{"read"}, + map[string]bool{ + "raise_exception": false, + }, + }, &result); err != nil { + log.Fatalln(err) + } + fmt.Println(result) + + if err := models.Close(); err != nil { + log.Fatalln(err) + } Result: @@ -344,6 +456,23 @@ database identifiers of all records matching the filter. asList(asList( asList("is_company", "=", true))) ))); + + .. code-tab:: go + + var records []int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search", + []any{[]any{[]any{"is_company", "=", true}}}, + }, &records); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(records, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -383,6 +512,24 @@ available to only retrieve a subset of all matched records. asList("is_company", "=", true))), new HashMap() {{ put("offset", 10); put("limit", 5); }} ))); + + .. code-tab:: go + + var records []int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search", + []any{[]any{[]any{"is_company", "=", true}}}, + map[string]int64{"offset": 10, "limit": 5}, + }, &records); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(records, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -424,6 +571,18 @@ only the number of records matching the query. It takes the same asList("is_company", "=", true))) )); + .. code-tab:: go + + var counter int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search_count", + []any{[]any{[]any{"is_company", "=", true}}}, + }, &counter); err != nil { + log.Fatalln(err) + } + fmt.Println(counter) + Result: .. code-block:: json @@ -488,6 +647,30 @@ which tends to be a huge amount. // count the number of fields fetched by default record.size(); + .. code-tab:: go + + var ids []int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search", + []any{[]any{[]any{"is_company", "=", true}}}, + map[string]int64{"limit": 1}, + }, &ids); err != nil { + log.Fatalln(err) + } + + var records []any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "read", + ids, + }, &records); err != nil { + log.Fatalln(err) + } + + // count the number of fields fetched by default + fmt.Println(len(records)) + Result: .. code-block:: json @@ -521,6 +704,26 @@ which tends to be a huge amount. }} ))); + .. code-tab:: go + + var recordFields []map[string]any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "read", + ids, + map[string][]string{ + "fields": {"name", "country_id", "comment"}, + }, + }, &recordFields); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(recordFields, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) + Result: .. code-block:: json @@ -568,6 +771,30 @@ updating a record). put("attributes", asList("string", "help", "type")); }} )); + + .. code-tab:: go + + recordFields := map[string]struct { + String string `xmlrpc:"string"` + Type string `xmlrpc:"type"` + Help string `xmlrpc:"help"` + }{} + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "fields_get", + []any{}, + map[string][]string{ + "attributes": {"string", "help", "type"}, + }, + }, &recordFields); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(recordFields, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -651,6 +878,27 @@ if that list is not provided it will fetch all fields of matched records). put("limit", 5); }} ))); + + .. code-tab:: go + + var recordFields []map[string]any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search_read", + []any{[]any{[]any{"is_company", "=", true}}}, + map[string]any{ + "fields": []string{"name", "country_id", "comment"}, + "limit": 5, + }, + }, &recordFields); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(recordFields, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -722,6 +970,21 @@ set through the mapping argument, the default value will be used. "res.partner", "create", asList(new HashMap() {{ put("name", "New Partner"); }}) )); + + .. code-tab:: go + + var id int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "create", + []map[string]string{ + {"name": "New Partner"}, + }, + }, &id); err != nil { + log.Fatalln(err) + } + + fmt.Println(id) Result: @@ -791,6 +1054,40 @@ a record). "res.partner", "name_get", asList(asList(id)) ))); + + .. code-tab:: go + + var result bool + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "write", + []any{ + []int64{id}, + map[string]string{"name": "Newer partner"}, + }, + }, &result); err != nil { + log.Fatalln(err) + } + fmt.Println(result) + + // get record name after having changed it + + var record []any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "name_get", + []any{ + []int64{id}, + }, + }, &record); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(record, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -840,6 +1137,41 @@ Records can be deleted in bulk by providing their ids to "res.partner", "search", asList(asList(asList("id", "=", 78))) ))); + + .. code-tab:: go + + var result bool + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "unlink", + []any{ + []int64{id}, + }, + }, &result); err != nil { + log.Fatalln(err) + } + fmt.Println(result) + + // check if the deleted record is still in the database + + var record []any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "res.partner", "search", + []any{ + []any{ + []any{"id", "=", id}, + }, + }, + }, &record); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(record, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) Result: @@ -953,6 +1285,46 @@ Provides information about Odoo models via its various fields. }} )); + .. code-tab:: go + + var id int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "ir.model", "create", + []map[string]string{ + { + "name": "Custom Model", + "model": "x_custom_model", + "state": "manual", + }, + }, + }, &id); err != nil { + log.Fatalln(err) + } + fmt.Println(id) + + recordFields := map[string]struct { + String string `xmlrpc:"string"` + Type string `xmlrpc:"type"` + Help string `xmlrpc:"help"` + }{} + if err := models.Call("execute_kw", []any{ + db, uid, password, + "x_custom_model", "fields_get", + []any{}, + map[string][]string{ + "attributes": {"string", "help", "type"}, + }, + }, &recordFields); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(recordFields, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) + Result: .. code-block:: json @@ -1118,6 +1490,68 @@ custom fields without using Python code. asList(asList(record_id)) )); + .. code-tab:: go + + var id int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "ir.model", "create", + []map[string]string{ + { + "name": "Custom Model", + "model": "x_custom", + "state": "manual", + }, + }, + }, &id); err != nil { + log.Fatalln(err) + } + + var fieldId int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "ir.model.fields", "create", + []map[string]any{ + { + "model_id": id, + "name": "x_name", + "ttype": "char", + "state": "manual", + "required": true, + }, + }, + }, &fieldId); err != nil { + log.Fatalln(err) + } + fmt.Println(fieldId) + + var recordId int64 + if err := models.Call("execute_kw", []any{ + db, uid, password, + "x_custom", "create", + []map[string]string{ + {"x_name": "test record"}, + }, + }, &recordId); err != nil { + log.Fatalln(err) + } + fmt.Println(recordId) + + var recordFields []map[string]any + if err := models.Call("execute_kw", []any{ + db, uid, password, + "x_custom", "read", + [][]int64{{recordId}}, + }, recordFields); err != nil { + log.Fatalln(err) + } + + json, err := json.MarshalIndent(recordFields, "", " ") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(json)) + Result: .. code-block:: json From 92543d1ed08c1edfcb4ebae963ad7e9862cb0923 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 9 Jul 2023 17:39:25 +0100 Subject: [PATCH 2/8] The RST text formatting errors have been fixed The RST text formatting errors have been fixed X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 58 ++++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index b92d565616..3f9167bc84 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -197,26 +197,26 @@ database: if err != nil { log.Fatalln(err) } - + info := struct { Url string `xmlrpc:"url"` Db string `xmlrpc:"db"` Username string `xmlrpc:"username"` Password string `xmlrpc:"password"` }{} - + if err := client.Call("start", nil, &info); err != nil { log.Fatalln(err) } - + url := info.Url db := info.Db username := info.Username password := info.Password - if err := client.Close(); err != nil { + if err := client.Close(); err != nil { log.Fatalln(err) - } + } .. note:: These examples use the `github.com/kolo/xmlrpc library `_. @@ -262,7 +262,7 @@ the login. client.execute(common_config, "version", emptyList()); .. code-tab:: go - + client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) if err != nil { log.Fatalln(err) @@ -278,13 +278,13 @@ the login. if err := client.Call("version", nil, &common); err != nil { log.Fatalln(err) } - + json, err := json.MarshalIndent(common, "", " ") if err != nil { log.Fatalln(err) } fmt.Println(string(json)) - + if err := client.Close(); err != nil { log.Fatalln(err) } @@ -318,9 +318,9 @@ Result: .. code-tab:: java int uid = (int)client.execute(common_config, "authenticate", asList(db, username, password, emptyMap())); - + .. code-tab:: go - + var uid int64 if err := client.Call("authenticate", []any{ @@ -330,7 +330,7 @@ Result: log.Fatalln(err) } fmt.Println(uid) - + if err := client.Close(); err != nil { log.Fatalln(err) } @@ -390,9 +390,9 @@ Each call to ``execute_kw`` takes the following parameters: asList("read"), new HashMap() {{ put("raise_exception", false); }} )); - + .. code-tab:: go - + models, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) if err != nil { log.Fatalln(err) @@ -410,7 +410,7 @@ Each call to ``execute_kw`` takes the following parameters: log.Fatalln(err) } fmt.Println(result) - + if err := models.Close(); err != nil { log.Fatalln(err) } @@ -456,9 +456,9 @@ database identifiers of all records matching the filter. asList(asList( asList("is_company", "=", true))) ))); - + .. code-tab:: go - + var records []int64 if err := models.Call("execute_kw", []any{ db, uid, password, @@ -512,9 +512,9 @@ available to only retrieve a subset of all matched records. asList("is_company", "=", true))), new HashMap() {{ put("offset", 10); put("limit", 5); }} ))); - + .. code-tab:: go - + var records []int64 if err := models.Call("execute_kw", []any{ db, uid, password, @@ -705,7 +705,7 @@ which tends to be a huge amount. ))); .. code-tab:: go - + var recordFields []map[string]any if err := models.Call("execute_kw", []any{ db, uid, password, @@ -717,7 +717,7 @@ which tends to be a huge amount. }, &recordFields); err != nil { log.Fatalln(err) } - + json, err := json.MarshalIndent(recordFields, "", " ") if err != nil { log.Fatalln(err) @@ -771,7 +771,7 @@ updating a record). put("attributes", asList("string", "help", "type")); }} )); - + .. code-tab:: go recordFields := map[string]struct { @@ -878,7 +878,7 @@ if that list is not provided it will fetch all fields of matched records). put("limit", 5); }} ))); - + .. code-tab:: go var recordFields []map[string]any @@ -970,9 +970,9 @@ set through the mapping argument, the default value will be used. "res.partner", "create", asList(new HashMap() {{ put("name", "New Partner"); }}) )); - + .. code-tab:: go - + var id int64 if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1054,9 +1054,9 @@ a record). "res.partner", "name_get", asList(asList(id)) ))); - + .. code-tab:: go - + var result bool if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1137,9 +1137,9 @@ Records can be deleted in bulk by providing their ids to "res.partner", "search", asList(asList(asList("id", "=", 78))) ))); - + .. code-tab:: go - + var result bool if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1491,7 +1491,7 @@ custom fields without using Python code. )); .. code-tab:: go - + var id int64 if err := models.Call("execute_kw", []any{ db, uid, password, From 4f7c5474fc889749702e6659fd58c02d35a23a0d Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 9 Jul 2023 17:48:22 +0100 Subject: [PATCH 3/8] The last issue with text formatting has been fixed The last issue with text formatting has been fixed. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index 3f9167bc84..7348d72fcb 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -212,7 +212,7 @@ database: url := info.Url db := info.Db username := info.Username - password := info.Password + password := info.Password if err := client.Close(); err != nil { log.Fatalln(err) From 5250a721d5f01d73135a1f6ace3c4567e77bfb99 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 10 Jul 2023 16:26:54 +0100 Subject: [PATCH 4/8] The code was rewritten according to the suggested changes. The requested changes have been made: All json output formatting has been removed in all snippets; The client is no longer closed; No more output of results in all snippets. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 105 +------------------ 1 file changed, 1 insertion(+), 104 deletions(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index 7348d72fcb..a1e2e208dc 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -197,27 +197,20 @@ database: if err != nil { log.Fatalln(err) } - info := struct { Url string `xmlrpc:"url"` Db string `xmlrpc:"db"` Username string `xmlrpc:"username"` Password string `xmlrpc:"password"` }{} - if err := client.Call("start", nil, &info); err != nil { log.Fatalln(err) } - url := info.Url db := info.Db username := info.Username password := info.Password - if err := client.Close(); err != nil { - log.Fatalln(err) - } - .. note:: These examples use the `github.com/kolo/xmlrpc library `_. @@ -267,28 +260,16 @@ the login. if err != nil { log.Fatalln(err) } - common := struct { ProtocolVersion int `xmlrpc:"protocol_version"` ServerSerie string `xmlrpc:"server_serie"` ServerVersion string `xmlrpc:"server_version"` ServerVersionInfo []any `xmlrpc:"server_version_info"` }{} - if err := client.Call("version", nil, &common); err != nil { log.Fatalln(err) } - json, err := json.MarshalIndent(common, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - - if err := client.Close(); err != nil { - log.Fatalln(err) - } - Result: .. code-block:: json @@ -322,18 +303,12 @@ Result: .. code-tab:: go var uid int64 - if err := client.Call("authenticate", []any{ db, username, password, []any{}, }, &uid); err != nil { log.Fatalln(err) } - fmt.Println(uid) - - if err := client.Close(); err != nil { - log.Fatalln(err) - } .. _api/external_api/calling_methods: @@ -397,7 +372,6 @@ Each call to ``execute_kw`` takes the following parameters: if err != nil { log.Fatalln(err) } - var result bool if err := models.Call("execute_kw", []any{ db, uid, password, @@ -409,11 +383,6 @@ Each call to ``execute_kw`` takes the following parameters: }, &result); err != nil { log.Fatalln(err) } - fmt.Println(result) - - if err := models.Close(); err != nil { - log.Fatalln(err) - } Result: @@ -468,12 +437,6 @@ database identifiers of all records matching the filter. log.Fatalln(err) } - json, err := json.MarshalIndent(records, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -525,12 +488,6 @@ available to only retrieve a subset of all matched records. log.Fatalln(err) } - json, err := json.MarshalIndent(records, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -581,7 +538,6 @@ only the number of records matching the query. It takes the same }, &counter); err != nil { log.Fatalln(err) } - fmt.Println(counter) Result: @@ -658,7 +614,6 @@ which tends to be a huge amount. }, &ids); err != nil { log.Fatalln(err) } - var records []any if err := models.Call("execute_kw", []any{ db, uid, password, @@ -667,9 +622,8 @@ which tends to be a huge amount. }, &records); err != nil { log.Fatalln(err) } - // count the number of fields fetched by default - fmt.Println(len(records)) + count := len(records) Result: @@ -718,12 +672,6 @@ which tends to be a huge amount. log.Fatalln(err) } - json, err := json.MarshalIndent(recordFields, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -790,12 +738,6 @@ updating a record). log.Fatalln(err) } - json, err := json.MarshalIndent(recordFields, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -894,12 +836,6 @@ if that list is not provided it will fetch all fields of matched records). log.Fatalln(err) } - json, err := json.MarshalIndent(recordFields, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -984,8 +920,6 @@ set through the mapping argument, the default value will be used. log.Fatalln(err) } - fmt.Println(id) - Result: .. code-block:: json @@ -1068,10 +1002,7 @@ a record). }, &result); err != nil { log.Fatalln(err) } - fmt.Println(result) - // get record name after having changed it - var record []any if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1083,12 +1014,6 @@ a record). log.Fatalln(err) } - json, err := json.MarshalIndent(record, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -1150,10 +1075,7 @@ Records can be deleted in bulk by providing their ids to }, &result); err != nil { log.Fatalln(err) } - fmt.Println(result) - // check if the deleted record is still in the database - var record []any if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1167,12 +1089,6 @@ Records can be deleted in bulk by providing their ids to log.Fatalln(err) } - json, err := json.MarshalIndent(record, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -1301,8 +1217,6 @@ Provides information about Odoo models via its various fields. }, &id); err != nil { log.Fatalln(err) } - fmt.Println(id) - recordFields := map[string]struct { String string `xmlrpc:"string"` Type string `xmlrpc:"type"` @@ -1319,12 +1233,6 @@ Provides information about Odoo models via its various fields. log.Fatalln(err) } - json, err := json.MarshalIndent(recordFields, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json @@ -1506,7 +1414,6 @@ custom fields without using Python code. }, &id); err != nil { log.Fatalln(err) } - var fieldId int64 if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1523,8 +1430,6 @@ custom fields without using Python code. }, &fieldId); err != nil { log.Fatalln(err) } - fmt.Println(fieldId) - var recordId int64 if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1535,8 +1440,6 @@ custom fields without using Python code. }, &recordId); err != nil { log.Fatalln(err) } - fmt.Println(recordId) - var recordFields []map[string]any if err := models.Call("execute_kw", []any{ db, uid, password, @@ -1546,12 +1449,6 @@ custom fields without using Python code. log.Fatalln(err) } - json, err := json.MarshalIndent(recordFields, "", " ") - if err != nil { - log.Fatalln(err) - } - fmt.Println(string(json)) - Result: .. code-block:: json From b9946002d8fe70f2a6f4229ef5e8cca8448ae022 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 12 Jul 2023 10:25:47 +0100 Subject: [PATCH 5/8] Code simplification and cleanup. All go examples have been simplified. No more outputs in all go examples (including error output). Inconsistency between maps and structures has been eliminated. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 206 +++++++------------ 1 file changed, 69 insertions(+), 137 deletions(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index a1e2e208dc..2ed9c170ad 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -193,23 +193,13 @@ database: .. code-block:: go - client, err := xmlrpc.NewClient("https://demo.odoo.com/start", nil) - if err != nil { - log.Fatalln(err) - } - info := struct { - Url string `xmlrpc:"url"` - Db string `xmlrpc:"db"` - Username string `xmlrpc:"username"` - Password string `xmlrpc:"password"` - }{} - if err := client.Call("start", nil, &info); err != nil { - log.Fatalln(err) - } - url := info.Url - db := info.Db - username := info.Username - password := info.Password + client, _ := xmlrpc.NewClient("https://demo.odoo.com/start", nil) + info := map[string]string{} + client.Call("start", nil, &info) + url := info["host"].(string) + db := info["database"].(string) + username := info["user"].(string) + password := info["password"].(string) .. note:: These examples use the `github.com/kolo/xmlrpc library `_. @@ -256,19 +246,9 @@ the login. .. code-tab:: go - client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) - if err != nil { - log.Fatalln(err) - } - common := struct { - ProtocolVersion int `xmlrpc:"protocol_version"` - ServerSerie string `xmlrpc:"server_serie"` - ServerVersion string `xmlrpc:"server_version"` - ServerVersionInfo []any `xmlrpc:"server_version_info"` - }{} - if err := client.Call("version", nil, &common); err != nil { - log.Fatalln(err) - } + client, _ := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) + common := map[string]any{} + client.Call("version", nil, &common) Result: @@ -303,12 +283,7 @@ Result: .. code-tab:: go var uid int64 - if err := client.Call("authenticate", []any{ - db, username, password, - []any{}, - }, &uid); err != nil { - log.Fatalln(err) - } + client.Call("authenticate", []any{db, username, password, []any{}}, &uid) .. _api/external_api/calling_methods: @@ -368,21 +343,14 @@ Each call to ``execute_kw`` takes the following parameters: .. code-tab:: go - models, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) - if err != nil { - log.Fatalln(err) - } + models, _ := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) var result bool - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "check_access_rights", []string{"read"}, - map[string]bool{ - "raise_exception": false, - }, - }, &result); err != nil { - log.Fatalln(err) - } + map[string]bool{"raise_exception": false}, + }, &result) Result: @@ -429,13 +397,13 @@ database identifiers of all records matching the filter. .. code-tab:: go var records []int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", - []any{[]any{[]any{"is_company", "=", true}}}, - }, &records); err != nil { - log.Fatalln(err) - } + []any{[]any{ + []any{"is_company", "=", true}, + }}, + }, &records) Result: @@ -479,14 +447,14 @@ available to only retrieve a subset of all matched records. .. code-tab:: go var records []int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", - []any{[]any{[]any{"is_company", "=", true}}}, + []any{[]any{ + []any{"is_company", "=", true}, + }}, map[string]int64{"offset": 10, "limit": 5}, - }, &records); err != nil { - log.Fatalln(err) - } + }, &records) Result: @@ -531,13 +499,13 @@ only the number of records matching the query. It takes the same .. code-tab:: go var counter int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search_count", - []any{[]any{[]any{"is_company", "=", true}}}, - }, &counter); err != nil { - log.Fatalln(err) - } + []any{[]any{ + []any{"is_company", "=", true}, + }}, + }, &counter) Result: @@ -606,22 +574,20 @@ which tends to be a huge amount. .. code-tab:: go var ids []int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", - []any{[]any{[]any{"is_company", "=", true}}}, + []any{[]any{ + []any{"is_company", "=", true}, + }}, map[string]int64{"limit": 1}, - }, &ids); err != nil { - log.Fatalln(err) - } + }, &ids) var records []any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "read", ids, - }, &records); err != nil { - log.Fatalln(err) - } + }, &records) // count the number of fields fetched by default count := len(records) @@ -661,16 +627,14 @@ which tends to be a huge amount. .. code-tab:: go var recordFields []map[string]any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "read", ids, map[string][]string{ "fields": {"name", "country_id", "comment"}, }, - }, &recordFields); err != nil { - log.Fatalln(err) - } + }, &recordFields) Result: @@ -722,11 +686,7 @@ updating a record). .. code-tab:: go - recordFields := map[string]struct { - String string `xmlrpc:"string"` - Type string `xmlrpc:"type"` - Help string `xmlrpc:"help"` - }{} + recordFields := map[string]string{} if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "fields_get", @@ -734,9 +694,7 @@ updating a record). map[string][]string{ "attributes": {"string", "help", "type"}, }, - }, &recordFields); err != nil { - log.Fatalln(err) - } + }, &recordFields) Result: @@ -824,17 +782,17 @@ if that list is not provided it will fetch all fields of matched records). .. code-tab:: go var recordFields []map[string]any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search_read", - []any{[]any{[]any{"is_company", "=", true}}}, + []any{[]any{ + []any{"is_company", "=", true}, + }}, map[string]any{ "fields": []string{"name", "country_id", "comment"}, "limit": 5, }, - }, &recordFields); err != nil { - log.Fatalln(err) - } + }, &recordFields) Result: @@ -910,15 +868,13 @@ set through the mapping argument, the default value will be used. .. code-tab:: go var id int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "create", []map[string]string{ {"name": "New Partner"}, }, - }, &id); err != nil { - log.Fatalln(err) - } + }, &id) Result: @@ -992,27 +948,23 @@ a record). .. code-tab:: go var result bool - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "write", []any{ []int64{id}, map[string]string{"name": "Newer partner"}, }, - }, &result); err != nil { - log.Fatalln(err) - } + }, &result) // get record name after having changed it var record []any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "name_get", []any{ []int64{id}, }, - }, &record); err != nil { - log.Fatalln(err) - } + }, &record) Result: @@ -1066,18 +1018,16 @@ Records can be deleted in bulk by providing their ids to .. code-tab:: go var result bool - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "unlink", []any{ []int64{id}, }, - }, &result); err != nil { - log.Fatalln(err) - } + }, &result) // check if the deleted record is still in the database var record []any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", []any{ @@ -1085,9 +1035,7 @@ Records can be deleted in bulk by providing their ids to []any{"id", "=", id}, }, }, - }, &record); err != nil { - log.Fatalln(err) - } + }, &record) Result: @@ -1204,7 +1152,7 @@ Provides information about Odoo models via its various fields. .. code-tab:: go var id int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "ir.model", "create", []map[string]string{ @@ -1214,24 +1162,16 @@ Provides information about Odoo models via its various fields. "state": "manual", }, }, - }, &id); err != nil { - log.Fatalln(err) - } - recordFields := map[string]struct { - String string `xmlrpc:"string"` - Type string `xmlrpc:"type"` - Help string `xmlrpc:"help"` - }{} - if err := models.Call("execute_kw", []any{ + }, &id) + recordFields := map[string]string{} + models.Call("execute_kw", []any{ db, uid, password, "x_custom_model", "fields_get", []any{}, map[string][]string{ "attributes": {"string", "help", "type"}, }, - }, &recordFields); err != nil { - log.Fatalln(err) - } + }, &recordFields) Result: @@ -1401,7 +1341,7 @@ custom fields without using Python code. .. code-tab:: go var id int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "ir.model", "create", []map[string]string{ @@ -1411,11 +1351,9 @@ custom fields without using Python code. "state": "manual", }, }, - }, &id); err != nil { - log.Fatalln(err) - } + }, &id) var fieldId int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "ir.model.fields", "create", []map[string]any{ @@ -1427,27 +1365,21 @@ custom fields without using Python code. "required": true, }, }, - }, &fieldId); err != nil { - log.Fatalln(err) - } + }, &fieldId) var recordId int64 - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "x_custom", "create", []map[string]string{ {"x_name": "test record"}, }, - }, &recordId); err != nil { - log.Fatalln(err) - } + }, &recordId) var recordFields []map[string]any - if err := models.Call("execute_kw", []any{ + models.Call("execute_kw", []any{ db, uid, password, "x_custom", "read", [][]int64{{recordId}}, - }, recordFields); err != nil { - log.Fatalln(err) - } + }, recordFields) Result: From 655305169d79951e2401df7865a9d72dacbd161a Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 12 Jul 2023 10:42:17 +0100 Subject: [PATCH 6/8] A small typo in the authentication example has been fixed. A small typo in the authentication example has been fixed. Array replacement by map. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index 2ed9c170ad..c55f390e93 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -283,7 +283,7 @@ Result: .. code-tab:: go var uid int64 - client.Call("authenticate", []any{db, username, password, []any{}}, &uid) + client.Call("authenticate", []any{db, username, password, map[string]any{}}, &uid) .. _api/external_api/calling_methods: From e658489854cfbb937ba45c8cc2fa9e7765cc24e7 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 12 Jul 2023 11:01:26 +0100 Subject: [PATCH 7/8] Another small fix! One more correction. The constants url, db, username and password were replaced by variables, so that they can be assigned with new values. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index c55f390e93..8f0834773e 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -74,9 +74,9 @@ If you already have an Odoo server installed, you can just use its parameters. .. code-tab:: go - const ( - url = - db = + var ( + url = + db = username = "admin" password = ) @@ -196,10 +196,10 @@ database: client, _ := xmlrpc.NewClient("https://demo.odoo.com/start", nil) info := map[string]string{} client.Call("start", nil, &info) - url := info["host"].(string) - db := info["database"].(string) - username := info["user"].(string) - password := info["password"].(string) + url = info["host"].(string) + db = info["database"].(string) + username = info["user"].(string) + password = info["password"].(string) .. note:: These examples use the `github.com/kolo/xmlrpc library `_. From d6d415e3fa8dcc37ac727de0a4394e3aeb802763 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Thu, 13 Jul 2023 10:44:59 +0100 Subject: [PATCH 8/8] The error checking stuff has been added again! Now errors can be checked immediately before proceeding to the next steps. X-original-commit: 0a65b0872de360a4d4e8cfebeed89c566d53b3f8 --- content/developer/reference/external_api.rst | 152 +++++++++++++------ 1 file changed, 103 insertions(+), 49 deletions(-) diff --git a/content/developer/reference/external_api.rst b/content/developer/reference/external_api.rst index 8f0834773e..af5787ada1 100644 --- a/content/developer/reference/external_api.rst +++ b/content/developer/reference/external_api.rst @@ -193,7 +193,10 @@ database: .. code-block:: go - client, _ := xmlrpc.NewClient("https://demo.odoo.com/start", nil) + client, err := xmlrpc.NewClient("https://demo.odoo.com/start", nil) + if err != nil { + log.Fatal(err) + } info := map[string]string{} client.Call("start", nil, &info) url = info["host"].(string) @@ -246,9 +249,14 @@ the login. .. code-tab:: go - client, _ := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) + client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil) + if err != nil { + log.Fatal(err) + } common := map[string]any{} - client.Call("version", nil, &common) + if err := client.Call("version", nil, &common); err != nil { + log.Fatal(err) + } Result: @@ -283,7 +291,12 @@ Result: .. code-tab:: go var uid int64 - client.Call("authenticate", []any{db, username, password, map[string]any{}}, &uid) + if err := client.Call("authenticate", []any{ + db, username, password, + map[string]any{}, + }, &uid); err != nil { + log.Fatal(err) + } .. _api/external_api/calling_methods: @@ -343,14 +356,19 @@ Each call to ``execute_kw`` takes the following parameters: .. code-tab:: go - models, _ := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) + models, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil) + if err != nil { + log.Fatal(err) + } var result bool - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "check_access_rights", []string{"read"}, map[string]bool{"raise_exception": false}, - }, &result) + }, &result); err != nil { + log.Fatal(err) + } Result: @@ -397,13 +415,15 @@ database identifiers of all records matching the filter. .. code-tab:: go var records []int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", []any{[]any{ []any{"is_company", "=", true}, }}, - }, &records) + }, &records); err != nil { + log.Fatal(err) + } Result: @@ -447,14 +467,16 @@ available to only retrieve a subset of all matched records. .. code-tab:: go var records []int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", []any{[]any{ []any{"is_company", "=", true}, }}, map[string]int64{"offset": 10, "limit": 5}, - }, &records) + }, &records); err != nil { + log.Fatal(err) + } Result: @@ -499,13 +521,15 @@ only the number of records matching the query. It takes the same .. code-tab:: go var counter int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search_count", []any{[]any{ []any{"is_company", "=", true}, }}, - }, &counter) + }, &counter); err != nil { + log.Fatal(err) + } Result: @@ -574,20 +598,24 @@ which tends to be a huge amount. .. code-tab:: go var ids []int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", []any{[]any{ []any{"is_company", "=", true}, }}, map[string]int64{"limit": 1}, - }, &ids) + }, &ids); err != nil { + log.Fatal(err) + } var records []any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "read", ids, - }, &records) + }, &records); err != nil { + log.Fatal(err) + } // count the number of fields fetched by default count := len(records) @@ -627,14 +655,16 @@ which tends to be a huge amount. .. code-tab:: go var recordFields []map[string]any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "read", ids, map[string][]string{ "fields": {"name", "country_id", "comment"}, }, - }, &recordFields) + }, &recordFields); err != nil { + log.Fatal(err) + } Result: @@ -694,7 +724,9 @@ updating a record). map[string][]string{ "attributes": {"string", "help", "type"}, }, - }, &recordFields) + }, &recordFields); err != nil { + log.Fatal(err) + } Result: @@ -782,7 +814,7 @@ if that list is not provided it will fetch all fields of matched records). .. code-tab:: go var recordFields []map[string]any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search_read", []any{[]any{ @@ -792,7 +824,9 @@ if that list is not provided it will fetch all fields of matched records). "fields": []string{"name", "country_id", "comment"}, "limit": 5, }, - }, &recordFields) + }, &recordFields); err != nil { + log.Fatal(err) + } Result: @@ -868,13 +902,15 @@ set through the mapping argument, the default value will be used. .. code-tab:: go var id int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "create", []map[string]string{ {"name": "New Partner"}, }, - }, &id) + }, &id); err != nil { + log.Fatal(err) + } Result: @@ -948,23 +984,27 @@ a record). .. code-tab:: go var result bool - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "write", []any{ []int64{id}, map[string]string{"name": "Newer partner"}, }, - }, &result) + }, &result); err != nil { + log.Fatal(err) + } // get record name after having changed it var record []any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "name_get", []any{ []int64{id}, }, - }, &record) + }, &record); err != nil { + log.Fatal(err) + } Result: @@ -1018,24 +1058,26 @@ Records can be deleted in bulk by providing their ids to .. code-tab:: go var result bool - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "unlink", []any{ []int64{id}, }, - }, &result) + }, &result); err != nil { + log.Fatal(err) + } // check if the deleted record is still in the database var record []any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "res.partner", "search", - []any{ - []any{ - []any{"id", "=", id}, - }, - }, - }, &record) + []any{[]any{ + []any{"id", "=", id}, + }}, + }, &record); err != nil { + log.Fatal(err) + } Result: @@ -1152,7 +1194,7 @@ Provides information about Odoo models via its various fields. .. code-tab:: go var id int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "ir.model", "create", []map[string]string{ @@ -1162,16 +1204,20 @@ Provides information about Odoo models via its various fields. "state": "manual", }, }, - }, &id) + }, &id); err != nil { + log.Fatal(err) + } recordFields := map[string]string{} - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "x_custom_model", "fields_get", []any{}, map[string][]string{ "attributes": {"string", "help", "type"}, }, - }, &recordFields) + }, &recordFields); err != nil { + log.Fatal(err) + } Result: @@ -1341,7 +1387,7 @@ custom fields without using Python code. .. code-tab:: go var id int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "ir.model", "create", []map[string]string{ @@ -1351,9 +1397,11 @@ custom fields without using Python code. "state": "manual", }, }, - }, &id) + }, &id); err != nil { + log.Fatal(err) + } var fieldId int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "ir.model.fields", "create", []map[string]any{ @@ -1365,21 +1413,27 @@ custom fields without using Python code. "required": true, }, }, - }, &fieldId) + }, &fieldId); err != nil { + log.Fatal(err) + } var recordId int64 - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "x_custom", "create", []map[string]string{ {"x_name": "test record"}, }, - }, &recordId) + }, &recordId); err != nil { + log.Fatal(err) + } var recordFields []map[string]any - models.Call("execute_kw", []any{ + if err := models.Call("execute_kw", []any{ db, uid, password, "x_custom", "read", [][]int64{{recordId}}, - }, recordFields) + }, recordFields); err != nil { + log.Fatal(err) + } Result: