[FIX] qweb: use cdn for asset files
Refactoring of qweb to call _post_processing_att for each nodes. This change remove the crappy ovewrite in website module. The website overwrite only the _post_processing_att to add the cdn parameters. The static node (without t- attributes) can stay static (remove overwrite of _is_static_node), the cdn is applied at the compile time for this nodes instead of at the running time like the dynamic node.
This commit is contained in:
committed by
Jeremy Kersten
parent
b05e4d5f95
commit
9cd982bcc8
@@ -16,7 +16,7 @@ class LivechatController(http.Controller):
|
|||||||
# _get_asset return the bundle html code (script and link list) but we want to use the attachment content
|
# _get_asset return the bundle html code (script and link list) but we want to use the attachment content
|
||||||
xmlid = 'im_livechat.external_lib'
|
xmlid = 'im_livechat.external_lib'
|
||||||
files, remains = request.env["ir.qweb"]._get_asset_content(xmlid, options=request.context)
|
files, remains = request.env["ir.qweb"]._get_asset_content(xmlid, options=request.context)
|
||||||
asset = AssetsBundle(xmlid, files, remains)
|
asset = AssetsBundle(xmlid, files)
|
||||||
|
|
||||||
mock_attachment = getattr(asset, ext)()
|
mock_attachment = getattr(asset, ext)()
|
||||||
if isinstance(mock_attachment, list): # suppose that CSS asset will not required to be split in pages
|
if isinstance(mock_attachment, list): # suppose that CSS asset will not required to be split in pages
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
from collections import OrderedDict
|
||||||
import ast
|
|
||||||
|
|
||||||
from odoo import models
|
from odoo import models
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
@@ -13,81 +12,33 @@ class QWeb(models.AbstractModel):
|
|||||||
_inherit = 'ir.qweb'
|
_inherit = 'ir.qweb'
|
||||||
|
|
||||||
URL_ATTRS = {
|
URL_ATTRS = {
|
||||||
'form': 'action',
|
'form': 'action',
|
||||||
'a': 'href',
|
'a': 'href',
|
||||||
|
'link': 'href',
|
||||||
|
'script': 'src',
|
||||||
|
'img': 'src',
|
||||||
}
|
}
|
||||||
|
|
||||||
CDN_TRIGGERS = {
|
def _post_processing_att(self, tagName, atts, options):
|
||||||
'link': 'href',
|
if atts.get('data-no-post-process'):
|
||||||
'script': 'src',
|
|
||||||
'img': 'src',
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
|
||||||
website = getattr(request, 'website', None) if request else None
|
|
||||||
if website and website.cdn_activated:
|
|
||||||
values = dict(values or {}, url_for=website.get_cdn_url)
|
|
||||||
return super(QWeb, self)._get_asset(xmlid, options, css, js, debug, async, values)
|
|
||||||
|
|
||||||
def _website_build_attribute(self, tagName, name, value, options, values):
|
|
||||||
""" Compute the value of an attribute while rendering the template. """
|
|
||||||
if name == self.URL_ATTRS.get(tagName) and values.get('url_for'):
|
|
||||||
return values.get('url_for')(value or '')
|
|
||||||
elif request and getattr(request, 'website', None) and request.website.cdn_activated and (name == self.URL_ATTRS.get(tagName) or name == self.CDN_TRIGGERS.get(tagName)):
|
|
||||||
return request.website.get_cdn_url(value or '')
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _wrap_build_attributes(self, el, items, options):
|
|
||||||
""" Map items corresponding to URL and CDN attributes to an ast expression. """
|
|
||||||
if options.get('rendering_bundle'):
|
|
||||||
return items
|
|
||||||
|
|
||||||
url_att = self.URL_ATTRS.get(el.tag)
|
|
||||||
cdn_att = self.CDN_TRIGGERS.get(el.tag)
|
|
||||||
|
|
||||||
def process(item):
|
|
||||||
if isinstance(item, tuple) and (item[0] in (url_att, cdn_att)):
|
|
||||||
return (item[0], ast.Call(
|
|
||||||
func=ast.Attribute(
|
|
||||||
value=ast.Name(id='self', ctx=ast.Load()),
|
|
||||||
attr='_website_build_attribute',
|
|
||||||
ctx=ast.Load()
|
|
||||||
),
|
|
||||||
args=[
|
|
||||||
ast.Str(el.tag),
|
|
||||||
ast.Str(item[0]),
|
|
||||||
item[1],
|
|
||||||
ast.Name(id='options', ctx=ast.Load()),
|
|
||||||
ast.Name(id='values', ctx=ast.Load()),
|
|
||||||
], keywords=[],
|
|
||||||
starargs=None, kwargs=None
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
return item
|
|
||||||
|
|
||||||
return [process(it) for it in items]
|
|
||||||
|
|
||||||
def _compile_static_attributes(self, el, options):
|
|
||||||
items = super(QWeb, self)._compile_static_attributes(el, options)
|
|
||||||
return self._wrap_build_attributes(el, items, options)
|
|
||||||
|
|
||||||
def _compile_dynamic_attributes(self, el, options):
|
|
||||||
items = super(QWeb, self)._compile_dynamic_attributes(el, options)
|
|
||||||
return self._wrap_build_attributes(el, items, options)
|
|
||||||
|
|
||||||
# method called by computing code
|
|
||||||
|
|
||||||
def _get_dynamic_att(self, tagName, atts, options, values):
|
|
||||||
atts = super(QWeb, self)._get_dynamic_att(tagName, atts, options, values)
|
|
||||||
if options.get('rendering_bundle'):
|
|
||||||
return atts
|
return atts
|
||||||
for name, value in atts.items():
|
|
||||||
atts[name] = self._website_build_attribute(tagName, name, value, options, values)
|
|
||||||
return atts
|
|
||||||
|
|
||||||
def _is_static_node(self, el):
|
atts = super(QWeb, self)._post_processing_att(tagName, atts, options)
|
||||||
url_att = self.URL_ATTRS.get(el.tag)
|
|
||||||
cdn_att = self.CDN_TRIGGERS.get(el.tag)
|
if options.get('inherit_branding') or options.get('rendering_bundle') or \
|
||||||
return super(QWeb, self)._is_static_node(el) and \
|
options.get('edit_translations') or options.get('debug') or (request and request.debug):
|
||||||
(not url_att or not el.get(url_att)) and \
|
return atts
|
||||||
(not cdn_att or not el.get(cdn_att))
|
|
||||||
|
website = request and getattr(request, 'website', None)
|
||||||
|
if not website and options.get('website_id'):
|
||||||
|
website = self.env['website'].browse(options['website_id'])
|
||||||
|
|
||||||
|
if not website or not website.cdn_activated:
|
||||||
|
return atts
|
||||||
|
|
||||||
|
name = self.URL_ATTRS.get(tagName)
|
||||||
|
if name and name in atts:
|
||||||
|
atts = OrderedDict(atts)
|
||||||
|
atts[name] = website.get_cdn_url(atts[name])
|
||||||
|
|
||||||
|
return atts
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ class Website(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def write(self, values):
|
def write(self, values):
|
||||||
self._get_languages.clear_cache(self)
|
self._get_languages.clear_cache(self)
|
||||||
return super(Website, self).write(values)
|
result = super(Website, self).write(values)
|
||||||
|
if 'cdn_activated' in values or 'cdn_url' in values or 'cdn_filters' in values:
|
||||||
|
# invalidate the caches from static node at compile time
|
||||||
|
self.env['ir.qweb'].clear_caches()
|
||||||
|
return result
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
# Page Management
|
# Page Management
|
||||||
@@ -563,15 +567,15 @@ class Website(models.Model):
|
|||||||
size = '' if size is None else '/%s' % size
|
size = '' if size is None else '/%s' % size
|
||||||
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
|
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
|
||||||
|
|
||||||
@api.model
|
|
||||||
def get_cdn_url(self, uri):
|
def get_cdn_url(self, uri):
|
||||||
# Currently only usable in a website_enable request context
|
self.ensure_one()
|
||||||
if request and request.website and not request.debug and request.website.user_id.id == request.uid:
|
if not uri:
|
||||||
cdn_url = request.website.cdn_url
|
return ''
|
||||||
cdn_filters = (request.website.cdn_filters or '').splitlines()
|
cdn_url = self.cdn_url
|
||||||
for flt in cdn_filters:
|
cdn_filters = (self.cdn_filters or '').splitlines()
|
||||||
if flt and re.match(flt, uri):
|
for flt in cdn_filters:
|
||||||
return urls.url_join(cdn_url, uri)
|
if flt and re.match(flt, uri):
|
||||||
|
return urls.url_join(cdn_url, uri)
|
||||||
return uri
|
return uri
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from . import test_converter
|
from . import test_converter
|
||||||
|
from . import test_qweb
|
||||||
from . import test_crawl
|
from . import test_crawl
|
||||||
from . import test_ui
|
from . import test_ui
|
||||||
from . import test_views
|
from . import test_views
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="website.test_bundle">
|
||||||
|
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/web/static/lib/jquery.ui/jquery-ui.css"/>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="website.test_template" name="test template 2">
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<t t-call-assets="website.test_bundle" t-js="False"/>
|
||||||
|
<meta/>
|
||||||
|
<t t-call-assets="website.test_bundle" t-css="False"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="http://test.external.link/img.png"/>
|
||||||
|
<img src="/website/static/img.png"/>
|
||||||
|
<a href="http://test.external.link/link">x</a>
|
||||||
|
<a href="/web/content/local_link">x</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import tools
|
||||||
|
from odoo.modules.module import get_module_resource
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestQweb(TransactionCase):
|
||||||
|
def _load(self, module, *args):
|
||||||
|
tools.convert_file(self.cr, 'website',
|
||||||
|
get_module_resource(module, *args),
|
||||||
|
{}, 'init', False, 'test', self.registry._assertion_report)
|
||||||
|
|
||||||
|
def test_qweb_cdn(self):
|
||||||
|
self._load('website', 'tests', 'template_qweb_test.xml')
|
||||||
|
|
||||||
|
website = self.env['website'].browse(1)
|
||||||
|
website.write({
|
||||||
|
"cdn_activated": True,
|
||||||
|
"cdn_url": "http://test.cdn"
|
||||||
|
})
|
||||||
|
|
||||||
|
demo = self.env['res.users'].search([('login', '=', 'demo')])[0]
|
||||||
|
demo_env = self.env(user=demo)
|
||||||
|
|
||||||
|
html = demo_env['ir.qweb'].render('website.test_template', {}, website_id= website.id)
|
||||||
|
|
||||||
|
attachments = demo_env['ir.attachment'].search([('url', '=like', '/web/content/%-%/website.test_bundle.%')])
|
||||||
|
self.assertEquals(len(attachments), 2)
|
||||||
|
self.assertEqual(html.strip(), """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||||
|
<link href="http://test.cdn%(css)s" rel="stylesheet"/>
|
||||||
|
<meta/>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.cdn%(js)s"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="http://test.external.link/img.png"/>
|
||||||
|
<img src="http://test.cdn/website/static/img.png"/>
|
||||||
|
<a href="http://test.external.link/link">x</a>
|
||||||
|
<a href="http://test.cdn/web/content/local_link">x</a>
|
||||||
|
</body>
|
||||||
|
</html>""" % {"js": attachments[0].url, "css": attachments[1].url})
|
||||||
@@ -9,9 +9,12 @@ import textwrap
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
from collections import OrderedDict
|
||||||
from odoo import fields, tools
|
from odoo import fields, tools
|
||||||
|
from odoo.tools.pycompat import string_types, to_text
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.modules.module import get_resource_path
|
from odoo.modules.module import get_resource_path
|
||||||
|
from odoo.addons.base.ir.ir_qweb.qweb import escape
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from odoo.tools import func, misc
|
from odoo.tools import func, misc
|
||||||
|
|
||||||
@@ -65,7 +68,6 @@ def rjsmin(script):
|
|||||||
).strip()
|
).strip()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class AssetError(Exception):
|
class AssetError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -79,17 +81,16 @@ class AssetsBundle(object):
|
|||||||
rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
|
rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
|
||||||
rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
|
rx_css_split = re.compile("\/\*\! ([a-f0-9-]+) \*\/")
|
||||||
|
|
||||||
def __init__(self, name, files, remains, env=None):
|
# remains attribute is depreciated and will remove after v11
|
||||||
|
def __init__(self, name, files, remains=None, env=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.env = request.env if env is None else env
|
self.env = request.env if env is None else env
|
||||||
self.max_css_rules = self.env.context.get('max_css_rules', MAX_CSS_RULES)
|
self.max_css_rules = self.env.context.get('max_css_rules', MAX_CSS_RULES)
|
||||||
self.javascripts = []
|
self.javascripts = []
|
||||||
self.stylesheets = []
|
self.stylesheets = []
|
||||||
self.css_errors = []
|
self.css_errors = []
|
||||||
self.remains = []
|
|
||||||
self._checksum = None
|
self._checksum = None
|
||||||
self.files = files
|
self.files = files
|
||||||
self.remains = remains
|
|
||||||
for f in files:
|
for f in files:
|
||||||
if f['atype'] == 'text/sass':
|
if f['atype'] == 'text/sass':
|
||||||
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
|
self.stylesheets.append(SassStylesheetAsset(self, url=f['url'], filename=f['filename'], inline=f['content'], media=f['media']))
|
||||||
@@ -100,10 +101,31 @@ class AssetsBundle(object):
|
|||||||
elif f['atype'] == 'text/javascript':
|
elif f['atype'] == 'text/javascript':
|
||||||
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
|
self.javascripts.append(JavascriptAsset(self, url=f['url'], filename=f['filename'], inline=f['content']))
|
||||||
|
|
||||||
|
# depreciated and will remove after v11
|
||||||
def to_html(self, sep=None, css=True, js=True, debug=False, async=False, url_for=(lambda url: url)):
|
def to_html(self, sep=None, css=True, js=True, debug=False, async=False, url_for=(lambda url: url)):
|
||||||
|
nodes = self.to_node(css=css, js=js, debug=debug, async=async)
|
||||||
|
|
||||||
if sep is None:
|
if sep is None:
|
||||||
sep = u'\n '
|
sep = u'\n '
|
||||||
response = []
|
response = []
|
||||||
|
for tagName, attributes, content in nodes:
|
||||||
|
html = u"<%s " % tagName
|
||||||
|
for name, value in attributes.items():
|
||||||
|
if value or isinstance(value, string_types):
|
||||||
|
html += u' %s="%s"' % (name, escape(to_text(value)))
|
||||||
|
if content is None:
|
||||||
|
html += u'/>'
|
||||||
|
else:
|
||||||
|
html += u'>%s</%s>' % (escape(to_text(content)), tagName)
|
||||||
|
response.append(html)
|
||||||
|
|
||||||
|
return sep + sep.join(response)
|
||||||
|
|
||||||
|
def to_node(self, css=True, js=True, debug=False, async=False):
|
||||||
|
"""
|
||||||
|
:returns [(tagName, attributes, content)] if the tag is auto close
|
||||||
|
"""
|
||||||
|
response = []
|
||||||
if debug == 'assets':
|
if debug == 'assets':
|
||||||
if css and self.stylesheets:
|
if css and self.stylesheets:
|
||||||
is_css_preprocessed, old_attachments = self.is_css_preprocessed()
|
is_css_preprocessed, old_attachments = self.is_css_preprocessed()
|
||||||
@@ -111,28 +133,37 @@ class AssetsBundle(object):
|
|||||||
self.preprocess_css(debug=debug, old_attachments=old_attachments)
|
self.preprocess_css(debug=debug, old_attachments=old_attachments)
|
||||||
if self.css_errors:
|
if self.css_errors:
|
||||||
msg = '\n'.join(self.css_errors)
|
msg = '\n'.join(self.css_errors)
|
||||||
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_html())
|
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
|
||||||
response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_html())
|
response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_node())
|
||||||
if not self.css_errors:
|
if not self.css_errors:
|
||||||
for style in self.stylesheets:
|
for style in self.stylesheets:
|
||||||
response.append(style.to_html())
|
response.append(style.to_node())
|
||||||
|
|
||||||
if js:
|
if js:
|
||||||
for jscript in self.javascripts:
|
for jscript in self.javascripts:
|
||||||
response.append(jscript.to_html())
|
response.append(jscript.to_node())
|
||||||
else:
|
else:
|
||||||
if css and self.stylesheets:
|
if css and self.stylesheets:
|
||||||
css_attachments = self.css() or []
|
css_attachments = self.css() or []
|
||||||
for attachment in css_attachments:
|
for attachment in css_attachments:
|
||||||
response.append(u'<link href="%s" rel="stylesheet"/>' % url_for(attachment.url))
|
attr = OrderedDict([
|
||||||
|
["type", "text/css"],
|
||||||
|
["rel", "stylesheet"],
|
||||||
|
["href", attachment.url],
|
||||||
|
])
|
||||||
|
response.append(("link", attr, None))
|
||||||
if self.css_errors:
|
if self.css_errors:
|
||||||
msg = '\n'.join(self.css_errors)
|
msg = '\n'.join(self.css_errors)
|
||||||
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_html())
|
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
|
||||||
if js and self.javascripts:
|
if js and self.javascripts:
|
||||||
response.append(u'<script %s type="text/javascript" src="%s"></script>' % (async and u'async="async"' or '', url_for(self.js().url)))
|
attr = OrderedDict([
|
||||||
response.extend(self.remains)
|
["async", "async" if async else None],
|
||||||
|
["type", "text/javascript"],
|
||||||
|
["src", self.js().url],
|
||||||
|
])
|
||||||
|
response.append(("script", attr, None))
|
||||||
|
|
||||||
return sep + sep.join(response)
|
return response
|
||||||
|
|
||||||
@func.lazy_property
|
@func.lazy_property
|
||||||
def last_modified(self):
|
def last_modified(self):
|
||||||
@@ -152,7 +183,7 @@ class AssetsBundle(object):
|
|||||||
Not really a full checksum.
|
Not really a full checksum.
|
||||||
We compute a SHA1 on the rendered bundle + max linked files last_modified date
|
We compute a SHA1 on the rendered bundle + max linked files last_modified date
|
||||||
"""
|
"""
|
||||||
check = u"%s%s%s" % (json.dumps(self.files, sort_keys=True), u",".join(self.remains), self.last_modified)
|
check = u"%s%s" % (json.dumps(self.files, sort_keys=True), self.last_modified)
|
||||||
return hashlib.sha1(check.encode('utf-8')).hexdigest()
|
return hashlib.sha1(check.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
def clean_attachments(self, type):
|
def clean_attachments(self, type):
|
||||||
@@ -314,7 +345,7 @@ class AssetsBundle(object):
|
|||||||
outdated = False
|
outdated = False
|
||||||
assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype))
|
assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype))
|
||||||
if assets:
|
if assets:
|
||||||
assets_domain = [('url', 'in', list(assets))]
|
assets_domain = [('url', 'in', list(assets.keys()))]
|
||||||
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
|
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
asset = assets[attachment.url]
|
asset = assets[attachment.url]
|
||||||
@@ -474,7 +505,20 @@ class WebAsset(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise AssetNotFound("Could not find %s" % self.name)
|
raise AssetNotFound("Could not find %s" % self.name)
|
||||||
|
|
||||||
|
# depreciated and will remove after v11
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
|
tagName, attributes, content = self.to_node()
|
||||||
|
html = u"<%s " % tagName
|
||||||
|
for name, value in attributes.items():
|
||||||
|
if value or isinstance(value, string_types):
|
||||||
|
html += u' %s="%s"' % (name, escape(to_text(value)))
|
||||||
|
if content is None:
|
||||||
|
html += u'/>'
|
||||||
|
else:
|
||||||
|
html += u'>%s</%s>' % (escape(to_text(content)), tagName)
|
||||||
|
return html
|
||||||
|
|
||||||
|
def to_node(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@func.lazy_property
|
@func.lazy_property
|
||||||
@@ -533,13 +577,19 @@ class JavascriptAsset(WebAsset):
|
|||||||
try:
|
try:
|
||||||
return super(JavascriptAsset, self)._fetch_content()
|
return super(JavascriptAsset, self)._fetch_content()
|
||||||
except AssetError as e:
|
except AssetError as e:
|
||||||
return "console.error(%s);" % json.dumps(str(e))
|
return u"console.error(%s);" % json.dumps(to_text(e))
|
||||||
|
|
||||||
def to_html(self):
|
def to_node(self):
|
||||||
if self.url:
|
if self.url:
|
||||||
return '<script type="text/javascript" src="%s"></script>' % (self.html_url)
|
return ("script", OrderedDict([
|
||||||
|
["type", "text/javascript"],
|
||||||
|
["src", self.html_url],
|
||||||
|
]), None)
|
||||||
else:
|
else:
|
||||||
return '<script type="text/javascript" charset="utf-8">%s</script>' % self.with_header()
|
return ("script", OrderedDict([
|
||||||
|
["type", "text/javascript"],
|
||||||
|
["charset", "utf-8"],
|
||||||
|
]), self.with_header())
|
||||||
|
|
||||||
|
|
||||||
class StylesheetAsset(WebAsset):
|
class StylesheetAsset(WebAsset):
|
||||||
@@ -595,13 +645,21 @@ class StylesheetAsset(WebAsset):
|
|||||||
content = re.sub(r' *([{}]) *', r'\1', content)
|
content = re.sub(r' *([{}]) *', r'\1', content)
|
||||||
return self.with_header(content)
|
return self.with_header(content)
|
||||||
|
|
||||||
def to_html(self):
|
def to_node(self):
|
||||||
media = (' media="%s"' % misc.html_escape(self.media)) if self.media else ''
|
|
||||||
if self.url:
|
if self.url:
|
||||||
href = self.html_url
|
attr = OrderedDict([
|
||||||
return '<link rel="stylesheet" href="%s" type="text/css"%s/>' % (href, media)
|
["type", "text/css"],
|
||||||
|
["rel", "stylesheet"],
|
||||||
|
["href", self.html_url],
|
||||||
|
["media", escape(to_text(self.media)) if self.media else None]
|
||||||
|
])
|
||||||
|
return ("link", attr, None)
|
||||||
else:
|
else:
|
||||||
return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
|
attr = OrderedDict([
|
||||||
|
["type", "text/css"],
|
||||||
|
["media", escape(to_text(self.media)) if self.media else None]
|
||||||
|
])
|
||||||
|
return ("style", attr, self.with_header())
|
||||||
|
|
||||||
|
|
||||||
class PreprocessedCSS(StylesheetAsset):
|
class PreprocessedCSS(StylesheetAsset):
|
||||||
|
|||||||
@@ -125,35 +125,122 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
if len(el):
|
if len(el):
|
||||||
raise SyntaxError("t-call-assets cannot contain children nodes")
|
raise SyntaxError("t-call-assets cannot contain children nodes")
|
||||||
|
|
||||||
# self._get_asset(xmlid, options, css=css, js=js, debug=values.get('debug'), async=async, values=values)
|
# nodes = self._get_asset(xmlid, options, css=css, js=js, debug=values.get('debug'), async=async, values=values)
|
||||||
|
#
|
||||||
|
# for index, (tagName, t_attrs, content) in enumerate(nodes):
|
||||||
|
# if index:
|
||||||
|
# append('\n ')
|
||||||
|
# append('<')
|
||||||
|
# append(tagName)
|
||||||
|
#
|
||||||
|
# self._post_processing_att(tagName, t_attrs, options)
|
||||||
|
# for name, value in t_attrs.items():
|
||||||
|
# if value or isinstance(value, string_types)):
|
||||||
|
# append(u' ')
|
||||||
|
# append(name)
|
||||||
|
# append(u'="')
|
||||||
|
# append(escape(pycompat.to_text((value)))
|
||||||
|
# append(u'"')
|
||||||
|
#
|
||||||
|
# if not content and tagName in self._void_elements:
|
||||||
|
# append('/>')
|
||||||
|
# else:
|
||||||
|
# append('>')
|
||||||
|
# if content:
|
||||||
|
# append(content)
|
||||||
|
# append('</')
|
||||||
|
# append(tagName)
|
||||||
|
# append('>')
|
||||||
|
#
|
||||||
|
space = el.getprevious() is not None and el.getprevious().tail or el.getparent().text
|
||||||
|
sep = u'\n' + space.rsplit('\n').pop()
|
||||||
return [
|
return [
|
||||||
self._append(ast.Call(
|
ast.Assign(
|
||||||
func=ast.Attribute(
|
targets=[ast.Name(id='nodes', ctx=ast.Store())],
|
||||||
value=ast.Name(id='self', ctx=ast.Load()),
|
value=ast.Call(
|
||||||
attr='_get_asset',
|
func=ast.Attribute(
|
||||||
ctx=ast.Load()
|
value=ast.Name(id='self', ctx=ast.Load()),
|
||||||
|
attr='_get_asset',
|
||||||
|
ctx=ast.Load()
|
||||||
|
),
|
||||||
|
args=[
|
||||||
|
ast.Str(el.get('t-call-assets')),
|
||||||
|
ast.Name(id='options', ctx=ast.Load()),
|
||||||
|
],
|
||||||
|
keywords=[
|
||||||
|
ast.keyword('css', self._get_attr_bool(el.get('t-css', True))),
|
||||||
|
ast.keyword('js', self._get_attr_bool(el.get('t-js', True))),
|
||||||
|
ast.keyword('debug', ast.Call(
|
||||||
|
func=ast.Attribute(
|
||||||
|
value=ast.Name(id='values', ctx=ast.Load()),
|
||||||
|
attr='get',
|
||||||
|
ctx=ast.Load()
|
||||||
|
),
|
||||||
|
args=[ast.Str('debug')],
|
||||||
|
keywords=[], starargs=None, kwargs=None
|
||||||
|
)),
|
||||||
|
ast.keyword('async', self._get_attr_bool(el.get('async', False))),
|
||||||
|
ast.keyword('values', ast.Name(id='values', ctx=ast.Load())),
|
||||||
|
],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ast.For(
|
||||||
|
target=ast.Tuple(elts=[
|
||||||
|
ast.Name(id='index', ctx=ast.Store()),
|
||||||
|
ast.Tuple(elts=[
|
||||||
|
ast.Name(id='tagName', ctx=ast.Store()),
|
||||||
|
ast.Name(id='t_attrs', ctx=ast.Store()),
|
||||||
|
ast.Name(id='content', ctx=ast.Store())
|
||||||
|
], ctx=ast.Store())
|
||||||
|
], ctx=ast.Store()),
|
||||||
|
iter=ast.Call(
|
||||||
|
func=ast.Name(id='enumerate', ctx=ast.Load()),
|
||||||
|
args=[ast.Name(id='nodes', ctx=ast.Load())],
|
||||||
|
keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
),
|
),
|
||||||
args=[
|
body=[
|
||||||
ast.Str(el.get('t-call-assets')),
|
ast.If(
|
||||||
ast.Name(id='options', ctx=ast.Load()),
|
test=ast.Name(id='index', ctx=ast.Load()),
|
||||||
],
|
body=[self._append(ast.Str(sep))],
|
||||||
keywords=[
|
orelse=[]
|
||||||
ast.keyword('css', self._get_attr_bool(el.get('t-css', True))),
|
),
|
||||||
ast.keyword('js', self._get_attr_bool(el.get('t-js', True))),
|
self._append(ast.Str(u'<')),
|
||||||
ast.keyword('debug', ast.Call(
|
self._append(ast.Name(id='tagName', ctx=ast.Load())),
|
||||||
func=ast.Attribute(
|
] + self._append_attributes() + [
|
||||||
value=ast.Name(id='values', ctx=ast.Load()),
|
ast.If(
|
||||||
attr='get',
|
test=ast.BoolOp(
|
||||||
ctx=ast.Load()
|
op=ast.And(),
|
||||||
|
values=[
|
||||||
|
ast.UnaryOp(ast.Not(), ast.Name(id='content', ctx=ast.Load()), lineno=0, col_offset=0),
|
||||||
|
ast.Compare(
|
||||||
|
left=ast.Name(id='tagName', ctx=ast.Load()),
|
||||||
|
ops=[ast.In()],
|
||||||
|
comparators=[ast.Attribute(
|
||||||
|
value=ast.Name(id='self', ctx=ast.Load()),
|
||||||
|
attr='_void_elements',
|
||||||
|
ctx=ast.Load()
|
||||||
|
)]
|
||||||
|
),
|
||||||
|
]
|
||||||
),
|
),
|
||||||
args=[ast.Str('debug')],
|
body=[self._append(ast.Str(u'/>'))],
|
||||||
keywords=[], starargs=None, kwargs=None
|
orelse=[
|
||||||
)),
|
self._append(ast.Str(u'>')),
|
||||||
ast.keyword('async', self._get_attr_bool(el.get('async', False))),
|
ast.If(
|
||||||
ast.keyword('values', ast.Name(id='values', ctx=ast.Load())),
|
test=ast.Name(id='content', ctx=ast.Load()),
|
||||||
|
body=[self._append(ast.Name(id='content', ctx=ast.Load()))],
|
||||||
|
orelse=[]
|
||||||
|
),
|
||||||
|
self._append(ast.Str(u'</')),
|
||||||
|
self._append(ast.Name(id='tagName', ctx=ast.Load())),
|
||||||
|
self._append(ast.Str(u'>')),
|
||||||
|
]
|
||||||
|
)
|
||||||
],
|
],
|
||||||
starargs=None, kwargs=None
|
orelse=[]
|
||||||
))
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# for backward compatibility to remove after v10
|
# for backward compatibility to remove after v10
|
||||||
@@ -193,8 +280,9 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
)
|
)
|
||||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||||
files, remains = self._get_asset_content(xmlid, options)
|
files, remains = self._get_asset_content(xmlid, options)
|
||||||
asset = AssetsBundle(xmlid, files, remains, env=self.env)
|
asset = AssetsBundle(xmlid, files, env=self.env)
|
||||||
return asset.to_html(css=css, js=js, debug=debug, async=async, url_for=(values or {}).get('url_for', lambda url: url))
|
remains = [node for node in remains if (css and node[0] == 'link') or (js and node[0] != 'link')]
|
||||||
|
return remains + asset.to_node(css=css, js=js, debug=debug, async=async)
|
||||||
|
|
||||||
@tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', keys=("website_id",))
|
@tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', keys=("website_id",))
|
||||||
def _get_asset_content(self, xmlid, options):
|
def _get_asset_content(self, xmlid, options):
|
||||||
@@ -205,6 +293,9 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
|
|
||||||
env = self.env(context=options)
|
env = self.env(context=options)
|
||||||
|
|
||||||
|
def can_aggregate(url):
|
||||||
|
return not urls.url_parse(url).scheme and not urls.url_parse(url).netloc and not url.startswith('/web/content')
|
||||||
|
|
||||||
# TODO: This helper can be used by any template that wants to embedd the backend.
|
# TODO: This helper can be used by any template that wants to embedd the backend.
|
||||||
# It is currently necessary because the ir.ui.view bundle inheritance does not
|
# It is currently necessary because the ir.ui.view bundle inheritance does not
|
||||||
# match the module dependency graph.
|
# match the module dependency graph.
|
||||||
@@ -218,16 +309,13 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
files = []
|
files = []
|
||||||
remains = []
|
remains = []
|
||||||
for el in html.fragments_fromstring(template):
|
for el in html.fragments_fromstring(template):
|
||||||
if isinstance(el, pycompat.string_types):
|
if isinstance(el, html.HtmlElement):
|
||||||
remains.append(pycompat.to_text(el))
|
|
||||||
elif isinstance(el, html.HtmlElement):
|
|
||||||
href = el.get('href', '')
|
href = el.get('href', '')
|
||||||
src = el.get('src', '')
|
src = el.get('src', '')
|
||||||
atype = el.get('type')
|
atype = el.get('type')
|
||||||
media = el.get('media')
|
media = el.get('media')
|
||||||
|
|
||||||
can_aggregate = not urls.url_parse(href).netloc and not href.startswith('/web/content')
|
if can_aggregate(href) and (el.tag == 'style' or (el.tag == 'link' and el.get('rel') == 'stylesheet')):
|
||||||
if el.tag == 'style' or (el.tag == 'link' and el.get('rel') == 'stylesheet' and can_aggregate):
|
|
||||||
if href.endswith('.sass'):
|
if href.endswith('.sass'):
|
||||||
atype = 'text/sass'
|
atype = 'text/sass'
|
||||||
elif href.endswith('.less'):
|
elif href.endswith('.less'):
|
||||||
@@ -237,19 +325,16 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
path = [segment for segment in href.split('/') if segment]
|
path = [segment for segment in href.split('/') if segment]
|
||||||
filename = get_resource_path(*path) if path else None
|
filename = get_resource_path(*path) if path else None
|
||||||
files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media})
|
files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media})
|
||||||
elif el.tag == 'script':
|
elif can_aggregate(src) and el.tag == 'script':
|
||||||
atype = 'text/javascript'
|
atype = 'text/javascript'
|
||||||
path = [segment for segment in src.split('/') if segment]
|
path = [segment for segment in href.split('/') if segment]
|
||||||
filename = get_resource_path(*path) if path else None
|
filename = get_resource_path(*path) if path else None
|
||||||
files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media})
|
files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media})
|
||||||
else:
|
else:
|
||||||
remains.append(html.tostring(el, encoding='unicode'))
|
remains.append((el.tag, OrderedDict(el.attrib), el.text))
|
||||||
else:
|
else:
|
||||||
try:
|
# the other cases are ignored
|
||||||
remains.append(html.tostring(el, encoding='unicode'))
|
pass
|
||||||
except Exception:
|
|
||||||
# notYETimplementederror
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
return (files, remains)
|
return (files, remains)
|
||||||
|
|
||||||
|
|||||||
@@ -695,6 +695,80 @@ class QWeb(object):
|
|||||||
ctx=ctx
|
ctx=ctx
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _append_attributes(self):
|
||||||
|
# t_attrs = self._post_processing_att(tagName, t_attrs, options)
|
||||||
|
# for name, value in t_attrs.items():
|
||||||
|
# if value or isinstance(value, string_types)):
|
||||||
|
# append(u' ')
|
||||||
|
# append(name)
|
||||||
|
# append(u'="')
|
||||||
|
# append(escape(pycompat.to_text((value)))
|
||||||
|
# append(u'"')
|
||||||
|
return [
|
||||||
|
ast.Assign(
|
||||||
|
targets=[ast.Name(id='t_attrs', ctx=ast.Store())],
|
||||||
|
value=ast.Call(
|
||||||
|
func=ast.Attribute(
|
||||||
|
value=ast.Name(id='self', ctx=ast.Load()),
|
||||||
|
attr='_post_processing_att',
|
||||||
|
ctx=ast.Load()
|
||||||
|
),
|
||||||
|
args=[
|
||||||
|
ast.Name(id='tagName', ctx=ast.Load()),
|
||||||
|
ast.Name(id='t_attrs', ctx=ast.Load()),
|
||||||
|
ast.Name(id='options', ctx=ast.Load()),
|
||||||
|
], keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ast.For(
|
||||||
|
target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()),
|
||||||
|
iter=ast.Call(
|
||||||
|
func=ast.Attribute(
|
||||||
|
value=ast.Name(id='t_attrs', ctx=ast.Load()),
|
||||||
|
attr='items',
|
||||||
|
ctx=ast.Load()
|
||||||
|
),
|
||||||
|
args=[], keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
),
|
||||||
|
body=[ast.If(
|
||||||
|
test=ast.BoolOp(
|
||||||
|
op=ast.Or(),
|
||||||
|
values=[
|
||||||
|
ast.Name(id='value', ctx=ast.Load()),
|
||||||
|
ast.Call(
|
||||||
|
func=ast.Name(id='isinstance', ctx=ast.Load()),
|
||||||
|
args=[
|
||||||
|
ast.Name(id='value', ctx=ast.Load()),
|
||||||
|
ast.Name(id='string_types', ctx=ast.Load())
|
||||||
|
],
|
||||||
|
keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
body=[
|
||||||
|
self._append(ast.Str(u' ')),
|
||||||
|
self._append(ast.Name(id='name', ctx=ast.Load())),
|
||||||
|
self._append(ast.Str(u'="')),
|
||||||
|
self._append(ast.Call(
|
||||||
|
func=ast.Name(id='escape', ctx=ast.Load()),
|
||||||
|
args=[ast.Call(
|
||||||
|
func=ast.Name(id='to_text', ctx=ast.Load()),
|
||||||
|
args=[ast.Name(id='value', ctx=ast.Load())], keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
)], keywords=[],
|
||||||
|
starargs=None, kwargs=None
|
||||||
|
)),
|
||||||
|
self._append(ast.Str(u'"')),
|
||||||
|
],
|
||||||
|
orelse=[]
|
||||||
|
)],
|
||||||
|
orelse=[]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# order
|
# order
|
||||||
|
|
||||||
def _directives_eval_order(self):
|
def _directives_eval_order(self):
|
||||||
@@ -733,7 +807,7 @@ class QWeb(object):
|
|||||||
if not el.nsmap:
|
if not el.nsmap:
|
||||||
unqualified_el_tag = el_tag = el.tag
|
unqualified_el_tag = el_tag = el.tag
|
||||||
content = self._compile_directive_content(el, options)
|
content = self._compile_directive_content(el, options)
|
||||||
attrib = el.attrib
|
attrib = self._post_processing_att(el.tag, el.attrib, options)
|
||||||
else:
|
else:
|
||||||
# Etree will remove the ns prefixes indirection by inlining the corresponding
|
# Etree will remove the ns prefixes indirection by inlining the corresponding
|
||||||
# nsmap definition into the tag attribute. Restore the tag and prefix here.
|
# nsmap definition into the tag attribute. Restore the tag and prefix here.
|
||||||
@@ -763,6 +837,8 @@ class QWeb(object):
|
|||||||
else:
|
else:
|
||||||
attrib[key] = value
|
attrib[key] = value
|
||||||
|
|
||||||
|
attrib = self._post_processing_att(el.tag, attrib, options)
|
||||||
|
|
||||||
# Update the dict of inherited namespaces before continuing the recursion. Note:
|
# Update the dict of inherited namespaces before continuing the recursion. Note:
|
||||||
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
|
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
|
||||||
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
|
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
|
||||||
@@ -871,59 +947,12 @@ class QWeb(object):
|
|||||||
)))
|
)))
|
||||||
|
|
||||||
if attr_already_created:
|
if attr_already_created:
|
||||||
# for name, value in t_attrs.items():
|
# tagName = $el.tag
|
||||||
# if value or isinstance(value, basestring)):
|
body.append(ast.Assign(
|
||||||
# append(u' ')
|
targets=[ast.Name(id='tagName', ctx=ast.Store())],
|
||||||
# append(name)
|
value=ast.Str(el.tag))
|
||||||
# append(u'="')
|
)
|
||||||
# append(escape(to_text((value)))
|
body.extend(self._append_attributes())
|
||||||
# append(u'"')
|
|
||||||
body.append(ast.For(
|
|
||||||
target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()),
|
|
||||||
iter=ast.Call(
|
|
||||||
func=ast.Attribute(
|
|
||||||
value=ast.Name(id='t_attrs', ctx=ast.Load()),
|
|
||||||
attr='items',
|
|
||||||
ctx=ast.Load()
|
|
||||||
),
|
|
||||||
args=[], keywords=[],
|
|
||||||
starargs=None, kwargs=None
|
|
||||||
),
|
|
||||||
body=[ast.If(
|
|
||||||
test=ast.BoolOp(
|
|
||||||
op=ast.Or(),
|
|
||||||
values=[
|
|
||||||
ast.Name(id='value', ctx=ast.Load()),
|
|
||||||
ast.Call(
|
|
||||||
func=ast.Name(id='isinstance', ctx=ast.Load()),
|
|
||||||
args=[
|
|
||||||
ast.Name(id='value', ctx=ast.Load()),
|
|
||||||
ast.Name(id='string_types', ctx=ast.Load())
|
|
||||||
],
|
|
||||||
keywords=[],
|
|
||||||
starargs=None, kwargs=None
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
body=[
|
|
||||||
self._append(ast.Str(u' ')),
|
|
||||||
self._append(ast.Name(id='name', ctx=ast.Load())),
|
|
||||||
self._append(ast.Str(u'="')),
|
|
||||||
self._append(ast.Call(
|
|
||||||
func=ast.Name(id='escape', ctx=ast.Load()),
|
|
||||||
args=[ast.Call(
|
|
||||||
func=ast.Name(id='to_text', ctx=ast.Load()),
|
|
||||||
args=[ast.Name(id='value', ctx=ast.Load())], keywords=[],
|
|
||||||
starargs=None, kwargs=None
|
|
||||||
)], keywords=[],
|
|
||||||
starargs=None, kwargs=None
|
|
||||||
)),
|
|
||||||
self._append(ast.Str(u'"')),
|
|
||||||
],
|
|
||||||
orelse=[]
|
|
||||||
)],
|
|
||||||
orelse=[]
|
|
||||||
))
|
|
||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@@ -1513,6 +1542,14 @@ class QWeb(object):
|
|||||||
atts = OrderedDict(atts)
|
atts = OrderedDict(atts)
|
||||||
return atts
|
return atts
|
||||||
|
|
||||||
|
def _post_processing_att(self, tagName, atts, options):
|
||||||
|
""" Method called by the compiled code. This method may be overwrited
|
||||||
|
to filter or modify the attributes after they are compiled.
|
||||||
|
|
||||||
|
@returns OrderedDict
|
||||||
|
"""
|
||||||
|
return atts
|
||||||
|
|
||||||
def _get_field(self, record, field_name, expression, tagName, field_options, options, values):
|
def _get_field(self, record, field_name, expression, tagName, field_options, options, values):
|
||||||
"""
|
"""
|
||||||
:returns: tuple:
|
:returns: tuple:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
def _get_asset(self, xmlid, env=None):
|
def _get_asset(self, xmlid, env=None):
|
||||||
env = (env or self.env)
|
env = (env or self.env)
|
||||||
files, remains = env['ir.qweb']._get_asset_content(xmlid, env.context)
|
files, remains = env['ir.qweb']._get_asset_content(xmlid, env.context)
|
||||||
return AssetsBundle(xmlid, files, remains, env=env)
|
return AssetsBundle(xmlid, files, env=env)
|
||||||
|
|
||||||
def _any_ira_for_bundle(self, type):
|
def _any_ira_for_bundle(self, type):
|
||||||
""" Returns all ir.attachments associated to a bundle, regardless of the verion.
|
""" Returns all ir.attachments associated to a bundle, regardless of the verion.
|
||||||
@@ -118,7 +118,6 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
bundle0 = self._get_asset(self.jsbundle_xmlid)
|
bundle0 = self._get_asset(self.jsbundle_xmlid)
|
||||||
bundle0.js()
|
bundle0.js()
|
||||||
files0 = bundle0.files
|
files0 = bundle0.files
|
||||||
remains0 = bundle0.remains
|
|
||||||
version0 = bundle0.version
|
version0 = bundle0.version
|
||||||
|
|
||||||
self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
|
self.assertEquals(len(self._any_ira_for_bundle('js')), 1)
|
||||||
@@ -141,11 +140,9 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
bundle1 = self._get_asset(self.jsbundle_xmlid, env=self.env(context={'check_view_ids': view.ids}))
|
bundle1 = self._get_asset(self.jsbundle_xmlid, env=self.env(context={'check_view_ids': view.ids}))
|
||||||
bundle1.js()
|
bundle1.js()
|
||||||
files1 = bundle1.files
|
files1 = bundle1.files
|
||||||
remains1 = bundle1.remains
|
|
||||||
version1 = bundle1.version
|
version1 = bundle1.version
|
||||||
|
|
||||||
self.assertNotEquals(files0, files1)
|
self.assertNotEquals(files0, files1)
|
||||||
self.assertEquals(remains0, remains1)
|
|
||||||
self.assertNotEquals(version0, version1)
|
self.assertNotEquals(version0, version1)
|
||||||
|
|
||||||
# check if the previous attachment are correctly cleaned
|
# check if the previous attachment are correctly cleaned
|
||||||
@@ -247,7 +244,6 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'max_css_rules': 1}))
|
||||||
bundle0.css()
|
bundle0.css()
|
||||||
files0 = bundle0.files
|
files0 = bundle0.files
|
||||||
remains0 = bundle0.remains
|
|
||||||
version0 = bundle0.version
|
version0 = bundle0.version
|
||||||
|
|
||||||
self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
|
self.assertEquals(len(self._any_ira_for_bundle('css')), 3)
|
||||||
@@ -270,11 +266,9 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': view.ids, 'max_css_rules': 1}))
|
bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'check_view_ids': view.ids, 'max_css_rules': 1}))
|
||||||
bundle1.css()
|
bundle1.css()
|
||||||
files1 = bundle1.files
|
files1 = bundle1.files
|
||||||
remains1 = bundle1.remains
|
|
||||||
version1 = bundle1.version
|
version1 = bundle1.version
|
||||||
|
|
||||||
self.assertNotEquals(files0, files1)
|
self.assertNotEquals(files0, files1)
|
||||||
self.assertEquals(remains0, remains1)
|
|
||||||
self.assertNotEquals(version0, version1)
|
self.assertNotEquals(version0, version1)
|
||||||
|
|
||||||
# check if the previous attachment are correctly cleaned
|
# check if the previous attachment are correctly cleaned
|
||||||
@@ -318,6 +312,48 @@ class TestJavascriptAssetsBundle(TransactionCase):
|
|||||||
content = bundle0.to_html()
|
content = bundle0.to_html()
|
||||||
self.assertEqual(content.count('test_assetsbundle.bundle2.0.css'), 1)
|
self.assertEqual(content.count('test_assetsbundle.bundle2.0.css'), 1)
|
||||||
|
|
||||||
|
def test_15_exteral_lib_assets(self):
|
||||||
|
html = self.env['ir.ui.view'].render_template('test_assetsbundle.template2')
|
||||||
|
attachments = self.env['ir.attachment'].search([('url', '=like', '/web/content/%-%/test_assetsbundle.bundle4.%')])
|
||||||
|
self.assertEquals(len(attachments), 2)
|
||||||
|
self.assertEqual(html.strip(), ("""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||||
|
<link type="text/css" rel="stylesheet" href="%(css)s"/>
|
||||||
|
<meta/>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||||
|
<script type="text/javascript" src="%(js)s"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>""" % {"js": attachments[0].url, "css": attachments[1].url}).encode('utf8'))
|
||||||
|
|
||||||
|
def test_16_exteral_lib_assets_debug_mode(self):
|
||||||
|
html = self.env['ir.ui.view'].render_template('test_assetsbundle.template2', {"debug": "assets"})
|
||||||
|
attachments = self.env['ir.attachment'].search([('url', '=like', '/web/content/%-%/test_assetsbundle.bundle4.%')])
|
||||||
|
self.assertEquals(len(attachments), 0)
|
||||||
|
self.assertEqual(html.strip(), ("""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile1.css"/>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile2.css"/>
|
||||||
|
<meta/>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile1.js"></script>
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile2.js"></script>
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile3.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>""").encode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestAssetsBundleInBrowser(HttpCase):
|
class TestAssetsBundleInBrowser(HttpCase):
|
||||||
def test_01_js_interpretation(self):
|
def test_01_js_interpretation(self):
|
||||||
|
|||||||
@@ -23,4 +23,33 @@
|
|||||||
<template id="test_assetsbundle.bundle3">
|
<template id="test_assetsbundle.bundle3">
|
||||||
<link rel="stylesheet" href="/test_assetsbundle/static/src/less/test_lessfile1.less"/>
|
<link rel="stylesheet" href="/test_assetsbundle/static/src/less/test_lessfile1.less"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template id="test_assetsbundle.bundle4">
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile1.js"></script>
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile2.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile1.css"/>
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||||
|
<link rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile2.css"/>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/test_assetsbundle/static/src/js/test_jsfile3.js"></script>
|
||||||
|
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="test_assetsbundle.template2" name="test template 2">
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<t t-call-assets="test_assetsbundle.bundle4" t-js="False"/>
|
||||||
|
<meta/>
|
||||||
|
<t t-call-assets="test_assetsbundle.bundle4" t-css="False"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</template>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
Reference in New Issue
Block a user