[FW][FIX] adapt odoo 15.0 to work with ubuntu jammy
Ubuntu Jammy brings some breaking changes for odoo 15.0. Since 15.0 is still the main branch, this pr aims to ensure compatibility if deployed on ubuntu Jammy. Main changes: - python 3.10 - werkzeug 2.0+, including vendoring werkzeug's user agent parser in `odoo/tools/_vendor/useragents.py` (= the one from version 0.16) as it is discontinued after v2.1 - currentThread is now deprecated (use current_thread instead) - default requirement version (based on deb package version) - new opcode in python 3.10 - distutils.version.LooseVersion is deprecated - wkhtmltopd version packaged with ubuntu jammy don't have patched qt but will work without headers and footers. Suggested wkhtmltopdf version 0.12.5 doesn't have a build working with jammy yet because of incompatible libssl dependencies. A parallel wip may provide a 0.12.5 version compatible with jammy latter (currently used as testing version on runbot). If this solution is chosen, this build may be provided by odoo latter. As usual, all changes are made with the spirit to keep compatibility with previous version (Focal) enterprise PR: https://github.com/odoo/enterprise/pull/26831 closes #84097 closes #88980 closes #89533 closes #78020 closes #83309 closes #87463 closes odoo/odoo#91927 Forward-port-of: odoo/odoo#91543 Forward-port-of: odoo/odoo#88803 Related: odoo/enterprise#27601 Signed-off-by: Christophe Monniez (moc) <moc@odoo.com>
This commit is contained in:
@@ -2156,7 +2156,7 @@ class Lead(models.Model):
|
||||
# - 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
|
||||
|
||||
@@ -445,7 +445,7 @@ class Team(models.Model):
|
||||
|
||||
BUNDLE_HOURS_DELAY = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.delay', default=0))
|
||||
BUNDLE_COMMIT_SIZE = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.commit.bundle', 100))
|
||||
auto_commit = not getattr(threading.currentThread(), 'testing', False)
|
||||
auto_commit = not getattr(threading.current_thread(), 'testing', False)
|
||||
|
||||
# leads
|
||||
max_create_dt = self.env.cr.now() - datetime.timedelta(hours=BUNDLE_HOURS_DELAY)
|
||||
|
||||
@@ -154,7 +154,7 @@ class TeamMember(models.Model):
|
||||
leads_done_ids = set()
|
||||
counter = 0
|
||||
# auto-commit except in testing mode
|
||||
auto_commit = not getattr(threading.currentThread(), 'testing', False)
|
||||
auto_commit = not getattr(threading.current_thread(), 'testing', False)
|
||||
commit_bundle_size = int(self.env['ir.config_parameter'].sudo().get_param('crm.assignment.commit.bundle', 100))
|
||||
while population:
|
||||
counter += 1
|
||||
|
||||
@@ -242,7 +242,7 @@ You receive this email because you are:
|
||||
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
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
@api.constrains('module_hr_presence', 'hr_presence_control_email', 'hr_presence_control_ip')
|
||||
def _check_advanced_presence(self):
|
||||
test_mode = self.env.registry.in_test_mode() or getattr(threading.currentThread(), 'testing', False)
|
||||
test_mode = self.env.registry.in_test_mode() or getattr(threading.current_thread(), 'testing', False)
|
||||
if self.env.context.get('install_mode', False) or test_mode:
|
||||
return
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import re
|
||||
|
||||
import markupsafe
|
||||
from werkzeug import urls, utils
|
||||
from html import unescape
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, models, tools
|
||||
|
||||
@@ -45,7 +46,7 @@ class MailRenderMixin(models.AbstractModel):
|
||||
continue
|
||||
label = (match[3] or '').strip()
|
||||
|
||||
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'].search_or_create(create_vals)
|
||||
if link.short_url:
|
||||
# `str` manipulation required to support replacing "&" characters, common in urls
|
||||
@@ -75,7 +76,7 @@ class MailRenderMixin(models.AbstractModel):
|
||||
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'].search_or_create(create_vals)
|
||||
if link.short_url:
|
||||
# Ensures we only replace the same link and not a subpart of a longer one, multiple times if applicable
|
||||
|
||||
@@ -197,7 +197,7 @@ class MailMail(models.Model):
|
||||
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")
|
||||
|
||||
@@ -2480,7 +2480,7 @@ class MailThread(models.AbstractModel):
|
||||
# 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
|
||||
|
||||
@@ -978,7 +978,7 @@ class MassMailing(models.Model):
|
||||
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._action_send_mail(auto_commit=auto_commit)
|
||||
mailing.write({
|
||||
'state': 'done',
|
||||
|
||||
@@ -27,7 +27,7 @@ class ResCompany(models.Model):
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
if not getattr(threading.currentThread(), 'testing', False):
|
||||
if not getattr(threading.current_thread(), 'testing', False):
|
||||
res.iap_enrich_auto()
|
||||
return res
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
from datetime import date, timedelta
|
||||
|
||||
import requests
|
||||
import werkzeug
|
||||
|
||||
from html import unescape
|
||||
|
||||
from odoo import models, api, service
|
||||
from odoo.tools.translate import _
|
||||
@@ -69,7 +70,7 @@ class MercuryTransaction(models.Model):
|
||||
try:
|
||||
r = requests.post(url, data=xml_transaction, headers=headers, timeout=65)
|
||||
r.raise_for_status()
|
||||
response = werkzeug.utils.unescape(r.content.decode())
|
||||
response = unescape(r.content.decode())
|
||||
except Exception:
|
||||
response = "timeout"
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class ProductTemplate(models.Model):
|
||||
|
||||
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:
|
||||
@@ -138,7 +138,7 @@ class ProductProduct(models.Model):
|
||||
|
||||
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:
|
||||
|
||||
@@ -96,7 +96,7 @@ class SmsSms(models.Model):
|
||||
for batch_ids in self._split_batch():
|
||||
self.browse(batch_ids)._send(unlink_failed=unlink_failed, unlink_sent=unlink_sent, 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 resend_failed(self):
|
||||
@@ -145,7 +145,7 @@ class SmsSms(models.Model):
|
||||
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(unlink_failed=False, unlink_sent=True, auto_commit=auto_commit, raise_exception=False)
|
||||
except Exception:
|
||||
_logger.exception("Failed processing SMS queue")
|
||||
|
||||
@@ -23,7 +23,7 @@ class Picking(models.Model):
|
||||
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 @@ class Picking(models.Model):
|
||||
|
||||
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.
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestCrmPerformance(CrmPerformanceCase):
|
||||
country_be = self.env.ref('base.be')
|
||||
lang_be_id = self.env['res.lang']._lang_get_id('fr_BE')
|
||||
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=243): # 235, sometimes +6
|
||||
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=244): # 235, sometimes +6
|
||||
self.env.cr._now = self.reference_now # force create_date to check schedulers
|
||||
crm_values = [
|
||||
{'country_id': country_be.id,
|
||||
|
||||
@@ -346,7 +346,7 @@ class IrUiView(models.Model):
|
||||
# During a theme module update, theme views' copies receiving an arch
|
||||
# update should not be considered as `arch_updated`, as this is not a
|
||||
# user made change.
|
||||
test_mode = getattr(threading.currentThread(), 'testing', False)
|
||||
test_mode = getattr(threading.current_thread(), 'testing', False)
|
||||
if not (test_mode or self.pool._init):
|
||||
return super().write(vals)
|
||||
no_arch_updated_views = other_views = self.env['ir.ui.view']
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ def registry(database_name=None):
|
||||
"""
|
||||
if database_name is None:
|
||||
import threading
|
||||
database_name = threading.currentThread().dbname
|
||||
database_name = threading.current_thread().dbname
|
||||
return modules.registry.Registry(database_name)
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.tools.safe_eval import safe_eval, time
|
||||
from odoo.tools.misc import find_in_path
|
||||
from odoo.tools import config, is_html_empty
|
||||
from odoo.tools import config, is_html_empty, parse_version
|
||||
from odoo.http import request
|
||||
from odoo.osv.expression import NEGATIVE_TERM_OPERATORS, FALSE_DOMAIN
|
||||
|
||||
@@ -21,7 +21,6 @@ import json
|
||||
|
||||
from lxml import etree
|
||||
from contextlib import closing
|
||||
from distutils.version import LooseVersion
|
||||
from reportlab.graphics.barcode import createBarcodeDrawing
|
||||
from PyPDF2 import PdfFileWriter, PdfFileReader, utils
|
||||
from collections import OrderedDict
|
||||
@@ -69,12 +68,12 @@ else:
|
||||
match = re.search(b'([0-9.]+)', out)
|
||||
if match:
|
||||
version = match.group(0).decode('ascii')
|
||||
if LooseVersion(version) < LooseVersion('0.12.0'):
|
||||
if parse_version(version) < parse_version('0.12.0'):
|
||||
_logger.info('Upgrade Wkhtmltopdf to (at least) 0.12.0')
|
||||
wkhtmltopdf_state = 'upgrade'
|
||||
else:
|
||||
wkhtmltopdf_state = 'ok'
|
||||
if LooseVersion(version) >= LooseVersion('0.12.2'):
|
||||
if parse_version(version) >= parse_version('0.12.2'):
|
||||
wkhtmltopdf_dpi_zoom_ratio = True
|
||||
|
||||
if config['workers'] == 1:
|
||||
|
||||
@@ -711,7 +711,7 @@ class IrMailServer(models.Model):
|
||||
Can be overridden in tests after mocking the SMTP lib to test in depth the
|
||||
outgoing mail server.
|
||||
"""
|
||||
return getattr(threading.currentThread(), 'testing', False) or self.env.registry.in_test_mode()
|
||||
return getattr(threading.current_thread(), 'testing', False) or self.env.registry.in_test_mode()
|
||||
|
||||
def _neutralize(self):
|
||||
super()._neutralize()
|
||||
|
||||
@@ -578,7 +578,7 @@ class Module(models.Model):
|
||||
}
|
||||
|
||||
def _button_immediate_function(self, function):
|
||||
if getattr(threading.currentThread(), 'testing', False):
|
||||
if getattr(threading.current_thread(), 'testing', False):
|
||||
raise RuntimeError(
|
||||
"Module operations inside tests are not transactional and thus forbidden.\n"
|
||||
"If you really need to perform module operations to test a specific behavior, it "
|
||||
|
||||
@@ -512,6 +512,6 @@ class TestEmailMessage(TransactionCase):
|
||||
msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>'
|
||||
|
||||
smtp = FakeSMTP()
|
||||
self.patch(threading.currentThread(), 'testing', False)
|
||||
self.patch(threading.current_thread(), 'testing', False)
|
||||
self.env['ir.mail_server'].send_email(msg, smtp_session=smtp)
|
||||
self.assertTrue(smtp.email_sent)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import html
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from urllib.parse import urlparse
|
||||
@@ -380,7 +381,7 @@ class TestHttpModels(TestHttpBase):
|
||||
milky_way = self.env.ref('test_http.milky_way')
|
||||
res = self.url_open(f'/test_http/{milky_way.id}/9999') # unknown gate
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertIn("The goa'uld destroyed the gate", res.text)
|
||||
self.assertIn("The goa'uld destroyed the gate", html.unescape(res.text))
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@@ -510,6 +511,7 @@ class TestHttpEnsureDb(TestHttpBase):
|
||||
self.assertEqual(new_session.uid, None)
|
||||
|
||||
# follow redirection
|
||||
self.opener.cookies['session_id'] = new_session.sid
|
||||
res = self.multidb_url_open('/test_http/ensure_db')
|
||||
res.raise_for_status()
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
@@ -7,7 +7,6 @@ try:
|
||||
except ImportError:
|
||||
pylint = None
|
||||
import subprocess
|
||||
from distutils.version import LooseVersion
|
||||
import os
|
||||
from os.path import join
|
||||
import sys
|
||||
@@ -53,10 +52,10 @@ class TestPyLint(TransactionCase):
|
||||
def test_pylint(self):
|
||||
if pylint is None:
|
||||
self._skip_test('please install pylint')
|
||||
required_pylint_version = LooseVersion('1.6.4')
|
||||
required_pylint_version = tools.parse_version('1.6.4')
|
||||
if sys.version_info >= (3, 6):
|
||||
required_pylint_version = LooseVersion('1.7.0')
|
||||
if LooseVersion(getattr(pylint, '__version__', '0.0.1')) < required_pylint_version:
|
||||
required_pylint_version = tools.parse_version('1.7.0')
|
||||
if tools.parse_version(getattr(pylint, '__version__', '0.0.1')) < required_pylint_version:
|
||||
self._skip_test('please upgrade pylint to >= %s' % required_pylint_version)
|
||||
|
||||
paths = [tools.config['root_path']]
|
||||
|
||||
+3
-1
@@ -160,6 +160,7 @@ from .tools.geoipresolver import GeoIPResolver
|
||||
from .tools.func import filter_kwargs, lazy_property
|
||||
from .tools.mimetypes import guess_mimetype
|
||||
from .tools._vendor import sessions
|
||||
from .tools._vendor.useragents import UserAgent
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -888,7 +889,7 @@ class Response(werkzeug.wrappers.Response):
|
||||
_logger.warning("%s returns an HTTPException instead of raising it.", fname)
|
||||
raise result
|
||||
|
||||
if isinstance(result, werkzeug.wrappers.BaseResponse):
|
||||
if isinstance(result, werkzeug.wrappers.Response):
|
||||
response = cls.force_type(result)
|
||||
response.set_default()
|
||||
return response
|
||||
@@ -1752,6 +1753,7 @@ class Application:
|
||||
return response(environ, start_response)
|
||||
|
||||
httprequest = werkzeug.wrappers.Request(environ)
|
||||
httprequest.user_agent_class = UserAgent # use vendored userAgent since it will be removed in 2.1
|
||||
httprequest.parameter_storage_class = (
|
||||
werkzeug.datastructures.ImmutableOrderedMultiDict)
|
||||
request = Request(httprequest)
|
||||
|
||||
@@ -62,7 +62,7 @@ def load_data(cr, idref, mode, kind, package):
|
||||
filename = None
|
||||
try:
|
||||
if kind in ('demo', 'test'):
|
||||
threading.currentThread().testing = True
|
||||
threading.current_thread().testing = True
|
||||
for filename in _get_files_of_kind(kind):
|
||||
_logger.info("loading %s/%s", package.name, filename)
|
||||
noupdate = False
|
||||
@@ -71,7 +71,7 @@ def load_data(cr, idref, mode, kind, package):
|
||||
tools.convert_file(cr, package.name, filename, idref, mode, noupdate, kind)
|
||||
finally:
|
||||
if kind in ('demo', 'test'):
|
||||
threading.currentThread().testing = False
|
||||
threading.current_thread().testing = False
|
||||
|
||||
return bool(filename)
|
||||
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ def init_logger():
|
||||
warnings.simplefilter('default', category=DeprecationWarning)
|
||||
# ignore deprecation warnings from invalid escape (there's a ton and it's
|
||||
# pretty likely a super low-value signal)
|
||||
warnings.filterwarnings('ignore', r'^invalid escape sequence \\.', category=DeprecationWarning)
|
||||
warnings.filterwarnings('ignore', r'^invalid escape sequence \'?\\.', category=DeprecationWarning)
|
||||
# recordsets are both sequence and set so trigger warning despite no issue
|
||||
warnings.filterwarnings('ignore', r'^Sampling from a set', category=DeprecationWarning, module='odoo')
|
||||
# ignore a bunch of warnings we can't really fix ourselves
|
||||
|
||||
+12
-12
@@ -123,7 +123,7 @@ class RequestHandler(werkzeug.serving.WSGIRequestHandler):
|
||||
self.timeout = 5
|
||||
# flag the current thread as handling a http request
|
||||
super(RequestHandler, self).setup()
|
||||
me = threading.currentThread()
|
||||
me = threading.current_thread()
|
||||
me.name = 'odoo.service.http.request.%s' % (me.ident,)
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ class FSWatcherInotify(FSWatcherBase):
|
||||
def start(self):
|
||||
self.started = True
|
||||
self.thread = threading.Thread(target=self.run, name="odoo.service.autoreload.watcher")
|
||||
self.thread.setDaemon(True)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
@@ -350,7 +350,7 @@ class CommonServer(object):
|
||||
class ThreadedServer(CommonServer):
|
||||
def __init__(self, app):
|
||||
super(ThreadedServer, self).__init__(app)
|
||||
self.main_thread_id = threading.currentThread().ident
|
||||
self.main_thread_id = threading.current_thread().ident
|
||||
# Variable keeping track of the number of calls to the signal handler defined
|
||||
# below. This variable is monitored by ``quit_on_signals()``.
|
||||
self.quit_signals_received = 0
|
||||
@@ -385,7 +385,7 @@ class ThreadedServer(CommonServer):
|
||||
memory = memory_info(psutil.Process(os.getpid()))
|
||||
if config['limit_memory_soft'] and memory > config['limit_memory_soft']:
|
||||
_logger.warning('Server memory limit (%s) reached.', memory)
|
||||
self.limits_reached_threads.add(threading.currentThread())
|
||||
self.limits_reached_threads.add(threading.current_thread())
|
||||
|
||||
for thread in threading.enumerate():
|
||||
if not thread.daemon or getattr(thread, 'type', None) == 'cron':
|
||||
@@ -448,7 +448,7 @@ class ThreadedServer(CommonServer):
|
||||
_logger.debug('cron%d polling for jobs', number)
|
||||
for db_name, registry in registries.d.items():
|
||||
if registry.ready:
|
||||
thread = threading.currentThread()
|
||||
thread = threading.current_thread()
|
||||
thread.start_time = time.time()
|
||||
try:
|
||||
ir_cron._process_jobs(db_name)
|
||||
@@ -472,7 +472,7 @@ class ThreadedServer(CommonServer):
|
||||
def target():
|
||||
self.cron_thread(i)
|
||||
t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i)
|
||||
t.setDaemon(True)
|
||||
t.daemon = True
|
||||
t.type = 'cron'
|
||||
t.start()
|
||||
_logger.debug("cron%d started!" % i)
|
||||
@@ -483,7 +483,7 @@ class ThreadedServer(CommonServer):
|
||||
|
||||
def http_spawn(self):
|
||||
t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
|
||||
t.setDaemon(True)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def start(self, stop=False):
|
||||
@@ -525,11 +525,11 @@ class ThreadedServer(CommonServer):
|
||||
# Manually join() all threads before calling sys.exit() to allow a second signal
|
||||
# to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
|
||||
# threading.Thread.join() should not mask signals (at least in python 2.5).
|
||||
me = threading.currentThread()
|
||||
me = threading.current_thread()
|
||||
_logger.debug('current thread: %r', me)
|
||||
for thread in threading.enumerate():
|
||||
_logger.debug('process %r (%r)', thread, thread.isDaemon())
|
||||
if (thread != me and not thread.isDaemon() and thread.ident != self.main_thread_id and
|
||||
_logger.debug('process %r (%r)', thread, thread.daemon)
|
||||
if (thread != me and not thread.daemon and thread.ident != self.main_thread_id and
|
||||
thread not in self.limits_reached_threads):
|
||||
while thread.is_alive() and (time.time() - stop_time) < 1:
|
||||
# We wait for requests to finish, up to 1 second.
|
||||
@@ -1217,7 +1217,7 @@ def _reexec(updated_modules=None):
|
||||
|
||||
def load_test_file_py(registry, test_file):
|
||||
from odoo.tests.common import OdooSuite
|
||||
threading.currentThread().testing = True
|
||||
threading.current_thread().testing = True
|
||||
try:
|
||||
test_path, _ = os.path.splitext(os.path.abspath(test_file))
|
||||
for mod in [m for m in get_modules() if '%s%s%s' % (os.path.sep, m, os.path.sep) in test_file]:
|
||||
@@ -1233,7 +1233,7 @@ def load_test_file_py(registry, test_file):
|
||||
_logger.error('%s: at least one error occurred in a test', test_file)
|
||||
return
|
||||
finally:
|
||||
threading.currentThread().testing = False
|
||||
threading.current_thread().testing = False
|
||||
|
||||
def preload_registries(dbnames):
|
||||
""" Preload a registries, possibly run a test file."""
|
||||
|
||||
@@ -72,12 +72,12 @@ def run_suite(suite, module_name=None):
|
||||
# avoid dependency hell
|
||||
from ..modules import module
|
||||
module.current_test = module_name
|
||||
threading.currentThread().testing = True
|
||||
threading.current_thread().testing = True
|
||||
|
||||
results = OdooTestResult()
|
||||
suite(results)
|
||||
|
||||
threading.currentThread().testing = False
|
||||
threading.current_thread().testing = False
|
||||
module.current_test = None
|
||||
return results
|
||||
|
||||
|
||||
@@ -20,14 +20,13 @@ import os
|
||||
import re
|
||||
import tempfile
|
||||
from hashlib import sha1
|
||||
from os import path
|
||||
from os import path, replace as rename
|
||||
from pickle import dump
|
||||
from pickle import HIGHEST_PROTOCOL
|
||||
from pickle import load
|
||||
from time import time
|
||||
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.posixemulation import rename
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_sha1_re = re.compile(r"^[a-f0-9]{40}$")
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.useragents
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a helper to inspect user agent strings. This module
|
||||
is far from complete but should work for most of the currently available
|
||||
browsers.
|
||||
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
|
||||
This package was vendored in odoo in order to prevent errors with werkzeug 2.1
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
class UserAgentParser(object):
|
||||
"""A simple user agent parser. Used by the `UserAgent`."""
|
||||
|
||||
platforms = (
|
||||
("cros", "chromeos"),
|
||||
("iphone|ios", "iphone"),
|
||||
("ipad", "ipad"),
|
||||
(r"darwin|mac|os\s*x", "macos"),
|
||||
("win", "windows"),
|
||||
(r"android", "android"),
|
||||
("netbsd", "netbsd"),
|
||||
("openbsd", "openbsd"),
|
||||
("freebsd", "freebsd"),
|
||||
("dragonfly", "dragonflybsd"),
|
||||
("(sun|i86)os", "solaris"),
|
||||
(r"x11|lin(\b|ux)?", "linux"),
|
||||
(r"nintendo\s+wii", "wii"),
|
||||
("irix", "irix"),
|
||||
("hp-?ux", "hpux"),
|
||||
("aix", "aix"),
|
||||
("sco|unix_sv", "sco"),
|
||||
("bsd", "bsd"),
|
||||
("amiga", "amiga"),
|
||||
("blackberry|playbook", "blackberry"),
|
||||
("symbian", "symbian"),
|
||||
)
|
||||
browsers = (
|
||||
("googlebot", "google"),
|
||||
("msnbot", "msn"),
|
||||
("yahoo", "yahoo"),
|
||||
("ask jeeves", "ask"),
|
||||
(r"aol|america\s+online\s+browser", "aol"),
|
||||
("opera", "opera"),
|
||||
("edge", "edge"),
|
||||
("chrome|crios", "chrome"),
|
||||
("seamonkey", "seamonkey"),
|
||||
("firefox|firebird|phoenix|iceweasel", "firefox"),
|
||||
("galeon", "galeon"),
|
||||
("safari|version", "safari"),
|
||||
("webkit", "webkit"),
|
||||
("camino", "camino"),
|
||||
("konqueror", "konqueror"),
|
||||
("k-meleon", "kmeleon"),
|
||||
("netscape", "netscape"),
|
||||
(r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"),
|
||||
("lynx", "lynx"),
|
||||
("links", "links"),
|
||||
("Baiduspider", "baidu"),
|
||||
("bingbot", "bing"),
|
||||
("mozilla", "mozilla"),
|
||||
)
|
||||
|
||||
_browser_version_re = r"(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?"
|
||||
_language_re = re.compile(
|
||||
r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|"
|
||||
r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms]
|
||||
self.browsers = [
|
||||
(b, re.compile(self._browser_version_re % a, re.I))
|
||||
for a, b in self.browsers
|
||||
]
|
||||
|
||||
def __call__(self, user_agent):
|
||||
for platform, regex in self.platforms: # noqa: B007
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
break
|
||||
else:
|
||||
platform = None
|
||||
for browser, regex in self.browsers: # noqa: B007
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
version = match.group(1)
|
||||
break
|
||||
else:
|
||||
browser = version = None
|
||||
match = self._language_re.search(user_agent)
|
||||
if match is not None:
|
||||
language = match.group(1) or match.group(2)
|
||||
else:
|
||||
language = None
|
||||
return platform, browser, version, language
|
||||
|
||||
|
||||
class UserAgent(object):
|
||||
"""Represents a user agent. Pass it a WSGI environment or a user agent
|
||||
string and you can inspect some of the details from the user agent
|
||||
string via the attributes. The following attributes exist:
|
||||
|
||||
.. attribute:: string
|
||||
|
||||
the raw user agent string
|
||||
|
||||
.. attribute:: platform
|
||||
|
||||
the browser platform. The following platforms are currently
|
||||
recognized:
|
||||
|
||||
- `aix`
|
||||
- `amiga`
|
||||
- `android`
|
||||
- `blackberry`
|
||||
- `bsd`
|
||||
- `chromeos`
|
||||
- `dragonflybsd`
|
||||
- `freebsd`
|
||||
- `hpux`
|
||||
- `ipad`
|
||||
- `iphone`
|
||||
- `irix`
|
||||
- `linux`
|
||||
- `macos`
|
||||
- `netbsd`
|
||||
- `openbsd`
|
||||
- `sco`
|
||||
- `solaris`
|
||||
- `symbian`
|
||||
- `wii`
|
||||
- `windows`
|
||||
|
||||
.. attribute:: browser
|
||||
|
||||
the name of the browser. The following browsers are currently
|
||||
recognized:
|
||||
|
||||
- `aol` *
|
||||
- `ask` *
|
||||
- `baidu` *
|
||||
- `bing` *
|
||||
- `camino`
|
||||
- `chrome`
|
||||
- `edge`
|
||||
- `firefox`
|
||||
- `galeon`
|
||||
- `google` *
|
||||
- `kmeleon`
|
||||
- `konqueror`
|
||||
- `links`
|
||||
- `lynx`
|
||||
- `mozilla`
|
||||
- `msie`
|
||||
- `msn`
|
||||
- `netscape`
|
||||
- `opera`
|
||||
- `safari`
|
||||
- `seamonkey`
|
||||
- `webkit`
|
||||
- `yahoo` *
|
||||
|
||||
(Browsers marked with a star (``*``) are crawlers.)
|
||||
|
||||
.. attribute:: version
|
||||
|
||||
the version of the browser
|
||||
|
||||
.. attribute:: language
|
||||
|
||||
the language of the browser
|
||||
"""
|
||||
|
||||
_parser = UserAgentParser()
|
||||
|
||||
def __init__(self, environ_or_string):
|
||||
if isinstance(environ_or_string, dict):
|
||||
environ_or_string = environ_or_string.get("HTTP_USER_AGENT", "")
|
||||
self.string = environ_or_string
|
||||
self.platform, self.browser, self.version, self.language = self._parser(
|
||||
environ_or_string
|
||||
)
|
||||
|
||||
def to_header(self):
|
||||
return self.string
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.browser)
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version)
|
||||
+1
-1
@@ -201,7 +201,7 @@ def log_ormcache_stats(sig=None, frame=None):
|
||||
from odoo.modules.registry import Registry
|
||||
import threading
|
||||
|
||||
me = threading.currentThread()
|
||||
me = threading.current_thread()
|
||||
me_dbname = getattr(me, 'dbname', 'n/a')
|
||||
|
||||
for dbname, reg in sorted(Registry.registries.d.items()):
|
||||
|
||||
@@ -177,5 +177,5 @@ def randdatetime(*, base_date=None, relative_before=None, relative_after=None, s
|
||||
seconds_after = relative_after and ((base_date + relative_after) - base_date).total_seconds() or 0
|
||||
|
||||
def get_rand_datetime(random=None, **kwargs):
|
||||
return base_date + relativedelta(seconds=random.randint(seconds_before, seconds_after))
|
||||
return base_date + relativedelta(seconds=random.randint(int(seconds_before), int(seconds_after)))
|
||||
return compute(get_rand_datetime, seed=seed)
|
||||
|
||||
@@ -86,6 +86,8 @@ _EXPR_OPCODES = _CONST_OPCODES.union(to_opcodes([
|
||||
# specialised comparisons
|
||||
'IS_OP', 'CONTAINS_OP',
|
||||
'DICT_MERGE', 'DICT_UPDATE',
|
||||
# Basically used in any "generator literal"
|
||||
'GEN_START', # added in 3.10 but already removed from 3.11.
|
||||
])) - _BLACKLIST
|
||||
|
||||
_SAFE_OPCODES = _EXPR_OPCODES.union(to_opcodes([
|
||||
|
||||
@@ -361,7 +361,7 @@ class GettextAlias(object):
|
||||
|
||||
def _get_db(self):
|
||||
# find current DB based on thread/worker db name (see netsvc)
|
||||
db_name = getattr(threading.currentThread(), 'dbname', None)
|
||||
db_name = getattr(threading.current_thread(), 'dbname', None)
|
||||
if db_name:
|
||||
return odoo.sql_db.db_connect(db_name)
|
||||
|
||||
|
||||
+8
-4
@@ -6,16 +6,19 @@ ebaysdk==2.1.5
|
||||
freezegun==0.3.11; python_version < '3.8'
|
||||
freezegun==0.3.15; python_version >= '3.8'
|
||||
gevent==1.5.0 ; python_version == '3.7'
|
||||
gevent==20.9.0 ; python_version >= '3.8'
|
||||
gevent==20.9.0 ; python_version > '3.7' and python_version <= '3.9'
|
||||
gevent==21.8.0 ; python_version > '3.9' # (Jammy)
|
||||
greenlet==0.4.15 ; python_version == '3.7'
|
||||
greenlet==0.4.17 ; python_version > '3.7'
|
||||
greenlet==0.4.17 ; python_version > '3.7' and python_version <= '3.9'
|
||||
greenlet==1.1.2 ; python_version > '3.9' # (Jammy)
|
||||
idna==2.8
|
||||
Jinja2==2.11.3 # min version = 2.10.1 (Focal - with security backports)
|
||||
libsass==0.18.0
|
||||
lxml==4.6.5 # min version = 4.5.0 (Focal - with security backports)
|
||||
MarkupSafe==1.1.0
|
||||
num2words==0.5.6
|
||||
ofxparse==0.19
|
||||
ofxparse==0.19; python_version <= '3.9'
|
||||
ofxparse==0.21; python_version > '3.9' # (Jammy)
|
||||
passlib==1.7.3 # min version = 1.7.2 (Focal with security backports)
|
||||
Pillow==9.0.1 # min version = 7.0.0 (Focal with security backports)
|
||||
polib==1.1.0
|
||||
@@ -37,7 +40,8 @@ reportlab==3.5.59 # version < 3.5.54 are not compatible with Pillow 8.1.2 and 3.
|
||||
requests==2.25.1 # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal)
|
||||
urllib3==1.26.5 # indirect / min version = 1.25.8 (Focal with security backports)
|
||||
vobject==0.9.6.1
|
||||
Werkzeug==0.16.1
|
||||
Werkzeug==0.16.1 ; python_version <= '3.9'
|
||||
Werkzeug==2.0.2 ; python_version > '3.9' # (Jammy)
|
||||
xlrd==1.1.0; python_version < '3.8'
|
||||
xlrd==1.2.0; python_version >= '3.8'
|
||||
XlsxWriter==1.1.2
|
||||
|
||||
Reference in New Issue
Block a user