From ed18095127b20da516fa5ada943b2dd6d5c84652 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Thu, 18 Jul 2019 15:18:41 +0000 Subject: [PATCH] [IMP] base: Configure document layout The onboarding modal for setting up the few base fields of a company has now been moved to a wizard It is accessible from the general settings, but also in the onboarding section of sale and account modules. The following company settings are editable with that wizard: - Set report **layout**: The user can chose the overall look of the report. The current choices are : *Standard* (default), *Background*, *Boxed* and *Clean*. - Set company **logo**: Changes the company logo. - Set report **colors**: The user can set the primary and secondary colors of the report through a newly added widget allowing to pick a custom color. When changing the **logo**, colors are automatically set to its most dominant colors. > A "Reset colors" button also triggers the color calculation. - Set report **font**: Changes the overall font of the report. Only Google Fonts are used for enhanced compatibility. - Company **tagline**, also called "header" - **Footer** - **Paper format** - Report **preview**: A mockup of a final report Automatically updates when changing **layout**, **logo**, **colors** or **font** Co-authored by: Julien Mougenot closes odoo/odoo#33863 Signed-off-by: VincentSchippefilt Co-authored-by: Lucas Perais --- addons/account/models/company.py | 7 - .../account_dashboard_setup_bar_tests.js | 0 .../account_dashboard_setup_bar_tests.js | 49 +++++ addons/account/tests/__init__.py | 1 + addons/account/tests/account_test_no_chart.py | 2 +- .../account/tests/test_account_onboarding.py | 18 ++ addons/account/views/account.xml | 2 +- .../views/account_onboarding_templates.xml | 25 +-- addons/account/wizard/__init__.py | 1 + addons/account/wizard/base_document_layout.py | 10 + .../base_setup/models/res_config_settings.py | 14 -- .../views/res_config_settings_views.xml | 8 +- addons/sale/models/res_company.py | 12 - addons/sale/views/sale_onboarding_views.xml | 28 +-- addons/web/data/report_layout.xml | 6 +- addons/web/models/__init__.py | 1 - addons/web/security/ir.model.access.csv | 1 - .../google/Montserrat/Montserrat-Black.ttf | Bin 0 -> 257064 bytes .../Montserrat/Montserrat-BlackItalic.ttf | Bin 0 -> 261232 bytes .../google/Montserrat/Montserrat-Bold.ttf | Bin 0 -> 244036 bytes .../Montserrat/Montserrat-BoldItalic.ttf | Bin 0 -> 249124 bytes .../Montserrat/Montserrat-ExtraBold.ttf | Bin 0 -> 244372 bytes .../Montserrat/Montserrat-ExtraBoldItalic.ttf | Bin 0 -> 249268 bytes .../Montserrat/Montserrat-ExtraLight.ttf | Bin 0 -> 241632 bytes .../Montserrat-ExtraLightItalic.ttf | Bin 0 -> 245664 bytes .../google/Montserrat/Montserrat-Italic.ttf | Bin 0 -> 248656 bytes .../google/Montserrat/Montserrat-Light.ttf | Bin 0 -> 241580 bytes .../Montserrat/Montserrat-LightItalic.ttf | Bin 0 -> 245776 bytes .../google/Montserrat/Montserrat-Medium.ttf | Bin 0 -> 242692 bytes .../Montserrat/Montserrat-MediumItalic.ttf | Bin 0 -> 247540 bytes .../google/Montserrat/Montserrat-Regular.ttf | Bin 0 -> 245276 bytes .../google/Montserrat/Montserrat-SemiBold.ttf | Bin 0 -> 243324 bytes .../Montserrat/Montserrat-SemiBoldItalic.ttf | Bin 0 -> 248684 bytes .../google/Montserrat/Montserrat-Thin.ttf | Bin 0 -> 240952 bytes .../Montserrat/Montserrat-ThinItalic.ttf | Bin 0 -> 244872 bytes .../src/fonts/google/Montserrat/OFL.txt | 93 ++++++++ .../src/fonts/google/Open Sans/LICENSE.txt | 202 +++++++++++++++++ .../fonts/google/Open Sans/Open Sans-Bold.ttf | Bin 0 -> 103616 bytes .../google/Open Sans/Open Sans-BoldItalic.ttf | Bin 0 -> 92124 bytes .../google/Open Sans/Open Sans-ExtraBold.ttf | Bin 0 -> 101512 bytes .../Open Sans/Open Sans-ExtraBoldItalic.ttf | Bin 0 -> 92196 bytes .../google/Open Sans/Open Sans-Italic.ttf | Bin 0 -> 91736 bytes .../google/Open Sans/Open Sans-Light.ttf | Bin 0 -> 101140 bytes .../Open Sans/Open Sans-LightItalic.ttf | Bin 0 -> 91920 bytes .../google/Open Sans/Open Sans-Regular.ttf | Bin 0 -> 96428 bytes .../google/Open Sans/Open Sans-SemiBold.ttf | Bin 0 -> 100256 bytes .../Open Sans/Open Sans-SemiBoldItalic.ttf | Bin 0 -> 91604 bytes .../static/src/fonts/google/Oswald/OFL.txt | 93 ++++++++ .../src/fonts/google/Oswald/Oswald-Bold.ttf | Bin 0 -> 87744 bytes .../fonts/google/Oswald/Oswald-ExtraLight.ttf | Bin 0 -> 84484 bytes .../src/fonts/google/Oswald/Oswald-Light.ttf | Bin 0 -> 85016 bytes .../src/fonts/google/Oswald/Oswald-Medium.ttf | Bin 0 -> 87756 bytes .../fonts/google/Oswald/Oswald-Regular.ttf | Bin 0 -> 86480 bytes .../fonts/google/Oswald/Oswald-SemiBold.ttf | Bin 0 -> 88720 bytes .../static/src/fonts/google/Raleway/OFL.txt | 95 ++++++++ .../fonts/google/Raleway/Raleway-Black.ttf | Bin 0 -> 173548 bytes .../google/Raleway/Raleway-BlackItalic.ttf | Bin 0 -> 141316 bytes .../src/fonts/google/Raleway/Raleway-Bold.ttf | Bin 0 -> 172040 bytes .../google/Raleway/Raleway-BoldItalic.ttf | Bin 0 -> 141076 bytes .../google/Raleway/Raleway-ExtraBold.ttf | Bin 0 -> 170936 bytes .../Raleway/Raleway-ExtraBoldItalic.ttf | Bin 0 -> 140904 bytes .../google/Raleway/Raleway-ExtraLight.ttf | Bin 0 -> 169152 bytes .../Raleway/Raleway-ExtraLightItalic.ttf | Bin 0 -> 134916 bytes .../fonts/google/Raleway/Raleway-Italic.ttf | Bin 0 -> 138804 bytes .../fonts/google/Raleway/Raleway-Light.ttf | Bin 0 -> 175144 bytes .../google/Raleway/Raleway-LightItalic.ttf | Bin 0 -> 141172 bytes .../fonts/google/Raleway/Raleway-Medium.ttf | Bin 0 -> 172264 bytes .../google/Raleway/Raleway-MediumItalic.ttf | Bin 0 -> 140472 bytes .../fonts/google/Raleway/Raleway-Regular.ttf | Bin 0 -> 171280 bytes .../fonts/google/Raleway/Raleway-SemiBold.ttf | Bin 0 -> 173272 bytes .../google/Raleway/Raleway-SemiBoldItalic.ttf | Bin 0 -> 139384 bytes .../src/fonts/google/Raleway/Raleway-Thin.ttf | Bin 0 -> 170340 bytes .../google/Raleway/Raleway-ThinItalic.ttf | Bin 0 -> 134656 bytes .../src/fonts/google/Roboto/LICENSE.txt | 202 +++++++++++++++++ .../src/fonts/google/Roboto/Roboto-Black.ttf | Bin 0 -> 170740 bytes .../google/Roboto/Roboto-BlackItalic.ttf | Bin 0 -> 176772 bytes .../src/fonts/google/Roboto/Roboto-Bold.ttf | Bin 0 -> 170064 bytes .../fonts/google/Roboto/Roboto-BoldItalic.ttf | Bin 0 -> 174236 bytes .../src/fonts/google/Roboto/Roboto-Italic.ttf | Bin 0 -> 173232 bytes .../src/fonts/google/Roboto/Roboto-Light.ttf | Bin 0 -> 169680 bytes .../google/Roboto/Roboto-LightItalic.ttf | Bin 0 -> 175836 bytes .../src/fonts/google/Roboto/Roboto-Medium.ttf | Bin 0 -> 171320 bytes .../google/Roboto/Roboto-MediumItalic.ttf | Bin 0 -> 176080 bytes .../fonts/google/Roboto/Roboto-Regular.ttf | Bin 0 -> 170984 bytes .../src/fonts/google/Roboto/Roboto-Thin.ttf | Bin 0 -> 171168 bytes .../fonts/google/Roboto/Roboto-ThinItalic.ttf | Bin 0 -> 175528 bytes .../static/src/fonts/google/Roboto/roboto.b64 | 1 + .../web/static/src/js/fields/basic_fields.js | 67 +++++- .../static/src/js/fields/field_registry.js | 6 +- .../static/src/js/fields/relational_fields.js | 27 +++ .../src/js/views/basic/basic_controller.js | 2 +- .../static/src/scss/base_document_layout.scss | 162 ++++++++++++++ addons/web/static/src/scss/colorpicker.scss | 4 +- addons/web/static/src/scss/fields.scss | 9 + addons/web/static/src/scss/fonts.scss | 24 ++ .../static/src/scss/layout_background.scss | 26 ++- addons/web/static/src/scss/layout_boxed.scss | 22 +- addons/web/static/src/scss/layout_clean.scss | 35 ++- .../web/static/src/scss/layout_standard.scss | 19 ++ addons/web/static/src/scss/report.scss | 5 + addons/web/static/src/xml/base.xml | 7 +- .../static/tests/fields/basic_fields_tests.js | 66 +++++- .../tests/fields/relational_fields_tests.js | 36 +++ addons/web/views/report_templates.xml | 8 + addons/web/views/webclient_templates.xml | 1 + odoo/addons/base/__manifest__.py | 1 + odoo/addons/base/data/res_company_data.xml | 12 +- odoo/addons/base/models/__init__.py | 2 +- odoo/addons/base/models/ir_actions_report.py | 13 +- .../addons/base}/models/report_layout.py | 9 +- odoo/addons/base/models/res_company.py | 26 ++- odoo/addons/base/security/ir.model.access.csv | 1 + odoo/addons/base/tests/__init__.py | 1 + odoo/addons/base/tests/fire_small.jpeg | Bin 0 -> 2038 bytes odoo/addons/base/tests/odoo.jpg | Bin 0 -> 8173 bytes odoo/addons/base/tests/overwatch.jpg | Bin 0 -> 7666 bytes .../base/tests/test_base_document_layout.py | 206 ++++++++++++++++++ odoo/addons/base/tests/tommy_small.jpeg | Bin 0 -> 2065 bytes odoo/addons/base/views/res_company_views.xml | 81 ------- odoo/addons/base/wizard/__init__.py | 1 + .../base/wizard/base_document_layout.py | 194 +++++++++++++++++ .../wizard/base_document_layout_views.xml | 169 ++++++++++++++ odoo/tools/image.py | 90 ++++++++ 123 files changed, 1960 insertions(+), 243 deletions(-) delete mode 100644 addons/account/static/tests/account_dashboard_setup_bar_tests.js create mode 100644 addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js create mode 100644 addons/account/tests/test_account_onboarding.py create mode 100644 addons/account/wizard/base_document_layout.py create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Black.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-BlackItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Bold.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-BoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-ExtraBold.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-ExtraBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-ExtraLight.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-ExtraLightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Italic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Light.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-LightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Medium.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-MediumItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Regular.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-SemiBold.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-SemiBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-Thin.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/Montserrat-ThinItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Montserrat/OFL.txt create mode 100644 addons/web/static/src/fonts/google/Open Sans/LICENSE.txt create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-Bold.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-BoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-ExtraBold.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-ExtraBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-Italic.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-Light.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-LightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-Regular.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-SemiBold.ttf create mode 100644 addons/web/static/src/fonts/google/Open Sans/Open Sans-SemiBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/OFL.txt create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-Bold.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-ExtraLight.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-Light.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-Medium.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-Regular.ttf create mode 100644 addons/web/static/src/fonts/google/Oswald/Oswald-SemiBold.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/OFL.txt create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Black.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-BlackItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Bold.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-BoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-ExtraBold.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-ExtraBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-ExtraLight.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-ExtraLightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Italic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Light.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-LightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Medium.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-MediumItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Regular.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-SemiBold.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-SemiBoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-Thin.ttf create mode 100644 addons/web/static/src/fonts/google/Raleway/Raleway-ThinItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/LICENSE.txt create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Black.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-BlackItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Bold.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-BoldItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Italic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Light.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-LightItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Medium.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-MediumItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Regular.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-Thin.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/Roboto-ThinItalic.ttf create mode 100644 addons/web/static/src/fonts/google/Roboto/roboto.b64 create mode 100644 addons/web/static/src/scss/base_document_layout.scss rename {addons/web => odoo/addons/base}/models/report_layout.py (82%) create mode 100644 odoo/addons/base/tests/fire_small.jpeg create mode 100644 odoo/addons/base/tests/odoo.jpg create mode 100644 odoo/addons/base/tests/overwatch.jpg create mode 100644 odoo/addons/base/tests/test_base_document_layout.py create mode 100644 odoo/addons/base/tests/tommy_small.jpeg create mode 100644 odoo/addons/base/wizard/base_document_layout.py create mode 100644 odoo/addons/base/wizard/base_document_layout_views.xml diff --git a/addons/account/models/company.py b/addons/account/models/company.py index ed1a8fdacbc..d5bcc3d6fa0 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -459,13 +459,6 @@ Best Regards,''')) """ Mark the dashboard onboarding panel as closed. """ self.env.company.account_dashboard_onboarding_state = 'closed' - @api.model - def action_open_account_onboarding_invoice_layout(self): - """ Onboarding step for the invoice layout. """ - action = self.env.ref('account.action_open_account_onboarding_invoice_layout').read()[0] - action['res_id'] = self.env.company.id - return action - @api.model def action_open_account_onboarding_sale_tax(self): """ Onboarding step for the invoice layout. """ diff --git a/addons/account/static/tests/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/account_dashboard_setup_bar_tests.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js new file mode 100644 index 00000000000..4043254aae9 --- /dev/null +++ b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js @@ -0,0 +1,49 @@ +odoo.define('account.dashboard.setup.tour', function (require) { + "use strict"; + + var core = require('web.core'); + var tour = require('web_tour.tour'); + + var _t = core._t; + + tour.register('account_render_report', { + test: true, + url: '/web', + }, [tour.STEPS.SHOW_APPS_MENU_ITEM, + { + trigger: '.o_app[data-menu-xmlid="account.menu_finance"]', + position: 'bottom', + edition: 'community' + }, { + trigger: '.o_app[data-menu-xmlid="account_accountant.menu_accounting"]', + position: 'bottom', + edition: 'enterprise' + }, { + trigger: 'a:contains(' + _t("Customer Invoices") + ')', + edition: 'enterprise' + }, { + trigger: '.o_data_row:first', + extra_trigger: '.breadcrumb', + }, { + trigger: '.o_control_panel button:contains("' + _t('Print') + '")', + }, { + trigger: '.o_control_panel div.o_dropdown_menu a:contains("' + _t('Invoices without Payment') + '")', + }, { + trigger: 'iframe .o_report_layout_standard h2', + content: 'Primary color is correct', + run: function () { + if (this.$anchor.css('color') !== "rgb(18, 52, 86)") { + console.error('The primary color should be the one set on the company.'); + } + }, + }, { + trigger: 'iframe .o_report_layout_standard #informations div strong', + content: 'Secondary color is correct', + run: function () { + if (this.$anchor.css('color') !== "rgb(120, 145, 1)") { + console.error('The secondary color should be the one set on the company.'); + } + }, + } + ]); +}); diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index c2612d50551..5c822cca737 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -19,3 +19,4 @@ from . import test_templates_consistency from . import test_account_fiscal_year from . import test_account_all_l10n from . import test_reconciliation_matching_rules +from . import test_account_onboarding \ No newline at end of file diff --git a/addons/account/tests/account_test_no_chart.py b/addons/account/tests/account_test_no_chart.py index ca1c971d0a6..ba145b02d48 100644 --- a/addons/account/tests/account_test_no_chart.py +++ b/addons/account/tests/account_test_no_chart.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests.common import SavepointCase +from odoo.tests.common import SavepointCase, HttpCase, tagged class TestAccountNoChartCommon(SavepointCase): diff --git a/addons/account/tests/test_account_onboarding.py b/addons/account/tests/test_account_onboarding.py new file mode 100644 index 00000000000..c4839a35a32 --- /dev/null +++ b/addons/account/tests/test_account_onboarding.py @@ -0,0 +1,18 @@ +from odoo.tests.common import tagged, HttpCase + + +@tagged('-at_install', 'post_install', 'document_layout') +class TestAccountDocumentLayout(HttpCase): + + def test_render_account_document_layout(self): + report_layout = self.env.ref('web.report_layout_standard') + main_company = self.env.ref('base.main_company') + main_company.write({ + 'primary_color': '#123456', + 'secondary_color': '#789101', + 'external_report_layout_id': report_layout.view_id.id, + }) + self.env.ref('account.account_invoices_without_payment').write({ + 'report_type': 'qweb-html', + }) + self.start_tour("/web", 'account_render_report', login='admin', timeout=200) diff --git a/addons/account/views/account.xml b/addons/account/views/account.xml index 448363ddec2..b06b122a6fc 100644 --- a/addons/account/views/account.xml +++ b/addons/account/views/account.xml @@ -27,7 +27,6 @@ - + diff --git a/addons/account/views/account_onboarding_templates.xml b/addons/account/views/account_onboarding_templates.xml index 8393efd7d79..1f2af4843fb 100644 --- a/addons/account/views/account_onboarding_templates.xml +++ b/addons/account/views/account_onboarding_templates.xml @@ -8,8 +8,8 @@ Customize Looks great! - - + + @@ -94,27 +94,6 @@ - - - account.invoice.layout.onboarding.form - res.company - - primary - 1000 - - - +
+
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index d537698432f..a67d4e19cbc 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -42,6 +42,7 @@ QUnit.module('basic_fields', { selection: {string: "Selection", type: "selection", searchable:true, selection: [['normal', 'Normal'],['blocked', 'Blocked'],['done', 'Done']]}, document: {string: "Binary", type: "binary"}, + hex_color: {string: "hexadecimal color", type: "char"}, }, records: [{ id: 1, @@ -57,6 +58,7 @@ QUnit.module('basic_fields', { trululu: 4, selection: 'blocked', document: 'coucou==\n', + hex_color: '#ff0000', }, { id: 2, display_name: "second record", @@ -2940,7 +2942,7 @@ QUnit.module('basic_fields', { await testUtils.form.clickEdit(form); await testUtils.dom.openDatepicker(form.$('.o_datepicker')); - + assert.containsOnce($('body'), '.bootstrap-datetimepicker-widget', "datepicker should be opened"); form.el.dispatchEvent(new Event('scroll')); @@ -5814,6 +5816,68 @@ QUnit.module('basic_fields', { form.destroy(); }); + + QUnit.module('FieldColor'); + + QUnit.test('Field Color: default widget state', async function (assert) { + assert.expect(3); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + await testUtils.dom.click(form.$('.o_field_color')); + await testUtils.nextTick(); + assert.containsOnce($, '.modal'); + assert.containsNone($('.modal'), '.o_opacity_slider', + "Opacity slider should not be present"); + assert.containsNone($('.modal'), '.o_opacity_input', + "Opacity input should not be present"); + + await testUtils.dom.click($('.modal .btn:contains("Discard")')); + + form.destroy(); + }); + + QUnit.test('Field Color: pick and reset colors', async function (assert) { + assert.expect(2); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
' + + '' + + '', + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + assert.strictEqual($('.o_field_color').css('backgroundColor'), 'rgb(255, 0, 0)', + "Background of the color field should be initially red"); + + await testUtils.dom.click(form.$('.o_field_color')); + await testUtils.fields.editAndTrigger($('.modal .o_hex_input'), '#00ff00', ['change']); + await testUtils.dom.click($('.modal .btn:contains("Choose")')); + + assert.strictEqual($('.o_field_color').css('backgroundColor'), 'rgb(0, 255, 0)', + "Background of the color field should be updated to green"); + + form.destroy(); + }); }); }); }); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index b5e098d230c..229d020bfbd 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -2131,6 +2131,42 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.module('FieldSelectionFont'); + + QUnit.test('FieldSelectionFont displays the correct fonts on options', async function (assert) { + assert.expect(4); + + this.data.partner.fields.fonts = { + type: "selection", + selection: [['Lato', "Lato"], ['Oswald', "Oswald"]], + default: 'Lato', + string: "Fonts", + }; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '', + }); + var options = form.$('.o_field_widget[name="fonts"] > option'); + + assert.strictEqual(form.$('.o_field_widget[name="fonts"]').css('fontFamily'), 'Lato', + "Widget font should be default (Lato)"); + assert.strictEqual($(options[0]).css('fontFamily'), 'Lato', + "Option 0 should have the correct font (Lato)"); + assert.strictEqual($(options[1]).css('fontFamily'), 'Oswald', + "Option 1 should have the correct font (Oswald)"); + + await testUtils.fields.editSelect(form.$('.o_field_widget[name="fonts"]'), '"Oswald"'); + assert.strictEqual(form.$('.o_field_widget[name="fonts"]').css('fontFamily'), 'Oswald', + "Widget font should be updated (Oswald)"); + + form.destroy(); + }); + QUnit.module('FieldMany2ManyCheckBoxes'); QUnit.test('widget many2many_checkboxes', async function (assert) { diff --git a/addons/web/views/report_templates.xml b/addons/web/views/report_templates.xml index 4865160be0e..0ba32cda223 100644 --- a/addons/web/views/report_templates.xml +++ b/addons/web/views/report_templates.xml @@ -18,6 +18,9 @@ + + + @@ -330,6 +333,8 @@
+ + @@ -351,6 +356,8 @@ + +
@@ -403,6 +410,7 @@ + + diff --git a/odoo/addons/base/__manifest__.py b/odoo/addons/base/__manifest__.py index 1a3658ceaef..aed63a8501d 100644 --- a/odoo/addons/base/__manifest__.py +++ b/odoo/addons/base/__manifest__.py @@ -58,6 +58,7 @@ The kernel of Odoo, needed for all installation. 'wizard/base_export_language_views.xml', 'wizard/base_update_translations_views.xml', 'wizard/base_partner_merge_views.xml', + 'wizard/base_document_layout_views.xml', 'data/ir_actions_data.xml', 'data/ir_demo_failure_data.xml', 'views/res_company_views.xml', diff --git a/odoo/addons/base/data/res_company_data.xml b/odoo/addons/base/data/res_company_data.xml index 50c19767c40..36c3eda67b3 100644 --- a/odoo/addons/base/data/res_company_data.xml +++ b/odoo/addons/base/data/res_company_data.xml @@ -8,8 +8,18 @@ + + + + JG8tY29tcGFueS1mb250OiAnTGF0byc7JG8tY29tcGFueS1wcmltYXJ5LWNvbG9yOiByZ2IoMCwgMCwgMCk7JG8tY29tcGFueS1zZWNvbmRhcnktY29sb3I6IHJnYigwLCAwLCAwKTs= + text/scss + res.company.scss + binary + /web/static/src/scss/report_styles.scss + + - + diff --git a/odoo/addons/base/models/__init__.py b/odoo/addons/base/models/__init__.py index d74b68c77d8..bdc3d0b1a0e 100644 --- a/odoo/addons/base/models/__init__.py +++ b/odoo/addons/base/models/__init__.py @@ -29,7 +29,7 @@ from . import ir_property from . import ir_module from . import ir_demo from . import ir_demo_failure - +from . import report_layout from . import report_paperformat from . import image_mixin diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index 1e76c8c1cf8..820d852e6b5 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -791,18 +791,7 @@ class IrActionsReport(models.Model): """ discard_logo_check = self.env.context.get('discard_logo_check') if self.env.is_admin() and ((not self.env.company.external_report_layout_id) or (not discard_logo_check and not self.env.company.logo)) and config: - template = self.env.ref('base.view_company_report_form_with_print') if self.env.context.get('from_transient_model', False) else self.env.ref('base.view_company_report_form') - return { - 'name': _('Choose Your Document Layout'), - 'type': 'ir.actions.act_window', - 'context': {'default_report_name': self.report_name, 'discard_logo_check': True}, - 'view_mode': 'form', - 'res_id': self.env.company.id, - 'res_model': 'res.company', - 'views': [(template.id, 'form')], - 'view_id': template.id, - 'target': 'new', - } + return self.env.ref('base.action_base_document_layout_configurator').read()[0] context = self.env.context if docids: diff --git a/addons/web/models/report_layout.py b/odoo/addons/base/models/report_layout.py similarity index 82% rename from addons/web/models/report_layout.py rename to odoo/addons/base/models/report_layout.py index a38f25c2033..e80fbb47c96 100644 --- a/addons/web/models/report_layout.py +++ b/odoo/addons/base/models/report_layout.py @@ -1,18 +1,15 @@ # -*- coding: ascii -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import logging - from odoo import fields, models -_logger = logging.getLogger(__name__) - - -class View(models.Model): +class ReportLayout(models.Model): _name = "report.layout" _description = 'Report Layout' view_id = fields.Many2one('ir.ui.view', 'Document Template', required=True) image = fields.Char(string="Preview image src") pdf = fields.Char(string="Preview pdf src") + + name = fields.Char() diff --git a/odoo/addons/base/models/res_company.py b/odoo/addons/base/models/res_company.py index 1c4a001096e..0d76629f661 100644 --- a/odoo/addons/base/models/res_company.py +++ b/odoo/addons/base/models/res_company.py @@ -94,8 +94,9 @@ class Company(models.Model): base_onboarding_company_state = fields.Selection([ ('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding company step", default='not_done') favicon = fields.Binary(string="Company Favicon", help="This field holds the image used to display a favicon for a given company.", default=_get_default_favicon) - - + font = fields.Selection([("Lato", "Lato"), ("Roboto", "Roboto"), ("Open Sans", "Open Sans"), ("Montserrat", "Montserrat"), ("Oswald", "Oswald"), ("Raleway", "Raleway")], default="Lato") + primary_color = fields.Char() + secondary_color = fields.Char() _sql_constraints = [ ('name_uniq', 'unique (name)', 'The company name must be unique !') ] @@ -309,3 +310,24 @@ 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')]) + attachment.write({'datas': scss_data}) + + return '' diff --git a/odoo/addons/base/security/ir.model.access.csv b/odoo/addons/base/security/ir.model.access.csv index d25dddad322..706f68e6f7d 100644 --- a/odoo/addons/base/security/ir.model.access.csv +++ b/odoo/addons/base/security/ir.model.access.csv @@ -96,3 +96,4 @@ "access_ir_logging","ir_logging admin","model_ir_logging","group_erp_manager",1,1,1,1 "paperformat_access_portal","ir_actions_report_paperformat group_portal","model_report_paperformat",,1,0,0,0 "paperformat_access_employee","ir_actions_report_paperformat group_system","model_report_paperformat",group_system,1,1,1,1 +"access_report_layout","access_report_layout","model_report_layout",,1,1,1,1 diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py index bba0d5b6742..db7b07f81af 100644 --- a/odoo/addons/base/tests/__init__.py +++ b/odoo/addons/base/tests/__init__.py @@ -38,3 +38,4 @@ from . import test_res_partner_bank from . import test_reports from . import test_tests_tags from . import test_non_regression +from . import test_base_document_layout diff --git a/odoo/addons/base/tests/fire_small.jpeg b/odoo/addons/base/tests/fire_small.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1dd0909e399bc6635940ed2502b3cbec1a0070ea GIT binary patch literal 2038 zcmb7^dpuNmAIHx*Gea2ToN>Qo$GCgQ483x{3?9XkM61blV`x~SQ6ePc62_VuxkPL( zYgVzMX_ZS};3zD-rpm7D*6q8U7cN= z0T2X%edq&-o&$~mEQKfFC1HXzfr!AeN_r|v3JOY8P3@g}mZmn=7N+KA_WS$}*gMhx zZDxM(xCi~4AZ94j){S-ML~xv62$Qk(5fC8~mE@I-RaA@_4(1Mw|9eCo0I~zH2kbE* z0ze3eK|oO#pbP*I1_Zts3>E+(9QsNL1Tg3|z8nz3U`5XWJbD6xF)#ptQ`j8}mXdNi z@X{Nr_1(_=T9#)q!@yFlR3_+1;oZg26Oph}9(ZH`KV$9#xhitJ{9|rNe!hZSHB-<-L`C7=`sX?{~K$$dQ z073`EV*bVgApnCAHOR79IZYFoVv6GJK&e3-v?;G=+U7h=?~gdtmeB9=gu9o&(TWuN zuGDaNWt7YAhV6XIm!K(S*i)mgG||` zLesPZ|CFd1Ejdtopu#lUG>4ZL^7JZV#v#`+>sZQJT}z6U%_>AaZWAl<{s#xdVK7&1 zNatPfsSIY;u~9)p6@8Xl$w(MX6d0Z#HJ@}oRei;CHmU9EjVike_czKNwpokOUvKef zZSeqN5cD}Fv{M8F6E#dcWT|9N3XQ!@1Z@#*NLxOA6gL2MS~GH_2>BP-*~PtNH8Gdi zd^v4zN4CNE%~@|vmeU0&DtcFjE}2|0c+*G(Nc@YDdR==qpet=%YwxJusM35>rOY3p zooo5HHCr2k5l3zdL>ui3#6V(SCd(0`X^ae8(D` zFS2+k_tReE<=@Gq&!(WFeC`C*UW4jE3uLi*aKl3R;NPa$X=G1j%vmQIr`*wb{6G%Z z>cj3m+Yb?vC!v&Gkxb)?3XqngaC558X@qdiqnB*-O?yEQN&I!6eWLJjqIHKbwuARN z&rdqXzQsG8XEK6SCe(AdM}PicGb6?NZa`i#88YZQ?~-R}k4g0i@%h%mjd0NQ)a@5N zChKYE>KmWmNfu-;Wj;LOHAcZ(m0@$&Xuika!qr;f>bX{-Bo-r{Y5%BINX*CXYB+nJ zCi$Nwy>E|tL#7m>{{z7A=mYls%`0DQ-wtU@TsW^U#62=^ zx!cisec|n-}hEPD7bSvP>>;0dKLaqs(UNNZQ)f=l4y+m1PF| zkjoXryFX^Z)GL{z`eRCdv!*(0fyFH2*HkUBDuh4j8(9__d1yL)n(+8yN`MrupDQn4 zoOL}|H_l8dgR%d{L;8&3_>jaySsEtYZd-4Z8id6D(R-Vk25%|VP`*H7Pi%8<=?zbu zJR3KTduF-G^VQ7_Ghx)yRIPbs!EFmGx^FZw+)E>uy^<6uvDvmW*K!MQKk;_bB6-RA z8v3N|JeesmJJ&jNd5^HW{xHuRac^A|0T+!!cm~|!+Af3LzACj;Rbe)Ttiox?NpC55 zM1nf0dfF|`ncm+0RJWx4>jf$@SJd6(Uft||v4stzE}~)ollHcfG?bdXz$<(KFJV(D+H4rYS53Z}|Wxr*OQKqO1a@I(?<~>K1*d<8(vf<0WjO17}uy4{7kVC$AQ9 zvKHbE{!H=7z*sG!b4D+YhAJz#BsrOwCF_V%)OZzKlZi8+(Kk|s;jj4HLU OEwT=htGjI{8vQTgARCkb literal 0 HcmV?d00001 diff --git a/odoo/addons/base/tests/odoo.jpg b/odoo/addons/base/tests/odoo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..312451a2ae10447c4d52e6061606854d4593ba39 GIT binary patch literal 8173 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 literal 0 HcmV?d00001 diff --git a/odoo/addons/base/tests/overwatch.jpg b/odoo/addons/base/tests/overwatch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..38fdaeffc510c3158f31e88e2f75d564cfa3b739 GIT binary patch literal 7666 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 Y8TmW&O-!*+kYX0I3|d<}?|S0@0pmoc!T
''', + }) + self.env['ir.model.data'].create({ + 'name': self.layout_template1.name, + 'model': 'ir.ui.view', + 'module': 'base', + 'res_id': self.layout_template1.id, + }) + self.default_colors = { + 'primary_color': '#000000', + 'secondary_color': '#000000', + } + self.report_layout1 = self.env['report.layout'].create({ + 'view_id': self.layout_template1.id, + 'name': 'report_%s' % self.layout_template1.name, + }) + self.layout_template2 = self.env['ir.ui.view'].create({ + 'name': 'layout_template2', + 'key': 'layout_template2', + 'type': 'qweb', + 'arch': '''
''', + }) + self.env['ir.model.data'].create({ + 'name': self.layout_template2.name, + 'model': 'ir.ui.view', + 'module': 'base', + 'res_id': self.layout_template2.id, + }) + self.report_layout2 = self.env['report.layout'].create({ + 'view_id': self.layout_template2.id, + 'name': 'report_%s' % self.layout_template2.name, + }) + + +@tagged('document_layout') +class TestBaseDocumentLayout(TestBaseDocumentLayoutHelpers): + # Logo change Tests + def test_company_no_color_change_logo(self): + """When neither a logo nor the colors are set + The wizard displays the colors of the report layout + Changing logo means the colors on the wizard change too + Emptying the logo works and doesn't change the colors""" + self.company.write({ + 'primary_color': False, + 'secondary_color': False, + 'logo': False, + 'external_report_layout_id': self.env.ref('base.layout_template1').id, + }) + default_colors = self.default_colors + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, default_colors) + self.assertEqual(doc_layout.company_id, self.company) + doc_layout.logo = self.company_imgs['overwatch']['img'] + + self.assertColors(doc_layout, self.company_imgs['overwatch']['colors']) + + doc_layout.logo = '' + self.assertColors(doc_layout, self.company_imgs['overwatch']['colors']) + self.assertEqual(doc_layout.logo, '') + + def test_company_no_color_but_logo_change_logo(self): + """When company colors are not set, but a logo is, + the wizard displays the computed colors from the logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['overwatch']['img'], + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + def test_company_colors_change_logo(self): + """changes of the logo implies displaying the new computed colors""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': False, + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + def test_company_colors_and_logo_change_logo(self): + """The colors of the company may differ from the one the logo computes + Opening the wizard in these condition displays the company's colors + When the logo changes, colors must change according to the logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['overwatch']['img'], + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.logo = self.company_imgs['odoo']['img'] + self.assertColors(doc_layout, self.company_imgs['odoo']['colors']) + + # Layout change tests + def test_company_colors_reset_colors(self): + """Reset the colors when they differ from the ones originally + computed from the company logo""" + self.company.write({ + 'primary_color': '#ff0080', + 'secondary_color': '#00ff00', + 'logo': self.company_imgs['overwatch']['img'], + }) + + with Form(self.env['base.document.layout']) as doc_layout: + self.assertColors(doc_layout, self.company) + doc_layout.custom_colors = False + self.assertColors(doc_layout, self.company_imgs['overwatch']['colors']) + + # /!\ This case is NOT supported, and probably not supportable + # res.partner resizes manu-militari the image it is given + # so res.company._get_logo differs from res.partner.[default image] + # def test_company_no_colors_default_logo_and_layout_change_layout(self): + # """When the default YourCompany logo is set, and no colors are set on company: + # change wizard's color according to template""" + # self.company.write({ + # 'primary_color': False, + # 'secondary_color': False, + # 'external_report_layout_id': self.layout_template1.id, + # }) + # default_colors = self.default_colors + # with Form(self.env['base.document.layout']) as doc_layout: + # self.assertColors(doc_layout, default_colors) + # doc_layout.report_layout_id = self.report_layout2 + # self.assertColors(doc_layout, self.report_layout2) diff --git a/odoo/addons/base/tests/tommy_small.jpeg b/odoo/addons/base/tests/tommy_small.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..70cd4fb3eb44628fcf18498f687b070295192f6e GIT binary patch literal 2065 zcmb7^X;f3!8ivnFf)d7aB7^{pNHwW|p!8DEA{Rj%fSN+BwwimO_Okl(?)_(_2Ki>tl;jhD0{|(jsHrGHYN~2D7}`KE)FI&U1o{?z zJwpqowH1qLZf55a>Seb($kEK)CxH_b77-I0W8*2zNRB!X8XXh4QUru?ID#gDs-r`V zv^Tep{J)3%E&wY6c7PokgaH%`qG3>e56}hx^or}XfkIl%ku-c-OBpOIP)Xoqq9d_DGEKH;b-9XwTd`ru|nfgXg2 z`-eUdg$6J%ZUc#o=ukw|feNU%i}eI`_TD`I_6q@3jQ1ts>z2Q>b+hE#*th9^*yfnp znBRJqb;d~`2XJm5O#@oEngcVbhD7(vzd72{1;v(HNs8xxUZ$LoVs3{|Kd<@JMENTC zYKDGtVf7O<`U}c0-8NRqLgPc%8TvNy?7?BG0@xJNxdwM5=9s5zi|PTk9Bzf=M`t&R#8tS}9k& zODDm28ZUtTa>&xfWqe?DhDS)YxWvoqT;k4MTg&EWnzWj~NnxFol#kL=wz_}gr*Tmh z)oX3wn=teHgrHm2Q-U022mvS%jZ#1>{7*Uvqex^N9b$8YxeR^-nkl|B{G|`;Y*Ae| z-rW`bd1_e3OPOAmNp>5fBBmrThg^d**}P`s7gOg9j-Va2fB&_5030`mD#=g=v){%8x^&w{_3AT zuSvfJwOfW9Q^>n^*vRRUkw%A)(j}kRCJzHgOQG+8Fzb7rk`8ml_d+wB#VeuPM0I{x z>|~8oQT9H=aDiV+-i3Rob^2yikMkC(3$vm@gJR!z+L|(U%VoBM#<0eJ`ifdO%Ds{8 zGqRYJu}6to6t#) zlOO@CUS%XB0DyE}U}on-XPQFm9>q2o2U+p+6)*K3DRN$Acm+5FyEPJ!Ea_|xw3^`x z!_ZIoVF61FzGdj;XvQb$=MGHz>d#fn!jFo^=)E;w^&zFFOfj<)PwJvEILH2qB$EE6 z>-)3#9=s~nPE4Qsll=WgT=h29CHvz;$suk}D^q8Vsdv=a5~*fiuwRA#ODmC=XH@{i z;jb!K!5-3;!LcH7PiJq}Nl6%S>xwC7Hh|n_9yRy3rM(oz-tu$tX!Pgh$wJ zo#Yvo>U*J2wBqh_^rN?~R9xqG=P(kU| v;}6OKvb_W}dbw~|8DB1WZu8C=ODEjC|7zaTYwMn`ws*Q5qtiMnN^z& literal 0 HcmV?d00001 diff --git a/odoo/addons/base/views/res_company_views.xml b/odoo/addons/base/views/res_company_views.xml index 29051f5a8a9..e549946ff04 100644 --- a/odoo/addons/base/views/res_company_views.xml +++ b/odoo/addons/base/views/res_company_views.xml @@ -115,86 +115,5 @@ - - - res.company.report.form - res.company - - primary - 1024 - - -
-
- Update your company details and upload your logo to get a beautiful document. -
-
-
- -

- -

-
- - -
-
- -
-
-
-
-
-
- - - res.company.report.form.with.print - res.company - primary - - 1024 - - - +
+ + + + + +
+
+ +
+
+ +
+ + + + ir.actions.act_window + Configure your document layout + form + new + base.document.layout + + + + + + + + diff --git a/odoo/tools/image.py b/odoo/tools/image.py index 7917cc7fd0e..ee7d109c10b 100644 --- a/odoo/tools/image.py +++ b/odoo/tools/image.py @@ -276,6 +276,65 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=0 # Misc image tools # --------------------------------------- +def average_dominant_color(colors, mitigate=175, max_margin=140): + """This function is used to calculate the dominant colors when given a list of colors + + There are 5 steps : + 1) Select dominant colors (highest count), isolate its values and remove + it from the current color set. + 2) Set margins according to the prevalence of the dominant color. + 3) Evaluate the colors. Similar colors are grouped in the dominant set + while others are put in the "remaining" list. + 4) Calculate the average color for the dominant set. This is done by + averaging each band and joining them into a tuple. + 5) Mitigate final average and convert it to hex + + :param colors: list of tuples having: + [0] color count in the image + [1] actual color: tuple(R, G, B, A) + -> these can be extracted from a PIL image using image.getcolors() + :param mitigate: maximum value a band can reach + :param max_margin: maximum difference from one of the dominant values + :returns: a tuple with two items: + [0] the average color of the dominant set as: tuple(R, G, B) + [1] list of remaining colors, used to evaluate subsequent dominant colors + """ + dominant_color = max(colors) + dominant_rgb = dominant_color[1][:3] + dominant_set = [dominant_color] + remaining = [] + + margins = [max_margin * (1 - dominant_color[0] / + sum([col[0] for col in colors]))] * 3 + + colors.remove(dominant_color) + + for color in colors: + rgb = color[1] + if (rgb[0] < dominant_rgb[0] + margins[0] and rgb[0] > dominant_rgb[0] - margins[0] and + rgb[1] < dominant_rgb[1] + margins[1] and rgb[1] > dominant_rgb[1] - margins[1] and + rgb[2] < dominant_rgb[2] + margins[2] and rgb[2] > dominant_rgb[2] - margins[2]): + dominant_set.append(color) + else: + remaining.append(color) + + dominant_avg = [] + for band in range(3): + avg = total = 0 + for color in dominant_set: + avg += color[0] * color[1][band] + total += color[0] + dominant_avg.append(int(avg / total)) + + final_dominant = [] + brightest = max(dominant_avg) + for color in range(3): + value = dominant_avg[color] / (brightest / mitigate) if brightest > mitigate else dominant_avg[color] + final_dominant.append(int(value)) + + return tuple(final_dominant), remaining + + def base64_to_image(base64_source): """Return a PIL image from the given `base64_source`. @@ -407,6 +466,37 @@ def image_data_uri(base64_source): ) +def get_saturation(rgb): + """Returns the saturation (hsl format) of a given rgb color + + :param rgb: rgb tuple or list + :return: saturation + """ + c_max = max(rgb) / 255 + c_min = min(rgb) / 255 + d = c_max - c_min + return 0 if d == 0 else d / (1 - abs(c_max + c_min - 1)) + + +def get_lightness(rgb): + """Returns the lightness (hsl format) of a given rgb color + + :param rgb: rgb tuple or list + :return: lightness + """ + return (max(rgb) + min(rgb)) / 2 / 255 + + +def hex_to_rgb(hx): + """Converts an hexadecimal string (starting with '#') to a RGB tuple""" + return tuple([int(hx[i:i+2], 16) for i in range(1, 6, 2)]) + + +def rgb_to_hex(rgb): + """Converts a RGB tuple or list to an hexadecimal string""" + return '#' + ''.join([(hex(c).split('x')[-1].zfill(2)) for c in rgb]) + + if __name__=="__main__": import sys