[ADD] website_theme_install: try to make theme multi website compliant
From now, when you install a theme, it load the data from xml to template table theme [ir.ui.view|ir.attachment|website.page|website.menu]. Data are only copied from this template table into the real table when you choose a theme on a website; Making them website_specific and with a link to the original to allow futur update. A special case is done to create theme.ir.ui.view when you are installing a theme, even if you continue to use template tag to create quick view. Co-authored-by: Derie Romain <rde@odoo.com> Co-authored-by: Kersten Jérémy <jke@odoo.com>
This commit is contained in:
co-authored by
Derie Romain
parent
066cfc9598
commit
c0968e68ed
@@ -146,6 +146,9 @@ class Web_Editor(http.Controller):
|
||||
# therefore we have to recover the files from the request object
|
||||
Attachments = request.env['ir.attachment'] # registry for the attachment table
|
||||
|
||||
website = request.env['website'].get_current_website() # # TO MOVE
|
||||
|
||||
|
||||
res_model = kwargs.get('res_model', 'ir.ui.view')
|
||||
if res_model != 'ir.ui.view' and kwargs.get('res_id'):
|
||||
res_id = int(kwargs['res_id'])
|
||||
@@ -167,6 +170,8 @@ class Web_Editor(http.Controller):
|
||||
'public': res_model == 'ir.ui.view',
|
||||
'res_id': res_id,
|
||||
'res_model': res_model,
|
||||
'website_id': website.id,
|
||||
|
||||
})
|
||||
attachment.generate_access_token()
|
||||
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
|
||||
@@ -198,6 +203,8 @@ class Web_Editor(http.Controller):
|
||||
'public': res_model == 'ir.ui.view',
|
||||
'res_id': res_id,
|
||||
'res_model': res_model,
|
||||
'website_id': website.id, # TO MOVE IN WEBSITEID
|
||||
|
||||
})
|
||||
attachment.generate_access_token()
|
||||
attachments += attachment
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from odoo import api, fields, models
|
||||
|
||||
from odoo import fields, models, api
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
|
||||
_inherit = "ir.attachment"
|
||||
|
||||
# related for backward compatibility with saas-6
|
||||
website_url = fields.Char(string="Website URL", related='local_url', deprecated=True)
|
||||
key = fields.Char(help='Technical field used to resolve multiple attachments in a multi-website environment.')
|
||||
website_id = fields.Many2one('website')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
@@ -23,50 +25,3 @@ class Attachment(models.Model):
|
||||
@api.model
|
||||
def get_serving_groups(self):
|
||||
return super(Attachment, self).get_serving_groups() + ['website.group_website_designer']
|
||||
|
||||
key = fields.Char(help='Technical field used to resolve multiple attachments in a multi-website environment.')
|
||||
theme_id = fields.Many2one('ir.module.module')
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
self |= self.search([('key', 'in', self.filtered('key').mapped('key'))])
|
||||
return super(Attachment, self).unlink()
|
||||
|
||||
@api.multi
|
||||
def _get_theme_specific_attachment(self, theme_name):
|
||||
self.ensure_one()
|
||||
attachment = self
|
||||
module_being_updated = self.env['ir.module.module'].search([('name', '=', theme_name)])
|
||||
xml_id = self.env['ir.model.data'].search([('model', '=', 'ir.attachment'), ('res_id', '=', attachment.id)])
|
||||
if xml_id and xml_id.module != theme_name:
|
||||
_logger.info('%s is updating attachment %s (ID: %s)', theme_name, xml_id.complete_name, attachment.id)
|
||||
|
||||
# check if a previously copied attachment for this theme already exists
|
||||
theme_specific_attachment = self.env['ir.attachment'].search([('key', '=', attachment.key), ('theme_id', '=', module_being_updated.id)])
|
||||
if theme_specific_attachment:
|
||||
attachment = theme_specific_attachment
|
||||
_logger.info('diverting write to %s (ID: %s)', attachment.name, attachment.id)
|
||||
else:
|
||||
attachment.with_context(no_cow=True).key = xml_id.complete_name
|
||||
attachment = attachment.copy({
|
||||
'theme_id': module_being_updated.id,
|
||||
'key': xml_id.complete_name,
|
||||
})
|
||||
_logger.info('created new theme-specific attachment %s (ID: %s)', attachment.name, attachment.id)
|
||||
|
||||
return attachment
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if not self._context.get('no_cow'):
|
||||
for attachment in self:
|
||||
currently_updating = self._context.get('install_mode_data', {}).get('module', '')
|
||||
if currently_updating.startswith('theme_'):
|
||||
attachment = attachment._get_theme_specific_attachment(currently_updating)
|
||||
|
||||
super(Attachment, attachment).write(vals)
|
||||
else:
|
||||
super(Attachment, self).write(vals)
|
||||
|
||||
return True
|
||||
>>>>>>> [IMP] website,*: support multiple websites
|
||||
|
||||
@@ -255,8 +255,8 @@ class Http(models.AbstractModel):
|
||||
@classmethod
|
||||
def _xmlid_to_obj(cls, env, xmlid):
|
||||
website_id = env['website'].get_current_website()
|
||||
if website_id and website_id.installed_theme_id:
|
||||
obj = env['ir.attachment'].search([('key', '=', xmlid), ('theme_id', '=', website_id.installed_theme_id.id)])
|
||||
if website_id and website_id.theme_id:
|
||||
obj = env['ir.attachment'].search([('key', '=', xmlid), ('website_id', '=', website_id.id)])
|
||||
if obj:
|
||||
return obj[0]
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ class View(models.Model):
|
||||
website_id = fields.Many2one('website', ondelete='cascade', string="Website")
|
||||
page_ids = fields.One2many('website.page', 'view_id')
|
||||
first_page_id = fields.Many2one('website.page', string='Website Page', help='First page linked to this view', compute='_compute_first_page_id')
|
||||
theme_id = fields.Many2one('ir.module.module')
|
||||
# theme_id = fields.Many2one('ir.module.module')
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def _compute_first_page_id(self):
|
||||
self.first_page_id = self.env['website.page'].search([('view_id', '=', self.id)], limit=1)
|
||||
for view in self:
|
||||
view.first_page_id = self.env['website.page'].search([('view_id', '=', view.id)], limit=1)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
@@ -38,11 +39,6 @@ class View(models.Model):
|
||||
if not self._context.get('no_cow'):
|
||||
current_website_id = self._context.get('website_id')
|
||||
for view in self:
|
||||
currently_updating = self._context.get('install_mode_data', {}).get('module', '')
|
||||
if 'theme_' in currently_updating:
|
||||
current_website_id = False
|
||||
view = view._get_theme_specific_view(currently_updating)
|
||||
|
||||
# if generic view in multi-website context
|
||||
if current_website_id and not view.website_id:
|
||||
new_website_specific_view = view.copy({'website_id': current_website_id})
|
||||
@@ -81,30 +77,10 @@ class View(models.Model):
|
||||
self.clear_caches()
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def _get_theme_specific_view(self, theme_name):
|
||||
self.ensure_one()
|
||||
view = self
|
||||
module_being_updated = self.env['ir.module.module'].search([('name', '=', theme_name)])
|
||||
xml_id = self.env['ir.model.data'].search([('model', '=', 'ir.ui.view'), ('res_id', '=', view.id)])
|
||||
if xml_id and xml_id.module != theme_name:
|
||||
_logger.info('%s is updating view %s (ID: %s)', theme_name, xml_id.complete_name, view.id)
|
||||
|
||||
# check if a previously copied view for this theme already exists
|
||||
theme_specific_view = self.env['ir.ui.view'].search([('key', '=', view.key), ('theme_id', '=', module_being_updated.id)])
|
||||
if theme_specific_view:
|
||||
view = theme_specific_view
|
||||
_logger.info('diverting write to %s (ID: %s)', view.name, view.id)
|
||||
else:
|
||||
view = view.copy({'theme_id': module_being_updated.id})
|
||||
_logger.info('created new theme-specific view %s (ID: %s)', view.name, view.id)
|
||||
|
||||
return view
|
||||
|
||||
def _create_website_specific_pages_for_view(self, new_view, website):
|
||||
for page in self.page_ids:
|
||||
# create new pages for this view
|
||||
new_page = page.copy({
|
||||
page.copy({
|
||||
'view_id': new_view.id,
|
||||
})
|
||||
|
||||
@@ -184,9 +160,6 @@ class View(models.Model):
|
||||
# we will prefer inactive website-specific views over active generic ones
|
||||
if current_website:
|
||||
domain = [leaf for leaf in domain if 'active' not in leaf]
|
||||
if current_website.theme_ids:
|
||||
theme_views_domain = [('theme_id', 'in', current_website.theme_ids.ids)]
|
||||
website_views_domain = expression.OR([website_views_domain, theme_views_domain])
|
||||
|
||||
return expression.AND([website_views_domain, domain])
|
||||
|
||||
@@ -207,10 +180,7 @@ class View(models.Model):
|
||||
def get_view_id(self, xml_id):
|
||||
if 'website_id' in self._context and not isinstance(xml_id, pycompat.integer_types):
|
||||
current_website = self.env['website'].browse(self._context.get('website_id'))
|
||||
key_domain = [('key', '=', xml_id)]
|
||||
theme_views_domain = [('theme_id', 'in', current_website.theme_ids.ids)]
|
||||
website_views_domain = [('theme_id', '=', False)] + current_website.website_domain()
|
||||
domain = expression.AND([expression.OR([theme_views_domain, website_views_domain]), key_domain])
|
||||
domain = ['&', ('key', '=', xml_id)] + current_website.website_domain()
|
||||
|
||||
view = self.search(domain, order='website_id', limit=1)
|
||||
if not view:
|
||||
|
||||
@@ -16,7 +16,7 @@ class ResUsers(models.Model):
|
||||
string='Website Editor', compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||
category_xml_id='base.module_category_website')
|
||||
has_group_multi_website = fields.Boolean(
|
||||
'Multi-Website',
|
||||
'Multi-Websites',
|
||||
compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||
group_xml_id='website.group_multi_website')
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ class Website(models.Model):
|
||||
menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
|
||||
homepage_id = fields.Many2one('website.page', string='Homepage')
|
||||
favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.")
|
||||
installed_theme_id = fields.Many2one('ir.module.module')
|
||||
theme_ids = fields.Many2many('ir.module.module', 'website_theme', 'website_id', 'ir_module_module_id')
|
||||
theme_id = fields.Many2one('ir.module.module', help='Installed theme')
|
||||
|
||||
auth_signup_uninvited = fields.Selection([
|
||||
('b2b', 'On invitation'),
|
||||
('b2c', 'Free sign up'),
|
||||
@@ -168,13 +168,14 @@ class Website(models.Model):
|
||||
self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id),
|
||||
('key', '=', standard_homepage.key)])
|
||||
top_menu = self.env['website.menu'].create({
|
||||
'name': 'Top Menu for website ' + str(self.id),
|
||||
'name': _('Top Menu for website %s') % self.id,
|
||||
'website_id': self.id,
|
||||
'sequence': 0
|
||||
})
|
||||
self.menu_id = top_menu.id
|
||||
self.env['website.menu'].create({
|
||||
'name': 'Home', 'url': '/',
|
||||
'name': _('Home'),
|
||||
'url': '/',
|
||||
'website_id': self.id,
|
||||
'parent_id': top_menu.id,
|
||||
'sequence': 10
|
||||
@@ -391,15 +392,16 @@ class Website(models.Model):
|
||||
|
||||
return dependencies
|
||||
|
||||
@api.model
|
||||
def page_exists(self, name, module='website'):
|
||||
try:
|
||||
name = (name or "").replace("/website.", "").replace("/", "")
|
||||
if not name:
|
||||
return False
|
||||
return self.env.ref('%s.%s' % module, name)
|
||||
except Exception:
|
||||
return False
|
||||
# removed by 3c9e6c89e7207636c1bdab4ed8118c0c1089d43e
|
||||
# @api.model
|
||||
# def page_exists(self, name, module='website'):
|
||||
# try:
|
||||
# name = (name or "").replace("/website.", "").replace("/", "")
|
||||
# if not name:
|
||||
# return False
|
||||
# return self.env.ref('%s.%s' % module, name)
|
||||
# except Exception:
|
||||
# return False
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Languages
|
||||
@@ -532,8 +534,7 @@ class Website(models.Model):
|
||||
endpoint.routing['type'] == 'http' and
|
||||
endpoint.routing['auth'] in ('none', 'public') and
|
||||
endpoint.routing.get('website', False) and
|
||||
all(hasattr(converter, 'generate') for converter in converters) and
|
||||
endpoint.routing.get('website')):
|
||||
all(hasattr(converter, 'generate') for converter in converters)):
|
||||
return False
|
||||
|
||||
# dont't list routes without argument having no default value or converter
|
||||
@@ -915,21 +916,14 @@ class Page(models.Model):
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
if default and not default.get('view_id'):
|
||||
view = self.env['ir.ui.view'].browse(self.view_id.id)
|
||||
# website.page's ir.ui.view should have a different key than the one it
|
||||
# is copied from.
|
||||
# (eg: website_version: an ir.ui.view record with the same key is
|
||||
# expected to be the same ir.ui.view but from another version)
|
||||
website_id = default.get('website_id')
|
||||
# website_id should be set during copy to avoid copying a generic page to another generic copy and then write on it
|
||||
# which would result in the generic page copied to a generic one and a specific one
|
||||
new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)')), 'website_id': website_id.id})
|
||||
default = {
|
||||
'name': '%s %s' % (self.name, _('(copy)')),
|
||||
'url': self.env['website'].get_unique_path(self.url),
|
||||
'view_id': new_view.id,
|
||||
}
|
||||
if default:
|
||||
if not default.get('view_id'):
|
||||
view = self.env['ir.ui.view'].browse(self.view_id.id)
|
||||
new_view = view.copy({'website_id': default.get('website_id')})
|
||||
default['view_id'] = new_view.id
|
||||
|
||||
default['name'] = default.get('name', '%s %s' % (self.name, _('(copy)')))
|
||||
default['url'] = default.get('url', self.env['website'].get_unique_path(self.url))
|
||||
return super(Page, self).copy(default=default)
|
||||
|
||||
@api.model
|
||||
@@ -938,7 +932,7 @@ class Page(models.Model):
|
||||
:param page_id : website.page identifier
|
||||
"""
|
||||
page = self.browse(int(page_id))
|
||||
new_page = page.copy(dict(website_id=self.env['website'].get_current_website()))
|
||||
new_page = page.copy(dict(website_id=self.env['website'].get_current_website().id))
|
||||
# Should not clone menu if the page was cloned from one website to another
|
||||
# Eg: Cloning a generic page (no website) will create a page with a website, we can't clone menu (not same container)
|
||||
if clone_menu and new_page.website_id == page.website_id:
|
||||
@@ -1013,7 +1007,7 @@ class Menu(models.Model):
|
||||
if vals.get('website_id'):
|
||||
return super(Menu, self).create(vals)
|
||||
elif self._context.get('website_id'):
|
||||
vals.update({'website_id': self._context.get('website_id')})
|
||||
vals['website_id'] = self._context.get('website_id')
|
||||
return super(Menu, self).create(vals)
|
||||
else:
|
||||
# create for every site
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_attachment_form_inherit_website" model="ir.ui.view">
|
||||
<record id="view_attachment_form_inherit_website" model="ir.ui.view">
|
||||
<field name="name">ir.attachment.form.inherit.website</field>
|
||||
<field name="model">ir.attachment</field>
|
||||
<field name="inherit_id" ref="base.view_attachment_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="mimetype" position="after">
|
||||
<field name="key" groups="base.group_no_one"/>
|
||||
<field name="theme_id" groups="base.group_no_one"/>
|
||||
<field name="website_id" groups="base.group_multi_website"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -104,7 +104,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="google_analytics_setting">
|
||||
<div id="multi_website" class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="group_multi_website"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="Multi-Websites" for="group_multi_website"/>
|
||||
<div class="text-muted">
|
||||
Manage multiple websites
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-offset-6 col-lg-6 o_setting_box" id="google_analytics_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="has_google_analytics"/>
|
||||
</div>
|
||||
@@ -184,7 +195,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="ab_testing_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_version" widget="upgrade_boolean"/>
|
||||
</div>
|
||||
@@ -296,31 +307,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="utm_tracker_settings" title="Analyze the efficiency of your marketing campaigns by using trackable UTM trackers (campaigns, medium, sources). Create trackers and follow clicks from the Promote menu of your website. Those trackers can be used in Google Analytics or in Odoo reports where you can see the opportunities and sales revenue generated thanks to your links.">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_links"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_website_links"/>
|
||||
<div class="text-muted">
|
||||
Track clicks on UTM links
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Multi-Website</h2>
|
||||
<div class="row mt16 o_settings_container" id="multi_website" name="multi_website">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="group_multi_website"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="Multi-Website" for="group_multi_website"/>
|
||||
<div class="text-muted">
|
||||
Manage multiple websites
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
<field name="country_group_ids" widget="many2many_tags"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="default_lang_id"/>
|
||||
<field name="installed_theme_id" groups="base.group_no_one"/>
|
||||
<field name="theme_ids" groups="base.group_no_one" widget="many2many_tags"/>
|
||||
<field name="theme_id" groups="base.group_no_one"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -265,7 +264,6 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="theme_id" groups="base.group_no_one"/>
|
||||
<field name="key" readonly="1"/>
|
||||
<field name="page_ids" invisible="1" />
|
||||
<field name="first_page_id" attrs="{'invisible': [('page_ids', '=', [])]}" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
'views/assets.xml',
|
||||
'views/views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'depends': ['website'],
|
||||
'auto_install': True,
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_module_module
|
||||
from . import ir_ui_view
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import theme_models
|
||||
from . import website
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -16,71 +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().installed_theme_id
|
||||
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':
|
||||
self._handle_auto_installs()
|
||||
|
||||
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 _assign_views(self):
|
||||
IrModelData = self.env['ir.model.data']
|
||||
for module in self:
|
||||
views = self.env['ir.ui.view'].browse(IrModelData.search([('module', '=', module.name),
|
||||
('model', '=', 'ir.ui.view')]).mapped('res_id'))
|
||||
views.write({
|
||||
'theme_id': module.id
|
||||
})
|
||||
|
||||
@api.model
|
||||
def _handle_auto_installs(self):
|
||||
for module in self.search([('auto_install', '=', True), ('name', '=like', 'theme%'), ('state', 'in', ('installed', 'to install'))]):
|
||||
theme_deps = module.get_upstream_theme_dependencies()
|
||||
websites = self.env['website'].search([]).filtered(lambda w: len(theme_deps - w.theme_ids) == 0)
|
||||
if websites:
|
||||
_logger.info('auto-loading %s on %s', module.name, websites.mapped('name'))
|
||||
websites.write({'theme_ids': [(4, module.id, 0)]})
|
||||
module._assign_views()
|
||||
|
||||
# put demo date on first website
|
||||
module._make_demo_data_website_specific(websites[0])
|
||||
|
||||
@api.multi
|
||||
def _get_module_data(self, model_name):
|
||||
IrModelData = self.env['ir.model.data']
|
||||
records = self.env[model_name]
|
||||
|
||||
for module in self:
|
||||
records |= self.env[model_name].browse(IrModelData.search([('module', '=', module.name), ('model', '=', model_name)]).mapped('res_id'))
|
||||
|
||||
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 _make_demo_data_website_specific(self, website):
|
||||
for module in self:
|
||||
menus = module._get_module_data('website.menu')
|
||||
menus.with_context(no_cow=True).write({'website_id': website.id})
|
||||
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))
|
||||
|
||||
pages = module._get_module_data('website.page')
|
||||
pages.with_context(no_cow=True).write({'website_id': website.id})
|
||||
pages.with_context(no_cow=True).mapped('view_id').write({'website_id': 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 _remove_website_specific_demo_data(self, website):
|
||||
for module in self:
|
||||
menus = module._get_module_data('website.menu')
|
||||
menus.filtered(lambda menu: menu.website_id == website).unlink()
|
||||
def _unload_one_theme_module(self, website):
|
||||
_logger.info('Unload theme %s for website %s from template.' % (self.mapped('name'), website.id))
|
||||
|
||||
pages = module._get_module_data('website.page')
|
||||
pages.filtered(lambda page: page.website_id == website).unlink()
|
||||
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):
|
||||
@@ -105,86 +207,4 @@ class IrModuleModule(models.Model):
|
||||
'res_model': self._name,
|
||||
'res_id': app.id,
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
def get_upstream_theme_dependencies(self):
|
||||
upstream_deps = self.upstream_dependencies(exclude_states=('uninstallable', 'to remove'))
|
||||
upstream_theme_deps = upstream_deps.filtered(lambda mod: any(mod.name.startswith(keyword) for keyword in ('theme_', 'snippet_', 'website_animate')))
|
||||
return upstream_theme_deps
|
||||
|
||||
@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]
|
||||
current_website = self.env['website'].get_current_website()
|
||||
|
||||
themes_to_uninstall = self.search([ # Uninstall the theme(s) which is (are) installed
|
||||
('state', '=', 'installed'),
|
||||
('category_id', 'not in', hidden_categories_ids),
|
||||
('id', 'in', current_website.theme_ids.ids),
|
||||
'|', ('category_id', '=', theme_category_id), ('category_id.parent_id', '=', theme_category_id)
|
||||
])
|
||||
|
||||
if themes_to_uninstall:
|
||||
themes_to_uninstall.button_immediate_uninstall()
|
||||
|
||||
modules_belonging_to_theme = self + self.get_upstream_theme_dependencies()
|
||||
current_website.installed_theme_id = self
|
||||
current_website.theme_ids |= modules_belonging_to_theme
|
||||
|
||||
next_action = self.button_immediate_install() # Then install the new chosen one
|
||||
modules_belonging_to_theme._assign_views()
|
||||
modules_belonging_to_theme._make_demo_data_website_specific(current_website)
|
||||
self._handle_auto_installs()
|
||||
_logger.info('installed themes %s on %s', modules_belonging_to_theme.mapped('name'), current_website.name)
|
||||
|
||||
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
|
||||
|
||||
@api.multi
|
||||
def button_uninstall(self):
|
||||
if not any(mod.name.startswith('theme_') for mod in self):
|
||||
return super(IrModuleModule, self).button_uninstall()
|
||||
|
||||
# only one theme can be uninstalled
|
||||
self.ensure_one()
|
||||
|
||||
Website = self.env['website']
|
||||
current_website = Website.get_current_website()
|
||||
if Website.search_count([('theme_ids', 'in', self.id)]) > 1:
|
||||
modules_belonging_to_theme = self + self.get_upstream_theme_dependencies() + self.downstream_dependencies()
|
||||
current_website.installed_theme_id -= modules_belonging_to_theme
|
||||
current_website.theme_ids -= modules_belonging_to_theme
|
||||
modules_belonging_to_theme._remove_website_specific_demo_data(current_website)
|
||||
_logger.info('removed themes %s from %s', modules_belonging_to_theme.mapped('name'), current_website.name)
|
||||
return True
|
||||
else:
|
||||
uninstalled_modules = self + self.downstream_dependencies()
|
||||
res = super(IrModuleModule, self).button_uninstall()
|
||||
|
||||
for website in Website.search([]):
|
||||
website.installed_theme_id -= uninstalled_modules
|
||||
website.theme_ids -= uninstalled_modules
|
||||
_logger.info('removed themes %s from %s', uninstalled_modules.mapped('name'), website.name)
|
||||
|
||||
# Attempt to uninstall upstream dependencies. This is
|
||||
# necessary for uninstalling e.g. theme_zap, when doing so
|
||||
# theme_treehouse should also be removed from the current
|
||||
# website. This way the user can install a completely new
|
||||
# theme.
|
||||
#
|
||||
# Make sure to only uninstall every module once with
|
||||
# context. Otherwise it might get removed from the current
|
||||
# website the first time and the second time it might end
|
||||
# up being wrongly uninstalled.
|
||||
if self._context.get('uninstall_upstream') is not False:
|
||||
for upstream_dependency in self.get_upstream_theme_dependencies().filtered(lambda mod: mod.name.startswith('theme_')):
|
||||
upstream_dependency.with_context(uninstall_upstream=False).button_uninstall()
|
||||
|
||||
return res
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# coding: utf-8
|
||||
from odoo import api, fields, models
|
||||
|
||||
class View(models.Model):
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
@api.model
|
||||
def _get_inheriting_views_arch_website(self, view_id):
|
||||
res = self.env['website'].get_current_website()
|
||||
|
||||
# In certain cases view validation as done by
|
||||
# _validate_module_views will fail. When attempting to load
|
||||
# inherited view CHILD:
|
||||
#
|
||||
# PARENT (theme_common)
|
||||
# ^
|
||||
# |
|
||||
# CHILD (other_theme)
|
||||
#
|
||||
# During the view validation no website_id will be in context
|
||||
# so the view will fail to apply, since PARENT won't be
|
||||
# selected by the inheriting view arch domain.
|
||||
#
|
||||
# This is not a problem however, CHILD will never be loaded on
|
||||
# a website where CHILD is not installed. To simulate this,
|
||||
# return a website which has the theme containing CHILD
|
||||
# installed.
|
||||
if res:
|
||||
return res
|
||||
else:
|
||||
view_theme = self.browse(view_id).theme_id
|
||||
if view_theme:
|
||||
return self.env['website'].search([('theme_ids', 'in', view_theme.id)], limit=1)
|
||||
else:
|
||||
return self.env['website']
|
||||
@@ -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
|
||||
@@ -1,11 +1,11 @@
|
||||
# coding: utf-8
|
||||
from odoo import api, fields, models
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
def website_install_theme(self):
|
||||
def install_theme_on_current_website(self):
|
||||
self.website_id._force()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
|
||||
@@ -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')
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
@@ -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_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_button_area {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.2s ease 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="website_go_to" position="after">
|
||||
<button name="website_install_theme" type="object" string="Change theme"/>
|
||||
</button>
|
||||
<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>
|
||||
|
||||
@@ -21,11 +21,14 @@
|
||||
<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" t-if="record.is_installed_on_current_website.raw_value" class="text-center bg-white text-success img-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 t-if="record.is_installed_on_current_website.raw_value" class="o_button_area">
|
||||
<button type="object" name="button_immediate_uninstall" class="btn btn-primary">Uninstall theme</button>
|
||||
<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>
|
||||
@@ -107,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>
|
||||
|
||||
@@ -573,7 +573,7 @@ form: module.record_id""" % (xml_id,)
|
||||
def _tag_record(self, rec, data_node=None, mode=None):
|
||||
rec_model = rec.get("model")
|
||||
model = self.env[rec_model]
|
||||
rec_id = rec.get("id",'')
|
||||
rec_id = rec.get("id", '')
|
||||
rec_context = rec.get("context", {})
|
||||
if rec_context:
|
||||
rec_context = safe_eval(rec_context)
|
||||
@@ -673,9 +673,14 @@ form: module.record_id""" % (xml_id,)
|
||||
el.tag = 'data'
|
||||
el.attrib.pop('id', None)
|
||||
|
||||
if self.module.startswith('theme_'):
|
||||
model = 'theme.ir.ui.view'
|
||||
else:
|
||||
model = 'ir.ui.view'
|
||||
|
||||
record_attrs = {
|
||||
'id': tpl_id,
|
||||
'model': 'ir.ui.view',
|
||||
'model': model,
|
||||
}
|
||||
for att in ['forcecreate', 'context']:
|
||||
if att in el.attrib:
|
||||
|
||||
Reference in New Issue
Block a user