diff options
| author | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2014-05-05 10:02:33 +0530 |
|---|---|---|
| committer | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2014-05-05 10:02:33 +0530 |
| commit | e41508cb8d7fd2a2c06e3fab9ce1503e8efa30fa (patch) | |
| tree | 8e0fbb849cebc9b3ac0f87355a5248f51826a491 /addons/web/controllers | |
| parent | 4312b921f3db41529cbd4f501a6b5dcba2bebd08 (diff) | |
| parent | 8fbebfd752fa8933584711522675d354b3217839 (diff) | |
[MERGE]Trunk.
bzr revid: vme@tinyerp.com-20140505043233-2t2e2vb1yv86hyxp
Diffstat (limited to 'addons/web/controllers')
| -rw-r--r-- | addons/web/controllers/__init__.py | 1 | ||||
| -rw-r--r-- | addons/web/controllers/main.py | 301 | ||||
| -rw-r--r-- | addons/web/controllers/testing.py | 159 |
3 files changed, 107 insertions, 354 deletions
diff --git a/addons/web/controllers/__init__.py b/addons/web/controllers/__init__.py index 74c27518ece..12a7e529b67 100644 --- a/addons/web/controllers/__init__.py +++ b/addons/web/controllers/__init__.py @@ -1,2 +1 @@ from . import main -from . import testing diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index f2101b27fd3..6dcc069fcc2 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -31,6 +31,7 @@ except ImportError: import openerp import openerp.modules.registry +from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound from openerp.tools.translate import _ from openerp import http @@ -52,50 +53,6 @@ env.filters["json"] = simplejson.dumps # OpenERP Web helpers #---------------------------------------------------------- -def rjsmin(script): - """ Minify js with a clever regex. - Taken from http://opensource.perlig.de/rjsmin - Apache License, Version 2.0 """ - def subber(match): - """ Substitution callback """ - groups = match.groups() - return ( - groups[0] or - groups[1] or - groups[2] or - groups[3] or - (groups[4] and '\n') or - (groups[5] and ' ') or - (groups[6] and ' ') or - (groups[7] and ' ') or - '' - ) - - result = re.sub( - r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' - r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' - r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]' - r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' - r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*' - r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*' - r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01' - r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/' - r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]' - r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./' - r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/' - r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01' - r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#' - r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-' - r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^' - r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|' - r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0' - r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0' - r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:' - r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*' - r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script - ).strip() - return result - db_list = http.db_list db_monodb = http.db_monodb @@ -309,45 +266,6 @@ def concat_xml(file_list): root.append(child) return ElementTree.tostring(root, 'utf-8'), checksum.hexdigest() -def concat_files(file_list, reader=None, intersperse=""): - """ Concatenates contents of all provided files - - :param list(str) file_list: list of files to check - :param function reader: reading procedure for each file - :param str intersperse: string to intersperse between file contents - :returns: (concatenation_result, checksum) - :rtype: (str, str) - """ - checksum = hashlib.new('sha1') - if not file_list: - return '', checksum.hexdigest() - - if reader is None: - def reader(f): - import codecs - with codecs.open(f, 'rb', "utf-8-sig") as fp: - return fp.read().encode("utf-8") - - files_content = [] - for fname in file_list: - contents = reader(fname) - checksum.update(contents) - files_content.append(contents) - - files_concat = intersperse.join(files_content) - return files_concat, checksum.hexdigest() - -concat_js_cache = {} - -def concat_js(file_list): - content, checksum = concat_files(file_list, intersperse=';') - if checksum in concat_js_cache: - content = concat_js_cache[checksum] - else: - content = rjsmin(content) - concat_js_cache[checksum] = content - return content, checksum - def fs2web(path): """convert FS path into web path""" return '/'.join(path.split(os.path.sep)) @@ -371,30 +289,17 @@ def manifest_glob(extension, addons=None, db=None, include_remotes=False): r.append((None, pattern)) else: for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))): - # Hack for IE, who limit 288Ko, 4095 rules, 31 sheets - # http://support.microsoft.com/kb/262161/en - if pattern == "static/lib/bootstrap/css/bootstrap.css": - if include_remotes: - r.insert(0, (None, fs2web(path[len(addons_path):]))) - else: - r.append((path, fs2web(path[len(addons_path):]))) + r.append((path, fs2web(path[len(addons_path):]))) return r -def manifest_list(extension, mods=None, db=None, debug=False): +def manifest_list(extension, mods=None, db=None, debug=None): """ list ressources to load specifying either: mods: a comma separated string listing modules db: a database name (return all installed modules in that database) """ + if debug is not None: + _logger.warning("openerp.addons.web.main.manifest_list(): debug parameter is deprecated") files = manifest_glob(extension, addons=mods, db=db, include_remotes=True) - if not debug: - path = '/web/webclient/' + extension - if mods is not None: - path += '?' + werkzeug.url_encode({'mods': mods}) - elif db: - path += '?' + werkzeug.url_encode({'db': db}) - - remotes = [wp for fp, wp in files if fp is None] - return [path] + remotes return [wp for _fp, wp in files] def get_last_modified(files): @@ -411,7 +316,7 @@ def get_last_modified(files): for f in files) return datetime.datetime(1970, 1, 1) -def make_conditional(response, last_modified=None, etag=None): +def make_conditional(response, last_modified=None, etag=None, max_age=0): """ Makes the provided response conditional based upon the request, and mandates revalidation from clients @@ -426,7 +331,7 @@ def make_conditional(response, last_modified=None, etag=None): :rtype: werkzeug.wrappers.Response """ response.cache_control.must_revalidate = True - response.cache_control.max_age = 0 + response.cache_control.max_age = max_age if last_modified: response.last_modified = last_modified if etag: @@ -603,6 +508,7 @@ html_template = """<!DOCTYPE html> <title>OpenERP</title> <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/> <link rel="stylesheet" href="/web/static/src/css/full.css" /> + %(css)s %(js)s <script type="text/javascript"> @@ -621,26 +527,16 @@ html_template = """<!DOCTYPE html> </html> """ -def render_bootstrap_template(template, values=None, debug=False, db=None, **kw): - if not db: - db = request.db - if request.debug: - debug = True +def render_bootstrap_template(template, values=None, **kw): if values is None: - values = {} - values['debug'] = debug - values['current_db'] = db + values = dict() try: values['databases'] = http.db_list() except openerp.exceptions.AccessDenied: values['databases'] = None - for res in ['js', 'css']: - if res not in values: - values[res] = manifest_list(res, db=db, debug=debug) - if 'modules' not in values: - values['modules'] = module_boot(db=db) + values['modules'] = module_boot() values['modules'] = simplejson.dumps(values['modules']) return request.render(template, values, **kw) @@ -682,9 +578,11 @@ class Home(http.Controller): redirect = '/web?' + request.httprequest.query_string values['redirect'] = redirect if request.httprequest.method == 'POST': + old_uid = request.uid uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password']) if uid is not False: return http.redirect_with_hash(redirect) + request.uid = old_uid values['error'] = "Wrong login/password" return render_bootstrap_template('web.login', values) @@ -692,83 +590,47 @@ class Home(http.Controller): def login(self, db, login, key, redirect="/web", **kw): return login_and_redirect(db, login, key, redirect_url=redirect) -class WebClient(http.Controller): - - @http.route('/web/webclient/csslist', type='json', auth="none") - def csslist(self, mods=None): - return manifest_list('css', mods=mods) - - @http.route('/web/webclient/jslist', type='json', auth="none") - def jslist(self, mods=None): - return manifest_list('js', mods=mods) - - @http.route('/web/webclient/qweblist', type='json', auth="none") - def qweblist(self, mods=None): - return manifest_list('qweb', mods=mods) - - @http.route('/web/webclient/css', type='http', auth="none") - def css(self, mods=None, db=None): - files = list(manifest_glob('css', addons=mods, db=db)) - last_modified = get_last_modified(f[0] for f in files) - if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified: - return werkzeug.wrappers.Response(status=304) - - file_map = dict(files) - - rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U) - rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U) - - def reader(f): - """read the a css file and absolutify all relative uris""" - with open(f, 'rb') as fp: - data = fp.read().decode('utf-8') - - path = file_map[f] - web_dir = os.path.dirname(path) - - data = re.sub( - rx_import, - r"""@import \1%s/""" % (web_dir,), - data, - ) - - data = re.sub( - rx_url, - r"url(\1%s/" % (web_dir,), - data, - ) - return data.encode('utf-8') + @http.route('/web/js/<xmlid>', type='http', auth="public") + def js_bundle(self, xmlid, **kw): + # manifest backward compatible mode, to be removed + values = {'manifest_list': manifest_list} + try: + assets_html = request.render(xmlid, lazy=False, qcontext=values) + except QWebTemplateNotFound: + return request.not_found() + bundle = AssetsBundle(xmlid, assets_html, debug=request.debug) - content, checksum = concat_files((f[0] for f in files), reader) + response = request.make_response( + bundle.js(), [('Content-Type', 'application/javascript')]) - # move up all @import and @charset rules to the top - matches = [] - def push(matchobj): - matches.append(matchobj.group(0)) - return '' + # TODO: check that we don't do weird lazy overriding of __call__ which break body-removal + return make_conditional( + response, bundle.last_modified, bundle.checksum, max_age=60*60*24) - content = re.sub(re.compile("(@charset.+;$)", re.M), push, content) - content = re.sub(re.compile("(@import.+;$)", re.M), push, content) + @http.route('/web/css/<xmlid>', type='http', auth='public') + def css_bundle(self, xmlid, **kw): + values = {'manifest_list': manifest_list} # manifest backward compatible mode, to be removed + try: + assets_html = request.render(xmlid, lazy=False, qcontext=values) + except QWebTemplateNotFound: + return request.not_found() + bundle = AssetsBundle(xmlid, assets_html, debug=request.debug) - matches.append(content) - content = '\n'.join(matches) + response = request.make_response( + bundle.css(), [('Content-Type', 'text/css')]) return make_conditional( - request.make_response(content, [('Content-Type', 'text/css')]), - last_modified, checksum) + response, bundle.last_modified, bundle.checksum, max_age=60*60*24) - @http.route('/web/webclient/js', type='http', auth="none") - def js(self, mods=None, db=None): - files = [f[0] for f in manifest_glob('js', addons=mods, db=db)] - last_modified = get_last_modified(files) - if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified: - return werkzeug.wrappers.Response(status=304) +class WebClient(http.Controller): - content, checksum = concat_js(files) + @http.route('/web/webclient/csslist', type='json', auth="none") + def csslist(self, mods=None): + return manifest_list('css', mods=mods) - return make_conditional( - request.make_response(content, [('Content-Type', 'application/javascript')]), - last_modified, checksum) + @http.route('/web/webclient/jslist', type='json', auth="none") + def jslist(self, mods=None): + return manifest_list('js', mods=mods) @http.route('/web/webclient/qweb', type='http', auth="none") def qweb(self, mods=None, db=None): @@ -843,6 +705,10 @@ class WebClient(http.Controller): def version_info(self): return openerp.service.common.exp_version() + @http.route('/web/tests', type='http', auth="none") + def index(self, mod=None, **kwargs): + return request.render('web.qunit_suite') + class Proxy(http.Controller): @http.route('/web/proxy/load', type='json', auth="none") @@ -879,8 +745,64 @@ class Database(http.Controller): def manager(self, **kw): # TODO: migrate the webclient's database manager to server side views request.session.logout() - js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', debug=request.debug)) - css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', debug=request.debug)) + css = """ + <link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/> + <link href="/web/static/lib/cleditor/jquery.cleditor.css" rel="stylesheet"/> + <link href="/web/static/lib/jquery.textext/jquery.textext.css" rel="stylesheet"/> + <link href="/web/static/lib/select2/select2.css" rel="stylesheet"/> + <link href="/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css" rel="stylesheet"/> + <link href="/web/static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css" rel="stylesheet"/> + <link href="/web/static/lib/jquery.ui.notify/css/ui.notify.css" rel="stylesheet"/> + <link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/> + <link href="/web/static/src/css/base.css" rel="stylesheet"/> + <link href="/web/static/src/css/data_export.css" rel="stylesheet"/> + <link href="/base/static/src/css/modules.css" rel="stylesheet"/>""" + js = """ + <script src="/web/static/lib/es5-shim/es5-shim.min.js" type="text/javascript"></script> + <script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script> + <script src="/web/static/lib/underscore.string/lib/underscore.string.js" type="text/javascript"></script> + <script src="/web/static/lib/datejs/globalization/en-US.js" type="text/javascript"></script> + <script src="/web/static/lib/spinjs/spin.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery/jquery.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.blockUI/jquery.blockUI.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.hotkeys/jquery.hotkeys.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.placeholder/jquery.placeholder.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.timeago/jquery.timeago.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.form/jquery.form.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js" type="text/javascript"></script> + <script src="/web/static/lib/datejs/core.js" type="text/javascript"></script> + <script src="/web/static/lib/datejs/parser.js" type="text/javascript"></script> + <script src="/web/static/lib/datejs/sugarpak.js" type="text/javascript"></script> + <script src="/web/static/lib/datejs/extras.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.validate/jquery.validate.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.autosize/jquery.autosize.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.scrollTo/jquery.scrollTo-min.js" type="text/javascript"></script> + <script src="/web/static/lib/cleditor/jquery.cleditor.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.textext/jquery.textext.js" type="text/javascript"></script> + <script src="/web/static/lib/select2/select2.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js" type="text/javascript"></script> + <script src="/web/static/lib/jquery.ui.notify/js/jquery.notify.js" type="text/javascript"></script> + <script src="/web/static/lib/bootstrap/js/bootstrap.js" type="text/javascript"></script> + <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script> + <script src="/web/static/lib/qweb/qweb2.js" type="text/javascript"></script> + <script src="/web/static/src/js/openerpframework.js" type="text/javascript"></script> + <script src="/web/static/lib/py.js/lib/py.js" type="text/javascript"></script> + <script src="/web/static/src/js/boot.js" type="text/javascript"></script> + <script src="/web/static/src/js/testing.js" type="text/javascript"></script> + <script src="/web/static/src/js/pyeval.js" type="text/javascript"></script> + <script src="/web/static/src/js/core.js" type="text/javascript"></script> + <script src="/web/static/src/js/formats.js" type="text/javascript"></script> + <script src="/web/static/src/js/chrome.js" type="text/javascript"></script> + <script src="/web/static/src/js/views.js" type="text/javascript"></script> + <script src="/web/static/src/js/data.js" type="text/javascript"></script> + <script src="/web/static/src/js/data_export.js" type="text/javascript"></script> + <script src="/web/static/src/js/search.js" type="text/javascript"></script> + <script src="/web/static/src/js/view_list.js" type="text/javascript"></script> + <script src="/web/static/src/js/view_form.js" type="text/javascript"></script> + <script src="/web/static/src/js/view_list_editable.js" type="text/javascript"></script> + <script src="/web/static/src/js/view_tree.js" type="text/javascript"></script> + <script src="/base/static/src/js/apps.js" type="text/javascript"></script>""" r = html_template % { 'js': js, @@ -1088,16 +1010,7 @@ class Menu(http.Controller): """ s = request.session Menus = s.model('ir.ui.menu') - # If a menu action is defined use its domain to get the root menu items - user_menu_id = s.model('res.users').read([s.uid], ['menu_id'], - request.context)[0]['menu_id'] - menu_domain = [('parent_id', '=', False)] - if user_menu_id: - domain_string = s.model('ir.actions.act_window').read( - [user_menu_id[0]], ['domain'],request.context)[0]['domain'] - if domain_string: - menu_domain = ast.literal_eval(domain_string) return Menus.search(menu_domain, 0, False, False, request.context) diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py deleted file mode 100644 index a51b619d49f..00000000000 --- a/addons/web/controllers/testing.py +++ /dev/null @@ -1,159 +0,0 @@ -# coding=utf-8 -# -*- encoding: utf-8 -*- - -import glob -import itertools -import json -import operator -import os - -from mako.template import Template -from openerp.modules import module -from openerp import http -from openerp.http import request - -from .main import module_topological_sort - -NOMODULE_TEMPLATE = Template(u"""<!DOCTYPE html> -<html> - <head> - <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>OpenERP Testing</title> - </head> - <body> - <form action="/web/tests" method="GET"> - <button name="mod" value="*">Run all tests</button> - <ul> - % for name, module in modules: - <li>${name} <button name="mod" value="${module}"> - Run Tests</button></li> - % endfor - </ul> - </form> - </body> -</html> -""", default_filters=['h']) -NOTFOUND = Template(u""" -<p>Unable to find the module [${module}], please check that the module - name is correct and the module is on OpenERP's path.</p> -<a href="/web/tests"><< Back to tests</a> -""", default_filters=['h']) -TESTING = Template(u"""<!DOCTYPE html> -<html style="height: 100%"> -<%def name="to_path(module, p)">/${module}/${p}</%def> -<head> - <meta http-equiv="X-UA-Compatible" content="IE=edge"/> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>OpenERP Web Tests</title> - <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/> - - <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css"> - <script src="/web/static/lib/qunit/qunit.js"></script> - - <script type="text/javascript"> - // List of modules, each module is preceded by its dependencies - var oe_all_dependencies = ${dependencies | n}; - QUnit.config.testTimeout = 5 * 60 * 1000; - </script> -</head> -<body id="oe" class="openerp"> - <div id="qunit"></div> - <div id="qunit-fixture"></div> -</body> -<!-- TODO xmo please use the regular template even for testing --> -% for module, jss, tests, templates in files: - % for js in jss: - % if not js.endswith('/apps.js'): - <script src="${to_path(module, js)}"></script> - % endif - % endfor - % if tests or templates: - <script> - openerp.testing.current_module = "${module}"; - % for template in templates: - openerp.testing.add_template("${to_path(module, template)}"); - % endfor - </script> - % endif - % if tests: - % for test in tests: - <script type="text/javascript" src="${to_path(module, test)}"></script> - % endfor - % endif -% endfor -</html> -""", default_filters=['h']) - -class TestRunnerController(http.Controller): - - @http.route('/web/tests', type='http', auth="none") - def index(self, mod=None, **kwargs): - ms = module.get_modules() - manifests = dict( - (name, desc) - for name, desc in zip(ms, map(self.load_manifest, ms)) - if desc # remove not-actually-openerp-modules - ) - - if not mod: - return NOMODULE_TEMPLATE.render(modules=( - (manifest['name'], name) - for name, manifest in manifests.iteritems() - if any(testfile.endswith('.js') - for testfile in manifest['test']) - )) - sorted_mods = module_topological_sort(dict( - (name, manifest.get('depends', [])) - for name, manifest in manifests.iteritems() - )) - # to_load and to_test should be zippable lists of the same length. - # A falsy value in to_test indicate nothing to test at that index (just - # load the corresponding part of to_load) - to_test = sorted_mods - if mod != '*': - if mod not in manifests: - return request.not_found(NOTFOUND.render(module=mod)) - idx = sorted_mods.index(mod) - to_test = [None] * len(sorted_mods) - to_test[idx] = mod - - tests_candicates = [ - filter(lambda path: path.endswith('.js'), - manifests[mod]['test'] if mod else []) - for mod in to_test] - # remove trailing test-less modules - tests = reversed(list( - itertools.dropwhile( - operator.not_, - reversed(tests_candicates)))) - - files = [ - (mod, manifests[mod]['js'], tests, manifests[mod]['qweb']) - for mod, tests in itertools.izip(sorted_mods, tests) - ] - - return TESTING.render(files=files, dependencies=json.dumps( - [name for name in sorted_mods - if module.get_module_resource(name, 'static') - if manifests[name]['js']])) - - def load_manifest(self, name): - manifest = module.load_information_from_description_file(name) - if manifest: - path = module.get_module_path(name) - manifest['js'] = list( - self.expand_patterns(path, manifest.get('js', []))) - manifest['test'] = list( - self.expand_patterns(path, manifest.get('test', []))) - manifest['qweb'] = list( - self.expand_patterns(path, manifest.get('qweb', []))) - return manifest - - def expand_patterns(self, root, patterns): - for pattern in patterns: - normalized_pattern = os.path.normpath(os.path.join(root, pattern)) - for path in glob.glob(normalized_pattern): - # replace OS path separators (from join & normpath) by URI ones - yield path[len(root):].replace(os.path.sep, '/') - |
