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)