Repository: ckan/ckan

Issue:
Flask Debug toolbar does not show when CKAN is not in the root
### CKAN 2.8.3


### Please describe the expected behaviour
When debug mode is enabled (by setting [the _debug_ option](https://docs.ckan.org/en/2.8/maintaining/configuration.html#debug) to True), a "FDT" button should show in the right margin offering [Flask-DebugToolbar](https://pypi.org/project/Flask-DebugToolbar/).

### Please describe the actual behaviour
The FDT button does not show and several network errors occur. JavaScript files required by Flask-DebugToolbar fail to load with HTTP 404 errors, because their URL according to the HTML document is wrong.

### What steps can be taken to reproduce the issue? 
1. Install CKAN below the root, for example in https://example.com/recherche/
1. Enable debugging (change the configuration file and restart Apache httpd)
1. Load a page, such as the home page

  
  
---

This occurs because the _static_path_ Jinja variable's value, which should be the path where Flask-DebugToolbar is, points too high. It lacks the site's base path. For example, on our site, instead of "/recherche/_debug_toolbar/static/", the value is just "/_debug_toolbar/static/" ("/recherche" is missing).

Flask-DebugToolbar defines _static_path_ in DebugToolbar.\_\_init\_\_(). To workaround, the base path can be hardcoded to prepend it to the value returned by url_for(), by changing flask_debugtoolbar/toolbar.py:20 to:
`'static_path': '/recherche' + url_for('_debug_toolbar.static', filename='')`


Discussion:
Not particularly related to this bug, but just to prevent others from being confused...
The FDT does not show on all pages. It is supposed to show on the homepage for instance, but as of CKAN 2.8.3, there are still many pages which have not moved to Flask, where the FDT button simply is not supposed to show. 

Resolving patch:
diff --git a/ckan/config/middleware/flask_app.py b/ckan/config/middleware/flask_app.py
--- a/ckan/config/middleware/flask_app.py
+++ b/ckan/config/middleware/flask_app.py
@@ -134,10 +134,28 @@ def make_flask_stack(conf):
         raise RuntimeError(u'You must provide a value for the secret key'
                            ' with the SECRET_KEY config option')
 
+    root_path = config.get('ckan.root_path', None)
     if debug:
         from flask_debugtoolbar import DebugToolbarExtension
         app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
-        DebugToolbarExtension(app)
+        debug_ext = DebugToolbarExtension()
+
+        # register path that includes `ckan.site_root` before
+        # initializing debug app. In such a way, our route receives
+        # higher precedence.
+
+        # TODO: After removal of Pylons code, switch to
+        # `APPLICATION_ROOT` config value for flask application. Right
+        # now it's a bad option because we are handling both pylons
+        # and flask urls inside helpers and splitting this logic will
+        # bring us tons of headache.
+        if root_path:
+            app.add_url_rule(
+                root_path.replace('{{LANG}}', '').rstrip('/') +
+                '/_debug_toolbar/static/<path:filename>',
+                '_debug_toolbar.static', debug_ext.send_static_file
+            )
+        debug_ext.init_app(app)
 
         from werkzeug.debug import DebuggedApplication
         app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
@@ -266,7 +284,7 @@ def ungettext_alias():
             'bundle': True,
             'rollup': fanstatic_enable_rollup,
         }
-    root_path = config.get('ckan.root_path', None)
+
     if root_path:
         root_path = re.sub('/{{LANG}}', '', root_path)
         fanstatic_config['base_url'] = root_path


Test patch:
diff --git a/ckan/tests/lib/test_helpers.py b/ckan/tests/lib/test_helpers.py
--- a/ckan/tests/lib/test_helpers.py
+++ b/ckan/tests/lib/test_helpers.py
@@ -150,6 +150,16 @@ def test_url_for_with_root_path_locale_and_script_name_env(self, monkeypatch):
         generated_url = h.url_for("dataset.read", id="my_dataset", locale="de")
         assert generated_url == url
 
+    @pytest.mark.ckan_config("debug", True)
+    @pytest.mark.ckan_config("DEBUG", True)  # Flask's internal debug flag
+    @pytest.mark.ckan_config("ckan.root_path", "/my/custom/path")
+    def test_debugtoolbar_url(self, ckan_config):
+        # test against built-in `url_for`, that is used by debugtoolbar ext.
+        from flask import url_for
+        expected = "/my/custom/path/_debug_toolbar/static/test.js"
+        url = url_for('_debug_toolbar.static', filename='test.js')
+        assert url == expected
+
 
 class TestHelpersUrlForFlaskandPylons(BaseUrlFor):
     def test_url_for_flask_route_new_syntax(self):