236 lines
10 KiB
Python
236 lines
10 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import logging
|
|
|
|
from lxml import html, etree
|
|
|
|
from odoo import models
|
|
from odoo.tools.translate import _
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WebsitePageGenerator(models.Model):
|
|
_name = 'website.page'
|
|
_inherit = 'website.page'
|
|
|
|
def _construct_homepage(self, homepage_data):
|
|
self._construct_page(homepage_data)
|
|
self._create_footer(homepage_data)
|
|
self._create_header(homepage_data)
|
|
self._apply_website_themes(homepage_data)
|
|
self._apply_user_values(homepage_data)
|
|
|
|
def _construct_page(self, page_data):
|
|
html_block_list = page_data.get('body_html')
|
|
if not html_block_list:
|
|
return
|
|
rendered_snippets = []
|
|
nb_snippets = len(html_block_list)
|
|
for i, snippet in enumerate(html_block_list, start=1):
|
|
try:
|
|
# Remove \ufeff character from the html (it is a BOM character that is not supported by lxml)
|
|
el = html.fromstring(snippet.replace('\ufeff', ''))
|
|
|
|
# TODO: Add the data-snippet attribute to identify the snippet
|
|
# for compatibility code
|
|
# el.attrib['data-snippet'] = snippet["name"]
|
|
|
|
# Tweak the shape of the first snippet to connect it
|
|
# properly with the header color in some themes
|
|
if i == 1:
|
|
shape_el = el.xpath("//*[hasclass('o_we_shape')]")
|
|
if shape_el:
|
|
shape_el[0].attrib['class'] += ' o_header_extra_shape_mapping'
|
|
|
|
# Tweak the shape of the last snippet to connect it
|
|
# properly with the footer color in some themes
|
|
if i == nb_snippets:
|
|
shape_el = el.xpath("//*[hasclass('o_we_shape')]")
|
|
if shape_el:
|
|
shape_el[0].attrib['class'] += ' o_footer_extra_shape_mapping'
|
|
rendered_snippet = etree.tostring(el, encoding='unicode')
|
|
rendered_snippets.append(rendered_snippet)
|
|
except ValueError as e:
|
|
logger.warning("Error rendering snippet: %s", e)
|
|
self.view_id.save(value=f'<div class="oe_structure">{"".join(rendered_snippets)}</div>',
|
|
xpath="(//div[hasclass('oe_structure')])[last()]")
|
|
|
|
def _create_header(self, homepage_data):
|
|
# Remove all website menus, keep only the top menu "container"
|
|
self.env['website.menu'].search([('website_id', '=', self.website_id.id)]).unlink()
|
|
|
|
# Create the top menu of which to bind the menu buttons to.
|
|
top_menu = self.env['website.menu'].create({
|
|
'name': _('Top Menu for Website %s', self.website_id.id),
|
|
'url': '/default-main-menu',
|
|
'website_id': self.website_id.id,
|
|
})
|
|
for button in homepage_data.get('header', {}).get('buttons', []):
|
|
if button.get('name', '').lower() in ['sign in', 'contact us']:
|
|
continue # those are Odoo menu already
|
|
menu_content = button.get('menu_content')
|
|
if menu_content:
|
|
menu_type = menu_content.get('type')
|
|
if menu_type == 'simple_menu':
|
|
# Create the parent menu
|
|
parent_menu = self._create_menu(button, top_menu)
|
|
# Create the submenu menu.
|
|
children_menus = button.get('menu_content', {}).get('content', [])
|
|
self.env['website.menu'].create([{
|
|
'name': child_menu['name'],
|
|
'url': child_menu['href'],
|
|
'parent_id': parent_menu and parent_menu.id,
|
|
'website_id': self.website_id.id} for child_menu in children_menus])
|
|
elif menu_type == 'mega_menu':
|
|
# Create mega menu instead
|
|
button_name = button.get('name')
|
|
button_href = button.get('href')
|
|
mega_menu_content = button.get('menu_content', {}).get('content')
|
|
if not button_name or not button_href or not mega_menu_content:
|
|
return
|
|
self.env['website.menu'].create({
|
|
'name': button_name,
|
|
'url': button_href,
|
|
'website_id': self.website_id.id,
|
|
'parent_id': top_menu.id,
|
|
'is_mega_menu': True,
|
|
'mega_menu_content': mega_menu_content,
|
|
})
|
|
else:
|
|
self._create_menu(button, top_menu)
|
|
|
|
# TODO: Need to try and set the level of transparency of the header overlay.
|
|
header_position = homepage_data.get('header_position', 'regular')
|
|
if header_position == 'regular':
|
|
self.header_overlay = False
|
|
elif header_position == 'over-the-content':
|
|
self.header_overlay = True
|
|
|
|
def _create_menu(self, menu_vals, top_menu):
|
|
menu_name = menu_vals.get('name')
|
|
menu_href = menu_vals.get('href')
|
|
if not menu_name or not menu_href:
|
|
return
|
|
menu = self.env['website.menu'].create({
|
|
'name': menu_name,
|
|
'url': menu_href,
|
|
'parent_id': top_menu.id,
|
|
'website_id': self.website_id.id,
|
|
})
|
|
return menu
|
|
|
|
def _apply_website_themes(self, homepage_data):
|
|
values = {}
|
|
color_palette = homepage_data.get('color_palette', {})
|
|
for i in range(1, 6):
|
|
color = color_palette.get(f'o-color-{i}')
|
|
if color:
|
|
values[f'o-color-{i}'] = color
|
|
|
|
header_color = homepage_data.get('header_color', {})
|
|
if header_color:
|
|
values.update({
|
|
'menu': header_color.get('menu', "'NULL'"),
|
|
'menu-gradient': header_color.get('menu-gradient', "'NULL'"),
|
|
'menu-custom': header_color.get('menu-custom', "'NULL'"),
|
|
})
|
|
|
|
footer_color = homepage_data.get('footer_color', {})
|
|
if footer_color:
|
|
values.update({
|
|
'footer': footer_color.get('footer', "'NULL'"),
|
|
'footer-gradient': footer_color.get('footer-gradient', "'NULL'"),
|
|
'footer-custom': footer_color.get('footer-custom', "'NULL'"),
|
|
})
|
|
|
|
# TODO: Try add this new color palette as an option to the odoo editor list of options.
|
|
self.env['web_editor.assets'].make_scss_customization(
|
|
'/website/static/src/scss/options/colors/user_color_palette.scss',
|
|
values,
|
|
)
|
|
|
|
def _apply_user_values(self, homepage_data):
|
|
user_values = {
|
|
'font': 'null'
|
|
}
|
|
if homepage_data.get('footer'):
|
|
user_values.update({'footer-template': f"'imported-footer-{self.website_id.id}'"})
|
|
|
|
fonts = homepage_data.get('fonts', {})
|
|
if fonts:
|
|
google_fonts = fonts.get('google-fonts', 'null')
|
|
google_fonts_tuple = 'null'
|
|
if google_fonts != 'null':
|
|
google_fonts_tuple = f"""('{"', '".join(google_fonts)}')"""
|
|
user_values.update({
|
|
'google-fonts': google_fonts_tuple,
|
|
'headings-font': fonts.get('h1', 'null'),
|
|
'h2-font': fonts.get('h2', 'null'),
|
|
'h3-font': fonts.get('h3', 'null'),
|
|
'h4-font': fonts.get('h4', 'null'),
|
|
'h5-font': fonts.get('h5', 'null'),
|
|
'h6-font': fonts.get('h6', 'null'),
|
|
'font': fonts.get('p', 'null'),
|
|
'buttons-font': fonts.get('button', 'null'),
|
|
'display-1-font': fonts.get('h1-display-1', 'null'),
|
|
'display-2-font': fonts.get('h1-display-2', 'null'),
|
|
'display-3-font': fonts.get('h1-display-3', 'null'),
|
|
'display-4-font': fonts.get('h1-display-4', 'null'),
|
|
})
|
|
|
|
self.env['web_editor.assets'].make_scss_customization(
|
|
'/website/static/src/scss/options/user_values.scss',
|
|
user_values,
|
|
)
|
|
|
|
def _create_footer(self, homepage_data):
|
|
footer_html_list = homepage_data.get('footer', [])
|
|
rendered_snippets = []
|
|
for snippet in footer_html_list:
|
|
try:
|
|
# Process the footer html content (for example, an <img> element becomes <img/>.)
|
|
el = html.fromstring(snippet)
|
|
rendered_snippet = etree.tostring(el, encoding='unicode')
|
|
rendered_snippets.append(rendered_snippet)
|
|
except ValueError as e:
|
|
logger.warning(e)
|
|
|
|
# Now generate the footer template.
|
|
new_footer_template = f"""
|
|
<xpath expr="//div[@id='footer']" position="replace">
|
|
<div id="footer" class="oe_structure oe_structure_solo" t-ignore="true" t-if="not no_footer">{"".join(rendered_snippets)}</div>
|
|
</xpath>"""
|
|
|
|
self.env['ir.ui.view'].create({
|
|
'name': 'Template WS Custom Footer',
|
|
'type': 'qweb',
|
|
'key': f'website_generator.template_ws_custom_footer_{self.website_id.id}',
|
|
'arch': new_footer_template,
|
|
'inherit_id': self.env['website'].with_context(website_id=self.website_id.id).viewref('website.layout').id,
|
|
'website_id': self.website_id.id,
|
|
})
|
|
|
|
# And generate the footer snippet (such that it is selectable in the editor.)
|
|
new_footer_snippet = f"""
|
|
<xpath expr="//we-select[@data-variable='footer-template']" position="inside">
|
|
<we-button title="Imported Footer"
|
|
class="position-relative"
|
|
data-customize-website-views="website_generator.template_ws_custom_footer_{self.website_id.id}"
|
|
data-customize-website-variable="'imported-footer-{self.website_id.id}'"
|
|
data-img="/website_generator/static/src/img/footer_template_imported.svg">
|
|
<span class="badge bg-info position-absolute ms-4" style="top: 10px; left: 4px;">
|
|
<i class="fa fa-gears"></i>
|
|
Imported
|
|
</span>
|
|
</we-button>
|
|
</xpath>"""
|
|
|
|
self.env['ir.ui.view'].create({
|
|
'name': 'WS Custom Footer',
|
|
'type': 'qweb',
|
|
'key': f'website.ws_custom_footer{self.website_id.id}',
|
|
'arch': new_footer_snippet,
|
|
'inherit_id': self.env.ref('website.snippet_options').id,
|
|
'website_id': self.website_id.id,
|
|
})
|