summaryrefslogtreecommitdiff
path: root/addons/web/controllers
diff options
context:
space:
mode:
authorJiten (OpenERP) <jra@tinyerp.com>2012-11-19 11:11:38 +0530
committerJiten (OpenERP) <jra@tinyerp.com>2012-11-19 11:11:38 +0530
commit4346af9c96ddc0e88d2d55d47ee0c4e21865aa2d (patch)
tree24feb522a8d01a5babe5a5a6fde7cc1c77b7de25 /addons/web/controllers
parent3646b669516f78aa0a1be69ca8a241468cc9d98b (diff)
parent37209eb5e76a7b373a0f302e1f62be32fd2f37d2 (diff)
[MERGE] Merged trunk.
bzr revid: jra@tinyerp.com-20121119054138-lkrx5jo1tgbr71ek
Diffstat (limited to 'addons/web/controllers')
-rw-r--r--addons/web/controllers/__init__.py1
-rw-r--r--addons/web/controllers/main.py288
-rw-r--r--addons/web/controllers/testing.py162
3 files changed, 239 insertions, 212 deletions
diff --git a/addons/web/controllers/__init__.py b/addons/web/controllers/__init__.py
index 12a7e529b67..74c27518ece 100644
--- a/addons/web/controllers/__init__.py
+++ b/addons/web/controllers/__init__.py
@@ -1 +1,2 @@
from . import main
+from . import testing
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index ad8d91ed1dd..fb7b45a4056 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -81,57 +81,6 @@ def rjsmin(script):
).strip()
return result
-def sass2scss(src):
- # Validated by diff -u of sass2scss against:
- # sass-convert -F sass -T scss openerp.sass openerp.scss
- block = []
- sass = ('', block)
- reComment = re.compile(r'//.*$')
- reIndent = re.compile(r'^\s+')
- reIgnore = re.compile(r'^\s*(//.*)?$')
- reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
- lastLevel = 0
- prevBlocks = {}
- for l in src.split('\n'):
- l = l.rstrip()
- if reIgnore.search(l): continue
- l = reComment.sub('', l)
- l = l.rstrip()
- indent = reIndent.match(l)
- level = indent.end() if indent else 0
- l = l[level:]
- if level>lastLevel:
- prevBlocks[lastLevel] = block
- newBlock = []
- block[-1] = (block[-1], newBlock)
- block = newBlock
- elif level<lastLevel:
- block = prevBlocks[level]
- lastLevel = level
- if not l: continue
- # Fixes
- for ereg, repl in reFixes.items():
- l = ereg.sub(repl if type(repl)==str else repl(), l)
- block.append(l)
-
- def write(sass, level=-1):
- out = ""
- indent = ' '*level
- if type(sass)==tuple:
- if level>=0:
- out += indent+sass[0]+" {\n"
- for e in sass[1]:
- out += write(e, level+1)
- if level>=0:
- out = out.rstrip(" \n")
- out += ' }\n'
- if level==0:
- out += "\n"
- else:
- out += indent+sass+";\n"
- return out
- return write(sass)
-
def db_list(req):
dbs = []
proxy = req.session.proxy("db")
@@ -142,6 +91,17 @@ def db_list(req):
dbs = [i for i in dbs if re.match(r, i)]
return dbs
+def db_monodb(req):
+ # if only one db is listed returns it else return False
+ try:
+ dbs = db_list(req)
+ if len(dbs) == 1:
+ return dbs[0]
+ except xmlrpclib.Fault:
+ # ignore access denied
+ pass
+ return False
+
def module_topological_sort(modules):
""" Return a list of module names sorted so that their dependencies of the
modules are listed before the module itself
@@ -231,22 +191,14 @@ def module_installed_bypass_session(dbname):
def module_boot(req):
server_wide_modules = openerp.conf.server_wide_modules or ['web']
- return [m for m in server_wide_modules if m in openerpweb.addons_manifest]
- # TODO the following will be enabled once we separate the module code and translation loading
serverside = []
dbside = []
for i in server_wide_modules:
if i in openerpweb.addons_manifest:
serverside.append(i)
- # if only one db load every module at boot
- dbs = []
- try:
- dbs = db_list(req)
- except xmlrpclib.Fault:
- # ignore access denied
- pass
- if len(dbs) == 1:
- dbside = module_installed_bypass_session(dbs[0])
+ monodb = db_monodb(req)
+ if monodb:
+ dbside = module_installed_bypass_session(monodb)
dbside = [i for i in dbside if i not in serverside]
addons = serverside + dbside
return addons
@@ -306,10 +258,20 @@ def concat_files(file_list, reader=None, intersperse=""):
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=';')
- content = rjsmin(content)
- return content, checksum
+ 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))
def manifest_glob(req, addons, key):
if addons is None:
@@ -326,7 +288,7 @@ def manifest_glob(req, addons, key):
globlist = manifest.get(key, [])
for pattern in globlist:
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
- r.append((path, path[len(addons_path):]))
+ r.append((path, fs2web(path[len(addons_path):])))
return r
def manifest_list(req, mods, extension):
@@ -549,7 +511,7 @@ def _local_web_translations(trans_file):
messages.append({'id': x.id, 'string': x.string})
return messages
-def from_elementtree(el, preserve_whitespaces=False):
+def xml2json_from_elementtree(el, preserve_whitespaces=False):
""" xml2json-direct
Simple and straightforward XML-to-JSON converter in Python
New BSD Licensed
@@ -569,13 +531,12 @@ def from_elementtree(el, preserve_whitespaces=False):
if el.text and (preserve_whitespaces or el.text.strip() != ''):
kids.append(el.text)
for kid in el:
- kids.append(from_elementtree(kid, preserve_whitespaces))
+ kids.append(xml2json_from_elementtree(kid, preserve_whitespaces))
if kid.tail and (preserve_whitespaces or kid.tail.strip() != ''):
kids.append(kid.tail)
res["children"] = kids
return res
-
def content_disposition(filename, req):
filename = filename.encode('utf8')
escaped = urllib2.quote(filename)
@@ -612,8 +573,7 @@ html_template = """<!DOCTYPE html>
</head>
<body>
<!--[if lte IE 8]>
- <script type="text/javascript"
- src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>
var test = function() {
CFInstall.check({
@@ -687,8 +647,7 @@ class WebClient(openerpweb.Controller):
data = fp.read().decode('utf-8')
path = file_map[f]
- # convert FS path into web path
- web_dir = '/'.join(os.path.dirname(path).split(os.path.sep))
+ web_dir = os.path.dirname(path)
data = re.sub(
rx_import,
@@ -741,21 +700,20 @@ class WebClient(openerpweb.Controller):
until we have established a valid session. This is meant only
for translating the login page and db management chrome, using
the browser's language. """
- lang = req.httprequest.accept_languages.best or 'en'
# For performance reasons we only load a single translation, so for
# sub-languages (that should only be partially translated) we load the
# main language PO instead - that should be enough for the login screen.
- if '-' in lang: # RFC2616 uses '-' separators for sublanguages
- lang = lang.split('-')[0]
+ lang = req.lang.split('_')[0]
translations_per_module = {}
for addon_name in mods:
- addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
- f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
- if not os.path.exists(f_name):
- continue
- translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
-
+ if openerpweb.addons_manifest[addon_name].get('bootstrap'):
+ addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
+ f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
+ if not os.path.exists(f_name):
+ continue
+ translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
+
return {"modules": translations_per_module,
"lang_parameters": None}
@@ -814,21 +772,36 @@ class Database(openerpweb.Controller):
@openerpweb.jsonrequest
def get_list(self, req):
- dbs = db_list(req)
- return {"db_list": dbs}
+ return db_list(req)
@openerpweb.jsonrequest
def create(self, req, fields):
params = dict(map(operator.itemgetter('name', 'value'), fields))
- create_attrs = (
+ return req.session.proxy("db").create_database(
params['super_admin_pwd'],
params['db_name'],
bool(params.get('demo_data')),
params['db_lang'],
- params['create_admin_pwd']
+ params['create_admin_pwd'])
+
+ @openerpweb.jsonrequest
+ def duplicate(self, req, fields):
+ params = dict(map(operator.itemgetter('name', 'value'), fields))
+ return req.session.proxy("db").duplicate_database(
+ params['super_admin_pwd'],
+ params['db_original_name'],
+ params['db_name'])
+
+ @openerpweb.jsonrequest
+ def duplicate(self, req, fields):
+ params = dict(map(operator.itemgetter('name', 'value'), fields))
+ duplicate_attrs = (
+ params['super_admin_pwd'],
+ params['db_original_name'],
+ params['db_name'],
)
- return req.session.proxy("db").create_database(*create_attrs)
+ return req.session.proxy("db").duplicate_database(*duplicate_attrs)
@openerpweb.jsonrequest
def drop(self, req, fields):
@@ -936,10 +909,7 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest
def get_lang_list(self, req):
try:
- return {
- 'lang_list': (req.session.proxy("db").list_lang() or []),
- 'error': ""
- }
+ return req.session.proxy("db").list_lang() or []
except Exception, e:
return {"error": e, "title": "Languages"}
@@ -1298,17 +1268,6 @@ class DataSet(openerpweb.Controller):
m.write(id, { field: i + offset })
return True
-class DataGroup(openerpweb.Controller):
- _cp_path = "/web/group"
- @openerpweb.jsonrequest
- def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
- Model = req.session.model(model)
- context, domain = eval_context_and_domain(req.session, req.context, domain)
-
- return Model.read_group(
- domain or [], fields, group_by_fields, 0, False,
- dict(context, group_by=group_by_fields), sort or False)
-
class View(openerpweb.Controller):
_cp_path = "/web/view"
@@ -1342,7 +1301,7 @@ class View(openerpweb.Controller):
xml = self.transform_view(arch, session, evaluation_context)
else:
xml = ElementTree.fromstring(arch)
- fvg['arch'] = from_elementtree(xml, preserve_whitespaces)
+ fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
if 'id' in fvg['fields']:
# Special case for id's
@@ -1516,11 +1475,24 @@ class Binary(openerpweb.Controller):
try:
if not id:
res = Model.default_get([field], context).get(field)
- image_data = base64.b64decode(res)
+ image_base64 = res
else:
res = Model.read([id], [last_update, field], context)[0]
retag = hashlib.md5(res.get(last_update)).hexdigest()
- image_data = base64.b64decode(res.get(field))
+ image_base64 = res.get(field)
+
+ if kw.get('resize'):
+ resize = kw.get('resize').split(',');
+ if len(resize) == 2 and int(resize[0]) and int(resize[1]):
+ width = int(resize[0])
+ height = int(resize[1])
+ # resize maximum 500*500
+ if width > 500: width = 500
+ if height > 500: height = 500
+ image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
+
+ image_data = base64.b64decode(image_base64)
+
except (TypeError, xmlrpclib.Fault):
image_data = self.placeholder(req)
headers.append(('ETag', retag))
@@ -2010,112 +1982,4 @@ class Reports(View):
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
-class Import(View):
- _cp_path = "/web/import"
-
- def fields_get(self, req, model):
- Model = req.session.model(model)
- fields = Model.fields_get(False, req.session.eval_context(req.context))
- return fields
-
- @openerpweb.httprequest
- def detect_data(self, req, csvfile, csvsep=',', csvdel='"', csvcode='utf-8', jsonp='callback'):
- try:
- data = list(csv.reader(
- csvfile, quotechar=str(csvdel), delimiter=str(csvsep)))
- except csv.Error, e:
- csvfile.seek(0)
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error': {
- 'message': 'Error parsing CSV file: %s' % e,
- # decodes each byte to a unicode character, which may or
- # may not be printable, but decoding will succeed.
- # Otherwise simplejson will try to decode the `str` using
- # utf-8, which is very likely to blow up on characters out
- # of the ascii range (in range [128, 256))
- 'preview': csvfile.read(200).decode('iso-8859-1')}}))
-
- try:
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps(
- {'records': data[:10]}, encoding=csvcode))
- except UnicodeDecodeError:
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({
- 'message': u"Failed to decode CSV file using encoding %s, "
- u"try switching to a different encoding" % csvcode
- }))
-
- @openerpweb.httprequest
- def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, jsonp,
- meta):
- modle_obj = req.session.model(model)
- skip, indices, fields = operator.itemgetter('skip', 'indices', 'fields')(
- simplejson.loads(meta))
-
- error = None
- if not (csvdel and len(csvdel) == 1):
- error = u"The CSV delimiter must be a single character"
-
- if not indices and fields:
- error = u"You must select at least one field to import"
-
- if error:
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error': {'message': error}}))
-
- # skip ignored records (@skip parameter)
- # then skip empty lines (not valid csv)
- # nb: should these operations be reverted?
- rows_to_import = itertools.ifilter(
- None,
- itertools.islice(
- csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep)),
- skip, None))
-
- # if only one index, itemgetter will return an atom rather than a tuple
- if len(indices) == 1: mapper = lambda row: [row[indices[0]]]
- else: mapper = operator.itemgetter(*indices)
-
- data = None
- error = None
- try:
- # decode each data row
- data = [
- [record.decode(csvcode) for record in row]
- for row in itertools.imap(mapper, rows_to_import)
- # don't insert completely empty rows (can happen due to fields
- # filtering in case of e.g. o2m content rows)
- if any(row)
- ]
- except UnicodeDecodeError:
- error = u"Failed to decode CSV file using encoding %s" % csvcode
- except csv.Error, e:
- error = u"Could not process CSV file: %s" % e
-
- # If the file contains nothing,
- if not data:
- error = u"File to import is empty"
- if error:
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error': {'message': error}}))
-
- try:
- (code, record, message, _nope) = modle_obj.import_data(
- fields, data, 'init', '', False,
- req.session.eval_context(req.context))
- except xmlrpclib.Fault, e:
- error = {"message": u"%s, %s" % (e.faultCode, e.faultString)}
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
-
- if code != -1:
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'success':True}))
-
- msg = u"Error during import: %s\n\nTrying to import record %r" % (
- message, record)
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error': {'message':msg}}))
-
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py
new file mode 100644
index 00000000000..3c7c4899b6c
--- /dev/null
+++ b/addons/web/controllers/testing.py
@@ -0,0 +1,162 @@
+# 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 .main import module_topological_sort
+from ..http import Controller, httprequest
+
+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>
+""")
+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>
+""")
+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">
+ var oe_db_info = ${db_info};
+ // List of modules, each module is preceded by its dependencies
+ var oe_all_dependencies = ${dependencies};
+ QUnit.config.testTimeout = 5 * 60 * 1000;
+ </script>
+</head>
+<body id="oe" class="openerp">
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+% for module, jss, tests, templates in files:
+ % for js in jss:
+ <script src="${to_path(module, js)}"></script>
+ % 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>
+""")
+
+class TestRunnerController(Controller):
+ _cp_path = '/web/tests'
+
+ @httprequest
+ def index(self, req, 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 req.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)
+ ]
+
+ # if all three db_info parameters are present, send them to the page
+ db_info = dict((k, v) for k, v in kwargs.iteritems()
+ if k in ['source', 'supadmin', 'password'])
+ if len(db_info) != 3:
+ db_info = None
+
+ 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']]), db_info=json.dumps(db_info))
+
+ 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, '/')