diff --git a/product_multi_image/README.rst b/product_multi_image/README.rst new file mode 100644 index 00000000000..4029de53fac --- /dev/null +++ b/product_multi_image/README.rst @@ -0,0 +1,150 @@ +=========================== +Multiple Images in Products +=========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/14.0/product_multi_image + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-14-0/product-attribute-14-0-product_multi_image + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/135/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module implements the possibility to have multiple images for a product +template, a.k.a. an image gallery. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +To install this module, you need to: + +* Install ``base_multi_image`` from + `OCA/server-tools `_. + +Configuration +============= + +You can manage your images at Product template level: + +#. Go to *Sales > Products > Products* and choose a product template. +#. Go to the *Images* tab. +#. Add a new image or edit the existing ones. +#. You can select for which variants you want to make available the image. + Keep it empty for making visible in all. +#. Refresh the page. +#. The first image in the collection is the main image for the product + template. + +Going to product variants form, you can manage also your images, but take +into account this behaviour: + +#. Go to *Sales > Products > Product Variants* and choose a product variant. +#. If you add an image here, the image is actually added to the product + template, and restricted to this variant. +#. When editing an existing image, the image is changed generally for all + the variants where is enabled, not only for this variant. +#. When removing an image from this form, if the image is only in this variant, + the image is removed. Otherwise, the image gets restricted to the rest of + the variants where is available. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/135/9.0 + +Known issues / Roadmap +====================== + +* When you change the image on the product variant, the preview image of the + *Images* tab doesn't get refreshed until you refresh the browser, or if you + go to its template, but the image has been actually saved! +* The field "Available in these variants" appears when opening the image + from the product variant. +* Add logic for handling to add images with the same name that another variant + of the same template, renaming the new image to a unique name. +* Add logic for handling to add the same image in several variants to a + already in another variant for not duplicating bytes. +* Provide proper migration scripts from module product_images from 7.0. +* Migrate to v8 api when https://github.com/odoo/odoo/issues/10799 gets fixed. +* If you try to sort images before saving the product variant or template, you + will get an error similar to ``DataError: invalid input syntax for integer: + "one2many_v_id_62"``. This bug has not been fixed yet, but a workaround is to + save and edit again to sort images. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Antiun Ingeniería +* Tecnativa +* LasLabs + +Contributors +~~~~~~~~~~~~ + +* Pedro M. Baeza +* Rafael Blasco +* Jairo Llopis +* Dave Lasley +* Shepilov Vladislav +* Marc Poch Mallandrich +* Hai Lang + +Other credits +~~~~~~~~~~~~~ + +* The migration of this module from 12.0 to 14.0 was financially supported by + Camptocamp. + +Original implementation +----------------------- +* This module is inspired in previous module *product_images* from OpenLabs + and Akretion. + +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/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_multi_image/__init__.py b/product_multi_image/__init__.py new file mode 100644 index 00000000000..38fe03ab959 --- /dev/null +++ b/product_multi_image/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from . import models +from .hooks import pre_init_hook, uninstall_hook diff --git a/product_multi_image/__manifest__.py b/product_multi_image/__manifest__.py new file mode 100644 index 00000000000..1549c2420bf --- /dev/null +++ b/product_multi_image/__manifest__.py @@ -0,0 +1,32 @@ +# © 2014-2016 Pedro M. Baeza +# © 2015 Antiun Ingeniería S.L. - Jairo Llopis +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Multiple Images in Products", + "version": "15.0.1.0.0", + "author": "Antiun Ingeniería, " + "Tecnativa, " + "LasLabs, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/product-attribute", + "category": "Sales Management", + "pre_init_hook": "pre_init_hook", + "uninstall_hook": "uninstall_hook", + "depends": [ + "base_multi_image", + "product", + ], + "data": [ + "views/image_view.xml", + "views/product_template_view.xml", + ], + "installable": True, + "images": [ + "images/product.png", + "images/db.png", + "images/file.png", + "images/url.png", + ], +} diff --git a/product_multi_image/hooks.py b/product_multi_image/hooks.py new file mode 100644 index 00000000000..40b7ce60d9a --- /dev/null +++ b/product_multi_image/hooks.py @@ -0,0 +1,25 @@ +# © 2016 Antiun Ingeniería S.L. - Jairo Llopis +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + +_logger = logging.getLogger(__name__) + + +try: + from odoo.addons.base_multi_image.hooks import ( + pre_init_hook_for_submodules, + uninstall_hook_for_submodules, + ) +except ImportError: + _logger.info("Cannot import base_multi_image hooks") + + +def pre_init_hook(cr): + pre_init_hook_for_submodules(cr, "product.template", "image_1920") + pre_init_hook_for_submodules(cr, "product.product", "image_variant_1920") + + +def uninstall_hook(cr, registry): + """Remove multi images for models that no longer use them.""" + uninstall_hook_for_submodules(cr, registry, "product.template") + uninstall_hook_for_submodules(cr, registry, "product.product") diff --git a/product_multi_image/i18n/es.po b/product_multi_image/i18n/es.po new file mode 100644 index 00000000000..52b908c2094 --- /dev/null +++ b/product_multi_image/i18n/es.po @@ -0,0 +1,118 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_multi_image +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-05-21 10:58+0000\n" +"PO-Revision-Date: 2021-05-21 10:58+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_base_multi_image_image +msgid " image model for multiple image functionality " +msgstr "" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_form_view +msgid "(keep empty for being visible for all variants)" +msgstr "(dejar vacío para hacerlo visible para todas las variantes)" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "Visible in all variants" +msgstr "Visible en todas las variantes" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__display_name +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__display_name +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__display_name +msgid "Display Name" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__id +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__id +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__id +msgid "ID" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,help:product_multi_image.field_base_multi_image_image__product_variant_ids +msgid "" +"If you leave it empty, all variants will show this image. Selecting one or " +"several of the available variants, you restrict the availability of the " +"image to those variants." +msgstr "" +"Si lo deja vacío, todas las variantes mostrarán esta imagen. Seleccionando " +"una o más de las variantes disponibles, restringirá la disponibilidad de la " +"imagen a esas variantes." + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_ids +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_ids +#: model_terms:ir.ui.view,arch_db:product_multi_image.view_product_template_form_img_inh +msgid "Images" +msgstr "Imágenes" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image____last_update +#: model:ir.model.fields,field_description:product_multi_image.field_product_product____last_update +#: model:ir.model.fields,field_description:product_multi_image.field_product_template____last_update +msgid "Last Modified on" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main +msgid "Main image" +msgstr "Imagen principal" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main_medium +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main_medium +msgid "Medium image" +msgstr "Imagen media" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_product_product +msgid "Product" +msgstr "Producto" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__product_variant_count +msgid "Product Variant Count" +msgstr "Nº de variantes" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main_small +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main_small +msgid "Small image" +msgstr "Imagen pequeña" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "Visible in" +msgstr "Visible en" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__product_variant_ids +msgid "Visible in these variants" +msgstr "Visible en estas variantes" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "variant(s)" +msgstr "variante(s)" diff --git a/product_multi_image/i18n/product_multi_image.pot b/product_multi_image/i18n/product_multi_image.pot new file mode 100644 index 00000000000..9c4684f180c --- /dev/null +++ b/product_multi_image/i18n/product_multi_image.pot @@ -0,0 +1,114 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_multi_image +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_base_multi_image_image +msgid " image model for multiple image functionality " +msgstr "" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_form_view +msgid "(keep empty for being visible for all variants)" +msgstr "" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "Visible in all variants" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__display_name +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__display_name +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__display_name +msgid "Display Name" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__id +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__id +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__id +msgid "ID" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,help:product_multi_image.field_base_multi_image_image__product_variant_ids +msgid "" +"If you leave it empty, all variants will show this image. Selecting one or " +"several of the available variants, you restrict the availability of the " +"image to those variants." +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_ids +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_ids +#: model_terms:ir.ui.view,arch_db:product_multi_image.product_variant_easy_edit_view +#: model_terms:ir.ui.view,arch_db:product_multi_image.view_product_template_form_img_inh +msgid "Images" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image____last_update +#: model:ir.model.fields,field_description:product_multi_image.field_product_product____last_update +#: model:ir.model.fields,field_description:product_multi_image.field_product_template____last_update +msgid "Last Modified on" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main +msgid "Main image" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main_medium +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main_medium +msgid "Medium image" +msgstr "" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_product_product +msgid "Product" +msgstr "" + +#. module: product_multi_image +#: model:ir.model,name:product_multi_image.model_product_template +msgid "Product Template" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__product_variant_count +msgid "Product Variant Count" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_product_product__image_main_small +#: model:ir.model.fields,field_description:product_multi_image.field_product_template__image_main_small +msgid "Small image" +msgstr "" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "Visible in" +msgstr "" + +#. module: product_multi_image +#: model:ir.model.fields,field_description:product_multi_image.field_base_multi_image_image__product_variant_ids +msgid "Visible in these variants" +msgstr "" + +#. module: product_multi_image +#: model_terms:ir.ui.view,arch_db:product_multi_image.image_kanban_view +msgid "variant(s)" +msgstr "" diff --git a/product_multi_image/images/db.png b/product_multi_image/images/db.png new file mode 100644 index 00000000000..2bfaab57ef4 Binary files /dev/null and b/product_multi_image/images/db.png differ diff --git a/product_multi_image/images/file.png b/product_multi_image/images/file.png new file mode 100644 index 00000000000..6efdafd82d6 Binary files /dev/null and b/product_multi_image/images/file.png differ diff --git a/product_multi_image/images/product.png b/product_multi_image/images/product.png new file mode 100644 index 00000000000..2c43bedc281 Binary files /dev/null and b/product_multi_image/images/product.png differ diff --git a/product_multi_image/images/url.png b/product_multi_image/images/url.png new file mode 100644 index 00000000000..00f0c3819ae Binary files /dev/null and b/product_multi_image/images/url.png differ diff --git a/product_multi_image/models/__init__.py b/product_multi_image/models/__init__.py new file mode 100644 index 00000000000..9d32c1f078a --- /dev/null +++ b/product_multi_image/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from . import image +from . import product_template +from . import product_product diff --git a/product_multi_image/models/image.py b/product_multi_image/models/image.py new file mode 100644 index 00000000000..ae95ea8b233 --- /dev/null +++ b/product_multi_image/models/image.py @@ -0,0 +1,22 @@ +# © 2016 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from odoo import api, fields, models + + +class Image(models.Model): + _inherit = "base_multi_image.image" + + product_variant_ids = fields.Many2many( + comodel_name="product.product", + string="Visible in these variants", + help="If you leave it empty, all variants will show this image. " + "Selecting one or several of the available variants, you " + "restrict the availability of the image to those variants.", + ) + product_variant_count = fields.Integer(compute="_compute_product_variant_count") + + @api.depends("product_variant_ids") + def _compute_product_variant_count(self): + for image in self: + image.product_variant_count = len(image.product_variant_ids) diff --git a/product_multi_image/models/product_product.py b/product_multi_image/models/product_product.py new file mode 100644 index 00000000000..cfe5f08b77b --- /dev/null +++ b/product_multi_image/models/product_product.py @@ -0,0 +1,84 @@ +# © 2016 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _name = "product.product" + _inherit = [_name, "base_multi_image.owner"] + + # Make this field computed for getting only the available images + image_ids = fields.One2many( + comodel_name="base_multi_image.image", + compute="_compute_image_ids", + inverse="_inverse_image_ids", + ) + + # image, image_medium, image_small fields are not available since 13.0 + + @api.depends( + "product_tmpl_id", + "product_tmpl_id.image_ids", + "product_tmpl_id.image_ids.product_variant_ids", + ) + def _compute_image_ids(self): + for product in self: + images = product.product_tmpl_id.image_ids.filtered( + lambda x: ( + not x.product_variant_ids or product.id in x.product_variant_ids.ids + ) + ) + product.image_ids = [(6, 0, images.ids)] + if product.image_ids: + product.image_1920 = product.image_ids[0].image_main + + def _inverse_image_ids(self): + for product in self: + # Remember the list of images that were before changes + previous_images = product.product_tmpl_id.image_ids.filtered( + lambda x: ( + not x.product_variant_ids or product.id in x.product_variant_ids.ids + ) + ) + for image in product.image_ids: + if isinstance(image.id, models.NewId): + # Image added + image.owner_id = product.product_tmpl_id.id + image.owner_model = "product.template" + image.product_variant_ids = [(6, 0, product.ids)] + image.create(image._convert_to_write(image._cache)) + else: + previous_images -= image + # Update existing records + image.write(image._convert_to_write(image._cache)) + for image in previous_images: + # Images removed + if not image.product_variant_ids: + variants = product.product_tmpl_id.product_variant_ids + else: + variants = image.product_variant_ids + variants -= product + if not variants: + # Remove the image, as there's no variant that contains it + image.unlink() + else: + # Leave the images for the rest of the variants + image.product_variant_ids = [(6, 0, variants.ids)] + product.image_1920 = ( + False if len(product.image_ids) < 1 else product.image_ids[0].image_main + ) + + def unlink(self): + obj = self.with_context(bypass_image_removal=True) + # Remove images that are linked only to the product variant + for product in self: + images2remove = product.image_ids.filtered( + lambda image: ( + product in image.product_variant_ids + and len(image.product_variant_ids) == 1 + ) + ) + images2remove.unlink() + # We need to pass context to super so this syntax is valid + return super(ProductProduct, obj).unlink() diff --git a/product_multi_image/models/product_template.py b/product_multi_image/models/product_template.py new file mode 100644 index 00000000000..cd4fca3d790 --- /dev/null +++ b/product_multi_image/models/product_template.py @@ -0,0 +1,12 @@ +# © 2014-2016 Pedro M. Baeza +# © 2015 Antiun Ingeniería S.L. - Jairo Llopis +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ProductTemplate(models.Model): + _name = "product.template" + _inherit = [_name, "base_multi_image.owner"] + + # image, image_medium, image_small fields are not available since 13.0 diff --git a/product_multi_image/readme/CONFIGURE.rst b/product_multi_image/readme/CONFIGURE.rst new file mode 100644 index 00000000000..d6561c83ee4 --- /dev/null +++ b/product_multi_image/readme/CONFIGURE.rst @@ -0,0 +1,26 @@ +You can manage your images at Product template level: + +#. Go to *Sales > Products > Products* and choose a product template. +#. Go to the *Images* tab. +#. Add a new image or edit the existing ones. +#. You can select for which variants you want to make available the image. + Keep it empty for making visible in all. +#. Refresh the page. +#. The first image in the collection is the main image for the product + template. + +Going to product variants form, you can manage also your images, but take +into account this behaviour: + +#. Go to *Sales > Products > Product Variants* and choose a product variant. +#. If you add an image here, the image is actually added to the product + template, and restricted to this variant. +#. When editing an existing image, the image is changed generally for all + the variants where is enabled, not only for this variant. +#. When removing an image from this form, if the image is only in this variant, + the image is removed. Otherwise, the image gets restricted to the rest of + the variants where is available. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/135/9.0 diff --git a/product_multi_image/readme/CONTRIBUTORS.rst b/product_multi_image/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..c95b1926ee5 --- /dev/null +++ b/product_multi_image/readme/CONTRIBUTORS.rst @@ -0,0 +1,10 @@ +* Pedro M. Baeza +* Rafael Blasco +* Jairo Llopis +* Dave Lasley +* Shepilov Vladislav +* Marc Poch Mallandrich +* Hai Lang +* `Greenice `_: + + * Fernando La Chica diff --git a/product_multi_image/readme/CREDITS.rst b/product_multi_image/readme/CREDITS.rst new file mode 100644 index 00000000000..ad670e92717 --- /dev/null +++ b/product_multi_image/readme/CREDITS.rst @@ -0,0 +1,7 @@ +* The migration of this module from 12.0 to 14.0 was financially supported by + Camptocamp. + +Original implementation +----------------------- +* This module is inspired in previous module *product_images* from OpenLabs + and Akretion. diff --git a/product_multi_image/readme/DESCRIPTION.rst b/product_multi_image/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..665079eb06a --- /dev/null +++ b/product_multi_image/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module implements the possibility to have multiple images for a product +template, a.k.a. an image gallery. diff --git a/product_multi_image/readme/INSTALL.rst b/product_multi_image/readme/INSTALL.rst new file mode 100644 index 00000000000..8ee6ad73a10 --- /dev/null +++ b/product_multi_image/readme/INSTALL.rst @@ -0,0 +1,4 @@ +To install this module, you need to: + +* Install ``base_multi_image`` from + `OCA/server-tools `_. diff --git a/product_multi_image/readme/ROADMAP.rst b/product_multi_image/readme/ROADMAP.rst new file mode 100644 index 00000000000..41a2add9f43 --- /dev/null +++ b/product_multi_image/readme/ROADMAP.rst @@ -0,0 +1,15 @@ +* When you change the image on the product variant, the preview image of the + *Images* tab doesn't get refreshed until you refresh the browser, or if you + go to its template, but the image has been actually saved! +* The field "Available in these variants" appears when opening the image + from the product variant. +* Add logic for handling to add images with the same name that another variant + of the same template, renaming the new image to a unique name. +* Add logic for handling to add the same image in several variants to a + already in another variant for not duplicating bytes. +* Provide proper migration scripts from module product_images from 7.0. +* Migrate to v8 api when https://github.com/odoo/odoo/issues/10799 gets fixed. +* If you try to sort images before saving the product variant or template, you + will get an error similar to ``DataError: invalid input syntax for integer: + "one2many_v_id_62"``. This bug has not been fixed yet, but a workaround is to + save and edit again to sort images. diff --git a/product_multi_image/static/description/icon.png b/product_multi_image/static/description/icon.png new file mode 100644 index 00000000000..b37cfe46958 Binary files /dev/null and b/product_multi_image/static/description/icon.png differ diff --git a/product_multi_image/static/description/icon.svg b/product_multi_image/static/description/icon.svg new file mode 100644 index 00000000000..7d0b12cd867 --- /dev/null +++ b/product_multi_image/static/description/icon.svg @@ -0,0 +1,464 @@ + + + Drawings Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + STUFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Drawings Icon + 2012-01-29T15:13:42 + Icon for Drawings/Pictures folder. + https://openclipart.org/detail/167547/drawings-icon-by-andreibranescu + + + andreibranescu + + + + + Inkscape + drawings + icon + pictures + + + + + + + + + + + diff --git a/product_multi_image/static/description/index.html b/product_multi_image/static/description/index.html new file mode 100644 index 00000000000..f46834f90b0 --- /dev/null +++ b/product_multi_image/static/description/index.html @@ -0,0 +1,504 @@ + + + + + + +Multiple Images in Products + + + +
+

Multiple Images in Products

+ + +

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runbot

+

This module implements the possibility to have multiple images for a product +template, a.k.a. an image gallery.

+

Table of contents

+ +
+

Installation

+

To install this module, you need to:

+ +
+
+

Configuration

+

You can manage your images at Product template level:

+
    +
  1. Go to Sales > Products > Products and choose a product template.
  2. +
  3. Go to the Images tab.
  4. +
  5. Add a new image or edit the existing ones.
  6. +
  7. You can select for which variants you want to make available the image. +Keep it empty for making visible in all.
  8. +
  9. Refresh the page.
  10. +
  11. The first image in the collection is the main image for the product +template.
  12. +
+

Going to product variants form, you can manage also your images, but take +into account this behaviour:

+
    +
  1. Go to Sales > Products > Product Variants and choose a product variant.
  2. +
  3. If you add an image here, the image is actually added to the product +template, and restricted to this variant.
  4. +
  5. When editing an existing image, the image is changed generally for all +the variants where is enabled, not only for this variant.
  6. +
  7. When removing an image from this form, if the image is only in this variant, +the image is removed. Otherwise, the image gets restricted to the rest of +the variants where is available.
  8. +
+Try me on Runbot +
+
+

Known issues / Roadmap

+
    +
  • When you change the image on the product variant, the preview image of the +Images tab doesn’t get refreshed until you refresh the browser, or if you +go to its template, but the image has been actually saved!
  • +
  • The field “Available in these variants” appears when opening the image +from the product variant.
  • +
  • Add logic for handling to add images with the same name that another variant +of the same template, renaming the new image to a unique name.
  • +
  • Add logic for handling to add the same image in several variants to a +already in another variant for not duplicating bytes.
  • +
  • Provide proper migration scripts from module product_images from 7.0.
  • +
  • Migrate to v8 api when https://github.com/odoo/odoo/issues/10799 gets fixed.
  • +
  • If you try to sort images before saving the product variant or template, you +will get an error similar to DataError: invalid input syntax for integer: +"one2many_v_id_62". This bug has not been fixed yet, but a workaround is to +save and edit again to sort images.
  • +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Antiun Ingeniería
  • +
  • Tecnativa
  • +
  • LasLabs
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+
    +
  • The migration of this module from 12.0 to 14.0 was financially supported by +Camptocamp.
  • +
+
+

Original implementation

+
    +
  • This module is inspired in previous module product_images from OpenLabs +and Akretion.
  • +
+
+
+
+

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/product-attribute project on GitHub.

+

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

+
+
+
+ + diff --git a/product_multi_image/tests/__init__.py b/product_multi_image/tests/__init__.py new file mode 100644 index 00000000000..d4608f19bed --- /dev/null +++ b/product_multi_image/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from . import test_product_multi_image diff --git a/product_multi_image/tests/test_product_multi_image.py b/product_multi_image/tests/test_product_multi_image.py new file mode 100644 index 00000000000..b116bcd29fe --- /dev/null +++ b/product_multi_image/tests/test_product_multi_image.py @@ -0,0 +1,224 @@ +# © 2016 Pedro M. Baeza +# Copyright 2016 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl-3). + +from odoo.tests import common + +from .. import hooks + + +class TestProductMultiImage(common.TransactionCase): + def setUp(self): + super().setUp() + self.transparent_image = ( # 1x1 Transparent GIF + b"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" + ) + self.grey_image = ( # 1x1 Grey GIF + b"R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw ==" + ) + self.black_image = ( # 1x1 Black GIF + b"R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" + ) + self.attribute = self.env["product.attribute"].create( + { + "name": "Test attribute", + } + ) + self.value_1 = self.env["product.attribute.value"].create( + { + "name": "Test value 1", + "attribute_id": self.attribute.id, + } + ) + self.value_2 = self.env["product.attribute.value"].create( + { + "name": "Test value 2", + "attribute_id": self.attribute.id, + } + ) + self.product_template = self.env["product.template"].create( + { + "name": "Test product", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": self.attribute.id, + "value_ids": [(6, 0, (self.value_1 + self.value_2).ids)], + }, + ) + ], + "image_ids": [ + ( + 0, + 0, + { + "storage": "db", + "name": "Image 1", + "file_db_store": self.transparent_image, + "owner_model": "product.template", + }, + ), + ( + 0, + 0, + { + "storage": "db", + "name": "Image 2", + "file_db_store": self.black_image, + "owner_model": "product.template", + }, + ), + ], + } + ) + self.product_1 = self.product_template.product_variant_ids[0] + self.product_2 = self.product_template.product_variant_ids[1] + + def test_all_images(self): + self.assertEqual(len(self.product_template.image_ids), 2) + self.assertEqual(len(self.product_1.image_ids), 2) + self.assertEqual(len(self.product_2.image_ids), 2) + + def test_restrict_one_image(self): + self.product_template.image_ids[0].product_variant_ids = [ + (6, 0, self.product_1.ids) + ] + self.assertEqual(len(self.product_1.image_ids), 2) + self.assertEqual(len(self.product_2.image_ids), 1) + self.assertEqual(self.product_1.image_1920, self.transparent_image) + self.assertEqual(self.product_2.image_1920, self.black_image) + + def test_add_image_variant(self): + self.product_1.image_ids = [ + (0, 0, {"storage": "db", "file_db_store": self.grey_image}) + ] + self.product_template.refresh() + self.assertEqual(len(self.product_template.image_ids), 3) + self.assertEqual( + self.product_template.image_ids[-1].product_variant_ids, self.product_1 + ) + + def test_remove_image_variant(self): + self.product_1.image_ids = [(3, self.product_1.image_ids[0].id)] + self.product_template.refresh() + self.assertEqual(len(self.product_template.image_ids), 2) + self.assertEqual( + self.product_template.image_ids[0].product_variant_ids, self.product_2 + ) + + def test_remove_image_all_variants(self): + self.product_1.image_ids = [(3, self.product_1.image_ids[0].id)] + self.product_2.image_ids = [(3, self.product_2.image_ids[0].id)] + self.product_template.refresh() + self.assertEqual(len(self.product_template.image_ids), 1) + + def test_edit_image_variant(self): + text = "Test name changed" + self.product_1.image_ids[0].name = text + self.product_template.refresh() + self.assertEqual(self.product_template.image_ids[0].name, text) + + def test_create_variant_afterwards(self): + """Create a template, assign an image, and then create the variant. + Check that the images are not lost. + """ + template = self.env["product.template"].create( + { + "name": "Test 2", + "image_ids": [ + ( + 0, + 0, + { + "storage": "db", + "name": "Image 1", + "file_db_store": self.transparent_image, + "owner_model": "product.template", + }, + ) + ], + } + ) + self.assertEqual( + len(template.image_ids), + 1, + "Product template did not start with singleton image_ids. " + "Got %s" % (template.image_ids,), + ) + template.write( + { + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": self.attribute.id, + "value_ids": [(6, 0, (self.value_1 + self.value_2).ids)], + }, + ) + ], + } + ) + self.assertEqual( + len(template.image_ids), + 1, + "Product template did not retain the singleton image_ids. " + "Got %s" % (template.image_ids,), + ) + for variant in template.product_variant_ids: + self.assertEqual( + len(variant.image_ids), + 1, + "Product variant did not receive the image_ids. Got %s" + % (variant.image_ids,), + ) + + def test_remove_variant_with_image(self): + self.product_template.image_ids[0].product_variant_ids = [ + (6, 0, self.product_1.ids) + ] + self.product_1.unlink() + self.assertEqual(len(self.product_template.image_ids), 1) + + def test_image_product_variant_count(self): + """It should provide a total of variants related to image""" + image = self.product_1.image_ids[0] + image.product_variant_ids = [(6, 0, self.product_1.ids)] + self.assertEqual( + image.product_variant_count, + 1, + ) + + def test_pre_init_hook_product(self): + """It should populate the ``image_ids`` on existing product""" + product = self.env.ref("product.product_product_3") + self.assertEqual( + len(product.image_ids), + 1, + ) + + def test_pre_init_hook_template(self): + """It should populate the ``image_ids`` on existing template""" + product = self.env.ref("product.product_product_3_product_template") + self.assertEqual( + len(product.image_ids), + 1, + ) + + def test_uninstall_hook_product(self): + """It should remove ``image_ids`` associated with products""" + hooks.uninstall_hook(self.env.cr, self.registry) + images = self.env["base_multi_image.image"].search( + [("owner_model", "=", "product.product")], + ) + self.assertFalse(len(images)) + + def test_uninstall_hook_teplate(self): + """It should remove ``image_ids`` associated with templates""" + hooks.uninstall_hook(self.env.cr, self.registry) + images = self.env["base_multi_image.image"].search( + [("owner_model", "=", "product.template")], + ) + self.assertFalse(len(images)) diff --git a/product_multi_image/views/image_view.xml b/product_multi_image/views/image_view.xml new file mode 100644 index 00000000000..a63a1d3fe97 --- /dev/null +++ b/product_multi_image/views/image_view.xml @@ -0,0 +1,50 @@ + + + + + + Multi image form + base_multi_image.image + + primary + + + + + + + + + Product multi image kanban + base_multi_image.image + + primary + + + + + + + + + + Visible in all variants + + + Visible in variant(s) + + + + + + + + diff --git a/product_multi_image/views/product_template_view.xml b/product_multi_image/views/product_template_view.xml new file mode 100644 index 00000000000..6516597106b --- /dev/null +++ b/product_multi_image/views/product_template_view.xml @@ -0,0 +1,53 @@ + + + + + + Add multi images + product.template + + + + + + + + + + + + product_multi_image.product_variant_easy_edit_view + product.product + + + + + + + + + + + diff --git a/setup/product_multi_image/odoo/addons/product_multi_image b/setup/product_multi_image/odoo/addons/product_multi_image new file mode 120000 index 00000000000..9dbd0dafa1f --- /dev/null +++ b/setup/product_multi_image/odoo/addons/product_multi_image @@ -0,0 +1 @@ +../../../../product_multi_image \ No newline at end of file diff --git a/setup/product_multi_image/setup.py b/setup/product_multi_image/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/product_multi_image/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)