diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 391dbf610e9..da58455d4be 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -5,10 +5,12 @@ import datetime import json import logging import math +import os import re import sys import xml # FIXME use lxml and etree import lxml +from urlparse import urlparse import babel import babel.dates @@ -388,42 +390,18 @@ class QWeb(orm.AbstractModel): return self.render(cr, uid, self.eval_format(template_attributes["call"], d), d) def render_tag_call_assets(self, element, template_attributes, generated_attributes, qwebcontext): - template_attributes['call'] = template_attributes['call-assets'] + """ This special 't-call' tag can be used in order to aggregate/minify javascript and css assets""" + name = template_attributes['call-assets'] + template_attributes['call'] = name + + # Backward compatibility hack for manifest usage + qwebcontext['manifest_list'] = openerp.addons.web.controllers.main.manifest_list + content = self.render_tag_call(element, template_attributes, generated_attributes, qwebcontext) - html = lxml.html.fragment_fromstring('%s' % content) # avoid this hack if possible - - tags = dict() - current_style = None - current_script = None - - for el in html: - if el.tag == 'link' and el.attrib.get('rel') == 'stylesheet': - cssfile = el.attrib.get('href') - if current_style is None: - current_style = el - tags[current_style] = [] - el.attrib['href'] = '/web/css/' - else: - html.remove(el) - tags[current_style].append(cssfile) - if el.tag == 'script': - if 'src' not in el.attrib: - current_script = None - else: - jsfile = el.attrib['src'] - if current_script is None: - current_script = el - tags[current_script] = [] - el.attrib['src'] = '/web/js/' - else: - html.remove(el) - tags[current_script].append(jsfile) - - for tag, files in tags.items(): - attr = 'href' if 'href' in tag.attrib else 'src' - tag.attrib[attr] += openerp.http.pathlist_to_base64(files) - r = lxml.html.tostring(html)[3:-4] # avoid this hack if possible - return r + if qwebcontext.get('debug'): + return content + bundle = AssetsBundle(name, html=content) + return bundle.to_html() def render_tag_set(self, element, template_attributes, generated_attributes, qwebcontext): if "value" in template_attributes: @@ -848,7 +826,6 @@ class RelativeDatetimeConverter(osv.AbstractModel): return babel.dates.format_timedelta( value - reference, add_direction=True, locale=locale) - class Contact(orm.AbstractModel): _name = 'ir.qweb.field.contact' _inherit = 'ir.qweb.field.many2one' @@ -923,4 +900,82 @@ def get_field_type(column, options): """ return options.get('widget', column._type) +class AssetsBundle(object): + def __init__(self, xmlid, html=None): + self.xmlid = xmlid + self.javascripts = [] + self.stylesheets = [] + self.remains = [] + if html: + self.parse(html) + + def parse(self, html): + fragments = lxml.html.fragments_fromstring(html) + for el in fragments: + if type(el) is basestring: + self.remains.append(el) + elif type(el) is lxml.html.HtmlElement: + src = el.attrib.get('src') + href = el.attrib.get('href') + if el.tag == 'style': + self.stylesheets.append(StylesheetAsset(inline=el.text)) + elif el.tag == 'link' and el.attrib['rel'] == 'stylesheet' and self.is_internal_url(href): + self.stylesheets.append(StylesheetAsset(url=href)) + elif el.tag == 'script' and not src: + self.javascripts.append(JavascriptAsset(inline=el.text)) + elif el.tag == 'script' and self.is_internal_url(src): + self.javascripts.append(JavascriptAsset(url=src)) + else: + self.remains.append(lxml.html.tostring(el)) + else: + try: + self.remains.append(lxml.html.tostring(el)) + except Exception: + raise NotImplementedError + + def is_internal_url(self, url): + return not urlparse(url).netloc + + def to_html(self, sep='\n'): + response = list(self.remains) + if self.stylesheets: + response.insert(0, '' % self.xmlid) + if self.javascripts: + response.insert(0, '' % self.xmlid) + return sep.join(response) + + def js(self): + content = '\n'.join([asset.get_content() for asset in self.javascripts]) + return content + + def css(self): + content = '\n'.join([asset.get_content() for asset in self.stylesheets]) + return content + +class WebAsset(object): + def __init__(self, source=None, url=None, filename=None): + self.source = source + self.filename = filename + self.url = url + + def get_content(self): + if self.source: + return self.source + if self.url: + module = filter(bool, self.url.split('/'))[0] + mpath = openerp.http.addons_manifest[module]['addons_path'] + self.filename = mpath + self.url.replace('/', os.path.sep) + with open(self.filename, 'rb') as fp: + data = fp.read().decode('utf-8') + return data + +class JavascriptAsset(WebAsset): + def get_content(self): + content = super(JavascriptAsset, self).get_content() + return content + ';' + +class StylesheetAsset(WebAsset): + pass + + # vim:et: diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 7c857e767ba..b6d785f5054 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -858,7 +858,8 @@ class view(osv.osv): values = dict() qcontext = dict( keep_query=keep_query, - request=request, + request=request, # might be unbound if we're not in an httprequest context + debug=request.debug if request else False, json=simplejson, quote_plus=werkzeug.url_quote_plus, ) diff --git a/openerp/http.py b/openerp/http.py index ccff43ed271..2c3e8d93c52 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -3,7 +3,6 @@ # OpenERP HTTP layer #---------------------------------------------------------- import ast -import base64 import collections import contextlib import errno @@ -138,51 +137,6 @@ def redirect_with_hash(url, code=303): return werkzeug.utils.redirect(url, code) return "" % url -def pathlist_to_base64(files): - """ Serialize a path list to an url-safe string trying to avoid path - duplication while keeping files order. - """ - seen = [] - concat = [] - current_path = None - for f in files: - if f in seen: - continue - seen.append(f) - path, fname = os.path.split(f) - if not path: - path = '/' - if path != current_path: - current_path = path - concat.append(path) - concat.append(fname) - r = '|'.join(concat) - return base64.urlsafe_b64encode(r.encode('zlib')) - -def base64_to_pathlist(s, fspath=False): - """ Deserialize a string generated by filelist_to_base64 - - :param fspath: returns tuple(filesystem path, url path) if True - """ - files = [] - decoded = base64.urlsafe_b64decode(str(s)).decode('zlib') - items = decoded.split('|') - current_path = '/' - while items: - item = items.pop(0) - if item.startswith('/'): - current_path = item - else: - path = current_path + '/' + item - if fspath: - module = filter(bool, path.split('/'))[0] - mpath = addons_manifest[module]['addons_path'] - fs_path = mpath + path.replace('/', os.path.sep) - files.append((fs_path, path)) - else: - files.append(path) - return files - class WebRequest(object): """ Parent class for all OpenERP Web request types, mostly deals with initialization and setup of the request object (the dispatching itself has @@ -613,7 +567,7 @@ class HttpRequest(WebRequest): response.set_cookie(k, v) return response - def render(self, template, qcontext=None, **kw): + def render(self, template, qcontext=None, lazy=True, **kw): """ Lazy render of QWeb template. The actual rendering of the given template will occur at then end of @@ -622,8 +576,12 @@ class HttpRequest(WebRequest): :param basestring template: template to render :param dict qcontext: Rendering context to use + :param dict lazy: Lazy rendering is processed later in wsgi response layer (default True) """ - return Response(template=template, qcontext=qcontext, **kw) + response = Response(template=template, qcontext=qcontext, **kw) + if not lazy: + return response.render() + return response def not_found(self, description=None): """ Helper for 404 response, return its result from the method