From 15aae7ea340c48052aa1769d07ce0ef05d6375ab Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Thu, 10 Jun 2021 13:30:01 +0000 Subject: [PATCH] [IMP] website: organize links in menu & footer on configurator apply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The configurator takes care of the organization of the links to the different pages. Some links are put in the menu and some are put in the footer. The order is predefined. If too many links are present in the menu then a sub-menu 'Company' is created and some links are put in it. For the 'News' and 'Succes Stories' features a website specific blog is created. Links have the following order in the menu and are present only if their corresponding website.configurator.feature has been selected in the configurator excepted for the 'Home' and 'Contact us' links which are default links: - 'Home' - 'Shop' - 'Event' - 'Courses' - 'Services' - 'Pricing' - 'Company': if more than 8 links in menu and more than 1 item in this submenu otherwise the three following links are in the top menu. - 'News' - 'Success Stories' - 'About us' - 'Appointment' - 'Contact us' Links in footer: - 'Privacy Policy' - 'Help': if website_helpdesk installed. This is not a website.configurator.feature. - 'Forum' Community: https://github.com/odoo/odoo/pull/71993 Enterprise: https://github.com/odoo/enterprise/pull/18930 task-2518565 Co-authored-by: Sébastien Mottet (oms) Co-authored-by: Romain Derie Co-authored-by: Jeremy Kersten --- addons/test_website_modules/__init__.py | 2 + addons/website/data/website_data.xml | 21 +++ addons/website/models/website.py | 136 +++++++++++++----- .../models/website_configurator_feature.py | 6 +- addons/website/views/website_templates.xml | 21 +++ addons/website_blog/models/website.py | 21 +++ addons/website_forum/models/website.py | 11 ++ 7 files changed, 184 insertions(+), 34 deletions(-) create mode 100644 addons/test_website_modules/__init__.py diff --git a/addons/test_website_modules/__init__.py b/addons/test_website_modules/__init__.py new file mode 100644 index 00000000000..67dee8c60db --- /dev/null +++ b/addons/test_website_modules/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index b0ed82447ac..5f4b5b2ad44 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -394,6 +394,9 @@ 1 fa-building + True + 50 + /about-us Services @@ -402,6 +405,8 @@ 3 fa-handshake-o + 30 + /our-services Pricing @@ -410,6 +415,8 @@ 4 fa-random + 35 + /pricing Privacy Policy @@ -418,6 +425,7 @@ 5 fa-gavel + /privacy @@ -428,6 +436,9 @@ blog fa-rss + True + 40 + /blog Success Stories @@ -435,6 +446,9 @@ 7 fa-star + True + 45 + /blog Career @@ -450,6 +464,8 @@ online_store fa-shopping-cart + 15 + /shop Events @@ -458,6 +474,8 @@ event fa-ticket + 20 + /event Forum @@ -465,6 +483,7 @@ 11 fa-users + /forum Live Chat @@ -480,6 +499,8 @@ elearning fa-graduation-cap + 25 + /slides Stores Locator diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 6f9e26b7865..c2a9c1a15db 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -2,11 +2,12 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 -import inspect -import logging import hashlib -import requests +import inspect +import json +import logging import re +import requests from lxml import etree, html from werkzeug import urls @@ -290,6 +291,16 @@ class Website(models.Model): def get_cta_data(self, website_purpose, website_type): return {'cta_btn_text': False, 'cta_btn_href': '/contactus'} + def configurator_set_menu_links(self, menu_company, module_data): + menus = self.env['website.menu'].search([('url', 'in', list(module_data.keys())), ('website_id', '=', self.id)]) + for m in menus: + m.sequence = module_data[m.url]['sequence'] + + def configurator_get_footer_links(self): + return [ + {'text': _("Privacy Policy"), 'href': '/privacy'}, + ] + @api.model def configurator_init(self): r = dict() @@ -299,7 +310,7 @@ class Website(models.Model): 'id': feature.id, 'name': feature.name, 'description': feature.description, - 'type': feature.type, + 'type': 'page' if feature.page_view_id else 'app', 'icon': feature.icon, 'website_config_preselection': feature.website_config_preselection, 'module_state': feature.module_id.state, @@ -341,15 +352,44 @@ class Website(models.Model): self.env['web_editor.assets'].make_scss_customization(url, values) def set_features(selected_features): - feature_ids = self.env['website.configurator.feature'].browse(selected_features) + features = self.env['website.configurator.feature'].browse(selected_features) + + menu_company = self.env['website.menu'] + if len(features.filtered('menu_sequence')) > 5 and len(features.filtered('menu_company')) > 1: + menu_company = self.env['website.menu'].create({ + 'name': _('Company'), + 'parent_id': website.menu_id.id, + 'website_id': website.id, + 'sequence': 40, + }) + pages_views = {} modules = self.env['ir.module.module'] - for feature_id in feature_ids: - if feature_id.type == 'app' and feature_id.module_id and feature_id.module_id.state != 'installed': - modules += feature_id.module_id - if feature_id.type == 'page' and feature_id.page_view_id: - result = self.env['website'].new_page(name=feature_id.name, add_menu=True, template=feature_id.page_view_id.key) - pages_views[feature_id.iap_page_code] = result['view_id'] + module_data = {} + for feature in features: + add_menu = bool(feature.menu_sequence) + if feature.module_id: + if feature.module_id.state != 'installed': + modules += feature.module_id + if add_menu: + if feature.module_id.name != 'website_blog': + module_data[feature.feature_url] = {'sequence': feature.menu_sequence} + else: + blogs = module_data.setdefault('#blog', []) + blogs.append({'name': feature.name, 'sequence': feature.menu_sequence}) + elif feature.page_view_id: + result = self.env['website'].new_page( + name=feature.name, + add_menu=add_menu, + page_values=dict(url=feature.feature_url, is_published=True), + menu_values=add_menu and { + 'url': feature.feature_url, + 'sequence': feature.menu_sequence, + 'parent_id': feature.menu_company and menu_company.id or website.menu_id.id, + }, + template=feature.page_view_id.key + ) + pages_views[feature.iap_page_code] = result['view_id'] if modules: modules.button_immediate_install() @@ -358,6 +398,8 @@ class Website(models.Model): api.Environment.reset() self.env = api.Environment(modules._cr, modules._uid, modules._context) + self.env['website'].browse(website.id).configurator_set_menu_links(menu_company, module_data) + return pages_views def configure_page(page_code, snippet_list, pages_views, cta_data): @@ -439,21 +481,6 @@ class Website(models.Model): if palette: set_colors(palette) - # modules - pages_views = set_features(kwargs.get('selected_features')) - # We need to refresh the environment of website because set_features installed some new module - # and we need the overrides of these new menus e.g. for .get_cta_data() - website = self.env['website'].browse(website.id) - - # Load suggestion from iap for selected pages - requested_pages = list(pages_views.keys()) - requested_pages.append('homepage') - params = { - 'theme': kwargs.get('theme_name'), - 'pages': requested_pages, - } - custom_resources = self._website_api_rpc('/api/website/1/configurator/custom_resources/%s' % kwargs.get('industry_id'), params) - # Update CTA cta_data = website.get_cta_data(kwargs.get('website_purpose'), kwargs.get('website_type')) if cta_data['cta_btn_text']: @@ -497,6 +524,43 @@ class Website(models.Model): except ValueError as e: logger.warning(e) + # modules + pages_views = set_features(kwargs.get('selected_features')) + # We need to refresh the environment of website because set_features installed some new module + # and we need the overrides of these new menus e.g. for .get_cta_data() + website = self.env['website'].browse(website.id) + + # Update footers links, needs to be done after `set_features` to go + # through module overide of `configurator_get_footer_links` + footer_links = website.configurator_get_footer_links() + footer_ids = [ + 'website.template_footer_contact', 'website.template_footer_headline', + 'website.footer_custom', 'website.template_footer_links', + 'website.template_footer_minimalist', + ] + for footer_id in footer_ids: + try: + view_id = self.env['website'].viewref(footer_id) + if view_id: + # Deliberately hardcode dynamic code inside the view arch, + # it will be transformed into static nodes after a save/edit + # thanks to the t-ignore in parents node. + arch_string = etree.fromstring(view_id.arch_db) + el = arch_string.xpath("//t[@t-set='configurator_footer_links']")[0] + el.attrib['t-value'] = json.dumps(footer_links) + view_id.with_context(website_id=website.id).write({'arch_db': etree.tostring(arch_string)}) + except Exception as e: + # The xml view could have been modified in the backend, we don't + # want the xpath error to break the configurator feature + logger.warning(e) + + # Load suggestion from iap for selected pages + requested_pages = list(pages_views.keys()) + ['homepage'] + custom_resources = self._website_api_rpc('/api/website/1/configurator/custom_resources/%s' % kwargs.get('industry_id'), { + 'theme': kwargs.get('theme_name'), + 'pages': requested_pages, + }) + # Update pages pages = custom_resources.get('pages', {}) for page_code, snippet_list in pages.items(): @@ -504,7 +568,6 @@ class Website(models.Model): images = custom_resources.get('images', []) set_images(images) - return url # ---------------------------------------------------------- @@ -562,11 +625,14 @@ class Website(models.Model): copy_menu(submenu, new_top_menu) @api.model - def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None): + def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None, page_values=None, menu_values=None): """ Create a new website page, and assign it a xmlid based on the given one :param name : the name of the page + :param add_menu : if True, add a menu for that page :param template : potential xml_id of the page to create :param namespace : module part of the xml_id if none, the template module name is used + :param page_values : default values for the page to be created + :param menu_values : default values for the menu to be created """ if namespace: template_module = namespace @@ -597,21 +663,27 @@ class Website(models.Model): website = self.get_current_website() if ispage: - page = self.env['website.page'].create({ + default_page_values = { 'url': page_url, 'website_id': website.id, # remove it if only one website or not? 'view_id': view.id, 'track': True, - }) + } + if page_values: + default_page_values.update(page_values) + page = self.env['website.page'].create(default_page_values) result['page_id'] = page.id if add_menu: - menu = self.env['website.menu'].create({ + default_menu_values = { 'name': name, 'url': page_url, 'parent_id': website.menu_id.id, 'page_id': page.id, 'website_id': website.id, - }) + } + if menu_values: + default_menu_values.update(menu_values) + menu = self.env['website.menu'].create(default_menu_values) result['menu_id'] = menu.id return result diff --git a/addons/website/models/website_configurator_feature.py b/addons/website/models/website_configurator_feature.py index aab1857caaa..5be5fd6e4be 100644 --- a/addons/website/models/website_configurator_feature.py +++ b/addons/website/models/website_configurator_feature.py @@ -17,11 +17,13 @@ class WebsiteConfiguratorFeature(models.Model): icon = fields.Char() iap_page_code = fields.Char(help='Page code used to tell IAP website_service for which page a snippet list should be generated') website_config_preselection = fields.Char(help='Comma-separated list of website type/purpose for which this feature should be pre-selected') - type = fields.Selection([('page', "Page"), ('app', "App")], compute='_compute_type') page_view_id = fields.Many2one('ir.ui.view', ondelete='cascade') module_id = fields.Many2one('ir.module.module', ondelete='cascade') + feature_url = fields.Char() + menu_sequence = fields.Integer(help='If set, a website menu will be created for the feature.') + menu_company = fields.Boolean(help='If set, add the menu as a second level menu, as a child of "Company" menu.') - @api.depends('module_id', 'page_view_id') + @api.depends('page_view_id') def _compute_type(self): for record in self: record.type = 'page' if record.page_view_id else 'app' diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index cb6fd83190e..0699d456b63 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -1361,6 +1361,10 @@
  • Products
  • Services
  • Legal
  • + +
  • + +
  • Contact us
  • @@ -1496,6 +1500,10 @@
  • Our Company
  • Case Studies
  • Blog
  • + +
  • + +
  • @@ -1708,6 +1725,10 @@
    diff --git a/addons/website_blog/models/website.py b/addons/website_blog/models/website.py index fb153e3d756..42bec17e38f 100644 --- a/addons/website_blog/models/website.py +++ b/addons/website_blog/models/website.py @@ -62,3 +62,24 @@ class Website(models.Model): suggested_controllers = super(Website, self).get_suggested_controllers() suggested_controllers.append((_('Blog'), url_for('/blog'), 'website_blog')) return suggested_controllers + + def configurator_set_menu_links(self, menu_company, module_data): + blogs = module_data.get('#blog', []) + for idx, blog in enumerate(blogs): + new_blog = self.env['blog.blog'].create({ + 'name': blog['name'], + 'website_id': self.id, + }) + blog_menu_values = { + 'name': blog['name'], + 'url': '/blog/%s' % new_blog.id, + 'sequence': blog['sequence'], + 'parent_id': menu_company.id if menu_company else self.menu_id.id, + 'website_id': self.id, + } + if idx == 0: + blog_menu = self.env['website.menu'].search([('url', '=', '/blog'), ('website_id', '=', self.id)]) + blog_menu.write(blog_menu_values) + else: + self.env['website.menu'].create(blog_menu_values) + super().configurator_set_menu_links(menu_company, module_data) diff --git a/addons/website_forum/models/website.py b/addons/website_forum/models/website.py index 0957acb9cc7..35256e4e9f1 100644 --- a/addons/website_forum/models/website.py +++ b/addons/website_forum/models/website.py @@ -18,3 +18,14 @@ class Website(models.Model): suggested_controllers = super(Website, self).get_suggested_controllers() suggested_controllers.append((_('Forum'), url_for('/forum'), 'website_forum')) return suggested_controllers + + def configurator_get_footer_links(self): + links = super().configurator_get_footer_links() + links.append({'text': _("Forum"), 'href': '/forum'}) + return links + + def configurator_set_menu_links(self, menu_company, module_data): + # Forum menu should only be a footer link, not a menu + forum_menu = self.env['website.menu'].search([('url', '=', '/forum'), ('website_id', '=', self.id)]) + forum_menu.unlink() + super().configurator_set_menu_links(menu_company, module_data)