diff --git a/setup/spreadsheet_oca_upload_base/odoo/addons/spreadsheet_oca_upload_base b/setup/spreadsheet_oca_upload_base/odoo/addons/spreadsheet_oca_upload_base new file mode 120000 index 00000000..8d9ade30 --- /dev/null +++ b/setup/spreadsheet_oca_upload_base/odoo/addons/spreadsheet_oca_upload_base @@ -0,0 +1 @@ +../../../../spreadsheet_oca_upload_base \ No newline at end of file diff --git a/setup/spreadsheet_oca_upload_base/setup.py b/setup/spreadsheet_oca_upload_base/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/spreadsheet_oca_upload_base/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/spreadsheet_oca_upload_base/README.rst b/spreadsheet_oca_upload_base/README.rst new file mode 100644 index 00000000..337e09f3 --- /dev/null +++ b/spreadsheet_oca_upload_base/README.rst @@ -0,0 +1,168 @@ +=========================== +Spreadsheet OCA Upload Base +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6495e77fa5a250c4e672af7e3a502bc571a4df9c527461a1031f4f2bfc593dc6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-OCA%2Fspreadsheet-lightgray.png?logo=github + :target: https://github.com/OCA/spreadsheet/tree/16.0/spreadsheet_oca_upload_base + :alt: OCA/spreadsheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/spreadsheet-16-0/spreadsheet-16-0-spreadsheet_oca_upload_base + :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/spreadsheet&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +- This module extends the integration with Odoo Spreadsheet to allow uploading XLSX files directly from any record. + +- It simplifies spreadsheet management by automatically generating attachments, validating file types, and linking the spreadsheet to the record, enabling quick and efficient access and updates. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +The SpreadsheetUploadMixin mixin can be used when a model needs the ability to upload an XLSX file and automatically create or link a spreadsheet document in Odoo. +It allows users to manage spreadsheets directly from the form view of a record. + +Let's consider the following models: + +.. code-block:: python + + class MyModelA(models.Model): + _name = "my.model.a" + _inherit = ["spreadsheet.upload.mixin"] + +By inheriting from spreadsheet.upload.mixin, the model gains the following: + +- upload_file: binary field to upload an XLSX file. + +- file_name: helper field to store the filename. + +- spreadsheet_id: link to the generated spreadsheet.spreadsheet record. + +- action_create_spreadsheet(): creates a new spreadsheet from the uploaded file. + +- action_open_spreadsheet(): opens the linked spreadsheet directly from the form. + +A corresponding form view can then be defined to expose these fields and buttons: + +.. code-block:: xml + + + my.model.a.form.view + my.model.a + +
+ + +
+
+ +
+
+ + +
+
+
+
+
+ +By using this mixin and view definition, you can upload an XLSX file from any record form, create a linked Odoo Spreadsheet, and open it for editing directly from the form view. + +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 +~~~~~~~ + +* BizzAppDev Systems Pvt. Ltd. + +Contributors +~~~~~~~~~~~~ + +* `BizzAppDev Systems `_: + + * Ruchir Shukla + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +- Agent ERP GmbH + +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. + +This module is part of the `OCA/spreadsheet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/spreadsheet_oca_upload_base/__init__.py b/spreadsheet_oca_upload_base/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/spreadsheet_oca_upload_base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/spreadsheet_oca_upload_base/__manifest__.py b/spreadsheet_oca_upload_base/__manifest__.py new file mode 100644 index 00000000..8f6df311 --- /dev/null +++ b/spreadsheet_oca_upload_base/__manifest__.py @@ -0,0 +1,11 @@ +{ + "name": "Spreadsheet OCA Upload Base", + "summary": "Base module for uploading spreadsheets to the model", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "BizzAppDev Systems Pvt. Ltd., Odoo Community Association (OCA)", + "website": "https://github.com/OCA/spreadsheet", + "depends": ["spreadsheet_oca"], + "installable": True, + "auto_install": False, +} diff --git a/spreadsheet_oca_upload_base/models/__init__.py b/spreadsheet_oca_upload_base/models/__init__.py new file mode 100644 index 00000000..11a0db25 --- /dev/null +++ b/spreadsheet_oca_upload_base/models/__init__.py @@ -0,0 +1 @@ +from . import spreadsheet_upload_mixin diff --git a/spreadsheet_oca_upload_base/models/spreadsheet_upload_mixin.py b/spreadsheet_oca_upload_base/models/spreadsheet_upload_mixin.py new file mode 100644 index 00000000..14c2363d --- /dev/null +++ b/spreadsheet_oca_upload_base/models/spreadsheet_upload_mixin.py @@ -0,0 +1,64 @@ +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class SpreadsheetUploadMixin(models.AbstractModel): + _name = "spreadsheet.upload.mixin" + _description = "Mixin to upload XLSX and create linked spreadsheet" + + spreadsheet_id = fields.Many2one( + comodel_name="spreadsheet.spreadsheet", + string="Spreadsheet", + help="Linked Spreadsheet", + copy=False, + ) + upload_file = fields.Binary( + string="Upload XLSX", copy=False, help="XLSX file to upload" + ) + file_name = fields.Char(copy=False, help="Name of the uploaded file") + + @api.constrains("file_name") + def _check_xlsx_file_type(self): + """New constraint to check uploaded file type is XLSX.""" + invalid_files = self.filtered( + lambda spreadsheet: not spreadsheet.file_name.lower().endswith(".xlsx") + ) + if invalid_files: + raise ValidationError(_("Please upload a valid XLSX file.")) + + def _get_attachment(self): + """New method to search for the attachment of the uploaded file.""" + self.ensure_one() + attachment = self.env["ir.attachment"].search( + [ + ("res_model", "=", self._name), + ("res_id", "=", self.id), + ("res_field", "=", "upload_file"), + ], + limit=1, + ) + if attachment: + attachment.write( + { + "name": self.file_name or "uploaded_file", + } + ) + return attachment + + def action_create_spreadsheet(self): + """New method to create spredsheet from the upload file.""" + for record in self: + attachment = record._get_attachment() + if not attachment: + continue + spreadsheet = self.env[ + "spreadsheet.spreadsheet" + ].create_document_from_attachment([attachment.id]) + record.spreadsheet_id = spreadsheet.get("res_id") if spreadsheet else False + + def action_open_spreadsheet(self): + """New method to open linked spredsheet.""" + self.ensure_one() + if not self.spreadsheet_id: + return + return self.spreadsheet_id.open_spreadsheet() diff --git a/spreadsheet_oca_upload_base/readme/CONTRIBUTORS.rst b/spreadsheet_oca_upload_base/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..d4402cd0 --- /dev/null +++ b/spreadsheet_oca_upload_base/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `BizzAppDev Systems `_: + + * Ruchir Shukla \ No newline at end of file diff --git a/spreadsheet_oca_upload_base/readme/CREDITS.rst b/spreadsheet_oca_upload_base/readme/CREDITS.rst new file mode 100644 index 00000000..28dbbfd1 --- /dev/null +++ b/spreadsheet_oca_upload_base/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- Agent ERP GmbH \ No newline at end of file diff --git a/spreadsheet_oca_upload_base/readme/DESCRIPTION.rst b/spreadsheet_oca_upload_base/readme/DESCRIPTION.rst new file mode 100644 index 00000000..3043bd37 --- /dev/null +++ b/spreadsheet_oca_upload_base/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +- This module extends the integration with Odoo Spreadsheet to allow uploading XLSX files directly from any record. + +- It simplifies spreadsheet management by automatically generating attachments, validating file types, and linking the spreadsheet to the record, enabling quick and efficient access and updates. \ No newline at end of file diff --git a/spreadsheet_oca_upload_base/readme/USAGE.rst b/spreadsheet_oca_upload_base/readme/USAGE.rst new file mode 100644 index 00000000..8f668ac2 --- /dev/null +++ b/spreadsheet_oca_upload_base/readme/USAGE.rst @@ -0,0 +1,77 @@ +The SpreadsheetUploadMixin mixin can be used when a model needs the ability to upload an XLSX file and automatically create or link a spreadsheet document in Odoo. +It allows users to manage spreadsheets directly from the form view of a record. + +Let's consider the following models: + +.. code-block:: python + + class MyModelA(models.Model): + _name = "my.model.a" + _inherit = ["spreadsheet.upload.mixin"] + +By inheriting from spreadsheet.upload.mixin, the model gains the following: + +- upload_file: binary field to upload an XLSX file. + +- file_name: helper field to store the filename. + +- spreadsheet_id: link to the generated spreadsheet.spreadsheet record. + +- action_create_spreadsheet(): creates a new spreadsheet from the uploaded file. + +- action_open_spreadsheet(): opens the linked spreadsheet directly from the form. + +A corresponding form view can then be defined to expose these fields and buttons: + +.. code-block:: xml + + + my.model.a.form.view + my.model.a + +
+ + +
+
+ +
+
+ + +
+
+
+
+
+ +By using this mixin and view definition, you can upload an XLSX file from any record form, create a linked Odoo Spreadsheet, and open it for editing directly from the form view. \ No newline at end of file diff --git a/spreadsheet_oca_upload_base/static/description/index.html b/spreadsheet_oca_upload_base/static/description/index.html new file mode 100644 index 00000000..5f82a367 --- /dev/null +++ b/spreadsheet_oca_upload_base/static/description/index.html @@ -0,0 +1,509 @@ + + + + + +Spreadsheet OCA Upload Base + + + +
+

Spreadsheet OCA Upload Base

+ + +

Beta License: AGPL-3 OCA/spreadsheet Translate me on Weblate Try me on Runboat

+
    +
  • This module extends the integration with Odoo Spreadsheet to allow uploading XLSX files directly from any record.
  • +
  • It simplifies spreadsheet management by automatically generating attachments, validating file types, and linking the spreadsheet to the record, enabling quick and efficient access and updates.
  • +
+

Table of contents

+ +
+

Usage

+

The SpreadsheetUploadMixin mixin can be used when a model needs the ability to upload an XLSX file and automatically create or link a spreadsheet document in Odoo. +It allows users to manage spreadsheets directly from the form view of a record.

+

Let’s consider the following models:

+
+class MyModelA(models.Model):
+    _name = "my.model.a"
+    _inherit = ["spreadsheet.upload.mixin"]
+
+

By inheriting from spreadsheet.upload.mixin, the model gains the following:

+
    +
  • upload_file: binary field to upload an XLSX file.
  • +
  • file_name: helper field to store the filename.
  • +
  • spreadsheet_id: link to the generated spreadsheet.spreadsheet record.
  • +
  • action_create_spreadsheet(): creates a new spreadsheet from the uploaded file.
  • +
  • action_open_spreadsheet(): opens the linked spreadsheet directly from the form.
  • +
+

A corresponding form view can then be defined to expose these fields and buttons:

+
+<record id="my_model_a_form_view" model="ir.ui.view">
+    <field name="name">my.model.a.form.view</field>
+    <field name="model">my.model.a</field>
+    <field name="arch" type="xml">
+        <form>
+            <sheet>
+                <group>
+                    <div class="o_row">
+                        <label for="spreadsheet_id"
+                            attrs="{'invisible': [('spreadsheet_id', '=', False)]}" />
+                        <div attrs="{'invisible': [('spreadsheet_id', '=', False)]}"
+                            style="margin-bottom: 15px;">
+                            <field name="spreadsheet_id"
+                                readonly="1"
+                                class="oe_inline"
+                                style="margin-right:7px;"/>
+                            <button name="action_open_spreadsheet"
+                                    type="object"
+                                    string="Edit"
+                                    icon="fa-pencil"
+                                    class="btn btn-primary"/>
+                        </div>
+                    </div>
+
+                    <div class="o_row">
+                        <label for="upload_file"
+                            class="o_form_label"
+                            style="font-weight: bold;"
+                            attrs="{'invisible': ['|', ('upload_file', '=', False), ('spreadsheet_id', '!=', False)]}"/>
+                        <field name="upload_file"
+                            filename="file_name"
+                            nolabel="1"
+                            attrs="{'invisible': [('spreadsheet_id', '!=', False)]}"/>
+                        <div style="margin-right: 400px; margin-bottom: 15px;">
+                            <button name="action_create_spreadsheet"
+                                    type="object"
+                                    string="Create Spreadsheet From File"
+                                    class="btn btn-primary"
+                                    attrs="{'invisible': ['|', ('upload_file', '=', False), ('spreadsheet_id', '!=', False)]}"/>
+                        </div>
+                    </div>
+
+                    <field name="file_name" invisible="1"/>
+                </group>
+            </sheet>
+        </form>
+    </field>
+</record>
+
+

By using this mixin and view definition, you can upload an XLSX file from any record form, create a linked Odoo Spreadsheet, and open it for editing directly from the form view.

+
+
+

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

+
    +
  • BizzAppDev Systems Pvt. Ltd.
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Agent ERP GmbH
  • +
+
+
+

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.

+

This module is part of the OCA/spreadsheet project on GitHub.

+

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

+
+
+
+ + diff --git a/spreadsheet_oca_upload_base/tests/__init__.py b/spreadsheet_oca_upload_base/tests/__init__.py new file mode 100644 index 00000000..88fca284 --- /dev/null +++ b/spreadsheet_oca_upload_base/tests/__init__.py @@ -0,0 +1 @@ +from . import test_spredsheet_mixin diff --git a/spreadsheet_oca_upload_base/tests/demo_model.py b/spreadsheet_oca_upload_base/tests/demo_model.py new file mode 100644 index 00000000..5064dbd7 --- /dev/null +++ b/spreadsheet_oca_upload_base/tests/demo_model.py @@ -0,0 +1,7 @@ +from odoo import models + + +class DemoSpreadsheetUploadModel(models.Model): + _name = "demo.spreadsheet.upload" + _inherit = "spreadsheet.upload.mixin" + _description = "Demo Model for Spreadsheet Upload Mixin" diff --git a/spreadsheet_oca_upload_base/tests/test_spredsheet_mixin.py b/spreadsheet_oca_upload_base/tests/test_spredsheet_mixin.py new file mode 100644 index 00000000..bfeb605f --- /dev/null +++ b/spreadsheet_oca_upload_base/tests/test_spredsheet_mixin.py @@ -0,0 +1,73 @@ +import base64 +import io + +from odoo_test_helper import FakeModelLoader +from openpyxl import Workbook + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestSpreadsheetUploadMixin(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .demo_model import DemoSpreadsheetUploadModel + + cls.loader.update_registry([DemoSpreadsheetUploadModel]) + + @staticmethod + def _generate_sample_xls_data(): + """New method to generate xls data.""" + workbook = Workbook() + worksheet = workbook.active + worksheet.title = "Sheet1" + worksheet["A1"] = "Sample XLSX Data" + worksheet["A2"] = "Demo Data" + worksheet["A3"] = "This is a sample XLSX file for testing." + buffer = io.BytesIO() + workbook.save(buffer) + buffer.seek(0) + return base64.b64encode(buffer.read()) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + return super().tearDownClass() + + def test_00_check_xlsx_file_type_valid(self): + """New method to check valid xlsx file type.""" + record = self.env["demo.spreadsheet.upload"].create( + {"file_name": "test_file.xlsx"} + ) + # Assers that file is created successfully. + self.assertEqual( + record.file_name, "test_file.xlsx", "File name is not set correctly." + ) + + def test_01_check_xlsx_file_type_invalid(self): + """New method to check invalid xlsx file type.""" + # Asserts that ValidationError is raised for invalid file type. + with self.assertRaises(ValidationError): + self.env["demo.spreadsheet.upload"].create({"file_name": "test_file.txt"}) + + def test_02_create_attachment(self): + """New method to check create attachment from upload file.""" + record = self.env["demo.spreadsheet.upload"].create( + { + "file_name": "test_file.xlsx", + "upload_file": self._generate_sample_xls_data(), + } + ) + record.action_create_spreadsheet() + # Asserts that spreadsheet is created successfully. + self.assertTrue(record.spreadsheet_id, "Spreadsheet is not created.") + action = record.action_open_spreadsheet() + # Asserts that the action returned has the correct spreadsheet ID. + self.assertEqual( + action["params"]["spreadsheet_id"], + record.spreadsheet_id.id, + "spredsheet is not same.", + ) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..934049e6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +odoo_test_helper +openpyxl