diff options
Diffstat (limited to 'addons/website/models/ir_ui_view.py')
| -rw-r--r-- | addons/website/models/ir_ui_view.py | 152 |
1 files changed, 149 insertions, 3 deletions
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 9a793b84fe6..8a8e5a3f8f3 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -4,9 +4,10 @@ import logging from itertools import groupby -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo import tools from odoo.addons.http_routing.models.ir_http import url_for +from odoo.osv import expression from odoo.http import request from odoo.tools import pycompat @@ -22,6 +23,7 @@ class View(models.Model): website_id = fields.Many2one('website', ondelete='cascade', string="Website") page_ids = fields.One2many('website.page', compute='_compute_page_ids', store=False) 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') @api.one def _compute_first_page_id(self): @@ -34,12 +36,112 @@ class View(models.Model): ) @api.multi + def write(self, vals): + '''COW for ir.ui.view. This way editing websites does not impact other + websites. Also this way newly created websites will only + contain the default views. + ''' + 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}) + view._create_website_specific_pages_for_view(new_website_specific_view, + view.env['website'].browse(current_website_id)) + + # trigger COW on inheriting views + for inherit_child in view.inherit_children_ids: + inherit_child.write({'inherit_id': new_website_specific_view.id}) + + new_website_specific_view.write(vals) + else: + super(View, view).write(vals) + else: + super(View, self).write(vals) + + return True + + @api.multi def unlink(self): + '''This implements COU (copy-on-unlink). When deleting a generic page + website-specific pages will be created so only the current + website is affected. + ''' + current_website_id = self._context.get('website_id') + + if current_website_id and not self._context.get('no_cow'): + for view in self.filtered(lambda view: not view.website_id): + for website in self.env['website'].search([('id', '!=', current_website_id)]): + # reuse the COW mechanism to create + # website-specific copies, it will take + # care of creating pages and menus. + view.with_context(website_id=website.id).write({'key': '%s [website %s]' % (view.key, website.id)}) + + self |= self.with_context(active_test=False).search([('key', 'in', self.filtered('key').mapped('key'))]) result = super(View, self).unlink() 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({ + 'view_id': new_view.id, + }) + for menu in page.menu_ids: + # trigger COW + menu.write({'page_id': new_page.id}) + + @api.model + def get_related_views(self, key, bundles=False): + '''Make this only return most specific views for website.''' + # get_related_views can be called through website=False routes + # (e.g. /web_editor/get_assets_editor_resources), so website + # dispatch_parameters may not be added. Manually set + # website_id. + self = self.with_context(website_id=self.env['website'].get_current_website().id) + views = super(View, self).get_related_views(key, bundles=bundles) + current_website_id = self._context.get('website_id') + most_specific_views = self.env['ir.ui.view'] + + if not current_website_id: + return views + + for view in views: + if view.website_id and view.website_id.id == current_website_id: + most_specific_views |= view + elif not view.website_id and not any(view.key == view2.key and view2.website_id and view2.website_id.id == current_website_id for view2 in views): + most_specific_views |= view + + return most_specific_views + + @api.multi def _sort_suitability_key(self): """ Key function to sort views by descending suitability Suitability of a view is defined as follow: @@ -80,15 +182,47 @@ class View(models.Model): return view_id @api.model + def _get_inheriting_views_arch_website(self, view_id): + return self.env['website'].browse(self._context.get('website_id')) + + @api.model def _get_inheriting_views_arch_domain(self, view_id, model): domain = super(View, self)._get_inheriting_views_arch_domain(view_id, model) - return ['|', ('website_id', '=', False), ('website_id', '=', self.env.context.get('website_id'))] + domain + current_website = self._get_inheriting_views_arch_website(view_id) + + website_views_domain = [('theme_id', '=', False), '|', ('website_id', '=', False), ('website_id', '=', current_website.id)] + # when rendering for the website we have to include inactive views + # 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]) + + @api.model + def get_inheriting_views_arch(self, view_id, model): + if not self._context.get('website_id'): + return super(View, self).get_inheriting_views_arch(view_id, model) + + inheriting_views = super(View, self.with_context(active_test=False)).get_inheriting_views_arch(view_id, model) + + # prefer inactive website-specific views over active generic ones + inheriting_views = self.browse([view[1] for view in inheriting_views]).filter_duplicate().filtered('active') + + return [(view.arch, view.id) for view in inheriting_views] @api.model @tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',)) def get_view_id(self, xml_id): if 'website_id' in self._context and not isinstance(xml_id, pycompat.integer_types): - domain = [('key', '=', xml_id), '|', ('website_id', '=', self._context['website_id']), ('website_id', '=', False)] + 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), '|', ('website_id', '=', False), ('website_id', '=', current_website.id)] + domain = expression.AND([expression.OR([theme_views_domain, website_views_domain]), key_domain]) + view = self.search(domain, order='website_id', limit=1) if not view: _logger.warning("Could not find view object with xml_id '%s'", xml_id) @@ -125,6 +259,7 @@ class View(models.Model): qcontext = super(View, self)._prepare_qcontext() if request and getattr(request, 'is_frontend', False): + Website = self.env['website'] editable = request.website.is_publisher() translatable = editable and self._context.get('lang') != request.env['ir.http']._get_default_lang().code editable = not translatable and editable @@ -132,6 +267,17 @@ class View(models.Model): if 'main_object' not in qcontext: qcontext['main_object'] = self + domain_based_info = {'website_id': '', 'name': _('Domain Based')} + force_website_id = request.session.get('force_website_id', False) + if force_website_id: + selected_website = Website.browse(force_website_id) + qcontext['multi_website_selected_website'] = {'website_id': selected_website.id, 'name': selected_website.name} + else: + qcontext['multi_website_selected_website'] = domain_based_info + + qcontext['multi_website_websites'] = [{'website_id': website.id, 'name': website.name} for website in Website.search([])] + qcontext['multi_website_websites'] += [domain_based_info] + qcontext.update(dict( self._context.copy(), website=request.website, |
