[FIX] web, base: make company-specific report assets static

Before this commit, the company specific colors and font were
implemented by systematically overwriting a "virtual" report SCSS
asset file before rendering each report.
This caused many issues, forced frequent asset bundles recomputations
(performance problem + cache invalidation causing random bugs).
And it could simply not work in a multi-company setup where multiple
styles are involved, as the asset management could be made not
thread-safe.

PR #44225 was a first attempt to mitigate the numerous problems by
making the asset bundle invalidation less frequent. But the problems
were still present and a more complete solution was necessary for
multi-company setups.

Besides, the design of the bundle forbade making company specific assets.

This commit uses a different approach: instead of having a
company-specific asset that needs to be constantly updated, a global
"multi-company" asset is maintained and included in the report assets.
It only needs to be generated when a company style changes, not for
every rendering operation.

Unfortunately this change cannot be fully performed without updating the
template declarations, so it will require an update of the `web` (or
`base`) module to be operational.

As this represents a rather invasive change in a stable branch, extensive
testing was conducted to minimize the effects and ensure proper
degradation of features for production deployments where the new code
would be deployed without forcing an update of the `web` module:
- The report SCSS files were left untouched, to prevent any bundle
  invalidation, ensuring that old cached assets would remain valid. This
  means that single-company setups should not see any visible difference
  after pulling the code (with or without updating `web`).
  SCSS cleanup will be done later.
- Existing Python methods were kept but emptied, to make sure that
  old templates and code would not crash.
- For multi-companies, the last used colors will be applied for all
  reports until the `web` module is updated. The old behavior was not
  working correctly anyways, so the degradation is actually limited.
- For all setups, changing the colors after deploying this patch will
  have no effect unless the `web` module is updated.

/UPDATED FOR master on top of #44393/:
- removed the `res_company.update_scss()` method entirely
- fixed the report SCSS styles, as the variable names referred to the old
  behavior, e.g. "$o-company-primary-color" is nonsense.
  Renamed to "$o-default-report-primary-color" etc.

--
Improves #44225
Forward of #44393

opw-2168623
opw-2171040

closes odoo/odoo#46647

X-original-commit: a5b1421aecf27b1de408434d3bb6d7ac81f57dc3
Signed-off-by: Olivier Dony (odo) <odo@openerp.com>
This commit is contained in:
Lucas Perais (lpe)
2020-03-02 13:22:09 +00:00
parent 5c127860fc
commit 9ce6136be7
10 changed files with 236 additions and 86 deletions
+7
View File
@@ -25,5 +25,12 @@
<field name="image">/web/static/img/preview_clean.png</field>
<field name="pdf">/web/static/pdf/preview_clean.pdf</field>
</record>
<record id="asset_styles_company_report" model="ir.attachment">
<field name="datas" model="res.company" eval="obj()._get_asset_style_b64()"/>
<field name="mimetype">text/scss</field>
<field name="name">res.company.scss</field>
<field name="type">binary</field>
<field name="url">/web/static/src/scss/asset_styles_company_report.scss</field>
</record>
</data>
</odoo>
+39
View File
@@ -2,6 +2,7 @@
import babel.dates
import pytz
from lxml import etree
import base64
from odoo import _, api, fields, models
from odoo.osv.expression import AND
@@ -360,3 +361,41 @@ class Base(models.AbstractModel):
})
return filter_values
class ResCompany(models.Model):
_inherit = 'res.company'
@api.model
def create(self, values):
res = super().create(values)
if 'primary_color' in values or 'secondary_color' in values or 'font' in values:
self._update_asset_style()
return res
def write(self, values):
res = super().write(values)
if 'primary_color' in values or 'secondary_color' in values or 'font' in values:
self._update_asset_style()
return res
def _get_asset_style_b64(self):
template_style = self.env.ref('web.styles_company_report', raise_if_not_found=False)
if not template_style:
return ''
# One bundle for everyone, so this method
# necessarily updates the style for every company at once
company_ids = self.sudo().search([])
company_styles = template_style.render({
'company_ids': company_ids,
})
return base64.b64encode((company_styles))
def _update_asset_style(self):
asset_attachment = self.env.ref('web.asset_styles_company_report', raise_if_not_found=False)
if not asset_attachment:
return
asset_attachment = asset_attachment.sudo()
b64_val = self._get_asset_style_b64()
if b64_val != asset_attachment.datas:
asset_attachment.write({'datas': b64_val})
@@ -11,7 +11,7 @@
margin-right: 16px;
}
h3 {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
font-weight: 700;
font-size: 1.25rem;
max-width: 300px;
@@ -22,12 +22,12 @@
white-space: nowrap;
}
ul {
border-top: 1px solid $o-company-secondary-color;
border-bottom: 1px solid $o-company-secondary-color;
border-top: 1px solid $o-default-report-secondary-color;
border-bottom: 1px solid $o-default-report-secondary-color;
padding: 4px 0;
margin: 0 0 4px 0;
li {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
}
}
@@ -39,7 +39,7 @@
min-height: 620px;
strong {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
.table {
border-top: 1px solid gray('300');
@@ -49,10 +49,10 @@
border-top: none;
}
h2 {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
thead tr th {
color: $o-company-secondary-color
color: $o-default-report-secondary-color
}
tbody {
color: gray('700');
@@ -84,7 +84,7 @@
border-top: 1px solid gray('400') !important;
}
strong {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
}
}
+8 -8
View File
@@ -16,23 +16,23 @@
.o_boxed_footer {
margin-top: 200px;
white-space: nowrap;
border-top: 3px solid $o-company-secondary-color;
border-top: 3px solid $o-default-report-secondary-color;
ul {
margin: 4px 0;
}
}
.o_report_layout_boxed {
#total strong {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
#informations strong {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
> h2 {
text-transform: uppercase;
}
h2 span {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
table {
border: 1px solid gray('700');
@@ -41,7 +41,7 @@
tr th {
text-transform: uppercase;
border: 1px solid gray('700');
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
}
tbody {
@@ -59,12 +59,12 @@
border-bottom: 1px solid gray('700');
}
&.o_line_section td {
background-color: rgba($o-company-primary-color, 0.7);
background-color: rgba($o-default-report-primary-color, 0.7);
color: #fff;
}
&.is-subtotal,
td.o_price_total {
background-color: rgba($o-company-secondary-color, 0.1);
background-color: rgba($o-default-report-secondary-color, 0.1);
}
}
}
@@ -92,7 +92,7 @@
}
&:last-child td,
&.o_total td {
background-color: rgba($o-company-primary-color, 0.9);
background-color: rgba($o-default-report-primary-color, 0.9);
color: #fff;
&:first-child {
+10 -10
View File
@@ -8,14 +8,14 @@
.o_clean_footer {
margin: 0 3px;
margin-top: 200px;
border-top: 3px solid $o-company-secondary-color;
border-top: 3px solid $o-default-report-secondary-color;
h4 {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
font-weight: bolder;
}
.pagenumber {
border: 3px solid $o-company-primary-color;
background-color: $o-company-secondary-color;
border: 3px solid $o-default-report-primary-color;
background-color: $o-default-report-secondary-color;
color: white;
padding: 4px 8px;
text-align: center;
@@ -23,11 +23,11 @@
}
.o_report_layout_clean {
h1, h2, h3 {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
font-weight: bolder;
}
strong {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
table {
&.o_main_table {
@@ -35,9 +35,9 @@
}
thead {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
tr th {
border-top: 3px solid $o-company-secondary-color !important;
border-top: 3px solid $o-default-report-secondary-color !important;
text-transform: uppercase;
}
tr th:first-child {
@@ -50,7 +50,7 @@
border-top: none;
}
tr:last-child td {
border-bottom: 3px solid $o-company-secondary-color;
border-bottom: 3px solid $o-default-report-secondary-color;
}
tr {
td {
@@ -63,7 +63,7 @@
}
#total {
strong {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
}
/*Total table*/
@@ -7,19 +7,19 @@
.o_report_layout_standard {
h2 {
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
#informations strong {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
#total strong{
color: $o-company-primary-color;
color: $o-default-report-primary-color;
}
table {
thead {
color: $o-company-secondary-color;
color: $o-default-report-secondary-color;
}
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
$o-company-font: 'Lato' !default;
$o-company-primary-color: rgb(0, 0, 0) !default;
$o-company-secondary-color: rgb(0, 0, 0) !default;
$o-default-report-font: 'Lato' !default;
$o-default-report-primary-color: rgb(0, 0, 0) !default;
$o-default-report-secondary-color: rgb(0, 0, 0) !default;
html, body {
height: 100%;
@@ -10,7 +10,7 @@ html, body {
body {
color: #000 !important;
word-wrap: break-word;
font-family: $o-company-font;
font-family: $o-default-report-font;
}
.border-black td, .border-black th {
border-top: 1px solid black !important;
+155 -17
View File
@@ -19,13 +19,13 @@
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/fonts.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/report_styles.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/report.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_standard.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_background.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_boxed.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_clean.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/asset_styles_company_report.scss"/>
<script type="text/javascript" src="/web/static/src/js/services/session.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
@@ -142,7 +142,7 @@
</template>
<template id="external_layout_background">
<div class="header">
<div t-attf-class="o_company_#{company.id}_layout header">
<div class="o_background_header">
<div class="float-right">
<h3 class="mt0 text-right" t-field="company.report_header"/>
@@ -159,12 +159,12 @@
</div>
</div>
<div class="article o_report_layout_background" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<div t-attf-class="o_company_#{company.id}_layout article o_report_layout_background" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-raw="0"/>
</div>
<div class="footer o_background_footer">
<div t-attf-class="o_company_#{company.id}_layout footer o_background_footer">
<div class="text-center">
<ul class="list-inline">
<li t-if="company.phone" class="list-inline-item"><i class="fa fa-phone" role="img" aria-label="Phone" title="Phone"/> <span t-field="company.phone"/></li>
@@ -184,7 +184,7 @@
</template>
<template id="external_layout_boxed">
<div class="header">
<div t-attf-class="header o_company_#{company.id}_layout">
<div class="o_boxed_header">
<div class="row mb8">
<div class="col-6">
@@ -201,12 +201,12 @@
</div>
</div>
<div class="article o_report_layout_boxed" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<div t-attf-class="article o_report_layout_boxed o_company_#{company.id}_layout" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-raw="0"/>
</div>
<div class="footer o_boxed_footer">
<div t-attf-class="footer o_boxed_footer o_company_#{company.id}_layout">
<div class="text-center">
<ul class="list-inline">
<li t-if="company.phone" class="list-inline-item"><span t-field="company.phone"/></li>
@@ -223,7 +223,7 @@
</template>
<template id="external_layout_clean">
<div class="header">
<div t-attf-class="header o_company_#{company.id}_layout">
<div class="o_clean_header">
<div class="row">
<div class="col-6">
@@ -242,12 +242,12 @@
</div>
</div>
<div class="article o_report_layout_clean" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<div t-attf-class="article o_report_layout_clean o_company_#{company.id}_layout" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-raw="0"/>
</div>
<div class="footer o_clean_footer">
<div t-attf-class="footer o_clean_footer o_company_#{company.id}_layout">
<div class="row mt8">
<div class="col-3">
<span t-field="company.report_footer"/>
@@ -269,7 +269,7 @@
</template>
<template id="external_layout_standard">
<div class="header">
<div t-attf-class="header o_company_#{company.id}_layout">
<div class="row">
<div class="col-3 mb4">
<img t-if="company.logo" t-att-src="image_data_uri(company.logo)" style="max-height: 45px;" alt="Logo"/>
@@ -290,12 +290,12 @@
</div>
</div>
<div class="article o_report_layout_standard" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<div t-attf-class="article o_report_layout_standard o_company_#{company.id}_layout" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-call="web.address_layout"/>
<t t-raw="0"/>
</div>
<div class="footer o_standard_footer">
<div t-attf-class="footer o_standard_footer o_company_#{company.id}_layout">
<div class="text-center" style="border-top: 1px solid black;">
<ul class="list-inline mb4">
<!-- using the list-inline-item class from bootstrap causes weird behaviours in pdf report
@@ -335,8 +335,6 @@
</t>
</t>
<t t-esc="company.update_scss()"/>
<t t-if="company.external_report_layout_id" t-call="{{company.external_report_layout_id.key}}"><t t-raw="0"/></t>
<t t-else="else" t-call="web.external_layout_standard"><t t-raw="0"/></t>
@@ -358,8 +356,6 @@
</t>
</t>
<t t-esc="company.update_scss()"/>
<div class="header">
<div class="row">
<div class="col-3">
@@ -413,6 +409,148 @@
</t>
</template>
<template id="styles_company_report">
<t t-foreach="company_ids" t-as="company">
<t t-set="font" t-value="company.font or 'Lato'" />
<t t-set="primary" t-value="company.primary_color or 'black'" />
<t t-set="secondary" t-value="company.secondary_color or 'black'" />
<t t-set="layout" t-value="company.external_report_layout_id.key or 'web.external_layout_standard'" />
.o_company_<t t-esc='company.id'/>_layout {
font-family: '<t t-esc="font" />';
<t t-if="layout == 'web.external_layout_background'">
.o_background_header {
h3 {
color: <t t-esc='primary'/>;
}
}
&amp;.o_background_footer {
ul {
li {
color: <t t-esc='secondary'/>;
}
}
}
&amp;.o_report_layout_background {
strong {
color: <t t-esc='secondary'/>;
}
h2 {
color: <t t-esc='primary'/>;
}
thead tr th {
color: <t t-esc='secondary'/>;
}
}
.row > div > table,
div#total table {
&amp;:first-child,
&amp;:last-child,
&amp;.o_subtotal,
&amp;.o_total {
strong {
color: <t t-esc='primary'/>;
}
}
}
</t>
<t t-elif="layout == 'web.external_layout_boxed'">
&amp;.o_report_layout_boxed {
#total strong {
color: <t t-esc='primary'/>;
}
#informations strong {
color: <t t-esc='secondary'/>;
}
h2 span {
color: <t t-esc='primary'/>;
}
table {
thead {
tr th {
color: <t t-esc='secondary'/>;
}
}
tbody tr td {
&amp;.o_line_section td {
background-color: rgba(<t t-esc='primary'/>, 0.7);
}
&amp;.is-subtotal,
td.o_price_total {
background-color: rgba(<t t-esc='secondary'/>, 0.1);
}
}
}
}
.row > div > table,
div#total table {
tr {
&amp;.o_total td {
background-color: rgba(<t t-esc='primary'/>, 0.9);
}
}
}
</t>
<t t-elif="layout == 'web.external_layout_clean'" >
&amp;.o_clean_footer {
border-top: 3px solid <t t-esc='secondary'/>;
h4 {
color: <t t-esc='secondary'/>;
}
.pagenumber {
border: 3px solid <t t-esc='primary'/>;
background-color: <t t-esc='secondary'/>;
}
}
&amp;.o_report_layout_clean {
h1, h2, h3 {
color: <t t-esc='primary'/>;
}
strong {
color: <t t-esc='secondary'/>;
}
table {
thead {
color: <t t-esc='secondary'/>;
tr th {
border-top: 3px solid <t t-esc='secondary'/> !important;
}
}
tbody {
tr:last-child td {
border-bottom: 3px solid <t t-esc='secondary'/>;
}
}
}
#total {
strong {
color: <t t-esc='secondary'/>;
}
}
}
</t>
<t t-elif="layout == 'web.external_layout_standard'">
&amp;.o_report_layout_standard {
h2 {
color: <t t-esc='primary'/>;
}
#informations strong {
color: <t t-esc='secondary'/>;
}
#total strong{
color: <t t-esc='primary'/>;
}
table {
thead {
color: <t t-esc='secondary'/>;
}
}
}
</t>
}
</t>
</template>
<report id="action_report_internalpreview"
model="res.company"
name="web.preview_internalreport"
+1 -11
View File
@@ -7,15 +7,5 @@
<field name="currency_id" ref="base.EUR"/>
<field name="favicon" model="res.company" eval="obj()._get_default_favicon(original=True)"/>
</record>
<record id="main_company_scss" model="ir.attachment">
<!-- Default data : -->
<!-- $o-company-font: 'Lato'; $o-company-primary-color: rgb(0, 0, 0); $o-company-secondary-color: rgb(0, 0, 0); -->
<field name="datas">JG8tY29tcGFueS1mb250OiAnTGF0byc7JG8tY29tcGFueS1wcmltYXJ5LWNvbG9yOiByZ2IoMCwgMCwgMCk7JG8tY29tcGFueS1zZWNvbmRhcnktY29sb3I6IHJnYigwLCAwLCAwKTs=</field>
<field name="mimetype">text/scss</field>
<field name="name">res.company.scss</field>
<field name="type">binary</field>
<field name="url">/web/static/src/scss/report_styles.scss</field>
</record>
</data>
</data>
</odoo>
-24
View File
@@ -299,27 +299,3 @@ class Company(models.Model):
main_company = self.env['res.company'].sudo().search([], limit=1, order="id")
return main_company
def update_scss(self):
""" update the company scss stylesheet """
scss_properties = []
if self.primary_color:
scss_properties.append('$o-company-primary-color:%s;' % self.primary_color)
if self.secondary_color:
scss_properties.append('$o-company-secondary-color:%s;' % self.secondary_color)
if self.font:
scss_properties.append('$o-company-font:%s;' % self.font)
scss_string = '\n'.join(scss_properties)
if not len(scss_string):
scss_string = ""
scss_data = base64.b64encode((scss_string).encode('utf-8'))
attachment = self.env['ir.attachment'].search([('name', '=', 'res.company.scss')])
if attachment.datas != scss_data:
# this invalidates the corresponding asset (write_date is modified),
# so we do it only when necessary
attachment.write({'datas': scss_data})
return ''