From fc50991780c40872ffb6e0bfc18a2c323e0f3a1f Mon Sep 17 00:00:00 2001 From: Patrick Tombez Date: Tue, 2 Oct 2018 10:57:52 +0200 Subject: [PATCH] Add module custom_image --- base_conditional_image/README.rst | 106 +++++ base_conditional_image/__init__.py | 2 + base_conditional_image/__manifest__.py | 20 + base_conditional_image/i18n/custom_image.pot | 141 ++++++ base_conditional_image/models/__init__.py | 3 + .../models/abstract_image.py | 66 +++ base_conditional_image/models/image.py | 64 +++ .../readme/CONTRIBUTORS.rst | 1 + base_conditional_image/readme/DESCRIPTION.rst | 8 + base_conditional_image/readme/INSTALL.rst | 11 + base_conditional_image/readme/USAGE.rst | 7 + .../security/ir.model.access.csv | 3 + .../static/description/index.html | 447 ++++++++++++++++++ base_conditional_image/views/image_view.xml | 53 +++ 14 files changed, 932 insertions(+) create mode 100644 base_conditional_image/README.rst create mode 100644 base_conditional_image/__init__.py create mode 100644 base_conditional_image/__manifest__.py create mode 100644 base_conditional_image/i18n/custom_image.pot create mode 100644 base_conditional_image/models/__init__.py create mode 100644 base_conditional_image/models/abstract_image.py create mode 100644 base_conditional_image/models/image.py create mode 100644 base_conditional_image/readme/CONTRIBUTORS.rst create mode 100644 base_conditional_image/readme/DESCRIPTION.rst create mode 100644 base_conditional_image/readme/INSTALL.rst create mode 100644 base_conditional_image/readme/USAGE.rst create mode 100644 base_conditional_image/security/ir.model.access.csv create mode 100644 base_conditional_image/static/description/index.html create mode 100644 base_conditional_image/views/image_view.xml diff --git a/base_conditional_image/README.rst b/base_conditional_image/README.rst new file mode 100644 index 000000000..a0c93c525 --- /dev/null +++ b/base_conditional_image/README.rst @@ -0,0 +1,106 @@ +================== +Conditional Images +================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/9.0/base_conditional_image + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-9-0/server-tools-9-0-base_conditional_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/149/9.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of any model to support conditional images +(based on the record attributes) and to manage them either globally or by company. + +The main goal behind this module is to avoid storing the same image multiple times. +For example, for every partner, there is a related image (most of the time, it's the default one). +With this module properly set up, it will be stored only one time and you can change it whenever you want for all partners. + +**WARNING**: this module cannot be used on the same objects using the module `base_multi_image`. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +The sole purpose of this module is to add an abstract model to be inherited. +So, you will not notice any changes on install. + +To develop using this module, you have to inherit the abstract model `abstract.conditional.image` +to the model that needs the conditional images:: + + class ResPartner(models.Model): + _inherit = ['res.partner', 'abstract.conditional.image'] + _name = 'res.partner' + +Then, configure how the images will be selected for each record. + +Usage +===== + +Go to *Technical Settings > Settings > Images* to configure all the images. +You can define images for specific objects, depending on the attributes and the company of the object. + +The `selector` should return a boolean expression. All fields of the object are available to compute the result. + +The system will first try to match an image with a company set up, then with the ones without a company. +If your object does not have a `company_id` field, this check will be ignored and only images without a company will be used. + +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 +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Patrick Tombez + +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/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_conditional_image/__init__.py b/base_conditional_image/__init__.py new file mode 100644 index 000000000..a0fdc10fe --- /dev/null +++ b/base_conditional_image/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/base_conditional_image/__manifest__.py b/base_conditional_image/__manifest__.py new file mode 100644 index 000000000..ac397861f --- /dev/null +++ b/base_conditional_image/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +{ + 'name': 'Conditional Images', + 'version': '12.0.1.0.0', + 'author': 'Camptocamp, Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'category': 'Misc', + 'depends': [ + 'base', + ], + 'website': 'http://github.com/OCA/server-tools', + 'data': [ + 'views/image_view.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, +} diff --git a/base_conditional_image/i18n/custom_image.pot b/base_conditional_image/i18n/custom_image.pot new file mode 100644 index 000000000..bf3f96c91 --- /dev/null +++ b/base_conditional_image/i18n/custom_image.pot @@ -0,0 +1,141 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * custom_image +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-02 08:36+0000\n" +"PO-Revision-Date: 2018-10-02 08:36+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: custom_image +#: code:addons/custom_image/models/image.py:47 +#, python-format +msgid "At least one image type must be specified" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_company_id +msgid "Company" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,help:custom_image.field_image_company_id +msgid "Company related check. If inherited object does not have a `company_id` field, it will be ignored. The check will first take the records with a company then, if not match is found, the ones without a company." +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_create_uid +msgid "Created by" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_create_date +msgid "Created on" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image_display_name +#: model:ir.model.fields,field_description:custom_image.field_image_display_name +msgid "Display Name" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image_id +#: model:ir.model.fields,field_description:custom_image.field_image_id +msgid "ID" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image_image +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image_image_medium +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image_image_small +#: model:ir.model.fields,field_description:custom_image.field_image_image +msgid "Image" +msgstr "" + +#. module: custom_image +#: model:ir.actions.act_window,name:custom_image.image_action +#: model:ir.ui.menu,name:custom_image.image_menu +msgid "Images" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_abstract_custom_image___last_update +#: model:ir.model.fields,field_description:custom_image.field_image___last_update +msgid "Last Modified on" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_write_date +msgid "Last Updated on" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_image_medium +msgid "Medium-sized image" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,help:custom_image.field_image_image_medium +msgid "Medium-sized image. It is automatically resized as a 128x128px image, with aspect ratio preserved. Use this field in form views or some kanban views." +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_model_name +msgid "Model Name" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_name +msgid "Name" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,help:custom_image.field_image_selector +msgid "Python expression used to distinguish the images for the same object. The variable `object` refer to the actual record on which the expression will be executed. An empty expression will always return `True`." +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_selector +#: model:ir.ui.view,arch_db:custom_image.view_image_form +msgid "Selector" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,field_description:custom_image.field_image_image_small +msgid "Small-sized image" +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,help:custom_image.field_image_image_small +msgid "Small-sized image. It is automatically resized as a 64x64px image, with aspect ratio preserved. Use this field anywhere a small image is required." +msgstr "" + +#. module: custom_image +#: model:ir.model.fields,help:custom_image.field_image_image +msgid "This field holds the standard image, limited to 1024x1024px" +msgstr "" + +#. module: custom_image +#: model:ir.model,name:custom_image.model_abstract_custom_image +msgid "abstract.custom.image" +msgstr "" + +#. module: custom_image +#: model:ir.model,name:custom_image.model_image +msgid "image" +msgstr "" + diff --git a/base_conditional_image/models/__init__.py b/base_conditional_image/models/__init__.py new file mode 100644 index 000000000..d7e2ed1ef --- /dev/null +++ b/base_conditional_image/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import image +from . import abstract_image diff --git a/base_conditional_image/models/abstract_image.py b/base_conditional_image/models/abstract_image.py new file mode 100644 index 000000000..89c1cba5a --- /dev/null +++ b/base_conditional_image/models/abstract_image.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from openerp import fields, models +from openerp.tools.safe_eval import safe_eval + + +class AbstractImageStorage(models.AbstractModel): + _name = 'abstract.conditional.image' + + image = fields.Binary( + compute='_compute_image', string="Image", + store=False, readonly=True + ) + image_medium = fields.Binary( + compute='_compute_image', string="Image", + store=False, readonly=True + ) + image_small = fields.Binary( + compute='_compute_image', string="Image", + store=False, readonly=True + ) + + @staticmethod + def _compute_selector_test_without_company(image_selector, record): + return bool( + safe_eval(image_selector.selector or "True", {'object': record}) + ) + + @staticmethod + def _compute_selector_test_with_company(image_selector, record): + if (image_selector.company_id == record.company_id or + record.company_id and not image_selector.company_id): + return AbstractImageStorage._compute_selector_test_without_company( + image_selector, record + ) + return False + + def _compute_image(self): + if 'company_id' in self._fields: + search_clause = [('model_name', '=', self._name)] + test_method = self._compute_selector_test_with_company + else: + # If inherited object doesn't have a `company_id` field, + # remove the items with a company defined and the related checks + search_clause = [('model_name', '=', self._name), + ('company_id', '=', False)] + test_method = self._compute_selector_test_without_company + + image_selectors = self.env['image'].search( + search_clause, order='company_id, selector' + ) + + for rec in self: + found = None + for image_selector in image_selectors: + if test_method(image_selector, rec): + found = image_selector + break + + rec.update({ + 'image': found and found.image, + 'image_medium': found and found.image_medium, + 'image_small': found and found.image_small, + }) diff --git a/base_conditional_image/models/image.py b/base_conditional_image/models/image.py new file mode 100644 index 000000000..27ce1cc82 --- /dev/null +++ b/base_conditional_image/models/image.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from openerp import api, fields, models, tools, _ +from openerp.exceptions import ValidationError + + +class Image(models.Model): + _name = 'image' + + name = fields.Char('Name', required=True) + model_name = fields.Char('Model Name', required=True) + selector = fields.Text( + 'Selector', + help='Python expression used as selector when multiple images are used' + 'for the same object. The variable `object` refer ' + 'to the actual record on which the expression will be executed. ' + 'An empty expression will always return `True`.' + ) + company_id = fields.Many2one( + 'res.company', 'Company', + help='Company related check. If inherited object does not have a ' + '`company_id` field, it will be ignored. ' + 'The check will first take the records with a company then, ' + 'if not match is found, the ones without a company.' + ) + + # image: all image fields are base64 encoded and PIL-supported + image = fields.Binary( + "Image", attachment=True, + help="This field holds the standard image, limited to 1024x1024px" + ) + image_medium = fields.Binary( + "Medium-sized image", attachment=True, + help="Medium-sized image. It is automatically " + "resized as a 128x128px image, with aspect ratio preserved. " + "Use this field in form views or some kanban views." + ) + image_small = fields.Binary( + "Small-sized image", attachment=True, + help="Small-sized image. It is automatically " + "resized as a 64x64px image, with aspect ratio preserved. " + "Use this field anywhere a small image is required." + ) + + @api.model + def _process_images(self, vals, required=False): + if set(['image', 'image_medium', 'image_small']) & set(vals.keys()): + tools.image_resize_images(vals) + elif required: + raise ValidationError( + _('At least one image type must be specified') + ) + + @api.model + def create(self, vals): + self._process_images(vals, required=True) + return super(Image, self).create(vals) + + @api.multi + def write(self, vals): + self._process_images(vals) + return super(Image, self).write(vals) diff --git a/base_conditional_image/readme/CONTRIBUTORS.rst b/base_conditional_image/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..78f10eddd --- /dev/null +++ b/base_conditional_image/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Patrick Tombez diff --git a/base_conditional_image/readme/DESCRIPTION.rst b/base_conditional_image/readme/DESCRIPTION.rst new file mode 100644 index 000000000..43630823e --- /dev/null +++ b/base_conditional_image/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +This module extends the functionality of any model to support conditional images +(based on the record attributes) and to manage them either globally or by company. + +The main goal behind this module is to avoid storing the same image multiple times. +For example, for every partner, there is a related image (most of the time, it's the default one). +With this module properly set up, it will be stored only one time and you can change it whenever you want for all partners. + +**WARNING**: this module cannot be used on the same objects using the module `base_multi_image`. diff --git a/base_conditional_image/readme/INSTALL.rst b/base_conditional_image/readme/INSTALL.rst new file mode 100644 index 000000000..18474d1c9 --- /dev/null +++ b/base_conditional_image/readme/INSTALL.rst @@ -0,0 +1,11 @@ +The sole purpose of this module is to add an abstract model to be inherited. +So, you will not notice any changes on install. + +To develop using this module, you have to inherit the abstract model `abstract.conditional.image` +to the model that needs the conditional images:: + + class ResPartner(models.Model): + _inherit = ['res.partner', 'abstract.conditional.image'] + _name = 'res.partner' + +Then, configure how the images will be selected for each record. diff --git a/base_conditional_image/readme/USAGE.rst b/base_conditional_image/readme/USAGE.rst new file mode 100644 index 000000000..6c70db038 --- /dev/null +++ b/base_conditional_image/readme/USAGE.rst @@ -0,0 +1,7 @@ +Go to *Technical Settings > Settings > Images* to configure all the images. +You can define images for specific objects, depending on the attributes and the company of the object. + +The `selector` should return a boolean expression. All fields of the object are available to compute the result. + +The system will first try to match an image with a company set up, then with the ones without a company. +If your object does not have a `company_id` field, this check will be ignored and only images without a company will be used. diff --git a/base_conditional_image/security/ir.model.access.csv b/base_conditional_image/security/ir.model.access.csv new file mode 100644 index 000000000..0609565ce --- /dev/null +++ b/base_conditional_image/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_conditional_image,Conditional Image,base_conditional_image.model_image,base.group_no_one,1,1,1,1 +access_conditional_image_users,Conditional Image Users,base_conditional_image.model_image,base.group_user,1,0,0,0 diff --git a/base_conditional_image/static/description/index.html b/base_conditional_image/static/description/index.html new file mode 100644 index 000000000..43ed7f52d --- /dev/null +++ b/base_conditional_image/static/description/index.html @@ -0,0 +1,447 @@ + + + + + + +Conditional Images + + + +
+

Conditional Images

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

This module extends the functionality of any model to support conditional images +(based on the record attributes) and to manage them either globally or by company.

+

The main goal behind this module is to avoid storing the same image multiple times. +For example, for every partner, there is a related image (most of the time, it’s the default one). +With this module properly set up, it will be stored only one time and you can change it whenever you want for all partners.

+

WARNING: this module cannot be used on the same objects using the module base_multi_image.

+

Table of contents

+ +
+

Installation

+

The sole purpose of this module is to add an abstract model to be inherited. +So, you will not notice any changes on install.

+

To develop using this module, you have to inherit the abstract model abstract.conditional.image +to the model that needs the conditional images:

+
+class ResPartner(models.Model):
+    _inherit = ['res.partner', 'abstract.conditional.image']
+    _name = 'res.partner'
+
+

Then, configure how the images will be selected for each record.

+
+
+

Usage

+

Go to Technical Settings > Settings > Images to configure all the images. +You can define images for specific objects, depending on the attributes and the company of the object.

+

The selector should return a boolean expression. All fields of the object are available to compute the result.

+

The system will first try to match an image with a company set up, then with the ones without a company. +If your object does not have a company_id field, this check will be ignored and only images without a company will be used.

+
+
+

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

+
    +
  • Camptocamp
  • +
+
+ +
+

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/server-tools project on GitHub.

+

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

+
+
+
+ + diff --git a/base_conditional_image/views/image_view.xml b/base_conditional_image/views/image_view.xml new file mode 100644 index 000000000..837299b7c --- /dev/null +++ b/base_conditional_image/views/image_view.xml @@ -0,0 +1,53 @@ + + + + + image.tree + image + + + + + + + + + + + + image.form + image + +
+ + + + + + + + + + + + + +
+
+
+ + + Images + image + form + tree,form + + + +
+