summaryrefslogtreecommitdiff
path: root/addons/website/models
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
committerChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
commit6c948c36bbf72dd896e0bff03aa7fbddadcbb0cd (patch)
treef8620905c011735d8a3c340c46d1417a54a75972 /addons/website/models
parent48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff)
parent197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (diff)
[MERGE] forward port branch saas-12.5 up to 197474e5b9a
Diffstat (limited to 'addons/website/models')
-rw-r--r--addons/website/models/ir_http.py16
-rw-r--r--addons/website/models/ir_qweb.py5
-rw-r--r--addons/website/models/ir_ui_view.py31
-rw-r--r--addons/website/models/res_config_settings.py12
-rw-r--r--addons/website/models/res_lang.py13
-rw-r--r--addons/website/models/website.py117
-rw-r--r--addons/website/models/website_menu.py50
-rw-r--r--addons/website/models/website_visitor.py8
8 files changed, 156 insertions, 96 deletions
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 58c15a8128c..ecacdc62bac 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -15,7 +15,7 @@ import odoo
from odoo import api, models, registry
from odoo import SUPERUSER_ID
from odoo.http import request
-from odoo.tools import config
+from odoo.tools import config, ormcache
from odoo.tools.safe_eval import safe_eval
from odoo.osv.expression import FALSE_DOMAIN, OR
@@ -133,18 +133,6 @@ class Http(models.AbstractModel):
request.website = request.website.with_context(request.context)
@classmethod
- def _get_languages(cls):
- if getattr(request, 'website', False):
- return request.website.language_ids
- return super(Http, cls)._get_languages()
-
- @classmethod
- def _get_language_codes(cls):
- if getattr(request, 'website', False):
- return request.website._get_languages()
- return super(Http, cls)._get_language_codes()
-
- @classmethod
def _get_default_lang(cls):
if getattr(request, 'website', False):
return request.website.default_lang_id
@@ -205,7 +193,7 @@ class Http(models.AbstractModel):
@classmethod
def _handle_exception(cls, exception):
code = 500 # default code
- is_website_request = bool(getattr(request, 'is_frontend', False) and getattr(request, 'website', False))
+ is_website_request = bool(getattr(request, 'is_frontend', False) and get_request_website())
if not is_website_request:
# Don't touch non website requests exception handling
return super(Http, cls)._handle_exception(exception)
diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py
index 0e93876be45..d3483fcb1b4 100644
--- a/addons/website/models/ir_qweb.py
+++ b/addons/website/models/ir_qweb.py
@@ -4,9 +4,10 @@ import re
from collections import OrderedDict
from odoo import models
-from odoo.addons.http_routing.models.ir_http import url_for
from odoo.http import request
from odoo.addons.base.models.assetsbundle import AssetsBundle
+from odoo.addons.http_routing.models.ir_http import url_for
+from odoo.addons.website.models import ir_http
from odoo.tools import html_escape as escape
re_background_image = re.compile(r"(background-image\s*:\s*url\(\s*['\"]?\s*)([^)'\"]+)")
@@ -47,7 +48,7 @@ class QWeb(models.AbstractModel):
options.get('edit_translations') or options.get('debug') or (request and request.session.debug):
return atts
- website = request and getattr(request, 'website', None)
+ website = ir_http.get_request_website()
if not website and options.get('website_id'):
website = self.env['website'].browse(options['website_id'])
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py
index 6949f173a46..a3aca36ced5 100644
--- a/addons/website/models/ir_ui_view.py
+++ b/addons/website/models/ir_ui_view.py
@@ -302,7 +302,7 @@ class View(models.Model):
if request and getattr(request, 'is_frontend', False):
editable = request.website.is_publisher()
- translatable = editable and self._context.get('lang') != request.website.default_lang_code
+ translatable = editable and self._context.get('lang') != request.website.default_lang_id.code
editable = not translatable and editable
# in edit mode ir.ui.view will tag nodes
@@ -336,18 +336,19 @@ class View(models.Model):
qcontext['main_object'] = self
cur = Website.get_current_website()
- qcontext['multi_website_websites_current'] = {'website_id': cur.id, 'name': cur.name, 'domain': cur.domain}
- qcontext['multi_website_websites'] = [
- {'website_id': website.id, 'name': website.name, 'domain': website.domain}
- for website in Website.search([]) if website != cur
- ]
-
- cur_company = self.env.company
- qcontext['multi_website_companies_current'] = {'company_id': cur_company.id, 'name': cur_company.name}
- qcontext['multi_website_companies'] = [
- {'company_id': comp.id, 'name': comp.name}
- for comp in self.env.user.company_ids if comp != cur_company
- ]
+ if self.env.user.has_group('website.group_website_publisher') and self.env.user.has_group('website.group_multi_website'):
+ qcontext['multi_website_websites_current'] = {'website_id': cur.id, 'name': cur.name, 'domain': cur.domain}
+ qcontext['multi_website_websites'] = [
+ {'website_id': website.id, 'name': website.name, 'domain': website.domain}
+ for website in Website.search([]) if website != cur
+ ]
+
+ cur_company = self.env.company
+ qcontext['multi_website_companies_current'] = {'company_id': cur_company.id, 'name': cur_company.name}
+ qcontext['multi_website_companies'] = [
+ {'company_id': comp.id, 'name': comp.name}
+ for comp in self.env.user.company_ids if comp != cur_company
+ ]
qcontext.update(dict(
self._context.copy(),
@@ -355,7 +356,7 @@ class View(models.Model):
url_for=url_for,
res_company=request.website.company_id.sudo(),
default_lang_code=request.env['ir.http']._get_default_lang().code,
- languages=request.env['ir.http']._get_language_codes(),
+ languages=request.env['res.lang'].get_available(),
translatable=translatable,
editable=editable,
menu_data=self.env['ir.ui.menu'].load_menus_root() if request.website.is_user() else None,
@@ -367,7 +368,7 @@ class View(models.Model):
def get_default_lang_code(self):
website_id = self.env.context.get('website_id')
if website_id:
- lang_code = self.env['website'].browse(website_id).default_lang_code
+ lang_code = self.env['website'].browse(website_id).default_lang_id.code
return lang_code
else:
return super(View, self).get_default_lang_code()
diff --git a/addons/website/models/res_config_settings.py b/addons/website/models/res_config_settings.py
index 6bcae68b61f..0fff2f6eb3b 100644
--- a/addons/website/models/res_config_settings.py
+++ b/addons/website/models/res_config_settings.py
@@ -19,15 +19,11 @@ class ResConfigSettings(models.TransientModel):
website_country_group_ids = fields.Many2many(related='website_id.country_group_ids', readonly=False)
website_company_id = fields.Many2one(related='website_id.company_id', string='Website Company', readonly=False)
website_logo = fields.Binary(related='website_id.logo', readonly=False)
- language_ids = fields.Many2many(related='website_id.language_ids', relation='res.lang',
- readonly=False)
+ language_ids = fields.Many2many(related='website_id.language_ids', relation='res.lang', readonly=False)
website_language_count = fields.Integer(string='Number of languages', compute='_compute_website_language_count', readonly=True)
- website_default_lang_id = fields.Many2one(
- string='Default language', related='website_id.default_lang_id', readonly=False,
- relation='res.lang')
- website_default_lang_code = fields.Char(
- 'Default language code', related='website_id.default_lang_code', readonly=False,
- )
+ website_default_lang_id = fields.Many2one(string='Default language', related='website_id.default_lang_id',
+ readonly=False, relation='res.lang')
+ website_default_lang_code = fields.Char('Default language code', related='website_id.default_lang_id.code', readonly=False)
specific_user_account = fields.Boolean(related='website_id.specific_user_account', readonly=False,
help='Are newly created user accounts website specific')
diff --git a/addons/website/models/res_lang.py b/addons/website/models/res_lang.py
index c84a212ed5a..9fc3f69e998 100644
--- a/addons/website/models/res_lang.py
+++ b/addons/website/models/res_lang.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models, _
+from odoo import api, models, tools, _
+from odoo.addons.website.models import ir_http
from odoo.exceptions import UserError
+from odoo.http import request
class Lang(models.Model):
@@ -13,3 +15,12 @@ class Lang(models.Model):
if self.env['website'].search([('language_ids', 'in', self._ids)]):
raise UserError(_("Cannot deactivate a language that is currently used on a website."))
return super(Lang, self).write(vals)
+
+ @api.model
+ @tools.ormcache_context(keys=("website_id",))
+ def get_available(self):
+ """ Return the available languages as a list of (code, name) sorted by name. """
+ website = ir_http.get_request_website()
+ if website:
+ return sorted([(lang.code, lang.url_code, lang.name) for lang in request.website.language_ids])
+ return super(Lang, self).get_available()
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 8ce3d46568a..0ddca6810e2 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -7,7 +7,9 @@ import logging
import hashlib
import re
+
from werkzeug import urls
+from werkzeug.datastructures import OrderedMultiDict
from werkzeug.exceptions import NotFound
from odoo import api, fields, models, tools
@@ -56,7 +58,6 @@ class Website(models.Model):
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company, required=True)
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_code = fields.Char("Default language code", related='default_lang_id.code', store=True, readonly=False)
auto_redirect_lang = fields.Boolean('Autoredirect Language', default=True, help="Should users be redirected to their browser's language")
def _default_social_facebook(self):
@@ -156,7 +157,8 @@ class Website(models.Model):
public_user_to_change_websites = self.env['website']
self._handle_favicon(values)
- self._get_languages.clear_cache(self)
+ self.clear_caches()
+
if 'company_id' in values and 'user_id' not in values:
public_user_to_change_websites = self.filtered(lambda w: w.sudo().user_id.company_id.id != values['company_id'])
if public_user_to_change_websites:
@@ -437,45 +439,42 @@ class Website(models.Model):
# Languages
# ----------------------------------------------------------
- def get_languages(self):
+ def _get_alternate_languages(self, canonical_params):
self.ensure_one()
- return self._get_languages()
- @tools.cache('self.id')
- def _get_languages(self):
- return [(lg.code, lg.name) for lg in self.language_ids]
+ if not self._is_canonical_url(canonical_params=canonical_params):
+ # no hreflang on non-canonical pages
+ return []
+
+ languages = self.language_ids
+ if len(languages) <= 1:
+ # no hreflang if no alternate language
+ return []
- def get_alternate_languages(self, req=None):
langs = []
- if req is None:
- req = request.httprequest
- default = self.get_current_website().default_lang_code
shorts = []
- def get_url_localized(router, lang):
- arguments = dict(request.endpoint_arguments)
- for key, val in list(arguments.items()):
- if isinstance(val, models.BaseModel):
- arguments[key] = val.with_context(lang=lang)
- return router.build(request.endpoint, arguments)
-
- router = request.httprequest.app.get_db_router(request.db).bind('')
- for code, dummy in self.get_languages():
- lg_path = ('/' + code) if code != default else ''
- lg_codes = code.split('_')
- shorts.append(lg_codes[0])
- uri = get_url_localized(router, code) if request.endpoint else request.httprequest.path
- if req.query_string:
- uri += u'?' + req.query_string.decode('utf-8')
- lang = {
+ for lg in languages:
+ lg_codes = lg.code.split('_')
+ short = lg_codes[0]
+ shorts.append(short)
+ langs.append({
'hreflang': ('-'.join(lg_codes)).lower(),
- 'short': lg_codes[0],
- 'href': req.url_root[0:-1] + lg_path + uri,
- }
- langs.append(lang)
+ 'short': short,
+ 'href': self._get_canonical_url_localized(lang=lg, canonical_params=canonical_params),
+ })
+
+ # if there is only one region for a language, use only the language code
for lang in langs:
if shorts.count(lang['short']) == 1:
lang['hreflang'] = lang['short']
+
+ # add the default
+ langs.append({
+ 'hreflang': 'x-default',
+ 'href': self._get_canonical_url_localized(lang=self.default_lang_id, canonical_params=canonical_params),
+ })
+
return langs
# ----------------------------------------------------------
@@ -741,9 +740,6 @@ class Website(models.Model):
domain_part, url = rule.build(value, append_unknown=False)
if not query_string or query_string.lower() in url.lower():
page = {'loc': url}
- for key, val in value.items():
- if key.startswith('__'):
- page[key[2:]] = val
if url in ('/sitemap.xml',):
continue
if url in url_set:
@@ -767,9 +763,9 @@ class Website(models.Model):
for page in pages:
record = {'loc': page['url'], 'id': page['id'], 'name': page['name']}
if page.view_id and page.view_id.priority != 16:
- record['__priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
+ record['priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
if page['write_date']:
- record['__lastmod'] = page['write_date'].date()
+ record['lastmod'] = page['write_date'].date()
yield record
def get_website_pages(self, domain=[], order='name', limit=None):
@@ -835,6 +831,55 @@ class Website(models.Model):
self.ensure_one()
return self._get_http_domain() or super(BaseModel, self).get_base_url()
+ def _get_canonical_url_localized(self, lang, canonical_params):
+ """Returns the canonical URL for the current request with translatable
+ elements appropriately translated in `lang`.
+
+ If `request.endpoint` is not true, returns the current `path` instead.
+
+ `url_quote_plus` is applied on the returned path.
+ """
+ self.ensure_one()
+ if request.endpoint:
+ router = request.httprequest.app.get_db_router(request.db).bind('')
+ arguments = dict(request.endpoint_arguments)
+ for key, val in list(arguments.items()):
+ if isinstance(val, models.BaseModel):
+ if val.env.context.get('lang') != lang.url_code:
+ arguments[key] = val.with_context(lang=lang.url_code)
+ path = router.build(request.endpoint, arguments)
+ else:
+ # The build method returns a quoted URL so convert in this case for consistency.
+ path = urls.url_quote_plus(request.httprequest.path, safe='/')
+ lang_path = ('/' + lang.url_code) if lang != self.default_lang_id else ''
+ canonical_query_string = '?%s' % urls.url_encode(canonical_params) if canonical_params else ''
+ return self.get_base_url() + lang_path + path + canonical_query_string
+
+ def _get_canonical_url(self, canonical_params):
+ """Returns the canonical URL for the current request."""
+ self.ensure_one()
+ return self._get_canonical_url_localized(lang=request.lang, canonical_params=canonical_params)
+
+ def _is_canonical_url(self, canonical_params):
+ """Returns whether the current request URL is canonical."""
+ self.ensure_one()
+ # Compare OrderedMultiDict because the order is important, there must be
+ # only one canonical and not params permutations.
+ params = request.httprequest.args
+ canonical_params = canonical_params or OrderedMultiDict()
+ if params != canonical_params:
+ return False
+ # Compare URL at the first rerouting iteration (if available) because
+ # it's the one with the language in the path.
+ # It is important to also test the domain of the current URL.
+ current_url = request.httprequest.url_root[:-1] + (hasattr(request, 'rerouting') and request.rerouting[0] or request.httprequest.path)
+ canonical_url = self._get_canonical_url_localized(lang=request.lang, canonical_params=None)
+ # A request path with quotable characters (such as ",") is never
+ # canonical because request.httprequest.base_url is always unquoted,
+ # and canonical url is always quoted, so it is never possible to tell
+ # if the current URL is indeed canonical or not.
+ return current_url == canonical_url
+
class BaseModel(models.AbstractModel):
_inherit = 'base'
diff --git a/addons/website/models/website_menu.py b/addons/website/models/website_menu.py
index e710979015f..195942ebc0d 100644
--- a/addons/website/models/website_menu.py
+++ b/addons/website/models/website_menu.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
+from odoo.tools.translate import html_translate
class Menu(models.Model):
@@ -16,6 +17,20 @@ class Menu(models.Model):
menu = self.search([], limit=1, order="sequence DESC")
return menu.sequence or 0
+ def _compute_field_is_mega_menu(self):
+ for menu in self:
+ menu.is_mega_menu = bool(menu.mega_menu_content)
+
+ def _set_field_is_mega_menu(self):
+ for menu in self:
+ if menu.is_mega_menu:
+ if not menu.mega_menu_content:
+ default_content = self.env['ir.ui.view'].render_template('website.s_mega_menu_multi_menus')
+ menu.mega_menu_content = default_content.decode()
+ else:
+ menu.mega_menu_content = False
+ menu.mega_menu_classes = False
+
name = fields.Char('Menu', required=True, translate=True)
url = fields.Char('Url', default='')
page_id = fields.Many2one('website.page', 'Related Page', ondelete='cascade')
@@ -28,6 +43,9 @@ class Menu(models.Model):
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
group_ids = fields.Many2many('res.groups', string='Visible Groups',
help="User need to be at least in one of these groups to see the menu")
+ is_mega_menu = fields.Boolean(compute=_compute_field_is_mega_menu, inverse=_set_field_is_mega_menu)
+ mega_menu_content = fields.Html(translate=html_translate, sanitize=False)
+ mega_menu_classes = fields.Char()
def name_get(self):
if not self._context.get('display_website') and not self.env.user.has_group('website.group_multi_website'):
@@ -108,25 +126,25 @@ class Menu(models.Model):
@api.model
def get_tree(self, website_id, menu_id=None):
def make_tree(node):
- page_id = node.page_id.id if node.page_id else None
- is_homepage = page_id and self.env['website'].browse(website_id).homepage_id.id == page_id
- menu_node = dict(
- id=node.id,
- name=node.name,
- url=node.page_id.url if page_id else node.url,
- new_window=node.new_window,
- sequence=node.sequence,
- parent_id=node.parent_id.id,
- children=[],
- is_homepage=is_homepage,
- )
+ is_homepage = bool(node.page_id and self.env['website'].browse(website_id).homepage_id.id == node.page_id.id)
+ menu_node = {
+ 'fields': {
+ 'id': node.id,
+ 'name': node.name,
+ 'url': node.page_id.url if node.page_id else node.url,
+ 'new_window': node.new_window,
+ 'is_mega_menu': node.is_mega_menu,
+ 'sequence': node.sequence,
+ 'parent_id': node.parent_id.id,
+ },
+ 'children': [],
+ 'is_homepage': is_homepage,
+ }
for child in node.child_id:
menu_node['children'].append(make_tree(child))
return menu_node
- if menu_id:
- menu = self.browse(menu_id)
- else:
- menu = self.env['website'].browse(website_id).menu_id
+
+ menu = menu_id and self.browse(menu_id) or self.env['website'].browse(website_id).menu_id
return make_tree(menu)
@api.model
diff --git a/addons/website/models/website_visitor.py b/addons/website/models/website_visitor.py
index 9b262c26031..7f7e50073b4 100644
--- a/addons/website/models/website_visitor.py
+++ b/addons/website/models/website_visitor.py
@@ -13,7 +13,7 @@ from odoo.http import request
class WebsitVisitorPage(models.Model):
_name = 'website.visitor.page'
_description = 'Visited Pages'
- _order = 'visit_datetime ASC'
+ _order = 'visit_datetime DESC'
_log_access = False
visitor_id = fields.Many2one('website.visitor', ondelete="cascade", index=True, required=True, readonly=True)
@@ -106,7 +106,8 @@ class WebsiteVisitor(models.Model):
# If visitor does not exist
visitor_sudo = self._create_visitor(website_page.id)
sign = visitor_sudo._get_visitor_sign().get(visitor_sudo.id)
- response.set_cookie('visitor_id', sign)
+ expiration_date = datetime.now() + timedelta(days=100*365) # never expire
+ response.set_cookie('visitor_id', sign, expires=expiration_date.timestamp())
else:
# Add page even if already in visitor_page_ids as checks on relations are done in many2many write method
vals = {
@@ -122,10 +123,9 @@ class WebsiteVisitor(models.Model):
def _create_visitor(self, website_page_id=False):
country_code = request.session.get('geoip', {}).get('country_code', False)
country_id = request.env['res.country'].sudo().search([('code', '=', country_code)], limit=1).id if country_code else False
- lang_id = request.env['res.lang'].sudo().search([('code', '=', request.lang)], limit=1).id
vals = {
'last_connection_datetime': datetime.now(),
- 'lang_id': lang_id,
+ 'lang_id': request.lang.id,
'country_id': country_id,
'website_id': request.website.id
}