diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py index 117860c5..0f747afb 100644 --- a/addons/website/__manifest__.py +++ b/addons/website/__manifest__.py @@ -10,7 +10,8 @@ 'website': 'https://flectrahq.com/page/website-builder', 'version': '1.0', 'description': "", - 'depends': ['web', 'web_editor', 'web_planner', 'http_routing', 'portal'], + 'depends': ['web', 'web_editor', 'web_planner', + 'http_routing', 'portal', 'base_automation'], 'installable': True, 'data': [ 'data/website_data.xml', @@ -23,6 +24,7 @@ 'views/res_config_settings_views.xml', 'views/ir_actions_views.xml', 'views/res_company_views.xml', + 'views/module_view.xml', 'wizard/base_language_install_views.xml', 'data/web_planner_data.xml', ], diff --git a/addons/website/controllers/backend.py b/addons/website/controllers/backend.py index b1e11ba6..5081b88a 100644 --- a/addons/website/controllers/backend.py +++ b/addons/website/controllers/backend.py @@ -6,9 +6,13 @@ from flectra.http import request class WebsiteBackend(http.Controller): - - @http.route('/website/fetch_dashboard_data', type="json", auth='user') - def fetch_dashboard_data(self, date_from, date_to): + + @http.route('/website/fetch_dashboard_data', type="json", auth='user', + website=True) + def fetch_dashboard_data(self, date_from, date_to, website_id=None): + if not website_id: + website_id = request.website.id + has_group_system = request.env.user.has_group('base.group_system') has_group_designer = request.env.user.has_group('website.group_website_designer') if has_group_system: @@ -36,6 +40,10 @@ class WebsiteBackend(http.Controller): ga_client_id=config.google_management_client_id or '', # void string instead of stringified False ga_analytics_key=config.google_analytics_key or '', # void string instead of stringified False ) + dashboard_data['website_ids'] = request.env['website'].search_read() + dashboard_data['website'] = request.env['website'].browse( + website_id).domain + dashboard_data['current_website'] = request.website.domain return dashboard_data @http.route('/website/dashboard/set_ga_data', type='json', auth='user') diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 9a0540d0..445460e7 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -272,7 +272,10 @@ class Website(Home): @http.route("/website/get_switchable_related_views", type="json", auth="user", website=True) def get_switchable_related_views(self, key): - views = request.env["ir.ui.view"].get_related_views(key, bundles=False).filtered(lambda v: v.customize_show) + views = request.env["ir.ui.view"].get_related_views( + key, bundles=False).filtered( + lambda v: v.customize_show and ( + v.website_id if v.website_id == request.website else None)) return views.read(['name', 'id', 'key', 'xml_id', 'arch', 'active', 'inherit_id']) @http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True) diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index 760d711a..077dc4e1 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -6,6 +6,7 @@ localhost + True @@ -35,6 +36,7 @@ Home qweb website.homepage + @@ -48,6 +50,7 @@ True / + @@ -91,6 +94,7 @@ /contactus True + @@ -147,6 +151,7 @@ True /aboutus + @@ -555,5 +560,15 @@ /website/static/src/img/snippets_demo/s_team_member_4.png + + + Multi Website: multi website rule on create + + code + model.multi_website_view_rule() + on_create + + + diff --git a/addons/website/data/website_demo.xml b/addons/website/data/website_demo.xml index 7d3c68ce..d949d79a 100644 --- a/addons/website/data/website_demo.xml +++ b/addons/website/data/website_demo.xml @@ -126,122 +126,6 @@ response = request.render("website.template_partner_comment", { Website localhost - - Website 0.0.0.0 - 0.0.0.0 - - - Home - qweb - website2.homepage - - - - - - - - - - - - - - Homepage 0.0.0.0 - Click to customize this text - - Contact us - - - - - - - - - - - - - - - - - - - - - True - / - - - - - - Contact Us - qweb - website2.contactus - - - - - - - Contact us - - - - - Contact us about anything related to our company or services. - We'll do our best to get back to you as soon as possible. - - - - Send us an email - - - - - - - - - - - - - - - True - /contactus - - - - - - - - - - Top Menu - - - - Home - / - - 10 - - - - - Contact us - /contactus - - 60 - - - diff --git a/addons/website/models/__init__.py b/addons/website/models/__init__.py index b90ecd64..44f48e8f 100644 --- a/addons/website/models/__init__.py +++ b/addons/website/models/__init__.py @@ -6,10 +6,12 @@ from . import ir_attachment from . import ir_http from . import ir_qweb from . import website +from . import ir_model from . import ir_ui_view from . import res_company from . import res_partner from . import web_planner +from . import module from . import res_config_settings from . import ir_model_fields from . import ir_model diff --git a/addons/website/models/ir_model.py b/addons/website/models/ir_model.py index e0cd8f18..4232256b 100644 --- a/addons/website/models/ir_model.py +++ b/addons/website/models/ir_model.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details -from flectra import api, models +import logging +from flectra import api, fields, models, _ + +_logger = logging.getLogger(__name__) # This is a nasty hack, targeted for V11 only @@ -13,3 +16,171 @@ class IrModel(models.Model): self.env.cr.execute( "DELETE FROM ir_model_fields WHERE name='website_id'") return super(IrModel, self).unlink() + + +class IrModelData(models.Model): + _inherit = 'ir.model.data' + + # Overriding Method + @api.model + def _update(self, model, module, values, xml_id=False, store=True, + noupdate=False, mode='init', res_id=False): + # records created during module install should not display the messages of OpenChatter + self = self.with_context(install_mode=True) + current_module = module + + if xml_id and ('.' in xml_id): + assert len(xml_id.split('.')) == 2, _( + "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % xml_id + module, xml_id = xml_id.split('.') + + action = self.browse() + record = self.env[model].browse(res_id) + + if xml_id: + self._cr.execute("""SELECT imd.id, imd.res_id, md.id, imd.model, imd.noupdate + FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id) + WHERE imd.module=%%s AND imd.name=%%s""" % record._table, + (module, xml_id)) + results = self._cr.fetchall() + for imd_id, imd_res_id, real_id, imd_model, imd_noupdate in results: + # In update mode, do not update a record if it's ir.model.data is flagged as noupdate + if mode == 'update' and imd_noupdate: + return imd_res_id + if not real_id: + self.clear_caches() + self._cr.execute('DELETE FROM ir_model_data WHERE id=%s', + (imd_id,)) + record = record.browse() + else: + assert model == imd_model, "External ID conflict, %s already refers to a `%s` record," \ + " you can't define a `%s` record with this ID." % ( + xml_id, imd_model, model) + action = self.browse(imd_id) + record = record.browse(imd_res_id) + + if action and record: + # Set ``is_cloned`` to ``False`` + if values.get('type') == 'qweb' and not values.get('is_cloned'): + values.update({'is_cloned': False}) + + record.write(values) + action.sudo().write({'date_update': fields.Datetime.now()}) + + elif record: + record.write(values) + if xml_id: + for parent_model, parent_field in record._inherits.items(): + self.sudo().create({ + 'name': xml_id + '_' + parent_model.replace('.', '_'), + 'model': parent_model, + 'module': module, + 'res_id': record[parent_field].id, + 'noupdate': noupdate, + }) + self.sudo().create({ + 'name': xml_id, + 'model': model, + 'module': module, + 'res_id': record.id, + 'noupdate': noupdate, + }) + + elif mode == 'init' or (mode == 'update' and xml_id): + existing_parents = set() # {parent_model, ...} + if xml_id: + for parent_model, parent_field in record._inherits.items(): + xid = self.sudo().search([ + ('module', '=', module), + ('name', '=', + xml_id + '_' + parent_model.replace('.', '_')), + ]) + # XML ID found in the database, try to recover an existing record + if xid: + parent = self.env[xid.model].browse(xid.res_id) + if parent.exists(): + existing_parents.add(xid.model) + values[parent_field] = parent.id + else: + xid.unlink() + + record = record.create(values) + if xml_id: + # To add an external identifiers to all inherits model + inherit_models = [record] + while inherit_models: + current_model = inherit_models.pop() + for parent_model_name, parent_field in current_model._inherits.items(): + inherit_models.append(self.env[parent_model_name]) + if parent_model_name in existing_parents: + continue + self.sudo().create({ + 'name': xml_id + '_' + parent_model_name.replace( + '.', '_'), + 'model': parent_model_name, + 'module': module, + 'res_id': record[parent_field].id, + 'noupdate': noupdate, + }) + existing_parents.add(parent_model_name) + self.sudo().create({ + 'name': xml_id, + 'model': model, + 'module': module, + 'res_id': record.id, + 'noupdate': noupdate + }) + if current_module and module != current_module: + _logger.warning( + "Creating the ir.model.data %s in module %s instead of %s.", + xml_id, module, current_module) + + if xml_id and record: + self.loads[(module, xml_id)] = (model, record.id) + for parent_model, parent_field in record._inherits.items(): + parent_xml_id = xml_id + '_' + parent_model.replace('.', '_') + self.loads[(module, parent_xml_id)] = ( + parent_model, record[parent_field].id) + + return record.id + + @api.model + def _process_end(self, modules): + super(IrModelData, self)._process_end(modules) + ir_ui_view = self.env['ir.ui.view'] + ir_model_data = self.env['ir.model.data'] + + default_website = self.env['website'].search([ + ('is_default_website', '=', True)]) + for cus_view in ir_ui_view.search([('customize_show', '=', True), + ('website_id', '=', False), + '|', ('active', '=', False), + ('active', '=', True)]): + if default_website: + cus_view.write({'website_id': default_website.id}) + + for website in self.env['website'].search( + [('is_default_website', '=', False)]): + for view in ir_ui_view.search( + [('website_id', '=', default_website.id), + ('customize_show', '=', True), ('is_cloned', '=', False), + '|', ('active', '=', False), ('active', '=', True)]): + if not ir_ui_view.search( + [('key', '=', view.key + + '_' + website.website_code), + '|', ('active', '=', False), ('active', '=', True)]): + new_cus_view = view.copy({ + 'is_cloned': True, + 'key': view.key + '_' + website.website_code, + 'website_id': website.id + }) + model_data_id = ir_model_data.create({ + 'model': view.model_data_id.model, + 'name': view.model_data_id.name + + '_' + website.website_code, + 'res_id': new_cus_view.id, + 'module': view.model_data_id.module, + }) + new_cus_view.write({ + 'model_data_id': model_data_id + }) diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 1605a869..8a67eec7 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -4,7 +4,7 @@ import logging from itertools import groupby -from flectra import api, fields, models +from flectra import api, fields, models, _ from flectra import tools from flectra.addons.http_routing.models.ir_http import url_for from flectra.http import request @@ -21,6 +21,11 @@ class View(models.Model): customize_show = fields.Boolean("Show As Optional Inherit", default=False) website_id = fields.Many2one('website', ondelete='cascade', string="Website") page_ids = fields.One2many('website.page', compute='_compute_page_ids', store=False) + is_cloned = fields.Boolean(string='Cloned', copy=False, default=False, + help="This view is cloned" + "(not present physically in file system) " + "from default website's view for " + "supporting multi-website feature.") @api.one def _compute_page_ids(self): @@ -157,3 +162,102 @@ class View(models.Model): 'url': '/website/pages', 'target': 'self', } + + # Multi Website: Automated Action On Create Rule + ################################################## + # If views are manually created for default website, + # then it'll automatically cloned for other websites. + # + # As this method is also called when new website is created. + # Because at the time of website creation ``Home`` page will be cloned, + # So, this method will automatically triggered to + # cloned all customize view(s). + @api.model + def multi_website_view_rule(self): + default_website = self.env['website'].search([ + ('is_default_website', '=', True)]) + ir_model_data = self.env['ir.model.data'] + for website in self.env['website'].search( + [('is_default_website', '=', False)]): + for cus_view in self.search( + [('website_id', '=', default_website.id), + ('customize_show', '=', True), + ('is_cloned', '=', False), + '|', ('active', '=', False), ('active', '=', True)]): + if not self.search( + [('key', '=', cus_view.key + + '_' + website.website_code), + '|', ('active', '=', False), ('active', '=', True)]): + new_cus_view = cus_view.copy( + {'is_cloned': True, + 'key': cus_view.key + '_' + website.website_code, + 'website_id': website.id + }) + model_data_id = ir_model_data.create({ + 'model': cus_view.model_data_id.model, + 'name': cus_view.model_data_id.name + + '_' + website.website_code, + 'res_id': new_cus_view.id, + 'module': cus_view.model_data_id.module, + }) + new_cus_view.write({ + 'model_data_id': model_data_id + }) + + # Add the website_id to each customize QWeb view(s) at the time + # of creation of new customize QWeb view(s). + @api.model + def create(self, values): + # For Theme's View(s) + if values.get('key') and values.get('type') == 'qweb' and \ + self.env.context.get('install_mode_data'): + module_name = self.env.context['install_mode_data']['module'] + module_obj = self.env['ir.module.module'].sudo().search( + [('name', '=', module_name)]) + if module_obj and \ + (module_obj.category_id.name == 'Theme' + or (module_obj.category_id.parent_id + and module_obj.category_id.parent_id.name + == 'Theme')): + values.update({ + 'website_id': module_obj.website_ids.id, + }) + return super(View, self).create(self._compute_defaults(values)) + + # Keep other website's view as it is when run server using -i/-u + # As other website's views are not present anywhere in FS(file system). + # So, once those are created/cloned from default website, + # they can be changed/updated via debug mode only(ir.ui.view) + # Menu: Settings/Technical/User Interface/Views + # + # Scenario 1: + # ----------- + # For Delete those views, Manually set ``is_cloned`` field to ``False`` + # + # Scenario 2: + # ----------- + # If you write the code for already cloned views in FS(file system)/Module + # to upgrade/update those views, then at the time of module update + # process that cloned views id are found in FS(file system)/Module, + # So in those particular views ``is_cloned`` will automatically + # set to ``False`` (Definitely it'll be done from another method!!), + # because now those views are not anymore cloned, + # now they are physically present!! + @api.multi + def unlink(self): + for view in self: + if view.is_cloned: + # Do not delete cloned view(s) + # ---------------- + # 'View(s) that you want delete are ' + # 'cloned view(s).\n' + # 'Cloned view(s) are automatically created ' + # 'for supporting multi website feature.\n' + # 'If you still want to delete this view(s) ' + # 'then first Uncheck(set to False) the ' + # 'cloned field.\n' + # 'By deleting cloned view(s) multi website ' + # 'will not work properly.\n' + # 'So, Be sure before deleting view(s).' + return True + return super(View, self).unlink() diff --git a/addons/website/models/module.py b/addons/website/models/module.py new file mode 100644 index 00000000..d8a8aca3 --- /dev/null +++ b/addons/website/models/module.py @@ -0,0 +1,24 @@ +from flectra import api, fields, models, _ +from flectra.exceptions import Warning + + +class IrModuleModule(models.Model): + _inherit = 'ir.module.module' + + website_ids = fields.One2many('website', 'website_theme_id', + string='Website', readonly=True) + + @api.multi + def button_immediate_install(self): + for app in self: + if app.category_id and ( + app.category_id.name == 'Theme' + or app.category_id.parent_id.name == 'Theme') and \ + not app.website_ids: + raise Warning(_('You are trying to install Theme module!\n' + 'As Flectra will support multi-website so, ' + 'please install theme in specific website.\n' + 'Go to...\n' + '- Menu: Website/Configuration/Settings\n' + '- Select website & its theme & Save it.')) + return super(IrModuleModule, self).button_immediate_install() diff --git a/addons/website/models/res_config_settings.py b/addons/website/models/res_config_settings.py index 7831f406..1d57097c 100644 --- a/addons/website/models/res_config_settings.py +++ b/addons/website/models/res_config_settings.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. +import logging from ast import literal_eval -from flectra import api, fields, models -from flectra.exceptions import AccessDenied +from flectra import api, fields, models, _ +from flectra.exceptions import AccessDenied, AccessError, Warning + +_logger = logging.getLogger(__name__) class ResConfigSettings(models.TransientModel): @@ -40,6 +43,25 @@ class ResConfigSettings(models.TransientModel): ('b2b', 'On invitation (B2B)'), ('b2c', 'Free sign up (B2C)'), ], string='Customer Account') + website_theme_id = fields.Many2one( + 'ir.module.module', string='Theme', + related='website_id.website_theme_id', + help='Choose theme for current website.') + + # Unique theme per Website for now ;) + # @todo Flectra: + # Do enable support for same theme in multiple website + @api.onchange('website_theme_id') + def onchange_theme_id(self): + if (self.website_id.id not in self.website_theme_id.website_ids.ids) \ + and (self.website_theme_id and + self.website_theme_id.website_ids): + warning = { + 'title': 'Warning', + 'message': _('Selected theme is already active in ' + 'different website.')} + self.website_theme_id = False + return {'warning': warning} @api.onchange('has_google_analytics') def onchange_has_google_analytics(self): @@ -96,3 +118,121 @@ class ResConfigSettings(models.TransientModel): action['res_id'] = literal_eval(self.env['ir.config_parameter'].sudo().get_param('auth_signup.template_user_id', 'False')) action['views'] = [[self.env.ref('base.view_users_form').id, 'form']] return action + + @api.model + def _get_classified_fields(self): + res = super(ResConfigSettings, self)._get_classified_fields() + if 'website_theme_id' in dir(self): + ir_module = self.env['ir.module.module'] + install_theme_lst = [] + uninstall_theme_lst = [] + install_theme_lst.append(self.website_theme_id) + theme_un = ir_module.sudo().search( + ['|', ('category_id.name', '=', 'Theme'), + ('category_id.parent_id.name', '=', 'Theme')] + ) + for theme in theme_un: + if not theme.website_ids and len(theme.website_ids.ids) < 1: + uninstall_theme_lst.append(theme) + res.update({ + 'install_theme': install_theme_lst, + 'uninstall_theme': uninstall_theme_lst + }) + return res + + # Overriding Method + @api.multi + def execute(self): + self.ensure_one() + + # Multi Website: Do not allow more than 1 website as default website + if self.env['website'].search_count( + [('is_default_website', '=', True)]) > 1: + raise Warning( + _('You can define only one website as default one.\n' + 'More than one websites are not allowed ' + 'as default website.')) + + if not self.env.user._is_superuser() and not \ + self.env.user.has_group('base.group_system'): + raise AccessError(_("Only administrators can change the settings")) + + self = self.with_context(active_test=False) + classified = self._get_classified_fields() + + # default values fields + IrDefault = self.env['ir.default'].sudo() + for name, model, field in classified['default']: + if isinstance(self[name], models.BaseModel): + if self._fields[name].type == 'many2one': + value = self[name].id + else: + value = self[name].ids + else: + value = self[name] + IrDefault.set(model, field, value) + + # group fields: modify group / implied groups + for name, groups, implied_group in classified['group']: + if self[name]: + groups.write({'implied_ids': [(4, implied_group.id)]}) + else: + groups.write({'implied_ids': [(3, implied_group.id)]}) + implied_group.write({'users': [(3, user.id) for user in + groups.mapped('users')]}) + + # other fields: execute method 'set_values' + # Methods that start with `set_` are now deprecated + for method in dir(self): + if method.startswith('set_') and method is not 'set_values': + _logger.warning(_('Methods that start with `set_` ' + 'are deprecated. Override `set_values` ' + 'instead (Method %s)') % method) + self.set_values() + + # module fields: install/uninstall the selected modules + to_install = [] + to_upgrade = self.env['ir.module.module'] + to_uninstall_modules = self.env['ir.module.module'] + lm = len('module_') + for name, module in classified['module']: + if self[name]: + to_install.append((name[lm:], module)) + else: + if module and module.state in ('installed', 'to upgrade'): + to_uninstall_modules += module + + if 'install_theme' in classified and 'uninstall_theme' in classified: + for theme in classified['install_theme']: + if theme: + to_install.append((theme.name, theme)) + if theme.state == 'installed': + to_upgrade += theme + for theme in classified['uninstall_theme']: + if theme and theme.state in ('installed', 'to upgrade'): + to_uninstall_modules += theme + + if to_uninstall_modules: + to_uninstall_modules.button_immediate_uninstall() + + if to_upgrade: + to_upgrade.button_immediate_upgrade() + + self._install_modules(to_install) + + if to_install or to_uninstall_modules: + # After the uninstall/install calls, the registry and environments + # are no longer valid. So we reset the environment. + self.env.reset() + self = self.env()[self._name] + + # pylint: disable=next-method-called + config = self.env['res.config'].next() or {} + if config.get('type') not in ('ir.actions.act_window_close',): + return config + + # force client-side reload (update user menu and current view) + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + } diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 22c44b9c..690770f0 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -5,6 +5,7 @@ import inspect import logging import hashlib import re +from uuid import uuid4 from werkzeug import urls from werkzeug.exceptions import NotFound @@ -17,6 +18,7 @@ from flectra.tools import pycompat from flectra.http import request from flectra.osv.expression import FALSE_DOMAIN from flectra.tools.translate import _ +from flectra.exceptions import Warning logger = logging.getLogger(__name__) @@ -71,6 +73,17 @@ 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.") + is_default_website = fields.Boolean(string='Default Website', readonly=1) + website_code = fields.Char(string='Website Code', readonly=1, + default=lambda self: uuid4().hex[:8], + help='Unique code per website.') + website_theme_id = fields.Many2one('ir.module.module', string='Theme', + help='Choose theme for current ' + 'website.') + + _sql_constraints = [ + ('domain_uniq', 'unique(domain)', 'Domain name already exists !'), + ] @api.multi def _compute_menu(self): @@ -82,14 +95,95 @@ class Website(models.Model): def noop(self, *args, **kwargs): pass + # ---------------------------------------------------------- + # Multi Website + # ---------------------------------------------------------- @api.multi def write(self, values): self._get_languages.clear_cache(self) + if values.get('website_code') or values.get('is_default_website'): + raise Warning(_('Unexpected bad things will happen!\n' + 'Changing website code or default website ' + 'can have unintended side effects.\n' + '- We will not updated your old views.\n' + '- If above action is not properly done ' + 'then it will break your current ' + 'multi website feature.')) return super(Website, self).write(values) - #---------------------------------------------------------- + @api.model + def create(self, values): + res = super(Website, self).create(values) + + default_website = self.env['website'].search([( + 'is_default_website', '=', True)]) + if not len(default_website) or len(default_website) > 1: + raise Warning(_('Either default website is not defined ' + 'or multiple default website is defined!!\n' + 'You can define only one website as ' + 'default website.')) + + website_menu = self.env['website.menu'] + ir_model_data = self.env['ir.model.data'] + + # Menu Entries: + # Clone top menu & home menu of default website for new website + top_menu = self.env.ref('website.main_menu', False) + home_menu = self.env.ref('website.menu_homepage', False) + new_home_menu = False + if top_menu and home_menu: + top_menu = website_menu.search([ + ('id', '=', self.env.ref('website.main_menu').id), + ('website_id', '=', default_website.id)]) + home_menu = website_menu.search([ + ('id', '=', self.env.ref('website.menu_homepage').id), + ('website_id', '=', default_website.id)]) + new_top_menu = top_menu.copy() + new_top_menu.write({ + 'website_id': res.id, + }) + new_home_menu = home_menu.copy() + new_home_menu.write({ + 'website_id': res.id, + 'parent_id': new_top_menu.id, + }) + + # Home Page & View Entry: + # Clone home page & view of default website for new website + home_page = self.env.ref('website.homepage_page', False) + if home_page and new_home_menu: + new_home_page = home_page.copy() + + new_home_page.view_id.write({ + 'name': home_page.view_id.name, + 'website_id': res.id, + 'key': home_page.view_id.key + '_' + res.website_code, + 'is_cloned': True, + }) + + home_model_data_id = ir_model_data.create({ + 'model': home_page.view_id.model_data_id.model, + 'name': home_page.view_id.model_data_id.name + + '_' + res.website_code, + 'res_id': new_home_page.view_id.id, + 'module': home_page.view_id.model_data_id.module, + }) + new_home_page.view_id.write({ + 'model_data_id': home_model_data_id + }) + + new_home_page.write({ + 'url': home_page.url, + 'view_id': new_home_page.view_id.id, + 'website_published': True, + 'website_ids': [(6, 0, [res.id])], + 'menu_ids': [(6, 0, [new_home_menu.id])], + }) + return res + + # ---------------------------------------------------------- # Page Management - #---------------------------------------------------------- + # ---------------------------------------------------------- @api.model def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None): """ Create a new website page, and assign it a xmlid based on the given one @@ -315,9 +409,9 @@ class Website(models.Model): except Exception: return False - #---------------------------------------------------------- + # ---------------------------------------------------------- # Languages - #---------------------------------------------------------- + # ---------------------------------------------------------- @api.multi def get_languages(self): @@ -362,9 +456,9 @@ class Website(models.Model): lang['hreflang'] = lang['short'] return langs - #---------------------------------------------------------- + # ---------------------------------------------------------- # Utilities - #---------------------------------------------------------- + # ---------------------------------------------------------- @api.model def get_current_website(self): diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js index 91b309f1..a3f8b3e3 100644 --- a/addons/website/static/src/js/backend/dashboard.js +++ b/addons/website/static/src/js/backend/dashboard.js @@ -30,6 +30,7 @@ var Dashboard = Widget.extend(ControlPanelMixin, { 'click .o_dashboard_action': 'on_dashboard_action', 'click .o_dashboard_action_form': 'on_dashboard_action_form', 'click .o_dashboard_hide_panel': 'on_dashboard_hide_panel', + 'click li.js_website_deshboard': 'js_website_deshboard', }, init: function(parent, context) { @@ -42,6 +43,8 @@ var Dashboard = Widget.extend(ControlPanelMixin, { this.dashboards_templates = ['website.dashboard_visits']; this.graphs = []; + this.is_bound = $.Deferred(); + this.dashboards_header = ['website.dashboard_header']; }, willStart: function() { @@ -58,21 +61,26 @@ var Dashboard = Widget.extend(ControlPanelMixin, { self.render_dashboards(); self.render_graphs(); self.$el.parent().addClass('oe_background_grey'); + self.bind_menu(); }); }, /** * Fetches dashboard data */ - fetch_data: function() { + fetch_data: function(website_id=null) { var self = this; return this._rpc({ route: '/website/fetch_dashboard_data', params: { date_from: this.date_from.year()+'-'+(this.date_from.month()+1)+'-'+this.date_from.date(), date_to: this.date_to.year()+'-'+(this.date_to.month()+1)+'-'+this.date_to.date(), + 'website_id': website_id, }, }).done(function(result) { + self.website_ids = result.website_ids; + self.website = result.website; + self.current_website = result.current_website; self.data = result; self.dashboards_data = result.dashboards; self.currency_id = result.currency_id; @@ -80,6 +88,80 @@ var Dashboard = Widget.extend(ControlPanelMixin, { }); }, + js_website_deshboard: function(ev){ + ev.preventDefault(); + var self = this; + $.when(this.fetch_data($(ev.target).data('website_id'))).then(function() { + self.$('.o_website_dashboard_content').empty(); + self.$('.o_dashboard_common').remove(); + self.render_dashboards(); + self.render_dashboards_header(); + self.render_graphs(); + }); + }, + + render_dashboards_header: function() { + var self = this; + _.each(this.dashboards_header, function(template) { + self.$('.o_website_dashboard_content').prepend(QWeb.render(template, {widget: self})); + }); + }, + + bind_menu: function() { + var self = this; + var lazyreflow = _.debounce(this.reflow.bind(this), 200); + core.bus.on('resize', this, function() { + if ($(window).width() < 768 ) { + lazyreflow('all_outside'); + } else { + lazyreflow(); + } + }); + core.bus.trigger('resize'); + + this.is_bound.resolve(); + }, + + reflow: function(behavior) { + var self = this; + var $more_container = this.$('#website_more_container').hide(); + var $more = this.$('#website_more'); + + $more.children('li').insertBefore($more_container); + + if (behavior === 'all_outside') { + // Show list of menu items + self.$el.show(); + this.$el.find('li').show(); + $more_container.hide(); + return; + } + + var $toplevel_items = this.$el.find('li').not($more_container).hide(); + + self.$el.show(); + $toplevel_items.each(function() { + var remaining_space = self.$el.find('div.navbar-collapse.collapse').width() - $more_container.outerWidth(); + self.$el.find('div.navbar-collapse.collapse ul.website_tab :visible').each + (function() { + if($(this).parent("ul").length){ + remaining_space -= $(this).width(); + } + }); + if ($(this).width() >= remaining_space) { + return false; + } + $(this).show(); + }); + $more.append($toplevel_items.filter(':hidden').show()); + $more_container.toggle(!!$more.children().length); + + var $toplevel = self.$el.children("ul.website_tab li:visible"); + if ($toplevel.length === 1) { + $toplevel.hide(); + } + }, + on_link_analytics_settings: function(ev) { ev.preventDefault(); diff --git a/addons/website/static/src/xml/website.backend.xml b/addons/website/static/src/xml/website.backend.xml index 7f3a3d15..478ddf52 100644 --- a/addons/website/static/src/xml/website.backend.xml +++ b/addons/website/static/src/xml/website.backend.xml @@ -7,9 +7,26 @@ - - - + + + + + + + + + + More + + + + + + + + @@ -17,7 +34,9 @@ - + Go to Website diff --git a/addons/website/views/module_view.xml b/addons/website/views/module_view.xml new file mode 100644 index 00000000..4e1f000b --- /dev/null +++ b/addons/website/views/module_view.xml @@ -0,0 +1,14 @@ + + + + ir.module.module.form.inherit + ir.module.module + + + + + + + + diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index 8b23edbe..2cc0650b 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -10,24 +10,30 @@ - Website - Name and favicon of your website + Name, favicon & theme of your website - + + + + + @@ -69,7 +75,7 @@ - How to get my Tracking ID @@ -96,7 +102,7 @@ - How to get my Client ID @@ -195,6 +201,24 @@ {'module' : 'website'} + + QWeb Views + ir.actions.act_window + ir.ui.view + + {'search_default_type': 'qweb', + 'search_default_group_website_id': True} + + + + + Websites + ir.actions.act_window + website + + {} + + + + + + + + diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index da15cf9b..1e4ba71f 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -21,8 +21,10 @@ - - + + + + @@ -57,6 +59,8 @@ + + @@ -212,6 +216,7 @@ + @@ -223,6 +228,20 @@ + + + ir.ui.view + + + + + + + + + + + Dashboard diff --git a/flectra/addons/base/module/module_data.xml b/flectra/addons/base/module/module_data.xml index b4f125bd..a822b785 100644 --- a/flectra/addons/base/module/module_data.xml +++ b/flectra/addons/base/module/module_data.xml @@ -157,7 +157,6 @@ Theme - 50
- Contact us -
Contact us about anything related to our company or services.
We'll do our best to get back to you as soon as possible.