Repository: ckan/ckan

Issue:
Removing pylons routes breaks extensions
### CKAN Version if known (or site URL)
 2.8

### Please describe the expected behaviour
The old named routes (e.g. 'home' or 'about') should be kept to improve the backwards compatibility of CKAN core. For extensions that want to support a range of CKAN versions this is important as they otherwise have to define these routes themselves, implement version-specific code or break one version. 

### Please describe the actual behaviour
The routes were removed frim master and therefore e.g. code that build the main navigation is doomed to fail in extensions. E.g  https://github.com/ckan/ckanext-showcase/blob/92c9b2b7d4cc07b68eeb77587a832cb3c7607daa/ckanext/showcase/templates/header.html

### What steps can be taken to reproduce the issue? 
Try to build a navigation to the homepage or the about page on both CKAN 2.7 and the latest master branch.
I would propose to either keep the old named routes or provide a way the old name to the new route.



Discussion:
Named routes that followed the `controller_action` convention should be supported. For cases like `about`, `search` etc we can definitely provide an alias, mapping, etc

@smotornyuk @tino097 what do you think it's the best approach? Is it just a matter of defining a new URL rule with a custom endpoint? or do we need an actual mapping in `h.url_for()`?
as documentation states, when using blueprints, we should use blueprint name when defining the endpoint, so i think we should keep using 'home.about' and 'dataset.search' and see if we could provide an alias 
We discussed it last Tuesday, i just had no time to write comment:( 
Yep, most of `c_a` routes are supported, unless there are no naming changes(which is a case for package controller - now it separated into `dataset` and `resource` controllers). But we have quite a bin of one-word routes, like `home` and `about`. 

I suggest adding dictionary with mapping of route names, which will be checked at the beginning of `url_for`. This dict can be created even in helpers file and should contain old names of routes as keys and flask-styled names as values. And, sure, we need to raise either warning or log deprecation message, when we find out that this dict is successfully used. 
I would suggest adding also function, that allows appending new keys into this dict(it potentially will be mutable global variable, what I don't like, but it's the fix for one|two minor releases, so i can go with it) or even some config directive, that can fill gaps in mapping(if we missed something, or there are some problems in extension itself)
We need this before 2.8 is released, as some of the breaking routes are already in it.
@tino097 can work on this this week

Resolving patch:
diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -305,6 +305,9 @@ def url_for(*args, **kw):
             _auto_flask_context.push()
 
         # First try to build the URL with the Flask router
+        # Temporary mapping for pylons to flask route names
+        if len(args):
+            args = (map_pylons_to_flask_route_name(args[0]),)
         my_url = _url_for_flask(*args, **kw)
 
     except FlaskRouteBuildError:
@@ -873,6 +876,28 @@ def build_nav(menu_item, title, **kw):
     return _make_menu_item(menu_item, title, icon=None, **kw)
 
 
+# Legacy route names
+LEGACY_ROUTE_NAMES = {
+    'home': 'home.index',
+    'about': 'home.about',
+}
+
+
+def map_pylons_to_flask_route_name(menu_item):
+    '''returns flask routes for old fashioned route names'''
+    # Pylons to Flask legacy route names mappings
+    if config.get('ckan.legacy_route_mappings'):
+        if isinstance(config.get('ckan.legacy_route_mappings'), string_types):
+            LEGACY_ROUTE_NAMES.update(json.loads(config.get(
+                'ckan.legacy_route_mappings')))
+
+    if menu_item in LEGACY_ROUTE_NAMES:
+        log.info('Route name "{}" is deprecated and will be removed.\
+                Please update calls to use "{}" instead'.format(
+                menu_item, LEGACY_ROUTE_NAMES[menu_item]))
+    return LEGACY_ROUTE_NAMES.get(menu_item, menu_item)
+
+
 @core_helper
 def build_extra_admin_nav():
     '''Build extra navigation items used in ``admin/base.html`` for values
@@ -906,6 +931,7 @@ def _make_menu_item(menu_item, title, **kw):
 
     This function is called by wrapper functions.
     '''
+    menu_item = map_pylons_to_flask_route_name(menu_item)
     _menu_items = config['routes.named_routes']
     if menu_item not in _menu_items:
         raise Exception('menu item `%s` cannot be found' % menu_item)


Test patch:
diff --git a/ckan/tests/controllers/test_home.py b/ckan/tests/controllers/test_home.py
--- a/ckan/tests/controllers/test_home.py
+++ b/ckan/tests/controllers/test_home.py
@@ -56,6 +56,16 @@ def test_email_address_no_nag(self):
 
         assert 'add your email address' not in response
 
+    @helpers.change_config('ckan.legacy_route_mappings',
+                           '{"my_home_route": "home.index"}')
+    def test_map_pylons_to_flask_route(self):
+        app = self._get_test_app()
+        response = app.get(url_for('my_home_route'))
+        assert 'Welcome to CKAN' in response.body
+
+        response = app.get(url_for('home'))
+        assert 'Welcome to CKAN' in response.body
+
 
 class TestI18nURLs(helpers.FunctionalTestBase):