Skip to content
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.

PR: #31811
  • Loading branch information...
seb-odoo committed Mar 25, 2019
1 parent cedc2e9 commit 7e5c541f5873fc16d7056864e6b93af8ae7bb89c
Showing with 24 additions and 4 deletions.
  1. +0 −1 addons/website/data/website_data.xml
  2. +13 −1 addons/website/models/
  3. +10 −2 odoo/tools/
  4. +1 −0 odoo/tools/
@@ -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"/>

@@ -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 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 import _

@@ -100,7 +102,13 @@ def _default_social_twitter(self):
partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner', readonly=False)
menu_id = fields.Many2one('', compute='_compute_menu', string='Main Menu')
homepage_id = fields.Many2one('', 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(

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 @@ def _compute_menu(self):

def create(self, vals):

if 'user_id' not in vals:
company = self.env[''].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 @@ def create(self, vals):

def write(self, values):

if 'company_id' in values and 'user_id' not in values:
company = self.env[''].browse(values['company_id'])
@@ -5,6 +5,9 @@
import io

from PIL import Image
# Ico is considered safe
from PIL import IcoImagePlugin

from random import randrange

from import _
@@ -72,7 +75,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 format: the output format. Can be PNG, JPEG or GIF.
:param 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 format: string
@@ -101,7 +104,7 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=8
format = (format or image.format).upper()
if format == 'BMP':
format = 'PNG'
elif format not in ['PNG', 'JPEG', 'GIF']:
elif format not in ['PNG', 'JPEG', 'GIF', 'ICO']:
format = 'JPEG'

opt = {'format': format}
@@ -282,6 +285,11 @@ def image_resize_images(vals,
vals[small_name] = False

def update_field_value_to_ico(vals, field='favicon'):
if field in vals:
vals[field] = image_process(vals[field], size=(256, 256), crop='center', format='ICO')

def image_data_uri(base64_source):
"""This returns data URL scheme according RFC 2397
( for all kind of supported images
@@ -125,6 +125,7 @@ def _check_svg(data):
_Entry('image/svg+xml', [b'<'], [
_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'], [

0 comments on commit 7e5c541

Please sign in to comment.
You can’t perform that action at this time.