commit b2dd3ffb1cc9fdf9ba9efb604d047bc444d2f75f Author: ieva Date: Wed Jun 4 16:23:46 2025 +0300 eis_drag_drop: added drag&drop module from Expert IT Solutions diff --git a/eis_drag_drop/__init__.py b/eis_drag_drop/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/eis_drag_drop/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/eis_drag_drop/__manifest__.py b/eis_drag_drop/__manifest__.py new file mode 100644 index 0000000..88862a1 --- /dev/null +++ b/eis_drag_drop/__manifest__.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +################################################################################# +# Author : Expert IT Solutions () +# Copyright(c): 2012-Present Expert IT Solutions +# All Rights Reserved. +# +# This program is copyright property of the author mentioned above. +# You can`t redistribute it and/or modify it. +# +################################################################################# +{ + 'name': "One2many Image Drag and Drop Widget | Image Drag And Drop Widget ", + 'version': '17.0.1.4.1', + 'author': "Expert IT Solutions", + 'website': "https://www.expertpk.com", + 'summary': """ + Two widgets. 1: Binary Image Drag and Drop Widget 2: One2Many Image(s) Field Drag And Drop Widget. + Enhance your Odoo experience with a custom widget for drag and drop image functionality. + Keywords: one2many image drag and drop, image drag and drop, drag & drop, binary field image upload, file uploader, bulk image upload, media management, image preview, product image uploader, one2many drag and drop, o2m drag and drop, binary drag & drop, drag and drop pictures, image widget, zoom image, image zoom, binary fields, image_ids, product_template_image_ids, product_variant_image_ids, multiple images, upload multiple images, one2many widget, o2m widget, o2m uploader upload uploading image images multi media multimedia media owl widget widget view custom widget field widget. + """, + 'description': """ + The Drag And Drop Binary Field Widget for Odoo is a cutting-edge proprietary solution designed for advanced media management. + It provides an intuitive drag and drop interface to upload, preview, and manage images efficiently within your Odoo records. + + Key Features and Keywords: + - Image Drag And Drop Widget + - Bulk Image Upload + - Media Management + - File Uploader + - Odoo Binary Field Widget + - Custom Odoo Widget + - E-commerce Image Management + - Real Estate Image Management + - Drag and drop widget for Odoo + - Odoo image upload + - Odoo image preview + - Odoo file uploader + - Drag and drop binary field + - o2m field widget + - one2many field widget + - one2many drag and drop + - one2many image upload + - multiple image upload + - multiple images drag and drop + - drag and drop multiple images + - drag and drop multiple files + - image_1920 field widget + - image.mixin field widget + - product.image field widget + - website product multiple images + - website product drag and drop widget + - website product image upload + - Drag and Drop Interface + - Binary Field Widget + - Image Upload & Preview + - File Uploader & Media Management + - Custom Odoo Widget for E-commerce, Real Estate, Media Portals, and More + - Seamless Integration with Odoo's Backend + - Product Image Uploader, Custom Image Fields, and Dynamic Widgets + + IMPORTANT LICENSE & USAGE TERMS: + - Licensed under Odoo Proprietary License v1 (OPL-1). This license is extremely restrictive. + - Modification, redistribution, or reuse of any part of this module’s code in any other modules or applications is strictly prohibited. + - This module is intended for use on a single database only. For additional databases, buy additional licenses. + - Code modifications, reverse-engineering, or using the code to generate derivative works is not allowed. + - For full details, please refer to the separate LICENSE file provided with the module. + + Perfect for: + - E-commerce websites looking to manage multiple product images effortlessly. + - Businesses with heavy media content, such as real estate, portfolios, or media portals. + - Developers and system integrators seeking to enhance the user experience without extra dependencies, + as the widget is available systemwide once installed. + """, + 'license': 'OPL-1', + 'category': 'Tools', + 'depends': ['base_setup', 'base', 'web', 'product', 'website_sale'], + # uncomment above line to automate the process of product upgrade and comment the below depend line + # 'depends': ['base_setup', 'base', 'web', 'product'], + + 'data': [ + 'views/product.xml', # uncomment this line only if you added website_sale as dependency. + ], + 'assets': { + 'web.assets_backend': [ + 'eis_drag_drop/static/src/css/style.scss', + 'eis_drag_drop/static/src/css/styles.css', + 'eis_drag_drop/static/src/js/dnd_image_widget.js', + 'eis_drag_drop/static/src/js/dnd_images_widget.js', + 'eis_drag_drop/static/src/xml/dnd_widgets_templates.xml', + ], + }, + 'images': ['static/description/banner.gif'], + 'live_test_url': 'https://www.youtube.com/watch?v=JRPchiEhS9U', + 'price': 70.0, + 'currency': 'EUR', + 'auto_install': False, + 'installable': True, +} + + diff --git a/eis_drag_drop/doc/index.rst b/eis_drag_drop/doc/index.rst new file mode 100644 index 0000000..2141e58 --- /dev/null +++ b/eis_drag_drop/doc/index.rst @@ -0,0 +1,949 @@ +.. container:: top-bar d-flex justify-content-between align-items-center + + .. container:: + + |Logo| + + .. container:: availability + + Community    Enterprise    Odoo Online + +.. container:: section hero-section + + .. container:: + + .. rubric:: Images Drag And Drop Widgets + :name: images-drag-and-drop-widgets + + Effortlessly manage and update images in your Odoo records with + our intuitive drag and drop widgets. + + .. container:: vector-placeholder + + |Hero Vector| + + .. container:: benefit-box + + Perfect for E-commerce, Media Portals & More! + +.. container:: section section + :name: introduction + + .. container:: + + .. rubric:: + Introduction + :name: introduction + :class: text-center mb-5 fw-bold + + The **Images Drag And Drop Widgets** module is a state-of-the-art + solution designed to enhance your Odoo experience by integrating + modern, user-friendly drag and drop image widgets. + + .. rubric:: Who Can Benefit? + :name: who-can-benefit + + Whether you run an e-commerce site, a media portal, a real estate + listing platform, or any business that requires the handling of + multiple images per record, this module is built for you. Enjoy: + + - **E-commerce websites:** Easily manage product images with a + simple drag and drop interface. + - **Businesses with heavy media content:** Quickly update + portfolios, galleries, and listings. + - **Developers and Designers:** Seamlessly integrate modern + widgets across all modules—no extra dependency required! + + .. rubric:: Use Cases + :name: use-cases + + **Online Stores:** Showcase products with multiple images that can + be easily updated on-the-fly. + + **Real Estate Platforms:** Effortlessly manage property photos, + floor plans, and virtual tours. + + **Agencies & Portfolios:** Create visually appealing portfolios + where media speaks louder than words. + + .. rubric:: Benefits + :name: benefits + + With our module, say goodbye to tedious file uploads and hello to + a more engaging user experience. It’s simple, robust, and designed + to boost your productivity! + +.. container:: section section + :name: installation + + .. container:: + + .. rubric:: + Installation + :name: installation + :class: text-center mb-5 fw-bold + + Follow these detailed steps to install the **eis_drag_drop** + module: + + #. **Download the Module:** Obtain the module as a ZIP file from + the official repository or your vendor. + #. **Extract the ZIP:** Unzip the downloaded file to reveal the + module folder. + #. **Copy to Addons Folder:** Move or copy the extracted module + folder into your Odoo ``addons`` directory. + #. **Restart Odoo Service:** + + - On systemd-based systems: ``sudo systemctl restart odoo`` + - On SysVinit-based systems: ``sudo service odoo restart`` + - Or use the appropriate command for your OS. + + #. **Update App List:** In your Odoo database, navigate to the + Apps menu and click *Update Apps List*. + #. **Install the Module:** Search for *Drag and Drop Widgets* (or + ``eis_drag_drop``) and click install. + + .. container:: tips + + **Important:** Always test the module in a staging environment + before deploying it to your production server. Caution is + key—ensure you have backups and proper rollback plans in place. + +.. container:: section section + :name: user-guides + + .. container:: + + .. rubric:: + User Guide + :name: user-guide + :class: text-center mb-5 fw-bold + + .. rubric:: Updating Existing Image Fields + :name: updating-existing-image-fields + + You can update any existing binary image field to use our drag and + drop widget without altering your custom modules. Simply update + your XML views by setting the widget attribute to: + + .. container:: code-snippet + + :: + + + + + **Options Explained:** + + - ``image_size``: Defines the display size of the widget. Example: + "150x150" sets the width to 150px and height to 150px. + - ``preview_image``: Specifies the field used for the image + preview. If not set, it defaults to the binary field name. + - ``acceptedFileExtensions``: Determines which image formats can + be uploaded. + - ``enableZoom``: If true, hovering over the image displays a zoom + popup. + - ``additionalStyles``: Custom CSS styles that are appended to the + widget's inline styles. + + .. rubric:: Detailed O2M Setup & Custom Integration + :name: detailed-o2m-setup-custom-integration + + For One2Many fields, our widget transforms the standard image + upload into a dynamic drag and drop area. **Note:** The related + model *must* have a Kanban view defined that includes at least the + ``name`` and ``image`` (or ``image_1920``) fields. + + **Why a Kanban View?** The Kanban view enables the widget to + render image previews instead of just record IDs. Without it, you + may only see numerical identifiers. + + **Form View for Videos & Manual Entry:** If you wish to add videos + or manually add images in the traditional way, ensure that your + One2Many field is also accessible via a form view. + + .. rubric:: Example of One2Many Field Integration + :name: example-of-one2many-field-integration + + .. container:: code-snippet + + :: + + + + + + .. rubric:: Options Explained: + :name: options-explained + + - ``childImageField``: Specifies the field in the child model + where the binary image data is stored. This is mandatory for the + widget to function correctly. If not specified, image_1920 will + be used. + - ``extraData``: Allows you to pass additional data to the child + records. This can include: + + - ``categ_id``: An example field that sets a default category + ID. This feature allows you to add any additional field and + its value if your model has a required field and without it, + it cannot create new records, or a field that you need to fill + with static data. + - ``sale_ok``: This is another example field and you can pass + any bool value if your required field is bool type + - ``previewImage``: Specifies a smaller image field to use for + previews, which is important if you want to show a smaller + size image to save bandwidth. This value should be used if + your model is inherited from ``image.mixin`` and you want to + display a smaller preview image. + - ``cssStyles``: Custom CSS styles for the image display. + Example: + ``"width:150px; height:200px; border-radius:15px; object-fit:cover;"``. + + - ``acceptedFileExtensions``: Determines which file types are + acceptable for upload. Default is ``"image/*"``. + +.. container:: section section + :name: user-guides + + .. container:: + + .. rubric:: + User Guide + :name: user-guide-1 + :class: text-center mb-5 fw-bold + + .. rubric:: Updating Existing Image Fields + :name: updating-existing-image-fields-1 + + You can update any existing binary image field to use our drag and + drop widget without altering your custom modules. Simply update + your XML views by setting the widget attribute to: + + .. container:: code-snippet + + :: + + + + + **Options Explained:** + + - ``image_size``: Defines the display size of the widget. Example: + "150x150" sets the width to 150px and height to 150px. + - ``preview_image``: Specifies the field used for the image + preview. If not set, it defaults to the binary field name. + - ``acceptedFileExtensions``: Determines which image formats can + be uploaded. + - ``enableZoom``: If true, hovering over the image displays a zoom + popup. + - ``additionalStyles``: Custom CSS styles that are appended to the + widget's inline styles. + + .. rubric:: Detailed O2M Setup & Custom Integration + :name: detailed-o2m-setup-custom-integration-1 + + For One2Many fields, our widget transforms the standard image + upload into a dynamic drag and drop area. **Note:** The related + model *must* have a Kanban view defined that includes at least the + ``name`` and ``image`` (or ``image_1920``) fields. + + **Why a Kanban View?** The Kanban view enables the widget to + render image previews instead of just record IDs. Without it, you + may only see numerical identifiers. + + **Form View for Videos & Manual Entry:** If you wish to add videos + or manually add images in the traditional way, ensure that your + One2Many field is also accessible via a form view. + + .. rubric:: Example of One2Many Field Integration + :name: example-of-one2many-field-integration-1 + + .. container:: code-snippet + + :: + + + + + + .. rubric:: Options Explained: + :name: options-explained-1 + + - ``childImageField``: Specifies the field in the child model + where the binary image data is stored. This is mandatory for the + widget to function correctly. If not specified, ``image_1920`` + will be used. + - ``extraData``: Allows you to pass additional data to the child + records. This can include: + + - ``categ_id``: An example field that sets a default category + ID. This feature allows you to add any additional field and + its value if your model has a required field and without it, + it cannot create new records, or a field that you need to fill + with static data. + - ``sale_ok``: This is another example field and you can pass + any bool value if your required field is bool type. + - ``previewImage``: Specifies a smaller image field to use for + previews, which is important if you want to show a smaller + size image to save bandwidth. This value should be used if + your model is inherited from ``image.mixin`` and you want to + display a smaller preview image. + - ``cssStyles``: Custom CSS styles for the image display. + Example: + ``"width:150px; height:200px; border-radius:15px; object-fit:cover;"``. + + - ``acceptedFileExtensions``: Determines which file types are + acceptable for upload. Default is ``"image/*"``. + + .. container:: alert alert-danger + + .. rubric:: Important Notice! + :name: important-notice + :class: alert-heading + + Do not create the models described in the following example. + Odoo already includes these models. This example is provided + for educational purposes to help you understand the + implementation. + + .. rubric:: Complete Example (Taken from Odoo builtin Code): + :name: complete-example-taken-from-odoo-builtin-code + + .. rubric:: Adding Multiple Image Support to ``product.template`` + :name: adding-multiple-image-support-to-product.template + + Let's say we have a model ``product.template`` and we need to add + multiple image support to it. We will create a model + ``product.image`` by inheriting from the ``image.mixin`` class. + This way, we automatically get fields like ``image_1920`` and + other related fields for our model ``product.image``. + + Here are the steps to achieve this: + + #. **Create the ``product.image`` Model:** + + Inherit from ``image.mixin`` to automatically get image-related + fields. + + .. container:: code-snippet + + :: + + class ProductImage(models.Model): + _name = 'product.image' + _inherit = 'image.mixin' + + product_tmpl_id = fields.Many2one('product.template', string='Product Template') + sequence = fields.Integer('Sequence') + video_url = fields.Char('Video URL') + + #. **Extend the ``product.template`` Model:** + + Add a One2Many field to link multiple images. + + .. container:: code-snippet + + :: + + class ProductTemplate(models.Model): + _inherit = 'product.template' + + product_template_image_ids = fields.One2many('product.image', 'product_tmpl_id', string='Images') + + #. **Create a Kanban View for ``product.image``:** + + Add a Kanban view with the necessary fields. + + .. container:: code-snippet + + :: + + + product.image.kanban + product.image + + + + + + + + + + + #. **Create a Form View for ``product.image``:** + + Add a form view with fields for video URL and image. + + .. container:: code-snippet + + :: + + + product.image.form + product.image + +
+ + + + + + +
+ + #. **Update the ``product.template`` Form View:** + + Add the One2Many field with the drag-and-drop widget. + + .. container:: code-snippet + + :: + + + product.template.form + product.template + + +
+ + + + + +
+
+
+ + .. container:: alert alert-warning + + **Important Considerations for Product Image Management (Odoo + 17 and 18)** + This guide outlines the necessary steps for integrating + d_and_d_images widget for product template, specifically when + using the ``website_sale`` module, across Odoo 17 and 18. + Please carefully follow the instructions for your respective + Odoo version to avoid conflicts and ensure proper + functionality. + + **Odoo 17:** + + #. **Do not duplicate the ``product.image`` model or its + views.** This model is already provided by the + ``website_sale`` module in Odoo. Creating a duplicate will + lead to conflicts. + #. **Inherit the ``product.template`` form view.**. You will + modify the form view ``product.product_template_form_view`` + to integrate d_and_d_images customizations seamlessly with + Odoo's core functionality. + #. **Customize the image field:** Replace the + ``product_template_image_ids`` field in the inherited form + view with d_and_d_images widget as described above to + enhance the product image management functionality. + + **Odoo 18:** + + Odoo 18 has modified the ``product.image`` Kanban view by + removing certain fields. So, to make it compatible again with + our d_and_d_images widghet, here's how to adjust Odoo 18: + + #. **Inherit the ``product.image`` Kanban view.** + ``website_sale.product_image_view_kanban``. Add the + necessary ``id`` and ``name`` fields back into the view to + maintain existing functionality. That's it. + #. **Enable modifications through the module's manifest:** + + - Declare dependency on ``website_sale`` in our + eis_drag_drop module's ``__manifest__.py`` file to ensure + proper loading order. You jsut need to uncomment the line + having website_sale with key depends and then comment the + existing depend key. + - Reference the XML file containing your Kanban view + modifications in the ``data`` key of the manifest to apply + these changes automatically when your module is installed + or updated. + + #. **Alternative approach:** Manually define the Kanban view + inheritance in a ``product.xml`` file if you prefer more + direct control over the modifications. + + Our module has pre-configured these enhancements for you. To + activate them, modify the ``__manifest__.py`` file to uncomment + the dependency on ``website_sale`` and the reference to + ``/views/product.xml`` in the ``data`` key. This ensures all + functionalities are seamlessly integrated when you install or + update the module. + +.. container:: section section + :name: screenshots + + .. container:: + + .. rubric:: + Screenshots Explaination + :name: screenshots-explaination + :class: text-center mb-5 fw-bold + + .. container:: screenshot-full + + .. container:: screenshot-number + + 01 + + .. rubric:: Drag and Drop Widget Overview + :name: drag-and-drop-widget-overview + + |Drag and Drop Widget Overview| + A clear view of the drag and drop widget areas for single + binary image and for one2many images., allowing users to + effortlessly update images. + + .. container:: screenshot-full + + .. container:: screenshot-number + + 02 + + .. rubric:: One2Many Drag And Drop Explained + :name: one2many-drag-and-drop-explained + + |One2many Field Enhancement| + In this screenshot, you can see a simple explanation of the + view. Each record's thumbnail, have a small cross on top right + corner to remove a record, view also display video thumbnail, + and buttons. + + .. container:: screenshot-full + + .. container:: screenshot-number + + 04 + + .. rubric:: Add Video Button + :name: add-video-button + + |image1| + This screenshot illustrates how the one2many field widget + ``'Add Video'`` button is working. You can access odoo builtin + method being used in website_sale module of odoo to add extra + media including videos or images. + + .. container:: screenshot-full + + .. container:: screenshot-number + + 05 + + .. rubric:: Manual File Uploding Button Display + :name: manual-file-uploding-button-display + + |image2| + This screenshot illustrates how the one2many field widget + manual upload button is working. If you want to upload images + manually from file uploader, you can click on this button and + it will open finder / file explorer for you. It will only allow + images to upload with jpg, jpeg, png, svg etc + + .. container:: screenshot-full + + .. container:: screenshot-number + + 06 + + .. rubric:: Iamges Dragging over One2many Field Display + :name: iamges-dragging-over-one2many-field-display + + |image3| + This screenshot illustrates how the one2many field is being + used to drop 4 images into drag drop area. Notice the blue + border that is highlighted when we dragged the images over it. + As soon as we will drop the images, they will be uploaded + automatically + +.. container:: section section + :name: video + + .. container:: + + .. rubric:: + Video Explaination + :name: video-explaination + :class: text-center mb-5 fw-bold + + .. container:: screenshot-full + + .. container:: screenshot-number + + 01 + + .. rubric:: Drag and Drop Widget Overview + :name: drag-and-drop-widget-overview-1 + + .. container:: video-container + + .. container:: iframe + + .. container:: + :name: player + + .. container:: player-unavailable + + .. rubric:: ایک خرابی پیش آ گئی۔ + :name: ایک-خرابی-پیش-آ-گئی + :class: message + + .. container:: submessage + + `اس ویڈیو کو www.youtube.com پر دیکھنے کی کوشش + کریں `__ + یا اگر آپ کے براؤزر میں JavaScript غیر فعال ہے تو + اسے فعال کریں۔ + + Watch the video to see how it works. + +.. container:: section section + :name: version-info + + .. container:: + + .. rubric:: + Module Version Information & Changelog + :name: module-version-information-changelog + :class: text-center mb-5 fw-bold + + .. container:: version-info + + - **v1.0.0** - Initial release with basic drag and drop + functionality. + - **v1.1.0** - Added support for one2many fields and improved + preview handling. + - **v1.2.0** - Enhanced UI, added zoom popup, and refined + installation instructions. + - **v1.3.0** - Bug fixes and performance improvements for large + image uploads. + - **v1.3.1** - New feature added to save dirty form to preserver new changes. + - 1: - add new key 'showConfirm' in extraData, it is bool value. it will show a confirmation dialog to ask user to save changes in form or not. + - 2: - If no option given, the default is true. + +.. container:: section section + :name: services + + .. container:: + + .. rubric:: + 90 Days Support + :name: days-support + :class: text-center mb-5 fw-bold + + .. container:: row text-center + + .. container:: col-md-4 mb-3 + + .. container:: service-tile + + .. rubric:: Website + :name: website + + `www.expertpk.com `__ + + .. container:: col-md-4 mb-3 + + .. container:: service-tile + + .. rubric:: Email + :name: email + + support@expertpk.com + + .. container:: col-md-4 mb-3 + + .. container:: service-tile + + .. rubric:: WhatsApp + :name: whatsapp + + +92 300 7888120 + +.. container:: section py-5 bg-light + :name: services + + .. container:: + + .. rubric:: + Our Services + :name: our-services + :class: text-center mb-5 fw-bold + + .. container:: row g-4 + + .. container:: col-md-6 col-lg-3 + + .. container:: card h-100 text-center border-0 shadow-sm + + .. container:: card-body + + .. rubric:: Odoo Development + :name: odoo-development + :class: card-title + + Build custom Odoo modules and applications tailored to + your business needs. + + `Learn More <#contact>`__ + + .. container:: col-md-6 col-lg-3 + + .. container:: card h-100 text-center border-0 shadow-sm + + .. container:: card-body + + .. rubric:: Customization + :name: customization + :class: card-title + + Customize Odoo to fit your unique workflows and + business processes. + + `Learn More <#contact>`__ + + .. container:: col-md-6 col-lg-3 + + .. container:: card h-100 text-center border-0 shadow-sm + + .. container:: card-body + + .. rubric:: Upgradation + :name: upgradation + :class: card-title + + Upgrade your Odoo instance to the latest version with + zero downtime. + + `Learn More <#contact>`__ + + .. container:: col-md-6 col-lg-3 + + .. container:: card h-100 text-center border-0 shadow-sm + + .. container:: card-body + + .. rubric:: Consultancy + :name: consultancy + :class: card-title + + Get expert advice on Odoo implementation, + optimization, and best practices. + + `Learn More <#contact>`__ + +.. container:: section section + :name: other-modules + + .. container:: + + .. rubric:: Explore Our Other Modules + :name: explore-our-other-modules + + .. container:: carousel slide module-slider + :name: modulesCarousel + + .. container:: carousel-inner + + .. container:: carousel-item active + + |Module 1| + + .. container:: carousel-caption d-none d-md-block + + .. rubric:: Module 1: Advanced Reporting + :name: module-1-advanced-reporting + + .. container:: carousel-item + + |Module 2| + + .. container:: carousel-caption d-none d-md-block + + .. rubric:: Module 2: CRM Enhancements + :name: module-2-crm-enhancements + + .. container:: carousel-item + + |Module 3| + + .. container:: carousel-caption d-none d-md-block + + .. rubric:: Module 3: Inventory Optimizer + :name: module-3-inventory-optimizer + + Previous + Next + +.. container:: section section + :name: best-practices + + .. container:: + + .. rubric:: Best Practices & Troubleshooting + :name: best-practices-troubleshooting + + .. container:: tips + + .. rubric:: Best Practices + :name: best-practices + + - Always backup your database before applying new modules. + - Test the module in a staging environment prior to production + deployment. + - Ensure that the related one2many models have the required + Kanban view with mandatory fields. + - Regularly update your Odoo instance to keep up with security + and performance improvements. + + .. container:: troubleshooting + + .. rubric:: Troubleshooting Steps + :name: troubleshooting-steps + + - If image previews are not displaying, verify that your Kanban + view contains the ``name`` and ``image``/``image_1920`` + fields. + - Check the browser console for JavaScript errors related to + the widget. + - Ensure file permissions are correctly set in your addons + folder. + - Review the server logs for any errors during file upload or + service restart. + +.. container:: section section + :name: code-snippets + + .. container:: + + .. rubric:: Code Snippets + :name: code-snippets + + Below are examples of typical XML view definitions where the new + drag and drop fields are highlighted: + + .. container:: code-snippet + + .. rubric:: Single Image Widget + :name: single-image-widget + + This snippet shows how to integrate a single image drag and + drop field into an Odoo form view: + + :: + +
+ + + + + + + + + +
+ + **Explanation:** This code defines a form view for a product. + The ``product_image`` field uses the ``d_and_d_image`` widget, + which allows users to drag and drop images. The ``options`` + attribute specifies settings like image size, preview image, + accepted file extensions, and zoom functionality. + + .. container:: code-snippet + + .. rubric:: One2Many Image Widget + :name: one2many-image-widget + + This snippet demonstrates how to integrate a One2Many image + drag and drop field into an Odoo form view: + + :: + +
+ + + + + + + + + +
+ + **Explanation:** This code defines a form view for a product + with a One2Many field ``product_images``. The + ``d_and_d_images`` widget allows users to drag and drop + multiple images. The ``options`` attribute specifies the child + image field and additional CSS styles for the image display. + +© 2025 Expert IT Solutions. All rights reserved. + +.. |Logo| image:: ../static/description/img/logo.png +.. |Hero Vector| image:: ../static/description/img/hero.svg +.. |Drag and Drop Widget Overview| image:: ../static/description/img/image1.png + :class: screenshot-img +.. |One2many Field Enhancement| image:: ../static/description/img/image3.png + :class: screenshot-img +.. |image1| image:: ../static/description/img/image4.png + :class: screenshot-img +.. |image2| image:: ../static/description/img/image5.png + :class: screenshot-img +.. |image3| image:: ../static/description/img/image6.png + :class: screenshot-img +.. |Module 1| image:: ../static/description/module1.png + :class: d-block w-100 +.. |Module 2| image:: ../static/description/module2.png + :class: d-block w-100 +.. |Module 3| image:: ../static/description/module3.png + :class: d-block w-100 diff --git a/eis_drag_drop/models/__init__.py b/eis_drag_drop/models/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/eis_drag_drop/models/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/eis_drag_drop/models/models.py b/eis_drag_drop/models/models.py new file mode 100644 index 0000000..691f704 --- /dev/null +++ b/eis_drag_drop/models/models.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +################################################################################# +# Author : Expert IT Solutions () +# Copyright(c): 2012-Present Expert IT Solutions +# All Rights Reserved. +# +# This program is copyright property of the author mentioned above. +# You can`t redistribute it and/or modify it. +# +################################################################################# +from odoo import models, fields, api +from lxml import etree +import logging + +_logger = logging.getLogger(__name__) + + +class Base(models.AbstractModel): + _inherit = 'base' + + + @api.model + def get_view(self, view_id=None, view_type='form', **options): + res = super().get_view(view_id, view_type, **options) + # Parse the XML architecture from the result + doc = etree.XML(res['arch']) + if view_type == 'form': + for node in doc.xpath("//field[@widget='d_and_d_images']"): + _logger.info("Found node: %s", node.get('name')) + field_name = node.get('name') + + # Create a new field node with kanban mode and style display none + new_node = etree.Element('field', { + 'name': field_name, + 'mode': 'kanban', + 'nolabel': '1', + 'style': 'display:none; max-width: 1px; max-height: 1px;', + 'context': "{'default_name': name}", + }) + parent = node.getparent() + index = parent.index(node) + parent.insert(index - 1, new_node) + + res['arch'] = etree.tostring(doc, pretty_print=True, encoding='unicode') + return res + return res + + +class IrAttachment(models.Model): + _inherit = "ir.attachment" + + @api.model + def action_save_drag_and_drop_images(self, resModel, resId, resField, childField, fileDatas, extraData=None): + parent = self.env[resModel].browse(int(resId)) + if not parent.exists(): + _logger.error(f"Parent record not found: Model={resModel}, ID={resId}") + return False + + records_data = [] + for fileData in fileDatas: + if 'filename' in fileData and 'base64' in fileData: + record_vals = { + 'name': fileData['filename'], + childField: fileData['base64'], + } + if extraData and isinstance(extraData, dict): + filtered_data = {k: v for k, v in extraData.items() if k not in ['cssStyles', 'previewImage', 'showConfirm']} + record_vals.update(filtered_data) # Merge any extra required fields + records_data.append(record_vals) + else: + _logger.warning("Missing 'filename' or 'base64' in file data") + + if records_data: + try: + parent.write({ + resField: [(0, 0, vals) for vals in records_data] + }) + _logger.info(f"Images successfully added to {resModel} ID {resId} in field {resField}") + return True + except Exception as e: + _logger.error(f"Error updating parent record: {e}") + return False + else: + _logger.info("No valid image data provided") + return False + + diff --git a/eis_drag_drop/static/description/banner.gif b/eis_drag_drop/static/description/banner.gif new file mode 100644 index 0000000..7ca287c Binary files /dev/null and b/eis_drag_drop/static/description/banner.gif differ diff --git a/eis_drag_drop/static/description/icon.png b/eis_drag_drop/static/description/icon.png new file mode 100644 index 0000000..da699f9 Binary files /dev/null and b/eis_drag_drop/static/description/icon.png differ diff --git a/eis_drag_drop/static/description/img/hero.svg b/eis_drag_drop/static/description/img/hero.svg new file mode 100644 index 0000000..9c447c5 --- /dev/null +++ b/eis_drag_drop/static/description/img/hero.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/eis_drag_drop/static/description/img/image1.png b/eis_drag_drop/static/description/img/image1.png new file mode 100644 index 0000000..7447dab Binary files /dev/null and b/eis_drag_drop/static/description/img/image1.png differ diff --git a/eis_drag_drop/static/description/img/image2.png b/eis_drag_drop/static/description/img/image2.png new file mode 100644 index 0000000..f16a4a8 Binary files /dev/null and b/eis_drag_drop/static/description/img/image2.png differ diff --git a/eis_drag_drop/static/description/img/image3.png b/eis_drag_drop/static/description/img/image3.png new file mode 100644 index 0000000..f16a4a8 Binary files /dev/null and b/eis_drag_drop/static/description/img/image3.png differ diff --git a/eis_drag_drop/static/description/img/image4.png b/eis_drag_drop/static/description/img/image4.png new file mode 100644 index 0000000..23b600a Binary files /dev/null and b/eis_drag_drop/static/description/img/image4.png differ diff --git a/eis_drag_drop/static/description/img/image5.png b/eis_drag_drop/static/description/img/image5.png new file mode 100644 index 0000000..d1db7c3 Binary files /dev/null and b/eis_drag_drop/static/description/img/image5.png differ diff --git a/eis_drag_drop/static/description/img/image6.png b/eis_drag_drop/static/description/img/image6.png new file mode 100644 index 0000000..d9e4521 Binary files /dev/null and b/eis_drag_drop/static/description/img/image6.png differ diff --git a/eis_drag_drop/static/description/img/logo.png b/eis_drag_drop/static/description/img/logo.png new file mode 100644 index 0000000..3292671 Binary files /dev/null and b/eis_drag_drop/static/description/img/logo.png differ diff --git a/eis_drag_drop/static/description/index.html b/eis_drag_drop/static/description/index.html new file mode 100644 index 0000000..a5fb26f --- /dev/null +++ b/eis_drag_drop/static/description/index.html @@ -0,0 +1,1474 @@ + + + + + eis_drag_drop - Drag And Drop Images Module Documentation + + + + + + + + + +
+
+ Logo +
+
+ + + Community + +    + + + Enterprise + +    + + + Odoo Online + +
+
+ + +
+
+

Images Drag And Drop Widgets

+

+ Effortlessly manage and update images in your Odoo records with our + intuitive drag and drop widgets. +

+
+ Hero Vector +
+
+ + Perfect for E-commerce, Media Portals + & More! +
+
+
+ + +
+
+

+ +
+ Introduction +

+

+ The + Images Drag And Drop Widgets + module is a + state-of-the-art solution designed to enhance your Odoo experience by + integrating modern, user-friendly drag and drop image widgets. +

+

Who Can Benefit?

+

+ Whether you run an e-commerce site, a media portal, a real estate + listing platform, or any business that requires the handling of + multiple images per record, this module is built for you. Enjoy: +

+
    +
  • + E-commerce websites: + Easily manage product images + with a simple drag and drop interface. +
  • +
  • + Businesses with heavy media content: + Quickly update + portfolios, galleries, and listings. +
  • +
  • + Developers and Designers: + Seamlessly integrate + modern widgets across all modules—no extra dependency required! +
  • +
+

Use Cases

+

+ Online Stores: + Showcase products with multiple images + that can be easily updated on-the-fly. +

+

+ Real Estate Platforms: + Effortlessly manage property + photos, floor plans, and virtual tours. +

+

+ Agencies & Portfolios: + Create visually appealing + portfolios where media speaks louder than words. +

+

Benefits

+

+ With our module, say goodbye to tedious file uploads and hello to a + more engaging user experience. It’s simple, robust, and designed to + boost your productivity! +

+
+
+ + +
+
+

+ +
+ Installation +

+

+ Follow these detailed steps to install the + eis_drag_drop + module: +

+
    +
  1. + Download the Module: + Obtain the module as a ZIP + file from the official repository or your vendor. +
  2. +
  3. + Extract the ZIP: + Unzip the downloaded file to + reveal the module folder. +
  4. +
  5. + Copy to Addons Folder: + Move or copy the extracted + module folder into your Odoo + addons + directory. +
  6. +
  7. + Restart Odoo Service: +
      +
    • + On systemd-based systems: + sudo systemctl restart odoo +
    • +
    • + On SysVinit-based systems: + sudo service odoo restart +
    • +
    • Or use the appropriate command for your OS.
    • +
    +
  8. +
  9. + Update App List: + In your Odoo database, navigate to + the Apps menu and click Update Apps List. +
  10. +
  11. + Install the Module: + Search for + Drag and Drop Widgets + (or eis_drag_drop) and + click install. +
  12. +
+
+ Important: + Always test the module in a staging + environment before deploying it to your production server. Caution is + key—ensure you have backups and proper rollback plans in place. +
+
+
+ + +
+
+

+ +
+ User Guide +

+ +

+ + Updating Existing Image Fields +

+

+ You can update any existing binary image field to use our drag and + drop widget without altering your custom modules. Simply update your + XML views by setting the widget attribute to: +

+
+
+                        <field name="image_field" widget="d_and_d_image" options="{
+                            "image_size": "150x150",
+                            "preview_image": "image_128",
+                            "acceptedFileExtensions": "image/*",
+                            "enableZoom": true,
+                            "additionalStyles": "border:2px solid #007bff;"
+                            }>
+                            </field>
+                        
+                    
+
+

+ Options Explained: +

+
    +
  • + image_size: Defines the display size of the widget. + Example: "150x150" sets the width to 150px and height to 150px. +
  • +
  • + preview_image: Specifies the field used for the image + preview. If not set, it defaults to the binary field name. +
  • +
  • + acceptedFileExtensions: Determines which image formats + can be uploaded. +
  • +
  • + enableZoom: If true, hovering over the image displays a + zoom popup. +
  • +
  • + additionalStyles: Custom CSS styles that are appended + to the widget's inline styles. +
  • +
+ +

+ + Detailed O2M Setup & Custom Integration +

+

+ For One2Many fields, our widget transforms the standard image upload + into a dynamic drag and drop area. + Note: + The related model + must + have a Kanban + view defined that includes at least the + name + and + image + (or image_1920) fields. +

+

+ Why a Kanban View? + The Kanban view enables the widget + to render image previews instead of just record IDs. Without it, you + may only see numerical identifiers. +

+

+ Form View for Videos & Manual Entry: + If you wish to + add videos or manually add images in the traditional way, ensure that + your One2Many field is also accessible via a form view. +

+ +

Example of One2Many Field Integration

+
+
+                        <page name="media" string="Extra Media">
+                            <field name="media_ids" widget="d_and_d_images"
+                            options="{
+                            'childImageField': 'image_1920',
+                            'extraData': {
+                            'categ_id': 1,
+                            'enable_zoom': true,
+                            'previewImage': 'image_128',
+                            'cssStyles': 'width:150px; height:200px; border-radius:15px;'
+                            }
+                            }"/>
+                            </page>
+                        
+                    
+
+ +

Options Explained:

+
    +
  • + childImageField: Specifies the field in the child model + where the binary image data is stored. This is mandatory for the + widget to function correctly. If not specified, image_1920 will be + used. +
  • +
  • + extraData: Allows you to pass additional data to the + child records. This can include: +
      +
    • + categ_id: An example field that sets a default + category ID. This feature allows you to add any additional field + and its value if your model has a required field and without it, + it cannot create new records, or a field that you need to fill + with static data. +
    • +
    • + sale_ok: This is another example field and you can + pass any bool value if your required field is bool type +
    • +
    • + previewImage: Specifies a smaller image field to + use for previews, which is important if you want to show a + smaller size image to save bandwidth. This value should be used + if your model is inherited from + image.mixin + and you + want to display a smaller preview image. +
    • +
    • + cssStyles: Custom CSS styles for the image display. + Example: + "width:150px; height:200px; border-radius:15px; + object-fit:cover;". +
    • +
    +
  • +
  • + acceptedFileExtensions: Determines which file types are + acceptable for upload. Default is "image/*". +
  • +
+
+
+ + +
+
+

+ +
+ User Guide +

+ +

+ + Updating Existing Image Fields +

+

+ You can update any existing binary image field to use our drag and + drop widget without altering your custom modules. Simply update your + XML views by setting the widget attribute to: +

+
+
+                        <field name="image_field" widget="d_and_d_image" options="{
+                            "image_size": "150x150",
+                            "preview_image": "image_128",
+                            "acceptedFileExtensions": "image/*",
+                            "enableZoom": true,
+                            "additionalStyles": "border:2px solid #007bff;"
+                            }>
+                            </field>
+                        
+                    
+
+

+ Options Explained: +

+
    +
  • + image_size: Defines the display size of the widget. + Example: "150x150" sets the width to 150px and height to 150px. +
  • +
  • + preview_image: Specifies the field used for the image + preview. If not set, it defaults to the binary field name. +
  • +
  • + acceptedFileExtensions: Determines which image formats + can be uploaded. +
  • +
  • + enableZoom: If true, hovering over the image displays a + zoom popup. +
  • +
  • + additionalStyles: Custom CSS styles that are appended + to the widget's inline styles. +
  • +
+ +

+ + Detailed O2M Setup & Custom Integration +

+

+ For One2Many fields, our widget transforms the standard image upload + into a dynamic drag and drop area. + Note: + The related + model + must + have a Kanban view defined that includes at least + the + name + and + image + (or + image_1920) fields. +

+

+ Why a Kanban View? + The Kanban view enables the widget + to render image previews instead of just record IDs. Without it, you + may only see numerical identifiers. +

+

+ Form View for Videos & Manual Entry: + If you wish to + add videos or manually add images in the traditional way, ensure that + your One2Many field is also accessible via a form view. +

+ +

Example of One2Many Field Integration

+
+
+                        <page name="media" string="Extra Media">
+                            <field name="media_ids" widget="d_and_d_images"
+                            options="{
+                            'childImageField': 'image_1920',
+                            'extraData': {
+                            'categ_id': 1,
+                            'enable_zoom': true,
+                            'previewImage': 'image_128',
+                            'cssStyles': 'width:150px; height:200px; border-radius:15px;'
+                            }
+                            }"/>
+                            </page>
+                        
+                    
+
+ +

Options Explained:

+
    +
  • + childImageField: Specifies the field in the child model + where the binary image data is stored. This is mandatory for the + widget to function correctly. If not specified, + image_1920 + will be used. +
  • +
  • + extraData: Allows you to pass additional data to the + child records. This can include: +
      +
    • + categ_id: An example field that sets a default + category ID. This feature allows you to add any additional field + and its value if your model has a required field and without it, + it cannot create new records, or a field that you need to fill + with static data. +
    • +
    • + sale_ok: This is another example field and you can + pass any bool value if your required field is bool type. +
    • +
    • + previewImage: Specifies a smaller image field to + use for previews, which is important if you want to show a + smaller size image to save bandwidth. This value should be used + if your model is inherited from + image.mixin + and you + want to display a smaller preview image. +
    • +
    • + cssStyles: Custom CSS styles for the image display. + Example: + "width:150px; height:200px; border-radius:15px; + object-fit:cover;". +
    • +
    +
  • +
  • + acceptedFileExtensions: Determines which file types are + acceptable for upload. Default is "image/*". +
  • +
+ + + +
+

+ Complete Example (Taken from Odoo builtin Code): +
+

+
Adding Multiple Image Support to + product.template +
+

+ Let's say we have a model + product.template + and we need to + add multiple image support to it. We will create a model + product.image + by inheriting from the + image.mixin + class. This way, we automatically get fields + like + image_1920 + and other related fields for our model + product.image. +

+

Here are the steps to achieve this:

+
    +
  1. + Create the + product.image + Model: + +

    + Inherit from + image.mixin + to automatically get + image-related fields. +

    +
    +
    +                                class ProductImage(models.Model):
    +                                    _name = 'product.image'
    +                                    _inherit = 'image.mixin'
    +
    +                                    product_tmpl_id = fields.Many2one('product.template', string='Product Template')
    +                                    sequence = fields.Integer('Sequence')
    +                                    video_url = fields.Char('Video URL')
    +                                
    +                            
    +
    +
  2. +
  3. + Extend the + product.template + Model: + +

    Add a One2Many field to link multiple images.

    +
    +
    +                                class ProductTemplate(models.Model):
    +                                    _inherit = 'product.template'
    +
    +                                    product_template_image_ids = fields.One2many('product.image', 'product_tmpl_id',
    +                                    string='Images')
    +                                
    +                            
    +
    +
  4. +
  5. + Create a Kanban View for product.image: + +

    Add a Kanban view with the necessary fields.

    +
    +
    +                                <record id="view_product_image_kanban" model="ir.ui.view">
    +                                    <field name="name">product.image.kanban</field>
    +                                    <field name="model">product.image</field>
    +                                    <field name="arch" type="xml">
    +                                    <kanban>
    +                                    <field name="id"/>
    +                                    <field name="name"/>
    +                                    <field name="image_1920" widget="image"/>
    +                                    <field name="sequence"/>
    +                                    </kanban>
    +                                    </field>
    +                                    </record>
    +                                
    +                            
    +
    +
  6. +
  7. + Create a Form View for product.image: +

    Add a form view with fields for video URL and image.

    +
    +
    +                                <record id="view_product_image_form" model="ir.ui.view">
    +                                    <field name="name">product.image.form</field>
    +                                    <field name="model">product.image</field>
    +                                    <field name="arch" type="xml">
    +                                    <form>
    +                                    <field name="id"/>
    +                                    <field name="name"/>
    +                                    <field name="video_url"/>
    +                                    <field name="image_1920" widget="image"/>
    +                                    </form>
    +                                    </field>
    +                                    </record>
    +                                
    +                            
    +
    +
  8. +
  9. + Update the + product.template + Form View: + +

    Add the One2Many field with the drag-and-drop widget.

    +
    +
    +                                <record id="view_product_template_form" model="ir.ui.view">
    +                                    <field name="name">product.template.form</field>
    +                                    <field name="model">product.template</field>
    +                                    <field name="inherit_id" ref="product.product_template_only_form_view"/>
    +                                    <field name="arch" type="xml">
    +                                    <form>
    +                                    <sheet>
    +                                    <group>
    +                                    <field name="product_template_image_ids" widget="d_and_d_images" options="{
    +                                    'childImageField': 'image_1920',
    +                                    'extraData': {
    +                                    'cssStyles': 'width:150px;height:150px;'
    +                                    }
    +                                    }"/>
    +                                    </group>
    +                                    </sheet>
    +                                    </form>
    +                                    </field>
    +                                    </record>
    +                                
    +                            
    +
    +
  10. +
+ +
+ Important Considerations for Product Image Management (Odoo 17 and 18) + +

This guide outlines the necessary steps for integrating d_and_d_images widget for product + template, specifically when using the + website_sale + module, across Odoo 17 and 18. Please carefully follow the instructions for your respective Odoo + version to avoid conflicts and ensure proper functionality. +

+ +

+ Odoo 17: +

    +
  1. + Do not duplicate the + product.image + model or its views. + + This model is already provided by the + website_sale + module in Odoo. Creating a duplicate will lead to conflicts. +
  2. +
  3. + Inherit the + product.template + form view. + + . You will modify the form view + product.product_template_form_view + to integrate d_and_d_images customizations seamlessly with Odoo's core functionality. +
  4. +
  5. + Customize the image field: + Replace the + product_template_image_ids + field in the inherited form view with d_and_d_images widget as described above to enhance + the product image management functionality. +
  6. +
+

+ +

+ Odoo 18: +

Odoo 18 has modified the + product.image + Kanban view by removing certain fields. So, to make it compatible again with our d_and_d_images + widghet, here's how to adjust Odoo 18: +

+
    +
  1. + Inherit the + product.image + Kanban view. + + website_sale.product_image_view_kanban. Add the necessary + id + and + name + fields back into the view to maintain existing functionality. That's it. +
  2. +
    +
  3. + Enable modifications through the module's manifest: +
      +
    • Declare dependency on + website_sale + in our eis_drag_drop module's + __manifest__.py + file to ensure proper loading order. You jsut need to uncomment the line having + website_sale with key depends and then comment the existing depend key. +
    • +
    • Reference the XML file containing your Kanban view modifications in the + data + key of the manifest to apply these changes automatically when your module is + installed or updated. +
    • +
    +
  4. +
  5. + Alternative approach: + Manually define the Kanban view inheritance in a + product.xml + file if you prefer more direct control over the modifications. +
  6. +
+

+ +

Our module has pre-configured these enhancements for you. To activate them, modify the + __manifest__.py + file to uncomment the dependency on + website_sale + and the reference to + /views/product.xml + in the + data + key. This ensures all functionalities are seamlessly integrated when you install or update the + module. +

+
+ +
+
+ + +
+
+

+ +
+ Screenshots Explaination +

+ + +
+
01
+

Drag and Drop Widget Overview

+ Drag and Drop Widget Overview +

+ A clear view of the drag and drop widget areas for single binary + image and for one2many images., allowing users to effortlessly + update images. +

+
+ + +
+
02
+

One2Many Drag And Drop Explained

+ One2many Field Enhancement +

+ In this screenshot, you can see a simple explaination of the view. + Each record's thumbnail, have a small cross on top right corner to + remove a record, view also display video thumbnail, and buttons. +

+
+ +
+
04
+

Add Video Button

+ One2many Field Enhancement +

+ This screenshot illustrates how the one2many field widget + 'Add Video' + button is working. You can access odoo + builtin method being used in website_sale module of odoo to add + extra media including videos or images. +

+
+ + +
+
05
+

Manual File Uploding Button Display

+ One2many Field Enhancement +

+ This screenshot illustrates how the one2many field widget manual + upload button is working. If you want to upload images manually from + file uploader, you can click on this button and it will open finder + / file explorer for you. It will only allow images to upload with + jpg, jpeg, png, svg etc +

+
+ + +
+
06
+

Iamges Dragging over One2many Field Display

+ One2many Field Enhancement +

+ This screenshot illustrates how the one2many field is being used to + drop 4 images into drag drop area. Notice the blue border that is + highlighted when we dragged the images over it. As soon as we will + drop the images, they will be uploaded automatically +

+
+
+
+
+
+

+ +
+ Video Explaination +

+
+
01
+

Drag and Drop Widget Overview

+ +
+ +
+

Watch the video to see how it works.

+
+
+
+ + +
+
+

+ +
+ Module Version Information & Changelog +

+ +
+
    +
  • + v1.0.0 + - Initial release with basic drag and drop + functionality. +
  • +
  • + v1.1.0 + - Added support for one2many fields and + improved preview handling. +
  • +
  • + v1.2.0 + - Enhanced UI, added zoom popup, and + refined installation instructions. +
  • +
  • + v1.3.0 + - Bug fixes and performance improvements + for large image uploads. +
  • +
  • + + v1.3.1 +
      +
    • Fixes the situation where the form is dirty and d_and_d_images was discarding changes. in this version it will save the changes. +
    • A new option added 'showConfirm' in multi image widget d_and_d_images. It is bool + field and it will show a configuration dialog to ask for saving changes if form is + dirty. the extraData should contain a key 'showConfirm': true, if not provided or is false, the form will saved without confirmation. +
    • +
    • If not passed, default is to save dirty form to preserve changes.
    • +
    +
  • +
+
+
+
+ + +
+
+

+ +
+ 90 Days Support +

+
+
+
+ +
Website
+

+ www.expertpk.com + +

+
+
+
+
+ +
Email
+

+ support@expertpk.com +

+
+
+
+
+ +
WhatsApp
+

+92 300 7888120

+
+
+
+
+
+ + +
+
+

+ +
+ Our Services +

+
+ +
+
+
+ +
Odoo Development
+

+ Build custom Odoo modules and applications tailored to your + business needs. +

+ + Learn More + + +
+
+
+ + +
+
+
+ +
Customization
+

+ Customize Odoo to fit your unique workflows and business + processes. +

+ + Learn More + + +
+
+
+ + +
+
+
+ +
Upgradation
+

+ Upgrade your Odoo instance to the latest version with zero + downtime. +

+ + Learn More + + +
+
+
+ + +
+
+
+ +
Consultancy
+

+ Get expert advice on Odoo implementation, optimization, and + best practices. +

+ + Learn More + + +
+
+
+
+
+
+ + +
+
+

Explore Our Other Modules

+ +
+
+ + +
+
+

Best Practices & Troubleshooting

+
+
Best Practices
+
    +
  • Always backup your database before applying new modules.
  • +
  • + Test the module in a staging environment prior to production + deployment. +
  • +
  • + Ensure that the related one2many models have the required Kanban + view with mandatory fields. +
  • +
  • + Regularly update your Odoo instance to keep up with security and + performance improvements. +
  • +
+
+
+
Troubleshooting Steps
+
    +
  • + If image previews are not displaying, verify that your Kanban view + contains the + name + and image/ + image_1920 + + fields. +
  • +
  • + Check the browser console for JavaScript errors related to the + widget. +
  • +
  • + Ensure file permissions are correctly set in your addons folder. +
  • +
  • + Review the server logs for any errors during file upload or + service restart. +
  • +
+
+
+
+ + +
+
+

Code Snippets

+

+ Below are examples of typical XML view definitions where the new drag + and drop fields are highlighted: +

+ + +
+

Single Image Widget

+

+ This snippet shows how to integrate a single image drag and drop + field into an Odoo form view: +

+
+                        <form string="Product Form">
+                            <sheet>
+                            <group>
+                            <field name="name" />
+                            <field name="description" />
+                            <!-- Our new drag and drop image field is highlighted below -->
+                             <field name="image_1920" widget="d_and_d_image"
+                                class="oe_avator" options="{
+                                "image_size": "200x200",
+                                "preview_image": "image_128",
+                                "acceptedFileExtensions": "image/*",
+                                "enableZoom": true
+                                }" />
+                                <field name="price" />
+                            
+                            </group>
+                            </sheet>
+                            </form>
+                        
+                    
+

+ Explanation: + This code defines a form view for a + product. The + product_image + field uses the + d_and_d_image + widget, which allows users to drag and + drop images. The + options + attribute specifies settings + like image size, preview image, accepted file extensions, and zoom + functionality. +

+
+ + +
+

One2Many Image Widget

+

+ This snippet demonstrates how to integrate a One2Many image drag and + drop field into an Odoo form view: +

+
+                        <form string="Product Form">
+                            <sheet>
+                            <group>
+                            <field name="name" />
+                            <field name="description" />
+                            <!-- One2Many drag and drop image field -->
+                             <field name="product_images" widget="d_and_d_images"
+                                options="{
+                                "childImageField": "image_1920",
+                                "extraData": {
+                                "cssStyles": "width:150px;height:150px;"
+                                }
+                                }" />
+                                <field name="price" />
+                            
+                            </group>
+                            </sheet>
+                            </form>
+                        
+                    
+

+ Explanation: + This code defines a form view for a + product with a One2Many field product_images. The + d_and_d_images + widget allows users to drag and drop + multiple images. The + options + attribute specifies the + child image field and additional CSS styles for the image display. +

+
+
+
+ + +
+ © 2025 Expert IT Solutions. All rights reserved. +
+ + + + + diff --git a/eis_drag_drop/static/src/css/style.scss b/eis_drag_drop/static/src/css/style.scss new file mode 100644 index 0000000..50baf17 --- /dev/null +++ b/eis_drag_drop/static/src/css/style.scss @@ -0,0 +1,49 @@ +.o_dnd_images_widget { + width: 100%; + .o_dnd_drop_zone { + width: 100%; + min-height: 150px; + border: 2px dashed #ccc; + border-radius: 4px; + text-align: center; + position: relative; + transition: transform 0.2s ease-in-out; + .o_drop_area_text { + margin-top: 1em; + color: #999; + } + } + .o_dnd_drop_zone.o_dnd_dragging { + border: 3px dashed #007bff; + transform: scale(1.02); + background-color: #f8f9fa; + } + .o_dnd_uploading { + color: #666; + } + .o_dnd_kanban_container { + display: flex; + flex-wrap: wrap; + gap: 1rem; + } + .o_dnd_kanban_card { + border: 1px solid #ddd; + border-radius: 4px; + width: 120px; + overflow: hidden; + .o_card_body { + position: relative; + .o_dnd_kanban_image { + width: 100%; + height: auto; + display: block; + } + .o_dnd_kanban_info { + padding: 4px; + background: #f2f2f2; + font-size: 0.8rem; + text-align: center; + } + } + } +} diff --git a/eis_drag_drop/static/src/css/styles.css b/eis_drag_drop/static/src/css/styles.css new file mode 100644 index 0000000..a0335ec --- /dev/null +++ b/eis_drag_drop/static/src/css/styles.css @@ -0,0 +1,249 @@ +/* Enforce 100% width for the drag-and-drop zone */ +.o_dnd_images_widget { + width: 100%; + min-width: 100vh; +} +.o_dnd_drop_zone { + width: 100%; /* Force the container to expand to 100% width */ + min-height: 150px; /* Minimum height */ + border: 2px dashed #ccc; /* Dashed border */ + border-radius: 4px; /* Rounded corners */ + text-align: center; /* Center any child content */ + position: relative; /* Required for overlays/effects */ + transition: all 0.2s ease-in-out; /* Smooth interactions */ + background-color: #f8f8f8; /* Set a light background color so it's clear this area is the drop zone */ + box-sizing: border-box; /* Includes padding/border in layout */ +} + +/* Ensure the drop zone responds to drag events fully, not only the border */ +.o_dnd_drop_zone:hover, +.o_dnd_drop_zone.o_dnd_dragging { + background-color: #eef7ff; /* Highlight background during dragging */ + border-color: #007bff; /* Highlight border color */ + transform: scale(1.02); /* Slight zoom effect */ +} + +/* Add padding inside the drag-and-drop container */ +.o_dnd_drop_zone .o_dnd_kanban_container { + padding: 10px; /* Add spacing around the inner content */ + display: flex; /* Flex container for items inside drop zone */ + flex-wrap: wrap; /* Allow items to wrap to new rows */ + gap: 15px; /* Consistent spacing between tiles */ + justify-content: flex-start; /* Items start aligned to the left */ + align-items: flex-start; /* Top-aligned items */ +} + +/* Ensure the drop zone always maintains its design, even when empty */ +.o_dnd_drop_zone_empty { + display: flex; /* Center the "empty" placeholder */ + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically */ + width: 100%; + height: 100%; /* Take full available drop zone space */ + color: #ccc; + font-size: 16px; +} + +/* Flex-based item cards/tile styles */ +.o_dnd_kanban_card { + width: 120px; + height: 120px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #fff; /* White background for cards */ + border: 1px solid #ddd; /* Subtle card border */ + border-radius: 4px; /* Rounded corners */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */ + position: relative; /* Required for close button positioning */ +} + +/* Close button inside cards */ +.o_dnd_kanban_card .close-button { + position: absolute; + top: -5px; + right: -5px; + background-color: red; + border: none; + color: white; + width: 20px; + height: 20px; + border-radius: 50%; + cursor: pointer; + font-size: 12px; +} + +/* Manual upload tile */ +.o_image_tile_upload { + background-color: #f8f9fa; /* Light gray background */ + border: 2px dashed #ccc; /* Dashed border for upload tile */ + width: 120px; /* Same size as other tiles */ + height: 120px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + transition: all 0.15s ease-in-out; /* Hover effect */ +} + +.o_image_tile_upload:hover { + border-color: #007bff; /* Highlight border on hover */ + background-color: #eef7ff; /* Subtle highlight background */ +} + +/* Ensure the upload indicator displays properly */ +.o_dnd_uploading { + color: #666; + text-align: center; + margin-top: 20px; +} + +/* single image styles below */ +.dnd_widget_container { + position: relative; + margin: 10px; +} + +.dnd_drop_zone { + position: relative; + border: 2px dashed #ccc; + border-radius: 8px; + padding: 10px; + text-align: center; + transition: border-color 0.3s; +} + +.dnd_active_drag { + border-color: #007bff; +} + +/* Controls hidden initially */ +.dnd_image_controls { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + padding: 10px; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; +} + +.dnd_drop_zone:hover .dnd_image_controls { + pointer-events: auto; + opacity: 1; +} + +/* Edit Button (Top Left) */ +.dnd_edit_button { + position: absolute; + top: 10px; + left: 10px; +} + +/* Upload Button (Top Right) */ +.dnd_upload_button { + position: absolute; + top: 10px; + right: 10px; +} + +/* Remove Button (Bottom Left) */ +.dnd_remove_button { + position: absolute; + bottom: 10px; + left: 10px; +} + +.dnd_button { + background-color: white; + border: 1px solid #ccc; + border-radius: 50%; + padding: 5px; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 2; +} + +.dnd_button:hover { + background-color: #f9f9f9; +} + +/* Zoom popup remains unaffected */ +.dnd_zoom_popup { + position: absolute; + z-index: 9999; + transition: all 0.3s ease-in-out; +} + +.dnd_image_preview { + display: block; + object-fit: cover; + border-radius: 5px; + transition: transform 0.3s ease; +} + +.dnd_image_placeholder { + color: #aaa; + font-size: 14px; + text-align: center; +} + +.dnd_uploading_spinner { + margin-top: 10px; + text-align: center; + color: #007bff; +} + +/* Style for the zoomed image */ +.dnd_zoom_popup { + display: flex; /* Use flexbox for centering the image */ + justify-content: center; + align-items: flex-start; /* Ensure the image aligns at the top inside the popup */ + position: absolute; /* Position the popup relative to the original element */ + /*top: 0; !* Ensure it aligns properly from the top *!*/ + /*margin-top: 40px;*/ + left: -110%; /* Place the popup to the left of the original block */ + z-index: 9999; /* Ensure popup appears above other elements */ + background-color: #fff; /* Add a background for better contrast */ + width: auto; /* Fixed width of 720p resolution */ + height: auto; /* Fixed height of 720p resolution */ + padding: 10px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Add shadow for better visibility */ + border: 2px solid #007bff; /* Blue border for aesthetic */ + max-width: none; + max-height: none; + + /* Add scrollable behavior for content that overflows the fixed size */ + overflow: auto; /* Enable scrolling for overflow content */ + visibility: hidden; /* Hidden by default */ + opacity: 0; /* Initial opacity set to 0 */ + transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; +} + +/* When zoom popup is visible */ +.dnd_zoom_popup.show { + visibility: visible; /* Make visible */ + opacity: 1; /* Add fade-in effect */ +} + +/* Zoomed image styling */ +.dnd_zoomed_image { + display: block; + /* Ensure the image expands or scrolls within the popup */ + max-width: 100%; /* Resize image width to fit popup */ + max-height: 100%; /* Resize image height to fit popup */ + width: auto; + height: auto; + object-fit: contain; /* Maintain the aspect ratio of the image */ + border-radius: 10px; /* Add border radius for aesthetics */ +} +/* single image styles above */ \ No newline at end of file diff --git a/eis_drag_drop/static/src/js/dnd_image_widget.js b/eis_drag_drop/static/src/js/dnd_image_widget.js new file mode 100644 index 0000000..d10d248 --- /dev/null +++ b/eis_drag_drop/static/src/js/dnd_image_widget.js @@ -0,0 +1,233 @@ +/** @odoo-module **/ + +import {registry} from "@web/core/registry"; +import {_t} from "@web/core/l10n/translation"; +import {FileUploader} from "@web/views/fields/file_handler"; +import {standardFieldProps} from "@web/views/fields/standard_field_props"; +import {url} from "@web/core/utils/urls"; +import {isBinarySize} from "@web/core/utils/binary"; +import {useService} from "@web/core/utils/hooks"; // optional if you want notifications +import {Component, useState, useRef} from "@odoo/owl"; +import {onWillUpdateProps} from "@odoo/owl"; + +/** + * Enhanced single-image drag-and-drop widget with custom image size and preview features. + */ + +const placeholder = "/web/static/img/placeholder.png"; +export class DndImageWidget extends Component { + static template = "eis_drag_drop.DndImageWidgetTemplate"; + static components = {FileUploader}; + static props = { + ...standardFieldProps, + acceptedFileExtensions: {type: String, optional: true}, + image_size: {type: String, optional: true}, // Example: "100x150" + preview_image: {type: String, optional: true}, // Example: "image_128" + enableZoom: {type: Boolean, optional: true}, // Optional zoom functionality + additionalStyles: {type: String, optional: true}, // Append custom styles + }; + static defaultProps = { + acceptedFileExtensions: "image/*", + image_size: "100x150", + }; + + setup() { + this.notification = useService("notification"); + this.isUploading = useState({value: false}); + this.isDraggingOver = useState({value: false}); + this.dropZoneRef = useRef("dropZoneRef"); + this.zoomTimer = null; // Timer for delayed zoom + this.showZoomPopup = useState({ value: false }); // Popup state + + // Extract width/height from image_size + const [width, height] = (this.props.image_size || "").split("x").map((value) => parseInt(value)); + this.imageWidth = isNaN(width) ? null : width; + this.imageHeight = isNaN(height) ? null : height; + + this.cacheKey = this.props.record.data.write_date; + onWillUpdateProps((nextProps) => { + const {record} = this.props; + const {record: nextRecord} = nextProps; + if (record.resId !== nextRecord.resId || nextRecord.mode === "readonly") { + this.cacheKey = nextRecord.data.write_date; + } + }); + } + + //-------------------------------------------------------------------------- + // Event Methods + //-------------------------------------------------------------------------- + // Handles hover on image (shows zoom popup after 1 second) + onMouseEnter() { + if (this.props.enableZoom) { + this.zoomTimer = setTimeout(() => { + this.showZoomPopup.value = true; // Display popup + }, 1000); // Trigger after 1-second hover + } + } + + // Handles mouse leave (hides zoom popup) + onMouseLeave() { + if (this.zoomTimer) { + clearTimeout(this.zoomTimer); // Cancel timeout if mouse leaves + this.zoomTimer = null; + } + this.showZoomPopup.value = false; // Hide popup + } + + // Prevents the popup from hiding when interacting within it + onMouseEnterPopup() { + this.showZoomPopup.value = true; // Ensure visibility on hover within the popup + } + + onMouseLeavePopup() { + this.showZoomPopup.value = false; // Hide popup when mouse leaves + } + + onDownloadOriginalImage() { + const anchor = document.createElement("a"); + anchor.href = this.originalImageUrl; + anchor.download = "original_image"; // Default filename + anchor.click(); + console.log("[DndImageWidget] Download initiated:", this.originalImageUrl); + } + + // Returns the URL for the original image (full resolution) + get originalImageUrl() { + return url("/web/image", { + model: this.props.record.resModel, + id: this.props.record.resId, + field: this.props.name, // Always load original image + unique: this.cacheKey || "", + }); + } + + get imageUrl() { + const bin = this.props.record.data[this.props.name]; + console.log('bin: ', bin); + if (!bin) return placeholder; + if (isBinarySize(bin)) { + let recUrl = url("/web/image", { + model: this.props.record.resModel, + id: this.props.record.resId, + field: this.props.preview_image || this.props.name, + unique: this.cacheKey || "", + }); + console.log('recUrl: ', recUrl); + return recUrl; + } + return `data:image/png;base64,${bin}`; + } + + get imageStyle() { + let style = this.isDraggingOver.value ? "border: 2px dashed #007bff;" : ""; // Dashed border on drag + if (this.imageWidth) { + style += `width: ${this.imageWidth}px;`; + } + if (this.imageHeight) { + style += `height: ${this.imageHeight}px;`; + } else { + style += "max-width: 100%; max-height: 100%;"; + } + if (this.props.additionalStyles) { + style += this.props.additionalStyles; // Append user-defined styles + } + style += 'object-fit: cover;'; + return style; + } + // Refresh the image URL when a new image is uploaded or replaced + refreshImage() { + this.cacheKey = Date.now(); // Generate a unique cache key to force image reloading + this.render(); // Trigger re-render to update the image + } + + + //-------------------------------------------------------------------------- + // Event Handlers + //-------------------------------------------------------------------------- + onDrop(ev) { + ev.preventDefault(); + this.isDraggingOver.value = false; + const file = ev.dataTransfer.files[0]; + if (!file) return; + this._readFileAsDataUrl(file); + } + + onDragOver(ev) { + ev.preventDefault(); + } + + onDragEnter(ev) { + ev.preventDefault(); + this.isDraggingOver.value = true; + } + + onDragLeave(ev) { + ev.preventDefault(); + this.isDraggingOver.value = false; + } + + async _readFileAsDataUrl(file) { + this.isUploading.value = true; + const reader = new FileReader(); + reader.onload = (e) => { + const dataUrl = e.target.result; + this._updateRecordWithBase64(dataUrl); + this.refreshImage(); // Call refreshImage to update the displayed image + }; + reader.readAsDataURL(file); + } + + _updateRecordWithBase64(dataUrl) { + const base64Part = dataUrl.split(",")[1] || ""; + this.props.record.update({[this.props.name]: base64Part}); + this.isUploading.value = false; + } + + onFileRemove() { + this.props.record.update({ [this.props.name]: false }); + this.refreshImage(); // Refresh the image if it is removed + } + + onLoadFailed() { + this.notification?.add(_t("Could not display the selected image"), { + type: "warning", + }); + } + + onFileUploaded(info) { + if (info.data) { + const mimeSuffix = info.type.split("/")[1] || "png"; + const dataUrl = `data:image/${mimeSuffix};base64,${info.data}`; + this._updateRecordWithBase64(dataUrl); + + // Refresh the zoomed image after file upload + this.refreshImage(); + } + } + + onClickImage() { + // Open image in a new tab + window.open(this.originalImageUrl, "_blank"); + } +} + +// Debugging the extractProps logic +const dndImageField = { + component: DndImageWidget, + displayName: _t("D&D Single Image"), + supportedTypes: ["binary"], + extractProps: ({attrs, options}) => { + console.log("[DndImageWidget] extractProps invoked - attrs:", attrs, "options:", options); + + return { + image_size: options.image_size || "100x120", // Fallback to "100x150" + preview_image: options.preview_image || attrs.name, // Fallback to field name + acceptedFileExtensions: options.acceptedFileExtensions || "image/*", // Default to all image types + enableZoom: options.enableZoom === "true" || false, // Ensure boolean value + }; + }, +}; + +// Register the widget +registry.category("fields").add("d_and_d_image", dndImageField); \ No newline at end of file diff --git a/eis_drag_drop/static/src/js/dnd_images_widget.js b/eis_drag_drop/static/src/js/dnd_images_widget.js new file mode 100644 index 0000000..ea8b52a --- /dev/null +++ b/eis_drag_drop/static/src/js/dnd_images_widget.js @@ -0,0 +1,254 @@ +/** @odoo-module **/ +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; +import { FileUploader } from "@web/views/fields/file_handler"; +import { Component, useState, useRef } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; + +export class DndImagesWidget extends Component { + static template = "eis_drag_drop.DndImagesWidgetTemplate"; + static components = { FileUploader }; + static props = { + ...standardFieldProps, + childImageField: { type: String, optional: true }, + extraData: { type: Object, optional: true }, + }; + + setup() { + super.setup(); + this.orm = useService("orm"); + this.action = useService("action"); + this.notification = useService("notification"); + this.isUploading = useState({ value: false }); + this.isDragging = useState({ value: false }); + this.dropZoneRef = useRef("dropZoneRef"); + // If one2many not loaded, load it explicitly + if (!this.props.record.data[this.props.name]) { + this.props.record.ensureOne2manyRecords(this.props.name); + } + } + + onDragOver(ev) { + ev.preventDefault(); + } + onDragEnter(ev) { + ev.preventDefault(); + this.isDragging.value = true; + } + onDragLeave(ev) { + ev.preventDefault(); + this.isDragging.value = false; + } + async onDrop(ev) { + ev.preventDefault(); + this.isDragging.value = false; + const files = ev.dataTransfer.files || []; + if (!files.length) { + return; + } + await this._handleMultipleFiles(files); + } + + async _handleMultipleFiles(files) { + this.isUploading.value = true; + try { + const extraData = this.props.extraData || {}; + // If parent record is dirty, confirm or just save + if (this.props.record.isDirty) { + if (extraData.showConfirm) { + const confirmed = await new Promise(resolve => { + this.env.services.dialog.add(ConfirmationDialog, { + body: _t("The record has unsaved changes. Do you want to save them?"), + confirm: () => resolve(true), + cancel: () => resolve(false), + }); + }); + if (!confirmed) { + this.isUploading.value = false; + return; + } + } + await this.props.record.save({ reload: true }); + } + + // Build fileDatas array + const fileDatas = []; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (!file.type.startsWith("image/")) { + continue; + } + const dataUrl = await this._readFile(file); + const base64Part = dataUrl.split(",")[1] || ""; + fileDatas.push({ base64: base64Part, filename: file.name }); + } + + if (fileDatas.length) { + const resModel = this.props.record.resModel; + const resId = this.props.record.resId; + const resField = this.props.name; + const childField = this.props.childImageField || "image_1920"; + + await this.orm.call("ir.attachment", "action_save_drag_and_drop_images", [ + resModel, + resId, + resField, + childField, + fileDatas, + extraData, + ]); + await this.props.record.load(); + await this.props.record.model.notify(); + } else { + this.notification.add(_t("No valid image files found."), { type: "warning" }); + } + } catch (error) { + console.error("Error uploading images:", error); + this.notification.add( + _t("Error uploading images. Check logs or required fields."), + { type: "danger" } + ); + } finally { + this.isUploading.value = false; + } + } + + _readFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = e => resolve(e.target.result); + reader.onerror = reject; + reader.readAsDataURL(file); + }); + } + + // When FileUploader (manual upload) fires: + onFileUploaded = async (ev) => { + // If parent record is dirty, confirm or just save + const extraData = this.props.extraData || {}; + if (this.props.record.isDirty) { + if (extraData.showConfirm) { + const confirmed = await new Promise(resolve => { + this.env.services.dialog.add(ConfirmationDialog, { + body: _t("The record has unsaved changes. Do you want to save them?"), + confirm: () => resolve(true), + cancel: () => resolve(false), + }); + }); + if (!confirmed) { + return; + } + } + await this.props.record.save({ reload: true }); + } + + if (!ev.data) { + this.notification.add(_t("No files uploaded or invalid file."), { type: "warning" }); + return; + } + + this.isUploading.value = true; + try { + const fileDatas = []; + // Normalize ev.data & ev.name to arrays + const dataList = Array.isArray(ev.data) ? ev.data : [ev.data]; + const nameList = Array.isArray(ev.name) ? ev.name : [ev.name]; + for (let i = 0; i < dataList.length; i++) { + const base64String = dataList[i]; + const filename = nameList[i] || `file_${i}`; + fileDatas.push({ base64: base64String, filename: filename }); + } + + const resModel = this.props.record.resModel; + const resId = this.props.record.resId; + const resField = this.props.name; + const childField = this.props.childImageField || "image_1920"; + + await this.orm.call("ir.attachment", "action_save_drag_and_drop_images", [ + resModel, + resId, + resField, + childField, + fileDatas, + extraData, + ]); + + await this.props.record.load(); + await this.props.record.model.notify(); + + } catch (error) { + console.error("Error during file upload:", error); + this.notification.add( + _t("Error uploading images. Check logs or required fields."), + { type: "danger" } + ); + } finally { + this.isUploading.value = false; + } + }; + + // Remove a single tile (child record) + removeRecord = async (recordId) => { + try { + const commands = [[3, recordId]]; + await this.orm.call(this.props.record.resModel, "write", [ + this.props.record.resId, + { [this.props.name]: commands }, + ]); + await this.props.record.load(); + this.notification.add("Image removed successfully.", { type: "success" }); + } catch (error) { + console.error("Error removing image:", error); + this.notification.add("Failed to remove image.", { type: "danger" }); + } + }; + + // Open a blank “new child” form in a modal + async openChildFormView() { + const childModel = this.props.record.data[this.props.name].resModel; + const inverseField = this.props.record.fields[this.props.name].relation_field; + const ctx = { + [`default_${inverseField}`]: this.props.record.resId, + default_parent_id: this.props.record.resId, + }; + try { + const action = { + type: "ir.actions.act_window", + res_model: childModel, + views: [[false, "form"]], + target: "new", + context: ctx, + }; + await this.action.doAction(action, { + onClose: async () => { + await this.props.record.load(); + this.props.record.model.notify(); + }, + }); + } catch (error) { + console.error("Error opening child form view:", error); + this.notification.add(_t("Error opening child form view."), { type: "danger" }); + } + } + + computeImageUrl(line) { + const model = this.props.record.data[this.props.name].resModel; + const field = this.props.extraData.previewImage || this.props.childImageField; + return `/web/image?model=${model}&id=${line.data.id}&field=${field}&unique=${line.data.write_date}`; + } +} + +const dndImagesField = { + component: DndImagesWidget, + displayName: _t("Enhanced DnD Multi Images"), + supportedTypes: ["one2many"], + extractProps: ({ attrs, options }) => { + return { + childImageField: options.childImageField || "image_1920", + extraData: options.extraData || {}, + }; + }, +}; +registry.category("fields").add("d_and_d_images", dndImagesField); diff --git a/eis_drag_drop/static/src/xml/dnd_widgets_templates.xml b/eis_drag_drop/static/src/xml/dnd_widgets_templates.xml new file mode 100644 index 0000000..28f3900 --- /dev/null +++ b/eis_drag_drop/static/src/xml/dnd_widgets_templates.xml @@ -0,0 +1,192 @@ + + + + +
+ +
+ + +
+ + + + + + + + + + + + +
+
+ + + Drag & Drop + + +
+ + + +
+ + + +
+ +

Drag & Drop Image Here

+
+
+
+ + + +
+ Uploading... +
+
+
+
+ + + + +
+ +
+ + + + +
+
+ +
+ + + + +
+
+
+ +
+ +
+ + +
+ + + + + +
+ + + +
+
+ + +
+ +
+ + + + +
+
+
+ + +
+
+ + + + +
+ Uploading... +
+
+ +
+
+ + +
\ No newline at end of file diff --git a/eis_drag_drop/views/product.xml b/eis_drag_drop/views/product.xml new file mode 100644 index 0000000..84f491f --- /dev/null +++ b/eis_drag_drop/views/product.xml @@ -0,0 +1,46 @@ + + + + + + + + inherit.product.template.common.faheem + product.template + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file