mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 12:32:02 +02:00
vanilla 17.0
This commit is contained in:
parent
d72e748793
commit
a9bcec8e91
1986 changed files with 1613876 additions and 568976 deletions
|
|
@ -18,8 +18,8 @@ This module provides the core of the Odoo Web Client.
|
|||
'views/webclient_templates.xml',
|
||||
'views/report_templates.xml',
|
||||
'views/base_document_layout_views.xml',
|
||||
'views/partner_view.xml',
|
||||
'views/speedscope_template.xml',
|
||||
'views/lazy_assets.xml',
|
||||
'views/neutralize_views.xml',
|
||||
'data/ir_attachment.xml',
|
||||
'data/report_layout.xml',
|
||||
|
|
@ -37,69 +37,33 @@ This module provides the core of the Odoo Web Client.
|
|||
# 3) an arbitrary name, relevant to the content of the bundle.
|
||||
#
|
||||
# Examples:
|
||||
# > web.assets_common = assets common to backend clients and others
|
||||
# (not frontend).
|
||||
# > web_editor.assets_wysiwyg = assets needed by components defined in the "web_editor" module.
|
||||
# > web_editor.assets_legacy_wysiwyg = assets needed by components defined in the "web_editor" module.
|
||||
|
||||
# Warning: Layouts using "assets_frontend" assets do not have the
|
||||
# "assets_common" assets anymore. So, if it make sense, files added in
|
||||
# "assets_common" should also be added in "assets_frontend".
|
||||
# TODO in the future, probably remove "assets_common" definition
|
||||
# entirely and let all "main" bundles evolve on their own, including the
|
||||
# files they need in their bundle.
|
||||
'web.assets_common': [
|
||||
'web.assets_emoji': [
|
||||
'web/static/src/core/emoji_picker/emoji_data.js'
|
||||
],
|
||||
'web.assets_backend': [
|
||||
('include', 'web._assets_helpers'),
|
||||
|
||||
('include', 'web._assets_backend_helpers'),
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
('include', 'web._assets_bootstrap_backend'),
|
||||
|
||||
('include', 'web._assets_core'),
|
||||
|
||||
'web/static/src/legacy/scss/tempusdominus_overridden.scss',
|
||||
'web/static/lib/tempusdominus/tempusdominus.scss',
|
||||
'web/static/lib/jquery.ui/jquery-ui.css',
|
||||
'web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'web/static/lib/odoo_ui_icons/*',
|
||||
'web/static/lib/select2/select2.css',
|
||||
'web/static/lib/select2-bootstrap-css/select2-bootstrap.css',
|
||||
'web/static/lib/daterangepicker/daterangepicker.css',
|
||||
'web/static/src/webclient/navbar/navbar.scss',
|
||||
'web/static/src/scss/animation.scss',
|
||||
'web/static/src/scss/fontawesome_overridden.scss',
|
||||
'web/static/src/scss/mimetypes.scss',
|
||||
'web/static/src/scss/ui.scss',
|
||||
'web/static/src/views/fields/translation_dialog.scss',
|
||||
'web/static/src/legacy/scss/ui.scss',
|
||||
'web/static/src/legacy/scss/mimetypes.scss',
|
||||
'web/static/src/legacy/scss/modal.scss',
|
||||
'web/static/src/legacy/scss/animation.scss',
|
||||
'web/static/src/legacy/scss/datepicker.scss',
|
||||
'web/static/src/legacy/scss/daterangepicker.scss',
|
||||
'web/static/src/legacy/scss/banner.scss',
|
||||
'web/static/src/legacy/scss/colorpicker.scss',
|
||||
'web/static/src/legacy/scss/popover.scss',
|
||||
'web/static/src/legacy/scss/translation_dialog.scss',
|
||||
'web/static/src/legacy/scss/keyboard.scss',
|
||||
'web/static/src/legacy/scss/name_and_signature.scss',
|
||||
'web/static/src/legacy/scss/web.zoomodoo.scss',
|
||||
'web/static/src/legacy/scss/fontawesome_overridden.scss',
|
||||
|
||||
'web/static/src/legacy/js/promise_extension.js',
|
||||
'web/static/src/boot.js',
|
||||
'web/static/src/session.js',
|
||||
'web/static/src/legacy/js/core/cookie_utils.js',
|
||||
'web/static/src/polyfills/clipboard.js',
|
||||
|
||||
'web/static/lib/underscore/underscore.js',
|
||||
'web/static/lib/underscore.string/lib/underscore.string.js',
|
||||
'web/static/lib/moment/moment.js',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/owl2_compatibility/*.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery/jquery.browser.js',
|
||||
'web/static/lib/jquery.blockUI/jquery.blockUI.js',
|
||||
'web/static/lib/jquery.hotkeys/jquery.hotkeys.js',
|
||||
'web/static/lib/jquery.placeholder/jquery.placeholder.js',
|
||||
'web/static/lib/jquery.form/jquery.form.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
'web/static/lib/popper/popper.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/data.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/event-handler.js',
|
||||
|
|
@ -118,81 +82,17 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/lib/bootstrap/js/dist/scrollspy.js',
|
||||
'web/static/lib/bootstrap/js/dist/tab.js',
|
||||
'web/static/lib/bootstrap/js/dist/toast.js',
|
||||
'web/static/lib/tempusdominus/tempusdominus.js',
|
||||
'web/static/lib/select2/select2.js',
|
||||
'web/static/lib/clipboard/clipboard.js',
|
||||
'web/static/lib/jSignature/jSignatureCustom.js',
|
||||
'web/static/lib/qweb/qweb2.js',
|
||||
'web/static/src/legacy/js/assets.js',
|
||||
'web/static/src/legacy/js/libs/autocomplete.js',
|
||||
'web/static/src/legacy/js/libs/bootstrap.js',
|
||||
'web/static/src/legacy/js/libs/content-disposition.js',
|
||||
'web/static/src/legacy/js/libs/download.js',
|
||||
'web/static/src/legacy/js/libs/jquery.js',
|
||||
'web/static/src/legacy/js/libs/moment.js',
|
||||
'web/static/src/legacy/js/libs/underscore.js',
|
||||
'web/static/src/legacy/js/libs/pdfjs.js',
|
||||
'web/static/src/legacy/js/libs/zoomodoo.js',
|
||||
'web/static/src/legacy/js/libs/jSignatureCustom.js',
|
||||
'web/static/src/legacy/js/core/abstract_service.js',
|
||||
'web/static/src/legacy/js/core/abstract_storage_service.js',
|
||||
'web/static/src/legacy/js/core/ajax.js',
|
||||
'web/static/src/legacy/js/core/browser_detection.js',
|
||||
'web/static/src/legacy/js/core/bus.js',
|
||||
'web/static/src/legacy/js/core/class.js',
|
||||
'web/static/src/legacy/js/core/collections.js',
|
||||
'web/static/src/legacy/js/core/concurrency.js',
|
||||
'web/static/src/legacy/js/core/dialog.js',
|
||||
'web/static/src/legacy/xml/dialog.xml',
|
||||
'web/static/src/legacy/js/core/owl_dialog.js',
|
||||
'web/static/src/legacy/js/core/popover.js',
|
||||
'web/static/src/legacy/js/core/minimal_dom.js',
|
||||
'web/static/src/legacy/js/core/dom.js',
|
||||
'web/static/src/legacy/js/core/local_storage.js',
|
||||
'web/static/src/legacy/js/core/mixins.js',
|
||||
'web/static/src/legacy/js/core/qweb.js',
|
||||
'web/static/src/legacy/js/core/ram_storage.js',
|
||||
'web/static/src/legacy/js/core/registry.js',
|
||||
'web/static/src/legacy/js/core/rpc.js',
|
||||
'web/static/src/legacy/js/core/service_mixins.js',
|
||||
'web/static/src/legacy/js/core/session.js',
|
||||
'web/static/src/legacy/js/core/session_storage.js',
|
||||
'web/static/src/legacy/js/core/time.js',
|
||||
'web/static/src/legacy/js/core/translation.js',
|
||||
'web/static/src/legacy/js/core/utils.js',
|
||||
'web/static/src/legacy/js/core/widget.js',
|
||||
'web/static/src/legacy/js/services/ajax_service.js',
|
||||
'web/static/src/legacy/js/services/config.js',
|
||||
'web/static/src/legacy/js/services/core.js',
|
||||
'web/static/src/legacy/js/services/local_storage_service.js',
|
||||
'web/static/src/legacy/js/services/session_storage_service.js',
|
||||
'web/static/src/legacy/js/common_env.js',
|
||||
'web/static/src/legacy/js/widgets/name_and_signature.js',
|
||||
'web/static/src/legacy/xml/name_and_signature.xml',
|
||||
'web/static/src/legacy/js/core/smooth_scroll_on_drag.js',
|
||||
'web/static/src/legacy/js/widgets/colorpicker.js',
|
||||
'web/static/src/legacy/xml/colorpicker.xml',
|
||||
'web/static/src/legacy/js/widgets/translation_dialog.js',
|
||||
'web/static/src/legacy/xml/translation_dialog.xml',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
('include', 'web._assets_helpers'),
|
||||
('include', 'web._assets_backend_helpers'),
|
||||
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
|
||||
('include', 'web._assets_bootstrap'),
|
||||
|
||||
'base/static/src/css/modules.css',
|
||||
|
||||
'web/static/src/core/utils/transitions.scss',
|
||||
'web/static/src/core/**/*',
|
||||
'web/static/src/model/**/*',
|
||||
'web/static/src/search/**/*',
|
||||
'web/static/src/webclient/icons.scss', # variables required in list_controller.scss
|
||||
'web/static/src/views/**/*',
|
||||
'web/static/src/webclient/**/*',
|
||||
('remove', 'web/static/src/webclient/navbar/navbar.scss'), # already in assets_common
|
||||
('remove', 'web/static/src/webclient/clickbot/clickbot.js'), # lazy loaded
|
||||
('remove', 'web/static/src/views/form/button_box/*.scss'),
|
||||
|
||||
|
|
@ -201,108 +101,33 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/src/webclient/actions/reports/*.js',
|
||||
'web/static/src/webclient/actions/reports/*.xml',
|
||||
|
||||
'web/static/src/env.js',
|
||||
'web/static/src/libs/pdfjs.js',
|
||||
|
||||
'web/static/lib/jquery.scrollTo/jquery.scrollTo.js',
|
||||
'web/static/lib/py.js/lib/py.js',
|
||||
'web/static/lib/py.js/lib/py_extras.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
'web/static/src/scss/ace.scss',
|
||||
'web/static/src/scss/base_document_layout.scss',
|
||||
|
||||
'web/static/src/legacy/scss/domain_selector.scss',
|
||||
'web/static/src/legacy/scss/model_field_selector.scss',
|
||||
'web/static/src/legacy/scss/dropdown.scss',
|
||||
'web/static/src/legacy/scss/tooltip.scss',
|
||||
'web/static/src/legacy/scss/switch_company_menu.scss',
|
||||
'web/static/src/legacy/scss/ace.scss',
|
||||
'web/static/src/legacy/scss/fields.scss',
|
||||
'web/static/src/legacy/scss/views.scss',
|
||||
'web/static/src/legacy/scss/form_view.scss',
|
||||
'web/static/src/legacy/scss/list_view.scss',
|
||||
'web/static/src/legacy/scss/kanban_dashboard.scss',
|
||||
'web/static/src/legacy/scss/kanban_examples_dialog.scss',
|
||||
'web/static/src/legacy/scss/kanban_column_progressbar.scss',
|
||||
'web/static/src/legacy/scss/kanban_view.scss',
|
||||
'web/static/src/legacy/scss/data_export.scss',
|
||||
'base/static/src/scss/onboarding.scss',
|
||||
'web/static/src/legacy/scss/attachment_preview.scss',
|
||||
'web/static/src/legacy/scss/base_document_layout.scss',
|
||||
'web/static/src/legacy/scss/special_fields.scss',
|
||||
'web/static/src/legacy/scss/fields_extra.scss',
|
||||
'web/static/src/legacy/scss/form_view_extra.scss',
|
||||
'web/static/src/legacy/scss/list_view_extra.scss',
|
||||
'web/static/src/legacy/scss/color_picker.scss',
|
||||
'base/static/src/scss/res_partner.scss',
|
||||
|
||||
# Form style should be computed before
|
||||
'web/static/src/views/form/button_box/*.scss',
|
||||
|
||||
'web/static/src/legacy/action_adapters.js',
|
||||
'web/static/src/legacy/debug_manager.js',
|
||||
'web/static/src/legacy/legacy_service_provider.js',
|
||||
'web/static/src/legacy/legacy_client_actions.js',
|
||||
'web/static/src/legacy/legacy_dialog.js',
|
||||
'web/static/src/legacy/legacy_load_views.js',
|
||||
'web/static/src/legacy/legacy_views.js',
|
||||
'web/static/src/legacy/legacy_promise_error_handler.js',
|
||||
'web/static/src/legacy/legacy_rpc_error_handler.js',
|
||||
'web/static/src/legacy/root_widget.js',
|
||||
'web/static/src/legacy/systray_menu.js',
|
||||
'web/static/src/legacy/systray_menu_item.js',
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/web_client.js',
|
||||
'web/static/src/legacy/js/_deprecated/*',
|
||||
'web/static/src/legacy/js/chrome/*',
|
||||
'web/static/src/legacy/js/components/*',
|
||||
'web/static/src/legacy/js/control_panel/*',
|
||||
'web/static/src/legacy/js/core/domain.js',
|
||||
'web/static/src/legacy/js/core/mvc.js',
|
||||
'web/static/src/legacy/js/core/py_utils.js',
|
||||
'web/static/src/legacy/js/core/context.js',
|
||||
'web/static/src/legacy/js/core/misc.js',
|
||||
'web/static/src/legacy/js/fields/*',
|
||||
'web/static/src/legacy/js/services/data_manager.js',
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
'web/static/src/legacy/js/tools/tools.js',
|
||||
'web/static/src/legacy/js/views/**/*',
|
||||
'web/static/src/legacy/js/widgets/data_export.js',
|
||||
'web/static/src/legacy/js/widgets/date_picker.js',
|
||||
'web/static/src/legacy/js/widgets/domain_selector_dialog.js',
|
||||
'web/static/src/legacy/js/widgets/domain_selector.js',
|
||||
'web/static/src/legacy/js/widgets/iframe_widget.js',
|
||||
'web/static/src/legacy/js/widgets/model_field_selector.js',
|
||||
'web/static/src/legacy/js/widgets/model_field_selector_popover.js',
|
||||
'web/static/src/legacy/js/widgets/ribbon.js',
|
||||
'web/static/src/legacy/js/widgets/week_days.js',
|
||||
'web/static/src/legacy/js/widgets/signature.js',
|
||||
'web/static/src/legacy/js/widgets/attach_document.js',
|
||||
'web/static/src/legacy/js/apps.js',
|
||||
'web/static/src/legacy/js/env.js',
|
||||
'web/static/src/legacy/js/model.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
|
||||
'web/static/src/legacy/xml/base.xml',
|
||||
'web/static/src/legacy/xml/ribbon.xml',
|
||||
'web/static/src/legacy/xml/control_panel.xml',
|
||||
'web/static/src/legacy/xml/fields.xml',
|
||||
'web/static/src/legacy/xml/kanban.xml',
|
||||
'web/static/src/legacy/xml/search_panel.xml',
|
||||
'web/static/src/legacy/xml/week_days.xml',
|
||||
# Don't include dark mode files in light mode
|
||||
('remove', 'web/static/src/**/*.dark.scss'),
|
||||
],
|
||||
"web.assets_backend_legacy_lazy": [
|
||||
("include", "web._assets_helpers"),
|
||||
('include', 'web._assets_backend_helpers'),
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
'web.assets_web': [
|
||||
('include', 'web.assets_backend'),
|
||||
'web/static/src/main.js',
|
||||
'web/static/src/start.js',
|
||||
],
|
||||
'web.assets_frontend_minimal': [
|
||||
'web/static/src/legacy/js/promise_extension.js',
|
||||
'web/static/src/boot.js',
|
||||
'web/static/src/polyfills/object.js',
|
||||
'web/static/src/polyfills/array.js',
|
||||
'web/static/src/module_loader.js',
|
||||
'web/static/src/session.js',
|
||||
'web/static/src/legacy/js/core/cookie_utils.js',
|
||||
'web/static/src/legacy/js/core/menu.js',
|
||||
'web/static/src/core/browser/cookie.js',
|
||||
'web/static/src/legacy/js/core/minimal_dom.js',
|
||||
'web/static/src/legacy/js/public/lazyloader.js',
|
||||
],
|
||||
|
|
@ -320,54 +145,31 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
|
||||
('include', 'web._assets_bootstrap'),
|
||||
('include', 'web._assets_bootstrap_frontend'),
|
||||
|
||||
'web/static/src/legacy/scss/tempusdominus_overridden.scss',
|
||||
'web/static/lib/tempusdominus/tempusdominus.scss',
|
||||
'web/static/lib/jquery.ui/jquery-ui.css',
|
||||
'web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'web/static/lib/odoo_ui_icons/*',
|
||||
'web/static/lib/select2/select2.css',
|
||||
'web/static/lib/select2-bootstrap-css/select2-bootstrap.css',
|
||||
'web/static/lib/daterangepicker/daterangepicker.css',
|
||||
'web/static/src/webclient/navbar/navbar.scss',
|
||||
'web/static/src/legacy/scss/ui.scss',
|
||||
'web/static/src/legacy/scss/mimetypes.scss',
|
||||
'web/static/src/legacy/scss/modal.scss',
|
||||
'web/static/src/legacy/scss/animation.scss',
|
||||
'web/static/src/legacy/scss/datepicker.scss',
|
||||
'web/static/src/legacy/scss/daterangepicker.scss',
|
||||
'web/static/src/legacy/scss/banner.scss',
|
||||
'web/static/src/legacy/scss/colorpicker.scss',
|
||||
'web/static/src/legacy/scss/popover.scss',
|
||||
'web/static/src/legacy/scss/translation_dialog.scss',
|
||||
'web/static/src/legacy/scss/keyboard.scss',
|
||||
'web/static/src/legacy/scss/name_and_signature.scss',
|
||||
'web/static/src/legacy/scss/web.zoomodoo.scss',
|
||||
'web/static/src/legacy/scss/fontawesome_overridden.scss',
|
||||
'web/static/src/scss/animation.scss',
|
||||
'web/static/src/scss/base_frontend.scss',
|
||||
'web/static/src/scss/fontawesome_overridden.scss',
|
||||
'web/static/src/scss/mimetypes.scss',
|
||||
'web/static/src/scss/ui.scss',
|
||||
'web/static/src/views/fields/translation_dialog.scss',
|
||||
'web/static/src/views/fields/signature/signature_field.scss',
|
||||
|
||||
'web/static/src/legacy/scss/ui.scss',
|
||||
'web/static/src/legacy/scss/modal.scss',
|
||||
|
||||
'web/static/src/legacy/scss/base_frontend.scss',
|
||||
'web/static/src/legacy/scss/lazyloader.scss',
|
||||
|
||||
('include', 'web.assets_frontend_minimal'),
|
||||
|
||||
'web/static/lib/underscore/underscore.js',
|
||||
'web/static/lib/underscore.string/lib/underscore.string.js',
|
||||
'web/static/lib/moment/moment.js',
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/owl2_compatibility/*.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery/jquery.browser.js',
|
||||
'web/static/lib/jquery.blockUI/jquery.blockUI.js',
|
||||
'web/static/lib/jquery.hotkeys/jquery.hotkeys.js',
|
||||
'web/static/lib/jquery.placeholder/jquery.placeholder.js',
|
||||
'web/static/lib/jquery.form/jquery.form.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
'web/static/lib/popper/popper.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/data.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/event-handler.js',
|
||||
|
|
@ -386,100 +188,46 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/lib/bootstrap/js/dist/scrollspy.js',
|
||||
'web/static/lib/bootstrap/js/dist/tab.js',
|
||||
'web/static/lib/bootstrap/js/dist/toast.js',
|
||||
'web/static/lib/tempusdominus/tempusdominus.js',
|
||||
'web/static/lib/select2/select2.js',
|
||||
'web/static/lib/clipboard/clipboard.js',
|
||||
'web/static/lib/jSignature/jSignatureCustom.js',
|
||||
'web/static/lib/qweb/qweb2.js',
|
||||
'web/static/src/legacy/js/assets.js',
|
||||
'web/static/src/legacy/js/libs/autocomplete.js',
|
||||
'web/static/src/legacy/js/libs/bootstrap.js',
|
||||
'web/static/src/legacy/js/libs/content-disposition.js',
|
||||
'web/static/src/legacy/js/libs/download.js',
|
||||
'web/static/src/legacy/js/libs/jquery.js',
|
||||
'web/static/src/legacy/js/libs/moment.js',
|
||||
'web/static/src/legacy/js/libs/underscore.js',
|
||||
'web/static/src/legacy/js/libs/pdfjs.js',
|
||||
'web/static/src/legacy/js/libs/zoomodoo.js',
|
||||
'web/static/src/legacy/js/libs/jSignatureCustom.js',
|
||||
'web/static/src/legacy/js/core/abstract_service.js',
|
||||
'web/static/src/legacy/js/core/abstract_storage_service.js',
|
||||
'web/static/src/legacy/js/core/ajax.js',
|
||||
'web/static/src/legacy/js/core/browser_detection.js',
|
||||
'web/static/src/legacy/js/core/bus.js',
|
||||
'web/static/src/legacy/js/core/class.js',
|
||||
'web/static/src/legacy/js/core/collections.js',
|
||||
'web/static/src/legacy/js/core/concurrency.js',
|
||||
'web/static/src/legacy/js/core/dialog.js',
|
||||
'web/static/src/legacy/xml/dialog.xml',
|
||||
'web/static/src/legacy/js/core/owl_dialog.js',
|
||||
'web/static/src/legacy/js/core/popover.js',
|
||||
'web/static/src/legacy/js/core/dom.js',
|
||||
'web/static/src/legacy/js/core/local_storage.js',
|
||||
'web/static/src/legacy/js/core/menu.js',
|
||||
'web/static/src/legacy/js/core/mixins.js',
|
||||
'web/static/src/legacy/js/core/qweb.js',
|
||||
'web/static/src/legacy/js/core/ram_storage.js',
|
||||
'web/static/src/legacy/js/core/registry.js',
|
||||
'web/static/src/legacy/js/core/rpc.js',
|
||||
'web/static/src/legacy/js/core/service_mixins.js',
|
||||
'web/static/src/legacy/js/core/session.js',
|
||||
'web/static/src/legacy/js/core/session_storage.js',
|
||||
'web/static/src/legacy/js/core/time.js',
|
||||
'web/static/src/legacy/js/core/translation.js',
|
||||
'web/static/src/legacy/js/core/utils.js',
|
||||
'web/static/src/legacy/js/core/widget.js',
|
||||
'web/static/src/legacy/js/services/ajax_service.js',
|
||||
'web/static/src/legacy/js/services/config.js',
|
||||
'web/static/src/legacy/js/services/core.js',
|
||||
'web/static/src/legacy/js/services/local_storage_service.js',
|
||||
'web/static/src/legacy/js/services/session_storage_service.js',
|
||||
'web/static/src/legacy/js/common_env.js',
|
||||
'web/static/src/legacy/js/widgets/name_and_signature.js',
|
||||
'web/static/src/legacy/xml/name_and_signature.xml',
|
||||
'web/static/src/legacy/js/core/smooth_scroll_on_drag.js',
|
||||
'web/static/src/legacy/js/widgets/colorpicker.js',
|
||||
'web/static/src/legacy/xml/colorpicker.xml',
|
||||
'web/static/src/legacy/js/widgets/translation_dialog.js',
|
||||
'web/static/src/legacy/xml/translation_dialog.xml',
|
||||
|
||||
'web/static/src/env.js',
|
||||
'web/static/src/core/utils/transitions.scss', # included early because used by other files
|
||||
'web/static/src/core/**/*',
|
||||
('remove', 'web/static/src/core/commands/**/*'),
|
||||
('remove', 'web/static/src/core/debug/debug_menu.js'),
|
||||
('remove', 'web/static/src/core/file_viewer/file_viewer.dark.scss'),
|
||||
('remove', 'web/static/src/core/emoji_picker/emoji_data.js'),
|
||||
'web/static/src/core/commands/default_providers.js',
|
||||
'web/static/src/core/commands/command_palette.js',
|
||||
'web/static/src/public/error_notifications.js',
|
||||
'web/static/src/public/public_component_service.js',
|
||||
'web/static/src/public/datetime_picker_widget.js',
|
||||
'web/static/src/libs/pdfjs.js',
|
||||
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/core/misc.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
'web/static/src/legacy/js/public/public_env.js',
|
||||
'web/static/src/legacy/js/public/public_root.js',
|
||||
'web/static/src/legacy/js/public/public_root_instance.js',
|
||||
'web/static/src/legacy/js/public/public_widget.js',
|
||||
'web/static/src/legacy/legacy_promise_error_handler.js',
|
||||
'web/static/src/legacy/legacy_rpc_error_handler.js',
|
||||
'web/static/src/legacy/js/fields/field_utils.js',
|
||||
'web/static/src/legacy/js/public/signin.js',
|
||||
|
||||
('include', 'web.frontend_legacy'),
|
||||
],
|
||||
'web.assets_frontend_lazy': [
|
||||
('include', 'web.assets_frontend'),
|
||||
# Remove assets_frontend_minimal
|
||||
('remove', 'web/static/src/legacy/js/promise_extension.js'),
|
||||
('remove', 'web/static/src/boot.js'),
|
||||
('remove', 'web/static/src/module_loader.js'),
|
||||
('remove', 'web/static/src/session.js'),
|
||||
('remove', 'web/static/src/legacy/js/core/cookie_utils.js'),
|
||||
('remove', 'web/static/src/legacy/js/core/menu.js'),
|
||||
('remove', 'web/static/src/core/browser/cookie.js'),
|
||||
('remove', 'web/static/src/legacy/js/core/minimal_dom.js'),
|
||||
('remove', 'web/static/src/legacy/js/public/lazyloader.js'),
|
||||
],
|
||||
'web.assets_backend_prod_only': [
|
||||
'web/static/src/main.js',
|
||||
'web/static/src/start.js',
|
||||
'web/static/src/legacy/legacy_setup.js',
|
||||
],
|
||||
# Optional Bundle for PDFJS lib
|
||||
# Since PDFJS is quite huge (80000≈ lines), please only load it when it is necessary.
|
||||
# For now, it is only use to display the PDF slide Viewer during an embed.
|
||||
|
|
@ -495,10 +243,29 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
|
||||
('include', 'web._assets_bootstrap'),
|
||||
('include', 'web._assets_bootstrap_backend'),
|
||||
|
||||
'web/static/lib/bootstrap/js/dist/dom/data.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/event-handler.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/manipulator.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/selector-engine.js',
|
||||
'web/static/lib/bootstrap/js/dist/base-component.js',
|
||||
'web/static/lib/bootstrap/js/dist/alert.js',
|
||||
'web/static/lib/bootstrap/js/dist/button.js',
|
||||
'web/static/lib/bootstrap/js/dist/carousel.js',
|
||||
'web/static/lib/bootstrap/js/dist/collapse.js',
|
||||
'web/static/lib/bootstrap/js/dist/dropdown.js',
|
||||
'web/static/lib/bootstrap/js/dist/modal.js',
|
||||
'web/static/lib/bootstrap/js/dist/offcanvas.js',
|
||||
'web/static/lib/bootstrap/js/dist/tooltip.js',
|
||||
'web/static/lib/bootstrap/js/dist/popover.js',
|
||||
'web/static/lib/bootstrap/js/dist/scrollspy.js',
|
||||
'web/static/lib/bootstrap/js/dist/tab.js',
|
||||
'web/static/lib/bootstrap/js/dist/toast.js',
|
||||
|
||||
'base/static/src/css/description.css',
|
||||
'web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'web/static/src/scss/fontawesome_overridden.scss',
|
||||
'web/static/lib/odoo_ui_icons/*',
|
||||
'web/static/fonts/fonts.scss',
|
||||
|
||||
|
|
@ -508,30 +275,29 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/src/webclient/actions/reports/layout_assets/layout_boxed.scss',
|
||||
'web/static/src/webclient/actions/reports/layout_assets/layout_clean.scss',
|
||||
'web/static/asset_styles_company_report.scss',
|
||||
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
'web/static/src/legacy/js/public/public_root.js',
|
||||
'web/static/src/legacy/js/public/public_root_instance.js',
|
||||
'web/static/src/legacy/js/public/public_widget.js',
|
||||
'web/static/src/legacy/js/report/report.js',
|
||||
],
|
||||
'web.report_assets_pdf': [
|
||||
'web/static/src/webclient/actions/reports/reset.min.css',
|
||||
],
|
||||
|
||||
'web.ace_lib': [
|
||||
"web/static/lib/ace/ace.js",
|
||||
"web/static/lib/ace/mode-js.js",
|
||||
"web/static/lib/ace/javascript_highlight_rules.js",
|
||||
"web/static/lib/ace/mode-xml.js",
|
||||
"web/static/lib/ace/mode-qweb.js",
|
||||
"web/static/lib/ace/mode-python.js",
|
||||
"web/static/lib/ace/mode-scss.js",
|
||||
"web/static/lib/ace/theme-monokai.js",
|
||||
],
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# COLOR SCHEME BUNDLES
|
||||
# ---------------------------------------------------------------------
|
||||
"web.dark_mode_assets_common": [
|
||||
('include', 'web.assets_common'),
|
||||
],
|
||||
"web.dark_mode_assets_backend": [
|
||||
('include', 'web.assets_backend'),
|
||||
"web.assets_web_dark": [
|
||||
('include', 'web.assets_web'),
|
||||
'web/static/src/**/*.dark.scss',
|
||||
],
|
||||
"web.dark_mode_variables": [
|
||||
('before', 'base/static/src/scss/onboarding.variables.scss', 'base/static/src/scss/onboarding.variables.dark.scss'),
|
||||
],
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# SUB BUNDLES
|
||||
|
|
@ -542,14 +308,25 @@ This module provides the core of the Odoo Web Client.
|
|||
# Their naming conventions are similar to those of the main bundles,
|
||||
# with the addition of a prefixed underscore to reflect the "private"
|
||||
# aspect.
|
||||
#
|
||||
# Examples:
|
||||
# > web._assets_helpers = define assets needed in most main bundles
|
||||
|
||||
# Bare javascript essentials: module loader, core folder and core libs
|
||||
'web._assets_core': [
|
||||
# module loader
|
||||
'web/static/src/module_loader.js',
|
||||
# libs
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
# core
|
||||
'web/static/src/env.js',
|
||||
'web/static/src/session.js',
|
||||
'web/static/src/core/utils/transitions.scss',
|
||||
'web/static/src/core/**/*',
|
||||
('remove', 'web/static/src/core/emoji_picker/emoji_data.js'), # always lazy-loaded
|
||||
],
|
||||
'web._assets_primary_variables': [
|
||||
'web/static/src/scss/primary_variables.scss',
|
||||
'web/static/src/**/**/*.variables.scss',
|
||||
'base/static/src/scss/onboarding.variables.scss',
|
||||
'web/static/src/**/*.variables.scss',
|
||||
],
|
||||
'web._assets_secondary_variables': [
|
||||
'web/static/src/scss/secondary_variables.scss',
|
||||
|
|
@ -557,9 +334,10 @@ This module provides the core of the Odoo Web Client.
|
|||
'web._assets_helpers': [
|
||||
'web/static/lib/bootstrap/scss/_functions.scss',
|
||||
'web/static/lib/bootstrap/scss/_mixins.scss',
|
||||
'web/static/src/scss/functions.scss',
|
||||
'web/static/src/scss/mixins_forwardport.scss',
|
||||
'web/static/src/scss/bs_mixins_overrides.scss',
|
||||
'web/static/src/legacy/scss/utils.scss',
|
||||
'web/static/src/scss/utils.scss',
|
||||
|
||||
('include', 'web._assets_primary_variables'),
|
||||
('include', 'web._assets_secondary_variables'),
|
||||
|
|
@ -571,6 +349,15 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/lib/bootstrap/scss/utilities/_api.scss',
|
||||
'web/static/src/scss/bootstrap_review.scss',
|
||||
],
|
||||
'web._assets_bootstrap_backend': [
|
||||
('include', 'web._assets_bootstrap'),
|
||||
('after', 'web/static/src/scss/utilities_custom.scss', 'web/static/src/scss/utilities_custom_backend.scss'),
|
||||
'web/static/src/scss/bootstrap_review_backend.scss',
|
||||
],
|
||||
'web._assets_bootstrap_frontend': [
|
||||
('include', 'web._assets_bootstrap'),
|
||||
'web/static/src/scss/bootstrap_review_frontend.scss',
|
||||
],
|
||||
'web._assets_backend_helpers': [
|
||||
'web/static/src/scss/bootstrap_overridden.scss',
|
||||
'web/static/src/scss/bs_mixins_overrides_backend.scss',
|
||||
|
|
@ -579,11 +366,6 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/src/scss/bootstrap_overridden_frontend.scss',
|
||||
],
|
||||
|
||||
# Used during the transition of the web architecture
|
||||
'web.frontend_legacy': [
|
||||
'web/static/src/legacy/frontend/**/*',
|
||||
],
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# TESTS BUNDLES
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
@ -591,29 +373,30 @@ This module provides the core of the Odoo Web Client.
|
|||
'web.assets_tests': [
|
||||
# No tours are defined in web, but the bundle "assets_tests" is
|
||||
# first called in web.
|
||||
'web/static/tests/legacy/helpers/test_utils_file.js',
|
||||
'web/static/tests/helpers/cleanup.js',
|
||||
'web/static/tests/helpers/utils.js',
|
||||
'web/static/tests/utils.js',
|
||||
],
|
||||
# remove this bundle alongside the owl2 compatibility layer
|
||||
'web.tests_assets_common': [
|
||||
('include', 'web.assets_common'),
|
||||
('after', 'web/static/src/owl2_compatibility/app.js', 'web/static/tests/owl2_compatibility_app.js'),
|
||||
'web.__assets_tests_call__': [
|
||||
'web/static/tests/ignore_missing_deps_start.js',
|
||||
('include', 'web.assets_tests'),
|
||||
'web/static/tests/ignore_missing_deps_stop.js',
|
||||
],
|
||||
'web.tests_assets': [
|
||||
('include', 'web.assets_backend'),
|
||||
|
||||
'web/static/src/public/public_component_service.js',
|
||||
'web/static/tests/patch_translations.js',
|
||||
'web/static/lib/qunit/qunit-2.9.1.css',
|
||||
'web/static/lib/qunit/qunit-2.9.1.js',
|
||||
'web/static/tests/legacy/helpers/**/*',
|
||||
('remove', 'web/static/tests/legacy/helpers/test_utils_tests.js'),
|
||||
'web/static/tests/legacy/legacy_setup.js',
|
||||
|
||||
'web/static/lib/fullcalendar/core/main.css',
|
||||
'web/static/lib/fullcalendar/daygrid/main.css',
|
||||
'web/static/lib/fullcalendar/timegrid/main.css',
|
||||
'web/static/lib/fullcalendar/list/main.css',
|
||||
'web/static/lib/fullcalendar/core/main.js',
|
||||
'web/static/lib/fullcalendar/moment/main.js',
|
||||
'web/static/lib/fullcalendar/interaction/main.js',
|
||||
'web/static/lib/fullcalendar/daygrid/main.js',
|
||||
'web/static/lib/fullcalendar/timegrid/main.js',
|
||||
|
|
@ -628,17 +411,13 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/lib/ace/mode-xml.js',
|
||||
'web/static/lib/ace/mode-js.js',
|
||||
'web/static/lib/ace/mode-qweb.js',
|
||||
'web/static/lib/nearest/jquery.nearest.js',
|
||||
'web/static/lib/daterangepicker/daterangepicker.js',
|
||||
'web/static/src/legacy/js/libs/daterangepicker.js',
|
||||
'web/static/lib/ace/theme-monokai.js',
|
||||
'web/static/lib/stacktracejs/stacktrace.js',
|
||||
'web/static/lib/Chart/Chart.js',
|
||||
('include', "web.chartjs_lib"),
|
||||
'web/static/lib/jSignature/jSignatureCustom.js',
|
||||
'web/static/src/libs/jSignatureCustom.js',
|
||||
|
||||
'/web/static/lib/daterangepicker/daterangepicker.js',
|
||||
|
||||
# 'web/static/tests/legacy/main_tests.js',
|
||||
'web/static/tests/helpers/**/*.js',
|
||||
'web/static/tests/utils.js',
|
||||
'web/static/tests/views/helpers.js',
|
||||
'web/static/tests/search/helpers.js',
|
||||
'web/static/tests/views/calendar/helpers.js',
|
||||
|
|
@ -647,50 +426,39 @@ This module provides the core of the Odoo Web Client.
|
|||
'web/static/tests/main.js',
|
||||
'web/static/tests/mock_server_tests.js',
|
||||
'web/static/tests/setup.js',
|
||||
|
||||
# These 2 lines below are taken from web.assets_frontend
|
||||
# They're required for the web.frontend_legacy to work properly
|
||||
# It is expected to add other lines coming from the web.assets_frontend
|
||||
# if we need to add more and more legacy stuff that would require other scss or js.
|
||||
('include', 'web._assets_helpers'),
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
|
||||
('include', 'web.frontend_legacy'),
|
||||
("include", "web.assets_backend_legacy_lazy"),
|
||||
'web/static/tests/utils.js',
|
||||
'web/static/src/webclient/clickbot/clickbot.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'web/static/tests/env_tests.js',
|
||||
'web/static/tests/dependencies_tests.js',
|
||||
'web/static/tests/reactivity_tests.js',
|
||||
'web/static/tests/core/**/*.js',
|
||||
'web/static/tests/l10n/**/*.js',
|
||||
'web/static/tests/search/**/*.js',
|
||||
'web/static/tests/model/**/*.js',
|
||||
('remove', 'web/static/tests/search/helpers.js'),
|
||||
'web/static/tests/views/**/*.js',
|
||||
('remove', 'web/static/tests/views/helpers.js'),
|
||||
('remove', 'web/static/tests/views/calendar/helpers.js'),
|
||||
'web/static/tests/webclient/**/*.js',
|
||||
('remove', 'web/static/tests/webclient/**/helpers.js'),
|
||||
'web/static/tests/legacy/**/*.js',
|
||||
('remove', 'web/static/tests/legacy/**/*_mobile_tests.js'),
|
||||
('remove', 'web/static/tests/legacy/**/*_benchmarks.js'),
|
||||
('remove', 'web/static/tests/legacy/helpers/**/*.js'),
|
||||
('remove', 'web/static/tests/legacy/legacy_setup.js'),
|
||||
'web/static/tests/public/**/*.js',
|
||||
|
||||
('include', 'web.frontend_legacy_tests'),
|
||||
# Legacy
|
||||
'web/static/tests/legacy/**/*.js',
|
||||
('remove', 'web/static/tests/legacy/helpers/**/*.js'),
|
||||
],
|
||||
'web.qunit_mobile_suite_tests': [
|
||||
'web/static/tests/mobile/**/*.js',
|
||||
|
||||
'web/static/tests/legacy/fields/basic_fields_mobile_tests.js',
|
||||
'web/static/tests/legacy/fields/relational_fields_mobile_tests.js',
|
||||
'web/static/tests/legacy/components/dropdown_menu_mobile_tests.js',
|
||||
],
|
||||
|
||||
# Used during the transition of the web architecture
|
||||
'web.frontend_legacy_tests': [
|
||||
'web/static/tests/legacy/frontend/*.js',
|
||||
'web.assets_clickbot': [
|
||||
'web/static/src/webclient/clickbot/clickbot.js',
|
||||
],
|
||||
"web.chartjs_lib" : [
|
||||
'/web/static/lib/Chart/Chart.js',
|
||||
'/web/static/lib/chartjs-adapter-luxon/chartjs-adapter-luxon.js',
|
||||
]
|
||||
},
|
||||
'bootstrap': True, # load translations for login screen,
|
||||
'license': 'LGPL-3',
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ from . import pivot
|
|||
from . import profiling
|
||||
from . import report
|
||||
from . import session
|
||||
from . import vcard
|
||||
from . import view
|
||||
from . import webclient
|
||||
from . import webmanifest
|
||||
|
||||
from . import main # deprecated
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from odoo import _
|
||||
from odoo.exceptions import MissingError
|
||||
from odoo.http import Controller, request, route
|
||||
from .utils import clean_action
|
||||
|
||||
|
|
@ -21,8 +23,8 @@ class Action(Controller):
|
|||
action = request.env.ref(action_id)
|
||||
assert action._name.startswith('ir.actions.')
|
||||
action_id = action.id
|
||||
except Exception:
|
||||
action_id = 0 # force failed read
|
||||
except Exception as exc:
|
||||
raise MissingError(_("The action %r does not exist.", action_id)) from exc
|
||||
|
||||
base_action = Actions.browse([action_id]).sudo().read(['type'])
|
||||
if base_action:
|
||||
|
|
@ -37,7 +39,9 @@ class Action(Controller):
|
|||
return value
|
||||
|
||||
@route('/web/action/run', type='json', auth="user")
|
||||
def run(self, action_id):
|
||||
def run(self, action_id, context=None):
|
||||
if context:
|
||||
request.update_context(**context)
|
||||
action = request.env['ir.actions.server'].browse([action_id])
|
||||
result = action.run()
|
||||
return clean_action(result, env=action.env) if result else False
|
||||
|
|
|
|||
|
|
@ -15,14 +15,13 @@ except ImportError:
|
|||
|
||||
import odoo
|
||||
import odoo.modules.registry
|
||||
from odoo import http, _
|
||||
from odoo import SUPERUSER_ID, _, http
|
||||
from odoo.addons.base.models.assetsbundle import ANY_UNIQUE
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.http import request, Response
|
||||
from odoo.modules import get_resource_path
|
||||
from odoo.tools import file_open, file_path, replace_exceptions, str2bool
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
from odoo.tools.image import image_guess_size_from_field_name
|
||||
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -85,33 +84,59 @@ class Binary(http.Controller):
|
|||
|
||||
return stream.get_response(**send_file_kwargs)
|
||||
|
||||
@http.route(['/web/assets/debug/<string:filename>',
|
||||
'/web/assets/debug/<path:extra>/<string:filename>',
|
||||
'/web/assets/<int:id>/<string:filename>',
|
||||
'/web/assets/<int:id>-<string:unique>/<string:filename>',
|
||||
'/web/assets/<int:id>-<string:unique>/<path:extra>/<string:filename>'], type='http', auth="public")
|
||||
# pylint: disable=redefined-builtin,invalid-name
|
||||
def content_assets(self, id=None, filename=None, unique=False, extra=None, nocache=False):
|
||||
if not id:
|
||||
domain = [('url', '!=', False), ('res_model', '=', 'ir.ui.view'),
|
||||
('res_id', '=', 0), ('create_uid', '=', odoo.SUPERUSER_ID)]
|
||||
if extra:
|
||||
domain += [('url', '=like', f'/web/assets/%/{extra}/{filename}')]
|
||||
else:
|
||||
domain += [
|
||||
('url', '=like', f'/web/assets/%/{filename}'),
|
||||
('url', 'not like', f'/web/assets/%/%/{filename}')
|
||||
]
|
||||
attachments = request.env['ir.attachment'].sudo().search_read(domain, fields=['id'], limit=1)
|
||||
if not attachments:
|
||||
raise request.not_found()
|
||||
id = attachments[0]['id']
|
||||
with replace_exceptions(UserError, by=request.not_found()):
|
||||
record = request.env['ir.binary']._find_record(res_id=int(id))
|
||||
stream = request.env['ir.binary']._get_stream_from(record, 'raw', filename)
|
||||
|
||||
send_file_kwargs = {'as_attachment': False, 'content_security_policy': None}
|
||||
if unique:
|
||||
@http.route([
|
||||
'/web/assets/<string:unique>/<string:filename>'], type='http', auth="public")
|
||||
def content_assets(self, filename=None, unique=ANY_UNIQUE, nocache=False, assets_params=None):
|
||||
assets_params = assets_params or {}
|
||||
assert isinstance(assets_params, dict)
|
||||
debug_assets = unique == 'debug'
|
||||
if unique in ('any', '%'):
|
||||
unique = ANY_UNIQUE
|
||||
attachment = None
|
||||
if unique != 'debug':
|
||||
url = request.env['ir.asset']._get_asset_bundle_url(filename, unique, assets_params)
|
||||
assert not '%' in url
|
||||
domain = [
|
||||
('public', '=', True),
|
||||
('url', '!=', False),
|
||||
('url', '=like', url),
|
||||
('res_model', '=', 'ir.ui.view'),
|
||||
('res_id', '=', 0),
|
||||
('create_uid', '=', SUPERUSER_ID),
|
||||
]
|
||||
attachment = request.env['ir.attachment'].sudo().search(domain, limit=1)
|
||||
if not attachment:
|
||||
# try to generate one
|
||||
try:
|
||||
if filename.endswith('.map'):
|
||||
_logger.error(".map should have been generated through debug assets, (version %s most likely outdated)", unique)
|
||||
raise request.not_found()
|
||||
bundle_name, rtl, asset_type = request.env['ir.asset']._parse_bundle_name(filename, debug_assets)
|
||||
css = asset_type == 'css'
|
||||
js = asset_type == 'js'
|
||||
bundle = request.env['ir.qweb']._get_asset_bundle(
|
||||
bundle_name,
|
||||
css=css,
|
||||
js=js,
|
||||
debug_assets=debug_assets,
|
||||
rtl=rtl,
|
||||
assets_params=assets_params,
|
||||
)
|
||||
# check if the version matches. If not, redirect to the last version
|
||||
if not debug_assets and unique != ANY_UNIQUE and unique != bundle.get_version(asset_type):
|
||||
return request.redirect(bundle.get_link(asset_type))
|
||||
if css and bundle.stylesheets:
|
||||
attachment = bundle.css()
|
||||
elif js and bundle.javascripts:
|
||||
attachment = bundle.js()
|
||||
except ValueError as e:
|
||||
_logger.warning("Parsing asset bundle %s has failed: %s", filename, e)
|
||||
raise request.not_found() from e
|
||||
if not attachment:
|
||||
raise request.not_found()
|
||||
stream = request.env['ir.binary']._get_stream_from(attachment, 'raw', filename)
|
||||
send_file_kwargs = {'as_attachment': False}
|
||||
if unique and unique != 'debug':
|
||||
send_file_kwargs['immutable'] = True
|
||||
send_file_kwargs['max_age'] = http.STATIC_CACHE_LONG
|
||||
if nocache:
|
||||
|
|
@ -190,7 +215,7 @@ class Binary(http.Controller):
|
|||
try:
|
||||
attachment = Model.create({
|
||||
'name': filename,
|
||||
'datas': base64.encodebytes(ufile.read()),
|
||||
'raw': ufile.read(),
|
||||
'res_model': model,
|
||||
'res_id': int(id)
|
||||
})
|
||||
|
|
@ -203,7 +228,7 @@ class Binary(http.Controller):
|
|||
else:
|
||||
args.append({
|
||||
'filename': clean(filename),
|
||||
'mimetype': ufile.content_type,
|
||||
'mimetype': attachment.mimetype,
|
||||
'id': attachment.id,
|
||||
'size': attachment.file_size
|
||||
})
|
||||
|
|
@ -217,12 +242,11 @@ class Binary(http.Controller):
|
|||
def company_logo(self, dbname=None, **kw):
|
||||
imgname = 'logo'
|
||||
imgext = '.png'
|
||||
placeholder = functools.partial(get_resource_path, 'web', 'static', 'img')
|
||||
dbname = request.db
|
||||
uid = (request.session.uid if dbname else None) or odoo.SUPERUSER_ID
|
||||
|
||||
if not dbname:
|
||||
response = http.Stream.from_path(placeholder(imgname + imgext)).get_response()
|
||||
response = http.Stream.from_path(file_path('web/static/img/logo.png')).get_response()
|
||||
else:
|
||||
try:
|
||||
# create an empty registry
|
||||
|
|
@ -258,9 +282,9 @@ class Binary(http.Controller):
|
|||
response_class=Response,
|
||||
)
|
||||
else:
|
||||
response = http.Stream.from_path(placeholder('nologo.png')).get_response()
|
||||
response = http.Stream.from_path(file_path('web/static/img/nologo.png')).get_response()
|
||||
except Exception:
|
||||
response = http.Stream.from_path(placeholder(imgname + imgext)).get_response()
|
||||
response = http.Stream.from_path(file_path(f'web/static/img/{imgname}{imgext}')).get_response()
|
||||
|
||||
return response
|
||||
|
||||
|
|
@ -276,7 +300,7 @@ class Binary(http.Controller):
|
|||
"""
|
||||
supported_exts = ('.ttf', '.otf', '.woff', '.woff2')
|
||||
fonts = []
|
||||
fonts_directory = file_path(os.path.join('web', 'static', 'fonts', 'sign'))
|
||||
fonts_directory = file_path('web/static/fonts/sign')
|
||||
if fontname:
|
||||
font_path = os.path.join(fonts_directory, fontname)
|
||||
with file_open(font_path, 'rb', filter_ext=supported_exts) as font_file:
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class Database(http.Controller):
|
|||
dispatch_rpc('db', 'change_admin_password', ["admin", master_pwd])
|
||||
try:
|
||||
if not re.match(DBNAME_PATTERN, name):
|
||||
raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
|
||||
raise Exception(_('Houston, we have a database naming issue! Make sure you only use letters, numbers, underscores, hyphens, or dots in the database name, and you\'ll be golden.'))
|
||||
# country code could be = "False" which is actually True in python
|
||||
country_code = post.get('country_code') or False
|
||||
dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code, post['phone']])
|
||||
|
|
@ -94,7 +94,7 @@ class Database(http.Controller):
|
|||
dispatch_rpc('db', 'change_admin_password', ["admin", master_pwd])
|
||||
try:
|
||||
if not re.match(DBNAME_PATTERN, new_name):
|
||||
raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
|
||||
raise Exception(_('Houston, we have a database naming issue! Make sure you only use letters, numbers, underscores, hyphens, or dots in the database name, and you\'ll be golden.'))
|
||||
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name, neutralize_database])
|
||||
if request.db == name:
|
||||
request.env.cr.close() # duplicating a database leads to an unusable cursor
|
||||
|
|
@ -140,7 +140,7 @@ class Database(http.Controller):
|
|||
error = "Database backup error: %s" % (str(e) or repr(e))
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False, max_content_length=None)
|
||||
def restore(self, master_pwd, backup_file, name, copy=False, neutralize_database=False):
|
||||
insecure = odoo.tools.config.verify_admin_password('admin')
|
||||
if insecure and master_pwd:
|
||||
|
|
|
|||
|
|
@ -15,29 +15,11 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
class DataSet(http.Controller):
|
||||
|
||||
@http.route('/web/dataset/search_read', type='json', auth="user")
|
||||
def search_read(self, model, fields=False, offset=0, limit=False, domain=None, sort=None):
|
||||
return request.env[model].web_search_read(domain, fields, offset=offset, limit=limit, order=sort)
|
||||
|
||||
@http.route('/web/dataset/load', type='json', auth="user")
|
||||
def load(self, model, id, fields):
|
||||
warnings.warn("the route /web/dataset/load is deprecated and will be removed in Odoo 17. Use /web/dataset/call_kw with method 'read' and a list containing the id as args instead", DeprecationWarning)
|
||||
value = {}
|
||||
r = request.env[model].browse([id]).read()
|
||||
if r:
|
||||
value = r[0]
|
||||
return {'value': value}
|
||||
|
||||
def _call_kw(self, model, method, args, kwargs):
|
||||
Model = request.env[model]
|
||||
get_public_method(Model, method) # Don't use the result, call_kw will redo the getattr
|
||||
return call_kw(Model, method, args, kwargs)
|
||||
|
||||
@http.route('/web/dataset/call', type='json', auth="user")
|
||||
def call(self, model, method, args, domain_id=None, context_id=None):
|
||||
warnings.warn("the route /web/dataset/call is deprecated and will be removed in Odoo 17. Use /web/dataset/call_kw with empty kwargs instead", DeprecationWarning)
|
||||
return self._call_kw(model, method, args, {})
|
||||
|
||||
@http.route(['/web/dataset/call_kw', '/web/dataset/call_kw/<path:path>'], type='json', auth="user")
|
||||
def call_kw(self, model, method, args, kwargs, path=None):
|
||||
return self._call_kw(model, method, args, kwargs)
|
||||
|
|
@ -50,7 +32,7 @@ class DataSet(http.Controller):
|
|||
return False
|
||||
|
||||
@http.route('/web/dataset/resequence', type='json', auth="user")
|
||||
def resequence(self, model, ids, field='sequence', offset=0):
|
||||
def resequence(self, model, ids, field='sequence', offset=0, context=None):
|
||||
""" Re-sequences a number of records in the model, by their ids
|
||||
|
||||
The re-sequencing starts at the first model of ``ids``, the sequence
|
||||
|
|
@ -64,6 +46,8 @@ class DataSet(http.Controller):
|
|||
starting the resequencing from an arbitrary number,
|
||||
defaults to ``0``
|
||||
"""
|
||||
if context:
|
||||
request.update_context(**context)
|
||||
m = request.env[model]
|
||||
if not m.fields_get([field]):
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class ExportXlsxWriter:
|
|||
cell_value = pycompat.to_text(cell_value)
|
||||
except UnicodeDecodeError:
|
||||
raise UserError(_("Binary fields can not be exported to Excel unless their content is base64-encoded. That does not seem to be the case for %s.", self.field_names)[column])
|
||||
elif isinstance(cell_value, (list, tuple)):
|
||||
elif isinstance(cell_value, (list, tuple, dict)):
|
||||
cell_value = pycompat.to_text(cell_value)
|
||||
|
||||
if isinstance(cell_value, str):
|
||||
|
|
@ -335,10 +335,7 @@ class Export(http.Controller):
|
|||
if field.get('type') in ('properties', 'properties_definition'):
|
||||
continue
|
||||
if field.get('readonly'):
|
||||
# If none of the field's states unsets readonly, skip the field
|
||||
if all(dict(attrs).get('readonly', True)
|
||||
for attrs in field.get('states', {}).values()):
|
||||
continue
|
||||
continue
|
||||
if not field.get('exportable', True):
|
||||
continue
|
||||
|
||||
|
|
@ -488,7 +485,7 @@ class ExportFormat(object):
|
|||
if not import_compat and groupby:
|
||||
groupby_type = [Model._fields[x.split(':')[0]].type for x in groupby]
|
||||
domain = [('id', 'in', ids)] if ids else domain
|
||||
groups_data = Model.with_context(active_test=False).read_group(domain, [x if x != '.id' else 'id' for x in field_names], groupby, lazy=False)
|
||||
groups_data = Model.with_context(active_test=False).read_group(domain, ['__count'], groupby, lazy=False)
|
||||
|
||||
# read_group(lazy=False) returns a dict only for final groups (with actual data),
|
||||
# not for intermediary groups. The full group tree must be re-constructed.
|
||||
|
|
@ -523,7 +520,7 @@ class ExportFormat(object):
|
|||
class CSVExport(ExportFormat, http.Controller):
|
||||
|
||||
@http.route('/web/export/csv', type='http', auth="user")
|
||||
def index(self, data):
|
||||
def web_export_csv(self, data):
|
||||
try:
|
||||
return self.base(data)
|
||||
except Exception as exc:
|
||||
|
|
@ -567,7 +564,7 @@ class CSVExport(ExportFormat, http.Controller):
|
|||
class ExcelExport(ExportFormat, http.Controller):
|
||||
|
||||
@http.route('/web/export/xlsx', type='http', auth="user")
|
||||
def index(self, data):
|
||||
def web_export_xlsx(self, data):
|
||||
try:
|
||||
return self.base(data)
|
||||
except Exception as exc:
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class Home(http.Controller):
|
|||
# Ensure we have both a database and a user
|
||||
ensure_db()
|
||||
if not request.session.uid:
|
||||
return request.redirect('/web/login', 303)
|
||||
return request.redirect_query('/web/login', query=request.params, code=303)
|
||||
if kw.get('redirect'):
|
||||
return request.redirect(kw.get('redirect'), 303)
|
||||
if not security.check_session(request.session, request.env):
|
||||
|
|
@ -63,12 +63,16 @@ class Home(http.Controller):
|
|||
return request.redirect('/web/login?error=access')
|
||||
|
||||
@http.route('/web/webclient/load_menus/<string:unique>', type='http', auth='user', methods=['GET'])
|
||||
def web_load_menus(self, unique):
|
||||
def web_load_menus(self, unique, lang=None):
|
||||
"""
|
||||
Loads the menus for the webclient
|
||||
:param unique: this parameters is not used, but mandatory: it is used by the HTTP stack to make a unique request
|
||||
:param lang: language in which the menus should be loaded (only works if language is installed)
|
||||
:return: the menus (including the images in Base64)
|
||||
"""
|
||||
if lang:
|
||||
request.update_context(lang=lang)
|
||||
|
||||
menus = request.env["ir.ui.menu"].load_web_menus(request.session.debug)
|
||||
body = json.dumps(menus, default=ustr)
|
||||
response = request.make_response(body, [
|
||||
|
|
@ -143,7 +147,7 @@ class Home(http.Controller):
|
|||
if request.env.user._is_system():
|
||||
uid = request.session.uid = odoo.SUPERUSER_ID
|
||||
# invalidate session token cache as we've changed the uid
|
||||
request.env['res.users'].clear_caches()
|
||||
request.env.registry.clear_cache()
|
||||
request.session.session_token = security.compute_session_token(request.session, request.env)
|
||||
|
||||
return request.redirect(self._login_redirect(uid))
|
||||
|
|
@ -165,11 +169,16 @@ class Home(http.Controller):
|
|||
('Cache-Control', 'no-store')]
|
||||
return request.make_response(data, headers, status=status)
|
||||
|
||||
@http.route(['/robots.txt'], type='http', auth="none")
|
||||
def robots(self, **kwargs):
|
||||
allowed_routes = self._get_allowed_robots_routes()
|
||||
robots_content = ["User-agent: *", "Disallow: /"]
|
||||
robots_content.extend(f"Allow: {route}" for route in allowed_routes)
|
||||
|
||||
return request.make_response("\n".join(robots_content), [('Content-Type', 'text/plain')])
|
||||
|
||||
def _get_allowed_robots_routes(self):
|
||||
"""Override this method to return a list of allowed routes.
|
||||
By default this controller does not serve robots.txt so all routes
|
||||
are implicitly open but we want any module to be able to append
|
||||
to this list, in case the website module is installed.
|
||||
|
||||
:return: A list of URL paths that should be allowed by robots.txt
|
||||
Examples: ['/social_instagram/', '/sitemap.xml', '/web/']
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class ReportController(http.Controller):
|
|||
else:
|
||||
return
|
||||
except Exception as e:
|
||||
_logger.exception("Error while generating report %s", reportname)
|
||||
_logger.warning("Error while generating report %s", reportname, exc_info=True)
|
||||
se = http.serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
|
|
|
|||
48
odoo-bringout-oca-ocb-web/web/controllers/vcard.py
Normal file
48
odoo-bringout-oca-ocb-web/web/controllers/vcard.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import importlib.util
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
import odoo.http as http
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request, content_disposition
|
||||
|
||||
|
||||
class Partner(http.Controller):
|
||||
|
||||
@http.route(['/web_enterprise/partner/<model("res.partner"):partner>/vcard',
|
||||
'/web/partner/vcard'], type='http', auth="user")
|
||||
def download_vcard(self, partner_ids=None, partner=None, **kwargs):
|
||||
if importlib.util.find_spec('vobject') is None:
|
||||
raise UserError('vobject library is not installed')
|
||||
|
||||
if partner_ids:
|
||||
partner_ids = list(filter(None, (int(pid) for pid in partner_ids.split(',') if pid.isdigit())))
|
||||
partners = request.env['res.partner'].browse(partner_ids)
|
||||
if len(partners) > 1:
|
||||
with io.BytesIO() as buffer:
|
||||
with zipfile.ZipFile(buffer, 'w') as zipf:
|
||||
for partner in partners:
|
||||
filename = f"{partner.name or partner.email}.vcf"
|
||||
content = partner._get_vcard_file()
|
||||
zipf.writestr(filename, content)
|
||||
|
||||
return request.make_response(buffer.getvalue(), [
|
||||
('Content-Type', 'application/zip'),
|
||||
('Content-Length', len(content)),
|
||||
('Content-Disposition', content_disposition('Contacts.zip'))
|
||||
])
|
||||
|
||||
if partner or partners:
|
||||
partner = partner or partners
|
||||
content = partner._get_vcard_file()
|
||||
return request.make_response(content, [
|
||||
('Content-Type', 'text/vcard'),
|
||||
('Content-Length', len(content)),
|
||||
('Content-Disposition', content_disposition(f"{partner.name or partner.email}.vcf")),
|
||||
])
|
||||
|
||||
return request.not_found()
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import Controller, route, request
|
||||
from odoo.tools.translate import _
|
||||
|
||||
|
||||
class View(Controller):
|
||||
|
|
@ -14,6 +16,8 @@ class View(Controller):
|
|||
:param str arch: the edited arch of the custom view
|
||||
:returns: dict with acknowledged operation (result set to True)
|
||||
"""
|
||||
custom_view = request.env['ir.ui.view.custom'].browse(custom_id)
|
||||
custom_view = request.env['ir.ui.view.custom'].sudo().browse(custom_id)
|
||||
if not custom_view.user_id == request.env.user:
|
||||
raise AccessError(_("Custom view %s does not belong to user %s", custom_id, self.env.user.login))
|
||||
custom_view.write({'arch': arch})
|
||||
return {'result': True}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import werkzeug.wsgi
|
|||
import odoo
|
||||
import odoo.modules.registry
|
||||
from odoo import http
|
||||
from odoo.modules import get_manifest, get_resource_path
|
||||
from odoo.modules import get_manifest
|
||||
from odoo.http import request
|
||||
from odoo.tools import lazy
|
||||
from odoo.tools.misc import file_open
|
||||
from odoo.tools.misc import file_open, file_path
|
||||
from .utils import _local_web_translations
|
||||
|
||||
|
||||
|
|
@ -29,32 +29,11 @@ def CONTENT_MAXAGE():
|
|||
return http.STATIC_CACHE_LONG
|
||||
|
||||
|
||||
MOMENTJS_LANG_CODES_MAP = {
|
||||
"sr_RS": "sr_cyrl",
|
||||
"sr@latin": "sr"
|
||||
}
|
||||
|
||||
|
||||
class WebClient(http.Controller):
|
||||
|
||||
# FIXME: to be removed in master, deprecated since momentjs removal in commit 4327c062d820
|
||||
@http.route('/web/webclient/locale/<string:lang>', type='http', auth="none")
|
||||
def load_locale(self, lang):
|
||||
lang = MOMENTJS_LANG_CODES_MAP.get(lang, lang)
|
||||
magic_file_finding = [lang.replace("_", '-').lower(), lang.split('_')[0]]
|
||||
for code in magic_file_finding:
|
||||
try:
|
||||
return http.Response(
|
||||
werkzeug.wsgi.wrap_file(
|
||||
request.httprequest.environ,
|
||||
file_open(f'web/static/lib/moment/locale/{code}.js', 'rb')
|
||||
),
|
||||
content_type='application/javascript; charset=utf-8',
|
||||
headers=[('Cache-Control', f'max-age={http.STATIC_CACHE}')],
|
||||
direct_passthrough=True,
|
||||
)
|
||||
except IOError:
|
||||
_logger.debug("No moment locale for code %s", code)
|
||||
|
||||
return request.make_response("", headers=[
|
||||
('Content-Type', 'application/javascript'),
|
||||
('Cache-Control', f'max-age={http.STATIC_CACHE}'),
|
||||
|
|
@ -80,7 +59,7 @@ class WebClient(http.Controller):
|
|||
for addon_name in mods:
|
||||
manifest = get_manifest(addon_name)
|
||||
if manifest and manifest['bootstrap']:
|
||||
f_name = get_resource_path(addon_name, 'i18n', f'{lang}.po')
|
||||
f_name = file_path(f'{addon_name}/i18n/{lang}.po')
|
||||
if not f_name:
|
||||
continue
|
||||
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
|
||||
|
|
@ -103,10 +82,13 @@ class WebClient(http.Controller):
|
|||
elif mods is None:
|
||||
mods = list(request.env.registry._init_modules) + (odoo.conf.server_wide_modules or [])
|
||||
|
||||
if lang and lang not in {code for code, _ in request.env['res.lang'].sudo().get_installed()}:
|
||||
lang = None
|
||||
|
||||
translations_per_module, lang_params = request.env["ir.http"].get_translations_for_webclient(mods, lang)
|
||||
|
||||
body = json.dumps({
|
||||
'lang': lang_params and lang_params["code"],
|
||||
'lang': lang,
|
||||
'lang_parameters': lang_params,
|
||||
'modules': translations_per_module,
|
||||
'multi_lang': len(request.env['res.lang'].sudo().get_installed()) > 1,
|
||||
|
|
@ -131,10 +113,6 @@ class WebClient(http.Controller):
|
|||
def test_mobile_suite(self, mod=None, **kwargs):
|
||||
return request.render('web.qunit_mobile_suite')
|
||||
|
||||
@http.route('/web/benchmarks', type='http', auth="none")
|
||||
def benchmarks(self, mod=None, **kwargs):
|
||||
return request.render('web.benchmark_suite')
|
||||
|
||||
@http.route('/web/bundle/<string:bundle_name>', auth="public", methods=["GET"])
|
||||
def bundle(self, bundle_name, **bundle_params):
|
||||
"""
|
||||
|
|
@ -148,7 +126,6 @@ class WebClient(http.Controller):
|
|||
data = [{
|
||||
"type": tag,
|
||||
"src": attrs.get("src") or attrs.get("data-src") or attrs.get('href'),
|
||||
"content": content,
|
||||
} for tag, attrs, content in files]
|
||||
} for tag, attrs in files]
|
||||
|
||||
return request.make_json_response(data)
|
||||
|
|
|
|||
97
odoo-bringout-oca-ocb-web/web/controllers/webmanifest.py
Normal file
97
odoo-bringout-oca-ocb-web/web/controllers/webmanifest.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import base64
|
||||
import json
|
||||
import mimetypes
|
||||
|
||||
from odoo import http
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import request
|
||||
from odoo.tools import ustr, file_open
|
||||
|
||||
|
||||
class WebManifest(http.Controller):
|
||||
|
||||
def _get_shortcuts(self):
|
||||
module_names = ['mail', 'crm', 'project', 'project_todo']
|
||||
try:
|
||||
module_ids = request.env['ir.module.module'].search([('state', '=', 'installed'), ('name', 'in', module_names)]) \
|
||||
.sorted(key=lambda r: module_names.index(r["name"]))
|
||||
except AccessError:
|
||||
return []
|
||||
menu_roots = request.env['ir.ui.menu'].get_user_roots()
|
||||
datas = request.env['ir.model.data'].sudo().search([('model', '=', 'ir.ui.menu'),
|
||||
('res_id', 'in', menu_roots.ids),
|
||||
('module', 'in', module_names)])
|
||||
shortcuts = []
|
||||
for module in module_ids:
|
||||
data = datas.filtered(lambda res: res.module == module.name)
|
||||
if data:
|
||||
shortcuts.append({
|
||||
'name': module.display_name,
|
||||
'url': '/web#menu_id=%s' % data.mapped('res_id')[0],
|
||||
'description': module.summary,
|
||||
'icons': [{
|
||||
'sizes': '100x100',
|
||||
'src': module.icon,
|
||||
'type': mimetypes.guess_type(module.icon)[0] or 'image/png'
|
||||
}]
|
||||
})
|
||||
return shortcuts
|
||||
|
||||
@http.route('/web/manifest.webmanifest', type='http', auth='public', methods=['GET'])
|
||||
def webmanifest(self):
|
||||
""" Returns a WebManifest describing the metadata associated with a web application.
|
||||
Using this metadata, user agents can provide developers with means to create user
|
||||
experiences that are more comparable to that of a native application.
|
||||
"""
|
||||
web_app_name = request.env['ir.config_parameter'].sudo().get_param('web.web_app_name', 'Odoo')
|
||||
manifest = {
|
||||
'name': web_app_name,
|
||||
'scope': '/web',
|
||||
'start_url': '/web',
|
||||
'display': 'standalone',
|
||||
'background_color': '#714B67',
|
||||
'theme_color': '#714B67',
|
||||
'prefer_related_applications': False,
|
||||
}
|
||||
icon_sizes = ['192x192', '512x512']
|
||||
manifest['icons'] = [{
|
||||
'src': '/web/static/img/odoo-icon-%s.png' % size,
|
||||
'sizes': size,
|
||||
'type': 'image/png',
|
||||
} for size in icon_sizes]
|
||||
manifest['shortcuts'] = self._get_shortcuts()
|
||||
body = json.dumps(manifest, default=ustr)
|
||||
response = request.make_response(body, [
|
||||
('Content-Type', 'application/manifest+json'),
|
||||
])
|
||||
return response
|
||||
|
||||
@http.route('/web/service-worker.js', type='http', auth='public', methods=['GET'])
|
||||
def service_worker(self):
|
||||
response = request.make_response(
|
||||
self._get_service_worker_content(),
|
||||
[
|
||||
('Content-Type', 'text/javascript'),
|
||||
('Service-Worker-Allowed', '/web'),
|
||||
]
|
||||
)
|
||||
return response
|
||||
|
||||
def _get_service_worker_content(self):
|
||||
""" Returns a ServiceWorker javascript file scoped for the backend (aka. '/web')
|
||||
"""
|
||||
with file_open('web/static/src/service_worker.js') as f:
|
||||
body = f.read()
|
||||
return body
|
||||
|
||||
def _icon_path(self):
|
||||
return 'web/static/img/odoo-icon-192x192.png'
|
||||
|
||||
@http.route('/web/offline', type='http', auth='public', methods=['GET'])
|
||||
def offline(self):
|
||||
""" Returns the offline page delivered by the service worker """
|
||||
return request.render('web.webclient_offline', {
|
||||
'odoo_icon': base64.b64encode(file_open(self._icon_path(), 'rb').read())
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
33900
odoo-bringout-oca-ocb-web/web/i18n/es_419.po
Normal file
33900
odoo-bringout-oca-ocb-web/web/i18n/es_419.po
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,20 +0,0 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~12.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-23 11:33+0000\n"
|
||||
"PO-Revision-Date: 2019-09-23 11:33+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: es_BO\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: web
|
||||
#: model_terms:ir.ui.view,arch_db:web.external_layout_background
|
||||
msgid ""
|
||||
"<i class=\"fa fa-building-o\" role=\"img\" aria-label=\"Fiscal number\"/>"
|
||||
msgstr "<i class=\"fa fa-building-o\" role=\"img\" aria-label=\"NIT\"/>"
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo 16.0\n"
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-01-25 10:43+0000\n"
|
||||
"POT-Creation-Date: 2024-02-05 16:24+0000\n"
|
||||
"PO-Revision-Date: 2016-03-12 06:25+0000\n"
|
||||
"Last-Translator: Martin Trigaux\n"
|
||||
"Language-Team: Spanish (Chile) (http://www.transifex.com/odoo/odoo-9/"
|
||||
|
|
@ -25,12 +25,11 @@ msgstr ""
|
|||
#. module: web
|
||||
#: model_terms:ir.ui.view,arch_db:web.report_invoice_wizard_preview
|
||||
msgid "<strong>Untaxed Amount</strong>"
|
||||
msgstr "<strong>Total neto</strong>"
|
||||
msgstr "<strong>Monto neto</strong>"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/components/action_menus.js:0
|
||||
#: code:addons/web/static/src/search/action_menus/action_menus.xml:0
|
||||
#: code:addons/web/static/src/views/form/status_bar_buttons/status_bar_buttons.xml:0
|
||||
#, python-format
|
||||
msgid "Action"
|
||||
|
|
@ -38,8 +37,7 @@ msgstr "Acción"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/relational_fields.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/kanban.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.js:0
|
||||
#: code:addons/web/static/src/views/fields/x2many/x2many_field.js:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_column_quick_create.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_record_quick_create.xml:0
|
||||
|
|
@ -49,17 +47,8 @@ msgstr "Agregar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/relational_fields.js:0
|
||||
#, python-format
|
||||
msgid "Add: "
|
||||
msgstr "Agregar: "
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/basic_fields.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.xml:0
|
||||
#: code:addons/web/static/src/search/group_by_menu/custom_group_by_item.xml:0
|
||||
#: code:addons/web/static/src/views/fields/daterange/daterange_field.js:0
|
||||
#: code:addons/web/static/src/core/datetime/datetime_picker_popover.xml:0
|
||||
#: code:addons/web/static/src/search/custom_group_by_item/custom_group_by_item.xml:0
|
||||
#, python-format
|
||||
msgid "Apply"
|
||||
msgstr "Aplicar"
|
||||
|
|
@ -67,17 +56,10 @@ msgstr "Aplicar"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js:0
|
||||
#: code:addons/web/static/src/core/errors/error_dialogs.xml:0
|
||||
#: code:addons/web/static/src/core/signature/signature_dialog.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/core/dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/fields/basic_fields.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_column.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_confirm_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/signature_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/control_panel.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.js:0
|
||||
#: code:addons/web/static/src/views/calendar/quick_create/calendar_quick_create.xml:0
|
||||
#: code:addons/web/static/src/views/fields/daterange/daterange_field.js:0
|
||||
#: code:addons/web/static/src/views/list/list_confirmation_dialog.xml:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.xml:0
|
||||
#: code:addons/web/static/src/webclient/settings_form_view/fields/upgrade_dialog.xml:0
|
||||
|
|
@ -91,14 +73,14 @@ msgstr "Cancelar"
|
|||
#: code:addons/web/static/src/core/debug/debug_menu_items.xml:0
|
||||
#: code:addons/web/static/src/core/dialog/dialog.xml:0
|
||||
#: code:addons/web/static/src/core/domain_selector_dialog/domain_selector_dialog.xml:0
|
||||
#: code:addons/web/static/src/core/errors/error_dialogs.xml:0
|
||||
#: code:addons/web/static/src/core/file_viewer/file_viewer.xml:0
|
||||
#: code:addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml:0
|
||||
#: code:addons/web/static/src/core/notifications/notification.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_column_quick_create.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/domain_selector_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/legacy/xml/dialog.xml:0
|
||||
#: code:addons/web/static/src/views/fields/dynamic_placeholder_popover.xml:0
|
||||
#: code:addons/web/static/src/views/fields/relational_utils.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_column_examples_dialog.xml:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.xml:0
|
||||
|
|
@ -112,7 +94,6 @@ msgstr "Cerrar"
|
|||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/core/dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_confirm_dialog.js:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_controller.js:0
|
||||
#: code:addons/web/static/src/views/list/list_confirmation_dialog.js:0
|
||||
#, python-format
|
||||
|
|
@ -122,17 +103,22 @@ msgstr "Confirmación"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/relational_fields.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_controller.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_year/calendar_year_popover.xml:0
|
||||
#: code:addons/web/static/src/views/calendar/quick_create/calendar_quick_create.xml:0
|
||||
#: code:addons/web/static/src/views/fields/many2one/many2one_field.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_renderer.js:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_record_quick_create.js:0
|
||||
#, python-format
|
||||
msgid "Create"
|
||||
msgstr "Crear"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/relational_fields.js:0
|
||||
#, python-format
|
||||
msgid "Create: "
|
||||
msgstr "Crear: "
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
|
|
@ -144,7 +130,7 @@ msgstr "Día"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/core/file_viewer/file_viewer.xml:0
|
||||
#: code:addons/web/static/src/views/fields/binary/binary_field.xml:0
|
||||
#: code:addons/web/static/src/views/fields/many2many_binary/many2many_binary_field.xml:0
|
||||
#, python-format
|
||||
|
|
@ -153,13 +139,12 @@ msgstr "Descargar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/legacy/xml/kanban.xml:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.xml:0
|
||||
#: code:addons/web/static/src/views/calendar/quick_create/calendar_quick_create.xml:0
|
||||
#: code:addons/web/static/src/views/fields/binary/binary_field.xml:0
|
||||
#: code:addons/web/static/src/views/fields/image/image_field.xml:0
|
||||
#: code:addons/web/static/src/views/fields/pdf_viewer/pdf_viewer_field.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_header.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_record_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Edit"
|
||||
|
|
@ -167,7 +152,6 @@ msgstr "Editar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/basic_fields.js:0
|
||||
#: code:addons/web/static/src/views/fields/email/email_field.js:0
|
||||
#: model:ir.model.fields,field_description:web.field_base_document_layout__email
|
||||
#: model_terms:ir.ui.view,arch_db:web.login
|
||||
|
|
@ -185,8 +169,6 @@ msgstr "Error"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_controller.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#: code:addons/web/static/src/views/list/list_controller.js:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.xml:0
|
||||
#, python-format
|
||||
|
|
@ -195,7 +177,6 @@ msgstr "Exportar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.js:0
|
||||
#, python-format
|
||||
msgid "Export Data"
|
||||
|
|
@ -203,25 +184,18 @@ msgstr "Exportar datos"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/search/favorite_menu/favorite_menu.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.xml:0
|
||||
#, python-format
|
||||
msgid "Favorites"
|
||||
msgstr "Favoritos"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/search/filter_menu/filter_menu.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.xml:0
|
||||
#, python-format
|
||||
msgid "Filters"
|
||||
msgstr "Filtros"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/views/form/form_view.js:0
|
||||
#, python-format
|
||||
msgid "Form"
|
||||
msgstr "Formulario"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/views/graph/graph_view.js:0
|
||||
|
|
@ -231,19 +205,15 @@ msgstr "Gráfico"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/search/group_by_menu/group_by_menu.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.xml:0
|
||||
#: code:addons/web/static/src/views/pivot/pivot_group_by_menu.xml:0
|
||||
#, python-format
|
||||
msgid "Group By"
|
||||
msgstr "Agrupar por"
|
||||
|
||||
#. module: web
|
||||
#: model:ir.model.fields,field_description:web.field_base_document_layout__id
|
||||
msgid "ID"
|
||||
msgstr "ID (identificación)"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/basic_fields.js:0
|
||||
#: code:addons/web/static/src/core/file_viewer/file_viewer.xml:0
|
||||
#: code:addons/web/static/src/views/fields/attachment_image/attachment_image_field.xml:0
|
||||
#: code:addons/web/static/src/views/fields/image/image_field.js:0
|
||||
#: code:addons/web/static/src/views/fields/image_url/image_url_field.js:0
|
||||
|
|
@ -259,13 +229,6 @@ msgstr "Imagen"
|
|||
msgid "Languages"
|
||||
msgstr "Idiomas"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_view.js:0
|
||||
#, python-format
|
||||
msgid "List"
|
||||
msgstr "Lista"
|
||||
|
||||
#. module: web
|
||||
#: model_terms:ir.ui.view,arch_db:web.login
|
||||
msgid "Log in"
|
||||
|
|
@ -294,11 +257,8 @@ msgstr "Nuevo"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_bar.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_column.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_renderer.js:0
|
||||
#: code:addons/web/static/src/search/search_bar/search_bar.js:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_renderer.js:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_header.js:0
|
||||
#: code:addons/web/static/src/views/list/list_renderer.js:0
|
||||
#: code:addons/web/static/src/views/pivot/pivot_model.js:0
|
||||
#, python-format
|
||||
|
|
@ -307,7 +267,6 @@ msgstr "No"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/views/view_button/view_button.xml:0
|
||||
#, python-format
|
||||
msgid "Object:"
|
||||
|
|
@ -317,15 +276,10 @@ msgstr "Objeto:"
|
|||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js:0
|
||||
#: code:addons/web/static/src/core/dialog/dialog.xml:0
|
||||
#: code:addons/web/static/src/core/errors/error_dialogs.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/core/dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_column.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_confirm_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/legacy/xml/control_panel.xml:0
|
||||
#: code:addons/web/static/src/public/error_notifications.js:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_year/calendar_year_popover.xml:0
|
||||
#: code:addons/web/static/src/views/list/list_confirmation_dialog.xml:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/form_view_dialog.xml:0
|
||||
#: code:addons/web/static/src/webclient/actions/action_dialog.xml:0
|
||||
#, python-format
|
||||
|
|
@ -335,8 +289,6 @@ msgstr "Aceptar"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/fields/relational_fields.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/form/form_controller.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#, python-format
|
||||
msgid "Open: "
|
||||
msgstr "Abrir: "
|
||||
|
|
@ -348,7 +300,6 @@ msgstr "Contraseña"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.js:0
|
||||
#, python-format
|
||||
msgid "Please enter save field list name"
|
||||
|
|
@ -356,14 +307,6 @@ msgstr "Por favor, introduzca el nombre de la lista de campos a guardar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#, python-format
|
||||
msgid "Please select fields to export..."
|
||||
msgstr "Por favor, seleccione los campos a exportar"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/widgets/data_export.js:0
|
||||
#: code:addons/web/static/src/views/view_dialogs/export_data_dialog.js:0
|
||||
#, python-format
|
||||
msgid "Please select fields to save export list..."
|
||||
|
|
@ -379,8 +322,10 @@ msgstr "Preferencias"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/file_viewer/file_viewer.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/components/action_menus.js:0
|
||||
#: code:addons/web/static/src/search/action_menus/action_menus.xml:0
|
||||
#: code:addons/web/static/src/search/cog_menu/cog_menu.xml:0
|
||||
#: code:addons/web/static/src/webclient/actions/reports/report_action.xml:0
|
||||
#, python-format
|
||||
msgid "Print"
|
||||
|
|
@ -388,8 +333,6 @@ msgstr "Imprimir"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar/search_bar.xml:0
|
||||
#: code:addons/web/static/src/views/fields/relational_utils.js:0
|
||||
#: code:addons/web/static/src/views/fields/relational_utils.xml:0
|
||||
|
|
@ -400,12 +343,8 @@ msgstr "Eliminar"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/domain_selector_dialog/domain_selector_dialog.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/views/view_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/domain_selector_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/translation_dialog.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/favorite_menu/custom_favorite_item.xml:0
|
||||
#: code:addons/web/static/src/search/custom_favorite_item/custom_favorite_item.xml:0
|
||||
#: code:addons/web/static/src/views/fields/relational_utils.xml:0
|
||||
#: code:addons/web/static/src/views/fields/translation_dialog.xml:0
|
||||
#: code:addons/web/static/src/views/form/form_controller.xml:0
|
||||
|
|
@ -444,12 +383,8 @@ msgstr "Sin definir"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/errors/error_dialogs.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/basic/basic_controller.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_controller.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/favorite_menu/favorite_menu.js:0
|
||||
#: code:addons/web/static/src/search/search_bar_menu/search_bar_menu.js:0
|
||||
#: code:addons/web/static/src/views/fields/domain/domain_field.xml:0
|
||||
#: code:addons/web/static/src/views/fields/translation_button.js:0
|
||||
#: code:addons/web/static/src/views/list/list_controller.js:0
|
||||
#, python-format
|
||||
msgid "Warning"
|
||||
|
|
@ -461,6 +396,7 @@ msgstr "Aviso"
|
|||
#: code:addons/web/static/src/search/utils/dates.js:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_common/calendar_common_renderer.js:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_controller.js:0
|
||||
#: code:addons/web/static/src/views/calendar/calendar_controller.xml:0
|
||||
#, python-format
|
||||
msgid "Week"
|
||||
msgstr "Semana"
|
||||
|
|
@ -476,13 +412,9 @@ msgstr "Año"
|
|||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_bar.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/kanban/kanban_column.js:0
|
||||
#: code:addons/web/static/src/legacy/js/views/list/list_renderer.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/search_bar/search_bar.js:0
|
||||
#: code:addons/web/static/src/views/fields/field_tooltip.xml:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_renderer.js:0
|
||||
#: code:addons/web/static/src/views/kanban/kanban_header.js:0
|
||||
#: code:addons/web/static/src/views/list/list_renderer.js:0
|
||||
#: code:addons/web/static/src/views/pivot/pivot_model.js:0
|
||||
#, python-format
|
||||
|
|
@ -494,7 +426,6 @@ msgstr "Sí"
|
|||
#: code:addons/web/static/src/core/domain_selector/domain_selector_operators.js:0
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/domain_selector.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "contains"
|
||||
msgstr "contiene"
|
||||
|
|
@ -502,7 +433,6 @@ msgstr "contiene"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "doesn't contain"
|
||||
msgstr "no contiene"
|
||||
|
|
@ -510,19 +440,16 @@ msgstr "no contiene"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "greater than"
|
||||
msgstr "mayor que"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/domain_selector/domain_selector_leaf_node.xml:0
|
||||
#: code:addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.js:0
|
||||
#: code:addons/web/static/src/core/domain_selector/domain_selector_operators.js:0
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/domain_selector.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "is"
|
||||
msgstr "es"
|
||||
|
|
@ -530,17 +457,15 @@ msgstr "es"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "is equal to"
|
||||
msgstr "es igual a"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/domain_selector/fields/domain_selector_boolean_field.js:0
|
||||
#: code:addons/web/static/src/core/domain_selector/domain_selector_operators.js:0
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/legacy/js/widgets/domain_selector.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "is not"
|
||||
msgstr "no es"
|
||||
|
|
@ -548,7 +473,6 @@ msgstr "no es"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "is not equal to"
|
||||
msgstr "es distinto de"
|
||||
|
|
@ -556,17 +480,14 @@ msgstr "es distinto de"
|
|||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/legacy/js/control_panel/search_utils.js:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.js:0
|
||||
#, python-format
|
||||
msgid "less than"
|
||||
msgstr "menor que"
|
||||
|
||||
#. module: web
|
||||
#. odoo-javascript
|
||||
#: code:addons/web/static/src/core/domain_selector/domain_selector_leaf_node.xml:0
|
||||
#: code:addons/web/static/src/legacy/js/views/action_model.js:0
|
||||
#: code:addons/web/static/src/core/domain_selector/utils.js:0
|
||||
#: code:addons/web/static/src/legacy/xml/base.xml:0
|
||||
#: code:addons/web/static/src/search/filter_menu/custom_filter_item.xml:0
|
||||
#: code:addons/web/static/src/search/search_model.js:0
|
||||
#, python-format
|
||||
msgid "or"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
33729
odoo-bringout-oca-ocb-web/web/i18n/ku.po
Normal file
33729
odoo-bringout-oca-ocb-web/web/i18n/ku.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
33733
odoo-bringout-oca-ocb-web/web/i18n/my.po
Normal file
33733
odoo-bringout-oca-ocb-web/web/i18n/my.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -7,3 +7,6 @@ from . import ir_model
|
|||
from . import ir_ui_menu
|
||||
from . import models
|
||||
from . import base_document_layout
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
import markupsafe
|
||||
import os
|
||||
from markupsafe import Markup
|
||||
from math import ceil
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
from odoo.addons.base.models.ir_qweb_fields import nl2br
|
||||
from odoo.modules import get_resource_path
|
||||
from odoo.tools import file_path, html2plaintext, is_html_empty
|
||||
from odoo.tools import html2plaintext, is_html_empty
|
||||
from odoo.tools.misc import file_path
|
||||
|
||||
try:
|
||||
import sass as libsass
|
||||
|
|
@ -217,7 +218,7 @@ class BaseDocumentLayout(models.TransientModel):
|
|||
return False, False
|
||||
|
||||
base_w, base_h = image.size
|
||||
w = int(50 * base_w / base_h)
|
||||
w = ceil(50 * base_w / base_h)
|
||||
h = 50
|
||||
|
||||
# Converts to RGBA (if already RGBA, this is a noop)
|
||||
|
|
@ -251,14 +252,6 @@ class BaseDocumentLayout(models.TransientModel):
|
|||
|
||||
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 = 'web.action_base_document_layout_configurator'
|
||||
res = self.env["ir.actions.actions"]._for_xml_id(action_ref)
|
||||
self.env[res["res_model"]].check_access_rights('write')
|
||||
return res
|
||||
|
||||
def document_layout_save(self):
|
||||
# meant to be overridden
|
||||
return self.env.context.get('report_action') or {'type': 'ir.actions.act_window_close'}
|
||||
|
|
@ -306,7 +299,7 @@ class BaseDocumentLayout(models.TransientModel):
|
|||
|
||||
precision = 8
|
||||
output_style = 'expanded'
|
||||
bootstrap_path = get_resource_path('web', 'static', 'lib', 'bootstrap', 'scss')
|
||||
bootstrap_path = file_path('web/static/lib/bootstrap/scss')
|
||||
|
||||
try:
|
||||
return libsass.compile(
|
||||
|
|
|
|||
|
|
@ -2,17 +2,14 @@
|
|||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
import odoo
|
||||
from odoo import api, http, models
|
||||
from odoo.http import request
|
||||
from odoo.tools import file_open, image_process, ustr
|
||||
from odoo import api, models
|
||||
from odoo.http import request, DEFAULT_MAX_CONTENT_LENGTH
|
||||
from odoo.tools import ormcache, ustr
|
||||
from odoo.tools.misc import str2bool
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
Debug mode is stored in session and should always be a string.
|
||||
It can be activated with an URL query string `debug=<mode>` where mode
|
||||
|
|
@ -58,6 +55,11 @@ class Http(models.AbstractModel):
|
|||
super()._pre_dispatch(rule, args)
|
||||
cls._handle_debug()
|
||||
|
||||
@classmethod
|
||||
def _post_logout(cls):
|
||||
super()._post_logout()
|
||||
request.future_response.set_cookie('cids', max_age=0)
|
||||
|
||||
def webclient_rendering_context(self):
|
||||
return {
|
||||
'menu_data': request.env['ir.ui.menu'].load_menus(request.session.debug),
|
||||
|
|
@ -79,24 +81,27 @@ class Http(models.AbstractModel):
|
|||
IrConfigSudo = self.env['ir.config_parameter'].sudo()
|
||||
max_file_upload_size = int(IrConfigSudo.get_param(
|
||||
'web.max_file_upload_size',
|
||||
default=128 * 1024 * 1024, # 128MiB
|
||||
default=DEFAULT_MAX_CONTENT_LENGTH,
|
||||
))
|
||||
mods = odoo.conf.server_wide_modules or []
|
||||
if request.db:
|
||||
mods = list(request.registry._init_modules) + mods
|
||||
is_internal_user = user.has_group('base.group_user')
|
||||
session_info = {
|
||||
"uid": session_uid,
|
||||
"is_system": user._is_system() if session_uid else False,
|
||||
"is_admin": user._is_admin() if session_uid else False,
|
||||
"is_public": user._is_public(),
|
||||
"is_internal_user": is_internal_user,
|
||||
"user_context": user_context,
|
||||
"db": self.env.cr.dbname,
|
||||
"user_settings": self.env['res.users.settings']._find_or_create_for_user(user)._res_users_settings_format(),
|
||||
"server_version": version_info.get('server_version'),
|
||||
"server_version_info": version_info.get('server_version_info'),
|
||||
"support_url": "https://www.odoo.com/buy",
|
||||
"name": user.name,
|
||||
"username": user.login,
|
||||
"partner_display_name": user.partner_id.display_name,
|
||||
"company_id": user.company_id.id if session_uid else None, # YTI TODO: Remove this from the user context
|
||||
"partner_id": user.partner_id.id if session_uid and user.partner_id else None,
|
||||
"web.base.url": IrConfigSudo.get_param('web.base.url', default=''),
|
||||
"active_ids_limit": int(IrConfigSudo.get_param('web.active_ids_limit', default='20000')),
|
||||
|
|
@ -117,7 +122,7 @@ class Http(models.AbstractModel):
|
|||
}
|
||||
if request.session.debug:
|
||||
session_info['bundle_params']['debug'] = request.session.debug
|
||||
if self.env.user.has_group('base.group_user'):
|
||||
if is_internal_user:
|
||||
# the following is only useful in the context of a webclient bootstrapping
|
||||
# but is still included in some other calls (e.g. '/web/session/authenticate')
|
||||
# to avoid access errors and unnecessary information, it is only included for users
|
||||
|
|
@ -128,6 +133,9 @@ class Http(models.AbstractModel):
|
|||
session_info['cache_hashes'].update({
|
||||
"load_menus": hashlib.sha512(menu_json_utf8).hexdigest()[:64], # sha512/256
|
||||
})
|
||||
# We need sudo since a user may not have access to ancestor companies
|
||||
disallowed_ancestor_companies_sudo = user.company_ids.sudo().parent_ids - user.company_ids
|
||||
all_companies_in_hierarchy_sudo = disallowed_ancestor_companies_sudo + user.company_ids
|
||||
session_info.update({
|
||||
# current_company should be default_company
|
||||
"user_companies": {
|
||||
|
|
@ -137,8 +145,19 @@ class Http(models.AbstractModel):
|
|||
'id': comp.id,
|
||||
'name': comp.name,
|
||||
'sequence': comp.sequence,
|
||||
'child_ids': (comp.child_ids & all_companies_in_hierarchy_sudo).ids,
|
||||
'parent_id': comp.parent_id.id,
|
||||
} for comp in user.company_ids
|
||||
},
|
||||
'disallowed_ancestor_companies': {
|
||||
comp.id: {
|
||||
'id': comp.id,
|
||||
'name': comp.name,
|
||||
'sequence': comp.sequence,
|
||||
'child_ids': (comp.child_ids & all_companies_in_hierarchy_sudo).ids,
|
||||
'parent_id': comp.parent_id.id,
|
||||
} for comp in disallowed_ancestor_companies_sudo
|
||||
},
|
||||
},
|
||||
"show_effect": True,
|
||||
"display_switch_company_menu": user.has_group('base.group_multi_company') and len(user.company_ids) > 1,
|
||||
|
|
@ -152,6 +171,7 @@ class Http(models.AbstractModel):
|
|||
session_info = {
|
||||
'is_admin': user._is_admin() if session_uid else False,
|
||||
'is_system': user._is_system() if session_uid else False,
|
||||
'is_public': user._is_public(),
|
||||
'is_website_user': user._is_public() if session_uid else False,
|
||||
'user_id': user.id if session_uid else False,
|
||||
'is_frontend': True,
|
||||
|
|
@ -159,6 +179,7 @@ class Http(models.AbstractModel):
|
|||
'profile_collectors': request.session.profile_collectors,
|
||||
'profile_params': request.session.profile_params,
|
||||
'show_effect': bool(request.env['ir.config_parameter'].sudo().get_param('base_setup.show_effect')),
|
||||
'currencies': self.get_currencies(),
|
||||
'bundle_params': {
|
||||
'lang': request.session.context['lang'],
|
||||
},
|
||||
|
|
@ -173,7 +194,11 @@ class Http(models.AbstractModel):
|
|||
})
|
||||
return session_info
|
||||
|
||||
@ormcache()
|
||||
def get_currencies(self):
|
||||
Currency = self.env['res.currency']
|
||||
currencies = Currency.search([]).read(['symbol', 'position', 'decimal_places'])
|
||||
return {c['id']: {'symbol': c['symbol'], 'position': c['position'], 'digits': [69,c['decimal_places']]} for c in currencies}
|
||||
currencies = Currency.search_fetch([], ['symbol', 'position', 'decimal_places'])
|
||||
return {
|
||||
c.id: {'symbol': c.symbol, 'position': c.position, 'digits': [69, c.decimal_places]}
|
||||
for c in currencies
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class IrModel(models.Model):
|
|||
accessible_models = []
|
||||
not_accessible_models = []
|
||||
for model in models:
|
||||
if self._check_model_access(model):
|
||||
if self._is_valid_for_model_selector(model):
|
||||
accessible_models.append(model)
|
||||
else:
|
||||
not_accessible_models.append({"display_name": model, "model": model})
|
||||
|
|
@ -34,9 +34,15 @@ class IrModel(models.Model):
|
|||
} for model in records]
|
||||
|
||||
@api.model
|
||||
def _check_model_access(self, model):
|
||||
return (self.env.user._is_internal() and model in self.env
|
||||
and self.env[model].check_access_rights("read", raise_exception=False))
|
||||
def _is_valid_for_model_selector(self, model):
|
||||
model = self.env.get(model)
|
||||
return (
|
||||
self.env.user._is_internal()
|
||||
and model is not None
|
||||
and model.check_access_rights("read", raise_exception=False)
|
||||
and not model._transient
|
||||
and not model._abstract
|
||||
)
|
||||
|
||||
@api.model
|
||||
def get_available_models(self):
|
||||
|
|
@ -44,5 +50,5 @@ class IrModel(models.Model):
|
|||
Return the list of models the current user has access to, with their
|
||||
corresponding display name.
|
||||
"""
|
||||
accessible_models = [model for model in self.pool.keys() if self._check_model_access(model)]
|
||||
accessible_models = [model for model in self.pool if self._is_valid_for_model_selector(model)]
|
||||
return self._display_name_for(accessible_models)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from collections import OrderedDict
|
|||
from werkzeug.urls import url_quote
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, models, fields
|
||||
from odoo.tools import pycompat
|
||||
from odoo.tools import html_escape as escape
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ class Image(models.AbstractModel):
|
|||
if max_width or max_height:
|
||||
max_size = '%sx%s' % (max_width, max_height)
|
||||
|
||||
sha = hashlib.sha512(str(getattr(record, '__last_update')).encode('utf-8')).hexdigest()[:7]
|
||||
sha = hashlib.sha512(str(getattr(record, 'write_date', fields.Datetime.now())).encode('utf-8')).hexdigest()[:7]
|
||||
max_size = '' if max_size is None else '/%s' % max_size
|
||||
|
||||
if options.get('filename-field') and options['filename-field'] in record and record[options['filename-field']]:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class IrUiMenu(models.Model):
|
|||
"actionModel": False,
|
||||
"webIcon": None,
|
||||
"webIconData": None,
|
||||
"webIconDataMimetype": None,
|
||||
"backgroundImage": menu.get('backgroundImage'),
|
||||
}
|
||||
else:
|
||||
|
|
@ -57,6 +58,7 @@ class IrUiMenu(models.Model):
|
|||
"actionModel": action_model,
|
||||
"webIcon": menu['web_icon'],
|
||||
"webIconData": menu['web_icon_data'],
|
||||
"webIconDataMimetype": menu['web_icon_data_mimetype'],
|
||||
}
|
||||
|
||||
return web_menus
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from typing import Dict, List
|
||||
|
||||
import babel.dates
|
||||
import pytz
|
||||
from lxml import etree
|
||||
import base64
|
||||
import copy
|
||||
import itertools
|
||||
import json
|
||||
import pytz
|
||||
|
||||
from odoo import _, _lt, api, fields, models
|
||||
from odoo.fields import Command
|
||||
from odoo.models import BaseModel, NewId
|
||||
from odoo.osv.expression import AND, TRUE_DOMAIN, normalize_domain
|
||||
from odoo.tools import date_utils, lazy, OrderedSet
|
||||
from odoo.tools.misc import get_lang
|
||||
from odoo.tools import date_utils, unique
|
||||
from odoo.tools.misc import OrderedSet, get_lang
|
||||
from odoo.exceptions import UserError
|
||||
from collections import defaultdict
|
||||
|
||||
|
|
@ -33,37 +38,20 @@ DISPLAY_DATE_FORMATS = {
|
|||
}
|
||||
|
||||
|
||||
class IrActionsActWindowView(models.Model):
|
||||
_inherit = 'ir.actions.act_window.view'
|
||||
|
||||
view_mode = fields.Selection(selection_add=[
|
||||
('qweb', 'QWeb')
|
||||
], ondelete={'qweb': 'cascade'})
|
||||
|
||||
|
||||
class Base(models.AbstractModel):
|
||||
_inherit = 'base'
|
||||
|
||||
@api.model
|
||||
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, count_limit=None):
|
||||
"""
|
||||
Performs a search_read and a search_count.
|
||||
def web_search_read(self, domain, specification, offset=0, limit=None, order=None, count_limit=None):
|
||||
records = self.search_fetch(domain, specification.keys(), offset=offset, limit=limit, order=order)
|
||||
values_records = records.web_read(specification)
|
||||
return self._format_web_search_read_results(domain, values_records, offset, limit, count_limit)
|
||||
|
||||
:param domain: search domain
|
||||
:param fields: list of fields to read
|
||||
:param limit: maximum number of records to read
|
||||
:param offset: number of records to skip
|
||||
:param order: columns to sort results
|
||||
:return: {
|
||||
'records': array of read records (result of a call to 'search_read')
|
||||
'length': number of records matching the domain (result of a call to 'search_count')
|
||||
}
|
||||
"""
|
||||
records = self.search_read(domain, fields, offset=offset, limit=limit, order=order)
|
||||
def _format_web_search_read_results(self, domain, records, offset=0, limit=None, count_limit=None):
|
||||
if not records:
|
||||
return {
|
||||
'length': 0,
|
||||
'records': []
|
||||
'records': [],
|
||||
}
|
||||
current_length = len(records) + offset
|
||||
limit_reached = len(records) == limit
|
||||
|
|
@ -75,15 +63,166 @@ class Base(models.AbstractModel):
|
|||
length = current_length
|
||||
return {
|
||||
'length': length,
|
||||
'records': records
|
||||
'records': records,
|
||||
}
|
||||
|
||||
def web_save(self, vals, specification: Dict[str, Dict], next_id=None) -> List[Dict]:
|
||||
if self:
|
||||
self.write(vals)
|
||||
else:
|
||||
self = self.create(vals)
|
||||
if next_id:
|
||||
self = self.browse(next_id)
|
||||
return self.with_context(bin_size=True).web_read(specification)
|
||||
|
||||
def web_read(self, specification: Dict[str, Dict]) -> List[Dict]:
|
||||
fields_to_read = list(specification) or ['id']
|
||||
|
||||
if fields_to_read == ['id']:
|
||||
# if we request to read only the ids, we have them already so we can build the return dictionaries immediately
|
||||
# this also avoid a call to read on the co-model that might have different access rules
|
||||
values_list = [{'id': id_} for id_ in self._ids]
|
||||
else:
|
||||
values_list: List[Dict] = self.read(fields_to_read, load=None)
|
||||
|
||||
if not values_list:
|
||||
return values_list
|
||||
|
||||
def cleanup(vals: Dict) -> Dict:
|
||||
""" Fixup vals['id'] of a new record. """
|
||||
if not vals['id']:
|
||||
vals['id'] = vals['id'].origin or False
|
||||
return vals
|
||||
|
||||
for field_name, field_spec in specification.items():
|
||||
field = self._fields.get(field_name)
|
||||
if field is None:
|
||||
continue
|
||||
|
||||
if field.type == 'many2one':
|
||||
if 'fields' not in field_spec:
|
||||
for values in values_list:
|
||||
if isinstance(values[field_name], NewId):
|
||||
values[field_name] = values[field_name].origin
|
||||
continue
|
||||
|
||||
co_records = self[field_name]
|
||||
if 'context' in field_spec:
|
||||
co_records = co_records.with_context(**field_spec['context'])
|
||||
|
||||
extra_fields = dict(field_spec['fields'])
|
||||
extra_fields.pop('display_name', None)
|
||||
|
||||
many2one_data = {
|
||||
vals['id']: cleanup(vals)
|
||||
for vals in co_records.web_read(extra_fields)
|
||||
}
|
||||
|
||||
if 'display_name' in field_spec['fields']:
|
||||
for rec in co_records.sudo():
|
||||
many2one_data[rec.id]['display_name'] = rec.display_name
|
||||
|
||||
for values in values_list:
|
||||
if values[field_name] is False:
|
||||
continue
|
||||
vals = many2one_data[values[field_name]]
|
||||
values[field_name] = vals['id'] and vals
|
||||
|
||||
elif field.type in ('one2many', 'many2many'):
|
||||
if not field_spec:
|
||||
continue
|
||||
|
||||
co_records = self[field_name]
|
||||
|
||||
if 'order' in field_spec and field_spec['order']:
|
||||
co_records = co_records.with_context(active_test=False).search(
|
||||
[('id', 'in', co_records.ids)], order=field_spec['order'],
|
||||
).with_context(co_records.env.context) # Reapply previous context
|
||||
order_key = {
|
||||
co_record.id: index
|
||||
for index, co_record in enumerate(co_records)
|
||||
}
|
||||
for values in values_list:
|
||||
# filter out inaccessible corecords in case of "cache pollution"
|
||||
values[field_name] = [id_ for id_ in values[field_name] if id_ in order_key]
|
||||
values[field_name] = sorted(values[field_name], key=order_key.__getitem__)
|
||||
|
||||
if 'context' in field_spec:
|
||||
co_records = co_records.with_context(**field_spec['context'])
|
||||
|
||||
if 'fields' in field_spec:
|
||||
if field_spec.get('limit') is not None:
|
||||
limit = field_spec['limit']
|
||||
ids_to_read = OrderedSet(
|
||||
id_
|
||||
for values in values_list
|
||||
for id_ in values[field_name][:limit]
|
||||
)
|
||||
co_records = co_records.browse(ids_to_read)
|
||||
|
||||
x2many_data = {
|
||||
vals['id']: vals
|
||||
for vals in co_records.web_read(field_spec['fields'])
|
||||
}
|
||||
|
||||
for values in values_list:
|
||||
values[field_name] = [x2many_data.get(id_) or {'id': id_} for id_ in values[field_name]]
|
||||
|
||||
elif field.type in ('reference', 'many2one_reference'):
|
||||
if not field_spec:
|
||||
continue
|
||||
|
||||
values_by_id = {
|
||||
vals['id']: vals
|
||||
for vals in values_list
|
||||
}
|
||||
for record in self:
|
||||
if not record[field_name]:
|
||||
continue
|
||||
|
||||
if field.type == 'reference':
|
||||
co_record = record[field_name]
|
||||
else: # field.type == 'many2one_reference'
|
||||
co_record = self.env[record[field.model_field]].browse(record[field_name])
|
||||
|
||||
if 'context' in field_spec:
|
||||
co_record = co_record.with_context(**field_spec['context'])
|
||||
|
||||
if 'fields' in field_spec:
|
||||
reference_read = co_record.web_read(field_spec['fields'])
|
||||
if any(fname != 'id' for fname in field_spec['fields']):
|
||||
# we can infer that if we can read fields for the co-record, it exists
|
||||
co_record_exists = bool(reference_read)
|
||||
else:
|
||||
co_record_exists = co_record.exists()
|
||||
else:
|
||||
# If there are no fields to read (field_spec.get('fields') --> None) and we web_read ids, it will
|
||||
# not actually read the records so we do not know if they exist.
|
||||
# This ensures the record actually exists
|
||||
co_record_exists = co_record.exists()
|
||||
|
||||
record_values = values_by_id[record.id]
|
||||
|
||||
if not co_record_exists:
|
||||
record_values[field_name] = False
|
||||
if field.type == 'many2one_reference':
|
||||
record_values[field.model_field] = False
|
||||
continue
|
||||
|
||||
if 'fields' in field_spec:
|
||||
record_values[field_name] = reference_read[0]
|
||||
if field.type == 'reference':
|
||||
record_values[field_name]['id'] = {
|
||||
'id': co_record.id,
|
||||
'model': co_record._name
|
||||
}
|
||||
|
||||
return values_list
|
||||
|
||||
@api.model
|
||||
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
|
||||
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
|
||||
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False, lazy=True):
|
||||
"""
|
||||
Returns the result of a read_group (and optionally search for and read records inside each
|
||||
group), and the total number of groups matching the search domain.
|
||||
Returns the result of a read_group and the total number of groups matching the search domain.
|
||||
|
||||
:param domain: search domain
|
||||
:param fields: list of fields to read (see ``fields``` param of ``read_group``)
|
||||
|
|
@ -92,29 +231,23 @@ class Base(models.AbstractModel):
|
|||
:param offset: see ``offset`` param of ``read_group``
|
||||
:param orderby: see ``orderby`` param of ``read_group``
|
||||
:param lazy: see ``lazy`` param of ``read_group``
|
||||
:param expand: if true, and groupby only contains one field, read records inside each group
|
||||
:param expand_limit: maximum number of records to read in each group
|
||||
:param expand_orderby: order to apply when reading records in each group
|
||||
:return: {
|
||||
'groups': array of read groups
|
||||
'length': total number of groups
|
||||
}
|
||||
"""
|
||||
groups = self._web_read_group(domain, fields, groupby, limit, offset, orderby, lazy, expand,
|
||||
expand_limit, expand_orderby)
|
||||
groups = self._web_read_group(domain, fields, groupby, limit, offset, orderby, lazy)
|
||||
|
||||
if not groups:
|
||||
length = 0
|
||||
elif limit and len(groups) == limit:
|
||||
# We need to fetch all groups to know the total number
|
||||
# this cannot be done all at once to avoid MemoryError
|
||||
length = limit
|
||||
chunk_size = 100000
|
||||
while True:
|
||||
more = len(self.read_group(domain, ['display_name'], groupby, offset=length, limit=chunk_size, lazy=True))
|
||||
length += more
|
||||
if more < chunk_size:
|
||||
break
|
||||
annoted_groupby = self._read_group_get_annoted_groupby(groupby, lazy=lazy)
|
||||
length = limit + len(self._read_group(
|
||||
domain,
|
||||
groupby=annoted_groupby.values(),
|
||||
offset=limit,
|
||||
))
|
||||
|
||||
else:
|
||||
length = len(groups) + offset
|
||||
return {
|
||||
|
|
@ -123,23 +256,14 @@ class Base(models.AbstractModel):
|
|||
}
|
||||
|
||||
@api.model
|
||||
def _web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
|
||||
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
|
||||
def _web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False, lazy=True):
|
||||
"""
|
||||
Performs a read_group and optionally a web_search_read for each group.
|
||||
See ``web_read_group`` for params description.
|
||||
|
||||
:returns: array of groups
|
||||
"""
|
||||
groups = self.read_group(domain, fields, groupby, offset=offset, limit=limit,
|
||||
orderby=orderby, lazy=lazy)
|
||||
|
||||
if expand and len(groupby) == 1:
|
||||
for group in groups:
|
||||
group['__data'] = self.web_search_read(domain=group['__domain'], fields=fields,
|
||||
offset=0, limit=expand_limit,
|
||||
order=expand_orderby)
|
||||
|
||||
return groups
|
||||
|
||||
@api.model
|
||||
|
|
@ -156,8 +280,9 @@ class Base(models.AbstractModel):
|
|||
:return a dictionnary mapping group_by values to dictionnaries mapping
|
||||
progress bar field values to the related number of records
|
||||
"""
|
||||
group_by_fname = group_by.partition(':')[0]
|
||||
field_type = self._fields[group_by_fname].type
|
||||
group_by_fullname = group_by.partition(':')[0]
|
||||
group_by_fieldname = group_by_fullname.split(".")[0] # split on "." in case we group on a property
|
||||
field_type = self._fields[group_by_fieldname].type
|
||||
if field_type == 'selection':
|
||||
selection_labels = dict(self.fields_get()[group_by]['selection'])
|
||||
|
||||
|
|
@ -185,26 +310,40 @@ class Base(models.AbstractModel):
|
|||
try:
|
||||
fname = progress_bar['field']
|
||||
return self.read_group(domain, [fname], [group_by, fname], lazy=False)
|
||||
except UserError:
|
||||
except ValueError:
|
||||
# possibly failed because of grouping on or aggregating non-stored
|
||||
# field; fallback on alternative implementation
|
||||
pass
|
||||
|
||||
# Workaround to match read_group's infrastructure
|
||||
# TO DO in master: harmonize this function and readgroup to allow factorization
|
||||
group_by_name = group_by.partition(':')[0]
|
||||
group_by_fullname = group_by.partition(':')[0]
|
||||
group_by_fieldname = group_by_fullname.split(".")[0] # split on "." in case we group on a property
|
||||
group_by_modifier = group_by.partition(':')[2] or 'month'
|
||||
|
||||
records_values = self.search_read(domain or [], [progress_bar['field'], group_by_name])
|
||||
field_type = self._fields[group_by_name].type
|
||||
records_values = self.search_read(domain or [], [progress_bar['field'], group_by_fieldname])
|
||||
field_type = self._fields[group_by_fieldname].type
|
||||
|
||||
for record_values in records_values:
|
||||
group_by_value = record_values.pop(group_by_name)
|
||||
group_by_value = record_values.pop(group_by_fieldname)
|
||||
property_name = group_by_fullname.partition('.')[2]
|
||||
if field_type == "properties" and group_by_value:
|
||||
group_by_value = next(
|
||||
(definition['value'] for definition in group_by_value
|
||||
if definition['name'] == property_name),
|
||||
False,
|
||||
)
|
||||
|
||||
# Again, imitating what _read_group_format_result and _read_group_prepare_data do
|
||||
if group_by_value and field_type in ['date', 'datetime']:
|
||||
locale = get_lang(self.env).code
|
||||
group_by_value = date_utils.start_of(fields.Datetime.to_datetime(group_by_value), group_by_modifier)
|
||||
group_by_value = fields.Datetime.to_datetime(group_by_value)
|
||||
if group_by_modifier != 'week':
|
||||
# start_of(v, 'week') does not take into account the locale
|
||||
# to determine the first day of the week; this part is not
|
||||
# necessary, since the formatting below handles the locale
|
||||
# as expected, and outputs correct results
|
||||
group_by_value = date_utils.start_of(group_by_value, group_by_modifier)
|
||||
group_by_value = pytz.timezone('UTC').localize(group_by_value)
|
||||
tz_info = None
|
||||
if field_type == 'datetime' and self._context.get('tz') in pytz.all_timezones:
|
||||
|
|
@ -225,32 +364,6 @@ class Base(models.AbstractModel):
|
|||
|
||||
return records_values
|
||||
|
||||
##### qweb view hooks #####
|
||||
@api.model
|
||||
def qweb_render_view(self, view_id, domain):
|
||||
assert view_id
|
||||
return self.env['ir.qweb']._render(
|
||||
view_id,
|
||||
{
|
||||
'model': self,
|
||||
'domain': domain,
|
||||
# not necessarily necessary as env is already part of the
|
||||
# non-minimal qcontext
|
||||
'context': self.env.context,
|
||||
'records': lazy(self.search, domain),
|
||||
})
|
||||
|
||||
@api.model
|
||||
def _get_view(self, view_id=None, view_type='form', **options):
|
||||
arch, view = super()._get_view(view_id, view_type, **options)
|
||||
# avoid leaking the raw (un-rendered) template, also avoids bloating
|
||||
# the response payload for no reason. Only send the root node,
|
||||
# to send attributes such as `js_class`.
|
||||
if view_type == 'qweb':
|
||||
root = arch
|
||||
arch = etree.Element('qweb', root.attrib)
|
||||
return arch, view
|
||||
|
||||
@api.model
|
||||
def _search_panel_field_image(self, field_name, **kwargs):
|
||||
"""
|
||||
|
|
@ -779,6 +892,232 @@ class Base(models.AbstractModel):
|
|||
|
||||
return { 'values': field_range, }
|
||||
|
||||
def onchange(self, values: Dict, field_names: List[str], fields_spec: Dict):
|
||||
"""
|
||||
Perform an onchange on the given fields, and return the result.
|
||||
|
||||
:param values: dictionary mapping field names to values on the form view,
|
||||
giving the current state of modification
|
||||
:param field_names: names of the modified fields
|
||||
:param fields_spec: dictionary specifying the fields in the view,
|
||||
just like the one used by :meth:`web_read`; it is used to format
|
||||
the resulting values
|
||||
|
||||
When creating a record from scratch, the client should call this with an
|
||||
empty list as ``field_names``. In that case, the method first adds
|
||||
default values to ``values``, computes the remaining fields, applies
|
||||
onchange methods to them, and return all the fields in ``fields_spec``.
|
||||
|
||||
The result is a dictionary with two optional keys. The key ``"value"``
|
||||
is used to return field values that should be modified on the caller.
|
||||
The corresponding value is a dict mapping field names to their value,
|
||||
in the format of :meth:`web_read`, except for x2many fields, where the
|
||||
value is a list of commands to be applied on the caller's field value.
|
||||
|
||||
The key ``"warning"`` provides a warning message to the caller. The
|
||||
corresponding value is a dictionary like::
|
||||
|
||||
{
|
||||
"title": "Be careful!", # subject of message
|
||||
"message": "Blah blah blah.", # full warning message
|
||||
"type": "dialog", # how to display the warning
|
||||
}
|
||||
|
||||
"""
|
||||
# this is for tests using `Form`
|
||||
self.env.flush_all()
|
||||
|
||||
env = self.env
|
||||
cache = env.cache
|
||||
first_call = not field_names
|
||||
|
||||
if any(fname not in self._fields for fname in field_names):
|
||||
return {}
|
||||
|
||||
if first_call:
|
||||
field_names = [fname for fname in values if fname != 'id']
|
||||
missing_names = [fname for fname in fields_spec if fname not in values]
|
||||
defaults = self.default_get(missing_names)
|
||||
for field_name in missing_names:
|
||||
values[field_name] = defaults.get(field_name, False)
|
||||
if field_name in defaults:
|
||||
field_names.append(field_name)
|
||||
|
||||
# prefetch x2many lines: this speeds up the initial snapshot by avoiding
|
||||
# computing fields on new records as much as possible, as that can be
|
||||
# costly and is not necessary at all
|
||||
self.fetch(fields_spec.keys())
|
||||
for field_name, field_spec in fields_spec.items():
|
||||
field = self._fields[field_name]
|
||||
if field.type not in ('one2many', 'many2many'):
|
||||
continue
|
||||
sub_fields_spec = field_spec.get('fields') or {}
|
||||
if sub_fields_spec and values.get(field_name):
|
||||
# retrieve all line ids in commands
|
||||
line_ids = OrderedSet(self[field_name].ids)
|
||||
for cmd in values[field_name]:
|
||||
if cmd[0] in (Command.UPDATE, Command.LINK):
|
||||
line_ids.add(cmd[1])
|
||||
elif cmd[0] == Command.SET:
|
||||
line_ids.update(cmd[2])
|
||||
# prefetch stored fields on lines
|
||||
lines = self[field_name].browse(line_ids)
|
||||
lines.fetch(sub_fields_spec.keys())
|
||||
# copy the cache of lines to their corresponding new records;
|
||||
# this avoids computing computed stored fields on new_lines
|
||||
new_lines = lines.browse(map(NewId, line_ids))
|
||||
for field_name in sub_fields_spec:
|
||||
field = lines._fields[field_name]
|
||||
line_values = [
|
||||
field.convert_to_cache(line[field_name], new_line, validate=False)
|
||||
for new_line, line in zip(new_lines, lines)
|
||||
]
|
||||
cache.update(new_lines, field, line_values)
|
||||
|
||||
# Isolate changed values, to handle inconsistent data sent from the
|
||||
# client side: when a form view contains two one2many fields that
|
||||
# overlap, the lines that appear in both fields may be sent with
|
||||
# different data. Consider, for instance:
|
||||
#
|
||||
# foo_ids: [line with value=1, ...]
|
||||
# bar_ids: [line with value=1, ...]
|
||||
#
|
||||
# If value=2 is set on 'line' in 'bar_ids', the client sends
|
||||
#
|
||||
# foo_ids: [line with value=1, ...]
|
||||
# bar_ids: [line with value=2, ...]
|
||||
#
|
||||
# The idea is to put 'foo_ids' in cache first, so that the snapshot
|
||||
# contains value=1 for line in 'foo_ids'. The snapshot is then updated
|
||||
# with the value of `bar_ids`, which will contain value=2 on line.
|
||||
#
|
||||
# The issue also occurs with other fields. For instance, an onchange on
|
||||
# a move line has a value for the field 'move_id' that contains the
|
||||
# values of the move, among which the one2many that contains the line
|
||||
# itself, with old values!
|
||||
#
|
||||
initial_values = dict(values)
|
||||
changed_values = {fname: initial_values.pop(fname) for fname in field_names}
|
||||
|
||||
# do not force delegate fields to False
|
||||
for parent_name in self._inherits.values():
|
||||
if not initial_values.get(parent_name, True):
|
||||
initial_values.pop(parent_name)
|
||||
|
||||
# create a new record with initial values
|
||||
if self:
|
||||
# fill in the cache of record with the values of self
|
||||
cache_values = {fname: self[fname] for fname in fields_spec}
|
||||
record = self.new(cache_values, origin=self)
|
||||
# apply initial values on top of the values of self
|
||||
record._update_cache(initial_values)
|
||||
else:
|
||||
# set changed values to null in initial_values; not setting them
|
||||
# triggers default_get() on the new record when creating snapshot0
|
||||
initial_values.update(dict.fromkeys(field_names, False))
|
||||
record = self.new(initial_values, origin=self)
|
||||
|
||||
# make parent records match with the form values; this ensures that
|
||||
# computed fields on parent records have all their dependencies at
|
||||
# their expected value
|
||||
for field_name in initial_values:
|
||||
field = self._fields.get(field_name)
|
||||
if field and field.inherited:
|
||||
parent_name, field_name = field.related.split('.', 1)
|
||||
if parent := record[parent_name]:
|
||||
parent._update_cache({field_name: record[field_name]})
|
||||
|
||||
# make a snapshot based on the initial values of record
|
||||
snapshot0 = RecordSnapshot(record, fields_spec, fetch=(not first_call))
|
||||
|
||||
# store changed values in cache; also trigger recomputations based on
|
||||
# subfields (e.g., line.a has been modified, line.b is computed stored
|
||||
# and depends on line.a, but line.b is not in the form view)
|
||||
record._update_cache(changed_values)
|
||||
|
||||
# update snapshot0 with changed values
|
||||
for field_name in field_names:
|
||||
snapshot0.fetch(field_name)
|
||||
|
||||
# Determine which field(s) should be triggered an onchange. On the first
|
||||
# call, 'names' only contains fields with a default. If 'self' is a new
|
||||
# line in a one2many field, 'names' also contains the one2many's inverse
|
||||
# field, and that field may not be in nametree.
|
||||
todo = list(unique(itertools.chain(field_names, fields_spec))) if first_call else list(field_names)
|
||||
done = set()
|
||||
|
||||
# mark fields to do as modified to trigger recomputations
|
||||
protected = [self._fields[fname] for fname in field_names]
|
||||
with self.env.protecting(protected, record):
|
||||
record.modified(todo)
|
||||
for field_name in todo:
|
||||
field = self._fields[field_name]
|
||||
if field.inherited:
|
||||
# modifying an inherited field should modify the parent
|
||||
# record accordingly; because we don't actually assign the
|
||||
# modified field on the record, the modification on the
|
||||
# parent record has to be done explicitly
|
||||
parent = record[field.related.split('.')[0]]
|
||||
parent[field_name] = record[field_name]
|
||||
|
||||
result = {'warnings': OrderedSet()}
|
||||
|
||||
# process names in order
|
||||
while todo:
|
||||
# apply field-specific onchange methods
|
||||
for field_name in todo:
|
||||
record._apply_onchange_methods(field_name, result)
|
||||
done.add(field_name)
|
||||
|
||||
if not env.context.get('recursive_onchanges', True):
|
||||
break
|
||||
|
||||
# determine which fields to process for the next pass
|
||||
todo = [
|
||||
field_name
|
||||
for field_name in fields_spec
|
||||
if field_name not in done and snapshot0.has_changed(field_name)
|
||||
]
|
||||
|
||||
# make the snapshot with the final values of record
|
||||
snapshot1 = RecordSnapshot(record, fields_spec)
|
||||
|
||||
# determine values that have changed by comparing snapshots
|
||||
result['value'] = snapshot1.diff(snapshot0, force=first_call)
|
||||
|
||||
# format warnings
|
||||
warnings = result.pop('warnings')
|
||||
if len(warnings) == 1:
|
||||
title, message, type_ = warnings.pop()
|
||||
if not type_:
|
||||
type_ = 'dialog'
|
||||
result['warning'] = dict(title=title, message=message, type=type_)
|
||||
elif len(warnings) > 1:
|
||||
# concatenate warning titles and messages
|
||||
title = _("Warnings")
|
||||
message = '\n\n'.join([warn_title + '\n\n' + warn_message for warn_title, warn_message, warn_type in warnings])
|
||||
result['warning'] = dict(title=title, message=message, type='dialog')
|
||||
|
||||
return result
|
||||
|
||||
def web_override_translations(self, values):
|
||||
"""
|
||||
This method is used to override all the modal translations of the given fields
|
||||
with the provided value for each field.
|
||||
|
||||
:param values: dictionary of the translations to apply for each field name
|
||||
ex: { "field_name": "new_value" }
|
||||
"""
|
||||
self.ensure_one()
|
||||
for field_name in values:
|
||||
field = self._fields[field_name]
|
||||
if field.translate is True:
|
||||
translations = {lang: False for lang, _ in self.env['res.lang'].get_installed()}
|
||||
translations['en_US'] = values[field_name]
|
||||
translations[self.env.lang or 'en_US'] = values[field_name]
|
||||
self.update_field_translations(field_name, translations)
|
||||
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
|
@ -815,3 +1154,114 @@ class ResCompany(models.Model):
|
|||
b64_val = self._get_asset_style_b64()
|
||||
if b64_val != asset_attachment.datas:
|
||||
asset_attachment.write({'datas': b64_val})
|
||||
|
||||
|
||||
class RecordSnapshot(dict):
|
||||
""" A dict with the values of a record, following a prefix tree. """
|
||||
__slots__ = ['record', 'fields_spec']
|
||||
|
||||
def __init__(self, record: BaseModel, fields_spec: Dict, fetch=True):
|
||||
# put record in dict to include it when comparing snapshots
|
||||
super().__init__()
|
||||
self.record = record
|
||||
self.fields_spec = fields_spec
|
||||
if fetch:
|
||||
for name in fields_spec:
|
||||
self.fetch(name)
|
||||
|
||||
def __eq__(self, other: 'RecordSnapshot'):
|
||||
return self.record == other.record and super().__eq__(other)
|
||||
|
||||
def fetch(self, field_name):
|
||||
""" Set the value of field ``name`` from the record's value. """
|
||||
if self.record._fields[field_name].type in ('one2many', 'many2many'):
|
||||
# x2many fields are serialized as a dict of line snapshots
|
||||
lines = self.record[field_name]
|
||||
if 'context' in self.fields_spec[field_name]:
|
||||
lines = lines.with_context(**self.fields_spec[field_name]['context'])
|
||||
sub_fields_spec = self.fields_spec[field_name].get('fields') or {}
|
||||
self[field_name] = {line.id: RecordSnapshot(line, sub_fields_spec) for line in lines}
|
||||
else:
|
||||
self[field_name] = self.record[field_name]
|
||||
|
||||
def has_changed(self, field_name) -> bool:
|
||||
""" Return whether a field on the record has changed. """
|
||||
if field_name not in self:
|
||||
return True
|
||||
if self.record._fields[field_name].type not in ('one2many', 'many2many'):
|
||||
return self[field_name] != self.record[field_name]
|
||||
return self[field_name].keys() != set(self.record[field_name]._ids) or any(
|
||||
line_snapshot.has_changed(subname)
|
||||
for line_snapshot in self[field_name].values()
|
||||
for subname in self.fields_spec[field_name].get('fields') or {}
|
||||
)
|
||||
|
||||
def diff(self, other: 'RecordSnapshot', force=False):
|
||||
""" Return the values in ``self`` that differ from ``other``. """
|
||||
|
||||
# determine fields to return
|
||||
simple_fields_spec = {}
|
||||
x2many_fields_spec = {}
|
||||
for field_name, field_spec in self.fields_spec.items():
|
||||
if field_name == 'id':
|
||||
continue
|
||||
if not force and other.get(field_name) == self[field_name]:
|
||||
continue
|
||||
field = self.record._fields[field_name]
|
||||
if field.type in ('one2many', 'many2many'):
|
||||
x2many_fields_spec[field_name] = field_spec
|
||||
else:
|
||||
simple_fields_spec[field_name] = field_spec
|
||||
|
||||
# use web_read() for simple fields
|
||||
[result] = self.record.web_read(simple_fields_spec)
|
||||
|
||||
# discard the NewId from the dict
|
||||
result.pop('id')
|
||||
|
||||
# for x2many fields: serialize value as commands
|
||||
for field_name, field_spec in x2many_fields_spec.items():
|
||||
commands = []
|
||||
|
||||
self_value = self[field_name]
|
||||
other_value = {} if force else other.get(field_name) or {}
|
||||
if any(other_value):
|
||||
# other may be a snapshot for a real record, adapt its x2many ids
|
||||
other_value = {NewId(id_): snap for id_, snap in other_value.items()}
|
||||
|
||||
# commands for removed lines
|
||||
field = self.record._fields[field_name]
|
||||
remove = Command.delete if field.type == 'one2many' else Command.unlink
|
||||
for id_ in other_value:
|
||||
if id_ not in self_value:
|
||||
commands.append(remove(id_.origin or id_.ref or 0))
|
||||
|
||||
# commands for modified or extra lines
|
||||
for id_, line_snapshot in self_value.items():
|
||||
if not force and id_ in other_value:
|
||||
# existing line: check diff and send update
|
||||
line_diff = line_snapshot.diff(other_value[id_])
|
||||
if line_diff:
|
||||
commands.append(Command.update(id_.origin or id_.ref or 0, line_diff))
|
||||
|
||||
elif not id_.origin:
|
||||
# new line: send diff from scratch
|
||||
line_diff = line_snapshot.diff({})
|
||||
commands.append((Command.CREATE, id_.origin or id_.ref or 0, line_diff))
|
||||
|
||||
else:
|
||||
# link line: send data to client
|
||||
base_line = line_snapshot.record._origin
|
||||
[base_data] = base_line.web_read(field_spec.get('fields') or {})
|
||||
commands.append((Command.LINK, base_line.id, base_data))
|
||||
|
||||
# check diff and send update
|
||||
base_snapshot = RecordSnapshot(base_line, field_spec.get('fields') or {})
|
||||
line_diff = line_snapshot.diff(base_snapshot)
|
||||
if line_diff:
|
||||
commands.append(Command.update(id_.origin, line_diff))
|
||||
|
||||
if commands:
|
||||
result[field_name] = commands
|
||||
|
||||
return result
|
||||
|
|
|
|||
10
odoo-bringout-oca-ocb-web/web/models/res_config_settings.py
Normal file
10
odoo-bringout-oca-ocb-web/web/models/res_config_settings.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
web_app_name = fields.Char('Web App Name', config_parameter='web.web_app_name')
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue