diff --git a/.gitignore b/.gitignore
index 3ad0a867b8ffd..71412d0ba8240 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,5 @@ setup/win32/static/postgresql*.exe
/man/
/share/
/src/
+/odoo.conf
+/dev/
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index c2e8a6fc91edf..8588d6073246f 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -55,7 +55,7 @@ def web_auth_signup(self, *args, **kw):
qcontext['error'] = _("Could not create a new account.")
response = request.render('auth_signup.signup', qcontext)
- response.headers['X-Frame-Options'] = 'DENY'
+ # response.headers['X-Frame-Options'] = 'DENY'
return response
@http.route('/web/reset_password', type='http', auth='public', website=True, sitemap=False)
@@ -87,7 +87,7 @@ def web_auth_reset_password(self, *args, **kw):
qcontext['error'] = str(e)
response = request.render('auth_signup.reset_password', qcontext)
- response.headers['X-Frame-Options'] = 'DENY'
+ # response.headers['X-Frame-Options'] = 'DENY'
return response
def get_auth_signup_config(self):
diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py
index b88a833601bce..2cffbf491a68c 100644
--- a/addons/bus/models/bus.py
+++ b/addons/bus/models/bus.py
@@ -110,7 +110,7 @@ def poll(self, dbname, channels, last, options=None, timeout=TIMEOUT):
current = threading.current_thread()
current._daemonic = True
# rename the thread to avoid tests waiting for a longpolling
- current.setName("openerp.longpolling.request.%s" % current.ident)
+ current.name = f"openerp.longpolling.request.{current.ident}"
registry = odoo.registry(dbname)
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 025f793e3f2ee..1bb33f03506c4 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -1850,7 +1850,7 @@ def _update_automated_probabilities(self):
# - avoid blocking the table for too long with a too big transaction
transactions_count, transactions_failed_count = 0, 0
cron_update_lead_start_date = datetime.now()
- auto_commit = not getattr(threading.currentThread(), 'testing', False)
+ auto_commit = not getattr(threading.current_thread(), 'testing', False)
for probability, probability_lead_ids in probability_leads.items():
for lead_ids_current in tools.split_every(PLS_UPDATE_BATCH_STEP, probability_lead_ids):
transactions_count += 1
diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py
index ae812e3907502..843b761b8eee2 100644
--- a/addons/event/models/event_mail.py
+++ b/addons/event/models/event_mail.py
@@ -179,7 +179,7 @@ def run(self, autocommit=False):
self.invalidate_cache()
self._warn_template_error(scheduler, e)
else:
- if autocommit and not getattr(threading.currentThread(), 'testing', False):
+ if autocommit and not getattr(threading.current_thread(), 'testing', False):
self.env.cr.commit()
return True
diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py
index 96127e5636740..39e6ec129e1f6 100644
--- a/addons/http_routing/models/ir_http.py
+++ b/addons/http_routing/models/ir_http.py
@@ -17,7 +17,8 @@
import odoo
from odoo import api, models, registry, exceptions, tools, http
-from odoo.addons.base.models.ir_http import RequestUID, ModelConverter
+from odoo.addons.base.models import ir_http
+from odoo.addons.base.models.ir_http import RequestUID
from odoo.addons.base.models.qweb import QWebException
from odoo.http import request
from odoo.osv import expression
@@ -155,9 +156,9 @@ def url_lang(path_or_uri, lang_code=None):
lang_code = pycompat.to_text(lang_code or request.context['lang'])
lang_url_code = Lang._lang_code_to_urlcode(lang_code)
lang_url_code = lang_url_code if lang_url_code in lang_url_codes else lang_code
-
if (len(lang_url_codes) > 1 or force_lang) and is_multilang_url(location, lang_url_codes):
- ps = location.split(u'/')
+ loc, sep, qs = location.partition('?')
+ ps = loc.split(u'/')
default_lg = request.env['ir.http']._get_default_lang()
if ps[1] in lang_url_codes:
# Replace the language only if we explicitly provide a language to url_for
@@ -169,7 +170,8 @@ def url_lang(path_or_uri, lang_code=None):
# Insert the context language or the provided language
elif lang_url_code != default_lg.url_code or force_lang:
ps.insert(1, lang_url_code)
- location = u'/'.join(ps)
+
+ location = u'/'.join(ps) + sep + qs
return location
@@ -197,7 +199,7 @@ def url_for(url_from, lang_code=None, no_rewrite=False):
and '/static/' not in path
and not path.startswith('/web/')
)):
- new_url = request.env['ir.http'].url_rewrite(path)
+ new_url, _ = request.env['ir.http'].url_rewrite(path)
new_url = new_url if not qs else new_url + '?%s' % qs
return url_lang(new_url or url_from, lang_code=lang_code)
@@ -229,7 +231,8 @@ def is_multilang_url(local_url, lang_url_codes=None):
# Try to match an endpoint in werkzeug's routing table
try:
- func = request.env['ir.http']._get_endpoint_qargs(path, query_args=query_string)
+ _, func = request.env['ir.http'].url_rewrite(path, query_args=query_string)
+
# /page/xxx has no endpoint/func but is multilang
return (not func or (
func.routing.get('website', False)
@@ -240,7 +243,7 @@ def is_multilang_url(local_url, lang_url_codes=None):
return False
-class ModelConverter(ModelConverter):
+class ModelConverter(ir_http.ModelConverter):
def __init__(self, url_map, model=False, domain='[]'):
super(ModelConverter, self).__init__(url_map, model)
@@ -425,7 +428,7 @@ def _dispatch(cls):
# handle // in url
if request.httprequest.method == 'GET' and '//' in request.httprequest.path:
new_url = request.httprequest.path.replace('//', '/') + '?' + request.httprequest.query_string.decode('utf-8')
- return werkzeug.utils.redirect(new_url, 301)
+ return request.redirect(new_url, code=301)
# locate the controller method
try:
@@ -439,7 +442,7 @@ def _dispatch(cls):
# most of the time the browser is loading and inexisting assets or image. A standard 404 is enough.
# Earlier check would be difficult since we don't want to break data modules
path_components = request.httprequest.path.split('/')
- request.is_frontend = len(path_components) < 3 or path_components[2] != 'static' or not '.' in path_components[-1]
+ request.is_frontend = len(path_components) < 3 or path_components[2] != 'static' or '.' not in path_components[-1]
routing_error = e
request.is_frontend_multilang = not func or (func and request.is_frontend and func.routing.get('multilang', func.routing['type'] == 'http'))
@@ -458,8 +461,6 @@ def _dispatch(cls):
# For website routes (only), add website params on `request`
if request.is_frontend:
- request.redirect = lambda url, code=302: werkzeug.utils.redirect(url_for(url), code)
-
cls._add_dispatch_parameters(func)
path = request.httprequest.path.split('/')
@@ -490,6 +491,12 @@ def _dispatch(cls):
return redirect
elif url_lg:
request.uid = None
+ if request.httprequest.path == '/%s/' % url_lg:
+ # special case for homepage controller, mimick `_postprocess_args()` redirect
+ path = request.httprequest.path[:-1]
+ if request.httprequest.query_string:
+ path += '?' + request.httprequest.query_string.decode('utf-8')
+ return request.redirect(path, code=301)
path.pop(1)
routing_error = None
return cls.reroute('/'.join(path) or '/')
@@ -512,11 +519,17 @@ def _dispatch(cls):
result = super(IrHttp, cls)._dispatch()
cook_lang = request.httprequest.cookies.get('frontend_lang')
- if request.is_frontend and cook_lang != request.lang.code and hasattr(result, 'set_cookie'):
- result.set_cookie('frontend_lang', request.lang.code)
+ if request.is_frontend and cook_lang != request.lang._get_cached('code') and hasattr(result, 'set_cookie'):
+ result.set_cookie('frontend_lang', request.lang._get_cached('code'))
return result
+ @classmethod
+ def _redirect(cls, location, code=303):
+ if request and request.db and getattr(request, 'is_frontend', False):
+ location = url_for(location)
+ return super()._redirect(location, code)
+
@classmethod
def reroute(cls, path):
if not hasattr(request, 'rerouting'):
@@ -528,8 +541,13 @@ def reroute(cls, path):
raise Exception("Rerouting limit exceeded")
request.httprequest.environ['PATH_INFO'] = path
# void werkzeug cached_property. TODO: find a proper way to do this
- for key in ('path', 'full_path', 'url', 'base_url'):
+ for key in ('full_path', 'url', 'base_url'):
request.httprequest.__dict__.pop(key, None)
+ # since werkzeug 2.0 `path`` became an attribute and is not a cached property anymore
+ if hasattr(type(request.httprequest), 'path'): # cached property
+ request.httprequest.__dict__.pop('path', None)
+ else: # direct attribute
+ request.httprequest.path = '/' + path.lstrip('/')
return cls._dispatch()
@@ -553,7 +571,7 @@ def _postprocess_args(cls, arguments, rule):
path = '/' + request.lang.url_code + path
if request.httprequest.query_string:
path += '?' + request.httprequest.query_string.decode('utf-8')
- return werkzeug.utils.redirect(path, code=301)
+ return request.redirect(path, code=301)
@classmethod
def _get_exception_code_values(cls, exception):
@@ -654,28 +672,9 @@ def _handle_exception(cls, exception):
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
@api.model
- @tools.ormcache('path')
- def url_rewrite(self, path):
+ @tools.ormcache('path', 'query_args')
+ def url_rewrite(self, path, query_args=None):
new_url = False
- router = http.root.get_db_router(request.db).bind('')
- try:
- _ = router.match(path, method='POST')
- except werkzeug.exceptions.MethodNotAllowed:
- _ = router.match(path, method='GET')
- except werkzeug.routing.RequestRedirect as e:
- # get path from http://{path}?{current query string}
- new_url = e.new_url.split('?')[0][7:]
- except werkzeug.exceptions.NotFound:
- new_url = path
- except Exception as e:
- raise e
-
- return new_url or path
-
- # merge with def url_rewrite in master/14.1
- @api.model
- @tools.cache('path', 'query_args')
- def _get_endpoint_qargs(self, path, query_args=None):
router = http.root.get_db_router(request.db).bind('')
endpoint = False
try:
@@ -683,10 +682,10 @@ def _get_endpoint_qargs(self, path, query_args=None):
except werkzeug.exceptions.MethodNotAllowed:
endpoint = router.match(path, method='GET', query_args=query_args)
except werkzeug.routing.RequestRedirect as e:
- new_url = e.new_url[7:] # remove scheme
- assert new_url != path
- endpoint = self._get_endpoint_qargs(new_url, query_args)
+ # get path from http://{path}?{current query string}
+ new_url = e.new_url.split('?')[0][7:]
+ _, endpoint = self.url_rewrite(new_url, query_args)
endpoint = endpoint and [endpoint]
except werkzeug.exceptions.NotFound:
- pass # endpoint = False
- return endpoint and endpoint[0]
+ new_url = path
+ return new_url or path, endpoint and endpoint[0]
diff --git a/addons/hw_drivers/tools/helpers.py b/addons/hw_drivers/tools/helpers.py
index cf4a67b2770cd..796d44bd53b85 100644
--- a/addons/hw_drivers/tools/helpers.py
+++ b/addons/hw_drivers/tools/helpers.py
@@ -200,7 +200,7 @@ def load_certificate():
db_uuid = read_file_first_line('odoo-db-uuid.conf')
enterprise_code = read_file_first_line('odoo-enterprise-code.conf')
if db_uuid and enterprise_code:
- url = 'https://www.odoo.com/odoo-enterprise/iot/x509'
+ url = 'https://www.odoochain.com/odoochain-enterprise/iot/x509'
data = {
'params': {
'db_uuid': db_uuid,
diff --git a/addons/l10n_cn/data/account_tax_group_data.xml b/addons/l10n_cn/data/account_tax_group_data.xml
index 47f108bf67d7f..58f3c7a969f2f 100644
--- a/addons/l10n_cn/data/account_tax_group_data.xml
+++ b/addons/l10n_cn/data/account_tax_group_data.xml
@@ -1,12 +1,24 @@
+
+ VAT 3%
+
VAT 6%
VAT 9%
+
+ VAT 11%
+
VAT 13%
+
+ VAT 16%
+
+
+ VAT 17%
+
diff --git a/addons/l10n_cn_city/__manifest__.py b/addons/l10n_cn_city/__manifest__.py
index 44f28990261dc..fe884ecbae1b5 100644
--- a/addons/l10n_cn_city/__manifest__.py
+++ b/addons/l10n_cn_city/__manifest__.py
@@ -14,7 +14,7 @@
City Data/城市数据
""",
- 'depends': ['l10n_cn','base_address_city'],
+ 'depends': ['base_address_city'],
'data': [
'data/res_city_data.xml',
],
diff --git a/addons/link_tracker/models/mail_render_mixin.py b/addons/link_tracker/models/mail_render_mixin.py
index 3fa3159f361b0..21ba81f4d90a4 100644
--- a/addons/link_tracker/models/mail_render_mixin.py
+++ b/addons/link_tracker/models/mail_render_mixin.py
@@ -3,7 +3,8 @@
import re
-from werkzeug import urls, utils
+from html import unescape
+from werkzeug import urls
from odoo import api, models, tools
@@ -40,7 +41,7 @@ def _shorten_links(self, html, link_tracker_vals, blacklist=None, base_url=None)
label = (match[3] or '').strip()
if not blacklist or not [s for s in blacklist if s in long_url] and not long_url.startswith(short_schema):
- create_vals = dict(link_tracker_vals, url=utils.unescape(long_url), label=utils.unescape(label))
+ create_vals = dict(link_tracker_vals, url=unescape(long_url), label=unescape(label))
link = self.env['link.tracker'].create(create_vals)
if link.short_url:
new_href = href.replace(long_url, link.short_url)
@@ -69,7 +70,7 @@ def _shorten_links_text(self, content, link_tracker_vals, blacklist=None, base_u
if blacklist and any(item in parsed.path for item in blacklist):
continue
- create_vals = dict(link_tracker_vals, url= utils.unescape(original_url))
+ create_vals = dict(link_tracker_vals, url=unescape(original_url))
link = self.env['link.tracker'].create(create_vals)
if link.short_url:
content = content.replace(original_url, link.short_url, 1)
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 80d1ece80f38f..3e042b0879238 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -141,7 +141,7 @@ def process_email_queue(self, ids=None):
res = None
try:
# auto-commit except in testing mode
- auto_commit = not getattr(threading.currentThread(), 'testing', False)
+ auto_commit = not getattr(threading.current_thread(), 'testing', False)
res = self.browse(ids).send(auto_commit=auto_commit)
except Exception:
_logger.exception("Failed processing mail queue")
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 70db999886326..9cf9e3b309582 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -2346,7 +2346,7 @@ def _notify_record_by_email(self, message, recipients_data, msg_vals=False,
# 2. do not send emails immediately if the registry is not loaded,
# to prevent sending email during a simple update of the database
# using the command-line.
- test_mode = getattr(threading.currentThread(), 'testing', False)
+ test_mode = getattr(threading.current_thread(), 'testing', False)
if force_send and len(emails) < recipients_max and (not self.pool._init or test_mode):
# unless asked specifically, send emails after the transaction to
# avoid side effects due to emails being sent while the transaction fails
diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py
index c052028f484cf..615a94c4d62d7 100644
--- a/addons/mass_mailing/models/mailing.py
+++ b/addons/mass_mailing/models/mailing.py
@@ -615,7 +615,7 @@ def action_send_mail(self, res_ids=None):
extra_context = mailing._get_mass_mailing_context()
composer = composer.with_context(active_ids=res_ids, **extra_context)
# auto-commit except in testing mode
- auto_commit = not getattr(threading.currentThread(), 'testing', False)
+ auto_commit = not getattr(threading.current_thread(), 'testing', False)
composer.send_mail(auto_commit=auto_commit)
mailing.write({
'state': 'done',
diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py
index 03db10759ac79..bba8cee013f6d 100644
--- a/addons/pad/models/pad.py
+++ b/addons/pad/models/pad.py
@@ -88,6 +88,7 @@ def pad_get_content(self, url):
path = len(split_url) == 2 and split_url[1]
try:
content = myPad.getHtml(path).get('html', '')
+ # todo pad跨域和不显示问题
except IOError:
_logger.warning('Http Error: the credentials might be absent for url: "%s". Falling back.' % url)
try:
diff --git a/addons/portal/controllers/portal.py b/addons/portal/controllers/portal.py
index 47394d900b36b..8ac4befc69733 100644
--- a/addons/portal/controllers/portal.py
+++ b/addons/portal/controllers/portal.py
@@ -214,7 +214,7 @@ def account(self, redirect=None, **post):
})
response = request.render("portal.portal_my_details", values)
- response.headers['X-Frame-Options'] = 'DENY'
+ # response.headers['X-Frame-Options'] = 'DENY'
return response
@route('/my/security', type='http', auth='user', website=True, methods=['GET', 'POST'])
diff --git a/addons/sale_timesheet/models/product.py b/addons/sale_timesheet/models/product.py
index f47a5dd9b8645..a37f6643f3411 100644
--- a/addons/sale_timesheet/models/product.py
+++ b/addons/sale_timesheet/models/product.py
@@ -98,7 +98,7 @@ def unlink(self):
def write(self, vals):
# timesheet product can't be archived
- test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode()
+ test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode()
if not test_mode and 'active' in vals and not vals['active']:
time_product = self.env.ref('sale_timesheet.time_product')
if time_product.product_tmpl_id in self:
@@ -143,7 +143,7 @@ def unlink(self):
def write(self, vals):
# timesheet product can't be archived
- test_mode = getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode()
+ test_mode = getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode()
if not test_mode and 'active' in vals and not vals['active']:
time_product = self.env.ref('sale_timesheet.time_product')
if time_product in self:
diff --git a/addons/sms/models/sms_sms.py b/addons/sms/models/sms_sms.py
index a1c197a6cb159..1991d04c0a62f 100644
--- a/addons/sms/models/sms_sms.py
+++ b/addons/sms/models/sms_sms.py
@@ -55,7 +55,7 @@ def send(self, delete_all=False, auto_commit=False, raise_exception=False):
for batch_ids in self._split_batch():
self.browse(batch_ids)._send(delete_all=delete_all, raise_exception=raise_exception)
# auto-commit if asked except in testing mode
- if auto_commit is True and not getattr(threading.currentThread(), 'testing', False):
+ if auto_commit is True and not getattr(threading.current_thread(), 'testing', False):
self._cr.commit()
def cancel(self):
@@ -81,7 +81,7 @@ def _process_queue(self, ids=None):
res = None
try:
# auto-commit except in testing mode
- auto_commit = not getattr(threading.currentThread(), 'testing', False)
+ auto_commit = not getattr(threading.current_thread(), 'testing', False)
res = self.browse(ids).send(delete_all=False, auto_commit=auto_commit, raise_exception=False)
except Exception:
_logger.exception("Failed processing SMS queue")
diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py
index 79612099e7750..58b9ab7032ae6 100644
--- a/addons/stock/__manifest__.py
+++ b/addons/stock/__manifest__.py
@@ -11,13 +11,13 @@
'category': 'Inventory/Inventory',
'sequence': 25,
'demo': [
- 'data/stock_demo_pre.xml',
- 'data/procurement_demo.xml',
- 'data/stock_demo.xml',
- 'data/stock_orderpoint_demo.xml',
- 'data/stock_demo2.xml',
- 'data/stock_location_demo_cpu1.xml',
- 'data/stock_location_demo_cpu3.xml',
+ # 'data/stock_demo_pre.xml',
+ # 'data/procurement_demo.xml',
+ # 'data/stock_demo.xml',
+ # 'data/stock_orderpoint_demo.xml',
+ # 'data/stock_demo2.xml',
+ # 'data/stock_location_demo_cpu1.xml',
+ # 'data/stock_location_demo_cpu3.xml',
],
'data': [
'security/stock_security.xml',
diff --git a/addons/stock_sms/models/stock_picking.py b/addons/stock_sms/models/stock_picking.py
index 846af19271fe5..554a160fa56d9 100644
--- a/addons/stock_sms/models/stock_picking.py
+++ b/addons/stock_sms/models/stock_picking.py
@@ -23,7 +23,7 @@ def _check_warn_sms(self):
is_delivery = picking.company_id.stock_move_sms_validation \
and picking.picking_type_id.code == 'outgoing' \
and (picking.partner_id.mobile or picking.partner_id.phone)
- if is_delivery and not getattr(threading.currentThread(), 'testing', False) \
+ if is_delivery and not getattr(threading.current_thread(), 'testing', False) \
and not self.env.registry.in_test_mode() \
and not picking.company_id.has_received_warning_stock_sms \
and picking.company_id.stock_move_sms_validation:
@@ -52,7 +52,7 @@ def _sms_get_number_fields(self):
def _send_confirmation_email(self):
super(Picking, self)._send_confirmation_email()
- if not self.env.context.get('skip_sms') and not getattr(threading.currentThread(), 'testing', False) and not self.env.registry.in_test_mode():
+ if not self.env.context.get('skip_sms') and not getattr(threading.current_thread(), 'testing', False) and not self.env.registry.in_test_mode():
pickings = self.filtered(lambda p: p.company_id.stock_move_sms_validation and p.picking_type_id.code == 'outgoing' and (p.partner_id.mobile or p.partner_id.phone))
for picking in pickings:
# Sudo as the user has not always the right to read this sms template.
diff --git a/addons/web/controllers/__init__.py b/addons/web/controllers/__init__.py
index 48cc37266c608..d38dcc630d8bc 100644
--- a/addons/web/controllers/__init__.py
+++ b/addons/web/controllers/__init__.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import main, pivot
+from . import main
+from . import pivot
+from . import profiling
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index bcac8f45e1e67..db4a1169ef408 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -18,7 +18,10 @@
import re
import sys
import tempfile
+import unicodedata
+from collections import OrderedDict, defaultdict
+import babel.messages.pofile
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
@@ -889,16 +892,16 @@ class Home(http.Controller):
@http.route('/', type='http', auth="none")
def index(self, s_action=None, db=None, **kw):
- return http.local_redirect('/web', query=request.params, keep_hash=True)
+ return request.redirect_query('/web', query=request.params)
# ideally, this route should be `auth="user"` but that don't work in non-monodb mode.
@http.route('/web', type='http', auth="none")
def web_client(self, s_action=None, **kw):
ensure_db()
if not request.session.uid:
- return werkzeug.utils.redirect('/web/login', 303)
+ return request.redirect('/web/login', 303)
if kw.get('redirect'):
- return werkzeug.utils.redirect(kw.get('redirect'), 303)
+ return request.redirect(kw.get('redirect'), 303)
request.uid = request.session.uid
try:
@@ -907,7 +910,7 @@ def web_client(self, s_action=None, **kw):
response.headers['X-Frame-Options'] = 'DENY'
return response
except AccessError:
- return werkzeug.utils.redirect('/web/login?error=access')
+ return request.redirect('/web/login?error=access')
@http.route('/web/webclient/load_menus/', type='http', auth='user', methods=['GET'])
def web_load_menus(self, unique):
@@ -916,7 +919,7 @@ def web_load_menus(self, unique):
:param unique: this parameters is not used, but mandatory: it is used by the HTTP stack to make a unique request
:return: the menus (including the images in Base64)
"""
- menus = request.env["ir.ui.menu"].load_menus(request.session.debug)
+ menus = request.env["ir.ui.menu"].load_web_menus(request.session.debug)
body = json.dumps(menus, default=ustr)
response = request.make_response(body, [
# this method must specify a content-type application/json instead of using the default text/html set because
@@ -934,7 +937,7 @@ def web_login(self, redirect=None, **kw):
ensure_db()
request.params['login_success'] = False
if request.httprequest.method == 'GET' and redirect and request.session.uid:
- return http.redirect_with_hash(redirect)
+ return request.redirect(redirect)
if not request.uid:
request.uid = odoo.SUPERUSER_ID
@@ -950,7 +953,7 @@ def web_login(self, redirect=None, **kw):
try:
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
request.params['login_success'] = True
- return http.redirect_with_hash(self._login_redirect(uid, redirect=redirect))
+ return request.redirect(self._login_redirect(uid, redirect=redirect))
except odoo.exceptions.AccessDenied as e:
request.uid = old_uid
if e.args == odoo.exceptions.AccessDenied().args:
@@ -980,7 +983,17 @@ def switch_to_admin(self):
request.env['res.users'].clear_caches()
request.session.session_token = security.compute_session_token(request.session, request.env)
- return http.local_redirect(self._login_redirect(uid), keep_hash=True)
+ return request.redirect(self._login_redirect(uid))
+
+ @http.route('/web/health', type='http', auth='none', save_session=False)
+ def health(self):
+ data = json.dumps({
+ 'status': 'pass',
+ })
+ headers = [('Content-Type', 'application/json'),
+ ('Cache-Control', 'no-store')]
+ return request.make_response(data, headers)
+
class WebClient(http.Controller):
diff --git a/addons/web/controllers/profiling.py b/addons/web/controllers/profiling.py
new file mode 100644
index 0000000000000..b320ee0cfba4e
--- /dev/null
+++ b/addons/web/controllers/profiling.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import json
+
+from odoo.exceptions import UserError
+from odoo.http import Controller, request, Response, route
+
+class Profiling(Controller):
+
+ @route('/web/set_profiling', type='http', auth='public', sitemap=False)
+ def profile(self, profile=None, collectors=None, **params):
+ if collectors is not None:
+ collectors = collectors.split(',')
+ else:
+ collectors = ['sql', 'traces_async']
+ profile = profile and profile != '0'
+ try:
+ state = request.env['ir.profile'].set_profiling(profile, collectors=collectors, params=params)
+ return json.dumps(state)
+ except UserError as e:
+ return Response(response='error: %s' % e, status=500)
+
+ @route(['/web/speedscope', '/web/speedscope/'], type='http', sitemap=False, auth='user')
+ def speedscope(self, profile=None):
+ # don't server speedscope index if profiling is not enabled
+ if not request.env['ir.profile']._enabled_until():
+ return request.not_found()
+ icp = request.env['ir.config_parameter']
+ context = {
+ 'profile': profile,
+ 'url_root': request.httprequest.url_root,
+ 'cdn': icp.sudo().get_param('speedscope_cdn', "https://cdn.jsdelivr.net/npm/speedscope@1.13.0/dist/release/")
+ }
+ return request.render('web.view_speedscope_index', context)
diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js
index 36af39567acf9..d6bc186ff75e6 100644
--- a/addons/web/static/src/js/fields/upgrade_fields.js
+++ b/addons/web/static/src/js/fields/upgrade_fields.js
@@ -43,7 +43,7 @@ var AbstractFieldUpgrade = {
args: [[["share", "=", false]]],
})
.then(function (data) {
- framework.redirect("https://www.odoo.com/odoo-enterprise/upgrade?num_users=" + data);
+ framework.redirect("https://www.odoochain.com/odoochain-enterprise/upgrade?num_users=" + data);
});
},
/**
diff --git a/addons/website/controllers/__init__.py b/addons/website/controllers/__init__.py
index 5ee7aaab8f264..12c98dba76637 100644
--- a/addons/website/controllers/__init__.py
+++ b/addons/website/controllers/__init__.py
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import backend
+from . import form
from . import main
diff --git a/addons/website/controllers/form.py b/addons/website/controllers/form.py
new file mode 100644
index 0000000000000..c7f802efaecb3
--- /dev/null
+++ b/addons/website/controllers/form.py
@@ -0,0 +1,283 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import base64
+import json
+
+from psycopg2 import IntegrityError
+from werkzeug.exceptions import BadRequest
+
+from odoo import http, SUPERUSER_ID, _
+from odoo.http import request
+from odoo.tools import plaintext2html
+from odoo.exceptions import ValidationError, UserError
+from odoo.addons.base.models.ir_qweb_fields import nl2br
+
+
+class WebsiteForm(http.Controller):
+
+ @http.route('/website/form', type='http', auth="public", methods=['POST'], multilang=False)
+ def website_form_empty(self, **kwargs):
+ # This is a workaround to don't add language prefix to