Files
odoo_source/addons/website/tools.py
T
Romain Derie 975e8f911e [FIX] website: set db_name on MockRequest
Commit [1] broke the standalone tour of website's themes, which is run
in nightly only (sadly).
That tour was added a few months ago with [2] which was tested the fix
done by [3].

[1]: https://github.com/odoo/odoo/commit/fcf6e462e116d13533014e31d4191763354811cf
[2]: https://github.com/odoo/design-themes/commit/0e324061d1c4437ef19d07f23eaf5663cb8fe657
[3]: https://github.com/odoo/odoo/commit/bed3cd54a5c7baeab173129036246bef543eef94

closes odoo/odoo#102670

X-original-commit: 326d33f129d73ed2b703b3a7173386e99f88b4d8
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
2022-10-07 17:48:09 +02:00

170 lines
5.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
import re
from lxml import etree
from unittest.mock import Mock, MagicMock, patch
from werkzeug.exceptions import NotFound
from werkzeug.test import EnvironBuilder
import odoo
from odoo.tests.common import HttpCase, HOST
from odoo.tools.misc import DotDict, frozendict
@contextlib.contextmanager
def MockRequest(
env, *, path='/mockrequest', routing=True, multilang=True,
context=frozendict(), cookies=frozendict(), country_code=None,
website=None, remote_addr=HOST, environ_base=None,
# website_sale
sale_order_id=None, website_sale_current_pl=None,
):
lang_code = context.get('lang', env.context.get('lang', 'en_US'))
env = env(context=dict(context, lang=lang_code))
request = Mock(
# request
httprequest=Mock(
host='localhost',
path=path,
app=odoo.http.root,
environ=dict(
EnvironBuilder(
path=path,
base_url=HttpCase.base_url(),
environ_base=environ_base,
).get_environ(),
REMOTE_ADDR=remote_addr,
),
cookies=cookies,
referrer='',
remote_addr=remote_addr,
),
type='http',
future_response=odoo.http.FutureResponse(),
params={},
redirect=env['ir.http']._redirect,
session=DotDict(
odoo.http.get_default_session(),
geoip={'country_code': country_code},
sale_order_id=sale_order_id,
website_sale_current_pl=website_sale_current_pl,
),
geoip={},
db=env.registry.db_name,
env=env,
registry=env.registry,
cr=env.cr,
uid=env.uid,
context=env.context,
lang=env['res.lang']._lang_get(lang_code),
website=website,
)
if website:
request.website_routing = website.id
# The following code mocks match() to return a fake rule with a fake
# 'routing' attribute (routing=True) or to raise a NotFound
# exception (routing=False).
#
# router = odoo.http.root.get_db_router()
# rule, args = router.bind(...).match(path)
# # arg routing is True => rule.endpoint.routing == {...}
# # arg routing is False => NotFound exception
router = MagicMock()
match = router.return_value.bind.return_value.match
if routing:
match.return_value[0].routing = {
'type': 'http',
'website': True,
'multilang': multilang
}
else:
match.side_effect = NotFound
def update_context(**overrides):
request.context = dict(request.context, **overrides)
request.update_context = update_context
with contextlib.ExitStack() as s:
odoo.http._request_stack.push(request)
s.callback(odoo.http._request_stack.pop)
s.enter_context(patch('odoo.http.root.get_db_router', router))
yield request
# Fuzzy matching tools
def distance(s1="", s2="", limit=4):
"""
Limited Levenshtein-ish distance (inspired from Apache text common)
Note: this does not return quick results for simple cases (empty string, equal strings)
those checks should be done outside loops that use this function.
:param s1: first string
:param s2: second string
:param limit: maximum distance to take into account, return -1 if exceeded
:return: number of character changes needed to transform s1 into s2 or -1 if this exceeds the limit
"""
BIG = 100000 # never reached integer
if len(s1) > len(s2):
s1, s2 = s2, s1
l1 = len(s1)
l2 = len(s2)
if l2 - l1 > limit:
return -1
boundary = min(l1, limit) + 1
p = [i if i < boundary else BIG for i in range(0, l1 + 1)]
d = [BIG for _ in range(0, l1 + 1)]
for j in range(1, l2 + 1):
j2 = s2[j - 1]
d[0] = j
range_min = max(1, j - limit)
range_max = min(l1, j + limit)
if range_min > 1:
d[range_min - 1] = BIG
for i in range(range_min, range_max + 1):
if s1[i - 1] == j2:
d[i] = p[i - 1]
else:
d[i] = 1 + min(d[i - 1], p[i], p[i - 1])
p, d = d, p
return p[l1] if p[l1] <= limit else -1
def similarity_score(s1, s2):
"""
Computes a score that describes how much two strings are matching.
:param s1: first string
:param s2: second string
:return: float score, the higher the more similar
pairs returning non-positive scores should be considered non similar
"""
dist = distance(s1, s2)
if dist == -1:
return -1
set1 = set(s1)
score = len(set1.intersection(s2)) / len(set1)
score -= dist / len(s1)
score -= len(set1.symmetric_difference(s2)) / (len(s1) + len(s2))
return score
def text_from_html(html_fragment, collapse_whitespace=False):
"""
Returns the plain non-tag text from an html
:param html_fragment: document from which text must be extracted
:return: text extracted from the html
"""
# lxml requires one single root element
tree = etree.fromstring('<p>%s</p>' % html_fragment, etree.XMLParser(recover=True))
content = ' '.join(tree.itertext())
if collapse_whitespace:
content = re.sub('\\s+', ' ', content).strip()
return content