[IMP] *: new asset for test files, new debug mode (stored in session)

This commit goal is to encapsulate every tour-test into a separate assets
bundle that would be called only during tests (command line) or by URL
(debug=tests). That way, a lot of .js files would not be loaded anymore
uselessly outside test mode and will speed up the page loads (especially in the
frontend as the backend do not reload the page anyway).

In order to do that, we needed to propagate the `debug` state from page to
page.
Otherwise, every page change during a test would simply lose the debug mode and
the test would stop as the test assets would not be loaded.
As we could not add the `&debug=tests` on every link (either hardcoded or
preprocess during the rendering), it has been decided to store it in session.

Technical summary:
1. `debug` state (currently only stored in URL) will be stored in session.
   Either when adding the `debug` param in URL (handled with _dispatch) or by
   starting Odoo with `test-enable` or `test-file` (handled by session init).
2. Once activated (and so set in session), debug mode will remain activated
   even if not visible in URL (after a page navigation eg).
   To deactivate it, set its value to nothing, `debug=`. That will exit debug mode
   (whatever mode it is: debug, assets, tests).
3. As tour-test files are now in a separate bundle, every layout (when needed)
   should `t-call="web.conditional_assets_tests"`.
   As it would be redundant and verbose, no `t-if` is needed on the t-call to
   load it only in test debug mode. That will be handled by
   `compiled_assets_tests` that will actually do the conditionnal t-call-assets
   to web.assets_tests, if tests debug mode is activated.
4. In addition to separating the tour-tests files in a separate bundle, we also
   moved those files to a specific folder under /static/tests/tours next to
   QUnit tests.
5. Also, tour files will be moved in a specific folder /static/src/js/tours for
   cleanness purpose (those files will still be kept in their 'normal' assets
   as needed outside test mode since it is tours).
   This will be done in the next commit.
6. It is possible to enable multiple debug mode, such as 'tests' and 'assets'
   together. Simply separate debug modes with a comma, eg '?debug=assets,tests'

task-1934445
Comes with https://github.com/odoo/enterprise/pull/4281
Closes #33213
This commit is contained in:
Romain Derie
2019-06-05 05:56:33 +00:00
committed by Jeremy Kersten
parent 2fab076e84
commit d7cd97a9be
19 changed files with 91 additions and 63 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ class Proxy(http.Controller):
<p>The list of connected USB devices as seen by the IoTBox</p>
"""
if debug is None:
resp += """(<a href="/hw_proxy/status?debug">debug version</a>)"""
resp += """(<a href="/hw_proxy/status?debug=1">debug version</a>)"""
devices = subprocess.check_output("lsusb").decode('utf-8').split('\n')
count = 0
resp += "<div class='devices'>\n"
+1 -1
View File
@@ -12,7 +12,7 @@ _logger = logging.getLogger(__name__)
class PosController(http.Controller):
@http.route('/pos/web', type='http', auth='user')
def pos_web(self, debug=False, **k):
def pos_web(self, **k):
# if user not logged in, log him in
pos_sessions = request.env['pos.session'].search([
('state', '=', 'opened'),
@@ -31,12 +31,14 @@
var odoo = {
csrf_token: "<t t-esc="request.csrf_token(None)"/>",
session_info: <t t-raw="session_info"/>,
debug: "<t t-esc="debug"/>",
};
</script>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="point_of_sale.pos_assets_backend" t-css="false"/>
<t t-call-assets="point_of_sale.assets"/>
<t t-call="web.conditional_assets_tests"/>
<script type="text/javascript" id="loading-script" t-raw="init">
odoo.define('web.web_client', function (require) {
+1 -1
View File
@@ -10,7 +10,7 @@ import json
class BarcodeController(http.Controller):
@http.route(['/stock/barcode/'], type='http', auth='user')
def a(self, debug=False, **k):
def a(self, **k):
if not request.session.uid:
return http.local_redirect('/web/login?redirect=/stock/barcode/')
+1 -1
View File
@@ -5,7 +5,7 @@ var tour = require('web_tour.tour');
tour.register('stock', {
test: true,
url: '/web?debug=assets#action=stock.action_stock_config_settings',
url: '/web#action=stock.action_stock_config_settings',
},
[
{
+2 -9
View File
@@ -485,7 +485,7 @@ class Home(http.Controller):
: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.debug)
menus = request.env["ir.ui.menu"].load_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
@@ -542,12 +542,6 @@ class Home(http.Controller):
if not odoo.tools.config['list_db']:
values['disable_database_manager'] = True
# otherwise no real way to test debug mode in template as ?debug =>
# values['debug'] = '' but that's also the fallback value when
# missing variables in qweb
if 'debug' in values:
values['debug'] = True
response = request.render('web.login', values)
response.headers['X-Frame-Options'] = 'DENY'
return response
@@ -1590,8 +1584,7 @@ class Apps(http.Controller):
action['views'] = [(False, u'form')]
sakey = Session().save_session_action(action)
debug = '?debug' if req.debug else ''
return werkzeug.utils.redirect('/web{0}#sa={1}'.format(debug, sakey))
return werkzeug.utils.redirect('/web#sa={0}'.format(sakey))
class ReportController(http.Controller):
+2 -2
View File
@@ -17,7 +17,7 @@ class Http(models.AbstractModel):
def webclient_rendering_context(self):
return {
'menu_data': request.env['ir.ui.menu'].load_menus(request.debug),
'menu_data': request.env['ir.ui.menu'].load_menus(request.session.debug),
'session_info': json.dumps(self.session_info()),
}
@@ -34,7 +34,7 @@ class Http(models.AbstractModel):
lang = user_context.get("lang")
translations_per_module, _ = request.env['ir.translation'].get_translations_for_webclient(mods, lang)
menu_json_utf8 = json.dumps(request.env['ir.ui.menu'].load_menus(request.debug), default=ustr, sort_keys=True).encode()
menu_json_utf8 = json.dumps(request.env['ir.ui.menu'].load_menus(request.session.debug), default=ustr, sort_keys=True).encode()
translations_json_utf8 = json.dumps(translations_per_module, sort_keys=True).encode()
return {
+11
View File
@@ -387,6 +387,14 @@
<xpath expr="//t[@t-call='web._assets_frontend_minimal_js']" position="replace"/>
</template>
<!-- Do not direcly call this template, call 'conditional_assets_tests' instead -->
<template id="assets_tests" name="Tests Assets">
</template>
<template id="conditional_assets_tests" name="Tests Assets Bundle">
<t t-call-assets="web.assets_tests" t-if="'tests' in debug" defer_load="True" />
</template>
<template id="web.layout" name="Web layout">&lt;!DOCTYPE html&gt;
<html t-att="html_data or {}">
<head>
@@ -400,6 +408,7 @@
<script type="text/javascript">
var odoo = {
csrf_token: "<t t-esc="request.csrf_token(None)"/>",
debug: "<t t-esc="debug"/>",
};
</script>
@@ -422,6 +431,7 @@
</script>
<t t-call-assets="web.assets_common_minimal_js" t-css="false" defer_load="True"/>
<t t-call-assets="web.assets_frontend_minimal_js" t-css="false" defer_load="True"/>
<t t-call="web.conditional_assets_tests"/>
<t t-call-assets="web.assets_common_lazy" t-css="false" lazy_load="True"/>
<t t-call-assets="web.assets_frontend_lazy" t-css="false" lazy_load="True"/>
</xpath>
@@ -840,6 +850,7 @@
<t t-call-assets="web.assets_backend" t-js="false"/>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="web.assets_backend" t-css="false"/>
<t t-call="web.conditional_assets_tests"/>
<![CDATA[
<!--[if lt IE 10]>
@@ -73,7 +73,7 @@ class WebSettingsDashboard(http.Controller):
'share': {
'server_version': release.version,
'expiration_date': expiration_date,
'debug': request.debug,
'debug': request.session.debug,
'demo_active': demo_active,
},
'company': {
@@ -123,13 +123,13 @@
</div>
</div>
<hr/>
<a t-if="debug != true" class="oe_activate_debug_mode float-right" href="?debug" >Activate the developer mode</a>
<br t-if="debug != true"/>
<a t-if="debug != 'assets'" class="oe_activate_debug_mode float-right" href="?debug=assets" >Activate the developer mode (with assets)</a>
<br t-if="debug != 'assets'"/>
<a t-if="debug != false" class="oe_activate_debug_mode float-right" href="/web" >Deactivate the developer mode</a>
<br t-if="debug != false"/>
<a t-if="(debug) and !(demo_active)" class="oe_activate_debug_mode float-right o_web_settings_dashboard_force_demo" href="#">Load demo data</a>
<div class="text-right">
<a t-if="debug.indexOf('1') == -1" class="d-block" href="?debug=1">Activate the developer mode</a>
<a t-if="debug.indexOf('assets') == -1" class="d-block" href="?debug=assets">Activate the developer mode (with assets)</a>
<a t-if="debug.indexOf('tests') == -1" class="d-block" href="?debug=assets,tests">Activate the developer mode (with tests assets)</a>
<a t-if="debug" class="d-block" href="?debug=">Deactivate the developer mode</a>
<a t-if="debug and !demo_active" class="o_web_settings_dashboard_force_demo" href="#">Load demo data</a>
</div>
</div>
</t>
+1 -1
View File
@@ -44,7 +44,7 @@ class QWeb(models.AbstractModel):
atts = super(QWeb, self)._post_processing_att(tagName, atts, options)
if options.get('inherit_branding') or options.get('rendering_bundle') or \
options.get('edit_translations') or options.get('debug') or (request and request.debug):
options.get('edit_translations') or options.get('debug') or (request and request.session.debug):
return atts
website = request and getattr(request, 'website', None)
+4 -1
View File
@@ -93,7 +93,10 @@ class MockRequest(object):
if not routing:
app.match = werkzeugRaiseNotFound
self.request = MockObject(
env=env, context=context or {}, db=None, debug=False,
env=env, context=context or {}, db=None,
session=MockObject(
debug=False
),
website=website, httprequest=MockObject(
path='/hello/',
app=app
+4 -4
View File
@@ -904,7 +904,7 @@
<div class="oe_structure">
<h1 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h1>
</div>
<t t-if="editable or request.debug">
<t t-if="editable or debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
@@ -969,7 +969,7 @@
<li><a href="/contactus">Contact Us</a></li>
</ul>
</div>
<t t-if="editable or request.debug">
<t t-if="editable or debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
@@ -996,7 +996,7 @@
</div>
</div>
<t t-if="request.debug">
<t t-if="debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
@@ -1123,7 +1123,7 @@
</div>
</div>
<t t-if="editable or request.debug">
<t t-if="editable or debug">
<t t-call="website.http_error_debug"/>
</t>
</main>
+1 -1
View File
@@ -135,7 +135,7 @@ class AssetsBundle(object):
:returns [(tagName, attributes, content)] if the tag is auto close
"""
response = []
if debug == 'assets':
if debug and 'assets' in debug:
if css and self.stylesheets:
is_css_preprocessed, old_attachments = self.is_css_preprocessed()
if not is_css_preprocessed:
+23
View File
@@ -24,6 +24,9 @@ from odoo.tools import pycompat, consteq
from odoo.tools.mimetypes import guess_mimetype
from odoo.modules.module import get_resource_path, get_module_path
from odoo.http import ALLOWED_DEBUG_MODES
from odoo.tools.misc import str2bool
_logger = logging.getLogger(__name__)
@@ -125,6 +128,21 @@ class IrHttp(models.AbstractModel):
raise AccessDenied()
return auth_method
@classmethod
def _handle_debug(cls):
# Store URL debug mode (might be empty) into session
if 'debug' in request.httprequest.args:
debug_mode = []
for debug in request.httprequest.args['debug'].split(','):
if debug not in ALLOWED_DEBUG_MODES:
debug = '1' if str2bool(debug, debug) else ''
debug_mode.append(debug)
debug_mode = ','.join(debug_mode)
# Write on session only when needed
if debug_mode != request.session.debug:
request.session.debug = debug_mode
@classmethod
def _serve_attachment(cls):
env = api.Environment(request.cr, SUPERUSER_ID, request.context)
@@ -162,6 +180,9 @@ class IrHttp(models.AbstractModel):
@classmethod
def _handle_exception(cls, exception):
# in case of Exception, e.g. 404, we don't step into _dispatch
cls._handle_debug()
# If handle_exception returns something different than None, it will be used as a response
# This is done first as the attachment path may
@@ -182,6 +203,8 @@ class IrHttp(models.AbstractModel):
@classmethod
def _dispatch(cls):
cls._handle_debug()
# locate the controller method
try:
rule, arguments = cls._find_handler(return_rule=True)
+1 -1
View File
@@ -122,7 +122,7 @@ class IrUiMenu(models.Model):
the menu hierarchy of the current user.
Uses a cache for speeding up the computation.
"""
visible_ids = self._visible_menu_ids(request.debug if request else False)
visible_ids = self._visible_menu_ids(request.session.debug if request else False)
return self.filtered(lambda menu: menu.id in visible_ids)
@api.model
+1 -1
View File
@@ -1356,7 +1356,7 @@ actual arch.
res_company=self.env.company.sudo(),
keep_query=keep_query,
request=request, # might be unbound if we're not in an httprequest context
debug=request.debug if request else False,
debug=request.session.debug if request else '',
json=json,
quote_plus=werkzeug.url_quote_plus,
time=time,
+25 -29
View File
@@ -64,6 +64,21 @@ STATIC_CACHE = 60 * 60 * 24 * 7
# To remove when corrected in Babel
babel.core.LOCALE_ALIASES['nb'] = 'nb_NO'
""" Debug mode is stored in session and should always be a string.
It can be activated with an URL query string `debug=<mode>` where
mode is either:
- 'tests' to load tests assets
- 'assets' to load assets non minified
- any other truthy value to enable simple debug mode (to show some
technical feature, to show complete traceback in frontend error..)
- any falsy value to disable debug mode
You can use any truthy/falsy value from `str2bool` (eg: 'on', 'f'..)
Multiple debug modes can be activated simultaneously, separated with
a comma (eg: 'tests, assets').
"""
ALLOWED_DEBUG_MODES = ['', '1', 'assets', 'tests']
#----------------------------------------------------------
# RequestHandler
#----------------------------------------------------------
@@ -143,15 +158,10 @@ def dispatch_rpc(service_name, method, params):
odoo.tools.debugger.post_mortem(odoo.tools.config, sys.exc_info())
raise
def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
def local_redirect(path, query=None, keep_hash=False, code=303):
url = path
if not query:
query = {}
if request and request.debug:
if forward_debug:
query['debug'] = ''
else:
query['debug'] = None
if query:
url += '?' + werkzeug.url_encode(query)
if keep_hash:
@@ -345,22 +355,6 @@ class WebRequest(object):
return checked_call(self.db, *args, **kwargs)
return self.endpoint(*args, **kwargs)
@property
def debug(self):
""" Indicates whether the current request is in "debug" mode
"""
debug = 'debug' in self.httprequest.args
if debug and self.httprequest.args.get('debug') == 'assets':
debug = 'assets'
# check if request from rpc in debug mode
if not debug:
debug = self.httprequest.environ.get('HTTP_X_DEBUG_MODE')
if not debug and self.httprequest.referrer:
debug = 'debug' in urls.url_parse(self.httprequest.referrer).decode_query()
return debug
@contextlib.contextmanager
def registry_cr(self):
warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
@@ -763,7 +757,7 @@ class HttpRequest(WebRequest):
if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
headers = {
'Access-Control-Max-Age': 60 * 60 * 24,
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode'
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
}
return Response(status=200, headers=headers)
@@ -1038,7 +1032,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
def logout(self, keep_db=False):
for k in list(self):
if not (keep_db and k == 'db'):
if not (keep_db and k == 'db') and k != 'debug':
del self[k]
self._default_values()
self.rotate = True
@@ -1049,6 +1043,9 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.setdefault("login", None)
self.setdefault("session_token", None)
self.setdefault("context", {})
# Force tests debug mode when test mode enabled to load 'assets_tests'
test_mode = odoo.tools.config['test_enable'] or odoo.tools.config['test_file']
self.setdefault("debug", 'tests' if test_mode else '')
def get_context(self):
"""
@@ -1242,13 +1239,12 @@ class Response(werkzeug.wrappers.Response):
class DisableCacheMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
def start_wrapped(status, headers):
referer = environ.get('HTTP_REFERER', '')
parsed = urls.url_parse(referer)
debug = parsed.query.count('debug') >= 1
if debug:
req = werkzeug.wrappers.Request(environ)
root.setup_session(req)
if req.session and req.session.debug:
new_headers = [('Cache-Control', 'no-cache')]
for k, v in headers:
+2 -2
View File
@@ -1237,7 +1237,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
for group_ext_id in not_has_groups:
if group_ext_id == 'base.group_no_one':
# check: the group_no_one is effective in debug mode only
if user.has_group(group_ext_id) and request and request.debug:
if user.has_group(group_ext_id) and request and request.session.debug:
return False
else:
if user.has_group(group_ext_id):
@@ -1246,7 +1246,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
for group_ext_id in has_groups:
if group_ext_id == 'base.group_no_one':
# check: the group_no_one is effective in debug mode only
if user.has_group(group_ext_id) and request and request.debug:
if user.has_group(group_ext_id) and request and request.session.debug:
return True
else:
if user.has_group(group_ext_id):