[ADD] Multi Website Core Support 🎉

This commit is contained in:
Siddharth Bhalgami 2017-12-22 17:38:41 +05:30
parent 7bdf18df35
commit d00801d466
17 changed files with 769 additions and 144 deletions

View File

@ -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',
],

View File

@ -7,8 +7,12 @@ 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')

View File

@ -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)

View File

@ -6,6 +6,7 @@
<field name="domain">localhost</field>
<field name="company_id" ref="base.main_company"/>
<field name="user_id" ref="base.public_user"/>
<field name="is_default_website">True</field>
<field name="favicon" type="base64" file="web/static/src/img/favicon.ico"/>
</record>
@ -35,6 +36,7 @@
<field name="name">Home</field>
<field name="type">qweb</field>
<field name="key">website.homepage</field>
<field name="website_id" ref="default_website"/>
<field name="arch" type="xml">
<t name="Home" priority="29" t-name="website.homepage">
<t t-call="website.layout">
@ -48,6 +50,7 @@
<field name="website_published">True</field>
<field name="url">/</field>
<field name="view_id" ref="homepage" />
<field name="website_ids" eval="[(4, ref('default_website'))]" />
</record>
<record id="default_website" model="website">
<field name="homepage_id" ref="homepage_page" />
@ -91,6 +94,7 @@
<field name="url">/contactus</field>
<field name="website_published">True</field>
<field name="view_id" ref="contactus" />
<field name="website_ids" eval="[(4, ref('default_website'))]" />
</record>
<record id="aboutus" model="ir.ui.view">
@ -147,6 +151,7 @@
<field name="website_published">True</field>
<field name="url">/aboutus</field>
<field name="view_id" ref="aboutus" />
<field name="website_ids" eval="[(4, ref('default_website'))]" />
</record>
<record id="menu_homepage" model="website.menu">
@ -555,5 +560,15 @@
<field name="url">/website/static/src/img/snippets_demo/s_team_member_4.png</field>
</record>
<!-- Multi Website Automated Action Rule -->
<record id="multi_website_views_on_create" model="base.automation">
<field name="name">Multi Website: multi website rule on create</field>
<field name="model_id" ref="base.model_ir_ui_view"/>
<field name="state">code</field>
<field name="code">model.multi_website_view_rule()</field>
<field name="trigger">on_create</field>
<field name="active" eval="True"/>
</record>
</data>
</flectra>

View File

@ -126,122 +126,6 @@ response = request.render("website.template_partner_comment", {
<record id="default_website" model="website">
<field name="name">Website localhost</field>
</record>
<record id="website2" model="website">
<field name="name">Website 0.0.0.0</field>
<field name="domain">0.0.0.0</field>
</record>
<record id="website2_homepage" model="ir.ui.view">
<field name="name">Home</field>
<field name="type">qweb</field>
<field name="key">website2.homepage</field>
<field name="arch" type="xml">
<t name="Home" priority="29" t-name="website2.homepage">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<div class="carousel slide mb32" id="myCarousel0" style="height: 320px;">
<ol class="carousel-indicators hidden">
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
</ol>
<div class="carousel-inner">
<div class="item image_text oe_img_bg active" style="background-image: url(http://0.0.0.0:8069/web/image/website.s_background_image_11);">
<div class="container">
<div class="row content">
<div class="carousel-content col-md-6 col-sm-12">
<h2>Homepage 0.0.0.0</h2>
<h3>Click to customize this text</h3>
<p>
<a class="btn btn-success btn-large" href="/contactus">Contact us</a>
</p>
</div>
<span class="carousel-img col-md-6 hidden-sm hidden-xs"> </span>
</div>
</div>
</div>
</div>
<div class="carousel-control left hidden" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-left"/>
</div>
<div class="carousel-control right hidden" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-right"/>
</div>
</div>
</div>
</t>
</t>
</field>
</record>
<record id="website2_homepage_page" model="website.page">
<field name="website_published">True</field>
<field name="url">/</field>
<field name="view_id" ref="website2_homepage" />
<field name="website_ids" eval="[(4, ref('website2'))]" />
</record>
<record id="website2_contactus" model="ir.ui.view">
<field name="name">Contact Us</field>
<field name="type">qweb</field>
<field name="key">website2.contactus</field>
<field name="arch" type="xml">
<t name="Contact Us" t-name="website2.contactus">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
<h1>Contact us</h1>
<div class="row">
<div class="col-md-8">
<div class="oe_structure">
<div>
<p>Contact us about anything related to our company or services.</p>
<p>We'll do our best to get back to you as soon as possible.</p>
</div>
</div>
<div class="text-center mt64" name="mail_button">
<a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary" id="o_contact_mail">Send us an email</a>
</div>
</div>
<div class="col-md-4 mb32">
<t t-call="website.company_description"/>
</div>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</t>
</field>
</record>
<record id="website2_contactus_page" model="website.page">
<field name="website_published">True</field>
<field name="url">/contactus</field>
<field name="view_id" ref="website2_contactus" />
<field name="website_ids" eval="[(4, ref('website2'))]" />
</record>
<!-- Menu & Homepage -->
<record id="website2" model="website">
<field name="homepage_id" ref="website2_homepage_page" />
</record>
<record id="website2_main_menu" model="website.menu">
<field name="name">Top Menu</field>
<field name="website_id" ref="website2"/>
</record>
<record id="website2_menu_homepage" model="website.menu">
<field name="name">Home</field>
<field name="url">/</field>
<field name="parent_id" ref="website.website2_main_menu"/>
<field name="sequence" type="int">10</field>
<field name="website_id" ref="website2"/>
<field name="page_id" ref="website2_homepage_page" />
</record>
<record id="website2_menu_contactus" model="website.menu">
<field name="name">Contact us</field>
<field name="url">/contactus</field>
<field name="parent_id" ref="website.website2_main_menu"/>
<field name="sequence" type="int">60</field>
<field name="website_id" ref="website2"/>
<field name="page_id" ref="website2_contactus_page" />
</record>
</data>
</flectra>

View File

@ -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

View File

@ -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
})

View File

@ -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()

View File

@ -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()

View File

@ -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',
}

View File

@ -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):

View File

@ -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();

View File

@ -7,9 +7,26 @@
<t t-name="website.WebsiteDashboardMain">
<div class="o_dashboards">
<div class="container-fluid o_website_dashboard">
<t t-call="website.dashboard_header"/>
<div class="o_website_dashboard_content"/>
<div class="o_dashboards">
<div class="navbar-collapse collapse" style="padding:0;">
<ul class="nav nav-tabs website_tab">
<li t-foreach="widget.website_ids" t-as="website"
t-attf-class="js_website_deshboard #{widget.current_website == website.domain and 'active' or ''}">
<a data-toggle="tab" t-att-data-website_id="website.id">
<t t-esc="website.name"/>
</a>
</li>
<li id="website_more_container" class="dropdown" style="display: none;">
<a href="#" class="dropdown-toggle"
data-toggle="dropdown">More <b class="caret"/></a>
<ul id="website_more" class="dropdown-menu"/>
</li>
</ul>
</div>
<div class="container-fluid o_website_dashboard">
<t t-call="website.dashboard_header"/>
<div class="o_website_dashboard_content"/>
</div>
</div>
</div>
</t>
@ -17,7 +34,9 @@
<t t-name="website.dashboard_header">
<div class="row o_dashboard_common">
<div class="o_box">
<a href="#" class="o_box_item o_dashboard_action" name="website.action_website" title="Go to Website">
<a t-attf-href="#{widget.website}"
class="o_box_item o_dashboard_action"
name="website.action_website" title="Go to Website">
<div class="o_inner_box o_primary">
<i class="fa fa-globe fa-3x"></i><br/>
Go to Website

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="base_module_form_inherit" model="ir.ui.view">
<field name="name">ir.module.module.form.inherit</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.module_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='state']" position="after">
<field name="website_ids" widget="many2many_tags"
invisible="0"/>
</xpath>
</field>
</record>
</flectra>

View File

@ -10,24 +10,30 @@
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Website" string="Website" data-key="website" groups="website.group_website_designer">
<field name="website_id" invisible="1"/>
<h2>Website</h2>
<div class="row mt16 o_settings_container" id="webmaster_settings">
<div class="col-xs-12 col-md-6 o_setting_box" id="domain_setting">
<div class="o_setting_right_pane">
<label string="Website Title"/>
<div class="text-muted">
Name and favicon of your website
Name, favicon &amp; theme of your website
</div>
<div class="content-group">
<div class="row mt16">
<label class="col-md-3 o_light_label" string="Name"/>
<field name="website_name"/>
</div>
<div class="row">
<div class="row mt8">
<label class="col-md-3 o_light_label" for="favicon" />
<field name="favicon" widget="image" class="pull-left oe_avatar"/>
</div>
<div class="row">
<label class="col-md-3 o_light_label" for="website_theme_id"/>
<field name="website_theme_id"
options="{'no_create': True, 'no_open': True}"
domain="['|', ('category_id.name', '=', 'Theme'),
('category_id.parent_id.name', '=', 'Theme')]"/>
</div>
</div>
</div>
</div>
@ -69,7 +75,7 @@
</div>
</div>
<div attrs="{'invisible': [('has_google_analytics', '=', False)]}">
<a href="https://www.flectra.com/documentation/user/11.0/website/optimize/google_analytics.html"
<a href="https://www.flectrahq.com/documentation/user/11.0/website/optimize/google_analytics.html"
class="oe_link fa fa-arrow-right" target="_blank">
How to get my Tracking ID
</a>
@ -96,7 +102,7 @@
</div>
</div>
<div attrs="{'invisible': [('has_google_analytics_dashboard', '=', False)]}">
<a href="https://www.flectra.com/documentation/user/online/website/optimize/google_analytics_dashboard.html"
<a href="https://www.flectrahq.com/documentation/user/online/website/optimize/google_analytics_dashboard.html"
class="oe_link fa fa-arrow-right" target="_blank">
How to get my Client ID
</a>
@ -195,6 +201,24 @@
<field name="context">{'module' : 'website'}</field>
</record>
<record id="action_ui_qweb_view" model="ir.actions.act_window">
<field name="name">QWeb Views</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.ui.view</field>
<field name="view_id" ref="base.view_view_tree"/>
<field name="context">{'search_default_type': 'qweb',
'search_default_group_website_id': True}
</field>
</record>
<record id="action_website_website_list" model="ir.actions.act_window">
<field name="name">Websites</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">website</field>
<field name="view_id" ref="website.view_website_tree"/>
<field name="context">{}</field>
</record>
<menuitem id="menu_website_global_configuration" parent="menu_website_configuration"
sequence="100" name="Configuration" groups="base.group_system"/>
<menuitem name="Settings"
@ -217,4 +241,25 @@
sequence="30"
groups="base.group_no_one"/>
<menuitem name="Websites"
id="menu_website_website_list"
action="action_website_website_list"
parent="menu_website_global_configuration"
sequence="10"
groups="base.group_no_one"/>
<menuitem name="Menus"
id="menu_website_menus_list"
action="action_website_menu"
parent="menu_website_global_configuration"
sequence="40"
groups="base.group_no_one"/>
<menuitem name="QWeb Views"
id="menu_website_qweb_views_list"
action="action_ui_qweb_view"
parent="menu_website_global_configuration"
sequence="50"
groups="base.group_no_one"/>
</flectra>

View File

@ -21,8 +21,10 @@
<div name="domain">
<separator name="domain" string="Domain"/>
<group name="domain">
<field name="name"/>
<field name="domain"/>
<field name="name" required="True"/>
<field name="domain" required="True"/>
<field name="website_code" invisible="True"/>
<field name="is_default_website" invisible="True"/>
<field name="google_analytics_key" placeholder="UA-XXXXXXXX-Y"/>
</group>
</div>
@ -57,6 +59,8 @@
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="default_lang_id"/>
<field name="website_code"/>
<field name="is_default_website"/>
</tree>
</field>
</record>
@ -212,6 +216,7 @@
<field name="name" position="after">
<field name="website_id" readonly="1"/>
<field name="key" readonly="1"/>
<field name="is_cloned"/>
<field name="page_ids" invisible="1" />
</field>
<sheet position="before">
@ -223,6 +228,20 @@
</field>
</record>
<!-- ir.ui.view search -->
<record model="ir.ui.view" id="view_view_search_extend">
<field name="model">ir.ui.view</field>
<field name="inherit_id" ref="base.view_view_search"/>
<field name="arch" type="xml">
<field name="type" position="after">
<field name="website_id"/>
</field>
<xpath expr="//group" position="inside">
<filter name="group_website_id" string="Website" domain="[]" context="{'group_by':'website_id'}"/>
</xpath>
</field>
</record>
<!-- Dashboard -->
<record id="backend_dashboard" model="ir.actions.client">
<field name="name">Dashboard</field>

View File

@ -157,7 +157,6 @@
<record model="ir.module.category" id="module_category_theme">
<field name="name">Theme</field>
<field name="exclusive" eval="1"/>
<field name="sequence">50</field>
</record>