[ADD] Multi Website Core Support 🎉
This commit is contained in:
parent
7bdf18df35
commit
d00801d466
@ -10,7 +10,8 @@
|
|||||||
'website': 'https://flectrahq.com/page/website-builder',
|
'website': 'https://flectrahq.com/page/website-builder',
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'description': "",
|
'description': "",
|
||||||
'depends': ['web', 'web_editor', 'web_planner', 'http_routing', 'portal'],
|
'depends': ['web', 'web_editor', 'web_planner',
|
||||||
|
'http_routing', 'portal', 'base_automation'],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'data': [
|
'data': [
|
||||||
'data/website_data.xml',
|
'data/website_data.xml',
|
||||||
@ -23,6 +24,7 @@
|
|||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
'views/ir_actions_views.xml',
|
'views/ir_actions_views.xml',
|
||||||
'views/res_company_views.xml',
|
'views/res_company_views.xml',
|
||||||
|
'views/module_view.xml',
|
||||||
'wizard/base_language_install_views.xml',
|
'wizard/base_language_install_views.xml',
|
||||||
'data/web_planner_data.xml',
|
'data/web_planner_data.xml',
|
||||||
],
|
],
|
||||||
|
@ -6,9 +6,13 @@ from flectra.http import request
|
|||||||
|
|
||||||
|
|
||||||
class WebsiteBackend(http.Controller):
|
class WebsiteBackend(http.Controller):
|
||||||
|
|
||||||
@http.route('/website/fetch_dashboard_data', type="json", auth='user')
|
@http.route('/website/fetch_dashboard_data', type="json", auth='user',
|
||||||
def fetch_dashboard_data(self, date_from, date_to):
|
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_system = request.env.user.has_group('base.group_system')
|
||||||
has_group_designer = request.env.user.has_group('website.group_website_designer')
|
has_group_designer = request.env.user.has_group('website.group_website_designer')
|
||||||
if has_group_system:
|
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_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
|
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
|
return dashboard_data
|
||||||
|
|
||||||
@http.route('/website/dashboard/set_ga_data', type='json', auth='user')
|
@http.route('/website/dashboard/set_ga_data', type='json', auth='user')
|
||||||
|
@ -272,7 +272,10 @@ class Website(Home):
|
|||||||
|
|
||||||
@http.route("/website/get_switchable_related_views", type="json", auth="user", website=True)
|
@http.route("/website/get_switchable_related_views", type="json", auth="user", website=True)
|
||||||
def get_switchable_related_views(self, key):
|
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'])
|
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)
|
@http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<field name="domain">localhost</field>
|
<field name="domain">localhost</field>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
<field name="user_id" ref="base.public_user"/>
|
<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"/>
|
<field name="favicon" type="base64" file="web/static/src/img/favicon.ico"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
@ -35,6 +36,7 @@
|
|||||||
<field name="name">Home</field>
|
<field name="name">Home</field>
|
||||||
<field name="type">qweb</field>
|
<field name="type">qweb</field>
|
||||||
<field name="key">website.homepage</field>
|
<field name="key">website.homepage</field>
|
||||||
|
<field name="website_id" ref="default_website"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<t name="Home" priority="29" t-name="website.homepage">
|
<t name="Home" priority="29" t-name="website.homepage">
|
||||||
<t t-call="website.layout">
|
<t t-call="website.layout">
|
||||||
@ -48,6 +50,7 @@
|
|||||||
<field name="website_published">True</field>
|
<field name="website_published">True</field>
|
||||||
<field name="url">/</field>
|
<field name="url">/</field>
|
||||||
<field name="view_id" ref="homepage" />
|
<field name="view_id" ref="homepage" />
|
||||||
|
<field name="website_ids" eval="[(4, ref('default_website'))]" />
|
||||||
</record>
|
</record>
|
||||||
<record id="default_website" model="website">
|
<record id="default_website" model="website">
|
||||||
<field name="homepage_id" ref="homepage_page" />
|
<field name="homepage_id" ref="homepage_page" />
|
||||||
@ -91,6 +94,7 @@
|
|||||||
<field name="url">/contactus</field>
|
<field name="url">/contactus</field>
|
||||||
<field name="website_published">True</field>
|
<field name="website_published">True</field>
|
||||||
<field name="view_id" ref="contactus" />
|
<field name="view_id" ref="contactus" />
|
||||||
|
<field name="website_ids" eval="[(4, ref('default_website'))]" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="aboutus" model="ir.ui.view">
|
<record id="aboutus" model="ir.ui.view">
|
||||||
@ -147,6 +151,7 @@
|
|||||||
<field name="website_published">True</field>
|
<field name="website_published">True</field>
|
||||||
<field name="url">/aboutus</field>
|
<field name="url">/aboutus</field>
|
||||||
<field name="view_id" ref="aboutus" />
|
<field name="view_id" ref="aboutus" />
|
||||||
|
<field name="website_ids" eval="[(4, ref('default_website'))]" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="menu_homepage" model="website.menu">
|
<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>
|
<field name="url">/website/static/src/img/snippets_demo/s_team_member_4.png</field>
|
||||||
</record>
|
</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>
|
</data>
|
||||||
</flectra>
|
</flectra>
|
||||||
|
@ -126,122 +126,6 @@ response = request.render("website.template_partner_comment", {
|
|||||||
<record id="default_website" model="website">
|
<record id="default_website" model="website">
|
||||||
<field name="name">Website localhost</field>
|
<field name="name">Website localhost</field>
|
||||||
</record>
|
</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>
|
</data>
|
||||||
</flectra>
|
</flectra>
|
||||||
|
@ -6,10 +6,12 @@ from . import ir_attachment
|
|||||||
from . import ir_http
|
from . import ir_http
|
||||||
from . import ir_qweb
|
from . import ir_qweb
|
||||||
from . import website
|
from . import website
|
||||||
|
from . import ir_model
|
||||||
from . import ir_ui_view
|
from . import ir_ui_view
|
||||||
from . import res_company
|
from . import res_company
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
from . import web_planner
|
from . import web_planner
|
||||||
|
from . import module
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
from . import ir_model_fields
|
from . import ir_model_fields
|
||||||
from . import ir_model
|
from . import ir_model
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details
|
# 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
|
# This is a nasty hack, targeted for V11 only
|
||||||
@ -13,3 +16,171 @@ class IrModel(models.Model):
|
|||||||
self.env.cr.execute(
|
self.env.cr.execute(
|
||||||
"DELETE FROM ir_model_fields WHERE name='website_id'")
|
"DELETE FROM ir_model_fields WHERE name='website_id'")
|
||||||
return super(IrModel, self).unlink()
|
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
|
||||||
|
})
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from flectra import api, fields, models
|
from flectra import api, fields, models, _
|
||||||
from flectra import tools
|
from flectra import tools
|
||||||
from flectra.addons.http_routing.models.ir_http import url_for
|
from flectra.addons.http_routing.models.ir_http import url_for
|
||||||
from flectra.http import request
|
from flectra.http import request
|
||||||
@ -21,6 +21,11 @@ class View(models.Model):
|
|||||||
customize_show = fields.Boolean("Show As Optional Inherit", default=False)
|
customize_show = fields.Boolean("Show As Optional Inherit", default=False)
|
||||||
website_id = fields.Many2one('website', ondelete='cascade', string="Website")
|
website_id = fields.Many2one('website', ondelete='cascade', string="Website")
|
||||||
page_ids = fields.One2many('website.page', compute='_compute_page_ids', store=False)
|
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
|
@api.one
|
||||||
def _compute_page_ids(self):
|
def _compute_page_ids(self):
|
||||||
@ -157,3 +162,102 @@ class View(models.Model):
|
|||||||
'url': '/website/pages',
|
'url': '/website/pages',
|
||||||
'target': 'self',
|
'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()
|
||||||
|
24
addons/website/models/module.py
Normal file
24
addons/website/models/module.py
Normal 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()
|
@ -1,10 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
|
||||||
from flectra import api, fields, models
|
from flectra import api, fields, models, _
|
||||||
from flectra.exceptions import AccessDenied
|
from flectra.exceptions import AccessDenied, AccessError, Warning
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ResConfigSettings(models.TransientModel):
|
class ResConfigSettings(models.TransientModel):
|
||||||
@ -40,6 +43,25 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
('b2b', 'On invitation (B2B)'),
|
('b2b', 'On invitation (B2B)'),
|
||||||
('b2c', 'Free sign up (B2C)'),
|
('b2c', 'Free sign up (B2C)'),
|
||||||
], string='Customer Account')
|
], 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')
|
@api.onchange('has_google_analytics')
|
||||||
def onchange_has_google_analytics(self):
|
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['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']]
|
action['views'] = [[self.env.ref('base.view_users_form').id, 'form']]
|
||||||
return action
|
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',
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from werkzeug import urls
|
from werkzeug import urls
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
@ -17,6 +18,7 @@ from flectra.tools import pycompat
|
|||||||
from flectra.http import request
|
from flectra.http import request
|
||||||
from flectra.osv.expression import FALSE_DOMAIN
|
from flectra.osv.expression import FALSE_DOMAIN
|
||||||
from flectra.tools.translate import _
|
from flectra.tools.translate import _
|
||||||
|
from flectra.exceptions import Warning
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -71,6 +73,17 @@ class Website(models.Model):
|
|||||||
menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
|
menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
|
||||||
homepage_id = fields.Many2one('website.page', string='Homepage')
|
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.")
|
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
|
@api.multi
|
||||||
def _compute_menu(self):
|
def _compute_menu(self):
|
||||||
@ -82,14 +95,95 @@ class Website(models.Model):
|
|||||||
def noop(self, *args, **kwargs):
|
def noop(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Multi Website
|
||||||
|
# ----------------------------------------------------------
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, values):
|
def write(self, values):
|
||||||
self._get_languages.clear_cache(self)
|
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)
|
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
|
# Page Management
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
@api.model
|
@api.model
|
||||||
def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None):
|
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
|
""" 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:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Languages
|
# Languages
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
@ -362,9 +456,9 @@ class Website(models.Model):
|
|||||||
lang['hreflang'] = lang['short']
|
lang['hreflang'] = lang['short']
|
||||||
return langs
|
return langs
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Utilities
|
# Utilities
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_current_website(self):
|
def get_current_website(self):
|
||||||
|
@ -30,6 +30,7 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
|
|||||||
'click .o_dashboard_action': 'on_dashboard_action',
|
'click .o_dashboard_action': 'on_dashboard_action',
|
||||||
'click .o_dashboard_action_form': 'on_dashboard_action_form',
|
'click .o_dashboard_action_form': 'on_dashboard_action_form',
|
||||||
'click .o_dashboard_hide_panel': 'on_dashboard_hide_panel',
|
'click .o_dashboard_hide_panel': 'on_dashboard_hide_panel',
|
||||||
|
'click li.js_website_deshboard': 'js_website_deshboard',
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function(parent, context) {
|
init: function(parent, context) {
|
||||||
@ -42,6 +43,8 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
|
|||||||
|
|
||||||
this.dashboards_templates = ['website.dashboard_visits'];
|
this.dashboards_templates = ['website.dashboard_visits'];
|
||||||
this.graphs = [];
|
this.graphs = [];
|
||||||
|
this.is_bound = $.Deferred();
|
||||||
|
this.dashboards_header = ['website.dashboard_header'];
|
||||||
},
|
},
|
||||||
|
|
||||||
willStart: function() {
|
willStart: function() {
|
||||||
@ -58,21 +61,26 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
|
|||||||
self.render_dashboards();
|
self.render_dashboards();
|
||||||
self.render_graphs();
|
self.render_graphs();
|
||||||
self.$el.parent().addClass('oe_background_grey');
|
self.$el.parent().addClass('oe_background_grey');
|
||||||
|
self.bind_menu();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches dashboard data
|
* Fetches dashboard data
|
||||||
*/
|
*/
|
||||||
fetch_data: function() {
|
fetch_data: function(website_id=null) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return this._rpc({
|
return this._rpc({
|
||||||
route: '/website/fetch_dashboard_data',
|
route: '/website/fetch_dashboard_data',
|
||||||
params: {
|
params: {
|
||||||
date_from: this.date_from.year()+'-'+(this.date_from.month()+1)+'-'+this.date_from.date(),
|
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(),
|
date_to: this.date_to.year()+'-'+(this.date_to.month()+1)+'-'+this.date_to.date(),
|
||||||
|
'website_id': website_id,
|
||||||
},
|
},
|
||||||
}).done(function(result) {
|
}).done(function(result) {
|
||||||
|
self.website_ids = result.website_ids;
|
||||||
|
self.website = result.website;
|
||||||
|
self.current_website = result.current_website;
|
||||||
self.data = result;
|
self.data = result;
|
||||||
self.dashboards_data = result.dashboards;
|
self.dashboards_data = result.dashboards;
|
||||||
self.currency_id = result.currency_id;
|
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) {
|
on_link_analytics_settings: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
@ -7,9 +7,26 @@
|
|||||||
|
|
||||||
<t t-name="website.WebsiteDashboardMain">
|
<t t-name="website.WebsiteDashboardMain">
|
||||||
<div class="o_dashboards">
|
<div class="o_dashboards">
|
||||||
<div class="container-fluid o_website_dashboard">
|
<div class="o_dashboards">
|
||||||
<t t-call="website.dashboard_header"/>
|
<div class="navbar-collapse collapse" style="padding:0;">
|
||||||
<div class="o_website_dashboard_content"/>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
@ -17,7 +34,9 @@
|
|||||||
<t t-name="website.dashboard_header">
|
<t t-name="website.dashboard_header">
|
||||||
<div class="row o_dashboard_common">
|
<div class="row o_dashboard_common">
|
||||||
<div class="o_box">
|
<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">
|
<div class="o_inner_box o_primary">
|
||||||
<i class="fa fa-globe fa-3x"></i><br/>
|
<i class="fa fa-globe fa-3x"></i><br/>
|
||||||
Go to Website
|
Go to Website
|
||||||
|
14
addons/website/views/module_view.xml
Normal file
14
addons/website/views/module_view.xml
Normal 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>
|
@ -10,24 +10,30 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[hasclass('settings')]" position="inside">
|
<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">
|
<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>
|
<h2>Website</h2>
|
||||||
<div class="row mt16 o_settings_container" id="webmaster_settings">
|
<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="col-xs-12 col-md-6 o_setting_box" id="domain_setting">
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label string="Website Title"/>
|
<label string="Website Title"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Name and favicon of your website
|
Name, favicon & theme of your website
|
||||||
</div>
|
</div>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<div class="row mt16">
|
<div class="row mt16">
|
||||||
<label class="col-md-3 o_light_label" string="Name"/>
|
<label class="col-md-3 o_light_label" string="Name"/>
|
||||||
<field name="website_name"/>
|
<field name="website_name"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row mt8">
|
||||||
<label class="col-md-3 o_light_label" for="favicon" />
|
<label class="col-md-3 o_light_label" for="favicon" />
|
||||||
<field name="favicon" widget="image" class="pull-left oe_avatar"/>
|
<field name="favicon" widget="image" class="pull-left oe_avatar"/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('has_google_analytics', '=', False)]}">
|
<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">
|
class="oe_link fa fa-arrow-right" target="_blank">
|
||||||
How to get my Tracking ID
|
How to get my Tracking ID
|
||||||
</a>
|
</a>
|
||||||
@ -96,7 +102,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('has_google_analytics_dashboard', '=', False)]}">
|
<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">
|
class="oe_link fa fa-arrow-right" target="_blank">
|
||||||
How to get my Client ID
|
How to get my Client ID
|
||||||
</a>
|
</a>
|
||||||
@ -195,6 +201,24 @@
|
|||||||
<field name="context">{'module' : 'website'}</field>
|
<field name="context">{'module' : 'website'}</field>
|
||||||
</record>
|
</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"
|
<menuitem id="menu_website_global_configuration" parent="menu_website_configuration"
|
||||||
sequence="100" name="Configuration" groups="base.group_system"/>
|
sequence="100" name="Configuration" groups="base.group_system"/>
|
||||||
<menuitem name="Settings"
|
<menuitem name="Settings"
|
||||||
@ -217,4 +241,25 @@
|
|||||||
sequence="30"
|
sequence="30"
|
||||||
groups="base.group_no_one"/>
|
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>
|
</flectra>
|
||||||
|
@ -21,8 +21,10 @@
|
|||||||
<div name="domain">
|
<div name="domain">
|
||||||
<separator name="domain" string="Domain"/>
|
<separator name="domain" string="Domain"/>
|
||||||
<group name="domain">
|
<group name="domain">
|
||||||
<field name="name"/>
|
<field name="name" required="True"/>
|
||||||
<field name="domain"/>
|
<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"/>
|
<field name="google_analytics_key" placeholder="UA-XXXXXXXX-Y"/>
|
||||||
</group>
|
</group>
|
||||||
</div>
|
</div>
|
||||||
@ -57,6 +59,8 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
<field name="default_lang_id"/>
|
<field name="default_lang_id"/>
|
||||||
|
<field name="website_code"/>
|
||||||
|
<field name="is_default_website"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -212,6 +216,7 @@
|
|||||||
<field name="name" position="after">
|
<field name="name" position="after">
|
||||||
<field name="website_id" readonly="1"/>
|
<field name="website_id" readonly="1"/>
|
||||||
<field name="key" readonly="1"/>
|
<field name="key" readonly="1"/>
|
||||||
|
<field name="is_cloned"/>
|
||||||
<field name="page_ids" invisible="1" />
|
<field name="page_ids" invisible="1" />
|
||||||
</field>
|
</field>
|
||||||
<sheet position="before">
|
<sheet position="before">
|
||||||
@ -223,6 +228,20 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</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 -->
|
<!-- Dashboard -->
|
||||||
<record id="backend_dashboard" model="ir.actions.client">
|
<record id="backend_dashboard" model="ir.actions.client">
|
||||||
<field name="name">Dashboard</field>
|
<field name="name">Dashboard</field>
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
|
|
||||||
<record model="ir.module.category" id="module_category_theme">
|
<record model="ir.module.category" id="module_category_theme">
|
||||||
<field name="name">Theme</field>
|
<field name="name">Theme</field>
|
||||||
<field name="exclusive" eval="1"/>
|
|
||||||
<field name="sequence">50</field>
|
<field name="sequence">50</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user