diff --git a/ckanext/odsh/collection/plugin.py b/ckanext/odsh/collection/plugin.py
index 7ac1d0f4461155075a071ac3a0f6493ae2ab32b3..0c8ccf0add71d68aa6a42eda97df4b4c8ecd8028 100644
--- a/ckanext/odsh/collection/plugin.py
+++ b/ckanext/odsh/collection/plugin.py
@@ -1,4 +1,4 @@
-from ckan.lib.plugins import DefaultTranslation, DefaultDatasetForm
+from ckan.lib.plugins import DefaultDatasetForm
 import ckan.plugins as plugins
 import ckan.plugins.toolkit as toolkit
 from . import helpers as collection_helpers
diff --git a/ckanext/odsh/controller.py b/ckanext/odsh/controller.py
index 4c37023cd7bd3739e2c36e8beb73a7d567a95d7a..cd31360d9675761baba04c8edbca37a8b88569b3 100644
--- a/ckanext/odsh/controller.py
+++ b/ckanext/odsh/controller.py
@@ -28,73 +28,6 @@ render = base.render
 get_action = logic.get_action
 
 
-class OdshRouteController(HomeController):
-    def info_page(self):
-        h.redirect_to('http://www.schleswig-holstein.de/odpinfo')
-
-    def start(self):
-        h.redirect_to('/dataset')
-
-    def not_found(self):
-        abort(404)
-
-
-class OdshUserController(UserController):
-    def index(self):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).index()
-
-    def me(self, locale=None):
-        if not c.user:
-            h.redirect_to(locale=locale, controller='user', action='login',
-                          id=None)
-        user_ref = c.userobj.get_reference_preferred_for_uri()
-        h.redirect_to(locale=locale, controller='dataset', action='search')
-
-    def dashboard(self, id=None, offset=0):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).dashboard(id, offset)
-
-    def dashboard_datasets(self):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).dashboard_datasets(id)
-
-    def read(self, id=None):
-        if not c.user:
-            h.redirect_to(controller='user', action='login')
-        return super(OdshUserController, self).read(id)
-
-    def follow(self, id):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).follow(id)
-
-    def unfollow(self, id):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).unfollow(id)
-
-    def activity(self, id, offset=0):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).activity(id, offset)
-
-    def register(self, data=None, errors=None, error_summary=None):
-        if not authz.is_sysadmin(c.user):
-            abort(404)
-        return super(OdshUserController, self).register(data, errors, error_summary)
-
-
-class OdshPackageController(PackageController):
-    def edit_view(self, id, resource_id, view_id=None):
-        if not authz.is_sysadmin(c.user):
-            abort(403)
-        return super(OdshPackageController, self).edit_view(id, resource_id, view_id)
-
-
 class OdshGroupController(OrganizationController):
 
     def _action(self, name):
diff --git a/ckanext/odsh/helpers.py b/ckanext/odsh/helpers.py
index 4ef5d8fef1a5e972cc9ed7cd08011036014f2e53..43d6f09b3c7ee1b62fcf1a04d3d08fdc22e6d216 100644
--- a/ckanext/odsh/helpers.py
+++ b/ckanext/odsh/helpers.py
@@ -309,20 +309,20 @@ def odsh_group_id_selected(selected, group_id):
     return False
 
 
-def odsh_remove_route(map, routename):
-    route = None
-    for i, r in enumerate(map.matchlist):
-
-        if r.name == routename:
-            route = r
-            break
-    if route is not None:
-        map.matchlist.remove(route)
-        for key in map.maxkeys:
-            if key == route.maxkeys:
-                map.maxkeys.pop(key)
-                map._routenames.pop(route.name)
-                break
+# def odsh_remove_route(map, routename):
+#     route = None
+#     for i, r in enumerate(map.matchlist):
+
+#         if r.name == routename:
+#             route = r
+#             break
+#     if route is not None:
+#         map.matchlist.remove(route)
+#         for key in map.maxkeys:
+#             if key == route.maxkeys:
+#                 map.maxkeys.pop(key)
+#                 map._routenames.pop(route.name)
+#                 break
 
 
 def is_within_last_month(date, date_ref=None):
diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py
index 94d7ec5f7a96bd31eb4e8a6d9e2bee1a205c1831..79364d6f03ef0ff0b206c4509301e249d1364e1d 100644
--- a/ckanext/odsh/plugin.py
+++ b/ckanext/odsh/plugin.py
@@ -27,12 +27,17 @@ import ckanext.odsh.validation as validation
 import ckanext.odsh.search as search
 from ckanext.odsh.odsh_logger import OdshLogger
 import ckanext.odsh.tools as tools
-
+from ckanext.odsh.views import default
+from ckanext.odsh.views import package
+from ckanext.odsh.views import user
+from ckanext.odsh.views import dashboard
+from ckanext.dcat import blueprints as dcat_view
 
 log = logging.getLogger(__name__)
 
 _ = toolkit._
 
+
 class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm):
     plugins.implements(plugins.IActions)
     plugins.implements(plugins.IConfigurer)
@@ -44,17 +49,62 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
     plugins.implements(plugins.ITranslation)
     plugins.implements(plugins.IValidators)
     plugins.implements(plugins.IResourceController, inherit=True)
+    plugins.implements(plugins.IBlueprint)
+
+    # IBlueprint
+    def get_blueprint(self):
+        log.info("OdshPlugin::get_blueprint")
+
+        # Default
+        bp_default = default.blueprint
+        rules = [
+            ('/info_page', 'info_page', default.info_page),
+            ('/home', 'start', default.start),
+            ('/not_found', 'not_found', default.not_found), ]
+        for rule in rules:
+            bp_default.add_url_rule(*rule)
+
+        # DCAT
+        # if toolkit.asbool(toolkit.config.get('ckanext.dcat.enable_rdf_endpoints', True)):
+        #     odsh_helpers.odsh_remove_route(map, 'dcat_catalog')
+        #     bp_default.add_url_rule('/catalog.<any("xml", "rdf", "n3", "ttl", "jsonld"):_format>', view_func=dcat_view.read_catalog, defaults={'_format': 'xml'}, methods=['GET'])
+
+        # Package
+        bp_package = package.blueprint
+        rules = [
+            ('/dataset/<id>/resource/<resource_id>', 'redirect_dataset_resource', package.redirect_dataset_resource), ]
+        for rule in rules:
+            bp_package.add_url_rule(*rule)
+
+        # User
+        bp_user = user.blueprint
+        bp_user.add_url_rule(u'/user', endpoint='user_index',
+                             view_func=user.index, strict_slashes=False)
+        bp_user.add_url_rule(u'/user/register', view_func=user.register)
+        bp_user.add_url_rule(u'/user/activity/<id>', view_func=user.activity)
+        bp_user.add_url_rule(u'/user/activity/<id>/<int:offset>', view_func=user.activity)
+        
+        # Dashboard
+        bp_dashboard = dashboard.blueprint
+        bp_dashboard.add_url_rule(
+            u'/dashboard', view_func=dashboard.dashboard, strict_slashes=False, defaults={
+                u'offset': 0
+            })
+        bp_dashboard.add_url_rule(
+            u'/dashboard/<int:offset>', view_func=dashboard.dashboard)
+        bp_dashboard.add_url_rule(
+            u'/dashboard/datasets', view_func=dashboard.dashboard_datasets)
+
+        return [bp_default, bp_package, bp_user, bp_dashboard]
 
-    
     # IActions
 
     def get_actions(self):
         return {'package_create': action.odsh_package_create,
-                'user_update':action.tpsh_user_update,
+                'user_update': action.tpsh_user_update,
                 'user_create': action.odsh_user_create,
-		'resource_create': action.odsh_resource_create,}
+                'resource_create': action.odsh_resource_create, }
 
-    
     # IConfigurer
 
     def update_config(self, config_):
@@ -62,7 +112,6 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         toolkit.add_public_directory(config_, 'public')
         toolkit.add_resource('fanstatic', 'ckanext-odsh')
 
-
     # IDatasetForm
 
     def package_types(self):
@@ -74,7 +123,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         # Return True to register this plugin as the default handler for
         # package types not handled by any other IDatasetForm plugin.
         return True
-    
+
     def create_package_schema(self):
         schema = super(OdshPlugin, self).create_package_schema()
         self._update_schema(schema)
@@ -95,14 +144,14 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
     def _update_schema(self, schema):
         for field in ['title', 'license_id']:
             schema.update({field: [toolkit.get_converter('not_empty')]})
-        
+
         schema.update({
             'reference': [
                 toolkit.get_validator('ignore_missing'),
                 toolkit.get_converter('convert_to_extras')
             ]
         })
-        
+
         for i, item in enumerate(schema['tags']['name']):
             if item == toolkit.get_validator('tag_name_validator'):
                 schema['tags']['name'][i] = toolkit.get_validator(
@@ -145,7 +194,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             ]
         })
         return schema
-    
+
     def _tpsh_update_show_package_schema(self, schema):
         schema.update({
             'language': [
@@ -167,15 +216,14 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         })
         return schema
 
-
     # IFacets
-    
+
     def dataset_facets(self, facets_dict, package_type):
         return OrderedDict({'organization': _('Organizations'),
                             'groups': _('Category'),
                             'res_format': _('File format'),
                             'license_title': _('License'),
-#                            'tags': _('Tags'),
+                            #                            'tags': _('Tags'),
                             'openness': _('Open-Data-Eigenschaften')
                             })
 
@@ -190,7 +238,6 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                             'res_format': _('File format'),
                             'license_title': _('License'),
                             'groups': _('Category')})
-    
 
     # IPackageController
 
@@ -205,7 +252,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             helpers_tpsh.get_pkg_relationships_from_model(pkg_dict)
         )
         self._update_is_new_in_pkg_dict(pkg_dict)
-        
+
         return pkg_dict
 
     def before_view(self, pkg_dict):
@@ -215,7 +262,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         '''
         self._update_is_new_in_pkg_dict(pkg_dict)
         return pkg_dict
-    
+
     def after_create(self, context, resource):
         if resource.get('package_id'):
             tools.add_attributes_resources(context, resource)
@@ -229,7 +276,6 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         is_new = HelperPgkDict(pkg_dict).is_package_new()
         pkg_dict.update({'is_new': is_new})
 
-    
     def before_index(self, dict_pkg):
         # make special date fields solr conform
         fields = ["issued", "temporal_start", "temporal_end"]
@@ -243,32 +289,17 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         self.map_qa_score(dict_pkg)
 
         return dict_pkg
-    
 
     # IRoutes
-    
-    def before_map(self, map):
-        map.connect(
-            'info_page', 
-            '/info_page',
-            controller='ckanext.odsh.controller:OdshRouteController', 
-            action='info_page'
-        )
-        map.connect(
-            'home', 
-            '/',
-            controller='ckanext.odsh.controller:OdshRouteController', 
-            action='start'
-        )
 
         map.redirect('/dataset/{id}/resource/{resource_id}', '/dataset/{id}')
 
         # /api ver 3
         GET_POST = dict(method=['GET', 'POST'])
         with SubMapper(
-            map, 
-            controller='ckanext.odsh.controller:OdshApiController', 
-            path_prefix='/api{ver:/3|}', 
+            map,
+            controller='ckanext.odsh.controller:OdshApiController',
+            path_prefix='/api{ver:/3|}',
             ver='/3'
         ) as m:
             m.connect('/action/{logic_function}',
@@ -277,55 +308,49 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         with SubMapper(map, controller='ckanext.odsh.controller:OdshFeedController') as m:
             m.connect('/feeds/custom.atom', action='custom')
 
-        with SubMapper(map, controller='ckanext.odsh.controller:OdshPackageController') as m:
-            m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view',
-                      action='edit_view', ckan_icon='pencil-square-o')
-
         with SubMapper(map, controller='ckanext.odsh.controller:OdshGroupController') as m:
             m.connect('organizations_index', '/organization', action='index')
 
         # redirect all user routes to custom controller
-        with SubMapper(map, controller='ckanext.odsh.controller:OdshUserController') as m:
-            m.connect('user_index', '/user', action='index')
-            m.connect('/user/edit', action='edit')
-            m.connect(
-                'user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog')
-            m.connect('user_delete', '/user/delete/{id}', action='delete')
-            m.connect('/user/reset/{id:.*}', action='perform_reset')
-            m.connect('/user/reset', action='request_reset')
-            m.connect('register', '/user/register', action='register')
-            m.connect('login', '/user/login', action='login')
-            m.connect('/user/_logout', action='logout')
-            m.connect('/user/logged_in', action='logged_in')
-            m.connect('/user/logged_out', action='logged_out')
-            m.connect('/user/logged_out_redirect', action='logged_out_page')
-            m.connect('user_datasets', '/user/{id:(?!(generate_key|activity)).*}', action='read',
-                      ckan_icon='sitemap')
+        # with SubMapper(map, controller='ckanext.odsh.controller:OdshUserController') as m:
+        #     m.connect('/user/edit', action='edit')
+        #     m.connect(
+        #         'user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog')
+        #     m.connect('user_delete', '/user/delete/{id}', action='delete')
+        #     m.connect('/user/reset/{id:.*}', action='perform_reset')
+        #     m.connect('/user/reset', action='request_reset')
+        #     m.connect('register', '/user/register', action='register')
+        #     m.connect('login', '/user/login', action='login')
+        #     m.connect('/user/_logout', action='logout')
+        #     m.connect('/user/logged_in', action='logged_in')
+        #     m.connect('/user/logged_out', action='logged_out')
+        #     m.connect('/user/logged_out_redirect', action='logged_out_page')
+        #     m.connect('user_datasets', '/user/{id:(?!(generate_key|activity)).*}', action='read',
+        #               ckan_icon='sitemap')
 
         map.connect(
-            'comment_datarequest', 
+            'comment_datarequest',
             '/datarequest/new',
             controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI',
-            action='new', 
-            conditions=dict(method=['GET', 'POST']), 
+            action='new',
+            conditions=dict(method=['GET', 'POST']),
             ckan_icon='comment'
         )
         map.connect(
-            'comment_datarequest', 
+            'comment_datarequest',
             '/datarequest/{id}',
             controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI',
-            action='comment', 
-            conditions=dict(method=['GET', 'POST']), 
+            action='comment',
+            conditions=dict(method=['GET', 'POST']),
             ckan_icon='comment'
         )
         return map
-    
+
     def after_map(self, map):
         return map
 
-
     # ITemplateHelpers
-    
+
     def get_helpers(self):
         # Template helper function names should begin with the name of the
         # extension they belong to, to avoid clashing with functions from
@@ -371,7 +396,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
 
     
     # IValidators
-    
+
     def get_validators(self):
         return validation.get_validators()
 
@@ -396,4 +421,3 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                             score = s
             if score > 0:
                 dict_pkg['openness'] = OdshPlugin.scores[score-1]
-
diff --git a/ckanext/odsh/views/dashboard.py b/ckanext/odsh/views/dashboard.py
new file mode 100644
index 0000000000000000000000000000000000000000..78fe3b1b16f872df0994f18c9f6b76c0669a2ca2
--- /dev/null
+++ b/ckanext/odsh/views/dashboard.py
@@ -0,0 +1,31 @@
+import ckan.plugins.toolkit as toolkit
+import ckan.authz as authz
+import ckan.logic as logic
+from ckan.common import g
+from flask import Blueprint
+from ckan.views.dashboard import index
+import logging
+
+log = logging.getLogger(__name__)
+
+blueprint = Blueprint('odsh_dashboard', __name__)
+
+
+def dashboard(offset=0):
+    log.info("views.dashboard::dashboard")
+    is_sysadmin = authz.is_sysadmin(g.user)
+
+    if not is_sysadmin:
+        toolkit.abort(403)
+
+    return index(offset)
+
+
+def dashboard_datasets():
+    log.info("views.dashboard::dashboard_datasets")
+    is_sysadmin = authz.is_sysadmin(g.user)
+
+    if not is_sysadmin:
+        toolkit.abort(403)
+
+    return index()
diff --git a/ckanext/odsh/views/default.py b/ckanext/odsh/views/default.py
new file mode 100644
index 0000000000000000000000000000000000000000..0058076e06e1e84903ca2320491edf6e53107ea7
--- /dev/null
+++ b/ckanext/odsh/views/default.py
@@ -0,0 +1,19 @@
+import ckan.plugins.toolkit as toolkit
+from flask import Blueprint
+import logging
+
+log = logging.getLogger(__name__)
+
+blueprint = Blueprint('odsh_default', __name__)
+
+def info_page():
+    log.info("views.default::info_page")
+    return toolkit.redirect_to('http://www.schleswig-holstein.de/odpinfo')
+
+def start():
+    log.info("views.default::start")
+    return toolkit.redirect_to('/dataset')
+
+def not_found():
+    log.info("views.default::not_found")
+    toolkit.abort(404)
diff --git a/ckanext/odsh/views/package.py b/ckanext/odsh/views/package.py
new file mode 100644
index 0000000000000000000000000000000000000000..64199f6389a849657b4ec7a0d01a2a42ef5859bc
--- /dev/null
+++ b/ckanext/odsh/views/package.py
@@ -0,0 +1,13 @@
+import ckan.plugins.toolkit as toolkit
+import ckan.authz as authz
+
+from flask import Blueprint
+import logging
+
+log = logging.getLogger(__name__)
+
+blueprint = Blueprint('odsh_package', __name__)
+
+def redirect_dataset_resource(id, resource_id):
+    log.info("views.package::redirect_dataset_resource")
+    return toolkit.redirect_to('/dataset/{}'.format(id))
diff --git a/ckanext/odsh/views/user.py b/ckanext/odsh/views/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad03408aa2b1a54a5cf23384004b625e4c4c838a
--- /dev/null
+++ b/ckanext/odsh/views/user.py
@@ -0,0 +1,45 @@
+import ckan.plugins.toolkit as toolkit
+import ckan.authz as authz
+import ckan.logic as logic
+from ckan.common import g
+from flask import Blueprint
+import ckan.views.user as ckan_user_view
+import logging
+
+log = logging.getLogger(__name__)
+
+blueprint = Blueprint('odsh_user', __name__)
+
+
+def index():
+    log.info("views.user::index")
+    is_sysadmin = authz.is_sysadmin(g.user)
+
+    if not is_sysadmin:
+        toolkit.abort(403)
+    return ckan_user_view.index()
+
+
+def register():
+    log.info("views.user::register")
+    is_sysadmin = authz.is_sysadmin(g.user)
+
+    if not is_sysadmin:
+        toolkit.abort(403)
+    return ckan_user_view.RegisterView.as_view(str(u'register'))
+
+
+def read(id=None):
+    log.info("views.user::read")
+    if not g.user:
+        return ckan_user_view.login()
+    return ckan_user_view.read(id)
+
+
+def activity(id, offset=0):
+    log.info("views.user::activity")
+    is_sysadmin = authz.is_sysadmin(g.user)
+
+    if not is_sysadmin:
+        toolkit.abort(403)
+    return ckan_user_view.activity(id, offset)
diff --git a/setup.py b/setup.py
index 4c717d157017d59b2cfb090ff2e9ad384244e5e5..260ce13ac5080cf9c11ac36d8595e26bf5b1dfa0 100755
--- a/setup.py
+++ b/setup.py
@@ -43,7 +43,7 @@ setup(
 
         # Specify the Python versions you support here. In particular, ensure
         # that you indicate whether you support Python 2, Python 3 or both.
-        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.8.10',
     ],
 
 
@@ -89,6 +89,7 @@ setup(
         odsh_dcat_harvest=ckanext.odsh.plugin_odsh_dcat_harvest:OdshDCATHarvestPlugin
         odsh_collections=ckanext.odsh.collection.plugin:CollectionsPlugin
         thumbnail=ckanext.odsh.pdf_to_thumbnail.plugin:ThumbnailPlugin
+        
         [paste.paster_command]
         odsh_initialization = ckanext.odsh.commands.initialization:Initialization