From 88fed49c65d9d5f3febfad46cf4f70ad7d327b2a Mon Sep 17 00:00:00 2001 From: Wesley Oliveira Date: Fri, 18 Sep 2026 11:30:22 -0300 Subject: [PATCH] [ADD] web_theme_color: add new module --- web_theme_color/README.rst | 100 ++++ web_theme_color/__init__.py | 1 + web_theme_color/__manifest__.py | 34 ++ web_theme_color/data/ir_config_parameter.xml | 14 + web_theme_color/models/__init__.py | 2 + web_theme_color/models/ir_config_parameter.py | 32 ++ web_theme_color/models/web_theme_color.py | 80 ++++ web_theme_color/pyproject.toml | 3 + web_theme_color/readme/CONTRIBUTORS.md | 3 + web_theme_color/readme/DESCRIPTION.md | 15 + web_theme_color/readme/USAGE.md | 17 + web_theme_color/static/description/icon.png | Bin 0 -> 2873 bytes web_theme_color/static/description/index.html | 452 ++++++++++++++++++ .../src/scss/primary_variables.dark.scss | 6 + .../static/src/scss/primary_variables.scss | 7 + web_theme_color/tests/__init__.py | 1 + web_theme_color/tests/test_web_theme_color.py | 102 ++++ 17 files changed, 869 insertions(+) create mode 100644 web_theme_color/README.rst create mode 100644 web_theme_color/__init__.py create mode 100644 web_theme_color/__manifest__.py create mode 100644 web_theme_color/data/ir_config_parameter.xml create mode 100644 web_theme_color/models/__init__.py create mode 100644 web_theme_color/models/ir_config_parameter.py create mode 100644 web_theme_color/models/web_theme_color.py create mode 100644 web_theme_color/pyproject.toml create mode 100644 web_theme_color/readme/CONTRIBUTORS.md create mode 100644 web_theme_color/readme/DESCRIPTION.md create mode 100644 web_theme_color/readme/USAGE.md create mode 100644 web_theme_color/static/description/icon.png create mode 100644 web_theme_color/static/description/index.html create mode 100644 web_theme_color/static/src/scss/primary_variables.dark.scss create mode 100644 web_theme_color/static/src/scss/primary_variables.scss create mode 100644 web_theme_color/tests/__init__.py create mode 100644 web_theme_color/tests/test_web_theme_color.py diff --git a/web_theme_color/README.rst b/web_theme_color/README.rst new file mode 100644 index 0000000..46264be --- /dev/null +++ b/web_theme_color/README.rst @@ -0,0 +1,100 @@ +=============== +Web Theme Color +=============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f7a4de7e94ea4f2272deb4559029be1c6453a345544d6c79412c0f1512076584 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-Escodoo%2Fserver--addons-lightgray.png?logo=github + :target: https://github.com/Escodoo/server-addons/tree/18.0/web_theme_color + :alt: Escodoo/server-addons + +|badge1| |badge2| |badge3| + +Set the Odoo backend brand color from a system parameter, without +writing a module for each customer. + +The navbar, its bottom border and the primary buttons follow the color. +Link and focus colors keep the standard Odoo action teal. + +The color is a scss variable, not a runtime style, so changing the +parameter rewrites the theme scss file and rebuilds the web assets. The +change is global for the database: every company and every user sees the +same color. + +A few spots keep the Odoo purple because the core hardcodes it instead +of reading the variable: the table picker of the html editor and the +``theme-color`` meta tag, which colors the browser bar on mobile. +Document layout colors (PDF and email) and the Website theme palette are +separate settings. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Go to **Settings > Technical > Parameters > System Parameters** and +change the value of these two parameters, created by the module: + +- ``web_theme_color.primary`` (default ``#71639e``): brand color of the + backend. +- ``web_theme_color.primary_dark`` (default ``#71639e``): brand color + in Enterprise dark mode. + +The value is a hexadecimal color such as ``#D01000``. Both the three +digit (``#D10``) and the six digit form are accepted. Any other value is +rejected when you save the parameter, so a typo cannot break the assets. + +``web_theme_color.primary_dark`` only has an effect on Odoo Enterprise, +which is where dark mode exists. On Community it can be left at the +default. + +After saving, reload the backend with a hard refresh (Ctrl+F5) so the +browser drops the cached assets. The default values are the standard +Odoo colors, so installing the module alone changes nothing. + +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 +------- + +* Escodoo + +Contributors +------------ + +- ``Escodoo ``\ \_: + + - Wesley Oliveira wesley.oliveira@escodoo.com.br + +Maintainers +----------- + +This module is part of the `Escodoo/server-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/web_theme_color/__init__.py b/web_theme_color/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/web_theme_color/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_theme_color/__manifest__.py b/web_theme_color/__manifest__.py new file mode 100644 index 0000000..6d765c4 --- /dev/null +++ b/web_theme_color/__manifest__.py @@ -0,0 +1,34 @@ +# Copyright 2026 - TODAY, Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Web Theme Color", + "summary": """ + Set the backend brand color from a system parameter""", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "Escodoo", + "website": "https://github.com/Escodoo/server-addons", + "depends": [ + "web", + "web_editor", + ], + "data": [ + "data/ir_config_parameter.xml", + ], + "assets": { + "web._assets_primary_variables": [ + ( + "prepend", + "web_theme_color/static/src/scss/primary_variables.scss", + ), + ], + "web.dark_mode_variables": [ + ( + "prepend", + "web_theme_color/static/src/scss/primary_variables.dark.scss", + ), + ], + }, + "installable": True, +} diff --git a/web_theme_color/data/ir_config_parameter.xml b/web_theme_color/data/ir_config_parameter.xml new file mode 100644 index 0000000..d608d55 --- /dev/null +++ b/web_theme_color/data/ir_config_parameter.xml @@ -0,0 +1,14 @@ + + + + + web_theme_color.primary + #71639e + + + + web_theme_color.primary_dark + #71639e + + diff --git a/web_theme_color/models/__init__.py b/web_theme_color/models/__init__.py new file mode 100644 index 0000000..7beafac --- /dev/null +++ b/web_theme_color/models/__init__.py @@ -0,0 +1,2 @@ +from . import web_theme_color +from . import ir_config_parameter diff --git a/web_theme_color/models/ir_config_parameter.py b/web_theme_color/models/ir_config_parameter.py new file mode 100644 index 0000000..f7cf319 --- /dev/null +++ b/web_theme_color/models/ir_config_parameter.py @@ -0,0 +1,32 @@ +# Copyright 2026 - TODAY, Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + +from .web_theme_color import THEME_PARAMS + + +class IrConfigParameter(models.Model): + _inherit = "ir.config_parameter" + + def _apply_web_theme_color(self, keys): + if THEME_PARAMS.intersection(keys): + self.env["web.theme.color"]._apply_colors() + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + records._apply_web_theme_color(records.mapped("key")) + return records + + def write(self, vals): + old_keys = set(self.mapped("key")) + res = super().write(vals) + self._apply_web_theme_color(old_keys | set(self.mapped("key"))) + return res + + def unlink(self): + keys = self.mapped("key") + res = super().unlink() + self._apply_web_theme_color(keys) + return res diff --git a/web_theme_color/models/web_theme_color.py b/web_theme_color/models/web_theme_color.py new file mode 100644 index 0000000..b99be0e --- /dev/null +++ b/web_theme_color/models/web_theme_color.py @@ -0,0 +1,80 @@ +# Copyright 2026 - TODAY, Escodoo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import re + +from odoo import api, models +from odoo.exceptions import ValidationError + +PARAM_PRIMARY = "web_theme_color.primary" +PARAM_PRIMARY_DARK = "web_theme_color.primary_dark" + +DEFAULT_PRIMARY = "#71639e" +DEFAULT_PRIMARY_DARK = "#71639e" + +COLOR_PATTERN = re.compile(r"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$") + +# (parameter, default, asset to rewrite, bundle it belongs to) +THEME_ASSETS = ( + ( + PARAM_PRIMARY, + DEFAULT_PRIMARY, + "/web_theme_color/static/src/scss/primary_variables.scss", + "web._assets_primary_variables", + ), + ( + PARAM_PRIMARY_DARK, + DEFAULT_PRIMARY_DARK, + "/web_theme_color/static/src/scss/primary_variables.dark.scss", + "web.dark_mode_variables", + ), +) + +THEME_PARAMS = frozenset(asset[0] for asset in THEME_ASSETS) + +SCSS_TEMPLATE = """\ +// Generated from the system parameter %(param)s. +// Change that parameter instead of editing this file. +$o-community-color: %(color)s !default; +$o-enterprise-color: %(color)s !default; +$o-brand-odoo: %(color)s !default; +$o-brand-primary: %(color)s !default; +""" + + +class WebThemeColor(models.AbstractModel): + _name = "web.theme.color" + _description = "Backend Theme Color" + + @api.model + def _check_color(self, param, color): + if not COLOR_PATTERN.match(color or ""): + raise ValidationError( + self.env._( + "%(color)s is not a valid color for %(param)s.\n\n" + "Use a hexadecimal value such as #D01000.", + color=color or "", + param=param, + ) + ) + return color + + @api.model + def _apply_colors(self): + """Rewrite the theme scss files from the system parameters. + + The colors are scss variables, so they are only picked up when the + bundle is compiled. Saving the asset replaces the file and invalidates + the bundle; on the default color the customization is removed instead, + so no attachment is left behind. + """ + assets = self.env["web_editor.assets"].sudo() + params = self.env["ir.config_parameter"].sudo() + for param, default, url, bundle in THEME_ASSETS: + color = params.get_param(param) or default + self._check_color(param, color) + if color.lower() == default.lower(): + assets.reset_asset(url, bundle) + continue + content = SCSS_TEMPLATE % {"color": color, "param": param} + assets.save_asset(url, bundle, content, "scss") diff --git a/web_theme_color/pyproject.toml b/web_theme_color/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/web_theme_color/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_theme_color/readme/CONTRIBUTORS.md b/web_theme_color/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..2f9bd6d --- /dev/null +++ b/web_theme_color/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +* `Escodoo `_: + + * Wesley Oliveira diff --git a/web_theme_color/readme/DESCRIPTION.md b/web_theme_color/readme/DESCRIPTION.md new file mode 100644 index 0000000..1bbdb95 --- /dev/null +++ b/web_theme_color/readme/DESCRIPTION.md @@ -0,0 +1,15 @@ +Set the Odoo backend brand color from a system parameter, without writing a +module for each customer. + +The navbar, its bottom border and the primary buttons follow the color. Link +and focus colors keep the standard Odoo action teal. + +The color is a scss variable, not a runtime style, so changing the parameter +rewrites the theme scss file and rebuilds the web assets. The change is global +for the database: every company and every user sees the same color. + +A few spots keep the Odoo purple because the core hardcodes it instead of +reading the variable: the table picker of the html editor and the +`theme-color` meta tag, which colors the browser bar on mobile. Document +layout colors (PDF and email) and the Website theme palette are separate +settings. diff --git a/web_theme_color/readme/USAGE.md b/web_theme_color/readme/USAGE.md new file mode 100644 index 0000000..8fb6ce1 --- /dev/null +++ b/web_theme_color/readme/USAGE.md @@ -0,0 +1,17 @@ +Go to **Settings > Technical > Parameters > System Parameters** and change the +value of these two parameters, created by the module: + +- `web_theme_color.primary` (default `#71639e`): brand color of the backend. +- `web_theme_color.primary_dark` (default `#71639e`): brand color in Enterprise + dark mode. + +The value is a hexadecimal color such as `#D01000`. Both the three digit +(`#D10`) and the six digit form are accepted. Any other value is rejected when +you save the parameter, so a typo cannot break the assets. + +`web_theme_color.primary_dark` only has an effect on Odoo Enterprise, which is +where dark mode exists. On Community it can be left at the default. + +After saving, reload the backend with a hard refresh (Ctrl+F5) so the browser +drops the cached assets. The default values are the standard Odoo colors, so +installing the module alone changes nothing. diff --git a/web_theme_color/static/description/icon.png b/web_theme_color/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..12ab0051e3b9ac413f0f0016b7cff54f02fc763e GIT binary patch literal 2873 zcmV-93&!+`P)`uGecH7++*a9tqf=D4K3Mya}O@tpzNQ@yQCf@Kr zF!+nnKqBHT7^8%kXfz5&3vvk;1t}w7Z?N*D?A{ zHrd_G%z4iDJn#2;p6~mP`2CNcJjDNjrs?X8sbe)Nav^9t4K(ppCb-d2d)&lZ>k%SMT{9YBg-a+&W{!hd#wDr8Wl{eXS z*$7*96lqpl{PUr=s8^hIiDg}g*f5f1&&S6&>%w7_k{mf$=0jJV!9`c@;PA^Op1%JL zjvcP7X-sQIgb*+?KF9@EjIw?AMgZbC#&KNql%Q6tlgni}^ZZSmdHyEeJ2=g=55CEv zmnMkgHHh!qH~xB6FC&#;=S7>@bLA+-%~=4VD8ltxIF5^^DFQzvj6#%BWV03ng*-|r z04k*>&p&>cm!5nFzZI@poU2BJnbtV_!&|uU$}y~h2|yTz*p5r9WG&z2} zgzMT!DN#yCDn%xfCZEq?8U_G%-Q$HP-sbto4&yXiD<1EPFO7UL&G{c2<=nkn(2e#} z^@9M%@$h}0bjoDo#v;0Q@};9FB8of+1YzLQY&J2?6oo>LOfJpk*PX*9*NpSZGe>#u zk%LT6G*&F`6(eG3TaNRu7-jszVWetTW!G!rx-L-^kx8c+94Mev4;4F(2?8GipD+k; zT^B>w$>nooEsH%@Z0CZHj&bnk*sdZMTrtMhorAOY5aYNW zj^iMu#L8sIWYP#BmUN9U4B9_|Ps?vnt=7opvgGFp>%@^N2Od4dtIr-Kik7z0m!uI& z6O3QDnTtL##=x*O8*qf7`Rm>4UQp;#P5$@Lpq;;yjH>WJ5(YUt#1IdZuydp@>#C#ExmKro8|zO=gyYw6f?D@) zO;dA3C`t5Rlo9o%65&TKb${xF|7l7xRh|Jrss7IhDMeQ!8kOD#v=fMk38}gh#3yucyF^u$%5|2^EB4zY-za^N18QYBnvew`D8=+@OK&R~LPuuFnak z5RxG1S0Y4rSxl9B-?Ncn+@jtppZ0~in(HN$Qhl2d2r+LOZT9>&S#ggO^|0ZoX&U8; zxe^-mDG~ECqBhz4)_{zn-quh%Bicmtw?+sw)hV4`&+qGbY?M+kRi3RJQuVP$%xk8m zlMs=>LhJMrN-3f!BJR6pqJ1xO8qvGCE0Z{hwdp2N6m<>Qe#;6W1VV_mm6ZWIN8uMj|6uCNY-{rRJ5;e(WU#y58<6D+!hbBs>^A*b2-{&Q*;#Zg1T713H;K=R@>9XTTE9 zP&rKx*J&YTyG-?qTh;9$q{Oj3+~(r@7#~EU&JsXZigKwlbI87Nw1tq}(m6FzJI$+4 zMxxFVKvNp!iT?C2I~5~o4*?0wC$w$y(y@7a9Q~pZa&FkF_2^vBM`DdIPVgoACnMTE zL^~sz)5(t(B_={j$z;dK?IVo4u1Q7!R3n?fOf%0ptS1tBDwuA^x>gM$O;x=tL$)Tfg*Q?P{c2@pqu zU)=d88g(1fG#DJnBc)_&YMNTDz6OSkKq(E~Fv(`KY#rH5*2++;Ho4~~4-ojtm#f-) z?x${z;$$;}Qi|7Kdz(idcoxIdIcw)OOw&Nub!^AQwrw;`>!#}I&}cNS}g>5^ShC!i_>z<-r2tA#lnjNu~rZZ5;lh5Z6Lh%04GI!naN2*7BwiZWO z%2EeVdv{%ZYqY>N-{OHm$}q@V7UhWw5AEAey>4^P?j5AlCaIK3%lE0(8wfzx^#xDZ zoh~Dk($F*m%gVBO(-7%&8ow3r`(Ho8FYnmL2DVbnZdfu20OFh1e?M-8%U6Bd^C(v; zScMc{`pRe7d--kvS}mWZZ6kzappeHf^poDZP)4-H&_LHUHWZ7b(L-7u zAciSg8V#p4 zVHhW5$Eh+x({yyhAeYOrX?O@-*O{KI^7A|I<>B8QVEe#MEYn(c+#RUPc3pF87$=+5 z7J?9hjF}>r$?(qG$9Qnxe!?K)oZVxXrh#dixSmI&VWX7ldI$4~kW!%=DbkrV!^1=5 zvKCPkbN_u$@}uwmj#RVArotAaSlN?&;+E@gi@h*;-R%Vp$EGw>VPtfOo4@{f#?KuE zm=)7@Wf>^snVg))^Bj~m+Y<~Fx~KLIzHyYhe((pviouqF5t7bEy+Yh_!)>t_tPT+X zVH7c4t5LNZeDuma+<5b+$>lS|aZJ;8a9xi$0wKY&=FV+48xFs@`$6_U{3>S+??hM0 zOJWkNYDUb5+iFpssh|wS7r%TpS6zECLJ0gIpfpio$BvQi5#pyFe}Q{`wvQom8v~ia zRgVooTS{lS6r>D;ZNrHuU^O2v0>bXk7=45J37f-Kl}rwLv_YC zo`+ar9as$FmK$!5y&##tw_F5a$W*n8-E#TlXD;F5OLueVjrVxyzGpb2xRbQ8a@C+$ zh;Q9+d+e?;5gjxghjO)oP?FJsF}4hhu1g#MTFduVONdVkE1hOTc8IJ|M9GsIt+j&Z z+sJbD{dKvxCUuJG5elhI>q(I({QN z2z?sv45gWOvE4eFE>qZUjkoRBK+sicvFHdR7XOOU0%|W1q%;d9)-MBq*+VWTdKUiy XXqTGI$C0NT00000NkvXXu0mjf?G|(} literal 0 HcmV?d00001 diff --git a/web_theme_color/static/description/index.html b/web_theme_color/static/description/index.html new file mode 100644 index 0000000..76cde7f --- /dev/null +++ b/web_theme_color/static/description/index.html @@ -0,0 +1,452 @@ + + + + + +Web Theme Color + + + +
+

Web Theme Color

+ + +

Beta License: AGPL-3 Escodoo/server-addons

+

Set the Odoo backend brand color from a system parameter, without +writing a module for each customer.

+

The navbar, its bottom border and the primary buttons follow the color. +Link and focus colors keep the standard Odoo action teal.

+

The color is a scss variable, not a runtime style, so changing the +parameter rewrites the theme scss file and rebuilds the web assets. The +change is global for the database: every company and every user sees the +same color.

+

A few spots keep the Odoo purple because the core hardcodes it instead +of reading the variable: the table picker of the html editor and the +theme-color meta tag, which colors the browser bar on mobile. +Document layout colors (PDF and email) and the Website theme palette are +separate settings.

+

Table of contents

+ +
+

Usage

+

Go to Settings > Technical > Parameters > System Parameters and +change the value of these two parameters, created by the module:

+
    +
  • web_theme_color.primary (default #71639e): brand color of the +backend.
  • +
  • web_theme_color.primary_dark (default #71639e): brand color +in Enterprise dark mode.
  • +
+

The value is a hexadecimal color such as #D01000. Both the three +digit (#D10) and the six digit form are accepted. Any other value is +rejected when you save the parameter, so a typo cannot break the assets.

+

web_theme_color.primary_dark only has an effect on Odoo Enterprise, +which is where dark mode exists. On Community it can be left at the +default.

+

After saving, reload the backend with a hard refresh (Ctrl+F5) so the +browser drops the cached assets. The default values are the standard +Odoo colors, so installing the module alone changes nothing.

+
+
+

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

+
    +
  • Escodoo
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the Escodoo/server-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/web_theme_color/static/src/scss/primary_variables.dark.scss b/web_theme_color/static/src/scss/primary_variables.dark.scss new file mode 100644 index 0000000..80f191d --- /dev/null +++ b/web_theme_color/static/src/scss/primary_variables.dark.scss @@ -0,0 +1,6 @@ +// Default backend brand color for Enterprise dark mode. +// Rewritten from the system parameter web_theme_color.primary_dark. +$o-community-color: #71639e !default; +$o-enterprise-color: #71639e !default; +$o-brand-odoo: #71639e !default; +$o-brand-primary: #71639e !default; diff --git a/web_theme_color/static/src/scss/primary_variables.scss b/web_theme_color/static/src/scss/primary_variables.scss new file mode 100644 index 0000000..ee71938 --- /dev/null +++ b/web_theme_color/static/src/scss/primary_variables.scss @@ -0,0 +1,7 @@ +// Default backend brand color. Loaded first in web._assets_primary_variables so +// the core !default assignments keep this value. +// Rewritten from the system parameter web_theme_color.primary. +$o-community-color: #71639e !default; +$o-enterprise-color: #71639e !default; +$o-brand-odoo: #71639e !default; +$o-brand-primary: #71639e !default; diff --git a/web_theme_color/tests/__init__.py b/web_theme_color/tests/__init__.py new file mode 100644 index 0000000..5a35afc --- /dev/null +++ b/web_theme_color/tests/__init__.py @@ -0,0 +1 @@ +from . import test_web_theme_color diff --git a/web_theme_color/tests/test_web_theme_color.py b/web_theme_color/tests/test_web_theme_color.py new file mode 100644 index 0000000..e941a2d --- /dev/null +++ b/web_theme_color/tests/test_web_theme_color.py @@ -0,0 +1,102 @@ +# Copyright 2026 - TODAY, Wesley Oliveira +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from odoo.exceptions import ValidationError +from odoo.tests import TransactionCase, tagged + +from odoo.addons.web_theme_color.models.web_theme_color import ( + DEFAULT_PRIMARY, + PARAM_PRIMARY, + THEME_ASSETS, +) + +CUSTOM_COLOR = "#d01000" + + +@tagged("post_install", "-at_install") +class TestWebThemeColor(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.params = cls.env["ir.config_parameter"].sudo() + cls.assets = cls.env["web_editor.assets"].sudo() + cls.primary_url, cls.primary_bundle = next( + (url, bundle) + for param, _default, url, bundle in THEME_ASSETS + if param == PARAM_PRIMARY + ) + + def setUp(self): + super().setUp() + # The database may already carry a customized color, so start every + # test from the default one. + for param, default, _url, _bundle in THEME_ASSETS: + parameter = self.params.search([("key", "=", param)]) + if parameter: + parameter.write({"value": default}) + else: + self.params.create({"key": param, "value": default}) + + def _custom_attachment(self, url, bundle): + custom_url = self.assets._make_custom_asset_url(url, bundle) + return self.assets._get_custom_attachment(custom_url) + + def _primary_attachment(self): + return self._custom_attachment(self.primary_url, self.primary_bundle) + + def _set_primary(self, color): + self.params.set_param(PARAM_PRIMARY, color) + + def test_default_keeps_the_assets_untouched(self): + """The default color leaves no scss override behind.""" + for _param, _default, url, bundle in THEME_ASSETS: + self.assertFalse(self._custom_attachment(url, bundle)) + + def test_custom_color_saves_the_asset(self): + """A color other than the default writes the scss override.""" + self._set_primary(CUSTOM_COLOR) + attachment = self._primary_attachment() + self.assertTrue(attachment) + content = base64.b64decode(attachment.datas).decode("utf-8") + self.assertIn(CUSTOM_COLOR, content) + self.assertIn("$o-brand-primary", content) + + def test_short_form_color_is_accepted(self): + """The three digit form is a valid color.""" + self._set_primary("#d10") + content = base64.b64decode(self._primary_attachment().datas).decode("utf-8") + self.assertIn("#d10", content) + + def test_default_color_resets_the_asset(self): + """Going back to the default removes the override instead of writing it.""" + self._set_primary(CUSTOM_COLOR) + self.assertTrue(self._primary_attachment()) + self._set_primary(DEFAULT_PRIMARY) + self.assertFalse(self._primary_attachment()) + + def test_invalid_color_is_rejected(self): + """A value that is not a hexadecimal color cannot be saved.""" + with self.assertRaises(ValidationError): + self._set_primary("red") + + def test_renaming_the_key_resets_the_asset(self): + """A parameter renamed away from a theme key stops applying its color.""" + self._set_primary(CUSTOM_COLOR) + self.assertTrue(self._primary_attachment()) + parameter = self.params.search([("key", "=", PARAM_PRIMARY)]) + parameter.key = "web_theme_color.primary_renamed" + self.assertFalse(self._primary_attachment()) + + def test_unlinking_the_parameter_resets_the_asset(self): + """Deleting the parameter falls back to the default color.""" + self._set_primary(CUSTOM_COLOR) + self.assertTrue(self._primary_attachment()) + self.params.search([("key", "=", PARAM_PRIMARY)]).unlink() + self.assertFalse(self._primary_attachment()) + + def test_unrelated_parameter_leaves_the_assets_alone(self): + """Other system parameters do not touch the theme assets.""" + self.params.set_param("web_theme_color_unrelated.param", "whatever") + self.assertFalse(self._primary_attachment())