[MERGE] website: add multi-websites support
This commit is contained in:
@@ -95,7 +95,7 @@ class AuthSignupHome(Home):
|
|||||||
|
|
||||||
get_param = request.env['ir.config_parameter'].sudo().get_param
|
get_param = request.env['ir.config_parameter'].sudo().get_param
|
||||||
return {
|
return {
|
||||||
'signup_enabled': get_param('auth_signup.invitation_scope', 'b2b') == 'b2c',
|
'signup_enabled': request.env['res.users']._get_signup_invitation_scope() == 'b2c',
|
||||||
'reset_password_enabled': get_param('auth_signup.reset_password') == 'True',
|
'reset_password_enabled': get_param('auth_signup.reset_password') == 'True',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
|
|
||||||
auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page', config_parameter='auth_signup.reset_password')
|
auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page', config_parameter='auth_signup.reset_password')
|
||||||
auth_signup_uninvited = fields.Selection([
|
auth_signup_uninvited = fields.Selection([
|
||||||
('b2b', 'On invitation (B2B)'),
|
('b2b', 'On invitation'),
|
||||||
('b2c', 'Free sign up (B2C)'),
|
('b2c', 'Free sign up'),
|
||||||
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
|
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
|
||||||
auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup',
|
auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup',
|
||||||
config_parameter='base.template_portal_user_id')
|
config_parameter='base.template_portal_user_id')
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class ResPartner(models.Model):
|
|||||||
"""
|
"""
|
||||||
res = defaultdict(dict)
|
res = defaultdict(dict)
|
||||||
|
|
||||||
allow_signup = self.env['ir.config_parameter'].sudo().get_param('auth_signup.invitation_scope', 'b2b') == 'b2c'
|
allow_signup = self.env['res.users']._get_signup_invitation_scope() == 'b2c'
|
||||||
for partner in self:
|
for partner in self:
|
||||||
if allow_signup and not partner.user_ids:
|
if allow_signup and not partner.user_ids:
|
||||||
partner = partner.sudo()
|
partner = partner.sudo()
|
||||||
|
|||||||
@@ -103,13 +103,17 @@ class ResUsers(models.Model):
|
|||||||
|
|
||||||
return (self.env.cr.dbname, values.get('login'), values.get('password'))
|
return (self.env.cr.dbname, values.get('login'), values.get('password'))
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_signup_invitation_scope(self):
|
||||||
|
return self.env['ir.config_parameter'].sudo().get_param('auth_signup.invitation_scope', 'b2b')
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _signup_create_user(self, values):
|
def _signup_create_user(self, values):
|
||||||
""" signup a new user using the template user """
|
""" signup a new user using the template user """
|
||||||
|
|
||||||
# check that uninvited users may sign up
|
# check that uninvited users may sign up
|
||||||
if 'partner_id' not in values:
|
if 'partner_id' not in values:
|
||||||
if self.env['ir.config_parameter'].sudo().get_param('auth_signup.invitation_scope', 'b2b') != 'b2c':
|
if self._get_signup_invitation_scope() != 'b2c':
|
||||||
raise SignupError(_('Signup is not allowed for uninvited users'))
|
raise SignupError(_('Signup is not allowed for uninvited users'))
|
||||||
return self._create_user_from_template(values)
|
return self._create_user_from_template(values)
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
module_website_sale_digital = fields.Boolean("Sell digital products - provide downloadable content on your customer portal")
|
module_website_sale_digital = fields.Boolean("Sell digital products - provide downloadable content on your customer portal")
|
||||||
|
|
||||||
auth_signup_uninvited = fields.Selection([
|
auth_signup_uninvited = fields.Selection([
|
||||||
('b2b', 'On invitation (B2B)'),
|
('b2b', 'On invitation'),
|
||||||
('b2c', 'Free sign up (B2C)'),
|
('b2c', 'Free sign up'),
|
||||||
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
|
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
|
||||||
|
|
||||||
module_delivery = fields.Boolean("Shipping Costs")
|
module_delivery = fields.Boolean("Shipping Costs")
|
||||||
|
|||||||
@@ -1002,6 +1002,7 @@ class Binary(http.Controller):
|
|||||||
'/web/content/<int:id>/<string:filename>',
|
'/web/content/<int:id>/<string:filename>',
|
||||||
'/web/content/<int:id>-<string:unique>',
|
'/web/content/<int:id>-<string:unique>',
|
||||||
'/web/content/<int:id>-<string:unique>/<string:filename>',
|
'/web/content/<int:id>-<string:unique>/<string:filename>',
|
||||||
|
'/web/content/<int:id>-<string:unique>/<path:extra>/<string:filename>',
|
||||||
'/web/content/<string:model>/<int:id>/<string:field>',
|
'/web/content/<string:model>/<int:id>/<string:field>',
|
||||||
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public")
|
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public")
|
||||||
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas',
|
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas',
|
||||||
|
|||||||
@@ -146,6 +146,9 @@ class Web_Editor(http.Controller):
|
|||||||
# therefore we have to recover the files from the request object
|
# therefore we have to recover the files from the request object
|
||||||
Attachments = request.env['ir.attachment'] # registry for the attachment table
|
Attachments = request.env['ir.attachment'] # registry for the attachment table
|
||||||
|
|
||||||
|
website = request.env['website'].get_current_website() # # TO MOVE
|
||||||
|
|
||||||
|
|
||||||
res_model = kwargs.get('res_model', 'ir.ui.view')
|
res_model = kwargs.get('res_model', 'ir.ui.view')
|
||||||
if res_model != 'ir.ui.view' and kwargs.get('res_id'):
|
if res_model != 'ir.ui.view' and kwargs.get('res_id'):
|
||||||
res_id = int(kwargs['res_id'])
|
res_id = int(kwargs['res_id'])
|
||||||
@@ -167,6 +170,8 @@ class Web_Editor(http.Controller):
|
|||||||
'public': res_model == 'ir.ui.view',
|
'public': res_model == 'ir.ui.view',
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'res_model': res_model,
|
'res_model': res_model,
|
||||||
|
'website_id': website.id,
|
||||||
|
|
||||||
})
|
})
|
||||||
attachment.generate_access_token()
|
attachment.generate_access_token()
|
||||||
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
|
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
|
||||||
@@ -198,6 +203,8 @@ class Web_Editor(http.Controller):
|
|||||||
'public': res_model == 'ir.ui.view',
|
'public': res_model == 'ir.ui.view',
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'res_model': res_model,
|
'res_model': res_model,
|
||||||
|
'website_id': website.id, # TO MOVE IN WEBSITEID
|
||||||
|
|
||||||
})
|
})
|
||||||
attachment.generate_access_token()
|
attachment.generate_access_token()
|
||||||
attachments += attachment
|
attachments += attachment
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
'views/website_views.xml',
|
'views/website_views.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
'views/ir_actions_views.xml',
|
'views/ir_actions_views.xml',
|
||||||
|
'views/ir_attachment_views.xml',
|
||||||
'views/res_users_views.xml',
|
'views/res_users_views.xml',
|
||||||
|
'views/res_partner_views.xml',
|
||||||
'wizard/base_language_install_views.xml',
|
'wizard/base_language_install_views.xml',
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ from odoo.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):
|
def fetch_dashboard_data(self, website_id, date_from, date_to):
|
||||||
|
Website = request.env['website']
|
||||||
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')
|
||||||
dashboard_data = {
|
dashboard_data = {
|
||||||
@@ -21,8 +22,16 @@ class WebsiteBackend(http.Controller):
|
|||||||
'visits': {},
|
'visits': {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_website = website_id and Website.browse(website_id) or Website.get_current_website()
|
||||||
|
dashboard_data['websites'] = request.env['website'].search_read([], ['id', 'name'])
|
||||||
|
for website in dashboard_data['websites']:
|
||||||
|
if website['id'] == current_website.id:
|
||||||
|
website['selected'] = True
|
||||||
|
|
||||||
if has_group_designer:
|
if has_group_designer:
|
||||||
config = request.env['res.config.settings'].sudo().create({})
|
config = request.env['res.config.settings'].sudo().create({})
|
||||||
|
config.website_id = current_website
|
||||||
if config.has_google_analytics_dashboard:
|
if config.has_google_analytics_dashboard:
|
||||||
dashboard_data['dashboards']['visits'] = dict(
|
dashboard_data['dashboards']['visits'] = dict(
|
||||||
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
|
||||||
@@ -31,7 +40,7 @@ class WebsiteBackend(http.Controller):
|
|||||||
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')
|
||||||
def website_set_ga_data(self, ga_client_id, ga_analytics_key):
|
def website_set_ga_data(self, website_id, ga_client_id, ga_analytics_key):
|
||||||
if not request.env.user.has_group('base.group_system'):
|
if not request.env.user.has_group('base.group_system'):
|
||||||
return {
|
return {
|
||||||
'error': {
|
'error': {
|
||||||
@@ -46,10 +55,14 @@ class WebsiteBackend(http.Controller):
|
|||||||
'message': 'The Google Analytics Client ID or Key you entered seems incorrect.',
|
'message': 'The Google Analytics Client ID or Key you entered seems incorrect.',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Website = request.env['website']
|
||||||
|
current_website = website_id and Website.browse(website_id) or Website.get_current_website()
|
||||||
|
|
||||||
request.env['res.config.settings'].create({
|
request.env['res.config.settings'].create({
|
||||||
'has_google_analytics': True,
|
'has_google_analytics': True,
|
||||||
'has_google_analytics_dashboard': True,
|
'has_google_analytics_dashboard': True,
|
||||||
'google_management_client_id': ga_client_id,
|
'google_management_client_id': ga_client_id,
|
||||||
'google_analytics_key': ga_analytics_key,
|
'google_analytics_key': ga_analytics_key,
|
||||||
|
'website_id': current_website,
|
||||||
}).execute()
|
}).execute()
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from odoo import http, models, fields, _
|
|||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.tools import pycompat, OrderedSet
|
from odoo.tools import pycompat, OrderedSet
|
||||||
from odoo.addons.http_routing.models.ir_http import slug, _guess_mimetype
|
from odoo.addons.http_routing.models.ir_http import slug, _guess_mimetype
|
||||||
from odoo.addons.web.controllers.main import WebClient, Binary
|
from odoo.addons.web.controllers.main import Binary
|
||||||
from odoo.addons.portal.controllers.portal import pager as portal_pager
|
from odoo.addons.portal.controllers.portal import pager as portal_pager
|
||||||
from odoo.addons.portal.controllers.web import Home
|
from odoo.addons.portal.controllers.web import Home
|
||||||
|
|
||||||
@@ -81,10 +81,15 @@ class Website(Home):
|
|||||||
|
|
||||||
raise request.not_found()
|
raise request.not_found()
|
||||||
|
|
||||||
#------------------------------------------------------
|
@http.route('/website/force_website', type='json', auth="user")
|
||||||
|
def force_website(self, website_id):
|
||||||
|
request.env['website']._force_website(website_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
# Login - overwrite of the web login so that regular users are redirected to the backend
|
# Login - overwrite of the web login so that regular users are redirected to the backend
|
||||||
# while portal users are redirected to the frontend by default
|
# while portal users are redirected to the frontend by default
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
@http.route(website=True, auth="public")
|
@http.route(website=True, auth="public")
|
||||||
def web_login(self, redirect=None, *args, **kw):
|
def web_login(self, redirect=None, *args, **kw):
|
||||||
@@ -97,9 +102,9 @@ class Website(Home):
|
|||||||
return http.redirect_with_hash(redirect)
|
return http.redirect_with_hash(redirect)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
# Business
|
# Business
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
@http.route('/website/lang/<lang>', type='http', auth="public", website=True, multilang=False)
|
@http.route('/website/lang/<lang>', type='http', auth="public", website=True, multilang=False)
|
||||||
def change_lang(self, lang, r='/', **kwargs):
|
def change_lang(self, lang, r='/', **kwargs):
|
||||||
@@ -135,7 +140,7 @@ class Website(Home):
|
|||||||
'name': url,
|
'name': url,
|
||||||
'url': url,
|
'url': url,
|
||||||
})
|
})
|
||||||
dom = [('url', '=' , '/sitemap-%d.xml' % current_website.id), ('type', '=', 'binary')]
|
dom = [('url', '=', '/sitemap-%d.xml' % current_website.id), ('type', '=', 'binary')]
|
||||||
sitemap = Attachment.search(dom, limit=1)
|
sitemap = Attachment.search(dom, limit=1)
|
||||||
if sitemap:
|
if sitemap:
|
||||||
# Check if stored version is still valid
|
# Check if stored version is still valid
|
||||||
@@ -203,9 +208,9 @@ class Website(Home):
|
|||||||
}
|
}
|
||||||
return request.render('website.website_info', values)
|
return request.render('website.website_info', values)
|
||||||
|
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
# Edit
|
# Edit
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
@http.route(['/website/pages', '/website/pages/page/<int:page>'], type='http', auth="user", website=True)
|
@http.route(['/website/pages', '/website/pages/page/<int:page>'], type='http', auth="user", website=True)
|
||||||
def pages_management(self, page=1, sortby='name', search='', **kw):
|
def pages_management(self, page=1, sortby='name', search='', **kw):
|
||||||
@@ -219,22 +224,27 @@ class Website(Home):
|
|||||||
'name': {'label': _('Sort by Name'), 'order': 'name'},
|
'name': {'label': _('Sort by Name'), 'order': 'name'},
|
||||||
}
|
}
|
||||||
# default sortby order
|
# default sortby order
|
||||||
sort_order = searchbar_sortings.get(sortby, 'name')['order']
|
sort_order = searchbar_sortings.get(sortby, 'name')['order'] + ', website_id desc'
|
||||||
|
|
||||||
domain = ['|', ('website_ids', 'in', request.website.id), ('website_ids', '=', False)]
|
domain = request.website.website_domain()
|
||||||
if search:
|
if search:
|
||||||
domain += ['|', ('name', 'ilike', search), ('url', 'ilike', search)]
|
domain += ['|', ('name', 'ilike', search), ('url', 'ilike', search)]
|
||||||
|
|
||||||
pages_count = Page.search_count(domain)
|
pages = Page.search(domain, order=sort_order)
|
||||||
|
if sortby != 'url':
|
||||||
|
pages = pages.filtered(pages._is_most_specific_page)
|
||||||
|
pages_count = len(pages)
|
||||||
|
|
||||||
|
step = 50
|
||||||
pager = portal_pager(
|
pager = portal_pager(
|
||||||
url="/website/pages",
|
url="/website/pages",
|
||||||
url_args={'sortby': sortby},
|
url_args={'sortby': sortby},
|
||||||
total=pages_count,
|
total=pages_count,
|
||||||
page=page,
|
page=page,
|
||||||
step=50
|
step=step
|
||||||
)
|
)
|
||||||
pages = Page.search(domain, order=sort_order, limit=50, offset=pager['offset'])
|
|
||||||
|
pages = pages[(page - 1) * step:page * step]
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'pager': pager,
|
'pager': pager,
|
||||||
@@ -243,13 +253,12 @@ class Website(Home):
|
|||||||
'sortby': sortby,
|
'sortby': sortby,
|
||||||
'searchbar_sortings': searchbar_sortings,
|
'searchbar_sortings': searchbar_sortings,
|
||||||
}
|
}
|
||||||
return request.render("website.edit_website_pages", values)
|
return request.render("website.list_website_pages", values)
|
||||||
|
|
||||||
@http.route(['/website/add/', '/website/add/<path:path>'], type='http', auth="user", website=True)
|
@http.route(['/website/add/', '/website/add/<path:path>'], type='http', auth="user", website=True)
|
||||||
def pagenew(self, path="", noredirect=False, add_menu=False, template=False):
|
def pagenew(self, path="", noredirect=False, add_menu=False, template=False):
|
||||||
# for supported mimetype, get correct default template
|
# for supported mimetype, get correct default template
|
||||||
_, ext = os.path.splitext(path)
|
_, ext = os.path.splitext(path)
|
||||||
View = request.env['ir.ui.view']
|
|
||||||
ext_special_case = ext and ext in _guess_mimetype() and ext != '.html'
|
ext_special_case = ext and ext in _guess_mimetype() and ext != '.html'
|
||||||
|
|
||||||
if not template and ext_special_case:
|
if not template and ext_special_case:
|
||||||
@@ -263,7 +272,7 @@ class Website(Home):
|
|||||||
if noredirect:
|
if noredirect:
|
||||||
return werkzeug.wrappers.Response(url, mimetype='text/plain')
|
return werkzeug.wrappers.Response(url, mimetype='text/plain')
|
||||||
|
|
||||||
if ext_special_case: # redirect non html pages to backend to edit
|
if ext_special_case: # redirect non html pages to backend to edit
|
||||||
return werkzeug.utils.redirect('/web#id=' + str(page.get('view_id')) + '&view_type=form&model=ir.ui.view')
|
return werkzeug.utils.redirect('/web#id=' + str(page.get('view_id')) + '&view_type=form&model=ir.ui.view')
|
||||||
return werkzeug.utils.redirect(url + "?enable_editor=1")
|
return werkzeug.utils.redirect(url + "?enable_editor=1")
|
||||||
|
|
||||||
@@ -321,9 +330,9 @@ class Website(Home):
|
|||||||
xmlroot = ET.fromstring(response)
|
xmlroot = ET.fromstring(response)
|
||||||
return json.dumps([sugg[0].attrib['data'] for sugg in xmlroot if len(sugg) and sugg[0].attrib['data']])
|
return json.dumps([sugg[0].attrib['data'] for sugg in xmlroot if len(sugg) and sugg[0].attrib['data']])
|
||||||
|
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
# Themes
|
# Themes
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
def get_view_ids(self, xml_ids):
|
def get_view_ids(self, xml_ids):
|
||||||
ids = []
|
ids = []
|
||||||
@@ -386,9 +395,9 @@ class Website(Home):
|
|||||||
res[id_or_xml_id] = View.render_template(id_or_xml_id, values)
|
res[id_or_xml_id] = View.render_template(id_or_xml_id, values)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
# Server actions
|
# Server actions
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
'/website/action/<path_or_xml_id_or_id>',
|
'/website/action/<path_or_xml_id_or_id>',
|
||||||
@@ -422,9 +431,9 @@ class Website(Home):
|
|||||||
return request.redirect('/')
|
return request.redirect('/')
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
# Retrocompatibility routes
|
# Retrocompatibility routes
|
||||||
#------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
class WebsiteBinary(http.Controller):
|
class WebsiteBinary(http.Controller):
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="main_menu" model="website.menu">
|
<record id="main_menu" model="website.menu">
|
||||||
<field name="name">Top Menu</field>
|
<field name="name">Top Menu</field>
|
||||||
<field name="website_id" ref="default_website"/>
|
<field name="website_id" ref="default_website"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="menu_homepage" model="website.menu">
|
<record id="menu_homepage" model="website.menu">
|
||||||
@@ -579,4 +579,9 @@
|
|||||||
<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>
|
||||||
</data>
|
</data>
|
||||||
|
<data>
|
||||||
|
<record id="group_multi_website" model="res.groups">
|
||||||
|
<field name="name">Multi-website</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -131,53 +131,6 @@ response = request.render("website.template_partner_comment", {
|
|||||||
<field name="domain">0.0.0.0</field>
|
<field name="domain">0.0.0.0</field>
|
||||||
</record>
|
</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 d-none">
|
|
||||||
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
|
|
||||||
</ol>
|
|
||||||
<div class="carousel-inner">
|
|
||||||
<div class="carousel-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-lg-6 col-md-12">
|
|
||||||
<h2>Homepage 0.0.0.0</h2>
|
|
||||||
<h3>Click to customize this text</h3>
|
|
||||||
<p>
|
|
||||||
<a role="button" class="btn btn-success btn-large" href="/contactus">Contact us</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="carousel-img col-lg-6 d-none d-lg-block"> </span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="carousel-control-prev d-none" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%" role="img" aria-label="Previous" title="Previous">
|
|
||||||
<i class="fa fa-chevron-left"/>
|
|
||||||
</div>
|
|
||||||
<div class="carousel-control-next d-none" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%" role="img" aria-label="Next" title="Next">
|
|
||||||
<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">
|
<record id="website2_contactus" model="ir.ui.view">
|
||||||
<field name="name">Contact Us</field>
|
<field name="name">Contact Us</field>
|
||||||
<field name="type">qweb</field>
|
<field name="type">qweb</field>
|
||||||
@@ -216,29 +169,14 @@ response = request.render("website.template_partner_comment", {
|
|||||||
<field name="website_published">True</field>
|
<field name="website_published">True</field>
|
||||||
<field name="url">/contactus</field>
|
<field name="url">/contactus</field>
|
||||||
<field name="view_id" ref="website2_contactus" />
|
<field name="view_id" ref="website2_contactus" />
|
||||||
<field name="website_ids" eval="[(4, ref('website2'))]" />
|
<field name="website_id" eval="ref('website2')" />
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Menu & Homepage -->
|
<!-- 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">
|
<record id="website2_menu_contactus" model="website.menu">
|
||||||
<field name="name">Contact us</field>
|
<field name="name">Contact us</field>
|
||||||
<field name="url">/contactus</field>
|
<field name="url">/contactus</field>
|
||||||
<field name="parent_id" ref="website.website2_main_menu"/>
|
<field name="parent_id" search="[('website_id', '=', ref('website2')), ('parent_id', '=', False)]"/>
|
||||||
<field name="sequence" type="int">60</field>
|
<field name="sequence" type="int">60</field>
|
||||||
<field name="website_id" ref="website2"/>
|
<field name="website_id" ref="website2"/>
|
||||||
<field name="page_id" ref="website2_contactus_page" />
|
<field name="page_id" ref="website2_contactus_page" />
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ from . import ir_http
|
|||||||
from . import ir_qweb
|
from . import ir_qweb
|
||||||
from . import ir_qweb_fields
|
from . import ir_qweb_fields
|
||||||
from . import website
|
from . import website
|
||||||
|
from . import ir_rule
|
||||||
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 res_users
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
from . import res_users
|
from . import res_users
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import api, fields, models
|
import logging
|
||||||
|
from odoo import fields, models, api
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Attachment(models.Model):
|
class Attachment(models.Model):
|
||||||
@@ -10,6 +12,15 @@ class Attachment(models.Model):
|
|||||||
|
|
||||||
# related for backward compatibility with saas-6
|
# related for backward compatibility with saas-6
|
||||||
website_url = fields.Char(string="Website URL", related='local_url', deprecated=True)
|
website_url = fields.Char(string="Website URL", related='local_url', deprecated=True)
|
||||||
|
key = fields.Char(help='Technical field used to resolve multiple attachments in a multi-website environment.')
|
||||||
|
website_id = fields.Many2one('website')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
website = self.env['website'].get_current_website()
|
||||||
|
if website and 'website_id' not in vals and 'not_force_website_id' not in self.env.context:
|
||||||
|
vals['website_id'] = website.id
|
||||||
|
return super(Attachment, self).create(vals)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_serving_groups(self):
|
def get_serving_groups(self):
|
||||||
|
|||||||
@@ -117,9 +117,13 @@ class Http(models.AbstractModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _serve_page(cls):
|
def _serve_page(cls):
|
||||||
req_page = request.httprequest.path
|
req_page = request.httprequest.path
|
||||||
|
page_domain = [('url', '=', req_page)] + request.website.website_domain()
|
||||||
|
|
||||||
domain = [('url', '=', req_page), '|', ('website_ids', 'in', request.website.id), ('website_ids', '=', False)]
|
published_domain = page_domain
|
||||||
pages = request.env['website.page'].search(domain)
|
# need to bypass website_published, to apply is_most_specific
|
||||||
|
# filter later if not publisher
|
||||||
|
pages = request.env['website.page'].sudo().search(published_domain, order='website_id')
|
||||||
|
pages = pages.filtered(pages._is_most_specific_page)
|
||||||
|
|
||||||
if not request.website.is_publisher():
|
if not request.website.is_publisher():
|
||||||
pages = pages.filtered('is_visible')
|
pages = pages.filtered('is_visible')
|
||||||
@@ -137,10 +141,7 @@ class Http(models.AbstractModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _serve_redirect(cls):
|
def _serve_redirect(cls):
|
||||||
req_page = request.httprequest.path
|
req_page = request.httprequest.path
|
||||||
domain = [
|
domain = [('url_from', '=', req_page)] + request.website.website_domain()
|
||||||
'|', ('website_id', '=', request.website.id), ('website_id', '=', False),
|
|
||||||
('url_from', '=', req_page)
|
|
||||||
]
|
|
||||||
return request.env['website.redirect'].search(domain, limit=1)
|
return request.env['website.redirect'].search(domain, limit=1)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -178,7 +179,7 @@ class Http(models.AbstractModel):
|
|||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')):
|
if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')):
|
||||||
raise
|
raise e
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
values = dict(
|
values = dict(
|
||||||
@@ -239,7 +240,7 @@ class Http(models.AbstractModel):
|
|||||||
env = env or request.env
|
env = env or request.env
|
||||||
obj = None
|
obj = None
|
||||||
if xmlid:
|
if xmlid:
|
||||||
obj = env.ref(xmlid, False)
|
obj = cls._xmlid_to_obj(env, xmlid)
|
||||||
elif id and model in env:
|
elif id and model in env:
|
||||||
obj = env[model].browse(int(id))
|
obj = env[model].browse(int(id))
|
||||||
if obj and 'website_published' in obj._fields:
|
if obj and 'website_published' in obj._fields:
|
||||||
@@ -251,11 +252,23 @@ class Http(models.AbstractModel):
|
|||||||
default_mimetype=default_mimetype, access_token=access_token, share_id=share_id, share_token=share_token,
|
default_mimetype=default_mimetype, access_token=access_token, share_id=share_id, share_token=share_token,
|
||||||
force_ext=force_ext, env=env)
|
force_ext=force_ext, env=env)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _xmlid_to_obj(cls, env, xmlid):
|
||||||
|
website_id = env['website'].get_current_website()
|
||||||
|
if website_id and website_id.theme_id:
|
||||||
|
obj = env['ir.attachment'].search([('key', '=', xmlid), ('website_id', '=', website_id.id)])
|
||||||
|
if obj:
|
||||||
|
return obj[0]
|
||||||
|
|
||||||
|
return super(Http, cls)._xmlid_to_obj(env, xmlid)
|
||||||
|
|
||||||
|
|
||||||
class ModelConverter(ModelConverter):
|
class ModelConverter(ModelConverter):
|
||||||
|
|
||||||
def generate(self, uid, dom=None, args=None):
|
def generate(self, uid, dom=None, args=None):
|
||||||
Model = request.env[self.model].sudo(uid)
|
Model = request.env[self.model].sudo(uid)
|
||||||
|
# Allow to current_website_id directly in route domain
|
||||||
|
args.update(current_website_id=request.env['website'].get_current_website().id)
|
||||||
domain = safe_eval(self.domain, (args or {}).copy())
|
domain = safe_eval(self.domain, (args or {}).copy())
|
||||||
if dom:
|
if dom:
|
||||||
domain += dom
|
domain += dom
|
||||||
|
|||||||
@@ -5,11 +5,22 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
from odoo import models
|
from odoo import models
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
from odoo.addons.base.models.assetsbundle import AssetsBundle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
re_background_image = re.compile(r"(background-image\s*:\s*url\(\s*['\"]?\s*)([^)'\"]+)")
|
re_background_image = re.compile(r"(background-image\s*:\s*url\(\s*['\"]?\s*)([^)'\"]+)")
|
||||||
|
|
||||||
|
|
||||||
|
class AssetsBundleMultiWebsite(AssetsBundle):
|
||||||
|
def _get_asset_url_values(self, id, unique, extra, name, page, type):
|
||||||
|
website_id = self.env.context.get('website_id')
|
||||||
|
website_id_path = website_id and ('%s/' % website_id) or ''
|
||||||
|
extra = website_id_path + extra
|
||||||
|
res = super(AssetsBundleMultiWebsite, self)._get_asset_url_values(id, unique, extra, name, page, type)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class QWeb(models.AbstractModel):
|
class QWeb(models.AbstractModel):
|
||||||
""" QWeb object for rendering stuff in the website context """
|
""" QWeb object for rendering stuff in the website context """
|
||||||
|
|
||||||
@@ -23,6 +34,9 @@ class QWeb(models.AbstractModel):
|
|||||||
'img': 'src',
|
'img': 'src',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_asset_bundle(self, xmlid, files, remains=None, env=None):
|
||||||
|
return AssetsBundleMultiWebsite(xmlid, files, remains=remains, env=env)
|
||||||
|
|
||||||
def _post_processing_att(self, tagName, atts, options):
|
def _post_processing_att(self, tagName, atts, options):
|
||||||
if atts.get('data-no-post-process'):
|
if atts.get('data-no-post-process'):
|
||||||
return atts
|
return atts
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class IrRule(models.Model):
|
||||||
|
_inherit = 'ir.rule'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _eval_context(self):
|
||||||
|
res = super(IrRule, self)._eval_context()
|
||||||
|
res['website_id'] = self.env['website'].get_current_website().id
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _compute_domain_keys(self):
|
||||||
|
""" Return the list of context keys to use for caching ``_compute_domain``. """
|
||||||
|
return super(IrRule, self)._compute_domain_keys() + ['website_id']
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
# -*- coding: ascii -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models, _
|
||||||
from odoo import tools
|
from odoo import tools
|
||||||
from odoo.addons.http_routing.models.ir_http import url_for
|
from odoo.addons.http_routing.models.ir_http import url_for
|
||||||
|
from odoo.osv import expression
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.tools import pycompat
|
from odoo.tools import pycompat
|
||||||
|
|
||||||
@@ -20,25 +21,92 @@ 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', 'view_id')
|
||||||
first_page_id = fields.Many2one('website.page', string='Website Page', help='First page linked to this view', compute='_compute_first_page_id')
|
first_page_id = fields.Many2one('website.page', string='Website Page', help='First page linked to this view', compute='_compute_first_page_id')
|
||||||
|
# theme_id = fields.Many2one('ir.module.module')
|
||||||
|
|
||||||
@api.one
|
@api.multi
|
||||||
def _compute_first_page_id(self):
|
def _compute_first_page_id(self):
|
||||||
self.first_page_id = self.env['website.page'].search([('view_id', '=', self.id)], limit=1)
|
for view in self:
|
||||||
|
view.first_page_id = self.env['website.page'].search([('view_id', '=', view.id)], limit=1)
|
||||||
|
|
||||||
@api.one
|
@api.multi
|
||||||
def _compute_page_ids(self):
|
def write(self, vals):
|
||||||
self.page_ids = self.env['website.page'].search(
|
'''COW for ir.ui.view. This way editing websites does not impact other
|
||||||
[('view_id', '=', self.id)]
|
websites. Also this way newly created websites will only
|
||||||
)
|
contain the default views.
|
||||||
|
'''
|
||||||
|
if not self._context.get('no_cow'):
|
||||||
|
current_website_id = self._context.get('website_id')
|
||||||
|
for view in self:
|
||||||
|
# if generic view in multi-website context
|
||||||
|
if current_website_id and not view.website_id:
|
||||||
|
new_website_specific_view = view.copy({'website_id': current_website_id})
|
||||||
|
view._create_website_specific_pages_for_view(new_website_specific_view,
|
||||||
|
view.env['website'].browse(current_website_id))
|
||||||
|
|
||||||
|
# trigger COW on inheriting views
|
||||||
|
for inherit_child in view.inherit_children_ids:
|
||||||
|
inherit_child.write({'inherit_id': new_website_specific_view.id})
|
||||||
|
|
||||||
|
new_website_specific_view.write(vals)
|
||||||
|
else:
|
||||||
|
super(View, view).write(vals)
|
||||||
|
else:
|
||||||
|
super(View, self).write(vals)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
|
'''This implements COU (copy-on-unlink). When deleting a generic page
|
||||||
|
website-specific pages will be created so only the current
|
||||||
|
website is affected.
|
||||||
|
'''
|
||||||
|
current_website_id = self._context.get('website_id')
|
||||||
|
|
||||||
|
if current_website_id and not self._context.get('no_cow'):
|
||||||
|
for view in self.filtered(lambda view: not view.website_id):
|
||||||
|
for website in self.env['website'].search([('id', '!=', current_website_id)]):
|
||||||
|
# reuse the COW mechanism to create
|
||||||
|
# website-specific copies, it will take
|
||||||
|
# care of creating pages and menus.
|
||||||
|
view.with_context(website_id=website.id).write({'name': view.name})
|
||||||
|
|
||||||
result = super(View, self).unlink()
|
result = super(View, self).unlink()
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _create_website_specific_pages_for_view(self, new_view, website):
|
||||||
|
for page in self.page_ids:
|
||||||
|
# create new pages for this view
|
||||||
|
page.copy({
|
||||||
|
'view_id': new_view.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_related_views(self, key, bundles=False):
|
||||||
|
'''Make this only return most specific views for website.'''
|
||||||
|
# get_related_views can be called through website=False routes
|
||||||
|
# (e.g. /web_editor/get_assets_editor_resources), so website
|
||||||
|
# dispatch_parameters may not be added. Manually set
|
||||||
|
# website_id.
|
||||||
|
self = self.with_context(website_id=self.env['website'].get_current_website().id)
|
||||||
|
views = super(View, self).get_related_views(key, bundles=bundles)
|
||||||
|
current_website_id = self._context.get('website_id')
|
||||||
|
most_specific_views = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
if not current_website_id:
|
||||||
|
return views
|
||||||
|
|
||||||
|
for view in views:
|
||||||
|
if view.website_id and view.website_id.id == current_website_id:
|
||||||
|
most_specific_views |= view
|
||||||
|
elif not view.website_id and not any(view.key == view2.key and view2.website_id and view2.website_id.id == current_website_id for view2 in views):
|
||||||
|
most_specific_views |= view
|
||||||
|
|
||||||
|
return most_specific_views
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _sort_suitability_key(self):
|
def _sort_suitability_key(self):
|
||||||
""" Key function to sort views by descending suitability
|
""" Key function to sort views by descending suitability
|
||||||
@@ -55,15 +123,15 @@ class View(models.Model):
|
|||||||
def filter_duplicate(self):
|
def filter_duplicate(self):
|
||||||
""" Filter current recordset only keeping the most suitable view per distinct key """
|
""" Filter current recordset only keeping the most suitable view per distinct key """
|
||||||
filtered = self.env['ir.ui.view']
|
filtered = self.env['ir.ui.view']
|
||||||
for dummy, group in groupby(self, key=lambda record: record.key):
|
for dummy, group in groupby(self.sorted('key'), key=lambda record: record.key):
|
||||||
filtered += sorted(group, key=lambda record: record._sort_suitability_key())[0]
|
filtered += sorted(group, key=lambda record: record._sort_suitability_key())[0]
|
||||||
return filtered
|
return filtered.sorted(key=lambda view: (view.priority, view.id))
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _view_obj(self, view_id):
|
def _view_obj(self, view_id):
|
||||||
if isinstance(view_id, pycompat.string_types):
|
if isinstance(view_id, pycompat.string_types):
|
||||||
if 'website_id' in self._context:
|
if 'website_id' in self._context:
|
||||||
domain = [('key', '=', view_id), '|', ('website_id', '=', False), ('website_id', '=', self._context.get('website_id'))]
|
domain = [('key', '=', view_id)] + self.env['website'].website_domain(self._context.get('website_id'))
|
||||||
order = 'website_id'
|
order = 'website_id'
|
||||||
else:
|
else:
|
||||||
domain = [('key', '=', view_id)]
|
domain = [('key', '=', view_id)]
|
||||||
@@ -79,16 +147,41 @@ class View(models.Model):
|
|||||||
# assume it's already a view object (WTF?)
|
# assume it's already a view object (WTF?)
|
||||||
return view_id
|
return view_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_inheriting_views_arch_website(self, view_id):
|
||||||
|
return self.env['website'].browse(self._context.get('website_id'))
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_inheriting_views_arch_domain(self, view_id, model):
|
def _get_inheriting_views_arch_domain(self, view_id, model):
|
||||||
domain = super(View, self)._get_inheriting_views_arch_domain(view_id, model)
|
domain = super(View, self)._get_inheriting_views_arch_domain(view_id, model)
|
||||||
return ['|', ('website_id', '=', False), ('website_id', '=', self.env.context.get('website_id'))] + domain
|
current_website = self._get_inheriting_views_arch_website(view_id)
|
||||||
|
website_views_domain = current_website.website_domain()
|
||||||
|
# when rendering for the website we have to include inactive views
|
||||||
|
# we will prefer inactive website-specific views over active generic ones
|
||||||
|
if current_website:
|
||||||
|
domain = [leaf for leaf in domain if 'active' not in leaf]
|
||||||
|
|
||||||
|
return expression.AND([website_views_domain, domain])
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_inheriting_views_arch(self, view_id, model):
|
||||||
|
if not self._context.get('website_id'):
|
||||||
|
return super(View, self).get_inheriting_views_arch(view_id, model)
|
||||||
|
|
||||||
|
inheriting_views = super(View, self.with_context(active_test=False)).get_inheriting_views_arch(view_id, model)
|
||||||
|
|
||||||
|
# prefer inactive website-specific views over active generic ones
|
||||||
|
inheriting_views = self.browse([view[1] for view in inheriting_views]).filter_duplicate().filtered('active')
|
||||||
|
|
||||||
|
return [(view.arch, view.id) for view in inheriting_views]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
|
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
|
||||||
def get_view_id(self, xml_id):
|
def get_view_id(self, xml_id):
|
||||||
if 'website_id' in self._context and not isinstance(xml_id, pycompat.integer_types):
|
if 'website_id' in self._context and not isinstance(xml_id, pycompat.integer_types):
|
||||||
domain = [('key', '=', xml_id), '|', ('website_id', '=', self._context['website_id']), ('website_id', '=', False)]
|
current_website = self.env['website'].browse(self._context.get('website_id'))
|
||||||
|
domain = ['&', ('key', '=', xml_id)] + current_website.website_domain()
|
||||||
|
|
||||||
view = self.search(domain, order='website_id', limit=1)
|
view = self.search(domain, order='website_id', limit=1)
|
||||||
if not view:
|
if not view:
|
||||||
_logger.warning("Could not find view object with xml_id '%s'", xml_id)
|
_logger.warning("Could not find view object with xml_id '%s'", xml_id)
|
||||||
@@ -125,6 +218,7 @@ class View(models.Model):
|
|||||||
qcontext = super(View, self)._prepare_qcontext()
|
qcontext = super(View, self)._prepare_qcontext()
|
||||||
|
|
||||||
if request and getattr(request, 'is_frontend', False):
|
if request and getattr(request, 'is_frontend', False):
|
||||||
|
Website = self.env['website']
|
||||||
editable = request.website.is_publisher()
|
editable = request.website.is_publisher()
|
||||||
translatable = editable and self._context.get('lang') != request.env['ir.http']._get_default_lang().code
|
translatable = editable and self._context.get('lang') != request.env['ir.http']._get_default_lang().code
|
||||||
editable = not translatable and editable
|
editable = not translatable and editable
|
||||||
@@ -132,6 +226,17 @@ class View(models.Model):
|
|||||||
if 'main_object' not in qcontext:
|
if 'main_object' not in qcontext:
|
||||||
qcontext['main_object'] = self
|
qcontext['main_object'] = self
|
||||||
|
|
||||||
|
domain_based_info = {'website_id': '', 'name': _('Domain Based')}
|
||||||
|
force_website_id = request.session.get('force_website_id', False)
|
||||||
|
if force_website_id:
|
||||||
|
selected_website = Website.browse(force_website_id)
|
||||||
|
qcontext['multi_website_selected_website'] = {'website_id': selected_website.id, 'name': selected_website.name}
|
||||||
|
else:
|
||||||
|
qcontext['multi_website_selected_website'] = domain_based_info
|
||||||
|
|
||||||
|
qcontext['multi_website_websites'] = [{'website_id': website.id, 'name': website.name} for website in Website.search([])]
|
||||||
|
qcontext['multi_website_websites'] += [domain_based_info]
|
||||||
|
|
||||||
qcontext.update(dict(
|
qcontext.update(dict(
|
||||||
self._context.copy(),
|
self._context.copy(),
|
||||||
website=request.website,
|
website=request.website,
|
||||||
@@ -162,3 +267,6 @@ class View(models.Model):
|
|||||||
'url': '/website/pages',
|
'url': '/website/pages',
|
||||||
'target': 'self',
|
'target': 'self',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _read_template_keys(self):
|
||||||
|
return super(View, self)._read_template_keys() + ['website_id']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
class Company(models.Model):
|
class Company(models.Model):
|
||||||
@@ -16,3 +16,19 @@ class Company(models.Model):
|
|||||||
def google_map_link(self, zoom=8):
|
def google_map_link(self, zoom=8):
|
||||||
partner = self.sudo().partner_id
|
partner = self.sudo().partner_id
|
||||||
return partner and partner.google_map_link(zoom) or None
|
return partner and partner.google_map_link(zoom) or None
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_public_user(self):
|
||||||
|
self.ensure_one()
|
||||||
|
public_users = self.env.ref('base.group_public').with_context(active_test=False).users
|
||||||
|
public_users_for_website = public_users.filtered(lambda user: user.company_id == self)
|
||||||
|
|
||||||
|
if public_users_for_website:
|
||||||
|
return public_users_for_website[0]
|
||||||
|
else:
|
||||||
|
return self.env.ref('base.public_user').copy({
|
||||||
|
'name': 'Public user for %s' % self.name,
|
||||||
|
'login': 'public_company_%s' % self.id,
|
||||||
|
'company_id': self.id,
|
||||||
|
'company_ids': [(6, 0, [self.id])],
|
||||||
|
})
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
website_id = fields.Many2one('website', string="website",
|
website_id = fields.Many2one('website', string="website",
|
||||||
default=_default_website, required=True, ondelete='cascade')
|
default=_default_website, required=True, ondelete='cascade')
|
||||||
website_name = fields.Char('Website Name', related='website_id.name')
|
website_name = fields.Char('Website Name', related='website_id.name')
|
||||||
|
website_domain = fields.Char('Website Domain', related='website_id.domain')
|
||||||
|
website_country_group_ids = fields.Many2many(related='website_id.country_group_ids')
|
||||||
|
website_company_id = fields.Many2one(related='website_id.company_id', string='Website Company')
|
||||||
language_ids = fields.Many2many(related='website_id.language_ids', relation='res.lang')
|
language_ids = fields.Many2many(related='website_id.language_ids', relation='res.lang')
|
||||||
language_count = fields.Integer(string='Number of languages', compute='_compute_language_count', readonly=True)
|
language_count = fields.Integer(string='Number of languages', compute='_compute_language_count', readonly=True)
|
||||||
website_default_lang_id = fields.Many2one(
|
website_default_lang_id = fields.Many2one(
|
||||||
@@ -25,27 +28,35 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
website_default_lang_code = fields.Char(
|
website_default_lang_code = fields.Char(
|
||||||
'Default language code', related='website_id.default_lang_code',
|
'Default language code', related='website_id.default_lang_code',
|
||||||
oldname='default_lang_code')
|
oldname='default_lang_code')
|
||||||
|
specific_user_account = fields.Boolean('Specific User Account', config_parameter='website_id.specific_user_account',
|
||||||
|
help='Are newly created user accounts website specific')
|
||||||
|
|
||||||
google_analytics_key = fields.Char('Google Analytics Key', related='website_id.google_analytics_key')
|
google_analytics_key = fields.Char('Google Analytics Key', related='website_id.google_analytics_key')
|
||||||
google_management_client_id = fields.Char('Google Client ID', related='website_id.google_management_client_id')
|
google_management_client_id = fields.Char('Google Client ID', related='website_id.google_management_client_id')
|
||||||
google_management_client_secret = fields.Char('Google Client Secret', related='website_id.google_management_client_secret')
|
google_management_client_secret = fields.Char('Google Client Secret', related='website_id.google_management_client_secret')
|
||||||
|
|
||||||
cdn_activated = fields.Boolean('Use a Content Delivery Network (CDN)', related='website_id.cdn_activated')
|
cdn_activated = fields.Boolean(related='website_id.cdn_activated')
|
||||||
cdn_url = fields.Char(related='website_id.cdn_url')
|
cdn_url = fields.Char(related='website_id.cdn_url')
|
||||||
cdn_filters = fields.Text(related='website_id.cdn_filters')
|
cdn_filters = fields.Text(related='website_id.cdn_filters')
|
||||||
module_website_version = fields.Boolean("A/B Testing")
|
module_website_version = fields.Boolean("A/B Testing")
|
||||||
module_website_links = fields.Boolean(string="Link Trackers")
|
module_website_links = fields.Boolean("Link Trackers")
|
||||||
|
auth_signup_uninvited = fields.Selection("Customer Account", related='website_id.auth_signup_uninvited')
|
||||||
|
|
||||||
favicon = fields.Binary('Favicon', related='website_id.favicon')
|
favicon = fields.Binary('Favicon', related='website_id.favicon')
|
||||||
# Set as global config parameter since methods using it are not website-aware. To be changed
|
|
||||||
# when multi-website is implemented
|
google_maps_api_key = fields.Char(related='website_id.google_maps_api_key')
|
||||||
google_maps_api_key = fields.Char(string='Google Maps API Key', config_parameter='google_maps_api_key', default='')
|
has_google_analytics = fields.Boolean(related='website_id.has_google_analytics')
|
||||||
has_google_analytics = fields.Boolean("Google Analytics", config_parameter='website.has_google_analytics')
|
has_google_analytics_dashboard = fields.Boolean(related='website_id.has_google_analytics_dashboard')
|
||||||
has_google_analytics_dashboard = fields.Boolean("Embedded Google Analytics", config_parameter='website.has_google_analytics_dashboard')
|
has_google_maps = fields.Boolean(related='website_id.has_google_maps')
|
||||||
has_google_maps = fields.Boolean("Google Maps", config_parameter='website.has_google_maps')
|
|
||||||
auth_signup_uninvited = fields.Selection([
|
social_twitter = fields.Char(related='website_id.social_twitter')
|
||||||
('b2b', 'On invitation (B2B)'),
|
social_facebook = fields.Char(related='website_id.social_facebook')
|
||||||
('b2c', 'Free sign up (B2C)'),
|
social_github = fields.Char(related='website_id.social_github')
|
||||||
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
|
social_linkedin = fields.Char(related='website_id.social_linkedin')
|
||||||
|
social_youtube = fields.Char(related='website_id.social_youtube')
|
||||||
|
social_googleplus = fields.Char(related='website_id.social_googleplus')
|
||||||
|
|
||||||
|
group_multi_website = fields.Boolean("Multi-website", implied_group="website.group_multi_website")
|
||||||
|
|
||||||
@api.onchange('has_google_analytics')
|
@api.onchange('has_google_analytics')
|
||||||
def onchange_has_google_analytics(self):
|
def onchange_has_google_analytics(self):
|
||||||
@@ -83,3 +94,11 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
action['res_id'] = literal_eval(self.env['ir.config_parameter'].sudo().get_param('base.template_portal_user_id', 'False'))
|
action['res_id'] = literal_eval(self.env['ir.config_parameter'].sudo().get_param('base.template_portal_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
|
||||||
|
|
||||||
|
def website_go_to(self):
|
||||||
|
self.website_id._force()
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': '/',
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
import werkzeug
|
import werkzeug
|
||||||
|
|
||||||
from odoo import api, models
|
from odoo import api, fields, models
|
||||||
|
from odoo.tools.pycompat import izip
|
||||||
|
|
||||||
|
|
||||||
def urlplus(url, params):
|
def urlplus(url, params):
|
||||||
@@ -11,12 +12,13 @@ def urlplus(url, params):
|
|||||||
|
|
||||||
|
|
||||||
class Partner(models.Model):
|
class Partner(models.Model):
|
||||||
|
|
||||||
_inherit = "res.partner"
|
_inherit = "res.partner"
|
||||||
|
|
||||||
|
website_id = fields.Many2one('website', string='Registration Website')
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def google_map_img(self, zoom=8, width=298, height=298):
|
def google_map_img(self, zoom=8, width=298, height=298):
|
||||||
google_maps_api_key = self.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
|
google_maps_api_key = self.env['website'].get_current_website().google_maps_api_key
|
||||||
if not google_maps_api_key:
|
if not google_maps_api_key:
|
||||||
return False
|
return False
|
||||||
params = {
|
params = {
|
||||||
@@ -35,3 +37,19 @@ class Partner(models.Model):
|
|||||||
'z': zoom,
|
'z': zoom,
|
||||||
}
|
}
|
||||||
return urlplus('https://maps.google.com/maps', params)
|
return urlplus('https://maps.google.com/maps', params)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_name(self):
|
||||||
|
name = super(Partner, self)._get_name()
|
||||||
|
if self._context.get('display_website') and self.env.user.has_group('website.group_multi_website'):
|
||||||
|
if self.website_id:
|
||||||
|
name += ' [%s]' % self.website_id.name
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _compute_display_name(self):
|
||||||
|
self2 = self.with_context(display_website=False)
|
||||||
|
super(Partner, self2)._compute_display_name()
|
||||||
|
|
||||||
|
# onchange uses the cache to retrieve value, we need to copy computed_value into the initial env
|
||||||
|
for record, record2 in izip(self, self2):
|
||||||
|
record.display_name = record2.display_name
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
import logging
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ResUsers(models.Model):
|
class ResUsers(models.Model):
|
||||||
@@ -11,6 +15,17 @@ class ResUsers(models.Model):
|
|||||||
selection=lambda self: self._get_group_selection('base.module_category_website'),
|
selection=lambda self: self._get_group_selection('base.module_category_website'),
|
||||||
string='Website Editor', compute='_compute_groups_id', inverse='_inverse_groups_id',
|
string='Website Editor', compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||||
category_xml_id='base.module_category_website')
|
category_xml_id='base.module_category_website')
|
||||||
|
has_group_multi_website = fields.Boolean(
|
||||||
|
'Multi-Websites',
|
||||||
|
compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||||
|
group_xml_id='website.group_multi_website')
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
# this is done in Python because a SQL constraint like UNIQUE
|
||||||
|
# (login, website_id) allows ('abc', NULL) and
|
||||||
|
# ('abc', NULL) to coexist because of how SQL handles NULLs.
|
||||||
|
('login_key', 'CHECK (1=1)', 'You can not have two users with the same login!')
|
||||||
|
]
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _has_unsplash_key_rights(self):
|
def _has_unsplash_key_rights(self):
|
||||||
@@ -18,3 +33,25 @@ class ResUsers(models.Model):
|
|||||||
if self.has_group('website.group_website_designer'):
|
if self.has_group('website.group_website_designer'):
|
||||||
return True
|
return True
|
||||||
return super(ResUsers, self)._has_unsplash_key_rights()
|
return super(ResUsers, self)._has_unsplash_key_rights()
|
||||||
|
|
||||||
|
@api.constrains('login', 'website_id')
|
||||||
|
def _check_login(self):
|
||||||
|
for user in self:
|
||||||
|
if self.search([('id', '!=', user.id), ('login', '=', user.login)] + user.website_id.website_domain()):
|
||||||
|
raise ValidationError(_('You can not have two users with the same login!'))
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_login_domain(self, login):
|
||||||
|
website = self.env['website'].get_current_website()
|
||||||
|
return super(ResUsers, self)._get_login_domain(login) + website.website_domain()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _signup_create_user(self, values):
|
||||||
|
new_user = super(ResUsers, self)._signup_create_user(values)
|
||||||
|
new_user.website_id = self.env['website'].get_current_website()
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_signup_invitation_scope(self):
|
||||||
|
current_website = self.env['website'].get_current_website()
|
||||||
|
return current_website.auth_signup_uninvited or super(ResUsers, self)._get_signup_invitation_scope()
|
||||||
|
|||||||
+326
-131
@@ -15,6 +15,7 @@ from odoo.addons.website.models.ir_http import sitemap_qs2dom
|
|||||||
from odoo.addons.portal.controllers.portal import pager
|
from odoo.addons.portal.controllers.portal import pager
|
||||||
from odoo.tools import pycompat
|
from odoo.tools import pycompat
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
from odoo.osv import expression
|
||||||
from odoo.osv.expression import FALSE_DOMAIN
|
from odoo.osv.expression import FALSE_DOMAIN
|
||||||
from odoo.tools.translate import _
|
from odoo.tools.translate import _
|
||||||
|
|
||||||
@@ -33,9 +34,13 @@ DEFAULT_CDN_FILTERS = [
|
|||||||
|
|
||||||
class Website(models.Model):
|
class Website(models.Model):
|
||||||
|
|
||||||
_name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
|
_name = "website"
|
||||||
_description = "Website"
|
_description = "Website"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def website_domain(self, website_id=False):
|
||||||
|
return [('website_id', 'in', (False, website_id or self.id))]
|
||||||
|
|
||||||
def _active_languages(self):
|
def _active_languages(self):
|
||||||
return self.env['res.lang'].search([]).ids
|
return self.env['res.lang'].search([]).ids
|
||||||
|
|
||||||
@@ -44,33 +49,64 @@ class Website(models.Model):
|
|||||||
def_lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
|
def_lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
|
||||||
return def_lang.id if def_lang else self._active_languages()[0]
|
return def_lang.id if def_lang else self._active_languages()[0]
|
||||||
|
|
||||||
|
def _default_social_twitter(self):
|
||||||
|
return self.env.ref('base.main_company').social_twitter
|
||||||
|
|
||||||
|
def _default_social_facebook(self):
|
||||||
|
return self.env.ref('base.main_company').social_facebook
|
||||||
|
|
||||||
|
def _default_social_github(self):
|
||||||
|
return self.env.ref('base.main_company').social_github
|
||||||
|
|
||||||
|
def _default_social_linkedin(self):
|
||||||
|
return self.env.ref('base.main_company').social_linkedin
|
||||||
|
|
||||||
|
def _default_social_youtube(self):
|
||||||
|
return self.env.ref('base.main_company').social_youtube
|
||||||
|
|
||||||
|
def _default_social_googleplus(self):
|
||||||
|
return self.env.ref('base.main_company').social_googleplus
|
||||||
|
|
||||||
name = fields.Char('Website Name')
|
name = fields.Char('Website Name')
|
||||||
domain = fields.Char('Website Domain')
|
domain = fields.Char('Website Domain')
|
||||||
|
country_group_ids = fields.Many2many('res.country.group', 'website_country_group_rel', 'website_id', 'country_group_id',
|
||||||
|
string='Country Groups', help='Used when multiple websites have the same domain.')
|
||||||
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.ref('base.main_company').id)
|
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.ref('base.main_company').id)
|
||||||
language_ids = fields.Many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages', default=_active_languages)
|
language_ids = fields.Many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages', default=_active_languages)
|
||||||
default_lang_id = fields.Many2one('res.lang', string="Default Language", default=_default_language, required=True)
|
default_lang_id = fields.Many2one('res.lang', string="Default Language", default=_default_language, required=True)
|
||||||
default_lang_code = fields.Char(related='default_lang_id.code', string="Default language code", store=True)
|
default_lang_code = fields.Char("Default language code", related='default_lang_id.code', store=True)
|
||||||
auto_redirect_lang = fields.Boolean('Autoredirect Language', default=True, help="Should users be redirected to their browser's language")
|
auto_redirect_lang = fields.Boolean('Autoredirect Language', default=True, help="Should users be redirected to their browser's language")
|
||||||
|
|
||||||
social_twitter = fields.Char(related="company_id.social_twitter")
|
social_twitter = fields.Char('Twitter Account', default=_default_social_twitter)
|
||||||
social_facebook = fields.Char(related="company_id.social_facebook")
|
social_facebook = fields.Char('Facebook Account', default=_default_social_facebook)
|
||||||
social_github = fields.Char(related="company_id.social_github")
|
social_github = fields.Char('GitHub Account', default=_default_social_github)
|
||||||
social_linkedin = fields.Char(related="company_id.social_linkedin")
|
social_linkedin = fields.Char('LinkedIn Account', default=_default_social_linkedin)
|
||||||
social_youtube = fields.Char(related="company_id.social_youtube")
|
social_youtube = fields.Char('Youtube Account', default=_default_social_youtube)
|
||||||
social_googleplus = fields.Char(related="company_id.social_googleplus")
|
social_googleplus = fields.Char('Google+ Account', default=_default_social_googleplus)
|
||||||
|
|
||||||
google_analytics_key = fields.Char('Google Analytics Key')
|
google_analytics_key = fields.Char('Google Analytics Key')
|
||||||
google_management_client_id = fields.Char('Google Client ID')
|
google_management_client_id = fields.Char('Google Client ID')
|
||||||
google_management_client_secret = fields.Char('Google Client Secret')
|
google_management_client_secret = fields.Char('Google Client Secret')
|
||||||
|
|
||||||
user_id = fields.Many2one('res.users', string='Public User', required=True, default=lambda self: self.env.ref('base.public_user').id)
|
google_maps_api_key = fields.Char('Google Maps API Key')
|
||||||
cdn_activated = fields.Boolean('Activate CDN for assets')
|
has_google_analytics = fields.Boolean("Google Analytics")
|
||||||
|
has_google_analytics_dashboard = fields.Boolean("Embedded Google Analytics")
|
||||||
|
has_google_maps = fields.Boolean("Google Maps")
|
||||||
|
|
||||||
|
user_id = fields.Many2one('res.users', string='Public User', required=True)
|
||||||
|
cdn_activated = fields.Boolean('Content Delivery Network (CDN)')
|
||||||
cdn_url = fields.Char('CDN Base URL', default='')
|
cdn_url = fields.Char('CDN Base URL', default='')
|
||||||
cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL")
|
cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL")
|
||||||
partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner')
|
partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner')
|
||||||
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.")
|
||||||
|
theme_id = fields.Many2one('ir.module.module', help='Installed theme')
|
||||||
|
|
||||||
|
auth_signup_uninvited = fields.Selection([
|
||||||
|
('b2b', 'On invitation'),
|
||||||
|
('b2c', 'Free sign up'),
|
||||||
|
], string='Customer Account', default='b2b')
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _compute_menu(self):
|
def _compute_menu(self):
|
||||||
@@ -82,18 +118,69 @@ class Website(models.Model):
|
|||||||
def noop(self, *args, **kwargs):
|
def noop(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if 'user_id' not in vals:
|
||||||
|
company = self.env['res.company'].browse(vals.get('company_id'))
|
||||||
|
vals['user_id'] = company._get_public_user().id if company else self.env.ref('base.public_user').id
|
||||||
|
|
||||||
|
res = super(Website, self).create(vals)
|
||||||
|
res._bootstrap_homepage()
|
||||||
|
return res
|
||||||
|
|
||||||
@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 'company_id' in values and 'user_id' not in values:
|
||||||
|
company = self.env['res.company'].browse(values['company_id'])
|
||||||
|
values['user_id'] = company._get_public_user().id
|
||||||
|
|
||||||
result = super(Website, self).write(values)
|
result = super(Website, self).write(values)
|
||||||
if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
|
if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
|
||||||
# invalidate the caches from static node at compile time
|
# invalidate the caches from static node at compile time
|
||||||
self.env['ir.qweb'].clear_caches()
|
self.env['ir.qweb'].clear_caches()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
#----------------------------------------------------------
|
@api.onchange('company_id')
|
||||||
|
def _onchange_company_id(self):
|
||||||
|
self.social_twitter = self.company_id.social_twitter
|
||||||
|
self.social_facebook = self.company_id.social_facebook
|
||||||
|
self.social_github = self.company_id.social_github
|
||||||
|
self.social_linkedin = self.company_id.social_linkedin
|
||||||
|
self.social_youtube = self.company_id.social_youtube
|
||||||
|
self.social_googleplus = self.company_id.social_googleplus
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
# Page Management
|
# Page Management
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
def _bootstrap_homepage(self):
|
||||||
|
standard_homepage = self.env.ref('website.homepage', raise_if_not_found=False)
|
||||||
|
if not standard_homepage:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_homepage_view = '''<t name="Homepage" t-name="website.homepage%s">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
%s
|
||||||
|
</t>
|
||||||
|
</t>''' % (self.id, self.env['ir.ui.view'].render_template('website.default_homepage', values={'website': self}).decode())
|
||||||
|
standard_homepage.with_context(website_id=self.id).arch_db = new_homepage_view
|
||||||
|
|
||||||
|
self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id),
|
||||||
|
('key', '=', standard_homepage.key)])
|
||||||
|
top_menu = self.env['website.menu'].create({
|
||||||
|
'name': _('Top Menu for website %s') % self.id,
|
||||||
|
'website_id': self.id,
|
||||||
|
'sequence': 0
|
||||||
|
})
|
||||||
|
self.menu_id = top_menu.id
|
||||||
|
self.env['website.menu'].create({
|
||||||
|
'name': _('Home'),
|
||||||
|
'url': '/',
|
||||||
|
'website_id': self.id,
|
||||||
|
'parent_id': top_menu.id,
|
||||||
|
'sequence': 10
|
||||||
|
})
|
||||||
|
|
||||||
@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
|
||||||
@@ -127,20 +214,21 @@ class Website(models.Model):
|
|||||||
if view.arch_fs:
|
if view.arch_fs:
|
||||||
view.arch_fs = False
|
view.arch_fs = False
|
||||||
|
|
||||||
|
website = self.get_current_website()
|
||||||
if ispage:
|
if ispage:
|
||||||
page = self.env['website.page'].create({
|
page = self.env['website.page'].create({
|
||||||
'url': page_url,
|
'url': page_url,
|
||||||
'website_ids': [(6, None, [self.get_current_website().id])],
|
'website_id': website.id, # remove it if only one webiste or not?
|
||||||
'view_id': view.id
|
'view_id': view.id,
|
||||||
})
|
})
|
||||||
result['view_id'] = view.id
|
result['view_id'] = view.id
|
||||||
if add_menu:
|
if add_menu:
|
||||||
self.env['website.menu'].create({
|
self.env['website.menu'].create({
|
||||||
'name': name,
|
'name': name,
|
||||||
'url': page_url,
|
'url': page_url,
|
||||||
'parent_id': self.get_current_website().menu_id.id,
|
'parent_id': website.menu_id.id,
|
||||||
'page_id': page.id,
|
'page_id': page.id,
|
||||||
'website_id': self.get_current_website().id,
|
'website_id': website.id,
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -152,9 +240,11 @@ class Website(models.Model):
|
|||||||
""" Given an url, return that url suffixed by counter if it already exists
|
""" Given an url, return that url suffixed by counter if it already exists
|
||||||
:param page_url : the url to be checked for uniqueness
|
:param page_url : the url to be checked for uniqueness
|
||||||
"""
|
"""
|
||||||
website_id = self.get_current_website().id
|
|
||||||
inc = 0
|
inc = 0
|
||||||
domain_static = ['|', ('website_ids', '=', False), ('website_ids', 'in', website_id)]
|
# we only want a unique_path for website specific.
|
||||||
|
# we need to be able to have /url for website=False, and /url for website=1
|
||||||
|
# in case of duplicate, page manager will allow you to manage this case
|
||||||
|
domain_static = [('website_id', '=', self.get_current_website().id)] # .website_domain()
|
||||||
page_temp = page_url
|
page_temp = page_url
|
||||||
while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static):
|
while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static):
|
||||||
inc += 1
|
inc += 1
|
||||||
@@ -167,17 +257,16 @@ class Website(models.Model):
|
|||||||
:param string : the key to be checked for uniqueness, you can pass it with 'website.' or not
|
:param string : the key to be checked for uniqueness, you can pass it with 'website.' or not
|
||||||
:param template_module : the module to be prefixed on the key, if not set, we will use website
|
:param template_module : the module to be prefixed on the key, if not set, we will use website
|
||||||
"""
|
"""
|
||||||
website_id = self.get_current_website().id
|
|
||||||
if template_module:
|
if template_module:
|
||||||
string = template_module + '.' + string
|
string = template_module + '.' + string
|
||||||
else:
|
else:
|
||||||
if not string.startswith('website.'):
|
if not string.startswith('website.'):
|
||||||
string = 'website.' + string
|
string = 'website.' + string
|
||||||
|
|
||||||
#Look for unique key
|
# Look for unique key
|
||||||
key_copy = string
|
key_copy = string
|
||||||
inc = 0
|
inc = 0
|
||||||
domain_static = ['|', ('website_ids', '=', False), ('website_ids', 'in', website_id)]
|
domain_static = self.get_current_website().website_domain()
|
||||||
while self.env['website.page'].with_context(active_test=False).sudo().search([('key', '=', key_copy)] + domain_static):
|
while self.env['website.page'].with_context(active_test=False).sudo().search([('key', '=', key_copy)] + domain_static):
|
||||||
inc += 1
|
inc += 1
|
||||||
key_copy = string + (inc and "-%s" % inc or "")
|
key_copy = string + (inc and "-%s" % inc or "")
|
||||||
@@ -185,10 +274,8 @@ class Website(models.Model):
|
|||||||
|
|
||||||
def key_to_view_id(self, view_id):
|
def key_to_view_id(self, view_id):
|
||||||
return self.env['ir.ui.view'].search([
|
return self.env['ir.ui.view'].search([
|
||||||
('id', '=', view_id),
|
('id', '=', view_id), ('type', '=', 'qweb')
|
||||||
'|', ('website_id', '=', self._context.get('website_id')), ('website_id', '=', False),
|
] + self.env['website'].website_domain(self._context.get('website_id')))
|
||||||
('type', '=', 'qweb')
|
|
||||||
])
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def page_search_dependencies(self, page_id=False):
|
def page_search_dependencies(self, page_id=False):
|
||||||
@@ -203,13 +290,11 @@ class Website(models.Model):
|
|||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
page = self.env['website.page'].browse(int(page_id))
|
page = self.env['website.page'].browse(int(page_id))
|
||||||
website_id = self._context.get('website_id')
|
website = self.env['website'].browse(self._context.get('website_id'))
|
||||||
url = page.url
|
url = page.url
|
||||||
|
|
||||||
# search for website_page with link
|
# search for website_page with link
|
||||||
website_page_search_dom = [
|
website_page_search_dom = [('view_id.arch_db', 'ilike', url)] + website.website_domain()
|
||||||
'|', ('website_ids', 'in', website_id), ('website_ids', '=', False), ('view_id.arch_db', 'ilike', url)
|
|
||||||
]
|
|
||||||
pages = self.env['website.page'].search(website_page_search_dom)
|
pages = self.env['website.page'].search(website_page_search_dom)
|
||||||
page_key = _('Page')
|
page_key = _('Page')
|
||||||
if len(pages) > 1:
|
if len(pages) > 1:
|
||||||
@@ -225,10 +310,7 @@ class Website(models.Model):
|
|||||||
page_view_ids.append(page.view_id.id)
|
page_view_ids.append(page.view_id.id)
|
||||||
|
|
||||||
# search for ir_ui_view (not from a website_page) with link
|
# search for ir_ui_view (not from a website_page) with link
|
||||||
page_search_dom = [
|
page_search_dom = [('arch_db', 'ilike', url), ('id', 'not in', page_view_ids)] + website.website_domain()
|
||||||
'|', ('website_id', '=', website_id), ('website_id', '=', False),
|
|
||||||
('arch_db', 'ilike', url), ('id', 'not in', page_view_ids)
|
|
||||||
]
|
|
||||||
views = self.env['ir.ui.view'].search(page_search_dom)
|
views = self.env['ir.ui.view'].search(page_search_dom)
|
||||||
view_key = _('Template')
|
view_key = _('Template')
|
||||||
if len(views) > 1:
|
if len(views) > 1:
|
||||||
@@ -241,9 +323,7 @@ class Website(models.Model):
|
|||||||
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
||||||
})
|
})
|
||||||
# search for menu with link
|
# search for menu with link
|
||||||
menu_search_dom = [
|
menu_search_dom = [('url', 'ilike', '%s' % url)] + website.website_domain()
|
||||||
'|', ('website_id', '=', website_id), ('website_id', '=', False), ('url', 'ilike', '%s' % url)
|
|
||||||
]
|
|
||||||
|
|
||||||
menus = self.env['website.menu'].search(menu_search_dom)
|
menus = self.env['website.menu'].search(menu_search_dom)
|
||||||
menu_key = _('Menu')
|
menu_key = _('Menu')
|
||||||
@@ -271,14 +351,14 @@ class Website(models.Model):
|
|||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
page = self.env['website.page'].browse(int(page_id))
|
page = self.env['website.page'].browse(int(page_id))
|
||||||
website_id = self._context.get('website_id')
|
website = self.env['website'].browse(self._context.get('website_id'))
|
||||||
key = page.key
|
key = page.key
|
||||||
|
|
||||||
# search for website_page with link
|
# search for website_page with link
|
||||||
website_page_search_dom = [
|
website_page_search_dom = [
|
||||||
'|', ('website_ids', 'in', website_id), ('website_ids', '=', False), ('view_id.arch_db', 'ilike', key),
|
('view_id.arch_db', 'ilike', key),
|
||||||
('id', '!=', page.id),
|
('id', '!=', page.id)
|
||||||
]
|
] + website.website_domain()
|
||||||
pages = self.env['website.page'].search(website_page_search_dom)
|
pages = self.env['website.page'].search(website_page_search_dom)
|
||||||
page_key = _('Page')
|
page_key = _('Page')
|
||||||
if len(pages) > 1:
|
if len(pages) > 1:
|
||||||
@@ -295,10 +375,9 @@ class Website(models.Model):
|
|||||||
|
|
||||||
# search for ir_ui_view (not from a website_page) with link
|
# search for ir_ui_view (not from a website_page) with link
|
||||||
page_search_dom = [
|
page_search_dom = [
|
||||||
'|', ('website_id', '=', website_id), ('website_id', '=', False),
|
|
||||||
('arch_db', 'ilike', key), ('id', 'not in', page_view_ids),
|
('arch_db', 'ilike', key), ('id', 'not in', page_view_ids),
|
||||||
('id', '!=', page.view_id.id),
|
('id', '!=', page.view_id.id),
|
||||||
]
|
] + website.website_domain()
|
||||||
views = self.env['ir.ui.view'].search(page_search_dom)
|
views = self.env['ir.ui.view'].search(page_search_dom)
|
||||||
view_key = _('Template')
|
view_key = _('Template')
|
||||||
if len(views) > 1:
|
if len(views) > 1:
|
||||||
@@ -306,26 +385,27 @@ class Website(models.Model):
|
|||||||
for view in views:
|
for view in views:
|
||||||
dependencies.setdefault(view_key, [])
|
dependencies.setdefault(view_key, [])
|
||||||
dependencies[view_key].append({
|
dependencies[view_key].append({
|
||||||
'text': _('Template <b>%s (id:%s)</b> is calling this file') % (view.key or view.name, view.id),
|
'text': _('Template <b>%s (id:%s)</b> is calling this file') % (view.key or view.name, view.id),
|
||||||
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
||||||
'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
|
'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
@api.model
|
# removed by 3c9e6c89e7207636c1bdab4ed8118c0c1089d43e
|
||||||
def page_exists(self, name, module='website'):
|
# @api.model
|
||||||
try:
|
# def page_exists(self, name, module='website'):
|
||||||
name = (name or "").replace("/website.", "").replace("/", "")
|
# try:
|
||||||
if not name:
|
# name = (name or "").replace("/website.", "").replace("/", "")
|
||||||
return False
|
# if not name:
|
||||||
return self.env.ref('%s.%s' % module, name)
|
# return False
|
||||||
except Exception:
|
# return self.env.ref('%s.%s' % module, name)
|
||||||
return False
|
# except Exception:
|
||||||
|
# return False
|
||||||
|
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
# Languages
|
# Languages
|
||||||
#----------------------------------------------------------
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
@@ -370,25 +450,46 @@ 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):
|
||||||
|
if request and request.session.get('force_website_id'):
|
||||||
|
return self.browse(request.session['force_website_id'])
|
||||||
|
|
||||||
domain_name = request and request.httprequest.environ.get('HTTP_HOST', '').split(':')[0] or None
|
domain_name = request and request.httprequest.environ.get('HTTP_HOST', '').split(':')[0] or None
|
||||||
website_id = self._get_current_website_id(domain_name)
|
|
||||||
|
country = request.session.geoip.get('country_code') if request and request.session.geoip else False
|
||||||
|
country_id = False
|
||||||
|
if country:
|
||||||
|
country_id = request.env['res.country'].search([('code', '=', country)], limit=1).id
|
||||||
|
|
||||||
|
website_id = self._get_current_website_id(domain_name, country_id)
|
||||||
if request:
|
if request:
|
||||||
request.context = dict(request.context, website_id=website_id)
|
request.context = dict(request.context, website_id=website_id)
|
||||||
return self.browse(website_id)
|
return self.browse(website_id)
|
||||||
|
|
||||||
@tools.cache('domain_name')
|
@tools.cache('domain_name', 'country_id')
|
||||||
def _get_current_website_id(self, domain_name):
|
def _get_current_website_id(self, domain_name, country_id):
|
||||||
""" Reminder : cached method should be return record, since they will use a closed cursor. """
|
# sort on country_group_ids so that we fall back on a generic website (empty country_group_ids)
|
||||||
website = self.search([('domain', '=', domain_name)], limit=1)
|
websites = self.search([('domain', '=', domain_name)]).sorted('country_group_ids')
|
||||||
if not website:
|
|
||||||
website = self.search([], limit=1)
|
if not websites:
|
||||||
return website.id
|
return self.search([], limit=1).id
|
||||||
|
elif len(websites) == 1:
|
||||||
|
return websites.id
|
||||||
|
else: # > 1 website with the same domain
|
||||||
|
country_specific_websites = websites.filtered(lambda website: country_id in website.country_group_ids.mapped('country_ids').ids)
|
||||||
|
return country_specific_websites[0].id if country_specific_websites else websites[0].id
|
||||||
|
|
||||||
|
def _force(self):
|
||||||
|
self._force_website(self.id)
|
||||||
|
|
||||||
|
def _force_website(self, website_id):
|
||||||
|
if request:
|
||||||
|
request.session['force_website_id'] = website_id and int(website_id)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def is_publisher(self):
|
def is_publisher(self):
|
||||||
@@ -429,13 +530,12 @@ class Website(models.Model):
|
|||||||
methods = endpoint.routing.get('methods') or ['GET']
|
methods = endpoint.routing.get('methods') or ['GET']
|
||||||
|
|
||||||
converters = list(rule._converters.values())
|
converters = list(rule._converters.values())
|
||||||
if not ('GET' in methods
|
if not ('GET' in methods and
|
||||||
and endpoint.routing['type'] == 'http'
|
endpoint.routing['type'] == 'http' and
|
||||||
and endpoint.routing['auth'] in ('none', 'public')
|
endpoint.routing['auth'] in ('none', 'public') and
|
||||||
and endpoint.routing.get('website', False)
|
endpoint.routing.get('website', False) and
|
||||||
and all(hasattr(converter, 'generate') for converter in converters)
|
all(hasattr(converter, 'generate') for converter in converters)):
|
||||||
and endpoint.routing.get('website')):
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
# dont't list routes without argument having no default value or converter
|
# dont't list routes without argument having no default value or converter
|
||||||
spec = inspect.getargspec(endpoint.method.original_func)
|
spec = inspect.getargspec(endpoint.method.original_func)
|
||||||
@@ -495,7 +595,7 @@ class Website(models.Model):
|
|||||||
for (i, (name, converter)) in enumerate(convitems):
|
for (i, (name, converter)) in enumerate(convitems):
|
||||||
newval = []
|
newval = []
|
||||||
for val in values:
|
for val in values:
|
||||||
query = i == len(convitems)-1 and query_string
|
query = i == len(convitems) - 1 and query_string
|
||||||
if query:
|
if query:
|
||||||
r = "".join([x[1] for x in rule._trace[1:] if not x[0]]) # remove model converter from route
|
r = "".join([x[1] for x in rule._trace[1:] if not x[0]]) # remove model converter from route
|
||||||
query = sitemap_qs2dom(query, r, self.env[converter.model]._rec_name)
|
query = sitemap_qs2dom(query, r, self.env[converter.model]._rec_name)
|
||||||
@@ -527,7 +627,7 @@ class Website(models.Model):
|
|||||||
domain = [('url', '!=', '/')]
|
domain = [('url', '!=', '/')]
|
||||||
if not force:
|
if not force:
|
||||||
domain += [('website_indexed', '=', True)]
|
domain += [('website_indexed', '=', True)]
|
||||||
#is_visible
|
# is_visible
|
||||||
domain += [('website_published', '=', True), '|', ('date_publish', '=', False), ('date_publish', '<=', fields.Datetime.now())]
|
domain += [('website_published', '=', True), '|', ('date_publish', '=', False), ('date_publish', '<=', fields.Datetime.now())]
|
||||||
|
|
||||||
if query_string:
|
if query_string:
|
||||||
@@ -545,7 +645,7 @@ class Website(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_website_pages(self, domain=[], order='name', limit=None):
|
def get_website_pages(self, domain=[], order='name', limit=None):
|
||||||
domain += ['|', ('website_ids', 'in', self.get_current_website().id), ('website_ids', '=', False)]
|
domain += self.get_current_website().website_domain()
|
||||||
pages = request.env['website.page'].search(domain, order='name', limit=limit)
|
pages = request.env['website.page'].search(domain, order='name', limit=limit)
|
||||||
return pages
|
return pages
|
||||||
|
|
||||||
@@ -595,11 +695,28 @@ class SeoMetadata(models.AbstractModel):
|
|||||||
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
|
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteMultiMixin(models.AbstractModel):
|
||||||
|
|
||||||
|
_name = 'website.multi.mixin'
|
||||||
|
|
||||||
|
website_id = fields.Many2one('website', string='Website', help='Restrict publishing to this website.')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def can_access_from_current_website(self, website_id=False):
|
||||||
|
can_access = True
|
||||||
|
for record in self:
|
||||||
|
if (website_id or record.website_id.id) not in (False, request.website.id):
|
||||||
|
can_access = False
|
||||||
|
continue
|
||||||
|
return can_access
|
||||||
|
|
||||||
|
|
||||||
class WebsitePublishedMixin(models.AbstractModel):
|
class WebsitePublishedMixin(models.AbstractModel):
|
||||||
|
|
||||||
_name = "website.published.mixin"
|
_name = "website.published.mixin"
|
||||||
|
|
||||||
website_published = fields.Boolean('Visible in Website', copy=False)
|
website_published = fields.Boolean('Visible on current website', related='is_published')
|
||||||
|
is_published = fields.Boolean('Is published')
|
||||||
website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.')
|
website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.')
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -611,6 +728,9 @@ class WebsitePublishedMixin(models.AbstractModel):
|
|||||||
def website_publish_button(self):
|
def website_publish_button(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#':
|
if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#':
|
||||||
|
# Force website to land on record's website to publish/unpublish it
|
||||||
|
if 'website_id' in self and self.env.user.has_group('website.group_multi_website'):
|
||||||
|
self.website_id._force()
|
||||||
return self.open_website_url()
|
return self.open_website_url()
|
||||||
return self.write({'website_published': not self.website_published})
|
return self.write({'website_published': not self.website_published})
|
||||||
|
|
||||||
@@ -622,14 +742,56 @@ class WebsitePublishedMixin(models.AbstractModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WebsitePublishedMultiMixin(WebsitePublishedMixin):
|
||||||
|
|
||||||
|
_name = 'website.published.multi.mixin'
|
||||||
|
_inherit = ['website.published.mixin', 'website.multi.mixin']
|
||||||
|
|
||||||
|
website_published = fields.Boolean(compute='_compute_website_published',
|
||||||
|
inverse='_inverse_website_published',
|
||||||
|
search='_search_website_published',
|
||||||
|
related=False)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
@api.depends('is_published', 'website_id')
|
||||||
|
def _compute_website_published(self):
|
||||||
|
current_website_id = self._context.get('website_id')
|
||||||
|
for record in self:
|
||||||
|
if current_website_id:
|
||||||
|
record.website_published = record.is_published and (not record.website_id or record.website_id.id == current_website_id)
|
||||||
|
else:
|
||||||
|
record.website_published = record.is_published
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _inverse_website_published(self):
|
||||||
|
for record in self:
|
||||||
|
record.is_published = record.website_published
|
||||||
|
|
||||||
|
def _search_website_published(self, operator, value):
|
||||||
|
if not isinstance(value, bool) or operator not in ('=', '!='):
|
||||||
|
logger.warning('unsupported search on website_published: %s, %s', operator, value)
|
||||||
|
return [()]
|
||||||
|
|
||||||
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
|
value = not value
|
||||||
|
|
||||||
|
current_website_id = self._context.get('website_id')
|
||||||
|
is_published = [('is_published', '=', value)]
|
||||||
|
if current_website_id:
|
||||||
|
on_current_website = self.env['website'].website_domain(current_website_id)
|
||||||
|
return (['!'] if value is False else []) + expression.AND([is_published, on_current_website])
|
||||||
|
else: # should be in the backend, return things that are published anywhere
|
||||||
|
return is_published
|
||||||
|
|
||||||
|
|
||||||
class Page(models.Model):
|
class Page(models.Model):
|
||||||
_name = 'website.page'
|
_name = 'website.page'
|
||||||
_inherits = {'ir.ui.view': 'view_id'}
|
_inherits = {'ir.ui.view': 'view_id'}
|
||||||
_inherit = 'website.published.mixin'
|
_inherit = 'website.published.multi.mixin'
|
||||||
_description = 'Page'
|
_description = 'Page'
|
||||||
|
_order = 'website_id'
|
||||||
|
|
||||||
url = fields.Char('Page URL')
|
url = fields.Char('Page URL')
|
||||||
website_ids = fields.Many2many('website', string='Websites')
|
|
||||||
view_id = fields.Many2one('ir.ui.view', string='View', required=True, ondelete="cascade")
|
view_id = fields.Many2one('ir.ui.view', string='View', required=True, ondelete="cascade")
|
||||||
website_indexed = fields.Boolean('Page Indexed', default=True)
|
website_indexed = fields.Boolean('Page Indexed', default=True)
|
||||||
date_publish = fields.Datetime('Publishing Date')
|
date_publish = fields.Datetime('Publishing Date')
|
||||||
@@ -642,6 +804,9 @@ class Page(models.Model):
|
|||||||
header_overlay = fields.Boolean()
|
header_overlay = fields.Boolean()
|
||||||
header_color = fields.Char()
|
header_color = fields.Char()
|
||||||
|
|
||||||
|
# don't use mixin website_id but use website_id on ir.ui.view instead
|
||||||
|
website_id = fields.Many2one(related='view_id.website_id', store=True)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def _compute_homepage(self):
|
def _compute_homepage(self):
|
||||||
self.is_homepage = self == self.env['website'].get_current_website().homepage_id
|
self.is_homepage = self == self.env['website'].get_current_website().homepage_id
|
||||||
@@ -660,11 +825,21 @@ class Page(models.Model):
|
|||||||
def _compute_visible(self):
|
def _compute_visible(self):
|
||||||
self.is_visible = self.website_published and (not self.date_publish or self.date_publish < fields.Datetime.now())
|
self.is_visible = self.website_published and (not self.date_publish or self.date_publish < fields.Datetime.now())
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _is_most_specific_page(self, page_to_test):
|
||||||
|
'''This will test if page_to_test is the most specific page in self.'''
|
||||||
|
pages_for_url = self.sorted(key=lambda p: not p.website_id).filtered(lambda page: page.url == page_to_test.url)
|
||||||
|
|
||||||
|
# this works because pages are _order'ed by website_id
|
||||||
|
most_specific_page = pages_for_url[0]
|
||||||
|
|
||||||
|
return most_specific_page == page_to_test
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_page_info(self, id, website_id):
|
def get_page_info(self, id):
|
||||||
domain = ['|', ('website_ids', 'in', website_id), ('website_ids', '=', False), ('id', '=', id)]
|
return self.browse(id).read(
|
||||||
item = self.search_read(domain, fields=['id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish', 'menu_ids', 'is_homepage'], limit=1)
|
['id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish', 'menu_ids', 'is_homepage', 'website_id'],
|
||||||
return item
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_view_identifier(self):
|
def get_view_identifier(self):
|
||||||
@@ -676,7 +851,7 @@ class Page(models.Model):
|
|||||||
website = self.env['website'].browse(website_id)
|
website = self.env['website'].browse(website_id)
|
||||||
page = self.browse(int(data['id']))
|
page = self.browse(int(data['id']))
|
||||||
|
|
||||||
#If URL has been edited, slug it
|
# If URL has been edited, slug it
|
||||||
original_url = page.url
|
original_url = page.url
|
||||||
url = data['url']
|
url = data['url']
|
||||||
if not url.startswith('/'):
|
if not url.startswith('/'):
|
||||||
@@ -685,7 +860,7 @@ class Page(models.Model):
|
|||||||
url = '/' + slugify(url, max_length=1024, path=True)
|
url = '/' + slugify(url, max_length=1024, path=True)
|
||||||
url = self.env['website'].get_unique_path(url)
|
url = self.env['website'].get_unique_path(url)
|
||||||
|
|
||||||
#If name has changed, check for key uniqueness
|
# If name has changed, check for key uniqueness
|
||||||
if page.name != data['name']:
|
if page.name != data['name']:
|
||||||
page_key = self.env['website'].get_unique_key(slugify(data['name']))
|
page_key = self.env['website'].get_unique_key(slugify(data['name']))
|
||||||
else:
|
else:
|
||||||
@@ -693,11 +868,11 @@ class Page(models.Model):
|
|||||||
|
|
||||||
menu = self.env['website.menu'].search([('page_id', '=', int(data['id']))])
|
menu = self.env['website.menu'].search([('page_id', '=', int(data['id']))])
|
||||||
if not data['is_menu']:
|
if not data['is_menu']:
|
||||||
#If the page is no longer in menu, we should remove its website_menu
|
# If the page is no longer in menu, we should remove its website_menu
|
||||||
if menu:
|
if menu:
|
||||||
menu.unlink()
|
menu.unlink()
|
||||||
else:
|
else:
|
||||||
#The page is now a menu, check if has already one
|
# The page is now a menu, check if has already one
|
||||||
if menu:
|
if menu:
|
||||||
menu.write({'url': url})
|
menu.write({'url': url})
|
||||||
else:
|
else:
|
||||||
@@ -709,15 +884,23 @@ class Page(models.Model):
|
|||||||
'website_id': website.id,
|
'website_id': website.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
page.write({
|
# Edits via the page manager shouldn't trigger the COW
|
||||||
|
# mechanism and generate new pages. The user manages page
|
||||||
|
# visibility manually with is_published here.
|
||||||
|
w_vals = {
|
||||||
'key': page_key,
|
'key': page_key,
|
||||||
'name': data['name'],
|
'name': data['name'],
|
||||||
'url': url,
|
'url': url,
|
||||||
'website_published': data['website_published'],
|
'is_published': data['website_published'],
|
||||||
|
'website_id': False if data['share_page_info'] else website.id,
|
||||||
'website_indexed': data['website_indexed'],
|
'website_indexed': data['website_indexed'],
|
||||||
'date_publish': data['date_publish'] or None,
|
'date_publish': data['date_publish'] or None,
|
||||||
'is_homepage': data['is_homepage'],
|
'is_homepage': data['is_homepage'],
|
||||||
})
|
}
|
||||||
|
# toggle is hidden to prevent user to unshare a page
|
||||||
|
if 'share_page_info' in data:
|
||||||
|
w_vals['website_id'] = False if data['share_page_info'] else website.id
|
||||||
|
page.with_context(no_cow=True).write(w_vals)
|
||||||
|
|
||||||
# Create redirect if needed
|
# Create redirect if needed
|
||||||
if data['create_redirect']:
|
if data['create_redirect']:
|
||||||
@@ -733,17 +916,14 @@ class Page(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
@api.returns('self', lambda value: value.id)
|
@api.returns('self', lambda value: value.id)
|
||||||
def copy(self, default=None):
|
def copy(self, default=None):
|
||||||
view = self.env['ir.ui.view'].browse(self.view_id.id)
|
if default:
|
||||||
# website.page's ir.ui.view should have a different key than the one it
|
if not default.get('view_id'):
|
||||||
# is copied from.
|
view = self.env['ir.ui.view'].browse(self.view_id.id)
|
||||||
# (eg: website_version: an ir.ui.view record with the same key is
|
new_view = view.copy({'website_id': default.get('website_id')})
|
||||||
# expected to be the same ir.ui.view but from another version)
|
default['view_id'] = new_view.id
|
||||||
new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)'))})
|
|
||||||
default = {
|
default['name'] = default.get('name', '%s %s' % (self.name, _('(copy)')))
|
||||||
'name': '%s %s' % (self.name, _('(copy)')),
|
default['url'] = default.get('url', self.env['website'].get_unique_path(self.url))
|
||||||
'url': self.env['website'].get_unique_path(self.url),
|
|
||||||
'view_id': new_view.id,
|
|
||||||
}
|
|
||||||
return super(Page, self).copy(default=default)
|
return super(Page, self).copy(default=default)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -752,53 +932,38 @@ class Page(models.Model):
|
|||||||
:param page_id : website.page identifier
|
:param page_id : website.page identifier
|
||||||
"""
|
"""
|
||||||
page = self.browse(int(page_id))
|
page = self.browse(int(page_id))
|
||||||
new_page = page.copy()
|
new_page = page.copy(dict(website_id=self.env['website'].get_current_website().id))
|
||||||
if clone_menu:
|
# Should not clone menu if the page was cloned from one website to another
|
||||||
|
# Eg: Cloning a generic page (no website) will create a page with a website, we can't clone menu (not same container)
|
||||||
|
if clone_menu and new_page.website_id == page.website_id:
|
||||||
menu = self.env['website.menu'].search([('page_id', '=', page_id)], limit=1)
|
menu = self.env['website.menu'].search([('page_id', '=', page_id)], limit=1)
|
||||||
if menu:
|
if menu:
|
||||||
# If the page being cloned has a menu, clone it too
|
# If the page being cloned has a menu, clone it too
|
||||||
new_menu = menu.copy()
|
new_menu = menu.copy()
|
||||||
new_menu.write({'url': new_page.url, 'name': '%s %s' % (menu.name, _('(copy)')), 'page_id': new_page.id})
|
new_menu.write({'url': new_page.url, 'name': '%s %s' % (menu.name, _('(copy)')), 'page_id': new_page.id})
|
||||||
|
|
||||||
return new_page.url + '?enable_editor=1'
|
return new_page.url + '?enable_editor=1'
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
""" When a website_page is deleted, the ORM does not delete its ir_ui_view.
|
# When a website_page is deleted, the ORM does not delete its
|
||||||
So we got to delete it ourself, but only if the ir_ui_view is not used by another website_page.
|
# ir_ui_view. So we got to delete it ourself, but only if the
|
||||||
"""
|
# ir_ui_view is not used by another website_page.
|
||||||
# Handle it's ir_ui_view
|
|
||||||
for page in self:
|
for page in self:
|
||||||
# Other pages linked to the ir_ui_view of the page being deleted (will it even be possible?)
|
# Other pages linked to the ir_ui_view of the page being deleted (will it even be possible?)
|
||||||
pages_linked_to_iruiview = self.search(
|
pages_linked_to_iruiview = self.search(
|
||||||
[('view_id', '=', self.view_id.id), ('id', '!=', self.id)]
|
[('view_id', '=', page.view_id.id), ('id', '!=', page.id)]
|
||||||
)
|
)
|
||||||
if len(pages_linked_to_iruiview) == 0:
|
if len(pages_linked_to_iruiview) == 0 and not page.view_id.inherit_children_ids:
|
||||||
# If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view
|
# If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view
|
||||||
self.env['ir.ui.view'].search([('id', '=', self.view_id.id)]).unlink()
|
page.view_id.unlink()
|
||||||
# And then delete the website_page itself
|
|
||||||
return super(Page, self).unlink()
|
return super(Page, self).unlink()
|
||||||
|
|
||||||
@api.model
|
|
||||||
def delete_page(self, page_id):
|
|
||||||
""" Delete a page, given its identifier
|
|
||||||
:param page_id : website.page identifier
|
|
||||||
"""
|
|
||||||
# If we are deleting a page (that could possibly be a menu with a page)
|
|
||||||
page = self.env['website.page'].browse(int(page_id))
|
|
||||||
if page:
|
|
||||||
# Check if it is a menu with a page and also delete menu if so
|
|
||||||
menu = self.env['website.menu'].search([('page_id', '=', page.id)], limit=1)
|
|
||||||
if menu:
|
|
||||||
menu.unlink()
|
|
||||||
page.unlink()
|
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if 'url' in vals and not vals['url'].startswith('/'):
|
if 'url' in vals and not vals['url'].startswith('/'):
|
||||||
vals['url'] = '/' + vals['url']
|
vals['url'] = '/' + vals['url']
|
||||||
result = super(Page, self).write(vals)
|
return super(Page, self).write(vals)
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Menu(models.Model):
|
class Menu(models.Model):
|
||||||
@@ -815,15 +980,45 @@ class Menu(models.Model):
|
|||||||
|
|
||||||
name = fields.Char('Menu', required=True, translate=True)
|
name = fields.Char('Menu', required=True, translate=True)
|
||||||
url = fields.Char('Url', default='')
|
url = fields.Char('Url', default='')
|
||||||
page_id = fields.Many2one('website.page', 'Related Page')
|
page_id = fields.Many2one('website.page', 'Related Page', ondelete='cascade')
|
||||||
new_window = fields.Boolean('New Window')
|
new_window = fields.Boolean('New Window')
|
||||||
sequence = fields.Integer(default=_default_sequence)
|
sequence = fields.Integer(default=_default_sequence)
|
||||||
website_id = fields.Many2one('website', 'Website') # TODO: support multiwebsite once done for ir.ui.views
|
website_id = fields.Many2one('website', 'Website')
|
||||||
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
|
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
|
||||||
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
|
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
|
||||||
parent_path = fields.Char(index=True)
|
parent_path = fields.Char(index=True)
|
||||||
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
|
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for menu in self:
|
||||||
|
website_suffix = '%s - %s' % (menu.name, menu.website_id.name)
|
||||||
|
res.append((menu.id, website_suffix if menu.website_id and self.env.user.has_group('website.group_multi_website') else menu.name))
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
''' In case a menu without a website_id is trying to be created, we duplicate
|
||||||
|
it for every website.
|
||||||
|
Note: Particulary useful when installing a module that adds a menu like
|
||||||
|
/shop. So every website has the shop menu.
|
||||||
|
'''
|
||||||
|
if vals.get('website_id'):
|
||||||
|
return super(Menu, self).create(vals)
|
||||||
|
elif self._context.get('website_id'):
|
||||||
|
vals['website_id'] = self._context.get('website_id')
|
||||||
|
return super(Menu, self).create(vals)
|
||||||
|
else:
|
||||||
|
# create for every site
|
||||||
|
for website in self.env['website'].search([]):
|
||||||
|
vals.update({
|
||||||
|
'website_id': website.id,
|
||||||
|
'parent_id': website.menu_id.id,
|
||||||
|
})
|
||||||
|
res = super(Menu, self).create(vals)
|
||||||
|
return res # Only one record is returned but multiple could have been created
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def _compute_visible(self):
|
def _compute_visible(self):
|
||||||
visible = True
|
visible = True
|
||||||
@@ -886,7 +1081,7 @@ class Menu(models.Model):
|
|||||||
mid = menu['id']
|
mid = menu['id']
|
||||||
# new menu are prefixed by new-
|
# new menu are prefixed by new-
|
||||||
if isinstance(mid, pycompat.string_types):
|
if isinstance(mid, pycompat.string_types):
|
||||||
new_menu = self.create({'name': menu['name']})
|
new_menu = self.create({'name': menu['name'], 'website_id': website_id})
|
||||||
replace_id(mid, new_menu.id)
|
replace_id(mid, new_menu.id)
|
||||||
for menu in data['data']:
|
for menu in data['data']:
|
||||||
menu_id = self.browse(menu['id'])
|
menu_id = self.browse(menu['id'])
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
|||||||
return this._rpc({
|
return this._rpc({
|
||||||
route: '/website/fetch_dashboard_data',
|
route: '/website/fetch_dashboard_data',
|
||||||
params: {
|
params: {
|
||||||
|
website_id: this.website_id || false,
|
||||||
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(),
|
||||||
},
|
},
|
||||||
@@ -72,6 +73,7 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
|||||||
self.dashboards_data = result.dashboards;
|
self.dashboards_data = result.dashboards;
|
||||||
self.currency_id = result.currency_id;
|
self.currency_id = result.currency_id;
|
||||||
self.groups = result.groups;
|
self.groups = result.groups;
|
||||||
|
self.websites = result.websites;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -110,6 +112,7 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
|||||||
return this._rpc({
|
return this._rpc({
|
||||||
route: '/website/dashboard/set_ga_data',
|
route: '/website/dashboard/set_ga_data',
|
||||||
params: {
|
params: {
|
||||||
|
'website_id': self.website_id,
|
||||||
'ga_client_id': ga_client_id,
|
'ga_client_id': ga_client_id,
|
||||||
'ga_analytics_key': ga_analytics_key,
|
'ga_analytics_key': ga_analytics_key,
|
||||||
},
|
},
|
||||||
@@ -245,6 +248,16 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
on_website_button: function(website_id) {
|
||||||
|
var self = this;
|
||||||
|
this.website_id = website_id;
|
||||||
|
$.when(this.fetch_data()).then(function() {
|
||||||
|
self.$('.o_website_dashboard').empty();
|
||||||
|
self.render_dashboards();
|
||||||
|
self.render_graphs();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
on_reverse_breadcrumb: function() {
|
on_reverse_breadcrumb: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
web_client.do_push_state({});
|
web_client.do_push_state({});
|
||||||
@@ -293,10 +306,15 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
|||||||
this.$searchview = $(QWeb.render("website.DateRangeButtons", {
|
this.$searchview = $(QWeb.render("website.DateRangeButtons", {
|
||||||
widget: this,
|
widget: this,
|
||||||
}));
|
}));
|
||||||
this.$searchview.click('button.js_date_range', function(ev) {
|
this.$searchview.find('button.js_date_range').click(function(ev) {
|
||||||
self.on_date_range_button($(ev.target).data('date'));
|
self.$searchview.find('button.js_date_range.active').removeClass('active');
|
||||||
$(this).find('button.js_date_range.active').removeClass('active');
|
|
||||||
$(ev.target).addClass('active');
|
$(ev.target).addClass('active');
|
||||||
|
self.on_date_range_button($(ev.target).data('date'));
|
||||||
|
});
|
||||||
|
this.$searchview.find('button.js_website').click(function(ev) {
|
||||||
|
self.$searchview.find('button.js_website.active').removeClass('active');
|
||||||
|
$(ev.target).addClass('active');
|
||||||
|
self.on_website_button($(ev.target).data('website-id'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.update_control_panel({
|
this.update_control_panel({
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ sAnimation.registry.affixMenu = sAnimation.Class.extend({
|
|||||||
|
|
||||||
// Reset opened menus
|
// Reset opened menus
|
||||||
this.$dropdowns.removeClass('show');
|
this.$dropdowns.removeClass('show');
|
||||||
this.$navbarCollapses.removeClass('in').attr('aria-expanded', false);
|
this.$navbarCollapses.removeClass('show').attr('aria-expanded', false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ var WebsiteRoot = BodyManager.extend({
|
|||||||
'click .js_publish_management .js_publish_btn': '_onPublishBtnClick',
|
'click .js_publish_management .js_publish_btn': '_onPublishBtnClick',
|
||||||
'submit .js_website_submit_form': '_onWebsiteFormSubmit',
|
'submit .js_website_submit_form': '_onWebsiteFormSubmit',
|
||||||
'click .js_disable_on_click': '_onDisableOnClick',
|
'click .js_disable_on_click': '_onDisableOnClick',
|
||||||
|
'click .js_multi_website_switch': '_multiWebsiteSwitch',
|
||||||
}),
|
}),
|
||||||
custom_events: _.extend({}, BodyManager.prototype.custom_events || {}, {
|
custom_events: _.extend({}, BodyManager.prototype.custom_events || {}, {
|
||||||
animation_start_demand: '_onAnimationStartDemand',
|
animation_start_demand: '_onAnimationStartDemand',
|
||||||
@@ -279,6 +280,23 @@ var WebsiteRoot = BodyManager.extend({
|
|||||||
_onDisableOnClick: function (ev) {
|
_onDisableOnClick: function (ev) {
|
||||||
$(ev.currentTarget).addClass('disabled');
|
$(ev.currentTarget).addClass('disabled');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when clicking on the multi-website switcher.
|
||||||
|
*
|
||||||
|
* @param {OdooEvent} ev
|
||||||
|
*/
|
||||||
|
_multiWebsiteSwitch: function (ev) {
|
||||||
|
var website_id_to_switch_to = ev.currentTarget.getAttribute('website-id');
|
||||||
|
this._rpc({
|
||||||
|
route: '/website/force_website',
|
||||||
|
params: {
|
||||||
|
website_id: website_id_to_switch_to || false,
|
||||||
|
},
|
||||||
|
}).then(function () {
|
||||||
|
window.location.reload(true);
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ var PagePropertiesDialog = weWidgets.Dialog.extend({
|
|||||||
defs.push(this._rpc({
|
defs.push(this._rpc({
|
||||||
model: 'website.page',
|
model: 'website.page',
|
||||||
method: 'get_page_info',
|
method: 'get_page_info',
|
||||||
args: [this.page_id, context.website_id],
|
args: [this.page_id],
|
||||||
context: context,
|
context: context,
|
||||||
}).then(function (page) {
|
}).then(function (page) {
|
||||||
page[0].url = _.str.startsWith(page[0].url, '/') ? page[0].url.substring(1) : page[0].url;
|
page[0].url = _.str.startsWith(page[0].url, '/') ? page[0].url.substring(1) : page[0].url;
|
||||||
@@ -145,6 +145,16 @@ var PagePropertiesDialog = weWidgets.Dialog.extend({
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
defs.push(this._rpc({model: 'res.users',
|
||||||
|
method: 'has_group',
|
||||||
|
args: ['website.group_multi_website'],
|
||||||
|
context: context})
|
||||||
|
.then(function (has_group) {
|
||||||
|
if (!has_group) {
|
||||||
|
self.$('#website_restriction').addClass('hidden');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
var datepickersOptions = {
|
var datepickersOptions = {
|
||||||
minDate: moment({y: 1900}),
|
minDate: moment({y: 1900}),
|
||||||
maxDate: moment().add(200, 'y'),
|
maxDate: moment().add(200, 'y'),
|
||||||
@@ -210,6 +220,7 @@ var PagePropertiesDialog = weWidgets.Dialog.extend({
|
|||||||
is_menu: this.$('#is_menu').prop('checked'),
|
is_menu: this.$('#is_menu').prop('checked'),
|
||||||
is_homepage: this.$('#is_homepage').prop('checked'),
|
is_homepage: this.$('#is_homepage').prop('checked'),
|
||||||
website_published: this.$('#is_published').prop('checked'),
|
website_published: this.$('#is_published').prop('checked'),
|
||||||
|
share_page_info: this.$('#share_page_info').prop('checked'),
|
||||||
create_redirect: this.$('#create_redirect').prop('checked'),
|
create_redirect: this.$('#create_redirect').prop('checked'),
|
||||||
redirect_type: this.$('#redirect_type').val(),
|
redirect_type: this.$('#redirect_type').val(),
|
||||||
website_indexed: this.$('#is_indexed').prop('checked'),
|
website_indexed: this.$('#is_indexed').prop('checked'),
|
||||||
@@ -531,7 +542,7 @@ var EditMenuDialog = weWidgets.Dialog.extend({
|
|||||||
this._rpc({
|
this._rpc({
|
||||||
model: 'website.menu',
|
model: 'website.menu',
|
||||||
method: 'save',
|
method: 'save',
|
||||||
args: [[context.website_id], { data: data, to_delete: self.to_delete }],
|
args: [context.website_id, { data: data, to_delete: self.to_delete }],
|
||||||
context: context,
|
context: context,
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
return _super();
|
return _super();
|
||||||
@@ -954,7 +965,7 @@ function _deletePage(pageId, fromPageManagement) {
|
|||||||
// Delete the page if the user confirmed
|
// Delete the page if the user confirmed
|
||||||
return self._rpc({
|
return self._rpc({
|
||||||
model: 'website.page',
|
model: 'website.page',
|
||||||
method: 'delete_page',
|
method: 'unlink',
|
||||||
args: [pageId],
|
args: [pageId],
|
||||||
context: context,
|
context: context,
|
||||||
});
|
});
|
||||||
@@ -969,7 +980,7 @@ function _deletePage(pageId, fromPageManagement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
websiteNavbarData.websiteNavbarRegistry.add(ContentMenu, '#content-menu');
|
websiteNavbarData.websiteNavbarRegistry.add(ContentMenu, '#content-menu');
|
||||||
websiteRootData.websiteRootRegistry.add(PageManagement, '#edit_website_pages');
|
websiteRootData.websiteRootRegistry.add(PageManagement, '#list_website_pages');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PagePropertiesDialog: PagePropertiesDialog,
|
PagePropertiesDialog: PagePropertiesDialog,
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ var ThemeCustomizeDialog = Dialog.extend({
|
|||||||
*/
|
*/
|
||||||
start: function () {
|
start: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._generateDialogHTML();
|
this._generateDialogHTML();
|
||||||
this.$modal.addClass('o_theme_customize_modal');
|
this.$modal.addClass('o_theme_customize_modal');
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ var ThemeCustomizeDialog = Dialog.extend({
|
|||||||
this._loadViews()
|
this._loadViews()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ body.o_connected_user {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.o_menu_systray {
|
.o_menu_systray {
|
||||||
a {
|
> li > a {
|
||||||
padding: 0 $grid-gutter-width/2;
|
padding: 0 $grid-gutter-width/2;
|
||||||
|
|
||||||
&.css_edit_dynamic{
|
&.css_edit_dynamic{
|
||||||
@@ -463,16 +463,24 @@ body .modal {
|
|||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#edit_website_pages {
|
#list_website_pages {
|
||||||
th {
|
th {
|
||||||
background-color: $o-brand-odoo;
|
background-color: $o-brand-odoo;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
td > a.fa {
|
td, th {
|
||||||
margin-left: 5px;
|
padding: 0.45rem;
|
||||||
color: $o-brand-odoo;
|
}
|
||||||
|
td {
|
||||||
|
> a.fa {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: $o-brand-odoo;
|
||||||
|
}
|
||||||
|
.text-muted {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fa-check, .fa-eye-slash {
|
.fa-check, .fa-eye-slash {
|
||||||
color: $o-brand-primary;
|
color: $info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,12 +53,14 @@
|
|||||||
|
|
||||||
<div t-name="website.ga_dialog_content">
|
<div t-name="website.ga_dialog_content">
|
||||||
Your Tracking ID: <input type="text" name="ga_analytics_key" placeholder="UA-XXXXXXXX-Y" t-att-value="ga_analytics_key" style="width: 100%"></input>
|
Your Tracking ID: <input type="text" name="ga_analytics_key" placeholder="UA-XXXXXXXX-Y" t-att-value="ga_analytics_key" style="width: 100%"></input>
|
||||||
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics.html" class="fa fa-arrow-right" target="_blank">
|
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics.html" target="_blank">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
How to get my Tracking ID
|
How to get my Tracking ID
|
||||||
</a>
|
</a>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
Your Client ID: <input type="text" name="ga_client_id" t-att-value="ga_key" style="width: 100%"></input>
|
Your Client ID: <input type="text" name="ga_client_id" t-att-value="ga_key" style="width: 100%"></input>
|
||||||
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics_dashboard.html" class="fa fa-arrow-right" target="_blank">
|
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics_dashboard.html" target="_blank">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
How to get my Client ID
|
How to get my Client ID
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,6 +72,14 @@
|
|||||||
<button class="btn btn-secondary js_date_range" data-date="month">Last Month</button>
|
<button class="btn btn-secondary js_date_range" data-date="month">Last Month</button>
|
||||||
<button class="btn btn-secondary js_date_range" data-date="year">Last Year</button>
|
<button class="btn btn-secondary js_date_range" data-date="year">Last Year</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-group pull-right" style="margin-right: 20px;">
|
||||||
|
<t t-foreach="widget.websites" t-as="website">
|
||||||
|
<button t-attf-class="btn btn-secondary js_website #{website.selected ? 'active' : ''}"
|
||||||
|
t-att-data-website-id="website.id">
|
||||||
|
<t t-esc="website.name"/>
|
||||||
|
</button>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
<t t-name="website.SalesChartTooltip">
|
<t t-name="website.SalesChartTooltip">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<li class="nav-item"><a aria-controls="advances_page_info" role="tab" data-toggle="tab" class="nav-link" href="#advances_page_info">Publish</a></li>
|
<li class="nav-item"><a aria-controls="advances_page_info" role="tab" data-toggle="tab" class="nav-link" href="#advances_page_info">Publish</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content mt16">
|
<div class="tab-content mt16">
|
||||||
<div role="tabpanel" id="basic_page_info" class="tab-pane fade in active">
|
<div role="tabpanel" id="basic_page_info" class="tab-pane fade show active">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="page_name">Page Name</label>
|
<label class="col-form-label col-md-3" for="page_name">Page Name</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div role="tabpanel" id="advances_page_info" class="tab-pane fade">
|
<div role="tabpanel" id="advances_page_info" class="tab-pane fade">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="is_menu">Show in Top Menu</label>
|
<label class="control-label col-md-5" for="is_menu">Show in Top Menu</label>
|
||||||
<div class="col-md-2">
|
<div class="col-sm-2">
|
||||||
<label class="o_switch" for="is_menu" >
|
<label class="o_switch" for="is_menu" >
|
||||||
<input type="checkbox" t-att-checked="widget.page.menu_ids.length > 0 ? true : undefined" id="is_menu"/>
|
<input type="checkbox" t-att-checked="widget.page.menu_ids.length > 0 ? true : undefined" id="is_menu"/>
|
||||||
<span/>
|
<span/>
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="is_homepage">Use as Homepage</label>
|
<label class="control-label col-md-5" for="is_homepage">Use as Homepage</label>
|
||||||
<div class="col-md-2">
|
<div class="col-sm-2">
|
||||||
<label class="o_switch" for="is_homepage" >
|
<label class="o_switch" for="is_homepage" >
|
||||||
<input type="checkbox" t-att-checked="widget.page.is_homepage ? true : undefined" id="is_homepage"/>
|
<input type="checkbox" t-att-checked="widget.page.is_homepage ? true : undefined" id="is_homepage"/>
|
||||||
<span/>
|
<span/>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="is_indexed">
|
<label class="control-label col-md-5" for="is_indexed">
|
||||||
Indexed
|
Indexed
|
||||||
<i class="fa fa-question-circle-o" title="Hide this page from search results" role="img" aria-label="Info"></i>
|
<i class="fa fa-question-circle-o" title="Hide this page from search results" role="img" aria-label="Info"></i>
|
||||||
</label>
|
</label>
|
||||||
@@ -119,17 +119,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="is_published">Publish</label>
|
<label class="control-label col-md-5" for="is_published">Publish</label>
|
||||||
<div class="col-md-2">
|
<div class="col-sm-2">
|
||||||
<label class="o_switch js_publish_btn" for="is_published">
|
<label class="o_switch js_publish_btn" for="is_published">
|
||||||
<input type="checkbox" t-att-checked="widget.page.website_published ? true : undefined" id="is_published"/>
|
<input type="checkbox" t-att-checked="widget.page.website_published ? true : undefined" id="is_published"/>
|
||||||
<span/>
|
<span/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div t-if='widget.page.website_id' class="form-group row" id="website_restriction">
|
||||||
|
<!-- TODO hide with multi-website group -->
|
||||||
|
<label class="control-label col-md-5" for="share_page_info">
|
||||||
|
Share this page
|
||||||
|
<i class="fa fa-question-circle-o" title="When enabled, this page will be shared between all the websites. This change is not reversible."></i>
|
||||||
|
</label>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<a>
|
||||||
|
<label class="o_switch" for="share_page_info" >
|
||||||
|
<input type="checkbox" t-att-checked="widget.page.website_id ? undefined : true" id="share_page_info"/>
|
||||||
|
<span/>
|
||||||
|
</label>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-form-label col-md-3" for="date_publish">Publishing Date</label>
|
<label class="control-label col-md-5" for="date_publish">Publishing Date</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-7">
|
||||||
<div class='input-group date' id='date_publish_container'>
|
<div class='input-group date' id='date_publish_container'>
|
||||||
<input type='text' class="form-control" name="date_publish" id="date_publish" />
|
<input type='text' class="form-control" name="date_publish" id="date_publish" />
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ from . import test_qweb
|
|||||||
from . import test_crawl
|
from . import test_crawl
|
||||||
from . import test_ui
|
from . import test_ui
|
||||||
from . import test_views
|
from . import test_views
|
||||||
|
from . import test_menu
|
||||||
|
from . import test_page
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from odoo.tests import common
|
||||||
|
|
||||||
|
|
||||||
|
class TestMenu(common.TransactionCase):
|
||||||
|
|
||||||
|
def test_website_own_menu_container(self):
|
||||||
|
Website = self.env['website']
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
|
||||||
|
total_menus = Menu.search_count([])
|
||||||
|
Website.create({'name': 'new website'})
|
||||||
|
self.assertEqual(total_menus + 2, Menu.search_count([]), "New website's bootstraping should have created a container menu and home menu")
|
||||||
|
|
||||||
|
def test_menu_got_duplicated(self):
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
total_menu_items = Menu.search_count([])
|
||||||
|
|
||||||
|
self.menu_root = Menu.create({
|
||||||
|
'name': 'Root',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.menu_child = Menu.create({
|
||||||
|
'name': 'Child',
|
||||||
|
'parent_id': self.menu_root.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(total_menu_items + 4, Menu.search_count([]), "Creating a menu without a website_id should create this menu for every website_id")
|
||||||
|
|
||||||
|
def test_menu_count(self):
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
total_menu_items = Menu.search_count([])
|
||||||
|
|
||||||
|
top_menu = self.env['website'].get_current_website().menu_id
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
'id': 'new-1',
|
||||||
|
'parent_id': top_menu.id,
|
||||||
|
'name': 'New Menu Specific 1',
|
||||||
|
'url': '/new-specific-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'new-2',
|
||||||
|
'parent_id': top_menu.id,
|
||||||
|
'name': 'New Menu Specific 2',
|
||||||
|
'url': '/new-specific-2',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Menu.save(1, {'data': data, 'to_delete': []})
|
||||||
|
|
||||||
|
self.assertEqual(total_menu_items + 2, Menu.search_count([]), "Creating 2 new menus should create only 2 menus records")
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from odoo.tests import common, HttpCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestPage(common.TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPage, self).setUp()
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
Page = self.env['website.page']
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
|
||||||
|
self.base_view = View.create({
|
||||||
|
'name': 'Base',
|
||||||
|
'type': 'qweb',
|
||||||
|
'arch': '<div>content</div>',
|
||||||
|
'key': 'test.base_view',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.extension_view = View.create({
|
||||||
|
'name': 'Extension',
|
||||||
|
'mode': 'extension',
|
||||||
|
'inherit_id': self.base_view.id,
|
||||||
|
'arch': '<div position="inside">, extended content</div>',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.page_1 = Page.create({
|
||||||
|
'view_id': self.base_view.id,
|
||||||
|
'url': '/page_1',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.page_1_menu = Menu.create({
|
||||||
|
'name': 'Page 1 menu',
|
||||||
|
'page_id': self.page_1.id,
|
||||||
|
'website_id': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_copy_page(self):
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
Page = self.env['website.page']
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
# Specific page
|
||||||
|
self.specific_view = View.create({
|
||||||
|
'name': 'Base',
|
||||||
|
'type': 'qweb',
|
||||||
|
'arch': '<div>Specific View</div>',
|
||||||
|
'key': 'test.specific_view',
|
||||||
|
})
|
||||||
|
self.page_specific = Page.create({
|
||||||
|
'view_id': self.specific_view.id,
|
||||||
|
'url': '/page_specific',
|
||||||
|
'website_id': 1,
|
||||||
|
})
|
||||||
|
self.page_specific_menu = Menu.create({
|
||||||
|
'name': 'Page Specific menu',
|
||||||
|
'page_id': self.page_specific.id,
|
||||||
|
'website_id': 1,
|
||||||
|
})
|
||||||
|
total_pages = Page.search_count([])
|
||||||
|
total_menus = Menu.search_count([])
|
||||||
|
# Copying a specific page should create a new page with an unique URL (suffixed by -X)
|
||||||
|
Page.clone_page(self.page_specific.id, True)
|
||||||
|
cloned_page = Page.search([('url', '=', '/page_specific-1')])
|
||||||
|
cloned_menu = Menu.search([('url', '=', '/page_specific-1')])
|
||||||
|
self.assertEqual(len(cloned_page), 1, "A page with an URL /page_specific-1 should've been created")
|
||||||
|
self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the page")
|
||||||
|
# It should also copy its menu with new url/name/page_id (if the page has a menu)
|
||||||
|
self.assertEqual(len(cloned_menu), 1, "A specific page (with a menu) being cloned should have it's menu also cloned")
|
||||||
|
self.assertEqual(cloned_menu.page_id, cloned_page, "The new cloned menu and the new cloned page should be linked (m2o)")
|
||||||
|
self.assertEqual(Menu.search_count([]), total_menus + 1, "Should have cloned the page menu")
|
||||||
|
|
||||||
|
total_pages = Page.search_count([])
|
||||||
|
total_menus = Menu.search_count([])
|
||||||
|
|
||||||
|
# Copying a generic page should create a specific page with same URL
|
||||||
|
Page.clone_page(self.page_1.id, True)
|
||||||
|
cloned_generic_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id), ('website_id', '!=', False)])
|
||||||
|
self.assertEqual(len(cloned_generic_page), 1, "A generic page being cloned should create a specific one for the current website")
|
||||||
|
self.assertEqual(cloned_generic_page.url, self.page_1.url, "The URL of the cloned specific page should be the same as the generic page it has been cloned from")
|
||||||
|
self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the generic page as a specific page for this website")
|
||||||
|
self.assertEqual(Menu.search_count([]), total_menus, "It should not create a new menu as the generic page's menu belong to another website")
|
||||||
|
# Except if the URL already exists for this website (its the case now that we already cloned it once)
|
||||||
|
Page.clone_page(self.page_1.id, True)
|
||||||
|
cloned_generic_page_2 = Page.search([('url', '=', '/page_1-1'), ('id', '!=', self.page_1.id)])
|
||||||
|
self.assertEqual(len(cloned_generic_page_2), 1, "A generic page being cloned should create a specific page with a new URL if there is already a specific page with that URL")
|
||||||
|
|
||||||
|
def test_cow_page(self):
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
Page = self.env['website.page']
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
# backend write, no COW
|
||||||
|
total_pages = Page.search_count([])
|
||||||
|
total_menus = Menu.search_count([])
|
||||||
|
total_views = View.search_count([])
|
||||||
|
self.page_1.write({'arch': '<div>modified base content</div>'})
|
||||||
|
self.assertEqual(total_pages, Page.search_count([]))
|
||||||
|
self.assertEqual(total_menus, Menu.search_count([]))
|
||||||
|
self.assertEqual(total_views, View.search_count([]))
|
||||||
|
|
||||||
|
# edit through frontend
|
||||||
|
self.page_1.with_context(website_id=1).write({'arch': '<div>website 1 content</div>'})
|
||||||
|
|
||||||
|
# 1. should have created website-specific copies for:
|
||||||
|
# - page
|
||||||
|
# - view x2 (base view + extension view)
|
||||||
|
# 2. should not have created menu copy as menus are not shared/COW
|
||||||
|
# 3. and shouldn't have touched original records
|
||||||
|
self.assertEqual(total_pages + 1, Page.search_count([]))
|
||||||
|
self.assertEqual(total_menus, Menu.search_count([]))
|
||||||
|
self.assertEqual(total_views + 2, View.search_count([]))
|
||||||
|
|
||||||
|
self.assertEqual(self.page_1.arch, '<div>modified base content</div>')
|
||||||
|
self.assertEqual(bool(self.page_1.website_id), False)
|
||||||
|
|
||||||
|
new_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id)])
|
||||||
|
self.assertEqual(new_page.website_id.id, 1)
|
||||||
|
self.assertEqual(new_page.view_id.inherit_children_ids[0].website_id.id, 1)
|
||||||
|
self.assertEqual(new_page.arch, '<div>website 1 content</div>')
|
||||||
|
|
||||||
|
def test_cow_extension_view(self):
|
||||||
|
''' test cow on extension view itself (like web_editor would do in the frontend) '''
|
||||||
|
Menu = self.env['website.menu']
|
||||||
|
Page = self.env['website.page']
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
# nothing special should happen when editing through the backend
|
||||||
|
total_pages = Page.search_count([])
|
||||||
|
total_menus = Menu.search_count([])
|
||||||
|
total_views = View.search_count([])
|
||||||
|
self.extension_view.write({'arch': '<div>modified extension content</div>'})
|
||||||
|
self.assertEqual(self.extension_view.arch, '<div>modified extension content</div>')
|
||||||
|
self.assertEqual(total_pages, Page.search_count([]))
|
||||||
|
self.assertEqual(total_menus, Menu.search_count([]))
|
||||||
|
self.assertEqual(total_views, View.search_count([]))
|
||||||
|
|
||||||
|
# When editing through the frontend a website-specific copy
|
||||||
|
# for the extension view should be created. When rendering the
|
||||||
|
# original website.page on website 1 it will look differently
|
||||||
|
# due to this new extension view.
|
||||||
|
self.extension_view.with_context(website_id=1).write({'arch': '<div>website 1 content</div>'})
|
||||||
|
self.assertEqual(total_pages, Page.search_count([]))
|
||||||
|
self.assertEqual(total_menus, Menu.search_count([]))
|
||||||
|
self.assertEqual(total_views + 1, View.search_count([]))
|
||||||
|
|
||||||
|
self.assertEqual(self.extension_view.arch, '<div>modified extension content</div>')
|
||||||
|
self.assertEqual(bool(self.page_1.website_id), False)
|
||||||
|
|
||||||
|
new_view = View.search([('name', '=', 'Extension'), ('website_id', '=', 1)])
|
||||||
|
self.assertEqual(new_view.arch, '<div>website 1 content</div>')
|
||||||
|
self.assertEqual(new_view.website_id.id, 1)
|
||||||
|
|
||||||
|
def test_cou_page_backend(self):
|
||||||
|
Page = self.env['website.page']
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
# currently the view unlink of website.page can't handle views with inherited views
|
||||||
|
self.extension_view.unlink()
|
||||||
|
|
||||||
|
self.page_1.unlink()
|
||||||
|
self.assertEqual(Page.search_count([('url', '=', '/page_1')]), 0)
|
||||||
|
self.assertEqual(View.search_count([('name', 'in', ('Base', 'Extension'))]), 0)
|
||||||
|
|
||||||
|
def test_cou_page_frontend(self):
|
||||||
|
Page = self.env['website.page']
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
# currently the view unlink of website.page can't handle views with inherited views
|
||||||
|
self.extension_view.unlink()
|
||||||
|
|
||||||
|
self.page_1.with_context(website_id=1).unlink()
|
||||||
|
|
||||||
|
self.assertEqual(bool(self.base_view.exists()), False)
|
||||||
|
self.assertEqual(bool(self.page_1.exists()), False)
|
||||||
|
# Not COU but deleting a page will delete its menu (cascade)
|
||||||
|
self.assertEqual(bool(self.page_1_menu.exists()), False)
|
||||||
|
|
||||||
|
self.assertEqual(Page.search([('url', '=', '/page_1')]).website_id.id, 2)
|
||||||
|
self.assertEqual(View.search([('name', 'in', ('Base', 'Extension'))]).mapped('website_id').id, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class Crawler(HttpCase):
|
||||||
|
def test_unpublished_page(self):
|
||||||
|
Page = self.env['website.page']
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
base_view = View.create({
|
||||||
|
'name': 'Base',
|
||||||
|
'type': 'qweb',
|
||||||
|
'arch': '''<t name="Homepage" t-name="website.base_view">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
I am a generic page
|
||||||
|
</t>
|
||||||
|
</t>''',
|
||||||
|
'key': 'test.base_view',
|
||||||
|
})
|
||||||
|
generic_page = Page.create({
|
||||||
|
'view_id': base_view.id,
|
||||||
|
'url': '/page_1',
|
||||||
|
'website_published': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
specific_page = generic_page.copy({'website_id': self.env['website'].get_current_website().id})
|
||||||
|
specific_page.write({'website_published': False, 'arch': generic_page.arch.replace('I am a generic page', 'I am a specific page')})
|
||||||
|
|
||||||
|
r = self.url_open(specific_page.url)
|
||||||
|
self.assertEqual(r.status_code, 404, "Restricted users should see a 404 and not the generic one as we unpublished the specific one")
|
||||||
|
|
||||||
|
self.authenticate('admin', 'admin')
|
||||||
|
r = self.url_open(specific_page.url)
|
||||||
|
self.assertEqual(r.status_code, 200, "Admin should see the specific unpublished page")
|
||||||
|
self.assertEqual('I am a specific page' in r.text, True, "Admin should see the specific unpublished page")
|
||||||
@@ -258,3 +258,86 @@ class TestViewSaving(common.TransactionCase):
|
|||||||
))
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_cow_leaf(self):
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
base_view = View.create({
|
||||||
|
'name': 'Base',
|
||||||
|
'type': 'qweb',
|
||||||
|
'arch': '<div>base content</div>',
|
||||||
|
}).with_context(load_all_views=True)
|
||||||
|
|
||||||
|
inherit_view = View.create({
|
||||||
|
'name': 'Extension',
|
||||||
|
'mode': 'extension',
|
||||||
|
'inherit_id': base_view.id,
|
||||||
|
'arch': '<div position="replace"><div>extended content</div></div>',
|
||||||
|
})
|
||||||
|
|
||||||
|
# edit on backend, regular write
|
||||||
|
inherit_view.write({'arch': '<div position="replace"><div>modified content</div></div>'})
|
||||||
|
self.assertEqual(View.search_count([('name', '=', 'Base')]), 1)
|
||||||
|
self.assertEqual(View.search_count([('name', '=', 'Extension')]), 1)
|
||||||
|
|
||||||
|
arch = base_view.read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>modified content</div>')
|
||||||
|
|
||||||
|
# edit on frontend, copy just the leaf
|
||||||
|
inherit_view.with_context(website_id=1).write({'arch': '<div position="replace"><div>website 1 content</div></div>'})
|
||||||
|
inherit_views = View.search([('name', '=', 'Extension')])
|
||||||
|
self.assertEqual(View.search_count([('name', '=', 'Base')]), 1)
|
||||||
|
self.assertEqual(len(inherit_views), 2)
|
||||||
|
self.assertEqual(len(inherit_views.filtered(lambda v: v.website_id.id == 1)), 1)
|
||||||
|
|
||||||
|
# read in backend should be unaffected
|
||||||
|
arch = base_view.read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>modified content</div>')
|
||||||
|
# read on website should reflect change
|
||||||
|
arch = base_view.with_context(website_id=1).read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>website 1 content</div>')
|
||||||
|
|
||||||
|
# website-specific inactive view should take preference over active generic one when viewing the website
|
||||||
|
# this is necessary to make customize_show=True templates work correctly
|
||||||
|
inherit_views.filtered(lambda v: v.website_id.id == 1).write({'active': False})
|
||||||
|
arch = base_view.with_context(website_id=1).read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>base content</div>')
|
||||||
|
|
||||||
|
def test_cow_root(self):
|
||||||
|
View = self.env['ir.ui.view']
|
||||||
|
|
||||||
|
base_view = View.create({
|
||||||
|
'name': 'Base',
|
||||||
|
'type': 'qweb',
|
||||||
|
'arch': '<div>content</div>',
|
||||||
|
})
|
||||||
|
|
||||||
|
View.create({
|
||||||
|
'name': 'Extension',
|
||||||
|
'mode': 'extension',
|
||||||
|
'inherit_id': base_view.id,
|
||||||
|
'arch': '<div position="inside">, extended content</div>',
|
||||||
|
})
|
||||||
|
|
||||||
|
# edit on backend, regular write
|
||||||
|
base_view.write({'arch': '<div>modified base content</div>'})
|
||||||
|
self.assertEqual(View.search_count([('name', '=', 'Base')]), 1)
|
||||||
|
self.assertEqual(View.search_count([('name', '=', 'Extension')]), 1)
|
||||||
|
|
||||||
|
# edit on frontend, copy the entire tree
|
||||||
|
base_view.with_context(website_id=1).write({'arch': '<div>website 1 content</div>'})
|
||||||
|
|
||||||
|
generic_base_view = View.search([('name', '=', 'Base'), ('website_id', '=', False)])
|
||||||
|
website_specific_base_view = View.search([('name', '=', 'Base'), ('website_id', '=', 1)])
|
||||||
|
self.assertEqual(len(generic_base_view), 1)
|
||||||
|
self.assertEqual(len(website_specific_base_view), 1)
|
||||||
|
|
||||||
|
inherit_views = View.search([('name', '=', 'Extension')])
|
||||||
|
self.assertEqual(len(inherit_views), 2)
|
||||||
|
self.assertEqual(len(inherit_views.filtered(lambda v: v.website_id.id == 1)), 1)
|
||||||
|
|
||||||
|
arch = generic_base_view.with_context(load_all_views=True).read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>modified base content, extended content</div>')
|
||||||
|
|
||||||
|
arch = website_specific_base_view.with_context(load_all_views=True, website_id=1).read_combined(['arch'])['arch']
|
||||||
|
self.assertEqual(arch, '<div>website 1 content, extended content</div>')
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_attachment_form_inherit_website" model="ir.ui.view">
|
||||||
|
<field name="name">ir.attachment.form.inherit.website</field>
|
||||||
|
<field name="model">ir.attachment</field>
|
||||||
|
<field name="inherit_id" ref="base.view_attachment_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="mimetype" position="after">
|
||||||
|
<field name="website_id" groups="base.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -10,12 +10,26 @@
|
|||||||
<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 groups="website.group_multi_website">Select the Website to Configure</h2>
|
||||||
|
<div class="row mt16 o_settings_container" id="website_selection_settings" groups="website.group_multi_website">
|
||||||
|
<div class="col-xs-12 col-md-6 o_setting_box" id="website">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Website" for="website_id"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Settings on this page will apply to this website
|
||||||
|
</div>
|
||||||
|
<div class="mt16">
|
||||||
|
<field name="website_id" options="{'no_open': True, 'no_create_edit': True}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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-12 col-lg-6 o_setting_box" id="domain_setting">
|
<div class="col-12 col-lg-6 o_setting_box" id="domain_setting">
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<span class="o_form_label">Website Title</span>
|
<label for="website_name" string="Website Title"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Name and favicon of your website
|
Name and favicon of your website
|
||||||
</div>
|
</div>
|
||||||
@@ -31,9 +45,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" id="company_settings" groups="base.group_multi_company">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Company" for="website_company_id"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
The company this website belongs to
|
||||||
|
</div>
|
||||||
|
<field name="website_company_id"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" id="domain_settings" groups="website.group_multi_website">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Domain" for="website_domain"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Display this website when users visit this domain
|
||||||
|
</div>
|
||||||
|
<div class="mt16">
|
||||||
|
<field name="website_domain"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button name="website_go_to" type="object" string="Go to website" class="btn-link" icon="fa-arrow-right"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" id="website_geoip_settings" groups="website.group_multi_website">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Country Groups" for="website_country_group_ids"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Display this website for users from these country groups
|
||||||
|
</div>
|
||||||
|
<div class="mt16">
|
||||||
|
<field name="website_country_group_ids" widget="many2many_tags"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box" id="languages_setting">
|
<div class="col-12 col-lg-6 o_setting_box" id="languages_setting">
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="language_ids"/>
|
<label for="language_ids"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Languages available on your website
|
Languages available on your website
|
||||||
</div>
|
</div>
|
||||||
@@ -52,12 +104,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box" id="google_analytics_setting">
|
<div id="multi_website" class="col-xs-12 col-md-6 o_setting_box">
|
||||||
|
<div class="o_setting_left_pane">
|
||||||
|
<field name="group_multi_website"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Multi-Websites" for="group_multi_website"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Manage multiple websites
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-offset-6 col-lg-6 o_setting_box" id="google_analytics_setting">
|
||||||
<div class="o_setting_left_pane">
|
<div class="o_setting_left_pane">
|
||||||
<field name="has_google_analytics"/>
|
<field name="has_google_analytics"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="has_google_analytics"/>
|
<label for="has_google_analytics"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Track visits in Google Analytics
|
Track visits in Google Analytics
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +134,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('has_google_analytics', '=', False)]}">
|
<div attrs="{'invisible': [('has_google_analytics', '=', False)]}">
|
||||||
<a href="https://www.odoo.com/documentation/user/11.0/website/optimize/google_analytics.html"
|
<a href="https://www.odoo.com/documentation/user/11.0/website/optimize/google_analytics.html"
|
||||||
class="oe_link fa fa-arrow-right" target="_blank">
|
class="oe_link" target="_blank">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
How to get my Tracking ID
|
How to get my Tracking ID
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +147,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="has_google_analytics_dashboard"/>
|
<label for="has_google_analytics_dashboard"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Follow your website traffic in Odoo.
|
Follow your website traffic in Odoo.
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +163,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('has_google_analytics_dashboard', '=', False)]}">
|
<div attrs="{'invisible': [('has_google_analytics_dashboard', '=', False)]}">
|
||||||
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics_dashboard.html"
|
<a href="https://www.odoo.com/documentation/user/online/website/optimize/google_analytics_dashboard.html"
|
||||||
class="oe_link fa fa-arrow-right" target="_blank">
|
class="oe_link" target="_blank">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
How to get my Client ID
|
How to get my Client ID
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,6 +176,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="has_google_maps"/>
|
<label for="has_google_maps"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Show your address on a map in the <a href="/contactus">Contact Us</a> page
|
Show your address on a map in the <a href="/contactus">Contact Us</a> page
|
||||||
</div>
|
</div>
|
||||||
@@ -119,14 +187,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('has_google_maps', '=', False)]}">
|
<div attrs="{'invisible': [('has_google_maps', '=', False)]}">
|
||||||
<a role="button" class="btn-link fa fa-arrow-right" target="_blank"
|
<a role="button" class="btn-link" target="_blank"
|
||||||
href="https://console.developers.google.com/flows/enableapi?apiid=maps_backend,static_maps_backend&keyType=CLIENT_SIDE&reusekey=true">
|
href="https://console.developers.google.com/flows/enableapi?apiid=maps_backend,static_maps_backend&keyType=CLIENT_SIDE&reusekey=true">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
Create a Google Project and Get a Key
|
Create a Google Project and Get a Key
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
<div class="col-12 col-lg-6 o_setting_box" id="ab_testing_setting">
|
||||||
<div class="o_setting_left_pane">
|
<div class="o_setting_left_pane">
|
||||||
<field name="module_website_version" widget="upgrade_boolean"/>
|
<field name="module_website_version" widget="upgrade_boolean"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,11 +206,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
<div class="col-12 col-lg-6 o_setting_box" id="utm_tracker_settings" title="Analyze the efficiency of your marketing campaigns by using trackable UTM trackers (campaigns, medium, sources). Create trackers and follow clicks from the Promote menu of your website. Those trackers can be used in Google Analytics or in Odoo reports where you can see the opportunities and sales revenue generated thanks to your links.">
|
||||||
|
<div class="o_setting_left_pane">
|
||||||
|
<field name="module_website_links"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label for="module_website_links"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Track clicks on UTM links
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" groups="website.group_multi_website">
|
||||||
|
<div class="o_setting_left_pane">
|
||||||
|
<field name="specific_user_account"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label for="specific_user_account"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Force your user to create an account per website
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" id="auth_signup_uninvited_setting">
|
||||||
<div class="o_setting_left_pane">
|
<div class="o_setting_left_pane">
|
||||||
</div>
|
</div>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="auth_signup_uninvited"/>
|
<label for="auth_signup_uninvited"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Let your customers log in to see their documents
|
Let your customers log in to see their documents
|
||||||
</div>
|
</div>
|
||||||
@@ -149,8 +241,42 @@
|
|||||||
<field name="auth_signup_uninvited" class="o_light_label" widget="radio" required="True"/>
|
<field name="auth_signup_uninvited" class="o_light_label" widget="radio" required="True"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<div class="mt16">
|
<button type="object" name="open_template_user" string="Default Access Rights" icon="fa-arrow-right" class="btn-link"/>
|
||||||
<button type="object" name="open_template_user" string="Default Access Rights" icon="fa-arrow-right" class="btn-link"/>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
|
<div class="o_setting_left_pane"/>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label string="Social Media" for="social_twitter"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Add links to social media on your website
|
||||||
|
</div>
|
||||||
|
<div class="content-group">
|
||||||
|
<div class="row mt16">
|
||||||
|
<label for="social_twitter" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_twitter"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for="social_facebook" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_facebook"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for="social_github" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_github"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for="social_linkedin" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_linkedin"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for="social_youtube" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_youtube"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for="social_googleplus" class="col-md-4 o_light_label"/>
|
||||||
|
<field name="social_googleplus"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,7 +286,8 @@
|
|||||||
<field name="cdn_activated"/>
|
<field name="cdn_activated"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="cdn_activated" string="Content Delivery Network (CDN)"/>
|
<label for="cdn_activated"/>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Use a CDN to optimize the availability of your website's content
|
Use a CDN to optimize the availability of your website's content
|
||||||
</div>
|
</div>
|
||||||
@@ -180,17 +307,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box" id="utm_tracker_settings" title="Analyze the efficiency of your marketing campaigns by using trackable UTM trackers (campaigns, medium, sources). Create trackers and follow clicks from the Promote menu of your website. Those trackers can be used in Google Analytics or in Odoo reports where you can see the opportunities and sales revenue generated thanks to your links.">
|
|
||||||
<div class="o_setting_left_pane">
|
|
||||||
<field name="module_website_links"/>
|
|
||||||
</div>
|
|
||||||
<div class="o_setting_right_pane">
|
|
||||||
<label for="module_website_links"/>
|
|
||||||
<div class="text-muted">
|
|
||||||
Track clicks on UTM links
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
@@ -227,6 +343,14 @@
|
|||||||
<menuitem id="menu_website_add_features" parent="website.menu_website_global_configuration"
|
<menuitem id="menu_website_add_features" parent="website.menu_website_global_configuration"
|
||||||
sequence="20" groups="base.group_system" action="action_website_add_features"/>
|
sequence="20" groups="base.group_system" action="action_website_add_features"/>
|
||||||
|
|
||||||
|
<menuitem name="Websites"
|
||||||
|
id="menu_website_websites_list"
|
||||||
|
action="action_website_list"
|
||||||
|
parent="menu_website_global_configuration"
|
||||||
|
groups="base.group_no_one"
|
||||||
|
sequence="10"
|
||||||
|
/>
|
||||||
|
|
||||||
<menuitem name="Pages"
|
<menuitem name="Pages"
|
||||||
id="menu_website_pages_list"
|
id="menu_website_pages_list"
|
||||||
action="action_website_pages_list"
|
action="action_website_pages_list"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_partner_form_inherit_website" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.website.inherit</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="company_id" position="after" options="{'no_create': True}">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -77,6 +77,21 @@
|
|||||||
<li class="o_mobile_preview" id="mobile-menu">
|
<li class="o_mobile_preview" id="mobile-menu">
|
||||||
<a data-action="show-mobile-preview" href="#"><span title="Mobile preview" role="img" aria-label="Mobile preview" class="fa fa-mobile"/></a>
|
<a data-action="show-mobile-preview" href="#"><span title="Mobile preview" role="img" aria-label="Mobile preview" class="fa fa-mobile"/></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li groups="website.group_multi_website">
|
||||||
|
<a class="dropdown-toggle waves" data-toggle="dropdown" href="#"><t t-esc="multi_website_selected_website['name']"/></a>
|
||||||
|
<div class="dropdown-menu" role="menu">
|
||||||
|
<t t-foreach="multi_website_websites" t-as="multi_website_website">
|
||||||
|
<a role="menuitem" href="#"
|
||||||
|
t-if="multi_website_selected_website['website_id'] != multi_website_website['website_id']"
|
||||||
|
class="dropdown-item oe_menu_text js_multi_website_switch"
|
||||||
|
t-att-website-id="str(multi_website_website['website_id'])"
|
||||||
|
>
|
||||||
|
<span t-esc="multi_website_website['name']" />
|
||||||
|
</a>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Page Edition -->
|
<!-- Page Edition -->
|
||||||
<li class="o_new_content_menu" id="new-content-menu">
|
<li class="o_new_content_menu" id="new-content-menu">
|
||||||
<a href="#"><span class="fa fa-plus"/>New</a>
|
<a href="#"><span class="fa fa-plus"/>New</a>
|
||||||
|
|||||||
@@ -1057,10 +1057,10 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||||||
</t>
|
</t>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="edit_website_pages" name="Website Pages Management">
|
<template id="list_website_pages" name="Website Pages Management">
|
||||||
<t t-call="website.layout">
|
<t t-call="website.layout">
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<div class="container" id="edit_website_pages">
|
<div class="container" id="list_website_pages">
|
||||||
<form class="mt8 float-right" role="search" t-attf-action="/website/pages" method="get">
|
<form class="mt8 float-right" role="search" t-attf-action="/website/pages" method="get">
|
||||||
<t t-call="website.website_search_box"/>
|
<t t-call="website.website_search_box"/>
|
||||||
</form>
|
</form>
|
||||||
@@ -1089,33 +1089,27 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="pages" class="table-responsive">
|
<div t-if="pages" class="table-responsive">
|
||||||
<table class="table table-hover o_my_status_table">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="active">
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Url</th>
|
<th>Url</th>
|
||||||
<th class="col-lg-1 text-center"><i title="Is the page included in the main menu?" role="img" aria-label="Is the page included in the main menu?" class="fa fa-thumb-tack"></i></th>
|
<th class="text-center"><i title="Is the page included in the main menu?" class="fa fa-thumb-tack"></i></th>
|
||||||
<th class="col-lg-1 text-center"><i title="Is the page published?" role="img" aria-label="Is the page published?" class="fa fa-eye"></i></th>
|
<th class="text-center"><i title="Is the page published?" class="fa fa-eye"></i></th>
|
||||||
<th class="col-lg-1 text-center"><i title="Is the page indexed by search engines?" role="img" aria-label="Is the page indexed by search engines?" class="fa fa-globe"></i></th>
|
<th class="text-center"><i title="Is the page indexed by search engines?" class="fa fa-globe"></i></th>
|
||||||
<th class="col-md-3 col-lg-2"></th>
|
<th style='width:140px;'></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<t t-foreach="pages" t-as="page">
|
<t t-set='prev_page' t-value='False' />
|
||||||
<tr>
|
<t t-set='page' t-value='pages[0]' />
|
||||||
<td><i t-if="page.is_homepage" class="fa fa-home" role="img" aria-label="Home" title="Home"></i> <span style="word-break: break-all;" t-esc="page.name"/></td>
|
<t t-foreach="pages[1:]" t-as="next_page">
|
||||||
<td><a style="word-break: break-all;" t-attf-href="{{page.url}}"><t t-esc="page.url"/></a></td>
|
<t t-call='website.one_page_line'/>
|
||||||
<td class="text-center"><i t-att-class="'fa fa-check' if page.menu_ids else 'fa fa-times text-muted'" role="img" t-att-aria-label="'Checked' if page.menu_ids else 'Not checked'" t-att-title="'Checked' if page.menu_ids else 'Not checked'"></i></td>
|
<t t-set='prev_page' t-value='page' />
|
||||||
<t t-set='date_formatted'><t t-options='{"widget": "date"}' t-esc="page.date_publish"/></t>
|
<t t-set='page' t-value='next_page' />
|
||||||
<td class="text-center"><i t-att-title="not page.is_visible and page.website_published and 'This page will be visible on ' + date_formatted" role="img" t-att-aria-label="not page.is_visible and page.website_published and 'This page will be visible on ' + date_formatted" t-att-class="'fa fa-check' if page.is_visible and page.website_published else 'fa fa-eye-slash' if not page.is_visible and page.website_published else 'fa fa-times text-muted'"></i></td>
|
|
||||||
<td class="text-center"><i t-att-class="'fa fa-check' if page.website_indexed else 'fa fa-times text-muted'" role="img" t-att-aria-label="'Checked' if page.website_indexed else 'Not checked'" t-att-title="'Checked' if page.website_indexed else 'Not checked'"></i></td>
|
|
||||||
<td class="text-right">
|
|
||||||
<a class="mr4 fa fa-cog js_page_properties" t-att-data-id="page.id" href="#" title="Manage this page" aria-label="Manage this page"></a>
|
|
||||||
<a class="mr4 fa fa-pencil-square-o" t-attf-href="/web#id=#{page.view_id.id}&view_type=form&model=ir.ui.view" title="Edit code in backend" aria-label="Edit code in backend"></a>
|
|
||||||
<a class="mr4 fa fa-clone js_clone_page" t-att-data-id="page.id" href="#" title="Clone this page" aria-label="Clone this page"></a>
|
|
||||||
<a class="fa fa-trash js_delete_page" t-att-data-id="page.id" href="#" title="Delete this page" aria-label="Delete this page"></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
|
<t t-set='next_page' t-value='False'/>
|
||||||
|
<t t-call='website.one_page_line' />
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="pager" class="o_portal_pager text-center">
|
<div t-if="pager" class="o_portal_pager text-center">
|
||||||
@@ -1126,4 +1120,48 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||||||
</t>
|
</t>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template id="one_page_line">
|
||||||
|
<t t-set='specific_page' t-value="page.website_id" />
|
||||||
|
<t t-set='final_page' t-value="(next_page and page.url != next_page.url) or not next_page or specific_page"/>
|
||||||
|
<tr t-att-style='not final_page and "color:#999"'>
|
||||||
|
<td>
|
||||||
|
<t t-if='specific_page and prev_page and prev_page.url == page.url and not prev_page.website_id'><i class="fa fa-level-up fa-rotate-90 ml32 mr4"></i></t>
|
||||||
|
<i t-else="1" class="fa fa-globe mr4" t-att-style='specific_page and "visibility:hidden;"' />
|
||||||
|
<i t-if="page.is_homepage" class="fa fa-home" title="Home"></i> <span t-esc="page.name"/>
|
||||||
|
</td>
|
||||||
|
<td><a t-if='final_page' t-attf-href="{{page.url}}"><t t-esc="page.url"/></a></td>
|
||||||
|
<td class="text-center"><i t-att-class="'fa fa-check' if page.menu_ids else 'fa fa-times text-muted'" t-att-title="'Checked' if page.menu_ids else 'Not checked'"></i></td>
|
||||||
|
<t t-set='date_formatted'><t t-options='{"widget": "date"}' t-esc="page.date_publish"/></t>
|
||||||
|
<td class="text-center">
|
||||||
|
<i t-att-title="not page.is_visible and page.website_published and 'This page will be visible on ' + date_formatted"
|
||||||
|
t-att-class="'fa fa-check' if page.is_visible and page.website_published else 'fa fa-eye-slash' if not page.is_visible and page.website_published else 'fa fa-times text-muted'"></i>
|
||||||
|
</td>
|
||||||
|
<td class="text-center"><i t-att-class="'fa fa-check' if page.website_indexed else 'fa fa-times text-muted'" t-att-title="'Checked' if page.website_indexed else 'Not checked'"></i></td>
|
||||||
|
<td class="text-right" style="white-space:nowrap;">
|
||||||
|
<a class="mr4 fa fa-cog js_page_properties" t-att-data-id="page.id" href="#" title="Manage this page"></a>
|
||||||
|
<a class="mr4 fa fa-pencil-square-o" t-attf-href="/web#id=#{page.view_id.id}&view_type=form&model=ir.ui.view" title="Edit code in backend"></a>
|
||||||
|
<a class="mr4 fa fa-clone js_clone_page" t-att-data-id="page.id" href="#" title="Clone this page"></a>
|
||||||
|
<a class="fa fa-trash js_delete_page" t-att-data-id="page.id" href="#" title="Delete this page"></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="default_homepage">
|
||||||
|
<div id="wrap" class="oe_structure oe_empty">
|
||||||
|
<div class="item image_text oe_img_bg active">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row content">
|
||||||
|
<div class="col-md-6 col-sm-12">
|
||||||
|
<h2>Homepage of <t t-esc="website.name"/></h2>
|
||||||
|
<h3>Click to customize this text</h3>
|
||||||
|
<p>
|
||||||
|
<a role="button" class="btn btn-success btn-large" href="/contactus">Contact us</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -14,29 +14,15 @@
|
|||||||
<field name="model">website</field>
|
<field name="model">website</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Website Settings">
|
<form string="Website Settings">
|
||||||
<header>
|
|
||||||
<button string="Apply" type="object" name="noop" class="oe_highlight"/>
|
|
||||||
<button string="Cancel" type="object" name="noop" class="oe_link"/>
|
|
||||||
</header>
|
|
||||||
<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"/>
|
||||||
<field name="domain"/>
|
<field name="domain"/>
|
||||||
|
<field name="country_group_ids" widget="many2many_tags"/>
|
||||||
<field name="google_analytics_key" placeholder="UA-XXXXXXXX-Y"/>
|
<field name="google_analytics_key" placeholder="UA-XXXXXXXX-Y"/>
|
||||||
</group>
|
</group>
|
||||||
</div>
|
</div>
|
||||||
<div name="social_media">
|
|
||||||
<separator string="Social Media"/>
|
|
||||||
<group name="social_media">
|
|
||||||
<field name="social_twitter" placeholder="https://twitter.com/Odoo"/>
|
|
||||||
<field name="social_facebook" placeholder="https://www.facebook.com/odoo"/>
|
|
||||||
<field name="social_googleplus" placeholder="https://plus.google.com/+Odooapps"/>
|
|
||||||
<field name="social_linkedin" placeholder="https://www.linkedin.com/company/odoo"/>
|
|
||||||
<field name="social_youtube" placeholder="https://www.youtube.com/user/OpenERPonline"/>
|
|
||||||
<field name="social_github" placeholder="https://youraccount.github.io"/>
|
|
||||||
</group>
|
|
||||||
</div>
|
|
||||||
<div name="other">
|
<div name="other">
|
||||||
<separator string="Other Info"/>
|
<separator string="Other Info"/>
|
||||||
<group name="other">
|
<group name="other">
|
||||||
@@ -55,8 +41,11 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Websites">
|
<tree string="Websites">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="domain"/>
|
||||||
|
<field name="country_group_ids" widget="many2many_tags"/>
|
||||||
<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="theme_id" groups="base.group_no_one"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -77,6 +66,15 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="action_website_list" model="ir.actions.act_window">
|
||||||
|
<field name="name">Websites</field>
|
||||||
|
<field name="res_model">website</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="view_id" ref="view_website_tree"/>
|
||||||
|
<field name="target">current</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- website.redirect views -->
|
<!-- website.redirect views -->
|
||||||
<record id="website_redirect_form_view" model="ir.ui.view">
|
<record id="website_redirect_form_view" model="ir.ui.view">
|
||||||
<field name="name">website.redirect.form</field>
|
<field name="name">website.redirect.form</field>
|
||||||
@@ -91,7 +89,7 @@
|
|||||||
<field name="url_to"/>
|
<field name="url_to"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="website_id"/>
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active"/>
|
<field name="active"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -108,7 +106,7 @@
|
|||||||
<field name="type"/>
|
<field name="type"/>
|
||||||
<field name="url_from"/>
|
<field name="url_from"/>
|
||||||
<field name="url_to"/>
|
<field name="url_to"/>
|
||||||
<field name="website_id"/>
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active"/>
|
<field name="active"/>
|
||||||
<field name="sequence" widget="handle" />
|
<field name="sequence" widget="handle" />
|
||||||
</tree>
|
</tree>
|
||||||
@@ -137,6 +135,8 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="url"/>
|
<field name="url"/>
|
||||||
<field name="view_id"/>
|
<field name="view_id"/>
|
||||||
|
<field groups="base.group_no_one" name="is_published"/>
|
||||||
|
<field groups="website.group_multi_website" name="website_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="website_indexed"/>
|
<field name="website_indexed"/>
|
||||||
@@ -144,7 +144,6 @@
|
|||||||
<field name="date_publish"/>
|
<field name="date_publish"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<field groups="base.group_no_one" name="website_ids"/>
|
|
||||||
<label for="menu_ids" string="Related Menu Items"/>
|
<label for="menu_ids" string="Related Menu Items"/>
|
||||||
<field name="menu_ids"/>
|
<field name="menu_ids"/>
|
||||||
</sheet>
|
</sheet>
|
||||||
@@ -156,11 +155,12 @@
|
|||||||
<field name="name">website.page.list</field>
|
<field name="name">website.page.list</field>
|
||||||
<field name="model">website.page</field>
|
<field name="model">website.page</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Website Pages">
|
<tree string="Website Pages" default_order="name">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="url"/>
|
<field name="url"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="website_indexed"/>
|
<field name="website_indexed"/>
|
||||||
<field name="website_published"/>
|
<field name="website_published" string="Page Published"/>
|
||||||
<field name="create_uid" invisible="1"/>
|
<field name="create_uid" invisible="1"/>
|
||||||
<field name="write_uid"/>
|
<field name="write_uid"/>
|
||||||
<field name="write_date"/>
|
<field name="write_date"/>
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="new_window"/>
|
<field name="new_window"/>
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
<field attrs="{'invisible': [('parent_id', '!=', False)]}" name="website_id"/>
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Website menu" editable="bottom">
|
<tree string="Website menu" editable="bottom">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="website_id" options="{'no_create': True}"/>
|
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="url"/>
|
<field name="url"/>
|
||||||
<field name="new_window"/>
|
<field name="new_window"/>
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
<field name="inherit_id" ref="base.view_view_form"/>
|
<field name="inherit_id" ref="base.view_view_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="name" position="after">
|
<field name="name" position="after">
|
||||||
<field name="website_id" readonly="1"/>
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="key" readonly="1"/>
|
<field name="key" readonly="1"/>
|
||||||
<field name="page_ids" invisible="1" />
|
<field name="page_ids" invisible="1" />
|
||||||
<field name="first_page_id" attrs="{'invisible': [('page_ids', '=', [])]}" />
|
<field name="first_page_id" attrs="{'invisible': [('page_ids', '=', [])]}" />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<field name="inherit_id" ref="base.view_base_language_install"/>
|
<field name="inherit_id" ref="base.view_base_language_install"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<group states="init" position="inside">
|
<group states="init" position="inside">
|
||||||
<field name="website_ids" widget="many2many_checkboxes"/>
|
<field name="website_ids" widget="many2many_checkboxes" groups="website.group_multi_website"/>
|
||||||
</group>
|
</group>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ class WebsiteBlog(http.Controller):
|
|||||||
'/blog/page/<int:page>',
|
'/blog/page/<int:page>',
|
||||||
], type='http', auth="public", website=True)
|
], type='http', auth="public", website=True)
|
||||||
def blogs(self, page=1, **post):
|
def blogs(self, page=1, **post):
|
||||||
|
domain = request.website.website_domain()
|
||||||
Blog = request.env['blog.blog']
|
Blog = request.env['blog.blog']
|
||||||
blogs = Blog.search([], limit=2)
|
blogs = Blog.search(domain, limit=2)
|
||||||
if len(blogs) == 1:
|
if len(blogs) == 1:
|
||||||
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
|
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
|
||||||
|
|
||||||
BlogPost = request.env['blog.post']
|
BlogPost = request.env['blog.post']
|
||||||
total = BlogPost.search([], count=True)
|
total = BlogPost.search_count(domain)
|
||||||
|
|
||||||
pager = request.website.pager(
|
pager = request.website.pager(
|
||||||
url='/blog',
|
url='/blog',
|
||||||
@@ -62,7 +63,7 @@ class WebsiteBlog(http.Controller):
|
|||||||
page=page,
|
page=page,
|
||||||
step=self._blog_post_per_page,
|
step=self._blog_post_per_page,
|
||||||
)
|
)
|
||||||
posts = BlogPost.search([], offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
|
posts = BlogPost.search(domain, offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
|
||||||
blog_url = QueryURL('', ['blog', 'tag'])
|
blog_url = QueryURL('', ['blog', 'tag'])
|
||||||
return request.render("website_blog.latest_blogs", {
|
return request.render("website_blog.latest_blogs", {
|
||||||
'posts': posts,
|
'posts': posts,
|
||||||
@@ -71,10 +72,10 @@ class WebsiteBlog(http.Controller):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
'/blog/<model("blog.blog"):blog>',
|
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>''',
|
||||||
'/blog/<model("blog.blog"):blog>/page/<int:page>',
|
'''/blog/<model("blog.blog"):blog>/page/<int:page>''',
|
||||||
'/blog/<model("blog.blog"):blog>/tag/<string:tag>',
|
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>''',
|
||||||
'/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>',
|
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>''',
|
||||||
], type='http', auth="public", website=True)
|
], type='http', auth="public", website=True)
|
||||||
def blog(self, blog=None, tag=None, page=1, **opt):
|
def blog(self, blog=None, tag=None, page=1, **opt):
|
||||||
""" Prepare all values to display the blog.
|
""" Prepare all values to display the blog.
|
||||||
@@ -92,16 +93,19 @@ class WebsiteBlog(http.Controller):
|
|||||||
- 'date': date_begin optional parameter, used in archives navigation
|
- 'date': date_begin optional parameter, used in archives navigation
|
||||||
- 'blog_url': help object to create URLs
|
- 'blog_url': help object to create URLs
|
||||||
"""
|
"""
|
||||||
|
if not blog.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
|
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
|
||||||
published_count, unpublished_count = 0, 0
|
published_count, unpublished_count = 0, 0
|
||||||
|
|
||||||
|
domain = request.website.website_domain()
|
||||||
|
|
||||||
BlogPost = request.env['blog.post']
|
BlogPost = request.env['blog.post']
|
||||||
|
|
||||||
Blog = request.env['blog.blog']
|
Blog = request.env['blog.blog']
|
||||||
blogs = Blog.search([], order="create_date asc")
|
blogs = Blog.search(domain, order="create_date asc")
|
||||||
|
|
||||||
# build the domain for blog post to display
|
|
||||||
domain = []
|
|
||||||
# retrocompatibility to accept tag as slug
|
# retrocompatibility to accept tag as slug
|
||||||
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
|
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
|
||||||
if active_tag_ids:
|
if active_tag_ids:
|
||||||
@@ -172,7 +176,7 @@ class WebsiteBlog(http.Controller):
|
|||||||
response = request.render("website_blog.blog_post_short", values)
|
response = request.render("website_blog.blog_post_short", values)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@http.route(['/blog/<model("blog.blog"):blog>/feed'], type='http', auth="public")
|
@http.route(['''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/feed'''], type='http', auth="public")
|
||||||
def blog_feed(self, blog, limit='15'):
|
def blog_feed(self, blog, limit='15'):
|
||||||
v = {}
|
v = {}
|
||||||
v['blog'] = blog
|
v['blog'] = blog
|
||||||
@@ -185,7 +189,7 @@ class WebsiteBlog(http.Controller):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
'''/blog/<model("blog.blog"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
|
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
|
||||||
], type='http', auth="public", website=True)
|
], type='http', auth="public", website=True)
|
||||||
def blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
|
def blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
|
||||||
""" Prepare all values to display the blog.
|
""" Prepare all values to display the blog.
|
||||||
@@ -201,6 +205,9 @@ class WebsiteBlog(http.Controller):
|
|||||||
- 'nav_list': a dict [year][month] for archives navigation
|
- 'nav_list': a dict [year][month] for archives navigation
|
||||||
- 'next_post': next blog post, to direct the user towards the next interesting post
|
- 'next_post': next blog post, to direct the user towards the next interesting post
|
||||||
"""
|
"""
|
||||||
|
if not blog.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
BlogPost = request.env['blog.post']
|
BlogPost = request.env['blog.post']
|
||||||
date_begin, date_end = post.get('date_begin'), post.get('date_end')
|
date_begin, date_end = post.get('date_begin'), post.get('date_end')
|
||||||
|
|
||||||
@@ -271,6 +278,12 @@ class WebsiteBlog(http.Controller):
|
|||||||
|
|
||||||
@http.route('/blog/<int:blog_id>/post/new', type='http', auth="public", website=True)
|
@http.route('/blog/<int:blog_id>/post/new', type='http', auth="public", website=True)
|
||||||
def blog_post_create(self, blog_id, **post):
|
def blog_post_create(self, blog_id, **post):
|
||||||
|
# Use sudo so this line prevents both editor and admin to access blog from another website
|
||||||
|
# as browse() will return the record even if forbidden by security rules but editor won't
|
||||||
|
# be able to access it
|
||||||
|
if not request.env['blog.blog'].browse(blog_id).sudo().can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
new_blog_post = request.env['blog.post'].create({
|
new_blog_post = request.env['blog.post'].create({
|
||||||
'blog_id': blog_id,
|
'blog_id': blog_id,
|
||||||
'website_published': False,
|
'website_published': False,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))"/>
|
<field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))"/>
|
||||||
<field name="parent_id" ref="website.main_menu"/>
|
<field name="parent_id" ref="website.main_menu"/>
|
||||||
<field name="sequence" type="int">40</field>
|
<field name="sequence" type="int">40</field>
|
||||||
<field name="website_id" ref="website.default_website"/>
|
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Pre loaded covers -->
|
<!-- Pre loaded covers -->
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from odoo.tools import html2plaintext
|
|||||||
class Blog(models.Model):
|
class Blog(models.Model):
|
||||||
_name = 'blog.blog'
|
_name = 'blog.blog'
|
||||||
_description = 'Blogs'
|
_description = 'Blogs'
|
||||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
_inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin']
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
|
|
||||||
name = fields.Char('Blog Name', required=True, translate=True)
|
name = fields.Char('Blog Name', required=True, translate=True)
|
||||||
@@ -108,7 +108,7 @@ class BlogTag(models.Model):
|
|||||||
class BlogPost(models.Model):
|
class BlogPost(models.Model):
|
||||||
_name = "blog.post"
|
_name = "blog.post"
|
||||||
_description = "Blog Post"
|
_description = "Blog Post"
|
||||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
|
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||||
_order = 'id DESC'
|
_order = 'id DESC'
|
||||||
_mail_post_access = 'read'
|
_mail_post_access = 'read'
|
||||||
|
|
||||||
@@ -168,6 +168,8 @@ class BlogPost(models.Model):
|
|||||||
visits = fields.Integer('No of Views', copy=False)
|
visits = fields.Integer('No of Views', copy=False)
|
||||||
ranking = fields.Float(compute='_compute_ranking', string='Ranking')
|
ranking = fields.Float(compute='_compute_ranking', string='Ranking')
|
||||||
|
|
||||||
|
website_id = fields.Many2one(related='blog_id.website_id', readonly=True)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@api.depends('content', 'teaser_manual')
|
@api.depends('content', 'teaser_manual')
|
||||||
def _compute_teaser(self):
|
def _compute_teaser(self):
|
||||||
|
|||||||
@@ -64,26 +64,26 @@
|
|||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
<span class="fa fa-calendar mr8" role="img" aria-label="Post date" title="Post date"> <span t-field="post.post_date"/></span>
|
<span class="fa fa-calendar mr8" role="img" aria-label="Post date" title="Post date" />
|
||||||
<span class="fa fa-folder-open"> In
|
<span t-field="post.post_date"/>
|
||||||
<a class="mr8" t-attf-href="#{blog_url(blog=post.blog_id)}">
|
<span class="fa fa-folder-open" /> In
|
||||||
<span t-field="post.blog_id"/>
|
<a class="mr8" t-attf-href="#{blog_url(blog=post.blog_id)}">
|
||||||
</a>
|
<span t-field="post.blog_id"/>
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted fa fa-tags">
|
<div class="text-muted">
|
||||||
|
<span class="fa fa-tags" role="img" aria-label="Tags" title="Tags"/>
|
||||||
<span t-field="post.website_meta_keywords"/>
|
<span t-field="post.website_meta_keywords"/>
|
||||||
<span t-if="editable and not post.website_meta_keywords" class="badge badge-danger">
|
<span t-if="editable and not post.website_meta_keywords" class="badge badge-danger">
|
||||||
No keywords defined!
|
No keywords defined!
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted" t-if="len(post.message_ids) > 0">
|
<div class="text-muted" t-if="len(post.message_ids) > 0">
|
||||||
<span class="fa fa-comment-o">
|
<span class="fa fa-comment-o"/>
|
||||||
<a t-attf-href="#{blog_url('', ['blogpost'], blogpost=post)}#comments">
|
<a t-attf-href="#{blog_url('', ['blogpost'], blogpost=post)}#comments">
|
||||||
<t t-if="len(post.message_ids) <= 1" ><t t-esc="len(post.message_ids)"/> comment</t>
|
<t t-if="len(post.message_ids) <= 1" ><t t-esc="len(post.message_ids)"/> comment</t>
|
||||||
<t t-if="len(post.message_ids) > 1"><t t-esc="len(post.message_ids)"/> comments</t>
|
<t t-if="len(post.message_ids) > 1"><t t-esc="len(post.message_ids)"/> comments</t>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted mb16" t-field="post.website_meta_description"/>
|
<div class="text-muted mb16" t-field="post.website_meta_description"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Blogs">
|
<tree string="Blogs">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="subtitle"/>
|
<field name="subtitle"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<div class="oe_chatter">
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="blog_id"/>
|
<field name="blog_id"/>
|
||||||
|
<field name="website_id"/>
|
||||||
<field name="visits"/>
|
<field name="visits"/>
|
||||||
<field name="create_uid" invisible="1"/>
|
<field name="create_uid" invisible="1"/>
|
||||||
<field name="write_uid"/>
|
<field name="write_uid"/>
|
||||||
@@ -73,6 +76,7 @@
|
|||||||
<field name="name" placeholder="Blog Post Title"/>
|
<field name="name" placeholder="Blog Post Title"/>
|
||||||
<field name="subtitle" placeholder="Blog Subtitle"/>
|
<field name="subtitle" placeholder="Blog Subtitle"/>
|
||||||
<field name="tag_ids" widget="many2many_tags"/>
|
<field name="tag_ids" widget="many2many_tags"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Publishing Options">
|
<group string="Publishing Options">
|
||||||
<field name="author_id"/>
|
<field name="author_id"/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<field name="model">res.config.settings</field>
|
<field name="model">res.config.settings</field>
|
||||||
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
|
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<div id="webmaster_settings" position="after">
|
<div id="multi_website" position="after">
|
||||||
<h2>Communication</h2>
|
<h2>Communication</h2>
|
||||||
<div class="row mt16 o_settings_container" id="communication_settings">
|
<div class="row mt16 o_settings_container" id="communication_settings">
|
||||||
<div class="col-12 col-lg-6 o_setting_box" id="contact_form_install_setting" title="New messages are managed as leads or opportunities in your CRM app.">
|
<div class="col-12 col-lg-6 o_setting_box" id="contact_form_install_setting" title="New messages are managed as leads or opportunities in your CRM app.">
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class WebsiteCrmPartnerAssign(WebsitePartnerPage):
|
|||||||
partners = partner_ids.sudo()
|
partners = partner_ids.sudo()
|
||||||
|
|
||||||
google_map_partner_ids = ','.join(str(p.id) for p in partners)
|
google_map_partner_ids = ','.join(str(p.id) for p in partners)
|
||||||
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
|
google_maps_api_key = request.website.google_maps_api_key
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'countries': countries,
|
'countries': countries,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class WebsiteCustomer(http.Controller):
|
|||||||
|
|
||||||
partners = Partner.sudo().search(domain, offset=pager['offset'], limit=self._references_per_page)
|
partners = Partner.sudo().search(domain, offset=pager['offset'], limit=self._references_per_page)
|
||||||
google_map_partner_ids = ','.join(str(it) for it in partners.ids)
|
google_map_partner_ids = ','.join(str(it) for it in partners.ids)
|
||||||
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
|
google_maps_api_key = request.website.google_maps_api_key
|
||||||
|
|
||||||
tags = Tag.search([('website_published', '=', True), ('partner_ids', 'in', partners.ids)], order='classname, name ASC')
|
tags = Tag.search([('website_published', '=', True), ('partner_ids', 'in', partners.ids)], order='classname, name ASC')
|
||||||
tag = tag_id and Tag.browse(tag_id) or False
|
tag = tag_id and Tag.browse(tag_id) or False
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class WebsiteEventController(http.Controller):
|
|||||||
searches.setdefault('type', 'all')
|
searches.setdefault('type', 'all')
|
||||||
searches.setdefault('country', 'all')
|
searches.setdefault('country', 'all')
|
||||||
|
|
||||||
domain_search = {}
|
|
||||||
|
|
||||||
def sdn(date):
|
def sdn(date):
|
||||||
return fields.Datetime.to_string(date.replace(hour=23, minute=59, second=59))
|
return fields.Datetime.to_string(date.replace(hour=23, minute=59, second=59))
|
||||||
@@ -64,6 +63,7 @@ class WebsiteEventController(http.Controller):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# search domains
|
# search domains
|
||||||
|
domain_search = {'website_specific': request.website.website_domain()}
|
||||||
current_date = None
|
current_date = None
|
||||||
current_type = None
|
current_type = None
|
||||||
current_country = None
|
current_country = None
|
||||||
@@ -141,8 +141,11 @@ class WebsiteEventController(http.Controller):
|
|||||||
|
|
||||||
return request.render("website_event.index", values)
|
return request.render("website_event.index", values)
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/page/<path:page>'], type='http', auth="public", website=True, sitemap=False)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/page/<path:page>'''], type='http', auth="public", website=True, sitemap=False)
|
||||||
def event_page(self, event, page, **post):
|
def event_page(self, event, page, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'event': event,
|
'event': event,
|
||||||
'main_object': event
|
'main_object': event
|
||||||
@@ -161,8 +164,11 @@ class WebsiteEventController(http.Controller):
|
|||||||
|
|
||||||
return request.render(page, values)
|
return request.render(page, values)
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>'], type='http', auth="public", website=True)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>'''], type='http', auth="public", website=True)
|
||||||
def event(self, event, **post):
|
def event(self, event, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
if event.menu_id and event.menu_id.child_id:
|
if event.menu_id and event.menu_id.child_id:
|
||||||
target_url = event.menu_id.child_id[0].url
|
target_url = event.menu_id.child_id[0].url
|
||||||
else:
|
else:
|
||||||
@@ -171,8 +177,11 @@ class WebsiteEventController(http.Controller):
|
|||||||
target_url += '?enable_editor=1'
|
target_url += '?enable_editor=1'
|
||||||
return request.redirect(target_url)
|
return request.redirect(target_url)
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True, sitemap=False)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/register'''], type='http', auth="public", website=True, sitemap=False)
|
||||||
def event_register(self, event, **post):
|
def event_register(self, event, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'event': event,
|
'event': event,
|
||||||
'main_object': event,
|
'main_object': event,
|
||||||
@@ -195,6 +204,7 @@ class WebsiteEventController(http.Controller):
|
|||||||
'date_begin': fields.Date.to_string(date_begin),
|
'date_begin': fields.Date.to_string(date_begin),
|
||||||
'date_end': fields.Date.to_string((date_begin + timedelta(days=(1)))),
|
'date_end': fields.Date.to_string((date_begin + timedelta(days=(1)))),
|
||||||
'seats_available': 1000,
|
'seats_available': 1000,
|
||||||
|
'website_id': request.website.id,
|
||||||
}
|
}
|
||||||
return request.env['event.event'].with_context(context or {}).create(vals)
|
return request.env['event.event'].with_context(context or {}).create(vals)
|
||||||
|
|
||||||
@@ -210,11 +220,12 @@ class WebsiteEventController(http.Controller):
|
|||||||
country_code = request.session['geoip'].get('country_code')
|
country_code = request.session['geoip'].get('country_code')
|
||||||
result = {'events': [], 'country': False}
|
result = {'events': [], 'country': False}
|
||||||
events = None
|
events = None
|
||||||
|
domain = request.website.website_domain()
|
||||||
if country_code:
|
if country_code:
|
||||||
country = request.env['res.country'].search([('code', '=', country_code)], limit=1)
|
country = request.env['res.country'].search([('code', '=', country_code)], limit=1)
|
||||||
events = Event.search(['|', ('address_id', '=', None), ('country_id.code', '=', country_code), ('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
events = Event.search(domain + ['|', ('address_id', '=', None), ('country_id.code', '=', country_code), ('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||||
if not events:
|
if not events:
|
||||||
events = Event.search([('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
events = Event.search(domain + [('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||||
for event in events:
|
for event in events:
|
||||||
if country_code and event.country_id.code == country_code:
|
if country_code and event.country_id.code == country_code:
|
||||||
result['country'] = country
|
result['country'] = country
|
||||||
@@ -252,8 +263,11 @@ class WebsiteEventController(http.Controller):
|
|||||||
registration[key] = value
|
registration[key] = value
|
||||||
return list(registrations.values())
|
return list(registrations.values())
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/registration/confirm'''], type='http', auth="public", methods=['POST'], website=True)
|
||||||
def registration_confirm(self, event, **post):
|
def registration_confirm(self, event, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
Attendees = request.env['event.registration']
|
Attendees = request.env['event.registration']
|
||||||
registrations = self._process_registration_details(post)
|
registrations = self._process_registration_details(post)
|
||||||
|
|
||||||
@@ -270,7 +284,7 @@ class WebsiteEventController(http.Controller):
|
|||||||
'iCal_url': urls.get('iCal_url')
|
'iCal_url': urls.get('iCal_url')
|
||||||
})
|
})
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/ics'], type='http', auth="public", website=True)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/ics'''], type='http', auth="public", website=True)
|
||||||
def make_event_ics_file(self, event, **kwargs):
|
def make_event_ics_file(self, event, **kwargs):
|
||||||
if not event or not event.registration_ids:
|
if not event or not event.registration_ids:
|
||||||
return request.not_found()
|
return request.not_found()
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ class EventType(models.Model):
|
|||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
_name = 'event.event'
|
_name = 'event.event'
|
||||||
_inherit = ['event.event', 'website.seo.metadata', 'website.published.mixin']
|
_inherit = ['event.event', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||||
|
|
||||||
website_published = fields.Boolean(track_visibility='onchange')
|
is_published = fields.Boolean(track_visibility='onchange')
|
||||||
|
|
||||||
is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating")
|
is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating")
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class Event(models.Model):
|
|||||||
|
|
||||||
def _compute_is_participating(self):
|
def _compute_is_participating(self):
|
||||||
# we don't allow public user to see participating label
|
# we don't allow public user to see participating label
|
||||||
if self.env.user != self.env.ref('base.public_user'):
|
if self.env.user != self.env['website'].get_current_website().user_id:
|
||||||
email = self.env.user.partner_id.email
|
email = self.env.user.partner_id.email
|
||||||
for event in self:
|
for event in self:
|
||||||
domain = ['&', '|', ('email', '=', email), ('partner_id', '=', self.env.user.partner_id.id), ('event_id', '=', event.id)]
|
domain = ['&', '|', ('email', '=', email), ('partner_id', '=', self.env.user.partner_id.id), ('event_id', '=', event.id)]
|
||||||
@@ -84,7 +84,7 @@ class Event(models.Model):
|
|||||||
event.menu_id.unlink()
|
event.menu_id.unlink()
|
||||||
elif event.website_menu:
|
elif event.website_menu:
|
||||||
if not event.menu_id:
|
if not event.menu_id:
|
||||||
root_menu = self.env['website.menu'].create({'name': event.name})
|
root_menu = self.env['website.menu'].create({'name': event.name, 'website_id': event.id})
|
||||||
event.menu_id = root_menu
|
event.menu_id = root_menu
|
||||||
for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
|
for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
|
||||||
event._create_menu(sequence, name, url, xml_id)
|
event._create_menu(sequence, name, url, xml_id)
|
||||||
@@ -99,6 +99,7 @@ class Event(models.Model):
|
|||||||
'url': url,
|
'url': url,
|
||||||
'parent_id': self.menu_id.id,
|
'parent_id': self.menu_id.id,
|
||||||
'sequence': sequence,
|
'sequence': sequence,
|
||||||
|
'website_id': self.id,
|
||||||
})
|
})
|
||||||
return menu
|
return menu
|
||||||
|
|
||||||
@@ -119,9 +120,9 @@ class Event(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _track_subtype(self, init_values):
|
def _track_subtype(self, init_values):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if 'website_published' in init_values and self.website_published:
|
if 'is_published' in init_values and self.is_published:
|
||||||
return 'website_event.mt_event_published'
|
return 'website_event.mt_event_published'
|
||||||
elif 'website_published' in init_values and not self.website_published:
|
elif 'is_published' in init_values and not self.is_published:
|
||||||
return 'website_event.mt_event_unpublished'
|
return 'website_event.mt_event_unpublished'
|
||||||
return super(Event, self)._track_subtype(init_values)
|
return super(Event, self)._track_subtype(init_values)
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
<field name="model">event.event</field>
|
<field name="model">event.event</field>
|
||||||
<field name="inherit_id" ref="event.view_event_form"/>
|
<field name="inherit_id" ref="event.view_event_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
<field name="organizer_id" position="before">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
<button name="toggle_active" position="before">
|
<button name="toggle_active" position="before">
|
||||||
<field name="website_url" invisible="1"/>
|
<field name="website_url" invisible="1"/>
|
||||||
<button name="website_publish_button"
|
<button name="website_publish_button"
|
||||||
@@ -57,4 +60,15 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="event_event_view_list_inherit_website" model="ir.ui.view">
|
||||||
|
<field name="name">event.event.view.list.inherit.website</field>
|
||||||
|
<field name="model">event.event</field>
|
||||||
|
<field name="inherit_id" ref="event.view_event_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ from odoo.http import request
|
|||||||
|
|
||||||
class WebsiteEventSaleController(WebsiteEventController):
|
class WebsiteEventSaleController(WebsiteEventController):
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True)
|
@http.route()
|
||||||
def event_register(self, event, **post):
|
def event_register(self, event, **post):
|
||||||
event = event.with_context(pricelist=request.website.get_current_pricelist().id)
|
event = event.with_context(pricelist=request.website.id)
|
||||||
return super(WebsiteEventSaleController, self).event_register(event, **post)
|
return super(WebsiteEventSaleController, self).event_register(event, **post)
|
||||||
|
|
||||||
def _process_tickets_details(self, data):
|
def _process_tickets_details(self, data):
|
||||||
@@ -25,7 +25,7 @@ class WebsiteEventSaleController(WebsiteEventController):
|
|||||||
tickets = request.env['event.event.ticket'].browse(tuple(ticket_post))
|
tickets = request.env['event.event.ticket'].browse(tuple(ticket_post))
|
||||||
return [{'id': ticket.id, 'name': ticket.name, 'quantity': ticket_post[ticket.id], 'price': ticket.price} for ticket in tickets if ticket_post[ticket.id]]
|
return [{'id': ticket.id, 'name': ticket.name, 'quantity': ticket_post[ticket.id], 'price': ticket.price} for ticket in tickets if ticket_post[ticket.id]]
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
@http.route()
|
||||||
def registration_confirm(self, event, **post):
|
def registration_confirm(self, event, **post):
|
||||||
order = request.website.sale_get_order(force_create=1)
|
order = request.website.sale_get_order(force_create=1)
|
||||||
attendee_ids = set()
|
attendee_ids = set()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import babel
|
|||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import pytz
|
import pytz
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from odoo import fields, http
|
from odoo import fields, http
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
@@ -13,8 +14,11 @@ from odoo.tools import html_escape as escape, html2plaintext
|
|||||||
|
|
||||||
class WebsiteEventTrackController(http.Controller):
|
class WebsiteEventTrackController(http.Controller):
|
||||||
|
|
||||||
@http.route(['''/event/<model("event.event"):event>/track/<model("event.track", "[('event_id','=',event[0])]"):track>'''], type='http', auth="public", website=True)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track/<model("event.track", "[('event_id','=',event[0])]"):track>'''], type='http', auth="public", website=True)
|
||||||
def event_track_view(self, event, track, **post):
|
def event_track_view(self, event, track, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
track = track.sudo().with_context(tz=event.date_tz or 'UTC')
|
track = track.sudo().with_context(tz=event.date_tz or 'UTC')
|
||||||
values = {'track': track, 'event': track.event_id, 'main_object': track}
|
values = {'track': track, 'event': track.event_id, 'main_object': track}
|
||||||
return request.render("website_event_track.track_view", values)
|
return request.render("website_event_track.track_view", values)
|
||||||
@@ -65,8 +69,11 @@ class WebsiteEventTrackController(http.Controller):
|
|||||||
'dates': dates
|
'dates': dates
|
||||||
}
|
}
|
||||||
|
|
||||||
@http.route(['''/event/<model("event.event"):event>/agenda'''], type='http', auth="public", website=True, sitemap=False)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/agenda'''], type='http', auth="public", website=True, sitemap=False)
|
||||||
def event_agenda(self, event, tag=None, **post):
|
def event_agenda(self, event, tag=None, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||||
local_tz = pytz.timezone(event.date_tz or 'UTC')
|
local_tz = pytz.timezone(event.date_tz or 'UTC')
|
||||||
days_tracks = collections.defaultdict(lambda: [])
|
days_tracks = collections.defaultdict(lambda: [])
|
||||||
@@ -90,10 +97,13 @@ class WebsiteEventTrackController(http.Controller):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
'''/event/<model("event.event"):event>/track''',
|
'''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track''',
|
||||||
'''/event/<model("event.event"):event>/track/tag/<model("event.track.tag"):tag>'''
|
'''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track/tag/<model("event.track.tag"):tag>'''
|
||||||
], type='http', auth="public", website=True, sitemap=False)
|
], type='http', auth="public", website=True, sitemap=False)
|
||||||
def event_tracks(self, event, tag=None, **post):
|
def event_tracks(self, event, tag=None, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||||
searches = {}
|
searches = {}
|
||||||
if tag:
|
if tag:
|
||||||
@@ -112,12 +122,18 @@ class WebsiteEventTrackController(http.Controller):
|
|||||||
}
|
}
|
||||||
return request.render("website_event_track.tracks", values)
|
return request.render("website_event_track.tracks", values)
|
||||||
|
|
||||||
@http.route(['''/event/<model("event.event"):event>/track_proposal'''], type='http', auth="public", website=True, sitemap=False)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track_proposal'''], type='http', auth="public", website=True, sitemap=False)
|
||||||
def event_track_proposal(self, event, **post):
|
def event_track_proposal(self, event, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
return request.render("website_event_track.event_track_proposal", {'event': event})
|
return request.render("website_event_track.event_track_proposal", {'event': event})
|
||||||
|
|
||||||
@http.route(['/event/<model("event.event"):event>/track_proposal/post'], type='http', auth="public", methods=['POST'], website=True)
|
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track_proposal/post'''], type='http', auth="public", methods=['POST'], website=True)
|
||||||
def event_track_proposal_post(self, event, **post):
|
def event_track_proposal_post(self, event, **post):
|
||||||
|
if not event.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
tags = []
|
tags = []
|
||||||
for tag in event.allowed_track_tag_ids:
|
for tag in event.allowed_track_tag_ids:
|
||||||
if post.get('tag_' + str(tag.id)):
|
if post.get('tag_' + str(tag.id)):
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ class WebsiteForum(http.Controller):
|
|||||||
|
|
||||||
@http.route(['/forum'], type='http', auth="public", website=True)
|
@http.route(['/forum'], type='http', auth="public", website=True)
|
||||||
def forum(self, **kwargs):
|
def forum(self, **kwargs):
|
||||||
forums = request.env['forum.forum'].search([])
|
domain = request.website.get_current_website().website_domain()
|
||||||
|
forums = request.env['forum.forum'].search(domain)
|
||||||
return request.render("website_forum.forum_all", {'forums': forums})
|
return request.render("website_forum.forum_all", {'forums': forums})
|
||||||
|
|
||||||
@http.route('/forum/new', type='json', auth="user", methods=['POST'], website=True)
|
@http.route('/forum/new', type='json', auth="user", methods=['POST'], website=True)
|
||||||
@@ -104,6 +105,7 @@ class WebsiteForum(http.Controller):
|
|||||||
def sitemap_forum(env, rule, qs):
|
def sitemap_forum(env, rule, qs):
|
||||||
Forum = env['forum.forum']
|
Forum = env['forum.forum']
|
||||||
dom = sitemap_qs2dom(qs, '/forum', Forum._rec_name)
|
dom = sitemap_qs2dom(qs, '/forum', Forum._rec_name)
|
||||||
|
dom += env['website'].get_current_website().website_domain()
|
||||||
for f in Forum.search(dom):
|
for f in Forum.search(dom):
|
||||||
loc = '/forum/%s' % slug(f)
|
loc = '/forum/%s' % slug(f)
|
||||||
if not qs or qs.lower() in loc:
|
if not qs or qs.lower() in loc:
|
||||||
@@ -115,6 +117,9 @@ class WebsiteForum(http.Controller):
|
|||||||
'''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions/page/<int:page>''',
|
'''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions/page/<int:page>''',
|
||||||
], type='http', auth="public", website=True, sitemap=sitemap_forum)
|
], type='http', auth="public", website=True, sitemap=sitemap_forum)
|
||||||
def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', post_type=None, **post):
|
def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', post_type=None, **post):
|
||||||
|
if not forum.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
Post = request.env['forum.post']
|
Post = request.env['forum.post']
|
||||||
|
|
||||||
domain = [('forum_id', '=', forum.id), ('parent_id', '=', False), ('state', '=', 'active')]
|
domain = [('forum_id', '=', forum.id), ('parent_id', '=', False), ('state', '=', 'active')]
|
||||||
@@ -174,7 +179,7 @@ class WebsiteForum(http.Controller):
|
|||||||
})
|
})
|
||||||
return request.render("website_forum.forum_index", values)
|
return request.render("website_forum.forum_index", values)
|
||||||
|
|
||||||
@http.route(['/forum/<model("forum.forum"):forum>/faq'], type='http', auth="public", website=True)
|
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/faq'''], type='http', auth="public", website=True)
|
||||||
def forum_faq(self, forum, **post):
|
def forum_faq(self, forum, **post):
|
||||||
values = self._prepare_forum_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post)
|
values = self._prepare_forum_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post)
|
||||||
return request.render("website_forum.faq", values)
|
return request.render("website_forum.faq", values)
|
||||||
@@ -232,7 +237,7 @@ class WebsiteForum(http.Controller):
|
|||||||
except IOError:
|
except IOError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@http.route(['''/forum/<model("forum.forum"):forum>/question/<model("forum.post", "[('forum_id','=',forum[0]),('parent_id','=',False),('can_view', '=', True)]"):question>'''], type='http', auth="public", website=True)
|
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/question/<model("forum.post", "[('forum_id','=',forum[0]),('parent_id','=',False),('can_view', '=', True)]"):question>'''], type='http', auth="public", website=True)
|
||||||
def question(self, forum, question, **post):
|
def question(self, forum, question, **post):
|
||||||
if not forum.active:
|
if not forum.active:
|
||||||
return request.render("website_forum.header", {'forum': forum})
|
return request.render("website_forum.header", {'forum': forum})
|
||||||
@@ -548,8 +553,8 @@ class WebsiteForum(http.Controller):
|
|||||||
# User
|
# User
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
@http.route(['/forum/<model("forum.forum"):forum>/users',
|
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/users''',
|
||||||
'/forum/<model("forum.forum"):forum>/users/page/<int:page>'],
|
'''/forum/<model("forum.forum"):forum>/users/page/<int:page>'''],
|
||||||
type='http', auth="public", website=True)
|
type='http', auth="public", website=True)
|
||||||
def users(self, forum, page=1, **searches):
|
def users(self, forum, page=1, **searches):
|
||||||
User = request.env['res.users']
|
User = request.env['res.users']
|
||||||
@@ -733,7 +738,7 @@ class WebsiteForum(http.Controller):
|
|||||||
# Badges
|
# Badges
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
@http.route('/forum/<model("forum.forum"):forum>/badge', type='http', auth="public", website=True)
|
@http.route('''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/badge''', type='http', auth="public", website=True)
|
||||||
def badges(self, forum, **searches):
|
def badges(self, forum, **searches):
|
||||||
Badge = request.env['gamification.badge']
|
Badge = request.env['gamification.badge']
|
||||||
badges = Badge.sudo().search([('challenge_ids.category', '=', 'forum')])
|
badges = Badge.sudo().search([('challenge_ids.category', '=', 'forum')])
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class KarmaError(Forbidden):
|
|||||||
class Forum(models.Model):
|
class Forum(models.Model):
|
||||||
_name = 'forum.forum'
|
_name = 'forum.forum'
|
||||||
_description = 'Forum'
|
_description = 'Forum'
|
||||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
_inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin']
|
||||||
|
|
||||||
@api.model_cr
|
@api.model_cr
|
||||||
def init(self):
|
def init(self):
|
||||||
@@ -208,6 +208,7 @@ class Post(models.Model):
|
|||||||
('discussion', 'Discussion')],
|
('discussion', 'Discussion')],
|
||||||
string='Type', default='question', required=True)
|
string='Type', default='question', required=True)
|
||||||
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
||||||
|
website_id = fields.Many2one(related='forum_id.website_id', readonly=True)
|
||||||
|
|
||||||
# history
|
# history
|
||||||
create_date = fields.Datetime('Asked on', index=True, readonly=True)
|
create_date = fields.Datetime('Asked on', index=True, readonly=True)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Forums">
|
<tree string="Forums">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="allow_question"/>
|
<field name="allow_question"/>
|
||||||
<field name="allow_link"/>
|
<field name="allow_link"/>
|
||||||
@@ -39,6 +40,9 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
</h1>
|
</h1>
|
||||||
<field name="description"/>
|
<field name="description"/>
|
||||||
|
<group>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Options">
|
<page string="Options">
|
||||||
<group string="Post Types">
|
<group string="Post Types">
|
||||||
@@ -138,6 +142,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="forum_id"/>
|
<field name="forum_id"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -160,6 +165,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="forum_id"/>
|
<field name="forum_id"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
</group><group>
|
</group><group>
|
||||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class GoogleMap(http.Controller):
|
|||||||
else:
|
else:
|
||||||
partner_url = '/partners/'
|
partner_url = '/partners/'
|
||||||
|
|
||||||
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
|
google_maps_api_key = request.website.google_maps_api_key
|
||||||
values = {
|
values = {
|
||||||
'partner_url': partner_url,
|
'partner_url': partner_url,
|
||||||
'partner_data': json.dumps(partner_data),
|
'partner_data': json.dumps(partner_data),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from odoo import api, fields, models
|
|||||||
|
|
||||||
class HrEmployee(models.Model):
|
class HrEmployee(models.Model):
|
||||||
_name = 'hr.employee'
|
_name = 'hr.employee'
|
||||||
_inherit = ['hr.employee', 'website.published.mixin']
|
_inherit = ['hr.employee', 'website.published.multi.mixin']
|
||||||
|
|
||||||
public_info = fields.Char(string='Public Info')
|
public_info = fields.Char(string='Public Info')
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Applicant(models.Model):
|
|||||||
class Job(models.Model):
|
class Job(models.Model):
|
||||||
|
|
||||||
_name = 'hr.job'
|
_name = 'hr.job'
|
||||||
_inherit = ['hr.job', 'website.seo.metadata', 'website.published.mixin']
|
_inherit = ['hr.job', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||||
|
|
||||||
def _get_default_website_description(self):
|
def _get_default_website_description(self):
|
||||||
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
|
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<div class="col-12 col-lg-6 o_setting_box" id="live_chat_install_setting">
|
<div class="col-12 col-lg-6 o_setting_box" id="live_chat_install_setting">
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<span class="o_form_label">Live Chat</span>
|
<span class="o_form_label">Live Chat</span>
|
||||||
|
<span class="fa fa-lg fa-globe" title="Values set here are website-specific." groups="website.group_multi_website"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Live chat channel of your website
|
Live chat channel of your website
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<template id="s_newsletter_subscribe_popup" name="Newsletter Popup">
|
<template id="s_newsletter_subscribe_popup" name="Newsletter Popup">
|
||||||
<div class="o_newsletter_popup" data-list-id="0">
|
<div class="o_newsletter_popup" data-list-id="0">
|
||||||
<div class="alert alert-warning alert-dismissible fade in css_non_editable_mode_hidden o_not_editable" role="status">
|
<div class="alert alert-warning alert-dismissible fade show css_non_editable_mode_hidden o_not_editable" role="status">
|
||||||
<strong>Newsletter Popup!</strong> The newsletter popup snippet effect is active on this page. Click <a id="edit_dialog" href="#">Here To Edit Dialog Content</a>
|
<strong>Newsletter Popup!</strong> The newsletter popup snippet effect is active on this page. Click <a id="edit_dialog" href="#">Here To Edit Dialog Content</a>
|
||||||
</div>
|
</div>
|
||||||
<div role="dialog" class="modal modal-dialog modal-md fade fade-custom" id="o_newsletter_popup" tabindex="-1">
|
<div role="dialog" class="modal modal-dialog modal-md fade fade-custom" id="o_newsletter_popup" tabindex="-1">
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class WebsiteMembership(http.Controller):
|
|||||||
count_members += len(free_partner_ids)
|
count_members += len(free_partner_ids)
|
||||||
|
|
||||||
google_map_partner_ids = ",".join(str(it) for it in google_map_partner_ids)
|
google_map_partner_ids = ",".join(str(it) for it in google_map_partner_ids)
|
||||||
google_maps_api_key = request.env['ir.config_parameter'].sudo().get_param('google_maps_api_key')
|
google_maps_api_key = request.website.google_maps_api_key
|
||||||
|
|
||||||
partners = {p.id: p for p in Partner.sudo().browse(list(page_partner_ids))}
|
partners = {p.id: p for p in Partner.sudo().browse(list(page_partner_ids))}
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@
|
|||||||
from . import controllers
|
from . import controllers
|
||||||
from . import models
|
from . import models
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
from . import report
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ from odoo.http import request
|
|||||||
class WebsiteSaleBackend(WebsiteBackend):
|
class WebsiteSaleBackend(WebsiteBackend):
|
||||||
|
|
||||||
@http.route()
|
@http.route()
|
||||||
def fetch_dashboard_data(self, date_from, date_to):
|
def fetch_dashboard_data(self, website_id, date_from, date_to):
|
||||||
results = super(WebsiteSaleBackend, self).fetch_dashboard_data(date_from, date_to)
|
Website = request.env['website']
|
||||||
|
current_website = website_id and Website.browse(website_id) or Website.get_current_website()
|
||||||
|
|
||||||
|
results = super(WebsiteSaleBackend, self).fetch_dashboard_data(website_id, date_from, date_to)
|
||||||
sales_values = dict(
|
sales_values = dict(
|
||||||
graph=[],
|
graph=[],
|
||||||
best_sellers=[],
|
best_sellers=[],
|
||||||
@@ -40,6 +42,7 @@ class WebsiteSaleBackend(WebsiteBackend):
|
|||||||
# Product-based computation
|
# Product-based computation
|
||||||
report_product_lines = request.env['sale.report'].read_group(
|
report_product_lines = request.env['sale.report'].read_group(
|
||||||
domain=[
|
domain=[
|
||||||
|
('website_id', '=', current_website.id),
|
||||||
('team_id.team_type', '=', 'website'),
|
('team_id.team_type', '=', 'website'),
|
||||||
('state', 'in', ['sale', 'done']),
|
('state', 'in', ['sale', 'done']),
|
||||||
('confirmation_date', '>=', date_from),
|
('confirmation_date', '>=', date_from),
|
||||||
@@ -57,6 +60,7 @@ class WebsiteSaleBackend(WebsiteBackend):
|
|||||||
|
|
||||||
# Sale-based results computation
|
# Sale-based results computation
|
||||||
sale_order_domain = [
|
sale_order_domain = [
|
||||||
|
('website_id', '=', current_website.id),
|
||||||
('team_id', 'in', request.env['crm.team'].search([('team_type', '=', 'website')]).ids),
|
('team_id', 'in', request.env['crm.team'].search([('team_type', '=', 'website')]).ids),
|
||||||
('date_order', '>=', fields.Datetime.to_string(datetime_from)),
|
('date_order', '>=', fields.Datetime.to_string(datetime_from)),
|
||||||
('date_order', '<=', fields.Datetime.to_string(datetime_to))]
|
('date_order', '<=', fields.Datetime.to_string(datetime_to))]
|
||||||
@@ -70,6 +74,7 @@ class WebsiteSaleBackend(WebsiteBackend):
|
|||||||
|
|
||||||
report_price_lines = request.env['sale.report'].read_group(
|
report_price_lines = request.env['sale.report'].read_group(
|
||||||
domain=[
|
domain=[
|
||||||
|
('website_id', '=', current_website.id),
|
||||||
('team_id.team_type', '=', 'website'),
|
('team_id.team_type', '=', 'website'),
|
||||||
('state', 'in', ['sale', 'done']),
|
('state', 'in', ['sale', 'done']),
|
||||||
('date', '>=', date_from),
|
('date', '>=', date_from),
|
||||||
@@ -110,6 +115,7 @@ class WebsiteSaleBackend(WebsiteBackend):
|
|||||||
previous_sale_label = _('Previous Year')
|
previous_sale_label = _('Previous Year')
|
||||||
|
|
||||||
sales_domain = [
|
sales_domain = [
|
||||||
|
('website_id', '=', current_website.id),
|
||||||
('team_id.team_type', '=', 'website'),
|
('team_id.team_type', '=', 'website'),
|
||||||
('state', 'in', ['sale', 'done']),
|
('state', 'in', ['sale', 'done']),
|
||||||
('confirmation_date', '>=', date_from),
|
('confirmation_date', '>=', date_from),
|
||||||
|
|||||||
@@ -206,12 +206,14 @@ class WebsiteSale(http.Controller):
|
|||||||
return domain
|
return domain
|
||||||
|
|
||||||
@http.route([
|
@http.route([
|
||||||
'/shop',
|
'''/shop''',
|
||||||
'/shop/page/<int:page>',
|
'''/shop/page/<int:page>''',
|
||||||
'/shop/category/<model("product.public.category"):category>',
|
'''/shop/category/<model("product.public.category", "[('website_id', 'in', (False, current_website_id))]"):category>''',
|
||||||
'/shop/category/<model("product.public.category"):category>/page/<int:page>'
|
'''/shop/category/<model("product.public.category", "[('website_id', 'in', (False, current_website_id))]"):category>/page/<int:page>'''
|
||||||
], type='http', auth="public", website=True)
|
], type='http', auth="public", website=True)
|
||||||
def shop(self, page=0, category=None, search='', ppg=False, **post):
|
def shop(self, page=0, category=None, search='', ppg=False, **post):
|
||||||
|
if category and not category.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
if ppg:
|
if ppg:
|
||||||
try:
|
try:
|
||||||
ppg = int(ppg)
|
ppg = int(ppg)
|
||||||
@@ -245,7 +247,7 @@ class WebsiteSale(http.Controller):
|
|||||||
if attrib_list:
|
if attrib_list:
|
||||||
post['attrib'] = attrib_list
|
post['attrib'] = attrib_list
|
||||||
|
|
||||||
categs = request.env['product.public.category'].search([('parent_id', '=', False)])
|
categs = request.env['product.public.category'].search([('parent_id', '=', False)] + request.website.website_domain())
|
||||||
Product = request.env['product.template']
|
Product = request.env['product.template']
|
||||||
|
|
||||||
parent_category_ids = []
|
parent_category_ids = []
|
||||||
@@ -292,6 +294,9 @@ class WebsiteSale(http.Controller):
|
|||||||
|
|
||||||
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
|
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
|
||||||
def product(self, product, category='', search='', **kwargs):
|
def product(self, product, category='', search='', **kwargs):
|
||||||
|
if not product.can_access_from_current_website():
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
product_context = dict(request.env.context,
|
product_context = dict(request.env.context,
|
||||||
active_id=product.id,
|
active_id=product.id,
|
||||||
partner=request.env.user.partner_id)
|
partner=request.env.user.partner_id)
|
||||||
@@ -578,6 +583,7 @@ class WebsiteSale(http.Controller):
|
|||||||
|
|
||||||
new_values['customer'] = True
|
new_values['customer'] = True
|
||||||
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
|
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
|
||||||
|
new_values['website_id'] = request.website.id
|
||||||
|
|
||||||
lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
|
lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
|
||||||
if lang:
|
if lang:
|
||||||
@@ -992,7 +998,8 @@ class WebsiteSale(http.Controller):
|
|||||||
def add_product(self, name=None, category=0, **post):
|
def add_product(self, name=None, category=0, **post):
|
||||||
product = request.env['product.product'].create({
|
product = request.env['product.product'].create({
|
||||||
'name': name or _("New Product"),
|
'name': name or _("New Product"),
|
||||||
'public_categ_ids': category
|
'public_categ_ids': category,
|
||||||
|
'website_id': request.website.id,
|
||||||
})
|
})
|
||||||
return "/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id)
|
return "/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo noupdate="1">
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<record model="website" id="website.website2">
|
||||||
|
<field name="salesteam_id" ref="sales_team.salesteam_website_sales"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="product.product_attribute_2" model="product.attribute">
|
<record id="product.product_attribute_2" model="product.attribute">
|
||||||
<field name="type">color</field>
|
<field name="type">color</field>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import account_invoice
|
||||||
from . import crm_team
|
from . import crm_team
|
||||||
from . import ir_http
|
from . import ir_http
|
||||||
from . import mail_compose_message
|
from . import mail_compose_message
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoice(models.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
website_id = fields.Many2one('website', related='partner_id.website_id', string='Website',
|
||||||
|
help='Website through which this invoice was created.',
|
||||||
|
store=True, readonly=True)
|
||||||
@@ -53,7 +53,7 @@ class ProductPricelist(models.Model):
|
|||||||
|
|
||||||
class ProductPublicCategory(models.Model):
|
class ProductPublicCategory(models.Model):
|
||||||
_name = "product.public.category"
|
_name = "product.public.category"
|
||||||
_inherit = ["website.seo.metadata"]
|
_inherit = ["website.seo.metadata", "website.published.multi.mixin"]
|
||||||
_description = "Website Product Category"
|
_description = "Website Product Category"
|
||||||
_order = "sequence, name"
|
_order = "sequence, name"
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class ProductPublicCategory(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ProductTemplate(models.Model):
|
class ProductTemplate(models.Model):
|
||||||
_inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin']
|
_inherit = ["product.template", "website.seo.metadata", 'website.published.multi.mixin', 'rating.mixin']
|
||||||
_order = 'website_published desc, website_sequence desc, name'
|
_order = 'website_published desc, website_sequence desc, name'
|
||||||
_name = 'product.template'
|
_name = 'product.template'
|
||||||
_mail_post_access = 'read'
|
_mail_post_access = 'read'
|
||||||
@@ -180,6 +180,8 @@ class ProductTemplate(models.Model):
|
|||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
_inherit = "product.product"
|
_inherit = "product.product"
|
||||||
|
|
||||||
|
website_id = fields.Many2one(related='product_tmpl_id.website_id')
|
||||||
|
|
||||||
website_price = fields.Float('Website price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
website_price = fields.Float('Website price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
||||||
website_public_price = fields.Float('Website public price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
website_public_price = fields.Float('Website public price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
||||||
website_price_difference = fields.Boolean('Website price difference', compute='_website_price')
|
website_price_difference = fields.Boolean('Website price difference', compute='_website_price')
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import models, fields
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class ResPartner(models.Model):
|
class ResPartner(models.Model):
|
||||||
_inherit = 'res.partner'
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
last_website_so_id = fields.Many2one('sale.order', string='Last Online Sales Order', copy=False)
|
last_website_so_id = fields.Many2one('sale.order', compute='_compute_last_website_so_id', string='Last Online Sales Order')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _compute_last_website_so_id(self):
|
||||||
|
SaleOrder = self.env['sale.order']
|
||||||
|
for partner in self:
|
||||||
|
current_website_so = SaleOrder.search([('partner_id', '=', partner.id)]).filtered(lambda so: so.order_line.mapped('product_id.product_tmpl_id').can_access_from_current_website())
|
||||||
|
partner.last_website_so_id = current_website_so and current_website_so[0]
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ class SaleOrder(models.Model):
|
|||||||
only_services = fields.Boolean(compute='_compute_cart_info', string='Only Services')
|
only_services = fields.Boolean(compute='_compute_cart_info', string='Only Services')
|
||||||
is_abandoned_cart = fields.Boolean('Abandoned Cart', compute='_compute_abandoned_cart', search='_search_abandoned_cart')
|
is_abandoned_cart = fields.Boolean('Abandoned Cart', compute='_compute_abandoned_cart', search='_search_abandoned_cart')
|
||||||
cart_recovery_email_sent = fields.Boolean('Cart recovery email already sent')
|
cart_recovery_email_sent = fields.Boolean('Cart recovery email already sent')
|
||||||
|
website_id = fields.Many2one('website', related='partner_id.website_id', string='Website',
|
||||||
|
help='Website through which this order was placed.',
|
||||||
|
store=True, readonly=True)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@api.depends('website_order_line.product_uom_qty', 'website_order_line.product_id')
|
@api.depends('website_order_line.product_uom_qty', 'website_order_line.product_id')
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class Website(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def sale_product_domain(self):
|
def sale_product_domain(self):
|
||||||
return [("sale_ok", "=", True)]
|
return [("sale_ok", "=", True)] + self.get_current_website().website_domain()
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def sale_get_payment_term(self, partner):
|
def sale_get_payment_term(self, partner):
|
||||||
@@ -172,6 +172,7 @@ class Website(models.Model):
|
|||||||
'partner_invoice_id': partner.id,
|
'partner_invoice_id': partner.id,
|
||||||
'partner_shipping_id': addr['delivery'],
|
'partner_shipping_id': addr['delivery'],
|
||||||
'user_id': salesperson_id or self.salesperson_id.id or default_user_id,
|
'user_id': salesperson_id or self.salesperson_id.id or default_user_id,
|
||||||
|
'website_id': self._context.get('website_id'),
|
||||||
}
|
}
|
||||||
company = self.company_id or pricelist.company_id
|
company = self.company_id or pricelist.company_id
|
||||||
if company:
|
if company:
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from . import sale_report
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class SaleReport(models.Model):
|
||||||
|
_inherit = 'sale.report'
|
||||||
|
|
||||||
|
website_id = fields.Many2one('website', readonly=True)
|
||||||
|
|
||||||
|
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
|
||||||
|
fields['website_id'] = ", s.website_id as website_id"
|
||||||
|
groupby += ', s.website_id'
|
||||||
|
return super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
|
||||||
@@ -12,6 +12,13 @@
|
|||||||
<field name="perm_unlink" eval="False"/>
|
<field name="perm_unlink" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="product_public_category_public" model="ir.rule">
|
||||||
|
<field name="name">Public product category public</field>
|
||||||
|
<field name="model_id" ref="model_product_public_category"/>
|
||||||
|
<field name="domain_force">[('website_published', '=', True)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="product_supplierinfo_public" model="ir.rule">
|
<record id="product_supplierinfo_public" model="ir.rule">
|
||||||
<field name="name">Public product supplierinfo</field>
|
<field name="name">Public product supplierinfo</field>
|
||||||
<field name="model_id" ref="product.model_product_supplierinfo"/>
|
<field name="model_id" ref="product.model_product_supplierinfo"/>
|
||||||
|
|||||||
@@ -21,4 +21,15 @@
|
|||||||
<field name="search_view_id" ref="product.product_pricelist_view_search" />
|
<field name="search_view_id" ref="product.product_pricelist_view_search" />
|
||||||
<field name="context">{"default_base":'list_price'}</field>
|
<field name="context">{"default_base":'list_price'}</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="account_invoice_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">account.invoice.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="fiscal_position_id" position="after">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -68,6 +68,28 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="product_product_website_tree_view">
|
||||||
|
<field name="name">product.product.website.tree</field>
|
||||||
|
<field name="model">product.product</field>
|
||||||
|
<field name="inherit_id" ref="product.product_product_tree_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="product_template_website_tree_view">
|
||||||
|
<field name="name">product.template.website.tree</field>
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_tree_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="name" position="after">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="product_template_form_view">
|
<record model="ir.ui.view" id="product_template_form_view">
|
||||||
<field name="name">product.template.product.website.form</field>
|
<field name="name">product.template.product.website.form</field>
|
||||||
<field name="model">product.template</field>
|
<field name="model">product.template</field>
|
||||||
@@ -85,6 +107,7 @@
|
|||||||
<group name="shop">
|
<group name="shop">
|
||||||
<group string="Shop">
|
<group string="Shop">
|
||||||
<field name="website_url" invisible="1"/>
|
<field name="website_url" invisible="1"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="public_categ_ids" widget="many2many_tags" string="Categories"/>
|
<field name="public_categ_ids" widget="many2many_tags" string="Categories"/>
|
||||||
<field name="alternative_product_ids" widget="many2many_tags"/>
|
<field name="alternative_product_ids" widget="many2many_tags"/>
|
||||||
<field name="accessory_product_ids" widget="many2many_tags"/>
|
<field name="accessory_product_ids" widget="many2many_tags"/>
|
||||||
@@ -172,11 +195,17 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Website Public Categories">
|
<form string="Website Public Categories">
|
||||||
<sheet>
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button class="oe_stat_button" name="website_publish_button" type="object" icon="fa-globe">
|
||||||
|
<field name="website_published" widget="website_button"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<field name="image_medium" widget='image' class="oe_avatar"/>
|
<field name="image_medium" widget='image' class="oe_avatar"/>
|
||||||
<div class="oe_left">
|
<div class="oe_left">
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
</group>
|
</group>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,6 +221,7 @@
|
|||||||
<tree string="Product Public Categories">
|
<tree string="Product Public Categories">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="display_name"/>
|
<field name="display_name"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -219,7 +249,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name='currency_id' position='after'>
|
<field name='currency_id' position='after'>
|
||||||
<field name="selectable"/>
|
<field name="selectable"/>
|
||||||
<field name="website_id" string='Allow to use on ' placeholder="None website" />
|
<field name="website_id" string="Allow to use on " placeholder="None website"/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -228,4 +228,17 @@
|
|||||||
action="product.variants_action"
|
action="product.variants_action"
|
||||||
parent="menu_product_settings" groups="product.group_product_variant" sequence="3"/>
|
parent="menu_product_settings" groups="product.group_product_variant" sequence="3"/>
|
||||||
|
|
||||||
|
<record id="sale_order_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">sale.order.form</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="partner_id" position="attributes">
|
||||||
|
<attribute name="context">{'display_website': True}</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="origin" position="after">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -271,7 +271,9 @@
|
|||||||
<!-- Add to cart button-->
|
<!-- Add to cart button-->
|
||||||
<template id="categories_recursive" name="Category list">
|
<template id="categories_recursive" name="Category list">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-field="c.name" t-attf-class="nav-link#{' active' if c.id == int(category or 0) else ''}"></a>
|
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-attf-class="nav-link#{' active' if c.id == int(category or 0) else ''}">
|
||||||
|
<span t-field="c.name"/> <span t-if="not c.website_published" class="text-danger">(Unpublished)</span>
|
||||||
|
</a>
|
||||||
<ul t-if="c.child_id" class="nav nav-pills flex-column nav-hierarchy">
|
<ul t-if="c.child_id" class="nav nav-pills flex-column nav-hierarchy">
|
||||||
<t t-foreach="c.child_id" t-as="c">
|
<t t-foreach="c.child_id" t-as="c">
|
||||||
<t t-call="website_sale.categories_recursive" />
|
<t t-call="website_sale.categories_recursive" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from odoo import fields, models
|
|||||||
|
|
||||||
class DeliveryCarrier(models.Model):
|
class DeliveryCarrier(models.Model):
|
||||||
_name = 'delivery.carrier'
|
_name = 'delivery.carrier'
|
||||||
_inherit = ['delivery.carrier', 'website.published.mixin']
|
_inherit = ['delivery.carrier', 'website.published.multi.mixin']
|
||||||
|
|
||||||
website_description = fields.Text(related='product_id.description_sale', string='Description for Online Quotations')
|
website_description = fields.Text(related='product_id.description_sale', string='Description for Online Quotations')
|
||||||
website_published = fields.Boolean(default=False)
|
website_published = fields.Boolean(default=False)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ from odoo.http import request
|
|||||||
class WebsiteSaleLinkTrackerBackend(WebsiteSaleBackend):
|
class WebsiteSaleLinkTrackerBackend(WebsiteSaleBackend):
|
||||||
|
|
||||||
@http.route()
|
@http.route()
|
||||||
def fetch_dashboard_data(self, date_from, date_to):
|
def fetch_dashboard_data(self, website_id, date_from, date_to):
|
||||||
results = super(WebsiteSaleLinkTrackerBackend, self).fetch_dashboard_data(date_from, date_to)
|
results = super(WebsiteSaleLinkTrackerBackend, self).fetch_dashboard_data(website_id, date_from, date_to)
|
||||||
results['dashboards']['sales']['utm_graph'] = self.fetch_utm_data(date_from, date_to)
|
results['dashboards']['sales']['utm_graph'] = self.fetch_utm_data(date_from, date_to)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
'data': [
|
'data': [
|
||||||
'views/product_template_views.xml',
|
'views/product_template_views.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
'views/website_sale_stock_templates.xml'
|
'views/website_sale_stock_templates.xml',
|
||||||
|
'views/stock_picking_views.xml'
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
'data/website_sale_stock_demo.xml',
|
'data/website_sale_stock_demo.xml',
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ from . import product_product
|
|||||||
from . import product_template
|
from . import product_template
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
from . import sale_order
|
from . import sale_order
|
||||||
|
from . import stock_picking
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import models, api, fields
|
||||||
|
from odoo.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
|
class StockPicking(models.Model):
|
||||||
|
_inherit = 'stock.picking'
|
||||||
|
|
||||||
|
website_id = fields.Many2one('website', related='partner_id.website_id', string='Website',
|
||||||
|
help='Website this picking belongs to.',
|
||||||
|
store=True, readonly=True)
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_picking_form_inherit_website_sale_stock" model="ir.ui.view">
|
||||||
|
<field name="name">stock.picking.form.inherit.website.sale.stock</field>
|
||||||
|
<field name="model">stock.picking</field>
|
||||||
|
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="company_id" position="before">
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -57,7 +57,8 @@ class WebsiteSlides(http.Controller):
|
|||||||
""" Returns a list of available channels: if only one is available,
|
""" Returns a list of available channels: if only one is available,
|
||||||
redirects directly to its slides
|
redirects directly to its slides
|
||||||
"""
|
"""
|
||||||
channels = request.env['slide.channel'].search([], order='sequence, id')
|
domain = request.website.website_domain()
|
||||||
|
channels = request.env['slide.channel'].search(domain, order='sequence, id')
|
||||||
if not channels:
|
if not channels:
|
||||||
return request.render("website_slides.channel_not_found")
|
return request.render("website_slides.channel_not_found")
|
||||||
elif len(channels) == 1:
|
elif len(channels) == 1:
|
||||||
@@ -71,6 +72,7 @@ class WebsiteSlides(http.Controller):
|
|||||||
def sitemap_slide(env, rule, qs):
|
def sitemap_slide(env, rule, qs):
|
||||||
Channel = env['slide.channel']
|
Channel = env['slide.channel']
|
||||||
dom = sitemap_qs2dom(qs=qs, route='/slides/', field=Channel._rec_name)
|
dom = sitemap_qs2dom(qs=qs, route='/slides/', field=Channel._rec_name)
|
||||||
|
dom += env['website'].get_current_website().website_domain()
|
||||||
for channel in Channel.search(dom):
|
for channel in Channel.search(dom):
|
||||||
loc = '/slides/%s' % slug(channel)
|
loc = '/slides/%s' % slug(channel)
|
||||||
if not qs or qs.lower() in loc:
|
if not qs or qs.lower() in loc:
|
||||||
@@ -93,6 +95,9 @@ class WebsiteSlides(http.Controller):
|
|||||||
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/<string:slide_type>/page/<int:page>'''],
|
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/<string:slide_type>/page/<int:page>'''],
|
||||||
type='http', auth="public", website=True, sitemap=sitemap_slide)
|
type='http', auth="public", website=True, sitemap=sitemap_slide)
|
||||||
def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sorting='creation', search=None, **kw):
|
def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sorting='creation', search=None, **kw):
|
||||||
|
if not channel.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
user = request.env.user
|
user = request.env.user
|
||||||
Slide = request.env['slide.slide']
|
Slide = request.env['slide.slide']
|
||||||
domain = [('channel_id', '=', channel.id)]
|
domain = [('channel_id', '=', channel.id)]
|
||||||
@@ -164,8 +169,11 @@ class WebsiteSlides(http.Controller):
|
|||||||
# SLIDE.SLIDE CONTOLLERS
|
# SLIDE.SLIDE CONTOLLERS
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
@http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True)]"):slide>''', type='http', auth="public", website=True)
|
@http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True), ('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True)
|
||||||
def slide_view(self, slide, **kwargs):
|
def slide_view(self, slide, **kwargs):
|
||||||
|
if not slide.channel_id.can_access_from_current_website():
|
||||||
|
raise werkzeug.exceptions.NotFound()
|
||||||
|
|
||||||
values = self._get_slide_detail(slide)
|
values = self._get_slide_detail(slide)
|
||||||
if not values.get('private'):
|
if not values.get('private'):
|
||||||
self._set_viewed_slide(slide, 'slide')
|
self._set_viewed_slide(slide, 'slide')
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
|
|
||||||
<record model="ir.config_parameter" id="google_app_key">
|
<record id="website.default_website" model="website">
|
||||||
<field name="key">website_slides.google_app_key</field>
|
<field name="website_slide_google_app_key">AIzaSyDOWlmDW-7DbLmOR9ZsT5AOEXf4n6TFwQA</field>
|
||||||
<field name="value">AIzaSyDOWlmDW-7DbLmOR9ZsT5AOEXf4n6TFwQA</field>
|
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="website_menu_slides" model="website.menu">
|
<record id="website_menu_slides" model="website.menu">
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
from . import slides
|
from . import slides
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
|
from . import website
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ from odoo import api, fields, models
|
|||||||
class ResConfigSettings(models.TransientModel):
|
class ResConfigSettings(models.TransientModel):
|
||||||
_inherit = "res.config.settings"
|
_inherit = "res.config.settings"
|
||||||
|
|
||||||
website_slide_google_app_key = fields.Char(string='Google Doc Key', config_parameter='website_slides.google_app_key')
|
website_slide_google_app_key = fields.Char(related='website_id.website_slide_google_app_key')
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Channel(models.Model):
|
|||||||
channels. """
|
channels. """
|
||||||
_name = 'slide.channel'
|
_name = 'slide.channel'
|
||||||
_description = 'Channel for Slides'
|
_description = 'Channel for Slides'
|
||||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
|
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||||
_order = 'sequence, id'
|
_order = 'sequence, id'
|
||||||
_order_by_strategy = {
|
_order_by_strategy = {
|
||||||
'most_viewed': 'total_views desc',
|
'most_viewed': 'total_views desc',
|
||||||
@@ -330,6 +330,7 @@ class Slide(models.Model):
|
|||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
# website
|
# website
|
||||||
|
website_id = fields.Many2one(related='channel_id.website_id', readonly=True)
|
||||||
date_published = fields.Datetime('Publish Date')
|
date_published = fields.Datetime('Publish Date')
|
||||||
likes = fields.Integer('Likes')
|
likes = fields.Integer('Likes')
|
||||||
dislikes = fields.Integer('Dislikes')
|
dislikes = fields.Integer('Dislikes')
|
||||||
@@ -469,14 +470,17 @@ class Slide(models.Model):
|
|||||||
return groups
|
return groups
|
||||||
|
|
||||||
def get_related_slides(self, limit=20):
|
def get_related_slides(self, limit=20):
|
||||||
domain = [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
domain = request.website.website_domain()
|
||||||
|
domain += [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
||||||
if self.category_id:
|
if self.category_id:
|
||||||
domain += [('category_id', '=', self.category_id.id)]
|
domain += [('category_id', '=', self.category_id.id)]
|
||||||
for record in self.search(domain, limit=limit):
|
for record in self.search(domain, limit=limit):
|
||||||
yield record
|
yield record
|
||||||
|
|
||||||
def get_most_viewed_slides(self, limit=20):
|
def get_most_viewed_slides(self, limit=20):
|
||||||
for record in self.search([('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)], limit=limit, order='total_views desc'):
|
domain = request.website.website_domain()
|
||||||
|
domain += [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
||||||
|
for record in self.search(domain, limit=limit, order='total_views desc'):
|
||||||
yield record
|
yield record
|
||||||
|
|
||||||
def _post_publication(self):
|
def _post_publication(self):
|
||||||
@@ -542,7 +546,7 @@ class Slide(models.Model):
|
|||||||
return {'error': _('Unknown document')}
|
return {'error': _('Unknown document')}
|
||||||
|
|
||||||
def _parse_youtube_document(self, document_id, only_preview_fields):
|
def _parse_youtube_document(self, document_id, only_preview_fields):
|
||||||
key = self.env['ir.config_parameter'].sudo().get_param('website_slides.google_app_key')
|
key = self.env['website'].get_current_website().website_slide_google_app_key
|
||||||
fetch_res = self._fetch_data('https://www.googleapis.com/youtube/v3/videos', {'id': document_id, 'key': key, 'part': 'snippet', 'fields': 'items(id,snippet)'}, 'json')
|
fetch_res = self._fetch_data('https://www.googleapis.com/youtube/v3/videos', {'id': document_id, 'key': key, 'part': 'snippet', 'fields': 'items(id,snippet)'}, 'json')
|
||||||
if fetch_res.get('error'):
|
if fetch_res.get('error'):
|
||||||
return fetch_res
|
return fetch_res
|
||||||
@@ -592,7 +596,7 @@ class Slide(models.Model):
|
|||||||
if access_token:
|
if access_token:
|
||||||
params['access_token'] = access_token
|
params['access_token'] = access_token
|
||||||
if not params.get('access_token'):
|
if not params.get('access_token'):
|
||||||
params['key'] = self.env['ir.config_parameter'].sudo().get_param('website_slides.google_app_key')
|
params['key'] = self.env['website'].get_current_website().website_slide_google_app_key
|
||||||
|
|
||||||
fetch_res = self._fetch_data('https://www.googleapis.com/drive/v2/files/%s' % document_id, params, "json")
|
fetch_res = self._fetch_data('https://www.googleapis.com/drive/v2/files/%s' % document_id, params, "json")
|
||||||
if fetch_res.get('error'):
|
if fetch_res.get('error'):
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class Website(models.Model):
|
||||||
|
_inherit = "website"
|
||||||
|
|
||||||
|
website_slide_google_app_key = fields.Char('Google Doc Key')
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="channel_id"/>
|
<field name="channel_id"/>
|
||||||
<field name="category_id" context="{'default_channel_id': channel_id}"/>
|
<field name="category_id" context="{'default_channel_id': channel_id}"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="website_url"/>
|
<field name="website_url"/>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
@@ -108,6 +109,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Slides">
|
<tree string="Slides">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="slide_type"/>
|
<field name="slide_type"/>
|
||||||
<field name="channel_id"/>
|
<field name="channel_id"/>
|
||||||
@@ -198,7 +200,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- SLIDE.CHANNEL VIEWS -->
|
<!-- SLIDE.CHANNEL VIEWS -->
|
||||||
<record model="ir.ui.view" id="view_slide_channel_form">
|
<record model="ir.ui.view" id="view_slide_channel_form">
|
||||||
<field name="name">slide.channel.form</field>
|
<field name="name">slide.channel.form</field>
|
||||||
@@ -229,6 +231,7 @@
|
|||||||
<page string="General">
|
<page string="General">
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="promote_strategy" widget="radio"/>
|
<field name="promote_strategy" widget="radio"/>
|
||||||
<field name="custom_slide_id" class="oe_edit_only" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}" domain="[('channel_id','=',id), ('website_published','=',True)]"/>
|
<field name="custom_slide_id" class="oe_edit_only" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}" domain="[('channel_id','=',id), ('website_published','=',True)]"/>
|
||||||
<field name="promoted_slide_id" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}"/>
|
<field name="promoted_slide_id" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}"/>
|
||||||
@@ -269,6 +272,7 @@
|
|||||||
<tree string="Channel">
|
<tree string="Channel">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="website_id" groups="website.group_multi_website"/>
|
||||||
<field name="active" invisible="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="visibility"/>
|
<field name="visibility"/>
|
||||||
<field name="promote_strategy"/>
|
<field name="promote_strategy"/>
|
||||||
@@ -297,7 +301,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem name="Slides"
|
<menuitem name="Slides"
|
||||||
id="menu_website_slides_root"
|
id="menu_website_slides_root"
|
||||||
sequence="32"
|
sequence="32"
|
||||||
@@ -310,13 +314,13 @@
|
|||||||
sequence="140"
|
sequence="140"
|
||||||
parent="website.menu_website_global_configuration"
|
parent="website.menu_website_global_configuration"
|
||||||
groups="website.group_website_designer"/>
|
groups="website.group_website_designer"/>
|
||||||
|
|
||||||
<menuitem action="action_slide_channels" id="menu_action_slide_channels_global"
|
<menuitem action="action_slide_channels" id="menu_action_slide_channels_global"
|
||||||
parent="menu_website_slides_root_global" sequence="1"/>
|
parent="menu_website_slides_root_global" sequence="1"/>
|
||||||
|
|
||||||
<menuitem action="action_ir_slide_category" id="menu_action_ir_slide_category_global" parent="menu_website_slides_root_global" sequence="2"/>
|
<menuitem action="action_ir_slide_category" id="menu_action_ir_slide_category_global" parent="menu_website_slides_root_global" sequence="2"/>
|
||||||
|
|
||||||
<menuitem action="action_slide_tag" id="menu_slide_tag" parent="menu_website_slides_root_global" sequence="10" groups="base.group_no_one"/>
|
<menuitem action="action_slide_tag" id="menu_slide_tag" parent="menu_website_slides_root_global" sequence="10" groups="base.group_no_one"/>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
'data': [
|
'data': [
|
||||||
'views/assets.xml',
|
'views/assets.xml',
|
||||||
'views/views.xml',
|
'views/views.xml',
|
||||||
|
'views/res_config_settings_views.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
],
|
],
|
||||||
'depends': ['website'],
|
'depends': ['website'],
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
|
|||||||
@@ -2,3 +2,7 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from . import ir_module_module
|
from . import ir_module_module
|
||||||
|
from . import res_company
|
||||||
|
from . import res_config_settings
|
||||||
|
from . import theme_models
|
||||||
|
from . import website
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IrModuleModule(models.Model):
|
class IrModuleModule(models.Model):
|
||||||
@@ -13,6 +15,174 @@ class IrModuleModule(models.Model):
|
|||||||
image_ids = fields.One2many('ir.attachment', 'res_id',
|
image_ids = fields.One2many('ir.attachment', 'res_id',
|
||||||
domain=[('res_model', '=', _name), ('mimetype', '=like', 'image/%')],
|
domain=[('res_model', '=', _name), ('mimetype', '=like', 'image/%')],
|
||||||
string='Screenshots', readonly=True)
|
string='Screenshots', readonly=True)
|
||||||
|
# for kanban view
|
||||||
|
is_installed_on_current_website = fields.Boolean(compute='_compute_is_installed_on_current_website')
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _compute_is_installed_on_current_website(self):
|
||||||
|
for module in self:
|
||||||
|
module.is_installed_on_current_website = module == self.env['website'].get_current_website().theme_id
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
if vals.get('state') == 'installed' and self.name.startswith('theme_'):
|
||||||
|
_logger.info('Module %s has been loaded as theme template' % self.name)
|
||||||
|
return super(IrModuleModule, self).write(vals)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_module_data(self, model_name):
|
||||||
|
IrModelData = self.env['ir.model.data']
|
||||||
|
records = self.env[model_name]
|
||||||
|
|
||||||
|
for module in self:
|
||||||
|
imd_ids = IrModelData.search([('module', '=', module.name), ('model', '=', model_name)]).mapped('res_id')
|
||||||
|
records |= self.env[model_name].browse(imd_ids)
|
||||||
|
return records
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _load_one_theme_module(self, website, loaded):
|
||||||
|
# loaded is a dict updated in place !
|
||||||
|
# keep previous record created to update overrided record
|
||||||
|
_logger.info('Load theme %s for website %s from template.' % (self.mapped('name'), website.id))
|
||||||
|
|
||||||
|
for attach in self._get_module_data('theme.ir.attachment'):
|
||||||
|
already_create = loaded['attachments'].filtered(lambda x: x.key == attach.key)
|
||||||
|
new_attach = {
|
||||||
|
'public': True,
|
||||||
|
'res_model': 'ir.ui.view',
|
||||||
|
'type': 'url',
|
||||||
|
'name': attach.name,
|
||||||
|
'url': attach.url,
|
||||||
|
'website_id': website.id,
|
||||||
|
'theme_template_id': attach.id,
|
||||||
|
}
|
||||||
|
if already_create:
|
||||||
|
already_create.update(new_attach)
|
||||||
|
else: # if module B override attachment from dependence A
|
||||||
|
loaded['attachments'] += self.env['ir.attachment'].create(new_attach)
|
||||||
|
|
||||||
|
for view in self._get_module_data('theme.ir.ui.view'):
|
||||||
|
if view.inherit_id and view.inherit_id._name == 'theme.ir.ui.view':
|
||||||
|
view.inherit_id = loaded['views'].filtered(lambda x: x.theme_template_id == view.inherit_id)
|
||||||
|
|
||||||
|
new_view = {
|
||||||
|
'type': view.type or 'qweb',
|
||||||
|
'name': view.name,
|
||||||
|
'arch': view.arch,
|
||||||
|
'key': view.key,
|
||||||
|
'arch': view.arch,
|
||||||
|
'arch_fs': view.arch_fs,
|
||||||
|
'priority': view.priority,
|
||||||
|
'active': view.active,
|
||||||
|
'inherit_id': view.inherit_id and view.inherit_id.id,
|
||||||
|
'theme_template_id': view.id,
|
||||||
|
'website_id': website.id,
|
||||||
|
}
|
||||||
|
loaded['views'] += self.env['ir.ui.view'].create(new_view)
|
||||||
|
|
||||||
|
for page in self._get_module_data('theme.website.page'):
|
||||||
|
new_page = {
|
||||||
|
'url': page.url,
|
||||||
|
'view_id': loaded['views'].filtered(lambda x: x.theme_template_id == page.view_id).id,
|
||||||
|
'website_indexed': page.website_indexed,
|
||||||
|
'theme_template_id': page.id,
|
||||||
|
}
|
||||||
|
loaded['pages'] += self.env['website.page'].create(new_page)
|
||||||
|
|
||||||
|
for menu in self._get_module_data('theme.website.menu'):
|
||||||
|
new_menu = {
|
||||||
|
'name': menu.name,
|
||||||
|
'url': menu.url,
|
||||||
|
'page_id': loaded['pages'].filtered(lambda x: x.theme_template_id == menu.page_id).id,
|
||||||
|
'new_window': menu.new_window,
|
||||||
|
'sequence': menu.sequence,
|
||||||
|
'parent_id': loaded['menus'].filtered(lambda x: x.theme_template_id == menu.parent_id).id,
|
||||||
|
'theme_template_id': menu.id,
|
||||||
|
}
|
||||||
|
loaded['menus'] += self.env['website.menu'].create(new_menu)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _unload_one_theme_module(self, website):
|
||||||
|
_logger.info('Unload theme %s for website %s from template.' % (self.mapped('name'), website.id))
|
||||||
|
|
||||||
|
for module in self:
|
||||||
|
menus = module._get_module_data('theme.website.menu')
|
||||||
|
menu_todel = menus.mapped('copy_ids').filtered(lambda m: m.website_id == website)
|
||||||
|
menu_todel.unlink()
|
||||||
|
|
||||||
|
pages = module._get_module_data('theme.website.page')
|
||||||
|
pages_todel = pages.mapped('copy_ids').filtered(lambda p: p.website_id == website)
|
||||||
|
pages_todel.unlink()
|
||||||
|
|
||||||
|
attachs = module._get_module_data('theme.ir.attachment')
|
||||||
|
atachs_todel = attachs.mapped('copy_ids').filtered(lambda a: a.website_id == website)
|
||||||
|
atachs_todel.unlink()
|
||||||
|
|
||||||
|
views = module._get_module_data('theme.ir.ui.view')
|
||||||
|
views_todel = views.mapped('copy_ids').filtered(lambda v: v.website_id == website)
|
||||||
|
views_todel.unlink()
|
||||||
|
|
||||||
|
def _remove_theme_on_website(self, website):
|
||||||
|
installed_deps = website.theme_id.upstream_dependencies(exclude_states=('',)).filtered(lambda x: x.name.startswith('theme_'))
|
||||||
|
for mod in installed_deps:
|
||||||
|
mod._unload_one_theme_module(website)
|
||||||
|
|
||||||
|
def _copy_theme_on_website(self, website):
|
||||||
|
loaded = {
|
||||||
|
'views': self.env['ir.ui.view'],
|
||||||
|
'attachments': self.env['ir.attachment'],
|
||||||
|
'pages': self.env['website.page'],
|
||||||
|
'menus': self.env['website.menu'],
|
||||||
|
}
|
||||||
|
mods_to_load = reversed(self + self.upstream_dependencies(exclude_states=('',)).filtered(lambda x: x.name.startswith('theme_')))
|
||||||
|
for mod in mods_to_load:
|
||||||
|
mod._load_one_theme_module(website, loaded)
|
||||||
|
self.env['theme.utils']._post_copy(mod)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def button_choose_theme(self):
|
||||||
|
website = self.env['website'].get_current_website()
|
||||||
|
|
||||||
|
# Unload previous theme
|
||||||
|
if website.theme_id:
|
||||||
|
website.theme_id._remove_theme_on_website(website)
|
||||||
|
|
||||||
|
website.theme_id = self
|
||||||
|
|
||||||
|
# create template data of missing theme
|
||||||
|
next_action = False
|
||||||
|
if self.state != 'installed':
|
||||||
|
next_action = self.button_immediate_install()
|
||||||
|
|
||||||
|
# Copy new theme from template table to real table
|
||||||
|
self._copy_theme_on_website(website)
|
||||||
|
|
||||||
|
# Alter next action for redirect
|
||||||
|
if not next_action:
|
||||||
|
next_action = website.button_go_website()
|
||||||
|
if next_action.get('tag') == 'reload' and not next_action.get('params', {}).get('menu_id'):
|
||||||
|
next_action = self.env.ref('website.action_website').read()[0]
|
||||||
|
|
||||||
|
return next_action
|
||||||
|
|
||||||
|
def button_remove_theme(self):
|
||||||
|
website = self.env['website'].get_current_website()
|
||||||
|
self._remove_theme_on_website(website)
|
||||||
|
website.theme_id = False
|
||||||
|
|
||||||
|
def button_refresh_theme(self):
|
||||||
|
website = self.env['website'].get_current_website()
|
||||||
|
# load data from xml to template table
|
||||||
|
self.button_immediate_upgrade()
|
||||||
|
|
||||||
|
# delete current theme
|
||||||
|
# todo: quid of no_update record?
|
||||||
|
self.button_remove_theme()
|
||||||
|
|
||||||
|
# copy data from template table to real table
|
||||||
|
self._copy_theme_on_website(website)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def update_list(self):
|
def update_list(self):
|
||||||
@@ -37,25 +207,4 @@ class IrModuleModule(models.Model):
|
|||||||
'res_model': self._name,
|
'res_model': self._name,
|
||||||
'res_id': app.id,
|
'res_id': app.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def button_choose_theme(self):
|
|
||||||
theme_category = self.env.ref('base.module_category_theme', False)
|
|
||||||
theme_hidden_category = self.env.ref('base.module_category_theme_hidden', False)
|
|
||||||
|
|
||||||
theme_category_id = theme_category.id if theme_category else 0
|
|
||||||
hidden_categories_ids = [theme_hidden_category.id if theme_hidden_category else 0]
|
|
||||||
|
|
||||||
self.search([ # Uninstall the theme(s) which is (are) installed
|
|
||||||
('state', '=', 'installed'),
|
|
||||||
('category_id', 'not in', hidden_categories_ids),
|
|
||||||
'|', ('category_id', '=', theme_category_id), ('category_id.parent_id', '=', theme_category_id)
|
|
||||||
]).button_immediate_uninstall()
|
|
||||||
|
|
||||||
next_action = self.button_immediate_install() # Then install the new chosen one
|
|
||||||
if next_action.get('tag') == 'reload' and not next_action.get('params', {}).get('menu_id'):
|
|
||||||
next_action = self.env.ref('website.action_website').read()[0]
|
|
||||||
|
|
||||||
return next_action
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from ast import literal_eval
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
def _compute_website_theme_onboarding_done(self):
|
||||||
|
""" The step is marked as done if one theme is installed. """
|
||||||
|
# we need the same domain as the existing action
|
||||||
|
action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0]
|
||||||
|
domain = literal_eval(action['domain'])
|
||||||
|
domain.append(('state', '=', 'installed'))
|
||||||
|
installed_themes_count = self.env['ir.module.module'].sudo().search_count(domain)
|
||||||
|
for record in self:
|
||||||
|
record.website_theme_onboarding_done = (installed_themes_count > 0)
|
||||||
|
|
||||||
|
website_theme_onboarding_done = fields.Boolean("Onboarding website theme step done",
|
||||||
|
compute='_compute_website_theme_onboarding_done')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def action_open_website_theme_selector(self):
|
||||||
|
action = self.env.ref('website_theme_install.theme_install_kanban_action').read()[0]
|
||||||
|
action['target'] = 'new'
|
||||||
|
return action
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
def install_theme_on_current_website(self):
|
||||||
|
self.website_id._force()
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': '/web#action=website_theme_install.theme_install_kanban_action',
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from odoo import fields, models
|
||||||
|
from odoo.modules.module import get_resource_from_path
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeView(models.Model):
|
||||||
|
_name = 'theme.ir.ui.view'
|
||||||
|
|
||||||
|
def compute_arch_fs(self):
|
||||||
|
path_info = get_resource_from_path(self._context['install_filename'])
|
||||||
|
if path_info:
|
||||||
|
return '/'.join(path_info[0:2])
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
|
key = fields.Char()
|
||||||
|
type = fields.Char()
|
||||||
|
priority = fields.Integer(default=16, required=True)
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
arch = fields.Text()
|
||||||
|
arch_fs = fields.Char(default=compute_arch_fs)
|
||||||
|
inherit_id = fields.Reference(selection=[('ir.ui.view', 'ir.ui.view'), ('theme.ir.ui.view', 'theme.ir.ui.view')])
|
||||||
|
copy_ids = fields.One2many('ir.ui.view', 'theme_template_id', 'Views using a copy of me', copy=False, readonly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeAttachment(models.Model):
|
||||||
|
_name = 'theme.ir.attachment'
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
|
key = fields.Char(required=True)
|
||||||
|
url = fields.Char()
|
||||||
|
copy_ids = fields.One2many('ir.attachment', 'theme_template_id', 'Attachment using a copy of me', copy=False, readonly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeMenu(models.Model):
|
||||||
|
_name = 'theme.website.menu'
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
|
url = fields.Char(default='')
|
||||||
|
page_id = fields.Many2one('theme.website.page', ondelete='cascade')
|
||||||
|
new_window = fields.Boolean('New Window')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
parent_id = fields.Many2one('theme.website.menu', index=True, ondelete="cascade")
|
||||||
|
copy_ids = fields.One2many('website.menu', 'theme_template_id', 'Menu using a copy of me', copy=False, readonly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ThemePage(models.Model):
|
||||||
|
_name = 'theme.website.page'
|
||||||
|
|
||||||
|
url = fields.Char()
|
||||||
|
view_id = fields.Many2one('theme.ir.ui.view', required=True, ondelete="cascade")
|
||||||
|
website_indexed = fields.Boolean('Page Indexed', default=True)
|
||||||
|
copy_ids = fields.One2many('website.page', 'theme_template_id', 'Theme using a copy of me', copy=False, readonly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Theme(models.AbstractModel):
|
||||||
|
_name = 'theme.utils'
|
||||||
|
_auto = False
|
||||||
|
|
||||||
|
def _post_copy(self, mod):
|
||||||
|
theme_post_copy = '_%s_post_copy' % mod.name
|
||||||
|
if hasattr(self, theme_post_copy):
|
||||||
|
_logger.info('Executing method %s' % theme_post_copy)
|
||||||
|
method = getattr(self, theme_post_copy)
|
||||||
|
return method()
|
||||||
|
|
||||||
|
|
||||||
|
class IrUiView(models.Model):
|
||||||
|
_inherit = 'ir.ui.view'
|
||||||
|
|
||||||
|
theme_template_id = fields.Many2one('theme.ir.ui.view')
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachment(models.Model):
|
||||||
|
_inherit = 'ir.attachment'
|
||||||
|
|
||||||
|
key = fields.Char()
|
||||||
|
theme_template_id = fields.Many2one('theme.ir.attachment')
|
||||||
|
|
||||||
|
|
||||||
|
class WebiteMenu(models.Model):
|
||||||
|
_inherit = 'website.menu'
|
||||||
|
|
||||||
|
theme_template_id = fields.Many2one('theme.website.menu')
|
||||||
|
|
||||||
|
|
||||||
|
class WebsitePage(models.Model):
|
||||||
|
_inherit = 'website.page'
|
||||||
|
|
||||||
|
theme_template_id = fields.Many2one('theme.website.page')
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user