diff --git a/setup/typesense_ir_exports/odoo/addons/typesense_ir_exports b/setup/typesense_ir_exports/odoo/addons/typesense_ir_exports new file mode 120000 index 00000000..62c2e5b0 --- /dev/null +++ b/setup/typesense_ir_exports/odoo/addons/typesense_ir_exports @@ -0,0 +1 @@ +../../../../typesense_ir_exports \ No newline at end of file diff --git a/setup/typesense_ir_exports/setup.py b/setup/typesense_ir_exports/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/typesense_ir_exports/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt index aff1b75a..3b534f66 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,3 +5,5 @@ unidecode # tests vcrpy-unittest + +odoo-addon-connector_search_engine @ git+https://github.com/OCA/search-engine.git@refs/pull/207/head#subdirectory=setup/connector_search_engine diff --git a/typesense_ir_exports/README.rst b/typesense_ir_exports/README.rst new file mode 100644 index 00000000..a27f7130 --- /dev/null +++ b/typesense_ir_exports/README.rst @@ -0,0 +1,223 @@ +============================== +Typesense Serializer Ir Export +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e34acef6723d2b511d4c8bc7121cb9d33741e025400c7f3af527c5a95a10ebaa + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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-OCA%2Fsearch--engine-lightgray.png?logo=github + :target: https://github.com/OCA/search-engine/tree/16.0/typesense_ir_exports + :alt: OCA/search-engine +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/search-engine-16-0/search-engine-16-0-typesense_ir_exports + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/search-engine&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Use Exporter (ir.exports) as serializer for connector_typesense + +Each ir.exports records is converted into JSON and JSON data get indexed +into the Search Engine. + +Data can be String, Integer, Float, Lists, and Relations in the form of +Object. + +Thnaks to the dynamic Schema configuration we can add new fields or +remove without breaking the Schema. + +Binary data like images are sent as string, but better if we use +external filestore to use images related external urls. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +You need to have typesense search engine running and successfully +connected to odoo. + +Make sure to have ``connector_search_engine`` and +``connector_typesense`` modules installed. + +SE Index Config +--------------- + +Transition to: Search Engine > Configuration > Index configurations + +Make sure to have this search index configuration in a new record: + +:: + + - Name: give it a unigue name + - Body Str: + +.. code:: json + + { + "name": "ts_products_collection", + "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": ".*", + "type": "auto", + "optional": true + } + ], + "enable_nested_fields": true + } + +SE Backend +---------- + +Transition to: Search Engine > Configuration > Backends + +Create a backend record, and create an index line with values: + +:: + + - Model: select the model you want to index + - Serializer Type: Exporter + - Exporter: select or create a new exporter template by clicking on "Open Exporter" button + - Config: select or create the config se index record mentioned above + +You can create, update and delete exporter templates through a button in +the tree view once you click on the exporter or the button under the +``exporter_id`` field in the se.index form view. + +Media +----- + +|Backend Configuration| + +|Exporter Dialog Button in Tree View| + +|Exporter Dialog| + +.. |Backend Configuration| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/backend.png +.. |Exporter Dialog Button in Tree View| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/exporter_dialog_button.png +.. |Exporter Dialog| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/exporter_dialog.png + +Usage +===== + +Simple Quick User Friendly Usage +-------------------------------- + +The key of the module is the user friendly usage of ir.exports to +serialize data and resolve it for JSON. + +Behind the scene everything is managed to jsonify: + +:: + + - images bytes into string + - images thumbnail into urls external or internal (any of them in sequence) + - many2one relation into string + - many2one inner fileds into object of key value pairs + - many2many into list of inner strings or objects + - intergers are indexed as integers + - floats are indexed as floats + - id is ESSENIALLY indexed as string and not as integer (typesene only) + +If relational field has inner relational field and that also has inner +relational field or data, no worries everything is managed smoothly. + +Selecting fields and relations and inner relations is friendly handled +by the exporter dialog. + +You can remove or add fields as you wish, as indexing is updated, and if +necessary recreated. + +(recreating a collection only if a data type changes from string into +object or vice versa, like having many2one relation and then having the +same relation with inner fields of it) + +Media +----- + +|se index form view with exporter button| + +|exporter dialog complicated tree| + +|serialized json data from exporter related record| + +.. |se index form view with exporter button| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/se_index_form_with_exporter_button.png +.. |exporter dialog complicated tree| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/dialog_tree_content.png +.. |serialized json data from exporter related record| image:: https://raw.githubusercontent.com/OCA/search-engine/16.0/typesense_ir_exports/static/img/serialized_data_from_exporter.png + +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 +------- + +* Kencove + +Contributors +------------ + +- Mohamed Alkobrosli malkobrosly@kencove.com + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-Kencove| image:: https://github.com/Kencove.png?size=40px + :target: https://github.com/Kencove + :alt: Kencove + +Current `maintainer `__: + +|maintainer-Kencove| + +This module is part of the `OCA/search-engine `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/typesense_ir_exports/__init__.py b/typesense_ir_exports/__init__.py new file mode 100644 index 00000000..738a2eec --- /dev/null +++ b/typesense_ir_exports/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import tools diff --git a/typesense_ir_exports/__manifest__.py b/typesense_ir_exports/__manifest__.py new file mode 100644 index 00000000..98918cf8 --- /dev/null +++ b/typesense_ir_exports/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright 2023 Kencove (https://kencove.com). +# @author Mohamed Alkobrosli +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Typesense Serializer Ir Export", + "summary": "Use Exporter (ir.exports) as serializer for index", + "version": "16.0.1.0.0", + "category": "Uncategorized", + "website": "https://github.com/OCA/search-engine", + "author": "Kencove, Odoo Community Association (OCA)", + "maintainers": ["Kencove"], + "license": "AGPL-3", + "development_status": "Alpha", + "depends": [ + "connector_search_engine_serializer_ir_export", + ], + "data": [ + "data/se_index_config_data.xml", + "views/se_index_view.xml", + ], + "installable": True, + "assets": { + "web.assets_backend": [ + "typesense_ir_exports/static/src/action_ir_export.xml", + "typesense_ir_exports/static/src/action_ir_export.esm.js", + ], + }, +} diff --git a/typesense_ir_exports/data/se_index_config_data.xml b/typesense_ir_exports/data/se_index_config_data.xml new file mode 100644 index 00000000..52d2ab4f --- /dev/null +++ b/typesense_ir_exports/data/se_index_config_data.xml @@ -0,0 +1,28 @@ + + + + TS Product Dynamic Fields Config + {} + +{ + "name": "ts_products_collection", + "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": ".*", + "type": "auto", + "optional": true + } + ], + "enable_nested_fields": true +} + + + diff --git a/typesense_ir_exports/models/__init__.py b/typesense_ir_exports/models/__init__.py new file mode 100644 index 00000000..9e3a4034 --- /dev/null +++ b/typesense_ir_exports/models/__init__.py @@ -0,0 +1 @@ +from . import se_index diff --git a/typesense_ir_exports/models/se_index.py b/typesense_ir_exports/models/se_index.py new file mode 100644 index 00000000..9f187623 --- /dev/null +++ b/typesense_ir_exports/models/se_index.py @@ -0,0 +1,23 @@ +# Copyright 2023 Kencove (https://kencove.com). +# @author Mohamed Alkobrosli +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + +from ..tools.resolver import IrExportsResolver +from ..tools.serializer import JsonifySerializer + + +class SeIndex(models.Model): + _inherit = "se.index" + + def _get_serializer(self): + if ( + self.serializer_type == "ir_exports" + and self.backend_id.backend_type == "typesense" + ): + parser = self.exporter_id.get_json_parser() + resolved_parser = IrExportsResolver(parser).resolved_parser + return JsonifySerializer(parser=resolved_parser, index=self) + else: + return super()._get_serializer() diff --git a/typesense_ir_exports/readme/CONFIGURE.md b/typesense_ir_exports/readme/CONFIGURE.md new file mode 100644 index 00000000..a6ff8ee2 --- /dev/null +++ b/typesense_ir_exports/readme/CONFIGURE.md @@ -0,0 +1,57 @@ +You need to have typesense search engine running and successfully connected to odoo. + +Make sure to have `connector_search_engine` and `connector_typesense` modules installed. + +## SE Index Config + +Transition to: Search Engine > Configuration > Index configurations + +Make sure to have this search index configuration in a new record: + + - Name: give it a unigue name + - Body Str: + +```json +{ + "name": "ts_products_collection", + "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": ".*", + "type": "auto", + "optional": true + } + ], + "enable_nested_fields": true +} +``` + +## SE Backend + +Transition to: Search Engine > Configuration > Backends + +Create a backend record, and create an index line with values: + + - Model: select the model you want to index + - Serializer Type: Exporter + - Exporter: select or create a new exporter template by clicking on "Open Exporter" button + - Config: select or create the config se index record mentioned above + +You can create, update and delete exporter templates through a button in the tree view +once you click on the exporter or the button under the `exporter_id` field in the +se.index form view. + +## Media + +![Backend Configuration](../static/img/backend.png) + +![Exporter Dialog Button in Tree View](../static/img/exporter_dialog_button.png) + +![Exporter Dialog](../static/img/exporter_dialog.png) diff --git a/typesense_ir_exports/readme/CONTRIBUTORS.md b/typesense_ir_exports/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..0f2759b1 --- /dev/null +++ b/typesense_ir_exports/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +* Mohamed Alkobrosli diff --git a/typesense_ir_exports/readme/DESCRIPTION.md b/typesense_ir_exports/readme/DESCRIPTION.md new file mode 100644 index 00000000..927e011b --- /dev/null +++ b/typesense_ir_exports/readme/DESCRIPTION.md @@ -0,0 +1,9 @@ +Use Exporter (ir.exports) as serializer for connector_typesense + +Each ir.exports records is converted into JSON and JSON data get indexed into the Search Engine. + +Data can be String, Integer, Float, Lists, and Relations in the form of Object. + +Thnaks to the dynamic Schema configuration we can add new fields or remove without breaking the Schema. + +Binary data like images are sent as string, but better if we use external filestore to use images related external urls. diff --git a/typesense_ir_exports/readme/USAGE.md b/typesense_ir_exports/readme/USAGE.md new file mode 100644 index 00000000..7740c76f --- /dev/null +++ b/typesense_ir_exports/readme/USAGE.md @@ -0,0 +1,36 @@ +## Simple Quick User Friendly Usage + +The key of the module is the user friendly usage of ir.exports to serialize data and +resolve it for JSON. + +Behind the scene everything is managed to jsonify: + + - images bytes into string + - images thumbnail into urls external or internal (any of them in sequence) + - many2one relation into string + - many2one inner fileds into object of key value pairs + - many2many into list of inner strings or objects + - intergers are indexed as integers + - floats are indexed as floats + - id is ESSENIALLY indexed as string and not as integer (typesene only) + +If relational field has inner relational field and that also has inner relational field +or data, no worries everything is managed smoothly. + +Selecting fields and relations and inner relations is friendly handled by the exporter +dialog. + +You can remove or add fields as you wish, as indexing is updated, and if necessary +recreated. + +(recreating a collection only if a data type changes from string into object or vice +versa, like having many2one relation and then having the same relation with inner fields +of it) + +## Media + +![se index form view with exporter button](../static/img/se_index_form_with_exporter_button.png) + +![exporter dialog complicated tree](../static/img/dialog_tree_content.png) + +![serialized json data from exporter related record](../static/img/serialized_data_from_exporter.png) diff --git a/typesense_ir_exports/static/description/icon.png b/typesense_ir_exports/static/description/icon.png new file mode 100644 index 00000000..64e41565 Binary files /dev/null and b/typesense_ir_exports/static/description/icon.png differ diff --git a/typesense_ir_exports/static/description/index.html b/typesense_ir_exports/static/description/index.html new file mode 100644 index 00000000..7521802a --- /dev/null +++ b/typesense_ir_exports/static/description/index.html @@ -0,0 +1,541 @@ + + + + + +Typesense Serializer Ir Export + + + +
+

Typesense Serializer Ir Export

+ + +

Alpha License: AGPL-3 OCA/search-engine Translate me on Weblate Try me on Runboat

+

Use Exporter (ir.exports) as serializer for connector_typesense

+

Each ir.exports records is converted into JSON and JSON data get indexed +into the Search Engine.

+

Data can be String, Integer, Float, Lists, and Relations in the form of +Object.

+

Thnaks to the dynamic Schema configuration we can add new fields or +remove without breaking the Schema.

+

Binary data like images are sent as string, but better if we use +external filestore to use images related external urls.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

You need to have typesense search engine running and successfully +connected to odoo.

+

Make sure to have connector_search_engine and +connector_typesense modules installed.

+
+

SE Index Config

+

Transition to: Search Engine > Configuration > Index configurations

+

Make sure to have this search index configuration in a new record:

+
+- Name: give it a unigue name
+- Body Str:
+
+
+{
+  "name": "ts_products_collection",
+  "fields": [
+    {
+      "name": "id",
+      "type": "string"
+    },
+    {
+      "name": "name",
+      "type": "string"
+    },
+    {
+      "name": ".*",
+      "type": "auto",
+      "optional": true
+    }
+  ],
+  "enable_nested_fields": true
+}
+
+
+
+

SE Backend

+

Transition to: Search Engine > Configuration > Backends

+

Create a backend record, and create an index line with values:

+
+- Model: select the model you want to index
+- Serializer Type: Exporter
+- Exporter: select or create a new exporter template by clicking on "Open Exporter" button
+- Config: select or create the config se index record mentioned above
+
+

You can create, update and delete exporter templates through a button in +the tree view once you click on the exporter or the button under the +exporter_id field in the se.index form view.

+
+
+

Media

+

Backend Configuration

+

Exporter Dialog Button in Tree View

+

Exporter Dialog

+
+
+
+

Usage

+
+

Simple Quick User Friendly Usage

+

The key of the module is the user friendly usage of ir.exports to +serialize data and resolve it for JSON.

+

Behind the scene everything is managed to jsonify:

+
+- images bytes into string
+- images thumbnail into urls external or internal (any of them in sequence)
+- many2one relation into string
+- many2one inner fileds into object of key value pairs
+- many2many into list of inner strings or objects
+- intergers are indexed as integers
+- floats are indexed as floats
+- id is ESSENIALLY indexed as string and not as integer (typesene only)
+
+

If relational field has inner relational field and that also has inner +relational field or data, no worries everything is managed smoothly.

+

Selecting fields and relations and inner relations is friendly handled +by the exporter dialog.

+

You can remove or add fields as you wish, as indexing is updated, and if +necessary recreated.

+

(recreating a collection only if a data type changes from string into +object or vice versa, like having many2one relation and then having the +same relation with inner fields of it)

+
+
+

Media

+

se index form view with exporter button

+

exporter dialog complicated tree

+

serialized json data from exporter related record

+
+
+
+

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

+
    +
  • Kencove
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

Kencove

+

This module is part of the OCA/search-engine project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/typesense_ir_exports/static/img/backend.png b/typesense_ir_exports/static/img/backend.png new file mode 100644 index 00000000..cabfc5b4 Binary files /dev/null and b/typesense_ir_exports/static/img/backend.png differ diff --git a/typesense_ir_exports/static/img/dialog_tree_content.png b/typesense_ir_exports/static/img/dialog_tree_content.png new file mode 100644 index 00000000..36e9d0b5 Binary files /dev/null and b/typesense_ir_exports/static/img/dialog_tree_content.png differ diff --git a/typesense_ir_exports/static/img/exporter_dialog.png b/typesense_ir_exports/static/img/exporter_dialog.png new file mode 100644 index 00000000..d188dfd9 Binary files /dev/null and b/typesense_ir_exports/static/img/exporter_dialog.png differ diff --git a/typesense_ir_exports/static/img/exporter_dialog_button.png b/typesense_ir_exports/static/img/exporter_dialog_button.png new file mode 100644 index 00000000..902a4cce Binary files /dev/null and b/typesense_ir_exports/static/img/exporter_dialog_button.png differ diff --git a/typesense_ir_exports/static/img/se_index_form_with_exporter_button.png b/typesense_ir_exports/static/img/se_index_form_with_exporter_button.png new file mode 100644 index 00000000..24664dca Binary files /dev/null and b/typesense_ir_exports/static/img/se_index_form_with_exporter_button.png differ diff --git a/typesense_ir_exports/static/img/serialized_data_from_exporter.png b/typesense_ir_exports/static/img/serialized_data_from_exporter.png new file mode 100644 index 00000000..017c479d Binary files /dev/null and b/typesense_ir_exports/static/img/serialized_data_from_exporter.png differ diff --git a/typesense_ir_exports/static/src/action_ir_export.esm.js b/typesense_ir_exports/static/src/action_ir_export.esm.js new file mode 100644 index 00000000..5b006569 --- /dev/null +++ b/typesense_ir_exports/static/src/action_ir_export.esm.js @@ -0,0 +1,149 @@ +/** @odoo-module **/ + +/* Copyright 2023 Kencove (https://kencove.com). + @author Mohamed Alkobrosli + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +import {registry} from "@web/core/registry"; +import {Many2OneField} from "@web/views/fields/many2one/many2one_field"; +import {ExportDataDialog} from "@web/views/view_dialogs/export_data_dialog"; +import {useService} from "@web/core/utils/hooks"; + +const {onWillDestroy} = owl; + +class CustomExportDataDialog extends ExportDataDialog { + setup() { + super.setup(); + Object.assign(this.state, { + showApplyTemplatetButton: false, + }); + this.title = this.env._t("Select Data for Indexing"); + // We hack the current model from props obj to avoid patching other methods + this.swapResModel = this.props.root.resModel; + this.props.root.resModel = this.props.context.resModel; + if (this.props.context.exporter_id) { + this.state.templateId = this.props.context.exporter_id[0]; + } + // Once we destroy the dialog we return the original value back + onWillDestroy(() => { + this.props.root.resModel = this.swapResModel; + }); + } + async onChangeExportList(ev) { + this.loadExportList(ev.target.value); + // Show button only when there is selected saved template with different id + if ( + this.state.templateId === this.props.context.exporter_id[0] || + this.state.templateId === "new_template" + ) { + this.state.showApplyTemplatetButton = false; + } else { + this.state.showApplyTemplatetButton = true; + } + } + onQuickOverlap(templ) { + this.props.context.overlap(templ); + } + onClickApplyTemplatetButton() { + const arrayOfTemplates = this.templates.map(({id, name}) => [id, name]); + const templ = arrayOfTemplates.find( + (subArray) => subArray[0] === this.state.templateId + ); + this.onQuickOverlap(templ); + this.props.close(); + } + async onUpdateExportTemplate() { + const oldRec = await this.orm.read( + "ir.exports", + [this.state.templateId], + ["name", "export_fields"] + ); + let oldLines = []; + if ( + oldRec.length && + oldRec[0].export_fields && + oldRec[0].export_fields.length + ) { + oldLines = await this.orm.read("ir.exports.line", oldRec[0].export_fields, [ + "name", + ]); + } + // Get list of field names from exportList and existing lines + const newFieldNames = this.state.exportList.map((field) => field.id); + const oldFieldMap = Object.fromEntries( + oldLines.map((line) => [line.name, line.id]) + ); + // Prepare commands + const fieldCommands = []; + // Keep or create + for (const field of this.state.exportList) { + if (oldFieldMap[field.id]) { + // Link existing line + fieldCommands.push([4, oldFieldMap[field.id]]); + } else { + // New line to create + fieldCommands.push([0, 0, {name: field.id}]); + } + } + // Unlink deleted fields + for (const oldLine of oldLines) { + if (!newFieldNames.includes(oldLine.name)) { + fieldCommands.push([3, oldLine.id]); // Unlink and delete + } + } + // Write back to ir.exports + await this.orm.write("ir.exports", [this.state.templateId], { + export_fields: fieldCommands, + }); + this.state.isEditingTemplate = false; + } +} +CustomExportDataDialog.template = "typesense_ir_exports.CustomExportDataDialog"; + +class IrExportWidget extends Many2OneField { + setup() { + super.setup(); + this.rpc = useService("rpc"); + this.orm = useService("orm"); + this.dialogService = useService("dialog"); + this.quickOverlap = (templ) => { + if (templ && templ[0] && templ[1]) { + return this.props.update(templ); + } + }; + } + async downloadExport() { + return true; + } + async getExportedFields(model, import_compat, parentParams) { + return await this.rpc("/web/export/get_fields", { + ...parentParams, + model, + import_compat, + }); + } + async onExportData() { + const dialogProps = { + context: { + ...this.props.record.context, + resModel: this.props.record.data.model_name, + exporter_id: this.props.value, + overlap: (templ) => { + this.quickOverlap(templ); + }, + }, + defaultExportList: [], + download: this.downloadExport.bind(this), + getExportedFields: this.getExportedFields.bind(this), + root: this.props.record.model.root, + }; + this.dialogService.add(CustomExportDataDialog, dialogProps); + } + openIrExport() { + this.onExportData(); + } +} + +IrExportWidget.template = "typesense_ir_exports.IrExportWidget"; + +registry.category("fields").add("ir_export_widget", IrExportWidget); diff --git a/typesense_ir_exports/static/src/action_ir_export.xml b/typesense_ir_exports/static/src/action_ir_export.xml new file mode 100644 index 00000000..5bd04f49 --- /dev/null +++ b/typesense_ir_exports/static/src/action_ir_export.xml @@ -0,0 +1,59 @@ + + + + +
+ +
+
+
+ + + false + + + false + + + false + + + + + + + + + + +
diff --git a/typesense_ir_exports/tests/__init__.py b/typesense_ir_exports/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/typesense_ir_exports/tools/__init__.py b/typesense_ir_exports/tools/__init__.py new file mode 100644 index 00000000..e3a236bb --- /dev/null +++ b/typesense_ir_exports/tools/__init__.py @@ -0,0 +1,2 @@ +from . import serializer +from . import resolver diff --git a/typesense_ir_exports/tools/resolver.py b/typesense_ir_exports/tools/resolver.py new file mode 100644 index 00000000..dd4f01a7 --- /dev/null +++ b/typesense_ir_exports/tools/resolver.py @@ -0,0 +1,61 @@ +# Copyright 2023 Kencove (https://kencove.com). +# @author Mohamed Alkobrosli +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +class IrExportsResolver: + """ + The role of this class is to convert data of branches + into a the form of data that jsonifer module can jsonify. + + I assume that the data coming from ir.exports record is looking like this: + { + 'fields': [ + {'name': 'name'}, + ( + {'name': 'categ_id'}, + [{'name': 'name'}, {'name': 'sale_ok'}, {'name': 'purchase_ok'}] + ) + ] + } + + The final datastructure should look similar to this structure: + ["id", "name", ("categ_id", ["id", "name", "sale_ok", "purchase_ok"])] + """ + + def __init__(self, parser): + fields = [] + if parser.get("fields") and isinstance(parser["fields"], list): + fields = parser["fields"] + self.resolved_parser = [self.convert(field) for field in fields] + # Remove elements from the list if they are empty lists + self.resolved_parser = [item for item in self.resolved_parser if item] + + def get_dict_key(self, field): + if isinstance(field, dict) and "name" in field: + return field["name"] + else: + return field + + def resolve_tuple_field(self, field): + if isinstance(field, tuple) and len(field) == 2: + parent, children = field + if isinstance(parent, dict): + return ( + self.get_dict_key(parent), + [ + self.get_dict_key(child) + if isinstance(child, dict) + else self.resolve_tuple_field(child) + for child in children + ], + ) + # Safeguarding the structure result if the branch is broken, + # assign this branch empty list to protect other branches and the root + return [] + + def convert(self, field): + if isinstance(field, dict): + return self.get_dict_key(field) + else: + return self.resolve_tuple_field(field) diff --git a/typesense_ir_exports/tools/serializer.py b/typesense_ir_exports/tools/serializer.py new file mode 100644 index 00000000..721f2acd --- /dev/null +++ b/typesense_ir_exports/tools/serializer.py @@ -0,0 +1,48 @@ +# Copyright 2023 Kencove (https://kencove.com). +# @author Mohamed Alkobrosli +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from odoo.addons.connector_search_engine.tools.serializer import ModelSerializer + +try: + from odoo.addons.fs_image.fields import FSImageValue +except ImportError: + FSImageValue = None + + +class JsonifySerializer(ModelSerializer): + def __init__(self, parser, index): + super().__init__() + self.parser = parser + self.index = index + + # typesense search engine requires id to be string + def stringify_id(self, obj): + if obj.get("id"): + obj["id"] = f"{obj['id']}" + return obj + + def serialize(self, record): + ################################ + # Validating populated json data + ################################ + data = record.jsonify(self.parser, one=True) + # Should convert binary data to string as data field is of type json + for key, value in data.items(): + # Convert binary fields into string json + if isinstance(value, bytes): + data[key] = base64.b64encode(value).decode("utf-8") + # Convert FS images into url string if fs_image is installed + if FSImageValue and isinstance(value, FSImageValue): + data[key] = value.url_path or value.url or value.internal_url + if isinstance(value, dict): + data[key] = self.stringify_id(value) + if isinstance(value, list): + for i in range(len(value)): + if isinstance(value[i], dict): + value[i] = self.stringify_id(value[i]) + data[key] = value + data = self.stringify_id(data) + return data diff --git a/typesense_ir_exports/views/se_index_view.xml b/typesense_ir_exports/views/se_index_view.xml new file mode 100644 index 00000000..bc840768 --- /dev/null +++ b/typesense_ir_exports/views/se_index_view.xml @@ -0,0 +1,30 @@ + + + + + se.index + + + + ir_export_widget + {'no_open': True, 'no_create': True} + + + + + + se.index + + + + ir_export_widget + {'no_open': True, 'no_create': True} + + + + +