Files
odoo_source/addons/website_theme_install/models/theme_models.py
T
Sébastien Theys 96ef4885a7 [FIX] website_theme_install: fix theme install/upgrade/remove
The goal of this commit is to handle 3 cases:
===============================================================================

- Correct loading of downstream themes for existing themes at (auto)install

  eg. If we have `theme_A` loaded on a website and we install `website_sale`,
    then `theme_A_sale` must also be loaded automatically on that website.

- Allow migration of every theme/website from the command line -u

    When there is such an explicit upgrade, the Odoo framework was already
    handling the upgrade of the modules, but it did not take care of reloading
    the appropriate themes on the websites using them.

- Allow upgrade of only one theme from the interface

    When using the UI to upgrade a theme, we want the user to be able to upgrade
    only the current website and not any other. This will upgrade the modules
    of all themes in the stream, but reload them only for the current website.

===============================================================================

- Move install/upgrade logic into the `write` method to automatically take
    advantage of base features such as correct order of dependencies.

- Create methods to correctly compute the dependency stream of a theme:
    upstream, downstream, and both ways.

    Upstream: limited to modules with name starting with `theme_`.
    Downstream: limited to modules with name starting with current theme name.

- Change `_update_records` method to better look for dependencies

- Change `button_choose_theme`:
    - Always upgrade the stream before installing a new theme.

- Move `_convert_to_base_model` logic into `theme_models.py`

================================================================================

There are a lot of flows to take into account, some of which may be combined
with others.

- Dependencies such as website_sale or website_blog can be:
    - already installed when loading a theme
    - (auto)installed later when the theme is already in use

  theme_A_sale and/or theme_A_blog has to be loaded on every website using
  theme_A as soon as it is needed.

  Not all themes have such dependencies.

- A theme can have other themes as dependency.
    - The proper dependency chain must be taken into account when installing
      or upgrading.

- Some themes do not have any dependency at all (theme_default, theme_bootswatch).

- Different websites can use the same theme.
    - All of them have to be upgraded when using -u
    - Only the current website must be upgraded when doing it from the UI

- Different websites can use different themes, including intermediary themes.
    eg. - website1 may use theme_A
        - website2 may use theme_B (depending on theme_A)

  Moreover, theme_A and theme_B might have downstream themes such as
  theme_A_sale and theme_B_sale (which depends on theme_A_sale).

In that case it is important when manipulating theme_B to also take into account
the relation it has with theme_A_sale even though theme_A_sale doesn't start
with the same name as theme_B and it is not in the direct stream of theme_B.

Authored by @seb-odoo with contributions from @dbh-odoo and @JKE-be
and precious help from @rde-odoo

PR: #27511

closes odoo/odoo#27838
2018-10-15 10:45:36 +00:00

202 lines
6.6 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models
from odoo.modules.module import get_resource_from_path
_logger = logging.getLogger(__name__)
class ThemeView(models.Model):
_name = 'theme.ir.ui.view'
_description = 'Theme 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)
mode = fields.Selection([('primary', "Base view"), ('extension', "Extension View")])
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)
# TODO master add missing field: customize_show
@api.multi
def _convert_to_base_model(self, website, **kwargs):
self.ensure_one()
inherit = self.inherit_id
if self.inherit_id and self.inherit_id._name == 'theme.ir.ui.view':
inherit = self.inherit_id.with_context(active_test=False).copy_ids.filtered(lambda x: x.website_id == website)
if not inherit:
# inherit_id not yet created, add to the queue
return False
new_view = {
'type': self.type or 'qweb',
'name': self.name,
'arch': self.arch,
'key': self.key,
'inherit_id': inherit and inherit.id,
'arch_fs': self.arch_fs,
'priority': self.priority,
'active': self.active,
'theme_template_id': self.id,
'website_id': website.id,
}
if self.mode: # if not provided, it will be computed automatically (if inherit_id or not)
new_view['mode'] = self.mode
return new_view
class ThemeAttachment(models.Model):
_name = 'theme.ir.attachment'
_description = 'Theme Attachments'
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)
# TODO in master: add missing field: datas_fname
@api.multi
def _convert_to_base_model(self, website, **kwargs):
self.ensure_one()
new_attach = {
'key': self.key,
'public': True,
'res_model': 'ir.ui.view',
'type': 'url',
'name': self.name,
'url': self.url,
'website_id': website.id,
'theme_template_id': self.id,
}
return new_attach
class ThemeMenu(models.Model):
_name = 'theme.website.menu'
_description = 'Website Theme 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)
@api.multi
def _convert_to_base_model(self, website, **kwargs):
self.ensure_one()
page_id = self.page_id.copy_ids.filtered(lambda x: x.website_id == website)
parent_id = self.copy_ids.filtered(lambda x: x.website_id == website)
new_menu = {
'name': self.name,
'url': self.url,
'page_id': page_id,
'new_window': self.new_window,
'sequence': self.sequence,
'parent_id': parent_id,
'theme_template_id': self.id,
}
return new_menu
class ThemePage(models.Model):
_name = 'theme.website.page'
_description = 'Website Theme 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', 'Page using a copy of me', copy=False, readonly=True)
@api.multi
def _convert_to_base_model(self, website, **kwargs):
self.ensure_one()
view_id = self.view_id.copy_ids.filtered(lambda x: x.website_id == website)
if not view_id:
# inherit_id not yet created, add to the queue
return False
new_page = {
'url': self.url,
'view_id': view_id.id,
'website_indexed': self.website_indexed,
'theme_template_id': self.id,
}
return new_page
class Theme(models.AbstractModel):
_name = 'theme.utils'
_description = 'Theme Utils'
_auto = False
def _post_copy(self, mod, website=False):
# deprecated: to remove in master
if not website: # remove optional website in master
website = self.env['website'].get_current_website()
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.with_context(website_id=website.id), theme_post_copy)
return method(mod)
return False
@api.model
def _toggle_view(self, xml_id, active):
obj = self.env.ref(xml_id)
if obj._name == 'theme.ir.ui.view':
website = self.env['website'].get_current_website()
obj = obj.with_context(active_test=False)
obj = obj.copy_ids.filtered(lambda x: x.website_id == website)
obj.write({'active': active})
@api.model
def enable_view(self, xml_id):
self._toggle_view(xml_id, True)
@api.model
def disable_view(self, xml_id):
self._toggle_view(xml_id, False)
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')