diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/_base/__init__.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+from pylons import tmpl_context as c
+
+from rhodecode.lib.utils2 import StrictAttributeDict
+
+log = logging.getLogger(__name__)
+
+
+class TemplateArgs(StrictAttributeDict):
+ pass
+
+
+class BaseAppView(object):
+
+ def __init__(self, context, request):
+ self.request = request
+ self.context = context
+ self.session = request.session
+ self._rhodecode_user = request.user
+
+ def _get_local_tmpl_context(self):
+ return TemplateArgs()
+
+ def _get_template_context(self, tmpl_args):
+
+ for k, v in tmpl_args.items():
+ setattr(c, k, v)
+
+ return {
+ 'defaults': {},
+ 'errors': {},
+ }
+
diff --git a/rhodecode/apps/user_profile/__init__.py b/rhodecode/apps/user_profile/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+
+def includeme(config):
+ config.add_route(
+ name='user_profile',
+ pattern='/_profiles/{username}')
+
+ # Scan module for configuration decorators.
+ config.scan()
diff --git a/rhodecode/apps/user_profile/tests/__init__.py b/rhodecode/apps/user_profile/tests/__init__.py
new file mode 100644
diff --git a/rhodecode/apps/user_profile/tests/test_users.py b/rhodecode/apps/user_profile/tests/test_users.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/tests/test_users.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import pytest
+
+from rhodecode.model.db import User
+from rhodecode.tests import (
+ TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
+ TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.utils import AssertResponse
+
+fixture = Fixture()
+
+
+def route_path(name, **kwargs):
+ return '/_profiles/{username}'.format(**kwargs)
+
+
+class TestUsersController(TestController):
+
+ def test_user_profile(self, user_util):
+ edit_link_css = '.user-profile .panel-edit'
+ self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+ user = user_util.create_user(
+ 'test-my-user', password='qweqwe', email='testme@rhodecode.org')
+ username = user.username
+
+ response = self.app.get(route_path('user_profile', username=username))
+ response.mustcontain('testme')
+ response.mustcontain('testme@rhodecode.org')
+ assert_response = AssertResponse(response)
+ assert_response.no_element_exists(edit_link_css)
+
+ # edit should be available to superadmin users
+ self.logout_user()
+ self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
+ response = self.app.get(route_path('user_profile', username=username))
+ assert_response = AssertResponse(response)
+ assert_response.element_contains(edit_link_css, 'Edit')
+
+ def test_user_profile_not_available(self, user_util):
+ user = user_util.create_user()
+ username = user.username
+
+ # not logged in, redirect
+ self.app.get(route_path('user_profile', username=username), status=302)
+
+ self.log_user()
+ # after log-in show
+ self.app.get(route_path('user_profile', username=username), status=200)
+
+ # default user, not allowed to show it
+ self.app.get(
+ route_path('user_profile', username=User.DEFAULT_USER), status=404)
+
+ # actual 404
+ self.app.get(route_path('user_profile', username='unknown'), status=404)
diff --git a/rhodecode/apps/user_profile/views.py b/rhodecode/apps/user_profile/views.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/views.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.view import view_config
+
+from rhodecode.apps._base import BaseAppView
+from rhodecode.lib.auth import LoginRequired, NotAnonymous
+
+from rhodecode.model.db import User
+from rhodecode.model.user import UserModel
+
+log = logging.getLogger(__name__)
+
+
+class UserProfileView(BaseAppView):
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='user_profile', request_method='GET',
+ renderer='rhodecode:templates/users/user.mako')
+ def login(self):
+ # register local template context
+ c = self._get_local_tmpl_context()
+ c.active = 'user_profile'
+
+ username = self.request.matchdict.get('username')
+
+ c.user = UserModel().get_by_username(username)
+ if not c.user or c.user.username == User.DEFAULT_USER:
+ raise HTTPNotFound()
+
+ return self._get_template_context(c)
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py
--- a/rhodecode/config/middleware.py
+++ b/rhodecode/config/middleware.py
@@ -281,7 +281,11 @@ def includeme(config):
config.include('rhodecode.admin')
config.include('rhodecode.authentication')
config.include('rhodecode.integrations')
+
+ # apps
config.include('rhodecode.apps.login')
+ config.include('rhodecode.apps.user_profile')
+
config.include('rhodecode.tweens')
config.include('rhodecode.api')
config.include('rhodecode.svn_support')
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -92,7 +92,7 @@ class JSRoutesMapper(Mapper):
def _extract_route_information(self, route):
"""
Convert a route into tuple(name, path, args), eg:
- ('user_profile', '/profile/%(username)s', ['username'])
+ ('show_user', '/profile/%(username)s', ['username'])
"""
routepath = route.routepath
def replace(matchobj):
@@ -198,10 +198,6 @@ def make_map(config):
rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
action='user_group_autocomplete_data', jsroute=True)
- rmap.connect(
- 'user_profile', '/_profiles/{username}', controller='users',
- action='user_profile')
-
# TODO: johbo: Static links, to be replaced by our redirection mechanism
rmap.connect('rst_help',
'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
diff --git a/rhodecode/controllers/users.py b/rhodecode/controllers/users.py
deleted file mode 100644
--- a/rhodecode/controllers/users.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-"""
-Users profile controller
-"""
-
-from pylons import tmpl_context as c
-from webob.exc import HTTPNotFound
-
-from rhodecode.lib.auth import LoginRequired, NotAnonymous
-from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import User
-from rhodecode.model.user import UserModel
-
-
-class UsersController(BaseController):
- @LoginRequired()
- @NotAnonymous()
- def user_profile(self, username):
- c.user = UserModel().get_by_username(username)
- if not c.user or c.user.username == User.DEFAULT_USER:
- raise HTTPNotFound()
-
- c.active = 'user_profile'
- return render('users/user.mako')
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -871,7 +871,7 @@ def link_to_user(author, length=0, **kwa
if user:
return link_to(
escape(display_person),
- url('user_profile', username=user.username),
+ route_path('user_profile', username=user.username),
**kwargs)
else:
return escape(display_person)
diff --git a/rhodecode/templates/email_templates/user_registration.mako b/rhodecode/templates/email_templates/user_registration.mako
--- a/rhodecode/templates/email_templates/user_registration.mako
+++ b/rhodecode/templates/email_templates/user_registration.mako
@@ -12,16 +12,16 @@ A new user `${user.username}` has regist
- Username: ${user.username}
- Full Name: ${user.firstname} ${user.lastname}
- Email: ${user.email}
-- Profile link: ${h.url('user_profile', username=user.username, qualified=True)}
+- Profile link: ${h.route_path('user_profile', username=user.username, qualified=True)}
${self.plaintext_footer()}
%def>
## BODY GOES BELOW
\ No newline at end of file
diff --git a/rhodecode/templates/users/user.mako b/rhodecode/templates/users/user.mako
--- a/rhodecode/templates/users/user.mako
+++ b/rhodecode/templates/users/user.mako
@@ -12,7 +12,7 @@
%def>
<%def name="menu_bar_nav()">
- ${self.menu_items(active='admin')}
+ ${self.menu_items(active='my_account')}
%def>
<%def name="main()">
@@ -26,7 +26,7 @@