diff --git a/usability_api_connector/README.rst b/usability_api_connector/README.rst new file mode 100644 index 00000000..b29fb579 --- /dev/null +++ b/usability_api_connector/README.rst @@ -0,0 +1,228 @@ +========================= +Usability - API Connector +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3b395dc9128b2117c3f81eee9443ce16ff816c3c80fc798f35ac9779a9635af2 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-ecosoft--odoo%2Fecosoft--addons-lightgray.png?logo=github + :target: https://github.com/ecosoft-odoo/ecosoft-addons/tree/18.0/usability_api_connector + :alt: ecosoft-odoo/ecosoft-addons + +|badge1| |badge2| |badge3| + +This module is base webhooks standard and keep all log that interface + +Step to see logs: + +1. Go to Settings > Technical > API Configuration > API Logs +2. this table will keep all log that interface '/api/create_data' or '/api/create_update_data' +3. Users can used this table for test API by click 'Update API' + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Before sending a REST API request to Odoo, an initial call to authenticate the API is necessary. +You can achieve this by calling the ``/web/session/authenticate`` route. + +The authentication format requires a header with ``Content-type`` set to ``application/json``, +and the body should include: + +.. code-block:: python + + { + "jsonrpc": "2.0", + "method": "call", + "params": { + "db": "", + "login": "", + "password": "" + } + } + +Following successful authentication, you can proceed with five API routes: + +1. ``/api/create_data``: This route allows the creation of new data only. + The format for creating data should be in the following structure: + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "payload": { + "field1": "value1", + ... + }, + "result_field": ["field1", ...] # optional + } + } + } + +2. ``/api/create_update_data``: This route facilitates updating data. + If the data does not exist, it will automatically create it. + The format follows that of ``create_data``, but it requires a unique key in the field to update the values. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "field1": "value1", + ... + }, + "result_field": ["field1", ...] # optional + } + } + } + +3. ``/api/update_data``: This route allows updating existing data, + using a unique key in the field to find the desired data and update values in that recordset. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "field1": "value1", + ... + } + } + } + } + +4. ``/api/search_data``: This route allows you to search for the value of a desired field in a model + by using a search domain to find the desired recordset. You can also limit and order the resulting data. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "payload": { + "search_field": ["field1", "field2", "field3{subfield1, subfield2}", ...], + "search_domain": "[('field', 'operator', 'value')]", + "limit": 1, + "order": "field1 , field2 desc, ..." + } + } + } + } + +5. ``/api/call_function``: This route allows you to call a function on a model object based on the provided input. + + **Parameters**: + - **name** (*str*): The name of the model to perform the function on. + - **method** (*str*): The name of the function to call. + - **parameter** (*dict*): A dictionary containing the arguments to pass to the function (if any). + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "method": "", + "parameter": {"": "", ...} + } + } + } + } + +**Note**: +If you want to attach a file to a record, you can add the key "attachment_ids" at any level of the payload. + + **Example Request with Attachment**: + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "attachment_ids": [ + { + "name": "", + "datas": "" + } + ], + ... + } + } + } + } + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Kitti Upariphutthiphong +* Saran Lim. + +Maintainers +~~~~~~~~~~~ + +.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px + :target: https://github.com/Saran440 + :alt: Saran440 + +Current maintainer: + +|maintainer-Saran440| + +This module is part of the `ecosoft-odoo/ecosoft-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/usability_api_connector/__init__.py b/usability_api_connector/__init__.py new file mode 100644 index 00000000..69f7babd --- /dev/null +++ b/usability_api_connector/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/usability_api_connector/__manifest__.py b/usability_api_connector/__manifest__.py new file mode 100644 index 00000000..ee786f4d --- /dev/null +++ b/usability_api_connector/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2022 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Usability - API Connector", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "category": "Tools", + "author": "Ecosoft, Odoo Community Association (OCA)", + "website": "https://github.com/ecosoft-odoo/ecosoft-addons", + "depends": ["base", "mail", "account"], + "data": [ + "security/ir.model.access.csv", + "views/api_config_views.xml", + ], + "maintainers": ["Saran440"], +} diff --git a/usability_api_connector/models/__init__.py b/usability_api_connector/models/__init__.py new file mode 100644 index 00000000..b12be22c --- /dev/null +++ b/usability_api_connector/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import common_base_api +from . import api_config diff --git a/usability_api_connector/models/api_config.py b/usability_api_connector/models/api_config.py new file mode 100644 index 00000000..caa2c190 --- /dev/null +++ b/usability_api_connector/models/api_config.py @@ -0,0 +1,67 @@ +# Copyright 2025 Ecosoft Co., Ltd. (https://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ApiConfig(models.Model): + _name = "api.config" + _inherit = "common.base.api" # For test api + _description = "API Configuration" + + name = fields.Char(required=True, translate=True) + code = fields.Char() + api_type = fields.Selection( + selection=[ + ("xmlrpc", "XML-RPC"), + ("rest_api", "Rest API"), + ], + string="API Type", + required=True, + ) + description = fields.Char() + model_id = fields.Many2one( + comodel_name="ir.model", + ondelete="cascade", + ) + endpoint_system = fields.Selection( + selection=[("odoo", "Odoo")], + ) + endpoint_url = fields.Char(string="Endpoint URL") + callback_url = fields.Char(string="Callback URL") + disable_ssl = fields.Boolean( + string="Disable SSL Verification", + help="Disable SSL verification for API calls", + ) + route_path = fields.Char() + method = fields.Selection( + selection=[ + ("get", "GET"), + ("post", "POST"), + ("put", "PUT"), + ("delete", "DELETE"), + ] + ) + active = fields.Boolean(default=True) + auth_required = fields.Boolean() + auth_username = fields.Char(string="Username") + auth_password = fields.Char(string="Password") + auth_db = fields.Char(string="Database") + auth_token = fields.Char() + + # XML-RPC + execute_model = fields.Char() + execute_method = fields.Char() + execute_context = fields.Char(default='{"context": {}}') + + # Rest API + headers = fields.Text() + + # Python code + python_code = fields.Text( + help="Write Python code that the action will execute. Some variables are " + "available for use; help about python expression is given in the help tab." + ) + + def action_test_call_api(self): + return self.action_call_api(self.code) diff --git a/usability_api_connector/models/common_base_api.py b/usability_api_connector/models/common_base_api.py new file mode 100644 index 00000000..95c50f5a --- /dev/null +++ b/usability_api_connector/models/common_base_api.py @@ -0,0 +1,321 @@ +# Copyright 2025 Ecosoft Co., Ltd. (https://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import json +import logging +import ssl +import xmlrpc.client + +import requests + +from odoo import fields, models +from odoo.exceptions import ValidationError +from odoo.tools.safe_eval import safe_eval + +_logger = logging.getLogger(__name__) + + +class CommonBaseApi(models.AbstractModel): + _name = "common.base.api" + _description = "Common Base API" + + api_status = fields.Selection( + selection=[ + ("draft", "Draft"), + ("failed", "Failed"), + ("success", "Success"), + ], + default="draft", + string="API Status", + copy=False, + ) + api_result = fields.Text(string="API Result", copy=False) + callback_status = fields.Selection( + [("none", "None"), ("success", "Success"), ("failed", "Failed")], + default="none", + copy=False, + ) + + def _notify_user(self, ttype, message): + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "type": ttype, + "message": message, + # NOTE: If it call from wizard + "next": {"type": "ir.actions.act_window_close"}, + }, + } + + def _write_failed_state(self, result): + """Mark as failed and store message safely""" + self.write( + { + "api_status": "failed", + "api_result": result, + } + ) + return False + + def _truncate_text(self, text, limit=1000): + """Helper: truncate text for safe storage""" + if not text: + return text + text = str(text) + return text[:limit] + + def _get_header_globals_dict(self, auth_token): + return {"auth_token": auth_token} + + def _get_payload_globals_dict(self): + return {"rec": self} + + def get_login(self, api_data, ssl_context): + """ + Handle authentication for REST and XMLRPC + - XMLRPC will return token is `uid` + - REST will return token is `session` + """ + api_data.ensure_one() + + if api_data.api_type == "rest_api": + payload = { + "jsonrpc": "2.0", + "method": "call", + "params": { + "db": api_data.auth_db, + "login": api_data.auth_username, + "password": api_data.auth_password, + }, + } + response = requests.post( + f"{api_data.endpoint_url}/web/session/authenticate", + json=payload, + timeout=10, + verify=not api_data.disable_ssl, + ) + response.raise_for_status() + token = response.cookies.get("session_id") + if not token: + raise ValidationError( + self.env._( + "Authentication failed: " + "Invalid database, username or password." + ) + ) + + elif api_data.api_type == "xmlrpc": + common = xmlrpc.client.ServerProxy( + f"{api_data.endpoint_url}/xmlrpc/2/common", context=ssl_context + ) + token = common.authenticate( + api_data.auth_db, api_data.auth_username, api_data.auth_password, {} + ) + # Authentication failed. + if not token: + raise ValidationError( + self.env._( + "Authentication failed: " + "Invalid database, username or password." + ) + ) + + else: + raise ValidationError( + self.env._("Unsupported API type: %s") % api_data.api_type + ) + # Update token + api_data.write({"auth_token": token}) + return token + + # ---------------------------------------------------------- + # XML-RPC + # ---------------------------------------------------------- + def _execute_xmlrpc_api(self, models, api_data, auth_token, payload=None): + """auth_token for xmlrpc is uid, It must be interger only""" + payload = payload or {} + try: + json_context = json.loads(api_data.execute_context or "{}") + except Exception as e: + raise ValidationError( + self.env._(f"execute_context is not valid JSON: {e}") + ) from e + + result = models.execute_kw( + api_data.auth_db, + int(auth_token), + api_data.auth_password, + api_data.execute_model, + api_data.execute_method, + [payload], + json_context, + ) + return result + + # ---------------------------------------------------------- + # REST API + # ---------------------------------------------------------- + def _execute_rest_api(self, api_data, auth_token, payload=None): + payload = payload or {} + headers = safe_eval( + api_data.headers or "{}", + globals_dict=self._get_header_globals_dict(auth_token), + ) + try: + result = requests.request( + api_data.method, + f"{api_data.endpoint_url}{api_data.route_path}", + json=payload, + headers=headers, + timeout=30, + ) + result.raise_for_status() + return result + except (requests.Timeout, requests.ConnectionError) as e: + raise ValidationError(self.env._("Connection error: %s") % e) from e + except requests.HTTPError as e: + raise ValidationError(self.env._("HTTP error: %s") % e) from e + + def _connect_odoo(self, api_data, auth_token, payload, ssl_context): + if api_data.api_type == "xmlrpc": + route = api_data.route_path or "/xmlrpc/2/object" + models = xmlrpc.client.ServerProxy( + f"{api_data.endpoint_url}{route}", context=ssl_context + ) + try: + result = self._execute_xmlrpc_api(models, api_data, auth_token, payload) + except Exception as e: + if "Access Denied" in str(e): + auth_token = self.get_login(api_data, ssl_context) + result = self._execute_xmlrpc_api( + models, api_data, auth_token, payload + ) + else: + _logger.exception("_connect_odoo with XML-RPC Error") + self._write_failed_state(str(e)) + return {"is_success": False, "message": str(e)} + + # Rest API + else: + try: + result = self._execute_rest_api(api_data, auth_token, payload) + data = result.json() + except Exception as e: + _logger.exception("_connect_odoo with Rest API Error") + self._write_failed_state(str(e)) + return {"is_success": False, "message": str(e)} + + # Retry on session expired + if ( + isinstance(data, dict) + and data.get("error", {}).get("message") == "Odoo Session Expired" + ): + auth_token = self.get_login(api_data, ssl_context) + response = self._execute_rest_api(api_data, auth_token, payload) + data = response.json() + result = data + return result + + def _connect_system(self, api_data, auth_token, payload, ssl_context): + """NOTE: Implement other system""" + return {"is_success": False, "message": "Not Implement yet."} + + def _execute_api_request(self, api_data, auth_token, payload, ssl_context): + if api_data.endpoint_system == "odoo": + return self._connect_odoo(api_data, auth_token, payload, ssl_context) + return self._connect_system(api_data, auth_token, payload, ssl_context) + + def _get_data_payload_callback(self, result): + """NOTE: Implement data dict callback here""" + return {"status": "success", "result": result} + + def _handle_callback(self, api_data, result): + """Send callback to external system if configured.""" + if not api_data.callback_url: + return + + try: + payload = self._get_data_payload_callback(result) + requests.post(api_data.callback_url, json=payload, timeout=10) + self.write({"callback_status": "success"}) + except Exception: + _logger.exception("Callback URL failed") + self.write({"callback_status": "failed"}) + + def _hook_update_data(self, code_api, result): + """ + This method is helper to update data + from result return from other system + """ + return + + def _action_update_result(self, api_data, result): + """ + This method for check result, each system will return result is not same. + So, this method can hook to check result message and do something. + + Returns: + tuple(bool, str): + - success flag (True/False) + - error message (non-empty if failed) + """ + # Error + if not result.get("is_success", False): + return 0, result.get("messages", "") + return 1, "" + + def action_call_api(self, code_api): + try: + api_data = self.env["api.config"].search([("code", "=", code_api)]) + if not api_data: + raise ValidationError( + self.env._("API config not found for code: %s") % code_api + ) + + auth_token = False + ssl_context = None + if api_data.disable_ssl: + ssl_context = ssl._create_unverified_context() + + if api_data.auth_required: + auth_token = api_data.auth_token or self.get_login( + api_data, ssl_context + ) + if not auth_token: + raise ValidationError(self.env._("No valid authentication token.")) + + payload = safe_eval( + api_data.python_code or "{}", + globals_dict=self._get_payload_globals_dict(), + ) + result = self._execute_api_request( + api_data, auth_token, payload, ssl_context + ) + + # Not success + is_success, message_err = self._action_update_result(api_data, result) + if not is_success: + self._write_failed_state(message_err) + return self._notify_user("danger", message_err) + + # Success + self.write( + { + "api_status": "success", + "api_result": self._truncate_text(result), + } + ) + + # Hook method + self._hook_update_data(code_api, result) + + # Callback (if any) + self._handle_callback(api_data, result) + + return self._notify_user("success", self.env._("API call successful.")) + + except Exception as e: + _logger.exception("Call API Error") + self._write_failed_state(str(e)) + return self._notify_user("danger", str(e)) diff --git a/usability_api_connector/pyproject.toml b/usability_api_connector/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/usability_api_connector/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/usability_api_connector/readme/CONTRIBUTORS.rst b/usability_api_connector/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..9cf80039 --- /dev/null +++ b/usability_api_connector/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Kitti Upariphutthiphong +* Saran Lim. diff --git a/usability_api_connector/readme/DESCRIPTION.rst b/usability_api_connector/readme/DESCRIPTION.rst new file mode 100644 index 00000000..a7e47a77 --- /dev/null +++ b/usability_api_connector/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module is base webhooks standard and keep all log that interface + +Step to see logs: + +1. Go to Settings > Technical > API Configuration > API Logs +2. this table will keep all log that interface '/api/create_data' or '/api/create_update_data' +3. Users can used this table for test API by click 'Update API' diff --git a/usability_api_connector/readme/USAGE.rst b/usability_api_connector/readme/USAGE.rst new file mode 100644 index 00000000..a5bebd0e --- /dev/null +++ b/usability_api_connector/readme/USAGE.rst @@ -0,0 +1,149 @@ +Before sending a REST API request to Odoo, an initial call to authenticate the API is necessary. +You can achieve this by calling the ``/web/session/authenticate`` route. + +The authentication format requires a header with ``Content-type`` set to ``application/json``, +and the body should include: + +.. code-block:: python + + { + "jsonrpc": "2.0", + "method": "call", + "params": { + "db": "", + "login": "", + "password": "" + } + } + +Following successful authentication, you can proceed with five API routes: + +1. ``/api/create_data``: This route allows the creation of new data only. + The format for creating data should be in the following structure: + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "payload": { + "field1": "value1", + ... + }, + "result_field": ["field1", ...] # optional + } + } + } + +2. ``/api/create_update_data``: This route facilitates updating data. + If the data does not exist, it will automatically create it. + The format follows that of ``create_data``, but it requires a unique key in the field to update the values. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "field1": "value1", + ... + }, + "result_field": ["field1", ...] # optional + } + } + } + +3. ``/api/update_data``: This route allows updating existing data, + using a unique key in the field to find the desired data and update values in that recordset. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "field1": "value1", + ... + } + } + } + } + +4. ``/api/search_data``: This route allows you to search for the value of a desired field in a model + by using a search domain to find the desired recordset. You can also limit and order the resulting data. + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "payload": { + "search_field": ["field1", "field2", "field3{subfield1, subfield2}", ...], + "search_domain": "[('field', 'operator', 'value')]", + "limit": 1, + "order": "field1 , field2 desc, ..." + } + } + } + } + +5. ``/api/call_function``: This route allows you to call a function on a model object based on the provided input. + + **Parameters**: + - **name** (*str*): The name of the model to perform the function on. + - **method** (*str*): The name of the function to call. + - **parameter** (*dict*): A dictionary containing the arguments to pass to the function (if any). + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "method": "", + "parameter": {"": "", ...} + } + } + } + } + +**Note**: +If you want to attach a file to a record, you can add the key "attachment_ids" at any level of the payload. + + **Example Request with Attachment**: + + .. code-block:: python + + { + "params": { + "model": "", + "vals": { + "search_key": { + "": "value", # can be ID or name search string + }, + "payload": { + "attachment_ids": [ + { + "name": "", + "datas": "" + } + ], + ... + } + } + } + } \ No newline at end of file diff --git a/usability_api_connector/security/ir.model.access.csv b/usability_api_connector/security/ir.model.access.csv new file mode 100644 index 00000000..72dbb374 --- /dev/null +++ b/usability_api_connector/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_api_config_user,access_api_config_user,model_api_config,base.group_user,1,1,1,1 diff --git a/usability_api_connector/static/description/index.html b/usability_api_connector/static/description/index.html new file mode 100644 index 00000000..beaa52a7 --- /dev/null +++ b/usability_api_connector/static/description/index.html @@ -0,0 +1,575 @@ + + + + + +Usability - API Connector + + + +
+

Usability - API Connector

+ + +

Beta License: AGPL-3 ecosoft-odoo/ecosoft-addons

+

This module is base webhooks standard and keep all log that interface

+

Step to see logs:

+
    +
  1. Go to Settings > Technical > API Configuration > API Logs
  2. +
  3. this table will keep all log that interface ‘/api/create_data’ or ‘/api/create_update_data’
  4. +
  5. Users can used this table for test API by click ‘Update API’
  6. +
+

Table of contents

+ +
+

Usage

+

Before sending a REST API request to Odoo, an initial call to authenticate the API is necessary. +You can achieve this by calling the /web/session/authenticate route.

+

The authentication format requires a header with Content-type set to application/json, +and the body should include:

+
+{
+   "jsonrpc": "2.0",
+   "method": "call",
+   "params": {
+      "db": "<db_name>",
+      "login": "<username>",
+      "password": "<password>"
+   }
+}
+
+

Following successful authentication, you can proceed with five API routes:

+
    +
  1. /api/create_data: This route allows the creation of new data only. +The format for creating data should be in the following structure:

    +
    +{
    +   "params": {
    +      "model": "<model name>",
    +      "vals": {
    +         "payload": {
    +            "field1": "value1",
    +            ...
    +         },
    +         "result_field": ["field1", ...]  # optional
    +      }
    +   }
    +}
    +
    +
  2. +
  3. /api/create_update_data: This route facilitates updating data. +If the data does not exist, it will automatically create it. +The format follows that of create_data, but it requires a unique key in the field to update the values.

    +
    +{
    +   "params": {
    +      "model": "<model name>",
    +      "vals": {
    +         "search_key": {
    +            "<key_field>": "value",  # can be ID or name search string
    +         },
    +         "payload": {
    +            "field1": "value1",
    +            ...
    +         },
    +         "result_field": ["field1", ...]  # optional
    +      }
    +   }
    +}
    +
    +
  4. +
  5. /api/update_data: This route allows updating existing data, +using a unique key in the field to find the desired data and update values in that recordset.

    +
    +{
    +   "params": {
    +      "model": "<model name>",
    +      "vals": {
    +         "search_key": {
    +            "<key_field>": "value",  # can be ID or name search string
    +         },
    +         "payload": {
    +            "field1": "value1",
    +            ...
    +         }
    +      }
    +   }
    +}
    +
    +
  6. +
  7. /api/search_data: This route allows you to search for the value of a desired field in a model +by using a search domain to find the desired recordset. You can also limit and order the resulting data.

    +
    +{
    +   "params": {
    +      "model": "<model name>",
    +      "vals": {
    +         "payload": {
    +            "search_field": ["field1", "field2", "field3{subfield1, subfield2}", ...],
    +            "search_domain": "[('field', 'operator', 'value')]",
    +            "limit": 1,
    +            "order": "field1 , field2 desc, ..."
    +         }
    +      }
    +   }
    +}
    +
    +
  8. +
  9. /api/call_function: This route allows you to call a function on a model object based on the provided input.

    +
    +
    Parameters:
    +
      +
    • name (str): The name of the model to perform the function on.
    • +
    • method (str): The name of the function to call.
    • +
    • parameter (dict): A dictionary containing the arguments to pass to the function (if any).
    • +
    +
    +
    +
    +{
    +   "params": {
    +      "model": "<model name>",
    +      "vals": {
    +         "search_key": {
    +            "<key_field>": "value",  # can be ID or name search string
    +         },
    +         "payload": {
    +            "method": "<method>",
    +            "parameter": {"<key>": "<value>", ...}
    +         }
    +      }
    +   }
    +}
    +
    +
  10. +
+

Note: +If you want to attach a file to a record, you can add the key “attachment_ids” at any level of the payload.

+
+

Example Request with Attachment:

+
+{
+   "params": {
+      "model": "<model name>",
+      "vals": {
+         "search_key": {
+            "<key_field>": "value",  # can be ID or name search string
+         },
+         "payload": {
+            "attachment_ids": [
+               {
+                  "name": "<file_name>",
+                  "datas": "<base64_encoded_data>"
+               }
+            ],
+            ...
+         }
+      }
+   }
+}
+
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

Current maintainer:

+

Saran440

+

This module is part of the ecosoft-odoo/ecosoft-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/usability_api_connector/views/api_config_views.xml b/usability_api_connector/views/api_config_views.xml new file mode 100644 index 00000000..a08f51f3 --- /dev/null +++ b/usability_api_connector/views/api_config_views.xml @@ -0,0 +1,141 @@ + + + + api.config.list.view + api.config + + + + + + + + + api.config.form.view + api.config + +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + API Exports + api.config + list,form + + + + +