summaryrefslogtreecommitdiff
path: root/addons/website_theme_install
diff options
context:
space:
mode:
Diffstat (limited to 'addons/website_theme_install')
-rw-r--r--addons/website_theme_install/__manifest__.py2
-rw-r--r--addons/website_theme_install/models/__init__.py4
-rw-r--r--addons/website_theme_install/models/ir_module_module.py191
-rw-r--r--addons/website_theme_install/models/res_company.py28
-rw-r--r--addons/website_theme_install/models/res_config_settings.py14
-rw-r--r--addons/website_theme_install/models/theme_models.py94
-rw-r--r--addons/website_theme_install/models/website.py25
-rw-r--r--addons/website_theme_install/security/ir.model.access.csv6
-rw-r--r--addons/website_theme_install/static/src/scss/website_theme_install.scss35
-rw-r--r--addons/website_theme_install/views/res_config_settings_views.xml15
-rw-r--r--addons/website_theme_install/views/views.xml53
11 files changed, 421 insertions, 46 deletions
diff --git a/addons/website_theme_install/__manifest__.py b/addons/website_theme_install/__manifest__.py
index 1fca59f40c5..26266cb8ff3 100644
--- a/addons/website_theme_install/__manifest__.py
+++ b/addons/website_theme_install/__manifest__.py
@@ -7,6 +7,8 @@
'data': [
'views/assets.xml',
'views/views.xml',
+ 'views/res_config_settings_views.xml',
+ 'security/ir.model.access.csv',
],
'depends': ['website'],
'auto_install': True,
diff --git a/addons/website_theme_install/models/__init__.py b/addons/website_theme_install/models/__init__.py
index de58fda6e31..0e35edfc5eb 100644
--- a/addons/website_theme_install/models/__init__.py
+++ b/addons/website_theme_install/models/__init__.py
@@ -2,3 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import ir_module_module
+from . import res_company
+from . import res_config_settings
+from . import theme_models
+from . import website
diff --git a/addons/website_theme_install/models/ir_module_module.py b/addons/website_theme_install/models/ir_module_module.py
index 5350abea437..7207a613ade 100644
--- a/addons/website_theme_install/models/ir_module_module.py
+++ b/addons/website_theme_install/models/ir_module_module.py
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import logging
import os
from odoo import api, fields, models
+_logger = logging.getLogger(__name__)
class IrModuleModule(models.Model):
@@ -13,6 +15,174 @@ class IrModuleModule(models.Model):
image_ids = fields.One2many('ir.attachment', 'res_id',
domain=[('res_model', '=', _name), ('mimetype', '=like', 'image/%')],
string='Screenshots', readonly=True)
+ # for kanban view
+ is_installed_on_current_website = fields.Boolean(compute='_compute_is_installed_on_current_website')
+
+ @api.multi
+ def _compute_is_installed_on_current_website(self):
+ for module in self:
+ module.is_installed_on_current_website = module == self.env['website'].get_current_website().theme_id
+
+ @api.multi
+ def write(self, vals):
+ if vals.get('state') == 'installed' and self.name.startswith('theme_'):
+ _logger.info('Module %s has been loaded as theme template' % self.name)
+ return super(IrModuleModule, self).write(vals)
+
+ @api.multi
+ def _get_module_data(self, model_name):
+ IrModelData = self.env['ir.model.data']
+ records = self.env[model_name]
+
+ for module in self:
+ imd_ids = IrModelData.search([('module', '=', module.name), ('model', '=', model_name)]).mapped('res_id')
+ records |= self.env[model_name].browse(imd_ids)
+ return records
+
+ @api.multi
+ def _load_one_theme_module(self, website, loaded):
+ # loaded is a dict updated in place !
+ # keep previous record created to update overrided record
+ _logger.info('Load theme %s for website %s from template.' % (self.mapped('name'), website.id))
+
+ for attach in self._get_module_data('theme.ir.attachment'):
+ already_create = loaded['attachments'].filtered(lambda x: x.key == attach.key)
+ new_attach = {
+ 'public': True,
+ 'res_model': 'ir.ui.view',
+ 'type': 'url',
+ 'name': attach.name,
+ 'url': attach.url,
+ 'website_id': website.id,
+ 'theme_template_id': attach.id,
+ }
+ if already_create:
+ already_create.update(new_attach)
+ else: # if module B override attachment from dependence A
+ loaded['attachments'] += self.env['ir.attachment'].create(new_attach)
+
+ for view in self._get_module_data('theme.ir.ui.view'):
+ if view.inherit_id and view.inherit_id._name == 'theme.ir.ui.view':
+ view.inherit_id = loaded['views'].filtered(lambda x: x.theme_template_id == view.inherit_id)
+
+ new_view = {
+ 'type': view.type or 'qweb',
+ 'name': view.name,
+ 'arch': view.arch,
+ 'key': view.key,
+ 'arch': view.arch,
+ 'arch_fs': view.arch_fs,
+ 'priority': view.priority,
+ 'active': view.active,
+ 'inherit_id': view.inherit_id and view.inherit_id.id,
+ 'theme_template_id': view.id,
+ 'website_id': website.id,
+ }
+ loaded['views'] += self.env['ir.ui.view'].create(new_view)
+
+ for page in self._get_module_data('theme.website.page'):
+ new_page = {
+ 'url': page.url,
+ 'view_id': loaded['views'].filtered(lambda x: x.theme_template_id == page.view_id).id,
+ 'website_indexed': page.website_indexed,
+ 'theme_template_id': page.id,
+ }
+ loaded['pages'] += self.env['website.page'].create(new_page)
+
+ for menu in self._get_module_data('theme.website.menu'):
+ new_menu = {
+ 'name': menu.name,
+ 'url': menu.url,
+ 'page_id': loaded['pages'].filtered(lambda x: x.theme_template_id == menu.page_id).id,
+ 'new_window': menu.new_window,
+ 'sequence': menu.sequence,
+ 'parent_id': loaded['menus'].filtered(lambda x: x.theme_template_id == menu.parent_id).id,
+ 'theme_template_id': menu.id,
+ }
+ loaded['menus'] += self.env['website.menu'].create(new_menu)
+
+ return True
+
+ @api.multi
+ def _unload_one_theme_module(self, website):
+ _logger.info('Unload theme %s for website %s from template.' % (self.mapped('name'), website.id))
+
+ for module in self:
+ menus = module._get_module_data('theme.website.menu')
+ menu_todel = menus.mapped('copy_ids').filtered(lambda m: m.website_id == website)
+ menu_todel.unlink()
+
+ pages = module._get_module_data('theme.website.page')
+ pages_todel = pages.mapped('copy_ids').filtered(lambda p: p.website_id == website)
+ pages_todel.unlink()
+
+ attachs = module._get_module_data('theme.ir.attachment')
+ atachs_todel = attachs.mapped('copy_ids').filtered(lambda a: a.website_id == website)
+ atachs_todel.unlink()
+
+ views = module._get_module_data('theme.ir.ui.view')
+ views_todel = views.mapped('copy_ids').filtered(lambda v: v.website_id == website)
+ views_todel.unlink()
+
+ def _remove_theme_on_website(self, website):
+ installed_deps = website.theme_id.upstream_dependencies(exclude_states=('',)).filtered(lambda x: x.name.startswith('theme_'))
+ for mod in installed_deps:
+ mod._unload_one_theme_module(website)
+
+ def _copy_theme_on_website(self, website):
+ loaded = {
+ 'views': self.env['ir.ui.view'],
+ 'attachments': self.env['ir.attachment'],
+ 'pages': self.env['website.page'],
+ 'menus': self.env['website.menu'],
+ }
+ mods_to_load = reversed(self + self.upstream_dependencies(exclude_states=('',)).filtered(lambda x: x.name.startswith('theme_')))
+ for mod in mods_to_load:
+ mod._load_one_theme_module(website, loaded)
+ self.env['theme.utils']._post_copy(mod)
+
+ @api.multi
+ def button_choose_theme(self):
+ website = self.env['website'].get_current_website()
+
+ # Unload previous theme
+ if website.theme_id:
+ website.theme_id._remove_theme_on_website(website)
+
+ website.theme_id = self
+
+ # create template data of missing theme
+ next_action = False
+ if self.state != 'installed':
+ next_action = self.button_immediate_install()
+
+ # Copy new theme from template table to real table
+ self._copy_theme_on_website(website)
+
+ # Alter next action for redirect
+ if not next_action:
+ next_action = website.button_go_website()
+ if next_action.get('tag') == 'reload' and not next_action.get('params', {}).get('menu_id'):
+ next_action = self.env.ref('website.action_website').read()[0]
+
+ return next_action
+
+ def button_remove_theme(self):
+ website = self.env['website'].get_current_website()
+ self._remove_theme_on_website(website)
+ website.theme_id = False
+
+ def button_refresh_theme(self):
+ website = self.env['website'].get_current_website()
+ # load data from xml to template table
+ self.button_immediate_upgrade()
+
+ # delete current theme
+ # todo: quid of no_update record?
+ self.button_remove_theme()
+
+ # copy data from template table to real table
+ self._copy_theme_on_website(website)
@api.model
def update_list(self):
@@ -37,25 +207,4 @@ class IrModuleModule(models.Model):
'res_model': self._name,
'res_id': app.id,
})
-
return res
-
- @api.multi
- def button_choose_theme(self):
- theme_category = self.env.ref('base.module_category_theme', False)
- theme_hidden_category = self.env.ref('base.module_category_theme_hidden', False)
-
- theme_category_id = theme_category.id if theme_category else 0
- hidden_categories_ids = [theme_hidden_category.id if theme_hidden_category else 0]
-
- self.search([ # Uninstall the theme(s) which is (are) installed
- ('state', '=', 'installed'),
- ('category_id', 'not in', hidden_categories_ids),
- '|', ('category_id', '=', theme_category_id), ('category_id.parent_id', '=', theme_category_id)
- ]).button_immediate_uninstall()
-
- next_action = self.button_immediate_install() # Then install the new chosen one
- if next_action.get('tag') == 'reload' and not next_action.get('params', {}).get('menu_id'):
- next_action = self.env.ref('website.action_website').read()[0]
-
- return next_action
diff --git a/addons/website_theme_install/models/res_company.py b/addons/website_theme_install/models/res_company.py
new file mode 100644
index 00000000000..f125a87dca3
--- /dev/null
+++ b/addons/website_theme_install/models/res_company.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+from ast import literal_eval
+
+
+class ResCompany(models.Model):
+ _inherit = 'res.company'
+
+ def _compute_website_theme_onboarding_done(self):
+ """ The step is marked as done if one theme is installed. """
+ # we need the same domain as the existing action
+ action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0]
+ domain = literal_eval(action['domain'])
+ domain.append(('state', '=', 'installed'))
+ installed_themes_count = self.env['ir.module.module'].sudo().search_count(domain)
+ for record in self:
+ record.website_theme_onboarding_done = (installed_themes_count > 0)
+
+ website_theme_onboarding_done = fields.Boolean("Onboarding website theme step done",
+ compute='_compute_website_theme_onboarding_done')
+
+ @api.model
+ def action_open_website_theme_selector(self):
+ action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0]
+ action['target'] = 'new'
+ return action
diff --git a/addons/website_theme_install/models/res_config_settings.py b/addons/website_theme_install/models/res_config_settings.py
new file mode 100644
index 00000000000..f080cea9575
--- /dev/null
+++ b/addons/website_theme_install/models/res_config_settings.py
@@ -0,0 +1,14 @@
+# coding: utf-8
+from odoo import models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ def install_theme_on_current_website(self):
+ self.website_id._force()
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': '/web#action=website_theme_install.theme_install_kanban_action',
+ 'target': 'self',
+ }
diff --git a/addons/website_theme_install/models/theme_models.py b/addons/website_theme_install/models/theme_models.py
new file mode 100644
index 00000000000..428fa55a559
--- /dev/null
+++ b/addons/website_theme_install/models/theme_models.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+from odoo import fields, models
+from odoo.modules.module import get_resource_from_path
+
+_logger = logging.getLogger(__name__)
+
+
+class ThemeView(models.Model):
+ _name = 'theme.ir.ui.view'
+
+ def compute_arch_fs(self):
+ path_info = get_resource_from_path(self._context['install_filename'])
+ if path_info:
+ return '/'.join(path_info[0:2])
+
+ name = fields.Char(required=True)
+ key = fields.Char()
+ type = fields.Char()
+ priority = fields.Integer(default=16, required=True)
+ active = fields.Boolean(default=True)
+ arch = fields.Text()
+ arch_fs = fields.Char(default=compute_arch_fs)
+ inherit_id = fields.Reference(selection=[('ir.ui.view', 'ir.ui.view'), ('theme.ir.ui.view', 'theme.ir.ui.view')])
+ copy_ids = fields.One2many('ir.ui.view', 'theme_template_id', 'Views using a copy of me', copy=False, readonly=True)
+
+
+class ThemeAttachment(models.Model):
+ _name = 'theme.ir.attachment'
+
+ name = fields.Char(required=True)
+ key = fields.Char(required=True)
+ url = fields.Char()
+ copy_ids = fields.One2many('ir.attachment', 'theme_template_id', 'Attachment using a copy of me', copy=False, readonly=True)
+
+
+class ThemeMenu(models.Model):
+ _name = 'theme.website.menu'
+
+ name = fields.Char(required=True)
+ url = fields.Char(default='')
+ page_id = fields.Many2one('theme.website.page', ondelete='cascade')
+ new_window = fields.Boolean('New Window')
+ sequence = fields.Integer()
+ parent_id = fields.Many2one('theme.website.menu', index=True, ondelete="cascade")
+ copy_ids = fields.One2many('website.menu', 'theme_template_id', 'Menu using a copy of me', copy=False, readonly=True)
+
+
+class ThemePage(models.Model):
+ _name = 'theme.website.page'
+
+ url = fields.Char()
+ view_id = fields.Many2one('theme.ir.ui.view', required=True, ondelete="cascade")
+ website_indexed = fields.Boolean('Page Indexed', default=True)
+ copy_ids = fields.One2many('website.page', 'theme_template_id', 'Theme using a copy of me', copy=False, readonly=True)
+
+
+class Theme(models.AbstractModel):
+ _name = 'theme.utils'
+ _auto = False
+
+ def _post_copy(self, mod):
+ theme_post_copy = '_%s_post_copy' % mod.name
+ if hasattr(self, theme_post_copy):
+ _logger.info('Executing method %s' % theme_post_copy)
+ method = getattr(self, theme_post_copy)
+ return method()
+
+
+class IrUiView(models.Model):
+ _inherit = 'ir.ui.view'
+
+ theme_template_id = fields.Many2one('theme.ir.ui.view')
+
+
+class IrAttachment(models.Model):
+ _inherit = 'ir.attachment'
+
+ key = fields.Char()
+ theme_template_id = fields.Many2one('theme.ir.attachment')
+
+
+class WebiteMenu(models.Model):
+ _inherit = 'website.menu'
+
+ theme_template_id = fields.Many2one('theme.website.menu')
+
+
+class WebsitePage(models.Model):
+ _inherit = 'website.page'
+
+ theme_template_id = fields.Many2one('theme.website.page')
diff --git a/addons/website_theme_install/models/website.py b/addons/website_theme_install/models/website.py
new file mode 100644
index 00000000000..46ee9df91e2
--- /dev/null
+++ b/addons/website_theme_install/models/website.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class Website(models.Model):
+ _name = "website"
+ _inherit = _name
+
+ def button_view_theme(self):
+ self._force()
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': '/web#action=website_theme_install.theme_install_kanban_action',
+ 'target': 'self',
+ }
+
+ def button_go_website(self):
+ self._force()
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': '/',
+ 'target': 'self',
+ }
diff --git a/addons/website_theme_install/security/ir.model.access.csv b/addons/website_theme_install/security/ir.model.access.csv
new file mode 100644
index 00000000000..fa2d6a8b262
--- /dev/null
+++ b/addons/website_theme_install/security/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_theme_ir_ui_view,access_theme_ir_ui_view,model_theme_ir_ui_view,base.group_system,1,1,1,1
+access_theme_ir_attachment,access_theme_ir_attachment,model_theme_ir_attachment,base.group_system,1,1,1,1
+access_theme_website_menu,access_theme_website_menu,model_theme_website_menu,base.group_system,1,1,1,1
+access_theme_website_page,access_theme_website_page,model_theme_website_page,base.group_system,1,1,1,1
+
diff --git a/addons/website_theme_install/static/src/scss/website_theme_install.scss b/addons/website_theme_install/static/src/scss/website_theme_install.scss
index cd83f76a4e7..ac4cf8ba575 100644
--- a/addons/website_theme_install/static/src/scss/website_theme_install.scss
+++ b/addons/website_theme_install/static/src/scss/website_theme_install.scss
@@ -25,9 +25,9 @@
background: none;
border: none;
- .o_browser_bar:before {
+ .o_browser_bar:after {
content: "•••";
- display: block;
+ display: inline-block;
padding-left: 10px;
color: darken($o-theme-kanban-gray, 10%);
font: 12px/20px Courier, monospace;
@@ -35,13 +35,10 @@
}
&.o_theme_installed .o_theme_preview_top {
- border-color: $o-brand-primary;
+ border: 3px solid $o-brand-primary;
- > .fa-check-circle {
- @include o-position-absolute(10px, 10px);
- width: 20px;
- height: 20px;
- font-size: 20px;
+ .fa {
+ color: $o-brand-primary;
}
}
@@ -95,18 +92,16 @@
}
}
- &:not(.o_theme_installed) {
- .o_theme_preview_top:hover {
- transition: all .3s ease 0s;
- transform: translateY(-10px);
- border-color: darken($o-theme-kanban-gray, 26%);
- box-shadow: 0 15px 12px -8px rgba(0, 0, 0, .4);
-
- .o_button_area {
- opacity: 1;
- visibility: visible;
- transition: opacity 0.2s ease 0.1s;
- }
+ .o_theme_preview_top:hover {
+ transition: all .3s ease 0s;
+ transform: translateY(-10px);
+ border-color: darken($o-theme-kanban-gray, 26%);
+ box-shadow: 0 15px 12px -8px rgba(0, 0, 0, .4);
+
+ .o_button_area {
+ opacity: 1;
+ visibility: visible;
+ transition: opacity 0.2s ease 0.1s;
}
}
}
diff --git a/addons/website_theme_install/views/res_config_settings_views.xml b/addons/website_theme_install/views/res_config_settings_views.xml
new file mode 100644
index 00000000000..b7c3598569c
--- /dev/null
+++ b/addons/website_theme_install/views/res_config_settings_views.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="res_config_settings_view_form" model="ir.ui.view">
+ <field name="name">res.config.settings.view.form.inherit.website_theme_install</field>
+ <field name="model">res.config.settings</field>
+ <field name="inherit_id" ref="website.res_config_settings_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@id='domain_setting']/div[hasclass('o_setting_right_pane')]" position="inside">
+ <div>
+ <button name="install_theme_on_current_website" type="object" string="Change theme" class="btn-link" icon="fa-arrow-right"/>
+ </div>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/website_theme_install/views/views.xml b/addons/website_theme_install/views/views.xml
index 37271fa817d..7ad568c4def 100644
--- a/addons/website_theme_install/views/views.xml
+++ b/addons/website_theme_install/views/views.xml
@@ -13,17 +13,24 @@
<field name="image_ids"/>
<field name="category_id"/>
<field name="display_name"/>
+ <field name="is_installed_on_current_website"/>
<templates>
- <div t-name="kanban-box" t-attf-class="o_theme_preview mb16 mt16 #{record.state.raw_value == 'installed'? 'o_theme_installed' : ''}">
+ <div t-name="kanban-box" t-attf-class="o_theme_preview mb16 mt16 #{record.is_installed_on_current_website.raw_value? 'o_theme_installed' : ''}">
<t t-set="has_image" t-value="record.image_ids.raw_value.length > 0"/>
<t t-set="has_screenshot" t-value="record.image_ids.raw_value.length > 1"/>
<t t-set="image_url" t-value="has_image ? 'web/image/' + record.image_ids.raw_value[0] : record.icon.value"/>
<div class="o_theme_preview_top bg-white mb4">
- <div class="o_browser_bar"/>
- <i states="installed" class="text-center bg-white text-success rounded-circle fa fa-check-circle" role="img" aria-label="Installed" title="Installed"/>
+ <div class="o_browser_bar">
+ <i states="installed" t-if="record.is_installed_on_current_website.raw_value" class="text-center ml4 img-circle fa fa-check-circle" role="img" aria-label="Installed" title="Installed"/>
+ </div>
<div t-attf-class="bg-gray-lighter #{has_screenshot? 'o_theme_screenshot' : (has_image ? 'o_theme_cover' : 'o_theme_logo')}" t-attf-style="background-image: url(#{image_url});"/>
- <div states="uninstalled" class="o_button_area">
+ <div t-if="record.is_installed_on_current_website.raw_value" class="o_button_area">
+ <button type="object" name="button_refresh_theme" class="btn btn-primary">Update theme</button>
+ <hr />
+ <button type="object" name="button_remove_theme" class="btn btn-secondary">Remove theme</button>
+ </div>
+ <div t-else="" class="o_button_area">
<button type="object" name="button_choose_theme" class="btn btn-primary">Use this theme</button>
<hr t-if="record.url.value"/>
<a role="button" t-if="record.url.value" class="btn btn-secondary" t-att-href="record.url.value" target="_blank">Live Preview</a>
@@ -59,9 +66,14 @@
</field>
</record>
+ <!-- themes should be installed through website_theme_install -->
+ <record id="base.open_module_tree" model="ir.actions.act_window">
+ <field name="domain">['!', ('name', '=like', 'theme_%')]</field>
+ </record>
+
<!-- Actions to list themes with custom kanban (launched on module installation) -->
<record id="theme_install_kanban_action" model="ir.actions.act_window">
- <field name="name">Choose Your Theme</field>
+ <field name="name">Choose a theme for this website</field>
<field name="res_model">ir.module.module</field>
<field name="view_mode">kanban</field>
<field name="view_id" ref="theme_view_kanban"/>
@@ -71,6 +83,7 @@
'|', ('category_id', '=', ref('base.module_category_theme', False)), ('category_id.parent_id', '=', ref('base.module_category_theme', False))
]"/>
</record>
+
<record id="theme_install_todo_action" model="ir.actions.server">
<field name="name">Config: Choose Your Theme</field>
<field name="model_id" ref="model_ir_module_module"/>
@@ -97,4 +110,34 @@ action = {
</div>
</xpath>
</template>
+
+ <!-- Add link to theme install in default customize modal -->
+ <record id="website_settings_payment" model="ir.ui.view">
+ <field name="name">website.form</field>
+ <field name="model">website</field>
+ <field name="inherit_id" ref="website.view_website_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@name='other']" position='before'>
+ <div name="theme">
+ <separator string="Theme"/>
+ <group name="Theme">
+ <button type="object" name="button_view_theme" class="btn btn-primary">View themes</button>
+ </group>
+ </div>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- ONBOARDING -->
+ <template id="onboarding_website_theme_step">
+ <t t-call="base.onboarding_step">
+ <t t-set="title">Website Theme</t>
+ <t t-set="description">Select a theme for your website.</t>
+ <t t-set="btn_text">Let's start!</t>
+ <t t-set="method" t-value="'action_open_website_theme_selector'" />
+ <t t-set="model" t-value="'res.company'" />
+ <t t-set="done" t-value="company.website_theme_onboarding_done" />
+ </t>
+ </template>
+
</odoo>