summaryrefslogtreecommitdiff
path: root/addons/web/controllers
diff options
context:
space:
mode:
authorVidhin Mehta (OpenERP) <vme@tinyerp.com>2014-05-05 10:02:33 +0530
committerVidhin Mehta (OpenERP) <vme@tinyerp.com>2014-05-05 10:02:33 +0530
commite41508cb8d7fd2a2c06e3fab9ce1503e8efa30fa (patch)
tree8e0fbb849cebc9b3ac0f87355a5248f51826a491 /addons/web/controllers
parent4312b921f3db41529cbd4f501a6b5dcba2bebd08 (diff)
parent8fbebfd752fa8933584711522675d354b3217839 (diff)
[MERGE]Trunk.
bzr revid: vme@tinyerp.com-20140505043233-2t2e2vb1yv86hyxp
Diffstat (limited to 'addons/web/controllers')
-rw-r--r--addons/web/controllers/__init__.py1
-rw-r--r--addons/web/controllers/main.py301
-rw-r--r--addons/web/controllers/testing.py159
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">&lt;&lt; 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, '/')
-