summaryrefslogtreecommitdiff
path: root/addons/web/controllers/main.py
diff options
context:
space:
mode:
authorVidhin Mehta (OpenERP) <vme@tinyerp.com>2014-05-05 10:02:33 +0530
committerVidhin Mehta (OpenERP) <vme@tinyerp.com>2014-05-05 10:02:33 +0530
commite41508cb8d7fd2a2c06e3fab9ce1503e8efa30fa (patch)
tree8e0fbb849cebc9b3ac0f87355a5248f51826a491 /addons/web/controllers/main.py
parent4312b921f3db41529cbd4f501a6b5dcba2bebd08 (diff)
parent8fbebfd752fa8933584711522675d354b3217839 (diff)
[MERGE]Trunk.
bzr revid: vme@tinyerp.com-20140505043233-2t2e2vb1yv86hyxp
Diffstat (limited to 'addons/web/controllers/main.py')
-rw-r--r--addons/web/controllers/main.py301
1 files changed, 107 insertions, 194 deletions
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index f2101b27fd3..6dcc069fcc2 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -31,6 +31,7 @@ except ImportError:
import openerp
import openerp.modules.registry
+from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
from openerp.tools.translate import _
from openerp import http
@@ -52,50 +53,6 @@ env.filters["json"] = simplejson.dumps
# OpenERP Web helpers
#----------------------------------------------------------
-def rjsmin(script):
- """ Minify js with a clever regex.
- Taken from http://opensource.perlig.de/rjsmin
- Apache License, Version 2.0 """
- def subber(match):
- """ Substitution callback """
- groups = match.groups()
- return (
- groups[0] or
- groups[1] or
- groups[2] or
- groups[3] or
- (groups[4] and '\n') or
- (groups[5] and ' ') or
- (groups[6] and ' ') or
- (groups[7] and ' ') or
- ''
- )
-
- result = re.sub(
- r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
- r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
- r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
- r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
- r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
- r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
- r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
- r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
- r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
- r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
- r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
- r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
- r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
- r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
- r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
- r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
- r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
- r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
- r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
- r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
- r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
- ).strip()
- return result
-
db_list = http.db_list
db_monodb = http.db_monodb
@@ -309,45 +266,6 @@ def concat_xml(file_list):
root.append(child)
return ElementTree.tostring(root, 'utf-8'), checksum.hexdigest()
-def concat_files(file_list, reader=None, intersperse=""):
- """ Concatenates contents of all provided files
-
- :param list(str) file_list: list of files to check
- :param function reader: reading procedure for each file
- :param str intersperse: string to intersperse between file contents
- :returns: (concatenation_result, checksum)
- :rtype: (str, str)
- """
- checksum = hashlib.new('sha1')
- if not file_list:
- return '', checksum.hexdigest()
-
- if reader is None:
- def reader(f):
- import codecs
- with codecs.open(f, 'rb', "utf-8-sig") as fp:
- return fp.read().encode("utf-8")
-
- files_content = []
- for fname in file_list:
- contents = reader(fname)
- checksum.update(contents)
- files_content.append(contents)
-
- files_concat = intersperse.join(files_content)
- return files_concat, checksum.hexdigest()
-
-concat_js_cache = {}
-
-def concat_js(file_list):
- content, checksum = concat_files(file_list, intersperse=';')
- if checksum in concat_js_cache:
- content = concat_js_cache[checksum]
- else:
- content = rjsmin(content)
- concat_js_cache[checksum] = content
- return content, checksum
-
def fs2web(path):
"""convert FS path into web path"""
return '/'.join(path.split(os.path.sep))
@@ -371,30 +289,17 @@ def manifest_glob(extension, addons=None, db=None, include_remotes=False):
r.append((None, pattern))
else:
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
- # Hack for IE, who limit 288Ko, 4095 rules, 31 sheets
- # http://support.microsoft.com/kb/262161/en
- if pattern == "static/lib/bootstrap/css/bootstrap.css":
- if include_remotes:
- r.insert(0, (None, fs2web(path[len(addons_path):])))
- else:
- r.append((path, fs2web(path[len(addons_path):])))
+ r.append((path, fs2web(path[len(addons_path):])))
return r
-def manifest_list(extension, mods=None, db=None, debug=False):
+def manifest_list(extension, mods=None, db=None, debug=None):
""" list ressources to load specifying either:
mods: a comma separated string listing modules
db: a database name (return all installed modules in that database)
"""
+ if debug is not None:
+ _logger.warning("openerp.addons.web.main.manifest_list(): debug parameter is deprecated")
files = manifest_glob(extension, addons=mods, db=db, include_remotes=True)
- if not debug:
- path = '/web/webclient/' + extension
- if mods is not None:
- path += '?' + werkzeug.url_encode({'mods': mods})
- elif db:
- path += '?' + werkzeug.url_encode({'db': db})
-
- remotes = [wp for fp, wp in files if fp is None]
- return [path] + remotes
return [wp for _fp, wp in files]
def get_last_modified(files):
@@ -411,7 +316,7 @@ def get_last_modified(files):
for f in files)
return datetime.datetime(1970, 1, 1)
-def make_conditional(response, last_modified=None, etag=None):
+def make_conditional(response, last_modified=None, etag=None, max_age=0):
""" Makes the provided response conditional based upon the request,
and mandates revalidation from clients
@@ -426,7 +331,7 @@ def make_conditional(response, last_modified=None, etag=None):
:rtype: werkzeug.wrappers.Response
"""
response.cache_control.must_revalidate = True
- response.cache_control.max_age = 0
+ response.cache_control.max_age = max_age
if last_modified:
response.last_modified = last_modified
if etag:
@@ -603,6 +508,7 @@ html_template = """<!DOCTYPE html>
<title>OpenERP</title>
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/web/static/src/css/full.css" />
+
%(css)s
%(js)s
<script type="text/javascript">
@@ -621,26 +527,16 @@ html_template = """<!DOCTYPE html>
</html>
"""
-def render_bootstrap_template(template, values=None, debug=False, db=None, **kw):
- if not db:
- db = request.db
- if request.debug:
- debug = True
+def render_bootstrap_template(template, values=None, **kw):
if values is None:
- values = {}
- values['debug'] = debug
- values['current_db'] = db
+ values = dict()
try:
values['databases'] = http.db_list()
except openerp.exceptions.AccessDenied:
values['databases'] = None
- for res in ['js', 'css']:
- if res not in values:
- values[res] = manifest_list(res, db=db, debug=debug)
-
if 'modules' not in values:
- values['modules'] = module_boot(db=db)
+ values['modules'] = module_boot()
values['modules'] = simplejson.dumps(values['modules'])
return request.render(template, values, **kw)
@@ -682,9 +578,11 @@ class Home(http.Controller):
redirect = '/web?' + request.httprequest.query_string
values['redirect'] = redirect
if request.httprequest.method == 'POST':
+ old_uid = request.uid
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
if uid is not False:
return http.redirect_with_hash(redirect)
+ request.uid = old_uid
values['error'] = "Wrong login/password"
return render_bootstrap_template('web.login', values)
@@ -692,83 +590,47 @@ class Home(http.Controller):
def login(self, db, login, key, redirect="/web", **kw):
return login_and_redirect(db, login, key, redirect_url=redirect)
-class WebClient(http.Controller):
-
- @http.route('/web/webclient/csslist', type='json', auth="none")
- def csslist(self, mods=None):
- return manifest_list('css', mods=mods)
-
- @http.route('/web/webclient/jslist', type='json', auth="none")
- def jslist(self, mods=None):
- return manifest_list('js', mods=mods)
-
- @http.route('/web/webclient/qweblist', type='json', auth="none")
- def qweblist(self, mods=None):
- return manifest_list('qweb', mods=mods)
-
- @http.route('/web/webclient/css', type='http', auth="none")
- def css(self, mods=None, db=None):
- files = list(manifest_glob('css', addons=mods, db=db))
- last_modified = get_last_modified(f[0] for f in files)
- if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
- return werkzeug.wrappers.Response(status=304)
-
- file_map = dict(files)
-
- rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
- rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
-
- def reader(f):
- """read the a css file and absolutify all relative uris"""
- with open(f, 'rb') as fp:
- data = fp.read().decode('utf-8')
-
- path = file_map[f]
- web_dir = os.path.dirname(path)
-
- data = re.sub(
- rx_import,
- r"""@import \1%s/""" % (web_dir,),
- data,
- )
-
- data = re.sub(
- rx_url,
- r"url(\1%s/" % (web_dir,),
- data,
- )
- return data.encode('utf-8')
+ @http.route('/web/js/<xmlid>', type='http', auth="public")
+ def js_bundle(self, xmlid, **kw):
+ # manifest backward compatible mode, to be removed
+ values = {'manifest_list': manifest_list}
+ try:
+ assets_html = request.render(xmlid, lazy=False, qcontext=values)
+ except QWebTemplateNotFound:
+ return request.not_found()
+ bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
- content, checksum = concat_files((f[0] for f in files), reader)
+ response = request.make_response(
+ bundle.js(), [('Content-Type', 'application/javascript')])
- # move up all @import and @charset rules to the top
- matches = []
- def push(matchobj):
- matches.append(matchobj.group(0))
- return ''
+ # TODO: check that we don't do weird lazy overriding of __call__ which break body-removal
+ return make_conditional(
+ response, bundle.last_modified, bundle.checksum, max_age=60*60*24)
- content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
- content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
+ @http.route('/web/css/<xmlid>', type='http', auth='public')
+ def css_bundle(self, xmlid, **kw):
+ values = {'manifest_list': manifest_list} # manifest backward compatible mode, to be removed
+ try:
+ assets_html = request.render(xmlid, lazy=False, qcontext=values)
+ except QWebTemplateNotFound:
+ return request.not_found()
+ bundle = AssetsBundle(xmlid, assets_html, debug=request.debug)
- matches.append(content)
- content = '\n'.join(matches)
+ response = request.make_response(
+ bundle.css(), [('Content-Type', 'text/css')])
return make_conditional(
- request.make_response(content, [('Content-Type', 'text/css')]),
- last_modified, checksum)
+ response, bundle.last_modified, bundle.checksum, max_age=60*60*24)
- @http.route('/web/webclient/js', type='http', auth="none")
- def js(self, mods=None, db=None):
- files = [f[0] for f in manifest_glob('js', addons=mods, db=db)]
- last_modified = get_last_modified(files)
- if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
- return werkzeug.wrappers.Response(status=304)
+class WebClient(http.Controller):
- content, checksum = concat_js(files)
+ @http.route('/web/webclient/csslist', type='json', auth="none")
+ def csslist(self, mods=None):
+ return manifest_list('css', mods=mods)
- return make_conditional(
- request.make_response(content, [('Content-Type', 'application/javascript')]),
- last_modified, checksum)
+ @http.route('/web/webclient/jslist', type='json', auth="none")
+ def jslist(self, mods=None):
+ return manifest_list('js', mods=mods)
@http.route('/web/webclient/qweb', type='http', auth="none")
def qweb(self, mods=None, db=None):
@@ -843,6 +705,10 @@ class WebClient(http.Controller):
def version_info(self):
return openerp.service.common.exp_version()
+ @http.route('/web/tests', type='http', auth="none")
+ def index(self, mod=None, **kwargs):
+ return request.render('web.qunit_suite')
+
class Proxy(http.Controller):
@http.route('/web/proxy/load', type='json', auth="none")
@@ -879,8 +745,64 @@ class Database(http.Controller):
def manager(self, **kw):
# TODO: migrate the webclient's database manager to server side views
request.session.logout()
- js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', debug=request.debug))
- css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', debug=request.debug))
+ css = """
+ <link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
+ <link href="/web/static/lib/cleditor/jquery.cleditor.css" rel="stylesheet"/>
+ <link href="/web/static/lib/jquery.textext/jquery.textext.css" rel="stylesheet"/>
+ <link href="/web/static/lib/select2/select2.css" rel="stylesheet"/>
+ <link href="/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css" rel="stylesheet"/>
+ <link href="/web/static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css" rel="stylesheet"/>
+ <link href="/web/static/lib/jquery.ui.notify/css/ui.notify.css" rel="stylesheet"/>
+ <link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
+ <link href="/web/static/src/css/base.css" rel="stylesheet"/>
+ <link href="/web/static/src/css/data_export.css" rel="stylesheet"/>
+ <link href="/base/static/src/css/modules.css" rel="stylesheet"/>"""
+ js = """
+ <script src="/web/static/lib/es5-shim/es5-shim.min.js" type="text/javascript"></script>
+ <script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script>
+ <script src="/web/static/lib/underscore.string/lib/underscore.string.js" type="text/javascript"></script>
+ <script src="/web/static/lib/datejs/globalization/en-US.js" type="text/javascript"></script>
+ <script src="/web/static/lib/spinjs/spin.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery/jquery.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.blockUI/jquery.blockUI.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.hotkeys/jquery.hotkeys.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.placeholder/jquery.placeholder.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.timeago/jquery.timeago.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.form/jquery.form.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js" type="text/javascript"></script>
+ <script src="/web/static/lib/datejs/core.js" type="text/javascript"></script>
+ <script src="/web/static/lib/datejs/parser.js" type="text/javascript"></script>
+ <script src="/web/static/lib/datejs/sugarpak.js" type="text/javascript"></script>
+ <script src="/web/static/lib/datejs/extras.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.validate/jquery.validate.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.autosize/jquery.autosize.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.scrollTo/jquery.scrollTo-min.js" type="text/javascript"></script>
+ <script src="/web/static/lib/cleditor/jquery.cleditor.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.textext/jquery.textext.js" type="text/javascript"></script>
+ <script src="/web/static/lib/select2/select2.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js" type="text/javascript"></script>
+ <script src="/web/static/lib/jquery.ui.notify/js/jquery.notify.js" type="text/javascript"></script>
+ <script src="/web/static/lib/bootstrap/js/bootstrap.js" type="text/javascript"></script>
+ <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
+ <script src="/web/static/lib/qweb/qweb2.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/openerpframework.js" type="text/javascript"></script>
+ <script src="/web/static/lib/py.js/lib/py.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/boot.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/testing.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/pyeval.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/core.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/formats.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/chrome.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/views.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/data.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/data_export.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/search.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/view_list.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/view_form.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/view_list_editable.js" type="text/javascript"></script>
+ <script src="/web/static/src/js/view_tree.js" type="text/javascript"></script>
+ <script src="/base/static/src/js/apps.js" type="text/javascript"></script>"""
r = html_template % {
'js': js,
@@ -1088,16 +1010,7 @@ class Menu(http.Controller):
"""
s = request.session
Menus = s.model('ir.ui.menu')
- # If a menu action is defined use its domain to get the root menu items
- user_menu_id = s.model('res.users').read([s.uid], ['menu_id'],
- request.context)[0]['menu_id']
-
menu_domain = [('parent_id', '=', False)]
- if user_menu_id:
- domain_string = s.model('ir.actions.act_window').read(
- [user_menu_id[0]], ['domain'],request.context)[0]['domain']
- if domain_string:
- menu_domain = ast.literal_eval(domain_string)
return Menus.search(menu_domain, 0, False, False, request.context)