[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
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- 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
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# -*- 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)
|
||||
+5
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user