[WIP] AssetsBundle
bzr revid: fme@openerp.com-20140409161758-3hye6u21rjmoiio9
This commit is contained in:
parent
cbb5488f97
commit
c1dc467701
|
@ -5,10 +5,12 @@ import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import xml # FIXME use lxml and etree
|
import xml # FIXME use lxml and etree
|
||||||
import lxml
|
import lxml
|
||||||
|
from urlparse import urlparse
|
||||||
|
|
||||||
import babel
|
import babel
|
||||||
import babel.dates
|
import babel.dates
|
||||||
|
@ -388,42 +390,18 @@ class QWeb(orm.AbstractModel):
|
||||||
return self.render(cr, uid, self.eval_format(template_attributes["call"], d), d)
|
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):
|
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)
|
content = self.render_tag_call(element, template_attributes, generated_attributes, qwebcontext)
|
||||||
html = lxml.html.fragment_fromstring('<s>%s</s>' % content) # avoid this hack if possible
|
if qwebcontext.get('debug'):
|
||||||
|
return content
|
||||||
tags = dict()
|
bundle = AssetsBundle(name, html=content)
|
||||||
current_style = None
|
return bundle.to_html()
|
||||||
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
|
|
||||||
|
|
||||||
def render_tag_set(self, element, template_attributes, generated_attributes, qwebcontext):
|
def render_tag_set(self, element, template_attributes, generated_attributes, qwebcontext):
|
||||||
if "value" in template_attributes:
|
if "value" in template_attributes:
|
||||||
|
@ -848,7 +826,6 @@ class RelativeDatetimeConverter(osv.AbstractModel):
|
||||||
return babel.dates.format_timedelta(
|
return babel.dates.format_timedelta(
|
||||||
value - reference, add_direction=True, locale=locale)
|
value - reference, add_direction=True, locale=locale)
|
||||||
|
|
||||||
|
|
||||||
class Contact(orm.AbstractModel):
|
class Contact(orm.AbstractModel):
|
||||||
_name = 'ir.qweb.field.contact'
|
_name = 'ir.qweb.field.contact'
|
||||||
_inherit = 'ir.qweb.field.many2one'
|
_inherit = 'ir.qweb.field.many2one'
|
||||||
|
@ -923,4 +900,82 @@ def get_field_type(column, options):
|
||||||
"""
|
"""
|
||||||
return options.get('widget', column._type)
|
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, '<link href="/web/css/%s" rel="stylesheet"/>' % self.xmlid)
|
||||||
|
if self.javascripts:
|
||||||
|
response.insert(0, '<script type="text/javascript" src="/web/js/%s"></script>' % 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:
|
# vim:et:
|
||||||
|
|
|
@ -858,7 +858,8 @@ class view(osv.osv):
|
||||||
values = dict()
|
values = dict()
|
||||||
qcontext = dict(
|
qcontext = dict(
|
||||||
keep_query=keep_query,
|
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,
|
json=simplejson,
|
||||||
quote_plus=werkzeug.url_quote_plus,
|
quote_plus=werkzeug.url_quote_plus,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# OpenERP HTTP layer
|
# OpenERP HTTP layer
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
import ast
|
import ast
|
||||||
import base64
|
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import errno
|
import errno
|
||||||
|
@ -138,51 +137,6 @@ def redirect_with_hash(url, code=303):
|
||||||
return werkzeug.utils.redirect(url, code)
|
return werkzeug.utils.redirect(url, code)
|
||||||
return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
|
return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % 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):
|
class WebRequest(object):
|
||||||
""" Parent class for all OpenERP Web request types, mostly deals with
|
""" Parent class for all OpenERP Web request types, mostly deals with
|
||||||
initialization and setup of the request object (the dispatching itself has
|
initialization and setup of the request object (the dispatching itself has
|
||||||
|
@ -613,7 +567,7 @@ class HttpRequest(WebRequest):
|
||||||
response.set_cookie(k, v)
|
response.set_cookie(k, v)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def render(self, template, qcontext=None, **kw):
|
def render(self, template, qcontext=None, lazy=True, **kw):
|
||||||
""" Lazy render of QWeb template.
|
""" Lazy render of QWeb template.
|
||||||
|
|
||||||
The actual rendering of the given template will occur at then end of
|
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 basestring template: template to render
|
||||||
:param dict qcontext: Rendering context to use
|
: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):
|
def not_found(self, description=None):
|
||||||
""" Helper for 404 response, return its result from the method
|
""" Helper for 404 response, return its result from the method
|
||||||
|
|
Loading…
Reference in New Issue