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 <http://www.gnu.org/licenses/>. +# +# 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 <http://www.gnu.org/licenses/>. +# +# 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 <http://www.gnu.org/licenses/>. +# +# 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 <http://www.gnu.org/licenses/>. +# +# 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 <http://www.gnu.org/licenses/>. -# -# 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 <table style="text-align:left;vertical-align:middle;"> - <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.url('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr> + <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_path('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr> <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16"> ${user.username}</td></tr> <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr> <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr> - <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.url('user_profile', username=user.username, qualified=True)}">${h.url('user_profile', username=user.username, qualified=True)}</a></td></tr> + <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_path('user_profile', username=user.username, qualified=True)}">${h.route_path('user_profile', username=user.username, qualified=True)}</a></td></tr> </table> \ 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 @@ <div class="sidebar"> <ul class="nav nav-pills nav-stacked"> <li class="${'active' if c.active=='user_profile' else ''}"> - <a href="${h.url('user_profile', username=c.user.username)}">${_('Profile')}</a></li> + <a href="${h.route_path('user_profile', username=c.user.username)}">${_('Profile')}</a></li> ## These placeholders are here only for styling purposes. For every new item added to the list, you should remove one placeholder <li class="placeholder"><a href="#" style="visibility: hidden;">placeholder</a></li> <li class="placeholder"><a href="#" style="visibility: hidden;">placeholder</a></li> diff --git a/rhodecode/tests/functional/test_admin_gists.py b/rhodecode/tests/functional/test_admin_gists.py --- a/rhodecode/tests/functional/test_admin_gists.py +++ b/rhodecode/tests/functional/test_admin_gists.py @@ -276,7 +276,7 @@ class TestGistsController(TestController assert_response = AssertResponse(response) assert_response.element_equals_to( 'div.rc-user span.user', - '<span class="user"> %s</span>' % h.link_to_user('test_admin')) + '<a href="/_profiles/test_admin">test_admin</a></span>') response.mustcontain('gist-desc') @@ -299,7 +299,7 @@ class TestGistsController(TestController assert_response = AssertResponse(response) assert_response.element_equals_to( 'div.rc-user span.user', - '<span class="user"> %s</span>' % h.link_to_user('test_admin')) + '<a href="/_profiles/test_admin">test_admin</a></span>') response.mustcontain('gist-desc') def test_show_as_raw(self, create_gist): diff --git a/rhodecode/tests/functional/test_users.py b/rhodecode/tests/functional/test_users.py deleted file mode 100644 --- a/rhodecode/tests/functional/test_users.py +++ /dev/null @@ -1,74 +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 <http://www.gnu.org/licenses/>. -# -# 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/ - -from rhodecode.model.meta import Session -from rhodecode.model.db import User -from rhodecode.tests import ( - TestController, url, 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() - - -class TestUsersController(TestController): - test_user_1 = 'testme' - destroy_users = set() - - @classmethod - def teardown_class(cls): - fixture.destroy_users(cls.destroy_users) - - def test_user_profile(self): - edit_link_css = '.user-profile .panel-edit' - self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) - user = fixture.create_user( - self.test_user_1, password='qweqwe', email='testme@rhodecode.org') - Session().commit() - self.destroy_users.add(self.test_user_1) - - response = self.app.get(url('user_profile', username=user.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(url('user_profile', username=user.username)) - assert_response = AssertResponse(response) - assert_response.element_contains(edit_link_css, 'Edit') - - def test_user_profile_not_available(self): - user = fixture.create_user( - self.test_user_1, password='qweqwe', email='testme@rhodecode.org') - Session().commit() - self.destroy_users.add(self.test_user_1) - - self.app.get(url('user_profile', username=user.username), status=302) - - self.log_user() - # default user - self.app.get( - url('user_profile', username=User.DEFAULT_USER), status=404) - # actual 404 - self.app.get(url('user_profile', username='unknown'), status=404)