response.include_files is now cleaner and easier to maintain

You can specify a tuple of (type, url) to include external assets
without extension (such as the usecase described in #628)

Added tests for include_files, that was never included in CI tests
This commit is contained in:
niphlod
2015-05-08 21:51:56 +02:00
parent 587ff56a94
commit 5c167907eb
3 changed files with 161 additions and 27 deletions
+36 -27
View File
@@ -26,6 +26,8 @@ import gluon.settings as settings
from gluon.utils import web2py_uuid, secure_dumps, secure_loads from gluon.utils import web2py_uuid, secure_dumps, secure_loads
from gluon.settings import global_settings from gluon.settings import global_settings
from gluon import recfile from gluon import recfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
import hashlib import hashlib
import portalocker import portalocker
try: try:
@@ -47,8 +49,7 @@ import cgi
import urlparse import urlparse
import copy import copy
import tempfile import tempfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
FMT = '%a, %d-%b-%Y %H:%M:%S PST' FMT = '%a, %d-%b-%Y %H:%M:%S PST'
PAST = 'Sat, 1-Jan-1971 00:00:00' PAST = 'Sat, 1-Jan-1971 00:00:00'
@@ -82,13 +83,22 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
css_inline = '<style type="text/css">\n%s\n</style>' css_inline = '<style type="text/css">\n%s\n</style>'
js_inline = '<script type="text/javascript">\n%s\n</script>' js_inline = '<script type="text/javascript">\n%s\n</script>'
template_mapping = {
'css': css_template,
'js': js_template,
'coffee': coffee_template,
'ts': typescript_template,
'less': less_template,
'css:inline': css_inline,
'js:inline': js_inline
}
# IMPORTANT: # IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__ # this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes # are sorted and web2py can detect without ambiguity when a session changes
class SortingPickler(Pickler): class SortingPickler(Pickler):
def save_dict(self, obj): def save_dict(self, obj):
self.write(EMPTY_DICT if self.bin else MARK+DICT) self.write(EMPTY_DICT if self.bin else MARK + DICT)
self.memoize(obj) self.memoize(obj)
self._batch_setitems([(key, obj[key]) for key in sorted(obj)]) self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
@@ -275,7 +285,7 @@ class Request(Storage):
""" """
self._vars = copy.copy(self.get_vars) self._vars = copy.copy(self.get_vars)
for key, value in self.post_vars.iteritems(): for key, value in self.post_vars.iteritems():
if not key in self._vars: if key not in self._vars:
self._vars[key] = value self._vars[key] = value
else: else:
if not isinstance(self._vars[key], list): if not isinstance(self._vars[key], list):
@@ -436,19 +446,20 @@ class Response(Storage):
return page return page
def include_meta(self): def include_meta(self):
s = "\n"; s = "\n"
for meta in (self.meta or {}).iteritems(): for meta in (self.meta or {}).iteritems():
k, v = meta k, v = meta
if isinstance(v,dict): if isinstance(v, dict):
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n' s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
else: else:
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v)) s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
self.write(s, escape=False) self.write(s, escape=False)
def include_files(self, extensions=None): def include_files(self, extensions=None):
""" """
Caching method for writing out files. Includes files (usually in the head).
Can minify and cache local files
By default, caches in ram for 5 minutes. To change, By default, caches in ram for 5 minutes. To change,
response.cache_includes = (cache_method, time_expire). response.cache_includes = (cache_method, time_expire).
Example: (cache.disk, 60) # caches to disk for 1 minute. Example: (cache.disk, 60) # caches to disk for 1 minute.
@@ -456,9 +467,13 @@ class Response(Storage):
from gluon import URL from gluon import URL
files = [] files = []
ext_files = []
has_js = has_css = False has_js = has_css = False
for item in self.files: for item in self.files:
if extensions and not item.split('.')[-1] in extensions: if isinstance(item, (list, tuple)):
ext_files.append(item)
continue
if extensions and not item.rpartition('.')[2] in extensions:
continue continue
if item in files: if item in files:
continue continue
@@ -487,10 +502,13 @@ class Response(Storage):
time_expire) time_expire)
else: else:
files = call_minify() files = call_minify()
s = ''
files.extend(ext_files)
s = []
for item in files: for item in files:
if isinstance(item, str): if isinstance(item, str):
f = item.lower().split('?')[0] f = item.lower().split('?')[0]
ext = f.rpartition('.')[2]
# if static_version we need also to check for # if static_version we need also to check for
# static_version_urls. In that case, the _.x.x.x # static_version_urls. In that case, the _.x.x.x
# bit would have already been added by the URL() # bit would have already been added by the URL()
@@ -498,24 +516,15 @@ class Response(Storage):
if self.static_version and not self.static_version_urls: if self.static_version and not self.static_version_urls:
item = item.replace( item = item.replace(
'/static/', '/static/_%s/' % self.static_version, 1) '/static/', '/static/_%s/' % self.static_version, 1)
if f.endswith('.css'): tmpl = template_mapping.get(ext)
s += css_template % item if tmpl:
elif f.endswith('.js'): s.append(tmpl % item)
s += js_template % item
elif f.endswith('.coffee'):
s += coffee_template % item
elif f.endswith('.ts'):
# http://www.typescriptlang.org/
s += typescript_template % item
elif f.endswith('.less'):
s += less_template % item
elif isinstance(item, (list, tuple)): elif isinstance(item, (list, tuple)):
f = item[0] f = item[0]
if f == 'css:inline': tmpl = template_mapping.get(f)
s += css_inline % item[1] if tmpl:
elif f == 'js:inline': s.append(tmpl % item[1])
s += js_inline % item[1] self.write(''.join(s), escape=False)
self.write(s, escape=False)
def stream(self, def stream(self,
stream, stream,
+1
View File
@@ -4,6 +4,7 @@ from test_http import *
from test_cache import * from test_cache import *
from test_contenttype import * from test_contenttype import *
from test_fileutils import * from test_fileutils import *
from test_globals import *
from test_html import * from test_html import *
from test_is_url import * from test_is_url import *
from test_languages import * from test_languages import *
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.globals
"""
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.globals import Response
from gluon import URL
class testResponse(unittest.TestCase):
def test_include_files(self):
def return_includes(response, extensions=None):
response.include_files(extensions)
return response.body.getvalue()
response = Response()
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.coffee'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.less'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
response = Response()
response.files.append(('css:inline', 'background-color; white;'))
content = return_includes(response)
self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
response = Response()
response.files.append(('js:inline', 'alert("hello")'))
content = return_includes(response)
self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
)
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response, extensions=['css'])
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
#regr test for #628
response = Response()
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
content = return_includes(response)
self.assertEqual(content, '')
#regr test for #628
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '')
if __name__ == '__main__':
unittest.main()