[IMP] website: organize links in menu & footer on configurator apply

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) <oms@odoo.com>
Co-authored-by: Romain Derie <rde@odoo.com>
Co-authored-by: Jeremy Kersten <jke@odoo.com>
This commit is contained in:
Romain Derie
2021-07-30 13:51:33 +00:00
committed by Jeremy Kersten
co-authored by Sébastien Mottet Jeremy Kersten
parent 59098dbc60
commit 15aae7ea34
7 changed files with 184 additions and 34 deletions
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+21
View File
@@ -394,6 +394,9 @@
<field name="sequence">1</field>
<field name="page_view_id" ref="aboutus"/>
<field name="icon">fa-building</field>
<field name="menu_company">True</field>
<field name="menu_sequence">50</field>
<field name="feature_url">/about-us</field>
</record>
<record id="feature_page_our_services" model="website.configurator.feature">
<field name="name">Services</field>
@@ -402,6 +405,8 @@
<field name="sequence">3</field>
<field name="page_view_id" ref="our_services"/>
<field name="icon">fa-handshake-o</field>
<field name="menu_sequence">30</field>
<field name="feature_url">/our-services</field>
</record>
<record id="feature_page_pricing" model="website.configurator.feature">
<field name="name">Pricing</field>
@@ -410,6 +415,8 @@
<field name="sequence">4</field>
<field name="page_view_id" ref="pricing"/>
<field name="icon">fa-random</field>
<field name="menu_sequence">35</field>
<field name="feature_url">/pricing</field>
</record>
<record id="feature_page_privacy_policy" model="website.configurator.feature">
<field name="name">Privacy Policy</field>
@@ -418,6 +425,7 @@
<field name="sequence">5</field>
<field name="page_view_id" ref="privacy_policy"/>
<field name="icon">fa-gavel</field>
<field name="feature_url">/privacy</field>
</record>
<!-- Configurator apps features -->
@@ -428,6 +436,9 @@
<field name="website_config_preselection">blog</field>
<field name="module_id" ref="base.module_website_blog"/>
<field name="icon">fa-rss</field>
<field name="menu_company">True</field>
<field name="menu_sequence">40</field>
<field name="feature_url">/blog</field>
</record>
<record id="feature_module_success_stories" model="website.configurator.feature">
<field name="name">Success Stories</field>
@@ -435,6 +446,9 @@
<field name="sequence">7</field>
<field name="module_id" ref="base.module_website_blog"/>
<field name="icon">fa-star</field>
<field name="menu_company">True</field>
<field name="menu_sequence">45</field>
<field name="feature_url">/blog</field>
</record>
<record id="feature_module_career" model="website.configurator.feature">
<field name="name">Career</field>
@@ -450,6 +464,8 @@
<field name="website_config_preselection">online_store</field>
<field name="module_id" ref="base.module_website_sale"/>
<field name="icon">fa-shopping-cart</field>
<field name="menu_sequence">15</field>
<field name="feature_url">/shop</field>
</record>
<record id="feature_module_event" model="website.configurator.feature">
<field name="name">Events</field>
@@ -458,6 +474,8 @@
<field name="website_config_preselection">event</field>
<field name="module_id" ref="base.module_website_event_sale"/>
<field name="icon">fa-ticket</field>
<field name="menu_sequence">20</field>
<field name="feature_url">/event</field>
</record>
<record id="feature_module_forum" model="website.configurator.feature">
<field name="name">Forum</field>
@@ -465,6 +483,7 @@
<field name="sequence">11</field>
<field name="module_id" ref="base.module_website_forum"/>
<field name="icon">fa-users</field>
<field name="feature_url">/forum</field>
</record>
<record id="feature_module_live_chat" model="website.configurator.feature">
<field name="name">Live Chat</field>
@@ -480,6 +499,8 @@
<field name="website_config_preselection">elearning</field>
<field name="module_id" ref="base.module_website_slides"/>
<field name="icon">fa-graduation-cap</field>
<field name="menu_sequence">25</field>
<field name="feature_url">/slides</field>
</record>
<record id="feature_module_stores_locator" model="website.configurator.feature">
<field name="name">Stores Locator</field>
+104 -32
View File
@@ -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
@@ -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'
@@ -1361,6 +1361,10 @@
<li><a href="#">Products</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Legal</a></li>
<t t-set="configurator_footer_links" t-value="[]"/>
<li t-foreach="configurator_footer_links" t-as="link">
<a t-att-href="link['href']" t-esc="link['text']"/>
</li>
<li><a href="/contactus">Contact us</a></li>
</ul>
</div>
@@ -1496,6 +1500,10 @@
<li class="list-item py-1"><a href="#">Our Company</a></li>
<li class="list-item py-1"><a href="#">Case Studies</a></li>
<li class="list-item py-1"><a href="#">Blog</a></li>
<t t-set="configurator_footer_links" t-value="[]"/>
<li t-foreach="configurator_footer_links" t-as="link" class="list-item py-1">
<a t-att-href="link['href']" t-esc="link['text']"/>
</li>
</ul>
</div>
<div class="col-lg-2 pb16">
@@ -1551,6 +1559,10 @@
<li class="list-inline-item"><a href="#">About us</a></li>
<li class="list-inline-item"><a href="#">Products</a></li>
<li class="list-inline-item"><a href="#">Services</a></li>
<t t-set="configurator_footer_links" t-value="[]"/>
<li t-foreach="configurator_footer_links" t-as="link" class="list-inline-item">
<a t-att-href="link['href']" t-esc="link['text']"/>
</li>
</ul>
</div>
<div class="col-lg-3 pt16 pb16">
@@ -1636,6 +1648,11 @@
<li class="list-inline-item"><a href="#">Products</a></li>
<li class="list-inline-item">•</li>
<li class="list-inline-item"><a href="#">Terms of Services</a></li>
<t t-set="configurator_footer_links" t-value="[]"/>
<t t-foreach="configurator_footer_links" t-as="link" class="list-inline-item">
<li class="list-inline-item">•</li>
<li class="list-inline-item"><a t-att-href="link['href']" t-esc="link['text']"/></li>
</t>
</ul>
</div>
</div>
@@ -1708,6 +1725,10 @@
<ul class="pl-3 mb-0">
<li><a href="/">Home</a></li>
<li><a href="/contactus">Contact us</a></li>
<t t-set="configurator_footer_links" t-value="[]"/>
<li t-foreach="configurator_footer_links" t-as="link">
<a t-att-href="link['href']" t-esc="link['text']"/>
</li>
</ul>
</div>
<div class="col-lg-6 pb24">
+21
View File
@@ -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)
+11
View File
@@ -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)