Skip to content
Snippets Groups Projects
Commit f159ba8f authored by Sébastien Theys's avatar Sébastien Theys
Browse files

[IMP] tools,website: correctly handle favicon and .ico files

Previously the image that we uploaded as a favicon of the website was not
resized, so it could be extremely excessive in size and resolution, and it also
was not guaranteed to be a square.

Now we always resize it appropriately and save it as an ICO file.

The default favicon is now applied to all websites instead of only the first.

task-1958000
PR: #31811
parent ec436c75
No related branches found
No related tags found
No related merge requests found
......@@ -156,7 +156,6 @@
<field name="domain"></field>
<field name="company_id" ref="base.main_company"/>
<field name="user_id" ref="base.public_user"/>
<field name="favicon" type="base64" file="web/static/src/img/favicon.ico"/>
<field name="homepage_id" ref="homepage_page"/>
</record>
......
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import inspect
import logging
import hashlib
......@@ -14,6 +15,7 @@ from odoo.addons.http_routing.models.ir_http import slugify, _guess_mimetype
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.addons.portal.controllers.portal import pager
from odoo.http import request
from odoo.modules.module import get_resource_path
from odoo.osv.expression import FALSE_DOMAIN
from odoo.tools.translate import _
......@@ -100,7 +102,13 @@ class Website(models.Model):
partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner', readonly=False)
menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
homepage_id = fields.Many2one('website.page', string='Homepage')
favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.")
def _default_favicon(self):
img_path = get_resource_path('web', 'static/src/img/favicon.ico')
with tools.file_open(img_path, 'rb') as f:
return base64.b64encode(f.read())
favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.", default=_default_favicon)
theme_id = fields.Many2one('ir.module.module', help='Installed theme')
specific_user_account = fields.Boolean('Specific User Account', help='If True, new accounts will be associated to the current website')
......@@ -122,6 +130,8 @@ class Website(models.Model):
@api.model
def create(self, vals):
self._handle_favicon(vals)
if 'user_id' not in vals:
company = self.env['res.company'].browse(vals.get('company_id'))
vals['user_id'] = company._get_public_user().id if company else self.env.ref('base.public_user').id
......@@ -138,6 +148,8 @@ class Website(models.Model):
@api.multi
def write(self, values):
self._handle_favicon(values)
self._get_languages.clear_cache(self)
if 'company_id' in values and 'user_id' not in values:
company = self.env['res.company'].browse(values['company_id'])
......@@ -149,6 +161,11 @@ class Website(models.Model):
self.env['ir.qweb'].clear_caches()
return result
@api.model
def _handle_favicon(self, vals):
if 'favicon' in vals:
vals['favicon'] = tools.image_process(vals['favicon'], size=(256, 256), crop='center', output_format='ICO')
# ----------------------------------------------------------
# Page Management
# ----------------------------------------------------------
......
......@@ -9,4 +9,5 @@ from . import test_ui
from . import test_views
from . import test_menu
from . import test_page
from . import test_website_favicon
from . import test_website_reset_password
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from PIL import Image
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
from odoo.tools import base64_to_image, image_to_base64
@tagged('post_install', '-at_install')
class TestWebsiteResetPassword(TransactionCase):
def test_01_website_favicon(self):
"""The goal of this test is to make sure the favicon is correctly
handled on the website."""
# Test setting an Ico file directly, done through create
Website = self.env['website']
website = Website.create({
'name': 'Test Website',
'favicon': Website._default_favicon(),
})
image = base64_to_image(website.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)
website.favicon = image_to_base64(image, 'JPEG')
image = base64_to_image(website.favicon)
self.assertEqual(image.format, 'ICO')
self.assertEqual(image.size, (256, 256))
self.assertEqual(image.getpixel((0, 0)), bg_color)
......@@ -4,6 +4,9 @@ import base64
import io
from PIL import Image
# We can preload Ico too because it is considered safe
from PIL import IcoImagePlugin
from random import randrange
from odoo.tools.translate import _
......@@ -71,7 +74,7 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=8
:param colorize: replace the trasparent background by a random color
:type colorize: bool
:param output_format: the output format. Can be PNG, JPEG or GIF.
:param output_format: the output format. Can be PNG, JPEG, GIF, or ICO.
Default to the format of the original image.
BMP is converted to PNG, other formats are converted to JPEG.
:type output_format: string
......@@ -99,7 +102,7 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=8
output_format = (output_format or image.format).upper()
if output_format == 'BMP':
output_format = 'PNG'
elif output_format not in ['PNG', 'JPEG', 'GIF']:
elif output_format not in ['PNG', 'JPEG', 'GIF', 'ICO']:
output_format = 'JPEG'
opt = {'format': output_format}
......
......@@ -125,6 +125,7 @@ _mime_mappings = (
_Entry('image/svg+xml', [b'<'], [
_check_svg,
]),
_Entry('image/x-icon', [b'\x00\x00\x01\x00'], []),
# OLECF files in general (Word, Excel, PPT, default to word because why not?)
_Entry('application/msword', [b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', b'\x0D\x44\x4F\x43'], [
_check_olecf
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment