diff --git a/conf.py b/conf.py index e433275e48..4e55a0207c 100644 --- a/conf.py +++ b/conf.py @@ -117,9 +117,6 @@ # Redirection generator 'redirects', - # Code switcher (switcher and case directives) - 'switcher', - # Content tabs 'sphinx_tabs.tabs', diff --git a/content/contributing/documentation/rst_cheat_sheet.rst b/content/contributing/documentation/rst_cheat_sheet.rst index 75553747aa..577115a8a6 100644 --- a/content/contributing/documentation/rst_cheat_sheet.rst +++ b/content/contributing/documentation/rst_cheat_sheet.rst @@ -57,7 +57,7 @@ Use the following markups to emphasize your text to your liking: +--------------+----------+ | \*Text\* | *Text* | +--------------+----------+ -| \`\`Text\`\` | ``Text`` | +| \`Text\` | ``Text`` | +--------------+----------+ .. seealso:: @@ -521,6 +521,29 @@ Render Customize this admonition with a **Title** of your choice. +.. _contributing/code-blocks: + +Code blocks +=========== + +RST +--- + +.. code-block:: text + + .. code-block:: python + + def main(): + print("Hello world!") + +Render +------ + +.. code-block:: python + + def main(): + print("Hello world!") + .. _contributing/tabs: Content tabs @@ -535,9 +558,14 @@ Content tabs - A tab cannot contain :ref:`internal hyperlink targets `. +.. _contributing/tabs/basic: + Basic tabs ---------- +Basic tabs are useful to split content into multiple options. The `tabs` directive is used to define +a sequence of tabs. Each tab is then defined with the `tab` directive followed by a label. + RST ~~~ @@ -574,9 +602,13 @@ Render Third version for On-premise users. +.. _contributing/tabs/nested: + Nested tabs ----------- +Tabs can be nested inside one another. + RST ~~~ @@ -645,9 +677,15 @@ Render Orbits Jupiter. +.. _contributing/tabs/group: + Group tabs ---------- +Group tabs are special tabs that synchronize based on a group label. The last selected group is +remembered and automatically selected when the user returns on the page or visits another page with +the tabs group. The `group-tab` directive is used to define group tabs. + RST ~~~ @@ -734,6 +772,62 @@ Render public static void main(String[] args) {} } +.. _contributing/tabs/code: + +Code tabs +--------- + +Code tabs are essentially :ref:`group tabs ` that treat the content as a +:ref:`code block `. The `code-tab` directive is used to define a code tab. +Just as for the `code-block` directive, the language defines the syntax highlighting of the tab. If +set, the label is used instead of the language for grouping tabs. + +RST +~~~ + +.. code-block:: rst + + .. tabs:: + + .. code-tab:: c++ Hello C++ + + #include + + int main() { + std::cout << "Hello World"; + return 0; + } + + .. code-tab:: python Hello Python + + print("Hello World") + + .. code-tab:: javascript Hello JavaScript + + console.log("Hello World"); + +Render +~~~~~~ + +.. tabs:: + + .. code-tab:: c++ Hello C++ + + #include + + int main() { + std::cout << "Hello World"; + return 0; + } + + .. code-tab:: python Hello Python + + print("Hello World") + + .. code-tab:: javascript Hello JavaScript + + console.log("Hello World"); + .. _contributing/document-metadata: Document metadata diff --git a/content/developer/misc/api/extract_api.rst b/content/developer/misc/api/extract_api.rst index 636113261e..aa92edaae0 100644 --- a/content/developer/misc/api/extract_api.rst +++ b/content/developer/misc/api/extract_api.rst @@ -76,28 +76,25 @@ Request Body The client email. .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "method": string, - "params": { - "account_token": string (hex), - "version": int, - "documents": [string], - "user_infos": { - "user_company_vat": string, - "user_company_name": string, - "user_company_country_code": string, - "user_lang": string, - "user_email": string, - }, - }, - "id": string (hex), - } +.. code-block:: text + + { + "jsonrpc": string, + "method": string, + "params": { + "account_token": string (hex), + "version": int, + "documents": [string], + "user_infos": { + "user_company_vat": string, + "user_company_name": string, + "user_company_country_code": string, + "user_lang": string, + "user_email": string, + }, + }, + "id": string (hex), + } Response ^^^^^^^^ @@ -127,21 +124,17 @@ Response ============= ============================================================== .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "id": string, - "result": { - "status_code": int, - "status_msg": string, - "document_id": int, - } - } - +.. code-block:: text + + { + "jsonrpc": string, + "id": string, + "result": { + "status_code": int, + "status_msg": string, + "document_id": int, + } + } .. _webservices/extract_api/invoice_get_results: @@ -168,21 +161,17 @@ Request Body The list of ``document_id`` for which you want to get the current parsing status. .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "method": string, - "params": { - "version": int, - "documents_ids": [int] - }, - "id": string (hex), - } - +.. code-block:: text + + { + "jsonrpc": string, + "method": string, + "params": { + "version": int, + "documents_ids": [int] + }, + "id": string (hex), + } Response ^^^^^^^^ @@ -213,34 +202,31 @@ Response ============= ============================================================== .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "id": string, - "result": { - "document_id_1": { - "status_code": int, - "status_msg": str, - "results": [{"feature_1_name": feature_1_result, - "feature_2_name": feature_2_result, - … - }] - }, - "document_id_2": { - "status_code": int, - "status_msg": str, - "results": [{"feature_1_name": feature_1_result, - "feature_2_name": feature_2_result, - … - }] - }, - ... - } - } +.. code-block:: text + + { + "jsonrpc": string, + "id": string, + "result": { + "document_id_1": { + "status_code": int, + "status_msg": str, + "results": [{"feature_1_name": feature_1_result, + "feature_2_name": feature_2_result, + … + }] + }, + "document_id_2": { + "status_code": int, + "status_msg": str, + "results": [{"feature_1_name": feature_1_result, + "feature_2_name": feature_2_result, + … + }] + }, + ... + } + } .. _webservices/extract_api/invoice_get_results/feature_result: @@ -256,16 +242,13 @@ For each feature, we return a list of candidates and we spotlight the candidate ``words`` List of all the candidates for this feature ordered by decreasing score. -.. container:: doc-aside - - .. switcher:: - - .. code-block:: text +.. rst-class:: setup doc-aside +.. code-block:: text - { - "selected_value": candidate_12, - "words": [candidate_12, candidate_3, candidate_4,...] - } + { + "selected_value": candidate_12, + "words": [candidate_12, candidate_3, candidate_4,...] + } ``candidate`` @@ -281,17 +264,14 @@ For each candidate we give its representation and position in the document. Cand ``page`` Page of the original document on which the candidate is located (starts at 0). -.. container:: doc-aside - - .. switcher:: - - .. code-block:: text +.. rst-class:: setup doc-aside +.. code-block:: text - { - "content": string|float, - "coords": [float, float, float, float, float], - "page": int - } + { + "content": string|float, + "coords": [float, float, float, float, float], + "page": int + } +-------------------------+------------------------------------------------------------------------------------+ | Feature name | Specifities | @@ -351,27 +331,24 @@ For each candidate we give its representation and position in the document. Cand It follows a more specific structure. It is basically a list of dictionaries where each dictionary represents an invoice line. Each value follows a :ref:`webservices/extract_api/invoice_get_results/feature_result` structure. -.. container:: doc-aside - - .. switcher:: - - .. code-block:: text - - [ - { - "description": feature_result, - "discount": feature_result, - "product": feature_result, - "quantity": feature_result, - "subtotal": feature_result, - "total": feature_result, - "taxes": feature_result, - "total": feature_result, - "unit": feature_result, - "unit_price": feature_result - }, - ... - ] +.. rst-class:: doc-aside +.. code-block:: text + + [ + { + "description": feature_result, + "discount": feature_result, + "product": feature_result, + "quantity": feature_result, + "subtotal": feature_result, + "total": feature_result, + "taxes": feature_result, + "total": feature_result, + "unit": feature_result, + "unit_price": feature_result + }, + ... + ] .. _webservices/extract_api/invoice_validate: @@ -400,25 +377,22 @@ Request Body Therefore you should validate all the features you want to validate at once. .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "method": string, - "params": { - "document_id": int, - "values": { - "merged_lines": bool - "feature_name_1": validation_1, - "feature_name_2": validation_2, - ... - } - }, - "id": string (hex), - } +.. code-block:: text + + { + "jsonrpc": string, + "method": string, + "params": { + "document_id": int, + "values": { + "merged_lines": bool + "feature_name_1": validation_1, + "feature_name_2": validation_2, + ... + } + }, + "id": string (hex), + } ``validation`` '''''''''''''' @@ -427,12 +401,9 @@ A **validation** for a given feature is a dictionary containing the textual repr This format apply for all the features except for ``global_taxes`` and ``invoice_lines`` which have more complex validation format. .. rst-class:: setup doc-aside +.. code-block:: text -.. switcher:: - - .. code-block:: text - - { "content": string|float } + { "content": string|float } validation for ``global_taxes`` ''''''''''''''''''''''''''''''' @@ -449,20 +420,17 @@ validation for ``global_taxes`` Indicates if ``amount`` already contains the tax or not. .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - {"content": [ - { - "amount": float, - "tax_amount": float, - "tax_amount_type": "fixed"|"percent", - "tax_price_include": bool - }, - ... - ]} +.. code-block:: text + + {"content": [ + { + "amount": float, + "tax_amount": float, + "tax_amount_type": "fixed"|"percent", + "tax_price_include": bool + }, + ... + ]} validation for ``invoice_lines`` '''''''''''''''''''''''''''''''' @@ -470,31 +438,28 @@ validation for ``invoice_lines`` **lines** is a list of dictionaries. Each dictionary represents an invoice line. The dictionary keys speak for themselves. .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - {"lines": [ - { - "description": string, - "quantity": float, - "unit_price": float, - "product": string, - "taxes_amount": float, - "taxes": [ - { - "amount": float, - "type": "fixed"|"percent", - "price_include": bool - }, - ... - ], - "subtotal": float, - "total": float - }, - ... - ]} +.. code-block:: text + + {"lines": [ + { + "description": string, + "quantity": float, + "unit_price": float, + "product": string, + "taxes_amount": float, + "taxes": [ + { + "amount": float, + "type": "fixed"|"percent", + "price_include": bool + }, + ... + ], + "subtotal": float, + "total": float + }, + ... + ]} Response -------- @@ -517,19 +482,16 @@ Response ============= ========================================================== .. rst-class:: setup doc-aside - -.. switcher:: - - .. code-block:: text - - { - "jsonrpc": string, - "id": string, - "result": { - "status_code": int, - "status_msg": string, - } - } +.. code-block:: text + + { + "jsonrpc": string, + "id": string, + "result": { + "status_code": int, + "status_msg": string, + } + } .. _webservices/extract_api/integration_testing: diff --git a/content/developer/misc/api/iap.rst b/content/developer/misc/api/iap.rst index 9bc2ef56a3..e484539f86 100644 --- a/content/developer/misc/api/iap.rst +++ b/content/developer/misc/api/iap.rst @@ -1,5 +1,5 @@ -.. _webservices/iap: +.. _api/iap: =============== In-App Purchase diff --git a/content/developer/misc/api/odoo.rst b/content/developer/misc/api/odoo.rst index 17c5c7e748..cd4dd58274 100644 --- a/content/developer/misc/api/odoo.rst +++ b/content/developer/misc/api/odoo.rst @@ -1,5 +1,3 @@ -:code-column: - ============ External API ============ @@ -9,88 +7,15 @@ all of its data are also available from the outside for external analysis or integration with various tools. Part of the :ref:`reference/orm/model` API is easily available over XML-RPC_ and accessible from a variety of languages. -.. Odoo XML-RPC idiosyncrasies: - * uses multiple endpoint and a nested call syntax instead of a - "hierarchical" server structure (e.g. ``odoo.res.partner.read()``) - * uses its own own manual auth system instead of basic auth or sessions - (basic is directly supported the Python and Ruby stdlibs as well as - ws-xmlrpc, not sure about ripcord) - * own auth is inconvenient as (uid, password) have to be explicitly passed - into every call. Session would allow db to be stored as well - These issues are especially visible in Java, somewhat less so in PHP - Connection ========== -.. kinda gross because it duplicates existing bits - -.. only:: html - - .. rst-class:: setupcode hidden - - .. code-block:: python3 - - import xmlrpc.client - info = xmlrpc.client.ServerProxy('https://demo.odoo.com/start').start() - url, db, username, password = \ - info['host'], info['database'], info['user'], info['password'] - common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url)) - uid = common.authenticate(db, username, password, {}) - models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url)) - - .. code-block:: ruby - - require "xmlrpc/client" - info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start') - url, db, username, password = \ - info['host'], info['database'], info['user'], info['password'] - common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") - uid = common.call('authenticate', db, username, password, {}) - models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy - - .. code-block:: php - - require_once('ripcord.php'); - $info = ripcord::client('https://demo.odoo.com/start')->start(); - list($url, $db, $username, $password) = - array($info['host'], $info['database'], $info['user'], $info['password']); - $common = ripcord::client("$url/xmlrpc/2/common"); - $uid = $common->authenticate($db, $username, $password, array()); - $models = ripcord::client("$url/xmlrpc/2/object"); - - .. code-block:: java - - final XmlRpcClient client = new XmlRpcClient(); - final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); - start_config.setServerURL(new URL("https://demo.odoo.com/start")); - final Map info = (Map)client.execute( - start_config, "start", emptyList()); - - final String url = info.get("host"), - db = info.get("database"), - username = info.get("user"), - password = info.get("password"); - - final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); - common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); - - int uid = (int)client.execute( - common_config, "authenticate", Arrays.asList( - db, username, password, emptyMap())); - - final XmlRpcClient models = new XmlRpcClient() {{ - setConfig(new XmlRpcClientConfigImpl() {{ - setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); - }}); - }}; - Configuration ------------- -If you already have an Odoo server installed, you can just use its -parameters +If you already have an Odoo server installed, you can just use its parameters. -.. warning:: +.. important:: For Odoo Online instances (.odoo.com), users are created without a *local* password (as a person you are logged in via the Odoo Online @@ -98,52 +23,49 @@ parameters Online instances, you will need to set a password on the user account you want to use: - * Log in your instance with an administrator account - * Go to :menuselection:`Settings --> Users & Companies --> Users` - * Click on the user you want to use for XML-RPC access - * Click on :guilabel:`Action` and select :guilabel:`Change Password` - * Set a :guilabel:`New Password` value then click - :guilabel:`Change Password`. + * Log in your instance with an administrator account. + * Go to :menuselection:`Settings --> Users & Companies --> Users`. + * Click on the user you want to use for XML-RPC access. + * Click on :guilabel:`Action` and select :guilabel:`Change Password`. + * Set a :guilabel:`New Password` value then click :guilabel:`Change Password`. The *server url* is the instance's domain (e.g. *https://mycompany.odoo.com*), the *database name* is the name of the instance (e.g. *mycompany*). The *username* is the configured user's login as shown by the *Change Password* screen. -.. rst-class:: setup doc-aside +.. tabs:: -.. switcher:: + .. code-tab:: python - .. code-block:: python3 + url = + db = + username = 'admin' + password = - url = - db = - username = 'admin' - password = + .. code-tab:: ruby - .. code-block:: ruby + url = + db = + username = "admin" + password = - url = - db = - username = "admin" - password = + .. code-tab:: php - .. code-block:: php + $url = ; + $db = ; + $username = "admin"; + $password = ; - $url = ; - $db = ; - $username = "admin"; - $password = ; + .. code-tab:: java - .. code-block:: java - - final String url = , - db = , - username = "admin", - password = ; + final String url = , + db = , + username = "admin", + password = ; API Keys -'''''''' +~~~~~~~~ .. versionadded:: 14.0 @@ -158,14 +80,14 @@ account (although they can not be used to log-in via the interface). In order to add a key to your account, simply go to your :guilabel:`Preferences` (or :guilabel:`My Profile`): -.. figure:: images/preferences.png - :align: center +.. image:: images/preferences.png + :align: center then open the :guilabel:`Account Security` tab, and click :guilabel:`New API Key`: -.. figure:: images/account-security.png - :align: center +.. image:: images/account-security.png + :align: center Input a description for the key, **this description should be as clear and complete as possible**: it is the only way you will have to identify your keys @@ -180,81 +102,73 @@ lost). Once you have keys configured on your account, they will appear above the :guilabel:`New API Key` button, and you will be able to delete them: -.. figure:: images/delete-key.png - :align: center +.. image:: images/delete-key.png + :align: center **A deleted API key can not be undeleted or re-set**. You will have to generate a new key and update all the places where you used the old one. -demo -'''' +Test database +~~~~~~~~~~~~~ To make exploration simpler, you can also ask https://demo.odoo.com for a test database: -.. rst-class:: setup doc-aside - -.. switcher:: +.. tabs:: - .. code-block:: python3 + .. code-tab:: python - import xmlrpc.client - info = xmlrpc.client.ServerProxy('https://demo.odoo.com/start').start() - url, db, username, password = \ - info['host'], info['database'], info['user'], info['password'] + import xmlrpc.client + info = xmlrpc.client.ServerProxy('https://demo.odoo.com/start').start() + url, db, username, password = info['host'], info['database'], info['user'], info['password'] - .. code-block:: ruby + .. code-tab:: ruby - require "xmlrpc/client" - info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start') - url, db, username, password = \ - info['host'], info['database'], info['user'], info['password'] + require "xmlrpc/client" + info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start') + url, db, username, password = info['host'], info['database'], info['user'], info['password'] - .. case:: PHP + .. group-tab:: PHP - .. code-block:: php + .. code-block:: php - require_once('ripcord.php'); - $info = ripcord::client('https://demo.odoo.com/start')->start(); - list($url, $db, $username, $password) = - array($info['host'], $info['database'], $info['user'], $info['password']); + require_once('ripcord.php'); + $info = ripcord::client('https://demo.odoo.com/start')->start(); + list($url, $db, $username, $password) = array($info['host'], $info['database'], $info['user'], $info['password']); - .. note:: + .. note:: + These examples use the `Ripcord `_ + library, which provides a simple XML-RPC API. Ripcord requires that + `XML-RPC support be enabled + `_ in your PHP + installation. - These examples use the `Ripcord `_ - library, which provides a simple XML-RPC API. Ripcord requires that - `XML-RPC support be enabled - `_ in your PHP - installation. + Since calls are performed over + `HTTPS `_, it also requires that + the `OpenSSL extension + `_ be enabled. - Since calls are performed over - `HTTPS `_, it also requires that - the `OpenSSL extension - `_ be enabled. + .. group-tab:: Java - .. case:: Java + .. code-block:: java - .. code-block:: java + final XmlRpcClient client = new XmlRpcClient(); - final XmlRpcClient client = new XmlRpcClient(); + final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); + start_config.setServerURL(new URL("https://demo.odoo.com/start")); + final Map info = (Map)client.execute( + start_config, "start", emptyList()); - final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); - start_config.setServerURL(new URL("https://demo.odoo.com/start")); - final Map info = (Map)client.execute( - start_config, "start", emptyList()); + final String url = info.get("host"), + db = info.get("database"), + username = info.get("user"), + password = info.get("password"); - final String url = info.get("host"), - db = info.get("database"), - username = info.get("user"), - password = info.get("password"); + .. note:: + These examples use the `Apache XML-RPC library `_. - .. note:: - - These examples use the `Apache XML-RPC library - `_ - - The examples do not include imports as these imports couldn't be - pasted in the code. + The examples do not include imports as these imports couldn't be + pasted in the code. Logging in ---------- @@ -270,71 +184,65 @@ authentication itself is done through the ``authenticate`` function and returns a user identifier (``uid``) used in authenticated calls instead of the login. -.. rst-class:: setup doc-aside - -.. switcher:: +.. tabs:: - .. code-block:: python3 + .. code-tab:: python - common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url)) - common.version() + common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url)) + common.version() - .. code-block:: ruby + .. code-tab:: ruby - common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") - common.call('version') + common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") + common.call('version') - .. code-block:: php + .. code-tab:: php - $common = ripcord::client("$url/xmlrpc/2/common"); - $common->version(); + $common = ripcord::client("$url/xmlrpc/2/common"); + $common->version(); - .. code-block:: java + .. code-tab:: java - final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); - common_config.setServerURL( - new URL(String.format("%s/xmlrpc/2/common", url))); - client.execute(common_config, "version", emptyList()); + final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); + common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); + client.execute(common_config, "version", emptyList()); -.. rst-class:: doc-aside +Result: .. code-block:: json - { - "server_version": "13.0", - "server_version_info": [13, 0, 0, "final", 0], - "server_serie": "13.0", - "protocol_version": 1, - } + { + "server_version": "13.0", + "server_version_info": [13, 0, 0, "final", 0], + "server_serie": "13.0", + "protocol_version": 1, + } -.. rst-class:: setup doc-aside -.. switcher:: +.. tabs:: - .. code-block:: python3 + .. code-tab:: python - uid = common.authenticate(db, username, password, {}) + uid = common.authenticate(db, username, password, {}) - .. code-block:: ruby + .. code-tab:: ruby - uid = common.call('authenticate', db, username, password, {}) + uid = common.call('authenticate', db, username, password, {}) - .. code-block:: php + .. code-tab:: php - $uid = $common->authenticate($db, $username, $password, array()); + $uid = $common->authenticate($db, $username, $password, array()); - .. code-block:: java + .. code-tab:: java - int uid = (int)client.execute( - common_config, "authenticate", asList( - db, username, password, emptyMap())); + int uid = (int)client.execute(common_config, "authenticate", asList(db, username, password, emptyMap())); -.. _webservices/odoo/calling_methods: +.. _api/odoo/calling_methods: Calling methods =============== -The second endpoint is ``xmlrpc/2/object``, is used to call methods of odoo +The second endpoint is ``xmlrpc/2/object``. It is used to call methods of odoo models via the ``execute_kw`` RPC function. Each call to ``execute_kw`` takes the following parameters: @@ -347,57 +255,49 @@ Each call to ``execute_kw`` takes the following parameters: * an array/list of parameters passed by position * a mapping/dict of parameters to pass by keyword (optional) -.. container:: doc-aside +.. example:: - For instance to see if we can read the ``res.partner`` model we can call - ``check_access_rights`` with ``operation`` passed by position and - ``raise_exception`` passed by keyword (in order to get a true/false result - rather than true/error): + For instance, to see if we can read the ``res.partner`` model, we can call + ``check_access_rights`` with ``operation`` passed by position and + ``raise_exception`` passed by keyword (in order to get a true/false result + rather than true/error): - .. rst-class:: setup + .. tabs:: - .. switcher:: + .. code-tab:: python - .. code-block:: python3 + models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url)) + models.execute_kw(db, uid, password, 'res.partner', 'check_access_rights', ['read'], {'raise_exception': False}) - models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url)) - models.execute_kw(db, uid, password, - 'res.partner', 'check_access_rights', - ['read'], {'raise_exception': False}) + .. code-tab:: ruby - .. code-block:: ruby + models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy + models.execute_kw(db, uid, password, 'res.partner', 'check_access_rights', ['read'], {raise_exception: false}) - models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy - models.execute_kw(db, uid, password, - 'res.partner', 'check_access_rights', - ['read'], {raise_exception: false}) + .. code-tab:: php - .. code-block:: php + $models = ripcord::client("$url/xmlrpc/2/object"); + $models->execute_kw($db, $uid, $password, 'res.partner', 'check_access_rights', array('read'), array('raise_exception' => false)); - $models = ripcord::client("$url/xmlrpc/2/object"); - $models->execute_kw($db, $uid, $password, - 'res.partner', 'check_access_rights', - array('read'), array('raise_exception' => false)); + .. code-tab:: java - .. code-block:: java + final XmlRpcClient models = new XmlRpcClient() {{ + setConfig(new XmlRpcClientConfigImpl() {{ + setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); + }}); + }}; + models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "check_access_rights", + asList("read"), + new HashMap() {{ put("raise_exception", false); }} + )); - final XmlRpcClient models = new XmlRpcClient() {{ - setConfig(new XmlRpcClientConfigImpl() {{ - setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); - }}); - }}; - models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "check_access_rights", - asList("read"), - new HashMap() {{ put("raise_exception", false); }} - )); + Result: - .. code-block:: json + .. code-block:: json - true - - .. todo:: this should be runnable and checked + true List records ------------ @@ -406,89 +306,79 @@ Records can be listed and filtered via :meth:`~odoo.models.Model.search`. :meth:`~odoo.models.Model.search` takes a mandatory :ref:`domain ` filter (possibly empty), and returns the -database identifiers of all records matching the filter. To list customer -companies for instance: +database identifiers of all records matching the filter. + +.. example:: -.. container:: doc-aside + To list customer companies, for instance: - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', True]]]) + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]]) - .. code-block:: ruby + .. code-tab:: ruby - models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', true]]]) + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true]]]) - .. code-block:: php + .. code-tab:: php - $models->execute_kw($db, $uid, $password, - 'res.partner', 'search', array( - array(array('is_company', '=', true)))); + $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true)))); - .. code-block:: java + .. code-tab:: java - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "search", - asList(asList( - asList("is_company", "=", true))) - ))); + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search", + asList(asList( + asList("is_company", "=", true))) + ))); - .. code-block:: json + Result: - [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74] + .. code-block:: json + + [7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74] Pagination -'''''''''' +~~~~~~~~~~ By default a search will return the ids of all records matching the condition, which may be a huge number. ``offset`` and ``limit`` parameters are available to only retrieve a subset of all matched records. -.. container:: doc-aside +.. example:: + + .. tabs:: - .. switcher:: + .. code-tab:: python - .. code-block:: python3 + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'offset': 10, 'limit': 5}) - models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', True]]], - {'offset': 10, 'limit': 5}) + .. code-tab:: ruby - .. code-block:: ruby + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true]]], {offset: 10, limit: 5}) - models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', true]]], - {offset: 10, limit: 5}) + .. code-tab:: php - .. code-block:: php + $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true))), array('offset'=>10, 'limit'=>5)); - $models->execute_kw($db, $uid, $password, - 'res.partner', 'search', - array(array(array('is_company', '=', true))), - array('offset'=>10, 'limit'=>5)); + .. code-tab:: java - .. code-block:: java + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search", + asList(asList( + asList("is_company", "=", true))), + new HashMap() {{ put("offset", 10); put("limit", 5); }} + ))); - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "search", - asList(asList( - asList("is_company", "=", true))), - new HashMap() {{ put("offset", 10); put("limit", 5); }} - ))); + Result: - .. code-block:: json + .. code-block:: json - [13, 20, 30, 22, 29] + [13, 20, 30, 22, 29] Count records ------------- @@ -499,160 +389,139 @@ only the number of records matching the query. It takes the same :ref:`domain ` filter as :meth:`~odoo.models.Model.search` and no other parameter. -.. container:: doc-aside +.. example:: - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - models.execute_kw(db, uid, password, - 'res.partner', 'search_count', - [[['is_company', '=', True]]]) + models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', True]]]) - .. code-block:: ruby + .. code-tab:: ruby - models.execute_kw(db, uid, password, - 'res.partner', 'search_count', - [[['is_company', '=', true]]]) + models.execute_kw(db, uid, password, 'res.partner', 'search_count', [[['is_company', '=', true]]]) - .. code-block:: php + .. code-tab:: php - $models->execute_kw($db, $uid, $password, - 'res.partner', 'search_count', - array(array(array('is_company', '=', true)))); + $models->execute_kw($db, $uid, $password, 'res.partner', 'search_count', array(array(array('is_company', '=', true)))); - .. code-block:: java + .. code-tab:: java - (Integer)models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "search_count", - asList(asList( - asList("is_company", "=", true))) - )); + (Integer)models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search_count", + asList(asList( + asList("is_company", "=", true))) + )); - .. code-block:: json + Result: - 19 + .. code-block:: json -.. warning:: + 19 - calling ``search`` then ``search_count`` (or the other way around) may not - yield coherent results if other users are using the server: stored data - could have changed between the calls +.. note:: + Calling ``search`` then ``search_count`` (or the other way around) may not + yield coherent results if other users are using the server: stored data + could have changed between the calls. Read records ------------ -Record data is accessible via the :meth:`~odoo.models.Model.read` method, +Record data are accessible via the :meth:`~odoo.models.Model.read` method, which takes a list of ids (as returned by -:meth:`~odoo.models.Model.search`) and optionally a list of fields to -fetch. By default, it will fetch all the fields the current user can read, +:meth:`~odoo.models.Model.search`), and optionally a list of fields to +fetch. By default, it fetches all the fields the current user can read, which tends to be a huge amount. -.. container:: doc-aside +.. example:: - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - ids = models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', True]]], - {'limit': 1}) - [record] = models.execute_kw(db, uid, password, - 'res.partner', 'read', [ids]) - # count the number of fields fetched by default - len(record) + ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', True]]], {'limit': 1}) + [record] = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids]) + # count the number of fields fetched by default + len(record) - .. code-block:: ruby + .. code-tab:: ruby - ids = models.execute_kw(db, uid, password, - 'res.partner', 'search', - [[['is_company', '=', true]]], - {limit: 1}) - record = models.execute_kw(db, uid, password, - 'res.partner', 'read', [ids]).first - # count the number of fields fetched by default - record.length + ids = models.execute_kw(db, uid, password, 'res.partner', 'search', [[['is_company', '=', true]]], {limit: 1}) + record = models.execute_kw(db, uid, password, 'res.partner', 'read', [ids]).first + # count the number of fields fetched by default + record.length - .. code-block:: php + .. code-tab:: php - $ids = $models->execute_kw($db, $uid, $password, - 'res.partner', 'search', - array(array(array('is_company', '=', true))), - array('limit'=>1)); - $records = $models->execute_kw($db, $uid, $password, - 'res.partner', 'read', array($ids)); - // count the number of fields fetched by default - count($records[0]); + $ids = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', array(array(array('is_company', '=', true))), array('limit'=>1)); + $records = $models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids)); + // count the number of fields fetched by default + count($records[0]); - .. code-block:: java + .. code-tab:: java - final List ids = asList((Object[])models.execute( - "execute_kw", asList( - db, uid, password, - "res.partner", "search", - asList(asList( - asList("is_company", "=", true))), - new HashMap() {{ put("limit", 1); }}))); - final Map record = (Map)((Object[])models.execute( - "execute_kw", asList( - db, uid, password, - "res.partner", "read", - asList(ids) - ) - ))[0]; - // count the number of fields fetched by default - record.size(); + final List ids = asList((Object[])models.execute( + "execute_kw", asList( + db, uid, password, + "res.partner", "search", + asList(asList( + asList("is_company", "=", true))), + new HashMap() {{ put("limit", 1); }}))); + final Map record = (Map)((Object[])models.execute( + "execute_kw", asList( + db, uid, password, + "res.partner", "read", + asList(ids) + ) + ))[0]; + // count the number of fields fetched by default + record.size(); - .. code-block:: json + Result: - 121 + .. code-block:: json -Conversedly, picking only three fields deemed interesting. + 121 -.. container:: doc-aside + Conversely, picking only three fields deemed interesting. - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - models.execute_kw(db, uid, password, - 'res.partner', 'read', - [ids], {'fields': ['name', 'country_id', 'comment']}) + models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {'fields': ['name', 'country_id', 'comment']}) - .. code-block:: ruby + .. code-tab:: ruby - models.execute_kw(db, uid, password, - 'res.partner', 'read', - [ids], {fields: %w(name country_id comment)}) + models.execute_kw(db, uid, password, 'res.partner', 'read', [ids], {fields: %w(name country_id comment)}) - .. code-block:: php + .. code-tab:: php - $models->execute_kw($db, $uid, $password, - 'res.partner', 'read', - array($ids), - array('fields'=>array('name', 'country_id', 'comment'))); + $models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids), array('fields'=>array('name', 'country_id', 'comment'))); - .. code-block:: java + .. code-tab:: java - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "read", - asList(ids), - new HashMap() {{ - put("fields", asList("name", "country_id", "comment")); - }} - ))); + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "read", + asList(ids), + new HashMap() {{ + put("fields", asList("name", "country_id", "comment")); + }} + ))); - .. code-block:: json + Result: - [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}] + .. code-block:: json -.. note:: even if the ``id`` field is not requested, it is always returned + [{"comment": false, "country_id": [21, "Belgium"], "id": 7, "name": "Agrolait"}] -Listing record fields ---------------------- +.. note:: + Even if the ``id`` field is not requested, it is always returned. + +List record fields +------------------ :meth:`~odoo.models.Model.fields_get` can be used to inspect a model's fields and check which ones seem to be of interest. @@ -661,283 +530,263 @@ Because it returns a large amount of meta-information (it is also used by client programs) it should be filtered before printing, the most interesting items for a human user are ``string`` (the field's label), ``help`` (a help text if available) and ``type`` (to know which values to expect, or to send when -updating a record): - -.. container:: doc-aside - - .. switcher:: - - .. code-block:: python3 - - models.execute_kw( - db, uid, password, 'res.partner', 'fields_get', - [], {'attributes': ['string', 'help', 'type']}) - - .. code-block:: ruby - - models.execute_kw( - db, uid, password, 'res.partner', 'fields_get', - [], {attributes: %w(string help type)}) - - .. code-block:: php - - $models->execute_kw($db, $uid, $password, - 'res.partner', 'fields_get', - array(), array('attributes' => array('string', 'help', 'type'))); - - .. code-block:: java - - (Map>)models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "fields_get", - emptyList(), - new HashMap() {{ - put("attributes", asList("string", "help", "type")); - }} - )); - - .. code-block:: json - - { - "ean13": { - "type": "char", - "help": "BarCode", - "string": "EAN13" - }, - "property_account_position_id": { - "type": "many2one", - "help": "The fiscal position will determine taxes and accounts used for the partner.", - "string": "Fiscal Position" - }, - "signup_valid": { - "type": "boolean", - "help": "", - "string": "Signup Token is Valid" - }, - "date_localization": { - "type": "date", - "help": "", - "string": "Geo Localization Date" - }, - "ref_company_ids": { - "type": "one2many", - "help": "", - "string": "Companies that refers to partner" - }, - "sale_order_count": { - "type": "integer", - "help": "", - "string": "# of Sales Order" - }, - "purchase_order_count": { - "type": "integer", - "help": "", - "string": "# of Purchase Order" - }, +updating a record). + +.. example:: + + .. tabs:: + + .. code-tab:: python + + models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [], {'attributes': ['string', 'help', 'type']}) + + .. code-tab:: ruby + + models.execute_kw(db, uid, password, 'res.partner', 'fields_get', [], {attributes: %w(string help type)}) + + .. code-tab:: php + + $models->execute_kw($db, $uid, $password, 'res.partner', 'fields_get', array(), array('attributes' => array('string', 'help', 'type'))); + + .. code-tab:: java + + (Map>)models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "fields_get", + emptyList(), + new HashMap() {{ + put("attributes", asList("string", "help", "type")); + }} + )); + + Result: + + .. code-block:: json + + { + "ean13": { + "type": "char", + "help": "BarCode", + "string": "EAN13" + }, + "property_account_position_id": { + "type": "many2one", + "help": "The fiscal position will determine taxes and accounts used for the partner.", + "string": "Fiscal Position" + }, + "signup_valid": { + "type": "boolean", + "help": "", + "string": "Signup Token is Valid" + }, + "date_localization": { + "type": "date", + "help": "", + "string": "Geo Localization Date" + }, + "ref_company_ids": { + "type": "one2many", + "help": "", + "string": "Companies that refers to partner" + }, + "sale_order_count": { + "type": "integer", + "help": "", + "string": "# of Sales Order" + }, + "purchase_order_count": { + "type": "integer", + "help": "", + "string": "# of Purchase Order" + }, Search and read --------------- Because it is a very common task, Odoo provides a -:meth:`~odoo.models.Model.search_read` shortcut which as its name suggests is +:meth:`~odoo.models.Model.search_read` shortcut which, as its name suggests, is equivalent to a :meth:`~odoo.models.Model.search` followed by a :meth:`~odoo.models.Model.read`, but avoids having to perform two requests and keep ids around. Its arguments are similar to :meth:`~odoo.models.Model.search`'s, but it can also take a list of ``fields`` (like :meth:`~odoo.models.Model.read`, -if that list is not provided it will fetch all fields of matched records): - -.. container:: doc-aside - - .. switcher:: - - .. code-block:: python3 - - models.execute_kw(db, uid, password, - 'res.partner', 'search_read', - [[['is_company', '=', True]]], - {'fields': ['name', 'country_id', 'comment'], 'limit': 5}) - - .. code-block:: ruby - - models.execute_kw(db, uid, password, - 'res.partner', 'search_read', - [[['is_company', '=', true]]], - {fields: %w(name country_id comment), limit: 5}) - - .. code-block:: php - - $models->execute_kw($db, $uid, $password, - 'res.partner', 'search_read', - array(array(array('is_company', '=', true))), - array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5)); - - .. code-block:: java - - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "search_read", - asList(asList( - asList("is_company", "=", true))), - new HashMap() {{ - put("fields", asList("name", "country_id", "comment")); - put("limit", 5); - }} - ))); - - .. code-block:: json - - [ - { - "comment": false, - "country_id": [ 21, "Belgium" ], - "id": 7, - "name": "Agrolait" - }, - { - "comment": false, - "country_id": [ 76, "France" ], - "id": 18, - "name": "Axelor" - }, - { - "comment": false, - "country_id": [ 233, "United Kingdom" ], - "id": 12, - "name": "Bank Wealthy and sons" - }, - { - "comment": false, - "country_id": [ 105, "India" ], - "id": 14, - "name": "Best Designers" - }, - { - "comment": false, - "country_id": [ 76, "France" ], - "id": 17, - "name": "Camptocamp" - } - ] - +if that list is not provided it will fetch all fields of matched records). + +.. example:: + + .. tabs:: + + .. code-tab:: python + + models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', True]]], {'fields': ['name', 'country_id', 'comment'], 'limit': 5}) + + .. code-tab:: ruby + + models.execute_kw(db, uid, password, 'res.partner', 'search_read', [[['is_company', '=', true]]], {fields: %w(name country_id comment), limit: 5}) + + .. code-tab:: php + + $models->execute_kw($db, $uid, $password, 'res.partner', 'search_read', array(array(array('is_company', '=', true))), array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5)); + + .. code-tab:: java + + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search_read", + asList(asList( + asList("is_company", "=", true))), + new HashMap() {{ + put("fields", asList("name", "country_id", "comment")); + put("limit", 5); + }} + ))); + + Result: + + .. code-block:: json + + [ + { + "comment": false, + "country_id": [ 21, "Belgium" ], + "id": 7, + "name": "Agrolait" + }, + { + "comment": false, + "country_id": [ 76, "France" ], + "id": 18, + "name": "Axelor" + }, + { + "comment": false, + "country_id": [ 233, "United Kingdom" ], + "id": 12, + "name": "Bank Wealthy and sons" + }, + { + "comment": false, + "country_id": [ 105, "India" ], + "id": 14, + "name": "Best Designers" + }, + { + "comment": false, + "country_id": [ 76, "France" ], + "id": 17, + "name": "Camptocamp" + } + ] Create records -------------- Records of a model are created using :meth:`~odoo.models.Model.create`. The -method will create a single record and return its database identifier. +method creates a single record and returns its database identifier. :meth:`~odoo.models.Model.create` takes a mapping of fields to values, used to initialize the record. For any field which has a default value and is not set through the mapping argument, the default value will be used. -.. container:: doc-aside +.. example:: - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{ - 'name': "New Partner", - }]) + id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{'name': "New Partner"}]) - .. code-block:: ruby + .. code-tab:: ruby - id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{ - name: "New Partner", - }]) + id = models.execute_kw(db, uid, password, 'res.partner', 'create', [{name: "New Partner"}]) - .. code-block:: php + .. code-tab:: php - $id = $models->execute_kw($db, $uid, $password, - 'res.partner', 'create', - array(array('name'=>"New Partner"))); + $id = $models->execute_kw($db, $uid, $password, 'res.partner', 'create', array(array('name'=>"New Partner"))); - .. code-block:: java + .. code-tab:: java - final Integer id = (Integer)models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "create", - asList(new HashMap() {{ put("name", "New Partner"); }}) - )); + final Integer id = (Integer)models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "create", + asList(new HashMap() {{ put("name", "New Partner"); }}) + )); - .. code-block:: json + Result: - 78 + .. code-block:: json -.. warning:: + 78 - while most value types are what would be expected (integer for - :class:`~odoo.fields.Integer`, string for :class:`~odoo.fields.Char` - or :class:`~odoo.fields.Text`), +.. warning:: + While most value types are what would expect (integer for + :class:`~odoo.fields.Integer`, string for :class:`~odoo.fields.Char` + or :class:`~odoo.fields.Text`), - * :class:`~odoo.fields.Date`, :class:`~odoo.fields.Datetime` and - :class:`~odoo.fields.Binary` fields use string values - * :class:`~odoo.fields.One2many` and :class:`~odoo.fields.Many2many` - use a special command protocol detailed in :meth:`the documentation to - the write method `. + - :class:`~odoo.fields.Date`, :class:`~odoo.fields.Datetime` and + :class:`~odoo.fields.Binary` fields use string values + - :class:`~odoo.fields.One2many` and :class:`~odoo.fields.Many2many` + use a special command protocol detailed in :meth:`the documentation to + the write method `. Update records -------------- -Records can be updated using :meth:`~odoo.models.Model.write`, it takes +Records can be updated using :meth:`~odoo.models.Model.write`. It takes a list of records to update and a mapping of updated fields to values similar to :meth:`~odoo.models.Model.create`. Multiple records can be updated simultaneously, but they will all get the same -values for the fields being set. It is not currently possible to perform +values for the fields being set. It is not possible to perform "computed" updates (where the value being set depends on an existing value of a record). -.. container:: doc-aside +.. example:: + + .. tabs:: - .. switcher:: + .. code-tab:: python - .. code-block:: python3 + models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {'name': "Newer partner"}]) + # get record name after having changed it + models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) - models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], { - 'name': "Newer partner" - }]) - # get record name after having changed it - models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) + .. code-tab:: ruby - .. code-block:: ruby + models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], {name: "Newer partner"}]) + # get record name after having changed it + models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) - models.execute_kw(db, uid, password, 'res.partner', 'write', [[id], { - name: "Newer partner" - }]) - # get record name after having changed it - models.execute_kw(db, uid, password, 'res.partner', 'name_get', [[id]]) + .. code-tab:: php - .. code-block:: php + $models->execute_kw($db, $uid, $password, 'res.partner', 'write', array(array($id), array('name'=>"Newer partner"))); + // get record name after having changed it + $models->execute_kw($db, $uid, $password, + 'res.partner', 'name_get', array(array($id))); - $models->execute_kw($db, $uid, $password, 'res.partner', 'write', - array(array($id), array('name'=>"Newer partner"))); - // get record name after having changed it - $models->execute_kw($db, $uid, $password, - 'res.partner', 'name_get', array(array($id))); + .. code-tab:: java - .. code-block:: java + models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "write", + asList( + asList(id), + new HashMap() {{ put("name", "Newer Partner"); }} + ) + )); + // get record name after having changed it + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "name_get", + asList(asList(id)) + ))); - models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "write", - asList( - asList(id), - new HashMap() {{ put("name", "Newer Partner"); }} - ) - )); - // get record name after having changed it - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "name_get", - asList(asList(id)) - ))); + Result: - .. code-block:: json + .. code-block:: json - [[78, "Newer partner"]] + [[78, "Newer partner"]] Delete records -------------- @@ -945,59 +794,52 @@ Delete records Records can be deleted in bulk by providing their ids to :meth:`~odoo.models.Model.unlink`. -.. container:: doc-aside +.. example:: - .. switcher:: + .. tabs:: - .. code-block:: python3 + .. code-tab:: python - models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) - # check if the deleted record is still in the database - models.execute_kw(db, uid, password, - 'res.partner', 'search', [[['id', '=', id]]]) + models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) + # check if the deleted record is still in the database + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['id', '=', id]]]) - .. code-block:: ruby + .. code-tab:: ruby - models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) - # check if the deleted record is still in the database - models.execute_kw(db, uid, password, - 'res.partner', 'search', [[['id', '=', id]]]) + models.execute_kw(db, uid, password, 'res.partner', 'unlink', [[id]]) + # check if the deleted record is still in the database + models.execute_kw(db, uid, password, 'res.partner', 'search', [[['id', '=', id]]]) - .. code-block:: php + .. code-tab:: php - $models->execute_kw($db, $uid, $password, - 'res.partner', 'unlink', - array(array($id))); - // check if the deleted record is still in the database - $models->execute_kw($db, $uid, $password, - 'res.partner', 'search', - array(array(array('id', '=', $id)))); + $models->execute_kw($db, $uid, $password, 'res.partner', 'unlink', array(array($id))); + // check if the deleted record is still in the database + $models->execute_kw( + $db, $uid, $password, 'res.partner', 'search', array(array(array('id', '=', $id))) + ); - .. code-block:: java + .. code-tab:: java - models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "unlink", - asList(asList(id)))); - // check if the deleted record is still in the database - asList((Object[])models.execute("execute_kw", asList( - db, uid, password, - "res.partner", "search", - asList(asList(asList("id", "=", 78))) - ))); + models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "unlink", + asList(asList(id)))); + // check if the deleted record is still in the database + asList((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search", + asList(asList(asList("id", "=", 78))) + ))); - .. code-block:: json + Result: - [] + .. code-block:: json + + [] Inspection and introspection ---------------------------- -.. todo:: ``get_external_id`` is kinda crap and may not return an id: it just - gets a random existing xid but won't generate one if there is no - xid currently associated with the record. And operating with xids - isn't exactly fun in RPC. - While we previously used :meth:`~odoo.models.Model.fields_get` to query a model and have been using an arbitrary model from the start, Odoo stores most model metadata inside a few meta-models which allow both querying the @@ -1007,9 +849,9 @@ XML-RPC. .. _reference/webservice/inspection/models: ``ir.model`` -'''''''''''' +~~~~~~~~~~~~ -Provides information about Odoo models via its various fields +Provides information about Odoo models via its various fields. ``name`` a human-readable description of the model @@ -1030,132 +872,119 @@ Provides information about Odoo models via its various fields ``ir.model`` can be used to -* query the system for installed models (as a precondition to operations - on the model or to explore the system's content) -* get information about a specific model (generally by listing the fields - associated with it) -* create new models dynamically over RPC - -.. warning:: - - * "custom" model names must start with ``x_`` - * the ``state`` must be provided and ``manual``, otherwise the model will - not be loaded - * it is not possible to add new *methods* to a custom model, only fields - -.. container:: doc-aside - - a custom model will initially contain only the "built-in" fields available - on all models: - - .. switcher:: - - .. code-block:: python3 - - models.execute_kw(db, uid, password, 'ir.model', 'create', [{ - 'name': "Custom Model", - 'model': "x_custom_model", - 'state': 'manual', - }]) - models.execute_kw( - db, uid, password, 'x_custom_model', 'fields_get', - [], {'attributes': ['string', 'help', 'type']}) - - .. code-block:: php - - $models->execute_kw( - $db, $uid, $password, - 'ir.model', 'create', array(array( - 'name' => "Custom Model", - 'model' => 'x_custom_model', - 'state' => 'manual' - )) - ); - $models->execute_kw( - $db, $uid, $password, - 'x_custom_model', 'fields_get', - array(), - array('attributes' => array('string', 'help', 'type')) - ); - - .. code-block:: ruby - - models.execute_kw( - db, uid, password, - 'ir.model', 'create', [{ - name: "Custom Model", - model: 'x_custom_model', - state: 'manual' - }]) - fields = models.execute_kw( - db, uid, password, 'x_custom_model', 'fields_get', - [], {attributes: %w(string help type)}) - - .. code-block:: java - - models.execute( - "execute_kw", asList( - db, uid, password, - "ir.model", "create", - asList(new HashMap() {{ - put("name", "Custom Model"); - put("model", "x_custom_model"); - put("state", "manual"); - }}) - )); - final Object fields = models.execute( - "execute_kw", asList( - db, uid, password, - "x_custom_model", "fields_get", - emptyList(), - new HashMap () {{ - put("attributes", asList( - "string", - "help", - "type")); - }} - )); - - .. code-block:: json - - { - "create_uid": { - "type": "many2one", - "string": "Created by" - }, - "create_date": { - "type": "datetime", - "string": "Created on" - }, - "__last_update": { - "type": "datetime", - "string": "Last Modified on" - }, - "write_uid": { - "type": "many2one", - "string": "Last Updated by" - }, - "write_date": { - "type": "datetime", - "string": "Last Updated on" - }, - "display_name": { - "type": "char", - "string": "Display Name" - }, - "id": { - "type": "integer", - "string": "Id" - } - } +- Query the system for installed models (as a precondition to operations + on the model or to explore the system's content). +- Get information about a specific model (generally by listing the fields + associated with it). +- Create new models dynamically over RPC. + +.. important:: + * Custom model names must start with ``x_``. + * The ``state`` must be provided and set to ``manual``, otherwise the model will + not be loaded. + * It is not possible to add new *methods* to a custom model, only fields. + +.. example:: + + A custom model will initially contain only the "built-in" fields available + on all models: + + .. tabs:: + + .. code-tab:: python + + models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + 'name': "Custom Model", + 'model': "x_custom_model", + 'state': 'manual', + }]) + models.execute_kw(db, uid, password, 'x_custom_model', 'fields_get', [], {'attributes': ['string', 'help', 'type']}) + + .. code-tab:: php + + $models->execute_kw($db, $uid, $password, 'ir.model', 'create', array(array( + 'name' => "Custom Model", + 'model' => 'x_custom_model', + 'state' => 'manual' + ))); + $models->execute_kw($db, $uid, $password, 'x_custom_model', 'fields_get', array(), array('attributes' => array('string', 'help', 'type'))); + + .. code-tab:: ruby + + models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + name: "Custom Model", + model: 'x_custom_model', + state: 'manual' + }]) + fields = models.execute_kw(db, uid, password, 'x_custom_model', 'fields_get', [], {attributes: %w(string help type)}) + + .. code-tab:: java + + models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model", "create", + asList(new HashMap() {{ + put("name", "Custom Model"); + put("model", "x_custom_model"); + put("state", "manual"); + }}) + )); + final Object fields = models.execute( + "execute_kw", asList( + db, uid, password, + "x_custom_model", "fields_get", + emptyList(), + new HashMap () {{ + put("attributes", asList( + "string", + "help", + "type")); + }} + )); + + Result: + + .. code-block:: json + + { + "create_uid": { + "type": "many2one", + "string": "Created by" + }, + "create_date": { + "type": "datetime", + "string": "Created on" + }, + "__last_update": { + "type": "datetime", + "string": "Last Modified on" + }, + "write_uid": { + "type": "many2one", + "string": "Last Updated by" + }, + "write_date": { + "type": "datetime", + "string": "Last Updated on" + }, + "display_name": { + "type": "char", + "string": "Display Name" + }, + "id": { + "type": "integer", + "string": "Id" + } + } .. _reference/webservice/inspection/fields: ``ir.model.fields`` -''''''''''''''''''' +~~~~~~~~~~~~~~~~~~~ Provides information about the fields of Odoo models and allows adding -custom fields without using Python code +custom fields without using Python code. ``model_id`` :class:`~odoo.fields.Many2one` to @@ -1178,155 +1007,123 @@ custom fields without using Python code type-specific properties and customizations, see :ref:`the fields documentation ` for details -Like custom models, only new fields created with ``state="manual"`` are -activated as actual fields on the model. - -.. warning:: computed fields can not be added via ``ir.model.fields``, some - field meta-information (defaults, onchange) can not be set either - -.. todo:: maybe new-API fields could store constant ``default`` in a new - column, maybe JSON-encoded? - -.. container:: doc-aside - - .. switcher:: - - .. code-block:: python3 - - id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{ - 'name': "Custom Model", - 'model': "x_custom", - 'state': 'manual', - }]) - models.execute_kw( - db, uid, password, - 'ir.model.fields', 'create', [{ - 'model_id': id, - 'name': 'x_name', - 'ttype': 'char', - 'state': 'manual', - 'required': True, - }]) - record_id = models.execute_kw( - db, uid, password, - 'x_custom', 'create', [{ - 'x_name': "test record", - }]) - models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]]) - - .. code-block:: php - - $id = $models->execute_kw( - $db, $uid, $password, - 'ir.model', 'create', array(array( - 'name' => "Custom Model", - 'model' => 'x_custom', - 'state' => 'manual' - )) - ); - $models->execute_kw( - $db, $uid, $password, - 'ir.model.fields', 'create', array(array( - 'model_id' => $id, - 'name' => 'x_name', - 'ttype' => 'char', - 'state' => 'manual', - 'required' => true - )) - ); - $record_id = $models->execute_kw( - $db, $uid, $password, - 'x_custom', 'create', array(array( - 'x_name' => "test record" - )) - ); - $models->execute_kw( - $db, $uid, $password, - 'x_custom', 'read', - array(array($record_id))); - - .. code-block:: ruby - - id = models.execute_kw( - db, uid, password, - 'ir.model', 'create', [{ - name: "Custom Model", - model: "x_custom", - state: 'manual' - }]) - models.execute_kw( - db, uid, password, - 'ir.model.fields', 'create', [{ - model_id: id, - name: "x_name", - ttype: "char", - state: "manual", - required: true - }]) - record_id = models.execute_kw( - db, uid, password, - 'x_custom', 'create', [{ - x_name: "test record" - }]) - models.execute_kw( - db, uid, password, - 'x_custom', 'read', [[record_id]]) - - .. code-block:: java - - final Integer id = (Integer)models.execute( - "execute_kw", asList( - db, uid, password, - "ir.model", "create", - asList(new HashMap() {{ - put("name", "Custom Model"); - put("model", "x_custom"); - put("state", "manual"); - }}) - )); - models.execute( - "execute_kw", asList( - db, uid, password, - "ir.model.fields", "create", - asList(new HashMap() {{ - put("model_id", id); - put("name", "x_name"); - put("ttype", "char"); - put("state", "manual"); - put("required", true); - }}) - )); - final Integer record_id = (Integer)models.execute( - "execute_kw", asList( - db, uid, password, - "x_custom", "create", - asList(new HashMap() {{ - put("x_name", "test record"); - }}) - )); - - client.execute( - "execute_kw", asList( - db, uid, password, - "x_custom", "read", - asList(asList(record_id)) - )); - - .. code-block:: json - - [ - { - "create_uid": [1, "Administrator"], - "x_name": "test record", - "__last_update": "2014-11-12 16:32:13", - "write_uid": [1, "Administrator"], - "write_date": "2014-11-12 16:32:13", - "create_date": "2014-11-12 16:32:13", - "id": 1, - "display_name": "test record" - } - ] - +.. important:: + - Like custom models, only new fields created with ``state="manual"`` are activated as actual + fields on the model. + - Computed fields can not be added via ``ir.model.fields``, some field meta-information + (defaults, onchange) can not be set either. + +.. example:: + + .. tabs:: + + .. code-tab:: python + + id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + 'name': "Custom Model", + 'model': "x_custom", + 'state': 'manual', + }]) + models.execute_kw(db, uid, password, 'ir.model.fields', 'create', [{ + 'model_id': id, + 'name': 'x_name', + 'ttype': 'char', + 'state': 'manual', + 'required': True, + }]) + record_id = models.execute_kw(db, uid, password, 'x_custom', 'create', [{'x_name': "test record"}]) + models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]]) + + .. code-tab:: php + + $id = $models->execute_kw($db, $uid, $password, 'ir.model', 'create', array(array( + 'name' => "Custom Model", + 'model' => 'x_custom', + 'state' => 'manual' + ))); + $models->execute_kw($db, $uid, $password, 'ir.model.fields', 'create', array(array( + 'model_id' => $id, + 'name' => 'x_name', + 'ttype' => 'char', + 'state' => 'manual', + 'required' => true + ))); + $record_id = $models->execute_kw($db, $uid, $password, 'x_custom', 'create', array(array('x_name' => "test record"))); + $models->execute_kw($db, $uid, $password, 'x_custom', 'read', array(array($record_id))); + + .. code-tab:: ruby + + id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + name: "Custom Model", + model: "x_custom", + state: 'manual' + }]) + models.execute_kw(db, uid, password, 'ir.model.fields', 'create', [{ + model_id: id, + name: "x_name", + ttype: "char", + state: "manual", + required: true + }]) + record_id = models.execute_kw(db, uid, password, 'x_custom', 'create', [{x_name: "test record"}]) + models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]]) + + .. code-tab:: java + + final Integer id = (Integer)models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model", "create", + asList(new HashMap() {{ + put("name", "Custom Model"); + put("model", "x_custom"); + put("state", "manual"); + }}) + )); + models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model.fields", "create", + asList(new HashMap() {{ + put("model_id", id); + put("name", "x_name"); + put("ttype", "char"); + put("state", "manual"); + put("required", true); + }}) + )); + final Integer record_id = (Integer)models.execute( + "execute_kw", asList( + db, uid, password, + "x_custom", "create", + asList(new HashMap() {{ + put("x_name", "test record"); + }}) + )); + + client.execute( + "execute_kw", asList( + db, uid, password, + "x_custom", "read", + asList(asList(record_id)) + )); + + Result: + + .. code-block:: json + + [ + { + "create_uid": [1, "Administrator"], + "x_name": "test record", + "__last_update": "2014-11-12 16:32:13", + "write_uid": [1, "Administrator"], + "write_date": "2014-11-12 16:32:13", + "create_date": "2014-11-12 16:32:13", + "id": 1, + "display_name": "test record" + } + ] .. _PostgreSQL: https://www.postgresql.org .. _XML-RPC: https://en.wikipedia.org/wiki/XML-RPC diff --git a/content/developer/reference/addons/security.rst b/content/developer/reference/addons/security.rst index 38d634bcef..30c2ad1c5f 100644 --- a/content/developer/reference/addons/security.rst +++ b/content/developer/reference/addons/security.rst @@ -203,7 +203,7 @@ Unsafe Public Methods --------------------- Any public method can be executed via a :ref:`RPC call -` with the chosen parameters. The methods +` with the chosen parameters. The methods starting with a ``_`` are not callable from an action button or external API. On public methods, the record on which a method is executed and the parameters diff --git a/extensions/odoo_theme/static/style.scss b/extensions/odoo_theme/static/style.scss index 04620701cd..ad804131ea 100644 --- a/extensions/odoo_theme/static/style.scss +++ b/extensions/odoo_theme/static/style.scss @@ -512,25 +512,6 @@ header.o_main_header { max-width: 100%; } - .content-switcher { - .nav-tabs .nav-link.active { - background-color: #f8f8f8; - border-color: #dee2e6 #dee2e6 #f8f8f8; - font-weight: $fw_semibold; - } - - .tab-content { - background: $doc_code-bg; - border: 1px $gray-300 solid; - border-top: 0; - - div[class^="highlight"] { - border: 0; - margin: 0; - } - } - } - @include media-breakpoint-up(xl) { width: 100%; padding-right: 0; diff --git a/extensions/switcher/__init__.py b/extensions/switcher/__init__.py deleted file mode 100644 index d713167868..0000000000 --- a/extensions/switcher/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -import os.path - -from docutils import nodes -from docutils.parsers.rst import Directive - -from pygments.lexers import get_lexer_by_name - - -def setup(app): - app.add_directive('switcher', SwitcherDirective) - app.add_directive('case', CaseDirective) - - app.connect('env-updated', add_statics) - - return { - 'parallel_read_safe': True, - 'parallel_write_safe': True - } - -def add_statics(app, env): - app.add_js_file('js/switcher.js') - env.config.html_static_path.append(statics()) - - -statics = lambda *p: os.path.join( - os.path.abspath(os.path.dirname(__file__)), - 'static', *p) - - -class SwitcherDirective(Directive): - has_content = True - - def run(self): - self.assert_has_content() - - body = nodes.compound('\n'.join(self.content), classes=['tab-content']) - self.state.nested_parse(self.content, self.content_offset, body) - - titles = [] - for child in body.children: - if isinstance(child, nodes.literal_block): - titles.append(get_lexer_by_name(child['language']).name) - else: - assert child['names'], ( - "A switcher case must be either a code block or a compound with a name" - ) - titles.append(' '.join(child['names'])) - tabs = nodes.bullet_list('', *[ - nodes.list_item('', nodes.Text(title), classes=['nav-link']) - for title in titles - ], classes=['nav nav-tabs'] ) - node = nodes.compound('', tabs, body, classes=['content-switcher']) - return [node] - - -class CaseDirective(Directive): - required_arguments = 1 - final_argument_whitespace = True - has_content = True - - def run(self): - self.assert_has_content() - node = nodes.compound('\n'.join(self.content), names=[self.arguments[0]]) - self.state.nested_parse(self.content, self.content_offset, node) - return [node] diff --git a/extensions/switcher/static/js/switcher.js b/extensions/switcher/static/js/switcher.js deleted file mode 100644 index 41797af516..0000000000 --- a/extensions/switcher/static/js/switcher.js +++ /dev/null @@ -1,36 +0,0 @@ -(function ($) { - - // TODO EDI custom css for content-switcher logic - // can be placed as css inside the extension ideally - document.addEventListener('DOMContentLoaded', function () { - document.querySelectorAll('.content-switcher').forEach(switcher => { - const links = switcher.querySelectorAll('ul li'); - const linksArray = Array.from(links); - const tabs = switcher.querySelectorAll('.tab-content > div'); - - tabs.forEach(tab => { - tab.classList.add('tab-pane'); - }); - - function select(index) { - links.forEach(link => { - link.classList.remove('active'); - }); - tabs.forEach(tab => { - tab.classList.remove('active'); - }); - links[index].classList.add('active'); - tabs[index].classList.add('active'); - } - - select(0); - links.forEach(link => { - link.addEventListener('click', ev => { - // const clickedLink = ev.target.closest('ul li'); - select(linksArray.indexOf(link)); - }); - }); - }); - }); - -})();