Repository: web2py/web2py

Issue:
can't use response.optimize_js in models/db.py, previous version can work
in models/db.py
response.optimize_js = 'concat,minify,inline'

Traceback (most recent call last):
  File "/Users/MacBookPro/site/web2py/gluon/restricted.py", line 219, in restricted
    exec(ccode, environment)
  File "<string>", line 36, in <module>
  File "/Users/MacBookPro/site/web2py/gluon/globals.py", line 501, in include_files
    time_expire)
  File "/Users/MacBookPro/site/web2py/gluon/cache.py", line 247, in __call__
    value = f()
  File "/Users/MacBookPro/site/web2py/gluon/globals.py", line 496, in call_minify
    self.optimize_js)
  File "/Users/MacBookPro/site/web2py/gluon/contrib/minify/minify.py", line 101, in minify
    contents = read_binary_file(abs_filename)
  File "/Users/MacBookPro/site/web2py/gluon/contrib/minify/minify.py", line 19, in read_binary_file
    f = open(filename, 'rb')
IOError: [Errno 2] No such file or directory: '/Users/MacBookPro/site/web2py/applications/welcome/static/min.js'


Discussion:
Can you provide a simple example we can use to replicate this?
Tested with latest 2.15.2 and it worked. @sugizo would you test it again?
@sugizo managed to replicate it visiting welcome/appadmin/d3_graph_model

Thanks for the bug report!

Resolving patch:
diff --git a/gluon/globals.py b/gluon/globals.py
--- a/gluon/globals.py
+++ b/gluon/globals.py
@@ -473,45 +473,67 @@ def include_files(self, extensions=None):
         response.cache_includes = (cache_method, time_expire).
         Example: (cache.disk, 60) # caches to disk for 1 minute.
         """
+        app = current.request.application
+
+        # We start by building a files list in which adjacent files internal to
+        # the application are placed in a list inside the files list.
+        #
+        # We will only minify and concat adjacent internal files as there's
+        # no way to know if changing the order with which the files are apppended
+        # will break things since the order matters in both CSS and JS and 
+        # internal files may be interleaved with external ones.
         files = []
-        ext_files = []
-        has_js = has_css = False
+        # For the adjacent list we're going to use storage List to both distinguish
+        # from the regular list and so we can add attributes
+        internal = List()  
+        internal.has_js = False
+        internal.has_css = False
+        done = set() # to remove duplicates
         for item in self.files:
-            if isinstance(item, (list, tuple)):
-                ext_files.append(item)
+            if not isinstance(item, list):
+                if item in done:
+                    continue
+                done.add(item)
+            if isinstance(item, (list, tuple)) or not item.startswith('/' + app): # also consider items in other web2py applications to be external
+                if internal:
+                    files.append(internal)
+                    internal = List()
+                    internal.has_js = False
+                    internal.has_css = False
+                files.append(item)
                 continue
             if extensions and not item.rpartition('.')[2] in extensions:
                 continue
-            if item in files:
-                continue
+            internal.append(item)
             if item.endswith('.js'):
-                has_js = True
+                internal.has_js = True
             if item.endswith('.css'):
-                has_css = True
-            files.append(item)
-
-        if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
-            # cache for 5 minutes by default
-            key = hashlib_md5(repr(files)).hexdigest()
-            cache = self.cache_includes or (current.cache.ram, 60 * 5)
-
-            def call_minify(files=files):
-                return minify.minify(files,
-                                     URL('static', 'temp'),
-                                     current.request.folder,
-                                     self.optimize_css,
-                                     self.optimize_js)
-            if cache:
-                cache_model, time_expire = cache
-                files = cache_model('response.files.minified/' + key,
-                                    call_minify,
-                                    time_expire)
-            else:
-                files = call_minify()
-
-        files.extend(ext_files)
-        s = []
-        for item in files:
+                internal.has_css = True            
+        if internal:
+            files.append(internal)
+
+        # We're done we can now minify
+        if have_minify:
+            for i, f in enumerate(files):
+                if isinstance(f, List) and ((self.optimize_css and f.has_css) or (self.optimize_js and f.has_js)):
+                    # cache for 5 minutes by default
+                    key = hashlib_md5(repr(f)).hexdigest()
+                    cache = self.cache_includes or (current.cache.ram, 60 * 5)
+                    def call_minify(files=f):
+                        return List(minify.minify(files,
+                                             URL('static', 'temp'),
+                                             current.request.folder,
+                                             self.optimize_css,
+                                             self.optimize_js))
+                    if cache:
+                        cache_model, time_expire = cache
+                        files[i] = cache_model('response.files.minified/' + key,
+                                            call_minify,
+                                            time_expire)
+                    else:
+                        files[i] = call_minify()
+        
+        def static_map(s, item):
             if isinstance(item, str):
                 f = item.lower().split('?')[0]
                 ext = f.rpartition('.')[2]
@@ -531,6 +553,13 @@ def call_minify(files=files):
                 if tmpl:
                     s.append(tmpl % item[1])
 
+        s = []
+        for item in files:
+            if isinstance(item, List):
+                for f in item:
+                    static_map(s, f)
+            else:
+                static_map(s, item)
         self.write(''.join(s), escape=False)
 
     def stream(self,


Test patch:
diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py
--- a/gluon/tests/test_globals.py
+++ b/gluon/tests/test_globals.py
@@ -158,10 +158,10 @@ def return_includes(response, extensions=None):
         response.files.append(URL('a', 'static', 'css/file.ts'))
         content = return_includes(response)
         self.assertEqual(content,
+                         '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>' +
                          '<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>'
+                         '<script src="/a/static/css/file.ts" type="text/typescript"></script>'
                          )
 
         response = Response()