[MERGE]Trunk.

bzr revid: vme@tinyerp.com-20140505043233-2t2e2vb1yv86hyxp
This commit is contained in:
Vidhin Mehta (OpenERP) 2014-05-05 10:02:33 +05:30
commit e41508cb8d
406 changed files with 1111 additions and 1908 deletions

View File

@ -14,81 +14,7 @@ This module provides the core of the OpenERP Web Client.
'data': [
'views/webclient_templates.xml',
],
'js': [
"static/lib/es5-shim/es5-shim.min.js",
"static/lib/datejs/globalization/en-US.js",
"static/lib/datejs/core.js",
"static/lib/datejs/parser.js",
"static/lib/datejs/sugarpak.js",
"static/lib/datejs/extras.js",
"static/lib/jquery/jquery.js",
"static/lib/jquery.form/jquery.form.js",
"static/lib/jquery.validate/jquery.validate.js",
"static/lib/jquery.ba-bbq/jquery.ba-bbq.js",
"static/lib/spinjs/spin.js",
"static/lib/jquery.autosize/jquery.autosize.js",
"static/lib/jquery.blockUI/jquery.blockUI.js",
"static/lib/jquery.hotkeys/jquery.hotkeys.js",
"static/lib/jquery.placeholder/jquery.placeholder.js",
"static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js",
"static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
"static/lib/jquery.ui.notify/js/jquery.notify.js",
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
"static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
"static/lib/jquery.textext/jquery.textext.js",
"static/lib/jquery.timeago/jquery.timeago.js",
"static/lib/bootstrap/js/bootstrap.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore.string/lib/underscore.string.js",
"static/lib/backbone/backbone.js",
"static/lib/cleditor/jquery.cleditor.js",
"static/lib/py.js/lib/py.js",
"static/lib/select2/select2.js",
"static/src/js/openerpframework.js",
"static/src/js/boot.js",
"static/src/js/testing.js",
"static/src/js/pyeval.js",
"static/src/js/core.js",
"static/src/js/formats.js",
"static/src/js/chrome.js",
"static/src/js/views.js",
"static/src/js/data.js",
"static/src/js/data_export.js",
"static/src/js/search.js",
"static/src/js/view_list.js",
"static/src/js/view_form.js",
"static/src/js/view_list_editable.js",
"static/src/js/view_tree.js",
],
'css' : [
"static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css",
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/lib/jquery.textext/jquery.textext.css",
"static/lib/fontawesome/css/font-awesome.css",
"static/lib/bootstrap/css/bootstrap.css",
"static/lib/select2/select2.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
"static/lib/cleditor/jquery.cleditor.css",
],
'qweb' : [
"static/src/xml/*.xml",
],
'test': [
"static/test/testing.js",
"static/test/framework.js",
"static/test/registry.js",
"static/test/form.js",
"static/test/data.js",
"static/test/list-utils.js",
"static/test/formats.js",
"static/test/rpc-misordered.js",
"static/test/evals.js",
"static/test/search.js",
"static/test/list.js",
"static/test/list-editable.js",
"static/test/mutex.js"
],
}

View File

@ -1,2 +1 @@
from . import main
from . import testing

View File

@ -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,6 +590,38 @@ class Home(http.Controller):
def login(self, db, login, key, redirect="/web", **kw):
return login_and_redirect(db, login, key, redirect_url=redirect)
@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)
response = request.make_response(
bundle.js(), [('Content-Type', 'application/javascript')])
# 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)
@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)
response = request.make_response(
bundle.css(), [('Content-Type', 'text/css')])
return make_conditional(
response, bundle.last_modified, bundle.checksum, max_age=60*60*24)
class WebClient(http.Controller):
@http.route('/web/webclient/csslist', type='json', auth="none")
@ -702,74 +632,6 @@ class WebClient(http.Controller):
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')
content, checksum = concat_files((f[0] for f in files), reader)
# move up all @import and @charset rules to the top
matches = []
def push(matchobj):
matches.append(matchobj.group(0))
return ''
content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
matches.append(content)
content = '\n'.join(matches)
return make_conditional(
request.make_response(content, [('Content-Type', 'text/css')]),
last_modified, checksum)
@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)
content, checksum = concat_js(files)
return make_conditional(
request.make_response(content, [('Content-Type', 'application/javascript')]),
last_modified, checksum)
@http.route('/web/webclient/qweb', type='http', auth="none")
def qweb(self, mods=None, db=None):
files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)]
@ -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)

View File

@ -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, '/')

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Poedit-Language: Czech\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"Language: es\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -15,7 +15,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:39+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:38+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:52+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:53+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web

View File

@ -207,6 +207,9 @@
.fa-star:before {
content: "\f005";
}
. fa-tasks:before {
content: "\f0ae";
}
.fa-star-o:before {
content: "\f006";
}
@ -237,6 +240,12 @@
.fa-search-minus:before {
content: "\f010";
}
.fa-adn:before {
content: "\f170";
}
. fa-times:before {
content: "\f00d";
}
.fa-power-off:before {
content: "\f011";
}
@ -308,6 +317,9 @@
.fa-qrcode:before {
content: "\f029";
}
.fa-calendar:before {
content: "\f073";
}
.fa-barcode:before {
content: "\f02a";
}
@ -759,6 +771,9 @@
.fa-pinterest:before {
content: "\f0d2";
}
.fa-pencil-square-o:before {
content: "\f044";
}
.fa-pinterest-square:before {
content: "\f0d3";
}

View File

@ -1,4 +1,4 @@
@charset "utf-8";
@charset "UTF-8";
@font-face {
font-family: "mnmliconsRegular";
src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
@ -79,7 +79,7 @@
vertical-align: top;
}
.openerp .oe_title {
width: 50%;
width: 38%;
float: left;
}
.openerp .oe_title:after {
@ -314,56 +314,58 @@
.openerp .oe_form_dirty button.oe_highlight_on_dirty:hover {
background: #ed6f6a;
}
.openerp .oe_button_box {
width: 400px;
text-align: left;
}
.openerp .oe_button_box .oe_stat_button:hover {
background: #7c7bad;
color: white;
}
.openerp .oe_button_box .oe_stat_button:hover .fa {
color: white;
}
.openerp .oe_button_box .oe_stat_button {
.openerp .oe_stat_button {
font-weight: normal;
display: inline-table;
width: 33% !important;
height: 42px;
width: 132px !important;
height: 40px;
color: #666666;
margin: 0px -1px -1px 0px;
padding: 0;
color: #666666;
border: 1px solid #dddddd;
border-radius: 0;
box-shadow: none;
background: white;
}
.openerp .oe_button_box .oe_stat_button > div {
.openerp .oe_stat_button > div {
display: table-cell;
vertical-align: middle;
text-align: left;
padding: 0;
line-height: 120%;
}
.openerp .oe_button_box .oe_stat_button .stat_button_icon {
.openerp .oe_stat_button .stat_button_icon {
color: #7c7bad;
font-size: 24px;
padding: 0px 3px;
width: 37px;
text-align: center;
}
.openerp .oe_button_box .oe_stat_button .oe_form_field_percent_pie {
.openerp .oe_stat_button .oe_form_field_percent_pie {
width: 42px;
}
.openerp .oe_button_box .oe_stat_button .oe_form_field_bar_chart {
.openerp .oe_stat_button .oe_form_field_bar_chart {
width: 42px;
}
.openerp .oe_button_box .oe_stat_button svg {
.openerp .oe_stat_button svg {
width: 38px;
height: 38px;
display: inline;
vertical-align: middle;
}
.openerp .oe_stat_button:hover {
background: #7c7bad;
color: white;
}
.openerp .oe_stat_button:hover .fa {
color: white;
}
.openerp .oe_button_box {
width: 400px;
text-align: right;
}
.openerp .oe_button_box .oe_stat_button {
display: inline-table;
}
.openerp .oe_avatar > img {
max-height: 90px;
max-width: 90px;
@ -720,7 +722,7 @@
border-bottom-left-radius: 8px;
}
.openerp .oe_notification {
z-index: 1050;
z-index: 1500;
}
.openerp .oe_webclient_timezone_notification a {
color: white;
@ -2742,13 +2744,15 @@
padding: 3px 6px;
white-space: pre-line;
}
.openerp .oe_list_content > tbody > tr > td > button.btn_img, .openerp .oe_list_content > tbody > tr > th > button.btn_img {
.openerp .oe_list_content > tbody > tr > td > button, .openerp .oe_list_content > tbody > tr > th > button {
border: none;
background: transparent;
padding: 0;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.openerp .oe_list_content > tbody > tr > td > button.btn_txt, .openerp .oe_list_content > tbody > tr > th > button.btn_txt {
border: 1px solid rgba(0, 0, 0, 0.4);
background: #e3e3e3;
padding: 3px 12px;
}
.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child {
width: 17px;
@ -3293,11 +3297,6 @@ body.oe_single_form .oe_single_form_container {
overflow: hidden !important;
}
}
.ui-icon {
width: 18px;
height: 18px;
}
.tooltip {
padding: 0;
margin: 0;
@ -3307,8 +3306,6 @@ body.oe_single_form .oe_single_form_container {
background: white;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
background-color: transparent;
/*We need a greater z-index in order for tooltip to go over bootstrap modal z-index*/
z-index: 1500;
}
.tooltip .tooltip-inner {
text-align: left !important;
@ -3345,6 +3342,12 @@ body.oe_single_form .oe_single_form_container {
.tooltip .tooltip-inner .oe_tooltip_message {
max-width: 310px;
}
.ui-icon {
width: 18px;
height: 18px;
}
.modal .modal-header button.close {
border: none;
background: none;
@ -3355,12 +3358,17 @@ body.oe_single_form .oe_single_form_container {
.modal .modal-footer {
text-align: left;
}
.modal .oe_act_window.modal-body{
padding: 0;
}
.modal .oe_button{
.modal .oe_button {
margin: 0 4px 0 0;
}
.modal .oe_act_window.modal-body {
padding: 0;
}
.ui-datepicker {
z-index: 1500 !important;
}
input[type="radio"], input[type="checkbox"] {
margin-right: 4px;
margin-left: 4px;

View File

@ -190,7 +190,7 @@ $sheet-padding: 16px
td
vertical-align: top
.oe_title
width: 50%
width: 38%
float: left
.oe_title:after
content: "."
@ -330,47 +330,48 @@ $sheet-padding: 16px
@include box-shadow(none)
&:hover
background: #ED6F6A
.oe_stat_button
font-weight: normal
width: 132px !important
height: 40px
color: #666
margin: 0px -1px -1px 0px
padding: 0
border: 1px solid #dddddd
border-radius: 0
box-shadow: none
background: white
> div
display: table-cell
vertical-align: middle
text-align: left
padding: 0
line-height: 120%
.stat_button_icon
color: #7C7BAD
font-size: 24px
padding: 0px 3px
width: 37px
text-align: center
.oe_form_field_percent_pie
width: 42px
.oe_form_field_bar_chart
width: 42px
svg
width: 38px
height: 38px
display: inline
vertical-align: middle
.oe_stat_button:hover
background: #7c7bad
color: white
.fa
color: white
.oe_button_box
width: 400px
text-align: left
.oe_stat_button:hover
background: #7c7bad
color: white
.fa
color: white
text-align: right
.oe_stat_button
font-weight: normal
display: inline-table
width: 33% !important
height: 42px
margin: 0px -1px -1px 0px
padding: 0
color: #666
border: 1px solid #dddddd
border-radius: 0
box-shadow: none
background: white
> div
display: table-cell
vertical-align: middle
text-align: left
padding: 0
line-height: 120%
.stat_button_icon
color: #7C7BAD
font-size: 24px
padding: 0px 3px
width: 37px
text-align: center
.oe_form_field_percent_pie
width: 42px
.oe_form_field_bar_chart
width: 42px
svg
width: 38px
height: 38px
display: inline
vertical-align: middle
.oe_avatar
> img
max-height: 90px
@ -639,7 +640,8 @@ $sheet-padding: 16px
// }}}
// Notifications {{{
.oe_notification
z-index: 1050
z-index: 1500
.oe_webclient_timezone_notification
a
color: white
@ -2220,11 +2222,14 @@ $sheet-padding: 16px
padding: 3px 6px
white-space: pre-line
> td, > th
> button.btn_img
> button
border: none
background: transparent
padding: 0
@include box-shadow(none)
> button.btn_txt
border: 1px solid rgba(0,0,0,0.4)
background: #e3e3e3
padding: 3px 12px
> td.oe_list_checkbox:first-child, th.oe_list_checkbox:first-child
width: 17px
&:after
@ -2736,6 +2741,9 @@ body.oe_single_form
.oe_act_window.modal-body
padding: 0
.ui-datepicker
z-index: 1500 !important
input[type="radio"], input[type="checkbox"]
margin-right: 4px
margin-left: 4px

View File

@ -1460,7 +1460,7 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
$('head').append($('<link>', {
'rel': 'stylesheet',
'type': 'text/css',
'href': origin +'/web/webclient/css'
'href': origin +'/web/css/web.assets_webclient'
}));
var currentScript = document.currentScript;
if (!currentScript) {

View File

@ -232,7 +232,7 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
var self = this;
return this.session_reload().then(function(result) {
var modules = instance._modules.join(',');
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
var deferred = self.load_qweb(modules);
if(self.session_is_valid()) {
return deferred.then(function() { return self.load_modules(); });
}
@ -318,7 +318,7 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
loaded = $.when(
loaded,
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
self.load_qweb(to_load),
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
file_list = file_list.concat(files);
})
@ -375,14 +375,12 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
}
return d;
},
load_qweb: function(files) {
load_qweb: function(mods) {
var self = this;
_.each(files, function(file) {
self.qweb_mutex.exec(function() {
return self.rpc('/web/proxy/load', {path: file}).then(function(xml) {
if (!xml) { return; }
instance.web.qweb.add_template(_.str.trim(xml));
});
self.qweb_mutex.exec(function() {
return self.rpc('/web/proxy/load', {path: '/web/webclient/qweb?mods=' + mods}).then(function(xml) {
if (!xml) { return; }
instance.web.qweb.add_template(_.str.trim(xml));
});
});
return self.qweb_mutex.def;
@ -460,14 +458,11 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
.appendTo(document.body)
.load(function () {
try {
if (options.error) {
if (!this.contentDocument.body.childNodes[1]) {
options.error(this.contentDocument.body.childNodes);
}
else {
options.error(JSON.parse(this.contentDocument.body.childNodes[1].textContent));
}
}
if (options.error) {
var body = this.contentDocument.body;
var node = body.childNodes[1] || body.childNodes[0];
options.error(JSON.parse(node.textContent));
}
} finally {
complete();
}

View File

@ -32,7 +32,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
var self = this;
var options = {
buttons: [
{text: _t("Close"), click: function () { self.close(); }},
{text: _t("Close"), click: function () { self.$el.parents('.modal').modal('hide'); }},
{text: _t("Export To File"), click: function () { self.on_click_export_data(); }}
],
close: function () { self.close();}

View File

@ -29,8 +29,8 @@ my.Facet = B.Model.extend({
B.Model.prototype.initialize.apply(this, arguments);
this.values = new my.FacetValues(values || []);
this.values.on('add remove change reset', function () {
this.trigger('change', this);
this.values.on('add remove change reset', function (_, options) {
this.trigger('change', this, options);
}, this);
},
get: function (key) {
@ -399,7 +399,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
this.setup_global_completion();
this.query = new my.SearchQuery()
.on('add change reset remove', this.proxy('do_search'))
.on('add change reset remove', this.proxy('renderFacets'));
.on('change', this.proxy('renderChangedFacets'))
.on('add reset remove', this.proxy('renderFacets'));
if (this.options.hidden) {
this.$el.hide();
@ -578,14 +579,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
.trigger('blur');
},
/**
*
* @param {openerp.web.search.SearchQuery | openerp.web.search.Facet} _1
* @param {openerp.web.search.Facet} [_2]
* Call the renderFacets method with the correct arguments.
* This is due to the fact that change events are called with two arguments
* (model, options) while add, reset and remove events are called with
* (collection, model, options) as arguments
*/
renderChangedFacets: function (model, options) {
this.renderFacets(undefined, model, options);
},
/**
* @param {openerp.web.search.SearchQuery | undefined} Undefined if event is change
* @param {openerp.web.search.Facet}
* @param {Object} [options]
*/
renderFacets: function (_1, _2, options) {
// _1: model if event=change, otherwise collection
// _2: undefined if event=change, otherwise model
renderFacets: function (collection, model, options) {
var self = this;
var started = [];
var $e = this.$('div.oe_searchview_facets');
@ -610,6 +617,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
});
$.when.apply(null, started).then(function () {
if (options && options.focus_input === false) return;
var input_to_focus;
// options.at: facet inserted at given index, focus next input
// otherwise just focus last input
@ -618,7 +626,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
} else {
input_to_focus = self.input_subviews[(options.at + 1) * 2];
}
input_to_focus.$el.focus();
});
},
@ -1602,8 +1609,11 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
return facetValue.get('label');
},
make_domain: function (name, operator, facetValue) {
if (operator === this.default_operator) {
switch(operator){
case this.default_operator:
return [[name, '=', facetValue.get('value')]];
case 'child_of':
return [[name, 'child_of', facetValue.get('value')]];
}
return this._super(name, operator, facetValue);
},

View File

@ -48,15 +48,6 @@ openerp.testing = {};
testing.dependencies = window['oe_all_dependencies'] || [];
testing.current_module = null;
testing.templates = { };
testing.add_template = function (name) {
var xhr = QWeb2.Engine.prototype.get_xhr();
xhr.open('GET', name, false);
xhr.send(null);
(testing.templates[testing.current_module] =
testing.templates[testing.current_module] || [])
.push(xhr.responseXML);
};
/**
* Function which does not do anything
*/
@ -206,7 +197,7 @@ openerp.testing = {};
teardown: testing.noop
});
QUnit.module(testing.current_module + '.' + name, {_oe: options});
QUnit.module(name, {_oe: options});
body(testing['case']);
};
testing['case'] = function (name, options, callback) {
@ -244,18 +235,6 @@ openerp.testing = {};
expect(opts.asserts);
}
if (opts.templates) {
for(var i=0; i<module_deps.length; ++i) {
var dep = module_deps[i];
var templates = testing.templates[dep];
if (_.isEmpty(templates)) { continue; }
for (var j=0; j < templates.length; ++j) {
instance.web.qweb.add_template(templates[j]);
}
}
}
var $fixture = $('#qunit-fixture');
var mock, async = false;

View File

@ -1940,7 +1940,7 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
init: function(field_manager, node) {
node.attrs.type = node.attrs['data-button-type'];
this.is_stat_button = /\boe_stat_button\b/.test(node.attrs['class']);
this.icon = node.attrs.icon && "<span class=\"fa " + node.attrs.icon + " fa-fw\"></span>";
this.icon_class = node.attrs.icon && "stat_button_icon fa " + node.attrs.icon + " fa-fw";
this._super(field_manager, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
@ -3356,13 +3356,16 @@ instance.web.form.CompletionFieldMixin = {
instance.web.form.M2ODialog = instance.web.Dialog.extend({
template: "M2ODialog",
init: function(parent) {
this.name = parent.string;
this._super(parent, {
title: _.str.sprintf(_t("Add %s"), parent.string),
title: _.str.sprintf(_t("Create a %s"), parent.string),
size: 'medium',
});
},
start: function() {
var self = this;
var text = _.str.sprintf(_t("You are creating a new %s, are you sure it does not exist yet?"), self.name);
this.$("p").text( text );
this.$buttons.html(QWeb.render("M2ODialog.buttons"));
this.$("input").val(this.getParent().last_query);
this.$buttons.find(".oe_form_m2o_qc_button").click(function(){
@ -3486,7 +3489,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
self.display_value_backup = {};
self.render_value();
self.focus();
self.view.do_onchange(self);
self.trigger('changed_value');
});
});
});
@ -3621,6 +3624,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
minLength: 0,
delay: 250
});
// set position for list of suggestions box
this.$input.autocomplete( "option", "position", { my : "left top", at: "left bottom" } );
this.$input.autocomplete("widget").openerpClass();
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
this.$input.keyup(function(e) {

View File

@ -40,7 +40,7 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title"><t t-raw="title"/></h3>
</div>
<div class="modal-body">
<div class="modal-body" style="overflow-y: auto;">
</div>
</div>
</div>
@ -800,12 +800,11 @@
</t>
<button t-name="ListView.row.text_button" type="button"
t-att-title="widget.string" t-att-disabled="disabled || undefined"
t-att-class="disabled ? 'oe_list_button_disabled btn' : 'btn'">
<t t-esc="widget.string"/>
</button>
t-att-class="disabled ? 'oe_list_button_disabled btn_txt oe_link' : 'btn_txt oe_link'"
><t t-esc="widget.string"/></button>
<button t-name="ListView.row.button" type="button"
t-att-title="widget.string" t-att-disabled="disabled || undefined"
t-att-class="disabled ? 'oe_list_button_disabled btn_img' : 'btn_img'"
t-att-class="disabled ? 'oe_list_button_disabled' : ''"
><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png"
t-att-alt="widget.string"/></button>
<t t-extend="ListView.row">
@ -1409,7 +1408,7 @@
t-att-autofocus="widget.node.attrs.autofocus"
t-att-accesskey="widget.node.attrs.accesskey">
<img t-if="!widget.is_stat_button and widget.node.attrs.icon " t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/>
<div t-if="widget.is_stat_button and widget.icon" class="stat_button_icon"><t t-raw="widget.icon"/></div>
<div t-if="widget.is_stat_button and widget.icon_class" t-att-class="widget.icon_class"></div>
<span t-if="widget.string and !widget.is_stat_button"><t t-esc="widget.string"/></span>
<div t-if="widget.string and widget.is_stat_button"><t t-esc="widget.string"/></div>
</button>
@ -1948,13 +1947,14 @@
</t>
<t t-name="M2ODialog">
<div>
<p class="oe_grey"/>
Name: <input class="oe_form_m2o_input_name" type="text"/>
</div>
</t>
<t t-name="M2ODialog.buttons">
<button class="oe_form_m2o_qc_button oe_button oe_highlight">Quick Add</button>
<button class="oe_form_m2o_sc_button oe_button">Add All Info...</button>
<button class="oe_form_m2o_cancel_button oe_button">Cancel</button>
<button class="oe_form_m2o_qc_button oe_button oe_highlight">Create</button>
<button class="oe_form_m2o_sc_button oe_button">Create and edit</button> or
<button class="oe_form_m2o_cancel_button oe_button oe_link"><span>Cancel</span></button>
</t>
<t t-name="FieldMonetary" t-extend="FieldChar">
<t t-jquery="t:first" t-operation="before">
@ -1985,6 +1985,5 @@
<a href="javascript:void(0)"><t t-esc="text"/></a>
</t>
<t t-name="StatInfo">
<strong><t t-esc="value"/></strong>
<t t-esc="text"/></t>
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
</templates>

View File

@ -361,8 +361,8 @@ openerp.testing.section('list.edition.onwrite', {
strictEqual(
$fix.find('tbody tr:eq(1)').css('color'), 'rgb(255, 0, 0)',
'shoud have color applied');
strictEqual(
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(0, 0, 0)',
notStrictEqual(
$fix.find('tbody tr:eq(2)').css('color'), 'rgb(255, 0, 0)',
'should have default color applied');
});
});

View File

@ -2,4 +2,3 @@
import test_js
import test_menu
import test_serving_base
import test_ui

View File

@ -1,116 +0,0 @@
QUnitSuite is a ``unittest.TestSuite`` able to run QUnit_ test suites
within the normal unittest process, through PhantomJS_.
QUnitSuite is built upon `Ben Alman`_'s work of for the interfacing
between PhantomJS_ and the host/reporting code: the shims and the
PhantomJS_ configuration files are those of grunt_'s ``qunit`` task.
Why
---
You're a Python shop or developer, you have tools and tests built
around unittest (or compatible with unittests) and your testing
pipeline is predicated upon that, you're doing web development of some
sort these days (as so many are) and you'd like to do some testing of
your web stuff.
But you don't really want to redo your whole testing stack just for
that.
QUnitSuite simply grafts QUnit_-based tests, run in PhantomJS_, in
your existing ``unittest``-based architecture.
What
----
QUnitSuite currently provides a single object as part of its API:
``qunitsuite.QUnitSuite(testfile[, timeout])``.
This produces a ``unittest.TestSuite`` suitable for all the usual
stuff (running it, and giving it to an other test suite which will run
it, that is).
``testfile`` is the HTML file bootstrapping your qunit tests, as would
usually be accessed via a browser. It can be either a local
(``file:``) url, or an HTTP one. As long as a regular browser can open
and execute it, PhantomJS_ will manage.
``timeout`` is a check passed to the PhantomJS_ runner: if the runner
produces no information for longer than ``timeout`` milliseconds, the
run will be cancelled and a test error will be generated. This
situation usually means either your ``testfile`` is not a qunit test
file, qunit is not running or qunit's runner was stopped (for an async
test) and never restarted.
The default value is very conservative, most tests should run
correctly with lower timeouts (especially if all tests are
synchronous).
How
---
``unittest``'s autodiscovery protocol does not directly work with test
suites (it looks for test cases). If you want autodiscovery to work
correctly, you will have to use the ``load_tests`` protocol::
# in a testing module
def load_tests(loader, tests, pattern):
tests.addTest(QUnitSuite(qunit_test_path.html))
return tests
outside of that specific case, you can use a ``QUnitSuite`` as a
standard ``TestSuite`` instance, running it, adding it to an other
suite or passing it to a ``TestRunner``
Complaints and Grievances
-------------------------
Speed
~~~~~
Starting up a phantomjs instance and running a suite turns out to have
a rather high overhead, on the order of a second on this machine
(2.4GHz, 8GB RAM and an SSD).
As each ``QUnitSuite`` currently creates its own phantomjs instance,
it's probably a good idea to create bigger suites (put many modules &
tests in the same QUnit html file, which doesn't preclude splitting
them across multiple js files).
Hacks
~~~~~
QUnitSuite contains a pretty big hack which may or may not cause
problem depending on your exact setup: in case of case failure or
error, ``unittest.TestResult`` formats the error traceback provided
alongside the test object. This goes through Python's
traceback-formatting code and there are no hooks there.
One could expect to use a custom ``TestResult``, but for test suites
the ``TestResult`` instance must be provided by the caller, so there
is no direct hook onto it.
This leaves three options:
* Create a custom ``TestResult`` class and require that it be the one
provided to the test suite. This requires altered work flows,
customization of the test runner and (as far as I know) isn't
available through Python 2.7's autodiscovery. It's the cleanest
option but completely fails on practicality.
* Create a custom ``TestResult`` which directly alters the original
result's ``errors`` and ``failures`` attributes as they're part of
the testrunner API. This would work but may put custom results in a
strange state and break e.g. unittest2's ``@failfast``.
* Lastly, monkeypatch the undocumented and implementation detail
``_exc_info_to_string`` on the provided ``result``. This is the
route taken, at least for now.
.. _QUnit: http://qunitjs.com/
.. _PhantomJS: http://phantomjs.org/
.. _Ben Alman: http://benalman.com/
.. _grunt: http://gruntjs.com/

View File

@ -1,95 +0,0 @@
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
/*global phantom:true*/
'use strict';
var fs = require('fs');
// The page .html file to load.
var url = phantom.args[0];
// Extra, optionally overridable stuff.
var options = JSON.parse(phantom.args[1] || {});
// Default options.
if (!options.timeout) { options.timeout = 5000; }
// Keep track of the last time a client message was sent.
var last = new Date();
// Messages are sent to the parent by appending them to the tempfile.
var sendMessage = function(arg) {
var args = Array.isArray(arg) ? arg : [].slice.call(arguments);
last = new Date();
console.log(JSON.stringify(args));
};
// This allows grunt to abort if the PhantomJS version isn't adequate.
sendMessage('private', 'version', phantom.version);
// Abort if the page doesn't send any messages for a while.
setInterval(function() {
if (new Date() - last > options.timeout) {
sendMessage('fail.timeout');
phantom.exit();
}
}, 100);
// Create a new page.
var page = require('webpage').create();
// The client page must send its messages via alert(jsonstring).
page.onAlert = function(args) {
sendMessage(JSON.parse(args));
};
// Keep track if the client-side helper script already has been injected.
var injected;
page.onUrlChanged = function(newUrl) {
injected = false;
sendMessage('onUrlChanged', newUrl);
};
// Relay console logging messages.
page.onConsoleMessage = function(message) {
sendMessage('console', message);
};
// For debugging.
page.onResourceRequested = function(request) {
sendMessage('onResourceRequested', request.url);
};
page.onResourceReceived = function(request) {
if (request.stage === 'end') {
sendMessage('onResourceReceived', request.url);
}
};
// Run when the page has finished loading.
page.onLoadFinished = function(status) {
// The window has loaded.
sendMessage('onLoadFinished', status);
if (status === 'success') {
if (options.inject && !injected) {
// Inject client-side helper script, but only if it has not yet been
// injected.
sendMessage('inject', options.inject);
page.injectJs(options.inject);
}
} else {
// File loading failure.
sendMessage('fail.load', url);
phantom.exit();
}
};
// Actually load url.
page.open(url);

View File

@ -1,22 +0,0 @@
Copyright (c) 2012 "Cowboy" Ben Alman
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,88 +0,0 @@
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
/*global QUnit:true, alert:true*/
'use strict';
// Don't re-order tests.
QUnit.config.reorder = false;
// Run tests serially, not in parallel.
QUnit.config.autorun = false;
// Send messages to the parent PhantomJS process via alert! Good times!!
function sendMessage() {
var args = [].slice.call(arguments);
alert(JSON.stringify(args));
}
// These methods connect QUnit to PhantomJS.
QUnit.log(function(obj) {
// What is this I dont even
if (obj.message === '[object Object], undefined:undefined') { return; }
// Parse some stuff before sending it.
var actual = QUnit.jsDump.parse(obj.actual);
var expected = QUnit.jsDump.parse(obj.expected);
// Send it.
sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source);
});
QUnit.testStart(function(obj) {
sendMessage('qunit.testStart', obj.name);
});
QUnit.testDone(function(obj) {
sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total);
});
QUnit.moduleStart(function(obj) {
sendMessage('qunit.moduleStart', obj.name);
});
QUnit.moduleDone(function(obj) {
sendMessage('qunit.moduleDone', obj.name, obj.failed, obj.passed, obj.total);
});
QUnit.begin(function() {
sendMessage('qunit.begin');
});
QUnit.done(function(obj) {
sendMessage('qunit.done', obj.failed, obj.passed, obj.total, obj.runtime);
});
// PhantomJS (up to and including 1.7) uses a version of webkit so old
// it does not have Function.prototype.bind:
// http://code.google.com/p/phantomjs/issues/detail?id=522
// Use moz polyfill:
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

View File

@ -1,136 +0,0 @@
import json
import subprocess
import unittest
import os
import time
ROOT = os.path.join(os.path.dirname(__file__), 'grunt')
__all__ = ['QUnitSuite']
def _exc_info_to_string(err, test):
return err
class QUnitTest(unittest.TestCase):
def __init__(self, module, name):
self.module = module
self.name = name
self.failed = False
def shortDescription(self):
return None
def __repr__(self):
return '<QUnitTest %s:%s>' % (self.module, self.name)
def __str__(self):
return '%s: %s' % (self.module, self.name)
class QUnitSuite(unittest.TestSuite):
def __init__(self, qunitfile, timeout=5000):
super(QUnitSuite, self).__init__()
self.testfile = qunitfile
self.timeout = timeout
self._module = None
self._test = None
def run(self, result):
try:
subprocess.call(['phantomjs', '-v'],
stdout=open(os.devnull, 'w'),
stderr=subprocess.STDOUT)
except OSError:
test = QUnitTest('phantomjs', 'javascript tests')
result.startTest(test)
result.startTest(test)
result.addSkip(test , "phantomjs command not found")
result.stopTest(test)
return
result._exc_info_to_string = _exc_info_to_string
try:
self._run(result)
finally:
del result._exc_info_to_string
def _run(self, result):
phantom = subprocess.Popen([
'phantomjs',
'--config=%s' % os.path.join(ROOT, 'phantomjs.json'),
os.path.join(ROOT, 'bootstrap.js'), self.testfile,
json.dumps({
'timeout': self.timeout,
'inject': os.path.join(ROOT, 'qunit-phantomjs-bridge.js')
})
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
while True:
line = phantom.stdout.readline()
if line:
if self.process(line, result):
break
else:
time.sleep(0.1)
finally:
# If the phantomjs process hasn't quit, kill it
if phantom.poll() is None:
phantom.terminate()
def process(self, line, result):
try:
args = json.loads(line)
except ValueError: # phantomjs stderr
if 'CoreText' not in line:
print line
return False
event_name = args[0]
if event_name == 'qunit.done':
return True
elif event_name == 'fail.load':
self.add_error(result, "PhantomJS unable to load %s" % args[1])
return True
elif event_name == 'fail.timeout':
self.add_error(result, "PhantomJS timed out, possibly due to a"
" missing QUnit start() call")
return True
elif event_name == 'qunit.moduleStart':
self._module = args[1].encode('utf-8') if args[1] else ''
elif event_name == 'qunit.moduleStop':
self._test = None
self._module = None
elif event_name == 'qunit.testStart':
self._test = QUnitTest(self._module, args[1].encode('utf-8'))
result.startTest(self._test)
elif event_name == 'qunit.testDone':
if not self._test.failed:
result.addSuccess(self._test)
result.stopTest(self._test)
self._test = None
elif event_name == 'qunit.log':
if args[1]:
return False
self._test.failed = True
result.addFailure(
self._test, self.failure_to_str(*args[2:]))
elif event_name == 'console':
print args[1]
return False
def add_error(self, result, s):
test = QUnitTest('phantomjs', 'startup')
result.startTest(test)
result.addError(test, s)
result.stopTest(test)
def failure_to_str(self, actual, expected, message, source):
if message or actual == expected:
formatted = str(message or '')
else:
formatted = "%s != %s" % (actual, expected)
if source:
formatted += '\n\n' + source
return formatted

View File

@ -1,24 +1,6 @@
import urllib
import urlparse
from openerp import sql_db, tools
from qunitsuite.suite import QUnitSuite
import openerp
class WebSuite(QUnitSuite):
def __init__(self, module):
url = urlparse.urlunsplit([
'http',
'localhost:{port}'.format(port=tools.config['xmlrpc_port']),
'/web/tests',
urllib.urlencode({
'mod': module,
'source': tools.config['db_name'],
'supadmin': tools.config['admin_passwd'],
'password': 'admin',
}),
''
])
super(WebSuite, self).__init__(url, 50000)
class WebSuite(openerp.tests.HttpCase):
def test_01_js(self):
self.phantom_js('/web/tests?mod=web',"","", login='admin')
def load_tests(loader, standard_tests, _):
standard_tests.addTest(WebSuite('web'))
return standard_tests

View File

@ -28,9 +28,7 @@ class LoadTest(common.MockRequestCase):
self.MockMenus = model('ir.ui.menu')
# Mock the absence of custom menu
model('res.users').read.return_value = [{
'menu_id': False
}]
model('res.users').read.return_value = []
def tearDown(self):
del self.MockMenus

View File

@ -3,6 +3,101 @@
-->
<openerp>
<data>
<template id="web.assets_common">
<script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/globalization/en-US.js"></script>
<script type="text/javascript" src="/web/static/lib/spinjs/spin.js"></script>
<!-- jQuery stuff -->
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.blockUI/jquery.blockUI.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.hotkeys/jquery.hotkeys.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.placeholder/jquery.placeholder.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.timeago/jquery.timeago.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.form/jquery.form.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css"/>
</template>
<template id="web.assets_backend">
<t t-call="web.assets_common"/>
<!-- Datejs -->
<script type="text/javascript" src="/web/static/lib/datejs/core.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/parser.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/sugarpak.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/extras.js"></script>
<!-- jQuery addons -->
<script type="text/javascript" src="/web/static/lib/jquery.validate/jquery.validate.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.autosize/jquery.autosize.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.scrollTo/jquery.scrollTo-min.js"></script>
<link rel="stylesheet" href="/web/static/lib/cleditor/jquery.cleditor.css"/>
<script type="text/javascript" src="/web/static/lib/cleditor/jquery.cleditor.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.textext/jquery.textext.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.textext/jquery.textext.js"></script>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
<script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
<!-- jQuery ui -->
<link rel="stylesheet" href="/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js"></script>
<link rel="stylesheet" href="/web/static/lib/jquery.ui.notify/css/ui.notify.css"/>
<script type="text/javascript" src="/web/static/lib/jquery.ui.notify/js/jquery.notify.js"></script>
<!-- Bootstrap must be loaded after jquery.ui until jquery.ui is removed -->
<link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css"/>
<script type="text/javascript" src="/web/static/lib/bootstrap/js/bootstrap.js"></script>
<!-- Backbone -->
<script type="text/javascript" src="/web/static/lib/backbone/backbone.js"></script>
<!-- Internals -->
<link rel="stylesheet" href="/web/static/src/css/base.css"/>
<link rel="stylesheet" href="/web/static/src/css/data_export.css"/>
<link rel="stylesheet" href="/base/static/src/css/modules.css"/>
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
<script type="text/javascript" src="/web/static/lib/py.js/lib/py.js"></script>
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
<script type="text/javascript" src="/web/static/src/js/testing.js"></script>
<script type="text/javascript" src="/web/static/src/js/pyeval.js"></script>
<script type="text/javascript" src="/web/static/src/js/core.js"></script>
<script type="text/javascript" src="/web/static/src/js/formats.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome.js"></script>
<script type="text/javascript" src="/web/static/src/js/views.js"></script>
<script type="text/javascript" src="/web/static/src/js/data.js"></script>
<script type="text/javascript" src="/web/static/src/js/data_export.js"></script>
<script type="text/javascript" src="/web/static/src/js/search.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_list.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_form.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_list_editable.js"></script>
<script type="text/javascript" src="/web/static/src/js/view_tree.js"></script>
<script type="text/javascript" src="/base/static/src/js/apps.js"></script>
</template>
<template id="web.assets_webclient_manifest">
<!-- This bundle can be used for module manifest asset declaration backward compatibility -->
<t t-foreach="manifest_list('css')" t-as="css_file">
<link rel="stylesheet" t-att-href="css_file"/>
</t>
<t t-foreach="manifest_list('js')" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
</template>
<template id="web.layout" name="Web layout">&lt;!DOCTYPE html&gt;
<html style="height: 100%">
@ -23,12 +118,7 @@
<template id="web.webclient_bootstrap" name="Webclient Bootstrap">
<t t-call="web.layout">
<t t-set="head">
<t t-foreach="css" t-as="css_file">
<link rel="stylesheet" t-att-href="css_file"/>
</t>
<t t-foreach="js" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
<t t-call-assets="web.assets_backend"/>
<script type="text/javascript">
$(function() {
var s = new openerp.init(<t t-raw="modules"/>);
@ -78,10 +168,10 @@
</script>
<div class="form-group field-db" t-if="databases and len(databases) &gt; 1">
<label for="db" class="control-label">Database</label>
<select name="db" id="db" class="form-control" required="required" t-att-autofocus="'autofocus' if current_db not in databases else None" onchange="dbchanged(this.value)">
<select name="db" id="db" class="form-control" required="required" t-att-autofocus="'autofocus' if request.db not in databases else None" onchange="dbchanged(this.value)">
<option></option>
<t t-foreach="databases" t-as="db">
<option t-att-selected="'selected' if db == current_db else None">
<option t-att-selected="'selected' if db == request.db else None">
<t t-esc="db"/>
</option>
</t>
@ -121,5 +211,55 @@
</t>
</template>
<template id="web.qunit_suite">
&lt;!DOCTYPE html&gt;
<html style="height: 100%">
<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>
<t t-call="web.assets_backend"/>
<script type="text/javascript" id="qunit_config">
QUnit.config.testTimeout = 5 * 60 * 1000;
QUnit.moduleDone(function(result) {
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
});
QUnit.done(function(result) {
if (result.failed === 0) {
console.log('ok');
}
});
openerp.web.qweb.add_template("/web/webclient/qweb");
</script>
<script type="text/javascript" src="/web/static/test/testing.js"></script>
<script type="text/javascript" src="/web/static/test/framework.js"></script>
<script type="text/javascript" src="/web/static/test/registry.js"></script>
<script type="text/javascript" src="/web/static/test/form.js"></script>
<script type="text/javascript" src="/web/static/test/data.js"></script>
<script type="text/javascript" src="/web/static/test/list-utils.js"></script>
<script type="text/javascript" src="/web/static/test/formats.js"></script>
<script type="text/javascript" src="/web/static/test/rpc-misordered.js"></script>
<script type="text/javascript" src="/web/static/test/evals.js"></script>
<script type="text/javascript" src="/web/static/test/search.js"></script>
<script type="text/javascript" src="/web/static/test/list.js"></script>
<script type="text/javascript" src="/web/static/test/list-editable.js"></script>
<script type="text/javascript" src="/web/static/test/mutex.js"></script>
</head>
<body id="oe" class="openerp">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
</template>
</data>
</openerp>

View File

@ -10,8 +10,4 @@ Openerp Web API.
'depends': ['web'],
'installable': True,
'auto_install': False,
'js' : [
],
'css' : [
],
}

View File

@ -9,14 +9,8 @@ OpenERP Web Calendar view.
'author': 'OpenERP SA, Valentino Lab (Kalysto)',
'version': '2.0',
'depends': ['web'],
'data' : [],
'js': [
'static/lib/fullcalendar/js/fullcalendar.js',
'static/src/js/*.js'
],
'css': [
'static/lib/fullcalendar/css/*.css',
'static/src/css/*.css'
'data' : [
'views/web_calendar.xml',
],
'qweb': [
'static/src/xml/*.xml',

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Poedit-Language: Czech\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:40+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

View File

@ -15,7 +15,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"Language: es\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-18 06:41+0000\n"
"X-Launchpad-Export-Date: 2014-04-26 06:54+0000\n"
"X-Generator: Launchpad (build 16985)\n"
#. module: web_calendar

Some files were not shown because too many files have changed in this diff Show More