-
Notifications
You must be signed in to change notification settings - Fork 23.2k
/
base_document_layout.py
189 lines (157 loc) · 8.26 KB
/
base_document_layout.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools
DEFAULT_PRIMARY = '#000000'
DEFAULT_SECONDARY = '#000000'
class BaseDocumentLayout(models.TransientModel):
"""
Customise the company document layout and display a live preview
"""
_name = 'base.document.layout'
_description = 'Company Document Layout'
company_id = fields.Many2one(
'res.company', default=lambda self: self.env.company, required=True)
logo = fields.Binary(related='company_id.logo', readonly=False)
preview_logo = fields.Binary(related='logo', string="Preview logo")
report_header = fields.Text(related='company_id.report_header', readonly=False)
report_footer = fields.Text(related='company_id.report_footer', readonly=False)
paperformat_id = fields.Many2one(related='company_id.paperformat_id', readonly=False)
external_report_layout_id = fields.Many2one(related='company_id.external_report_layout_id', readonly=False)
font = fields.Selection(related='company_id.font', readonly=False)
primary_color = fields.Char(related='company_id.primary_color', readonly=False)
secondary_color = fields.Char(related='company_id.secondary_color', readonly=False)
custom_colors = fields.Boolean(compute="_compute_custom_colors", readonly=False)
logo_primary_color = fields.Char(compute="_compute_logo_colors")
logo_secondary_color = fields.Char(compute="_compute_logo_colors")
report_layout_id = fields.Many2one('report.layout')
preview = fields.Html(compute='_compute_preview')
@api.depends('logo_primary_color', 'logo_secondary_color', 'primary_color', 'secondary_color')
def _compute_custom_colors(self):
for wizard in self:
logo_primary = wizard.logo_primary_color or ''
logo_secondary = wizard.logo_secondary_color or ''
# Force lower case on color to ensure that FF01AA == ff01aa
wizard.custom_colors = (
wizard.logo and wizard.primary_color and wizard.secondary_color
and not(
wizard.primary_color.lower() == logo_primary.lower()
and wizard.secondary_color.lower() == logo_secondary.lower()
)
)
@api.depends('logo')
def _compute_logo_colors(self):
for wizard in self:
if wizard._context.get('bin_size'):
wizard_for_image = wizard.with_context(bin_size=False)
else:
wizard_for_image = wizard
wizard.logo_primary_color, wizard.logo_secondary_color = wizard_for_image._parse_logo_colors()
@api.depends('report_layout_id', 'logo', 'font', 'primary_color', 'secondary_color')
def _compute_preview(self):
""" compute a qweb based preview to display on the wizard """
for wizard in self:
if wizard.report_layout_id:
ir_qweb = wizard.env['ir.qweb']
wizard.preview = ir_qweb.render('base.layout_preview', {'company': wizard})
else:
wizard.preview = False
@api.onchange('company_id')
def _onchange_company_id(self):
for wizard in self:
wizard.logo = wizard.company_id.logo
wizard.report_header = wizard.company_id.report_header
wizard.report_footer = wizard.company_id.report_footer
wizard.paperformat_id = wizard.company_id.paperformat_id
wizard.external_report_layout_id = wizard.company_id.external_report_layout_id
wizard.font = wizard.company_id.font
wizard.primary_color = wizard.company_id.primary_color
wizard.secondary_color = wizard.company_id.secondary_color
wizard_layout = wizard.env["report.layout"].search([
('view_id.key', '=', wizard.company_id.external_report_layout_id.key)
])
wizard.report_layout_id = wizard_layout or wizard_layout.search([], limit=1)
if not wizard.primary_color:
wizard.primary_color = wizard.logo_primary_color or DEFAULT_PRIMARY
if not wizard.secondary_color:
wizard.secondary_color = wizard.logo_secondary_color or DEFAULT_SECONDARY
@api.onchange('custom_colors')
def _onchange_custom_colors(self):
for wizard in self:
if wizard.logo and not wizard.custom_colors:
wizard.primary_color = wizard.logo_primary_color or DEFAULT_PRIMARY
wizard.secondary_color = wizard.logo_secondary_color or DEFAULT_SECONDARY
@api.onchange('report_layout_id')
def _onchange_report_layout_id(self):
for wizard in self:
wizard.external_report_layout_id = wizard.report_layout_id.view_id
@api.onchange('logo')
def _onchange_logo(self):
for wizard in self:
# It is admitted that if the user puts the original image back, it won't change colors
company = wizard.company_id
# at that point wizard.logo has been assigned the value present in DB
if wizard.logo == company.logo and company.primary_color and company.secondary_color:
continue
if wizard.logo_primary_color:
wizard.primary_color = wizard.logo_primary_color
if wizard.logo_secondary_color:
wizard.secondary_color = wizard.logo_secondary_color
def _parse_logo_colors(self, logo=None, white_threshold=225):
"""
Identifies dominant colors
First resizes the original image to improve performance, then discards
transparent colors and white-ish colors, then calls the averaging
method twice to evaluate both primary and secondary colors.
:param logo: alternate logo to process
:param white_threshold: arbitrary value defining the maximum value a color can reach
:return colors: hex values of primary and secondary colors
"""
self.ensure_one()
logo = logo or self.logo
if not logo:
return False, False
# The "===" gives different base64 encoding a correct padding
logo += b'===' if type(logo) == bytes else '==='
try:
# Catches exceptions caused by logo not being an image
image = tools.image_fix_orientation(tools.base64_to_image(logo))
except Exception:
return False, False
base_w, base_h = image.size
w = int(50 * base_w / base_h)
h = 50
# Converts to RGBA if no alpha detected
image_converted = image.convert(
'RGBA') if 'A' not in image.getbands() else image
image_resized = image_converted.resize((w, h))
colors = []
for color in image_resized.getcolors(w * h):
if not(color[1][0] > white_threshold and
color[1][1] > white_threshold and
color[1][2] > white_threshold) and color[1][3] > 0:
colors.append(color)
if not colors: # May happen when the whole image is white
return False, False
primary, remaining = tools.average_dominant_color(colors)
secondary = tools.average_dominant_color(
remaining)[0] if len(remaining) > 0 else primary
# Lightness and saturation are calculated here.
# - If both colors have a similar lightness, the most colorful becomes primary
# - When the difference in lightness is too great, the brightest color becomes primary
l_primary = tools.get_lightness(primary)
l_secondary = tools.get_lightness(secondary)
if (l_primary < 0.2 and l_secondary < 0.2) or (l_primary >= 0.2 and l_secondary >= 0.2):
s_primary = tools.get_saturation(primary)
s_secondary = tools.get_saturation(secondary)
if s_primary < s_secondary:
primary, secondary = secondary, primary
elif l_secondary > l_primary:
primary, secondary = secondary, primary
return tools.rgb_to_hex(primary), tools.rgb_to_hex(secondary)
@api.model
def action_open_base_document_layout(self, action_ref=None):
if not action_ref:
action_ref = 'base.action_base_document_layout_configurator'
return self.env.ref(action_ref).read()[0]
def document_layout_save(self):
# meant to be overridden
return self.env.context.get('report_action') or {'type': 'ir.actions.act_window_close'}