diff --git a/web_favicon/README.rst b/web_favicon/README.rst new file mode 100644 index 000000000..a4644b0a9 --- /dev/null +++ b/web_favicon/README.rst @@ -0,0 +1,120 @@ +==================== +Custom shortcut icon +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d484f6acfd0e0513696306b9fc933198baadece9a848422172ea0b51cf7633c4 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/18.0/web_favicon + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_favicon + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module was written to allow you to customize your Odoo instance's +shortcut icon (aka favicon). This is useful for branding purposes, but +also for integrators who have many different Odoo instances running and +need to see at a glance which browser tab does what. + +The icon is shown also for portal users when the website modules are not +installed. + +More info about favicon: https://en.wikipedia.org/wiki/Favicon + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Upload your favicon (16x16, 32x32, 64x64 or "as big as possible") on the +company form. The file format would be ico, gif or png with 16x16, 32x32 +or 64x64 pixels and 16 colors. Highers resolutions or colors support +depends on the used browser, but most modern browsers do. + +Note that most browsers cache favicons basically forever, so if you want +your icon to show up, you'll most probably have to delete you browser +cache. Some browsers can refresh the favicon, accessing the URL +/web_favicon/favicon. + +You have a sample SVG that can be used as template for generating your +icon in /static/src/img/master_original_favicon.svg. You can also search +for some favicon generators across the web. + +To allow a user to edit the favicon it has to be member of group +"Administration / Settings". + +Known issues / Roadmap +====================== + +- Allow to upload some big icon (preferrably SVG or the like) and + generate all the icons from it +- Generate icons suitable for mobile devices and web apps (see + /static/src/img/ folder inside the module for a sample of the + possible current formats. +- Put the icon definition at system level, not at company level. It + doesn't make sense (as the icon is cached) to have a different icon + per company. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Therp BV +* Tecnativa +* OERP Canada + +Contributors +------------ + +- OERP Canada : + + - Daryl Chen + +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/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_favicon/__init__.py b/web_favicon/__init__.py new file mode 100644 index 000000000..69f7babdf --- /dev/null +++ b/web_favicon/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/web_favicon/__manifest__.py b/web_favicon/__manifest__.py new file mode 100644 index 000000000..4f55e4a7a --- /dev/null +++ b/web_favicon/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2015 Therp BV +# Copyright 2016 Pedro M. Baeza +# Copyright 2024 OERP Canada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Custom shortcut icon", + "version": "18.0.1.0.0", + "author": "Therp BV, " + "Tecnativa, " + "OERP Canada," + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Website", + "summary": "Allows to set a custom shortcut icon (aka favicon)", + "website": "https://github.com/OCA/web", + "depends": [ + "web", + ], + "data": ["views/res_company_views.xml", "views/templates.xml"], + "installable": True, +} diff --git a/web_favicon/i18n/it.po b/web_favicon/i18n/it.po new file mode 100644 index 000000000..0cccdc760 --- /dev/null +++ b/web_favicon/i18n/it.po @@ -0,0 +1,35 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_favicon +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-04-19 09:35+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: web_favicon +#: model:ir.model,name:web_favicon.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: web_favicon +#: model:ir.model.fields,field_description:web_favicon.field_res_company__favicon +msgid "Company Favicon" +msgstr "Favicon azienda" + +#. module: web_favicon +#: model:ir.model.fields,help:web_favicon.field_res_company__favicon +msgid "" +"This field holds the image used to display favicon for a given company." +msgstr "" +"Questo campo contiene l'immagine utilizzata per visualizzare la favicon per " +"una data azienda." diff --git a/web_favicon/i18n/web_favicon.pot b/web_favicon/i18n/web_favicon.pot new file mode 100644 index 000000000..e8dcbee91 --- /dev/null +++ b/web_favicon/i18n/web_favicon.pot @@ -0,0 +1,30 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_favicon +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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: web_favicon +#: model:ir.model,name:web_favicon.model_res_company +msgid "Companies" +msgstr "" + +#. module: web_favicon +#: model:ir.model.fields,field_description:web_favicon.field_res_company__favicon +msgid "Company Favicon" +msgstr "" + +#. module: web_favicon +#: model:ir.model.fields,help:web_favicon.field_res_company__favicon +msgid "" +"This field holds the image used to display favicon for a given company." +msgstr "" diff --git a/web_favicon/models/__init__.py b/web_favicon/models/__init__.py new file mode 100644 index 000000000..a12c1b8ee --- /dev/null +++ b/web_favicon/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import res_company diff --git a/web_favicon/models/res_company.py b/web_favicon/models/res_company.py new file mode 100644 index 000000000..f0f468ae5 --- /dev/null +++ b/web_favicon/models/res_company.py @@ -0,0 +1,90 @@ +# Copyright 2015 Therp BV +# Copyright 2016 Pedro M. Baeza +# Copyright 2024 OERP Canada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import hashlib +import io +from random import randrange + +from PIL import Image + +import odoo +from odoo import api, fields, models, tools +from odoo.http import request + + +class ResCompany(models.Model): + _inherit = "res.company" + + def _get_default_favicon(self, original=False): + img_path = odoo.tools.misc.file_path("web/static/img/favicon.ico") + with tools.file_open(img_path, "rb") as f: + if original: + return base64.b64encode(f.read()) + # Modify the source image to add a colored bar on the bottom + # This could seem overkill to modify the pixels 1 by 1, but + # Pillow doesn't provide an easy way to do it, and this + # is acceptable for a 16x16 image. + color = ( + randrange(32, 224, 24), + randrange(32, 224, 24), + randrange(32, 224, 24), + ) + original = Image.open(f) + new_image = Image.new("RGBA", original.size) + height = original.size[1] + width = original.size[0] + bar_size = 1 + for y in range(height): + for x in range(width): + pixel = original.getpixel((x, y)) + if height - bar_size <= y + 1 <= height: + new_image.putpixel((x, y), (color[0], color[1], color[2], 255)) + else: + new_image.putpixel( + (x, y), (pixel[0], pixel[1], pixel[2], pixel[3]) + ) + stream = io.BytesIO() + new_image.save(stream, format="ICO") + return base64.b64encode(stream.getvalue()) + + favicon = fields.Binary( + string="Company Favicon", + help="This field holds the image used to display favicon for a given company.", + default=_get_default_favicon, + ) + + @api.model_create_multi + def create(self, vals_list): + # add default favicon + for vals in vals_list: + if not vals.get("favicon"): + vals["favicon"] = self._get_default_favicon() + return super().create(vals_list) + + # Get favicon from current company + @api.model + def _get_favicon(self): + """Returns a local url that points to the image field of a given record.""" + if self.env.context.get("website_id"): + website = self.env["website"].browse(self.env.context.get("website_id")) + return website.image_url(website, "favicon") + company_id = ( + request.httprequest.cookies.get("cids") + if request.httprequest.cookies.get("cids") + else False + ) + company = ( + self.browse(int(company_id.split("-")[0])).sudo() + if company_id and self.browse(int(company_id.split("-")[0])).sudo().favicon + else False + ) + if company: + sha = hashlib.sha512(str(company.write_date).encode("utf-8")).hexdigest()[ + :7 + ] + return f"/web/image/{self._name}/{company.id}/favicon?unique={sha}" + else: + return False diff --git a/web_favicon/pyproject.toml b/web_favicon/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/web_favicon/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_favicon/readme/CONFIGURE.md b/web_favicon/readme/CONFIGURE.md new file mode 100644 index 000000000..55777b650 --- /dev/null +++ b/web_favicon/readme/CONFIGURE.md @@ -0,0 +1,15 @@ +Upload your favicon (16x16, 32x32, 64x64 or "as big as possible") on the +company form. The file format would be ico, gif or png with 16x16, 32x32 or +64x64 pixels and 16 colors. Highers resolutions or colors support depends on +the used browser, but most modern browsers do. + +Note that most browsers cache favicons basically forever, so if you want your +icon to show up, you'll most probably have to delete you browser cache. +Some browsers can refresh the favicon, accessing the URL +/web_favicon/favicon. + +You have a sample SVG that can be used as template for generating your icon +in /static/src/img/master_original_favicon.svg. You can also search for some +favicon generators across the web. + +To allow a user to edit the favicon it has to be member of group "Administration / Settings". diff --git a/web_favicon/readme/CONTRIBUTORS.md b/web_favicon/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..896e5f02f --- /dev/null +++ b/web_favicon/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +* OERP Canada \<\>: + - Daryl Chen \<\> diff --git a/web_favicon/readme/DESCRIPTION.md b/web_favicon/readme/DESCRIPTION.md new file mode 100644 index 000000000..832607856 --- /dev/null +++ b/web_favicon/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ + +This module was written to allow you to customize your Odoo instance's shortcut +icon (aka favicon). This is useful for branding purposes, but also for +integrators who have many different Odoo instances running and need to see at a +glance which browser tab does what. + +The icon is shown also for portal users when the website modules are not +installed. + +More info about favicon: https://en.wikipedia.org/wiki/Favicon diff --git a/web_favicon/readme/ROADMAP.md b/web_favicon/readme/ROADMAP.md new file mode 100644 index 000000000..b4d00087c --- /dev/null +++ b/web_favicon/readme/ROADMAP.md @@ -0,0 +1,6 @@ +* Allow to upload some big icon (preferrably SVG or the like) and generate + all the icons from it +* Generate icons suitable for mobile devices and web apps (see /static/src/img/ + folder inside the module for a sample of the possible current formats. +* Put the icon definition at system level, not at company level. It doesn't + make sense (as the icon is cached) to have a different icon per company. diff --git a/web_favicon/static/description/icon.png b/web_favicon/static/description/icon.png new file mode 100644 index 000000000..bc0090b3d Binary files /dev/null and b/web_favicon/static/description/icon.png differ diff --git a/web_favicon/static/description/index.html b/web_favicon/static/description/index.html new file mode 100644 index 000000000..cc6aab455 --- /dev/null +++ b/web_favicon/static/description/index.html @@ -0,0 +1,465 @@ + + + + + +Custom shortcut icon + + + +
+

Custom shortcut icon

+ + +

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

+

This module was written to allow you to customize your Odoo instance’s +shortcut icon (aka favicon). This is useful for branding purposes, but +also for integrators who have many different Odoo instances running and +need to see at a glance which browser tab does what.

+

The icon is shown also for portal users when the website modules are not +installed.

+

More info about favicon: https://en.wikipedia.org/wiki/Favicon

+

Table of contents

+ +
+

Configuration

+

Upload your favicon (16x16, 32x32, 64x64 or “as big as possible”) on the +company form. The file format would be ico, gif or png with 16x16, 32x32 +or 64x64 pixels and 16 colors. Highers resolutions or colors support +depends on the used browser, but most modern browsers do.

+

Note that most browsers cache favicons basically forever, so if you want +your icon to show up, you’ll most probably have to delete you browser +cache. Some browsers can refresh the favicon, accessing the URL +<base_url>/web_favicon/favicon.

+

You have a sample SVG that can be used as template for generating your +icon in /static/src/img/master_original_favicon.svg. You can also search +for some favicon generators across the web.

+

To allow a user to edit the favicon it has to be member of group +“Administration / Settings”.

+
+
+

Known issues / Roadmap

+
    +
  • Allow to upload some big icon (preferrably SVG or the like) and +generate all the icons from it
  • +
  • Generate icons suitable for mobile devices and web apps (see +/static/src/img/ folder inside the module for a sample of the +possible current formats.
  • +
  • Put the icon definition at system level, not at company level. It +doesn’t make sense (as the icon is cached) to have a different icon +per company.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Therp BV
  • +
  • Tecnativa
  • +
  • OERP Canada
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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

+

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

+

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

+
+
+
+ + diff --git a/web_favicon/tests/__init__.py b/web_favicon/tests/__init__.py new file mode 100644 index 000000000..9be3cd521 --- /dev/null +++ b/web_favicon/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_web_favicon diff --git a/web_favicon/tests/test_web_favicon.py b/web_favicon/tests/test_web_favicon.py new file mode 100644 index 000000000..387c53bbd --- /dev/null +++ b/web_favicon/tests/test_web_favicon.py @@ -0,0 +1,100 @@ +# Copyright 2024 OERP Canada +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from PIL import Image + +from odoo.tests import tagged +from odoo.tests.common import TransactionCase +from odoo.tools.image import base64_to_image, image_to_base64 + +from odoo.addons.website.tools import MockRequest + + +@tagged("post_install", "-at_install") +class TestWebFavicon(TransactionCase): + def test_01_web_favicon(self): + """The goal of this test is to make sure the favicon is correctly + handled on the backend.""" + + # Test setting an Ico file directly, done through create + Company = self.env["res.company"] + + company = Company.create( + { + "name": "Test Company", + "favicon": Company._get_default_favicon(), + } + ) + + image = base64_to_image(company.favicon) + self.assertEqual(image.format, "ICO") + + # Test setting a JPEG file that is too big, done through write + bg_color = (135, 90, 123) + image = Image.new("RGB", (1920, 1080), color=bg_color) + company.favicon = image_to_base64(image, "JPEG") + image = base64_to_image(company.favicon) + self.assertEqual(image.format, "JPEG") + self.assertEqual(image.size, (1920, 1080)) + self.assertEqual(image.getpixel((0, 0)), bg_color) + with MockRequest(self.env) as mock_request: + mock_request.httprequest.cookies = {"cids": str(company.id)} + self.assertTrue(Company._get_favicon()) + + def test_02_default_favicon_creation(self): + """Test if default favicon is set when creating a company without favicon.""" + Company = self.env["res.company"] + company = Company.create({"name": "Test Company"}) + self.assertTrue(company.favicon, "Default favicon not set on company creation.") + + def test_03_website_favicon(self): + """Test if favicon URL is correctly returned when website_id is in context.""" + if self.env["ir.module.module"].search( + [("name", "=", "website"), ("state", "=", "installed")] + ): + company = self.env["res.company"].create( + { + "name": "Test Company with Website", + } + ) + website = self.env["website"].create( + { + "name": "Test Website", + "domain": "www.test.com", + "company_id": company.id, + } + ) + website.favicon = website._default_favicon() + favicon_url = company.with_context(website_id=website.id)._get_favicon() + expected_favicon_url = website.image_url(website, "favicon") + self.assertEqual( + favicon_url, + expected_favicon_url, + "The favicon URL should match the expected value.", + ) + + def test_04_favicon_multiple_companies(self): + """Test _get_favicon with multiple companies in cids cookie.""" + Company = self.env["res.company"] + + company_1 = Company.create( + {"name": "Company 1", "favicon": Company._get_default_favicon()} + ) + company_2 = Company.create( + {"name": "Company 2", "favicon": Company._get_default_favicon()} + ) + + with MockRequest(self.env) as mock_request: + mock_request.httprequest.cookies = { + "cids": f"{company_1.id}-{company_2.id}" + } + favicon_url = Company._get_favicon() + + self.assertTrue( + favicon_url, "Favicon URL should be generated for multiple companies." + ) + self.assertIn( + str(company_1.id), + favicon_url, + "Favicon URL should correspond to the first company in the cids cookie.", + ) diff --git a/web_favicon/views/res_company_views.xml b/web_favicon/views/res_company_views.xml new file mode 100644 index 000000000..ef4ec7566 --- /dev/null +++ b/web_favicon/views/res_company_views.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + res.company.form.inherit + res.company + + + + + + + + diff --git a/web_favicon/views/templates.xml b/web_favicon/views/templates.xml new file mode 100644 index 000000000..58af7157b --- /dev/null +++ b/web_favicon/views/templates.xml @@ -0,0 +1,12 @@ + + + + +