diff --git a/gluon/globals.py b/gluon/globals.py index 1a3ebb6e..05f94994 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -26,6 +26,8 @@ import gluon.settings as settings from gluon.utils import web2py_uuid, secure_dumps, secure_loads from gluon.settings import global_settings from gluon import recfile +from gluon.cache import CacheInRam +from gluon.fileutils import copystream import hashlib import portalocker try: @@ -47,8 +49,7 @@ import cgi import urlparse import copy import tempfile -from gluon.cache import CacheInRam -from gluon.fileutils import copystream + FMT = '%a, %d-%b-%Y %H:%M:%S PST' PAST = 'Sat, 1-Jan-1971 00:00:00' @@ -82,13 +83,22 @@ less_template = '' css_inline = '' js_inline = '' +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: # this is required so that pickled dict(s) and class.__dict__ # are sorted and web2py can detect without ambiguity when a session changes class SortingPickler(Pickler): 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._batch_setitems([(key, obj[key]) for key in sorted(obj)]) @@ -275,7 +285,7 @@ class Request(Storage): """ self._vars = copy.copy(self.get_vars) for key, value in self.post_vars.iteritems(): - if not key in self._vars: + if key not in self._vars: self._vars[key] = value else: if not isinstance(self._vars[key], list): @@ -436,19 +446,20 @@ class Response(Storage): return page def include_meta(self): - s = "\n"; + s = "\n" for meta in (self.meta or {}).iteritems(): k, v = meta - if isinstance(v,dict): - s = s+'\n' + if isinstance(v, dict): + s += '\n' else: - s = s+'\n' % (k, xmlescape(v)) + s += '\n' % (k, xmlescape(v)) self.write(s, escape=False) 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, response.cache_includes = (cache_method, time_expire). Example: (cache.disk, 60) # caches to disk for 1 minute. @@ -456,9 +467,13 @@ class Response(Storage): from gluon import URL files = [] + ext_files = [] has_js = has_css = False 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 if item in files: continue @@ -487,10 +502,13 @@ class Response(Storage): time_expire) else: files = call_minify() - s = '' + + files.extend(ext_files) + s = [] for item in files: if isinstance(item, str): f = item.lower().split('?')[0] + ext = f.rpartition('.')[2] # if static_version we need also to check for # static_version_urls. In that case, the _.x.x.x # 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: item = item.replace( '/static/', '/static/_%s/' % self.static_version, 1) - if f.endswith('.css'): - s += css_template % item - elif f.endswith('.js'): - 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 + tmpl = template_mapping.get(ext) + if tmpl: + s.append(tmpl % item) elif isinstance(item, (list, tuple)): f = item[0] - if f == 'css:inline': - s += css_inline % item[1] - elif f == 'js:inline': - s += js_inline % item[1] - self.write(s, escape=False) + tmpl = template_mapping.get(f) + if tmpl: + s.append(tmpl % item[1]) + self.write(''.join(s), escape=False) def stream(self, stream, diff --git a/gluon/tests/__init__.py b/gluon/tests/__init__.py index 03e67935..64a104d3 100644 --- a/gluon/tests/__init__.py +++ b/gluon/tests/__init__.py @@ -4,6 +4,7 @@ from test_http import * from test_cache import * from test_contenttype import * from test_fileutils import * +from test_globals import * from test_html import * from test_is_url import * from test_languages import * diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py new file mode 100644 index 00000000..5f9d5526 --- /dev/null +++ b/gluon/tests/test_globals.py @@ -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, '') + + response = Response() + response.files.append(URL('a', 'static', 'css/file.js')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append(URL('a', 'static', 'css/file.coffee')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append(URL('a', 'static', 'css/file.ts')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append(URL('a', 'static', 'css/file.less')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append(('css:inline', 'background-color; white;')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append(('js:inline', 'alert("hello")')) + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append('https://code.jquery.com/jquery-1.11.3.min.js') + content = return_includes(response) + self.assertEqual(content, '') + + response = Response() + response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0') + content = return_includes(response) + self.assertEqual(content, '') + + 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, + '' + + '') + + 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, + '' + + '' + + '' + + '' + ) + + + 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, '') + + #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, '') + + response = Response() + response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false']) + content = return_includes(response) + self.assertEqual(content, '') + + 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()