diff --git a/rhodecode/apps/home/tests/test_home.py b/rhodecode/apps/home/tests/test_home.py
--- a/rhodecode/apps/home/tests/test_home.py
+++ b/rhodecode/apps/home/tests/test_home.py
@@ -132,3 +132,9 @@ class TestHomeController(TestController)
response.mustcontain(version_string)
if state is False:
response.mustcontain(no=[version_string])
+
+ def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
+ response = self.app.get(route_path('home'))
+ assert_response = response.assert_response()
+ element = assert_response.get_element('.logout #csrf_token')
+ assert element.value == csrf_token
diff --git a/rhodecode/apps/my_account/__init__.py b/rhodecode/apps/my_account/__init__.py
--- a/rhodecode/apps/my_account/__init__.py
+++ b/rhodecode/apps/my_account/__init__.py
@@ -28,6 +28,15 @@ def includeme(config):
name='my_account_profile',
pattern=ADMIN_PREFIX + '/my_account/profile')
+ # my account edit details
+ config.add_route(
+ name='my_account_edit',
+ pattern=ADMIN_PREFIX + '/my_account/edit')
+ config.add_route(
+ name='my_account_update',
+ pattern=ADMIN_PREFIX + '/my_account/update')
+
+ # my account password
config.add_route(
name='my_account_password',
pattern=ADMIN_PREFIX + '/my_account/password')
@@ -36,6 +45,7 @@ def includeme(config):
name='my_account_password_update',
pattern=ADMIN_PREFIX + '/my_account/password')
+ # my account tokens
config.add_route(
name='my_account_auth_tokens',
pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
@@ -46,6 +56,7 @@ def includeme(config):
name='my_account_auth_tokens_delete',
pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
+ # my account emails
config.add_route(
name='my_account_emails',
pattern=ADMIN_PREFIX + '/my_account/emails')
@@ -76,6 +87,14 @@ def includeme(config):
name='my_account_notifications_toggle_visibility',
pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
+ # my account pull requests
+ config.add_route(
+ name='my_account_pullrequests',
+ pattern=ADMIN_PREFIX + '/my_account/pull_requests')
+ config.add_route(
+ name='my_account_pullrequests_data',
+ pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
+
# channelstream test
config.add_route(
name='my_account_notifications_test_channelstream',
diff --git a/rhodecode/apps/my_account/tests/test_my_account_edit.py b/rhodecode/apps/my_account/tests/test_my_account_edit.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/my_account/tests/test_my_account_edit.py
@@ -0,0 +1,205 @@
+# -*- 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/
+# -*- 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 pytest
+
+from rhodecode.model.db import User
+from rhodecode.tests import TestController, assert_session_flash
+from rhodecode.lib import helpers as h
+
+
+def route_path(name, params=None, **kwargs):
+ import urllib
+ from rhodecode.apps._base import ADMIN_PREFIX
+
+ base_url = {
+ 'my_account_edit': ADMIN_PREFIX + '/my_account/edit',
+ 'my_account_update': ADMIN_PREFIX + '/my_account/update',
+ 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests',
+ 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data',
+ }[name].format(**kwargs)
+
+ if params:
+ base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
+ return base_url
+
+
+class TestMyAccountEdit(TestController):
+
+ def test_my_account_edit(self):
+ self.log_user()
+ response = self.app.get(route_path('my_account_edit'))
+
+ response.mustcontain('value="test_admin')
+
+ @pytest.mark.backends("git", "hg")
+ def test_my_account_my_pullrequests(self, pr_util):
+ self.log_user()
+ response = self.app.get(route_path('my_account_pullrequests'))
+ response.mustcontain('There are currently no open pull '
+ 'requests requiring your participation.')
+
+ @pytest.mark.backends("git", "hg")
+ def test_my_account_my_pullrequests_data(self, pr_util, xhr_header):
+ self.log_user()
+ response = self.app.get(route_path('my_account_pullrequests_data'),
+ extra_environ=xhr_header)
+ assert response.json == {
+ u'data': [], u'draw': None,
+ u'recordsFiltered': 0, u'recordsTotal': 0}
+
+ pr = pr_util.create_pull_request(title='TestMyAccountPR')
+ expected = {
+ 'author_raw': 'RhodeCode Admin',
+ 'name_raw': pr.pull_request_id
+ }
+ response = self.app.get(route_path('my_account_pullrequests_data'),
+ extra_environ=xhr_header)
+ assert response.json['recordsTotal'] == 1
+ assert response.json['data'][0]['author_raw'] == expected['author_raw']
+
+ assert response.json['data'][0]['author_raw'] == expected['author_raw']
+ assert response.json['data'][0]['name_raw'] == expected['name_raw']
+
+ @pytest.mark.parametrize(
+ "name, attrs", [
+ ('firstname', {'firstname': 'new_username'}),
+ ('lastname', {'lastname': 'new_username'}),
+ ('admin', {'admin': True}),
+ ('admin', {'admin': False}),
+ ('extern_type', {'extern_type': 'ldap'}),
+ ('extern_type', {'extern_type': None}),
+ # ('extern_name', {'extern_name': 'test'}),
+ # ('extern_name', {'extern_name': None}),
+ ('active', {'active': False}),
+ ('active', {'active': True}),
+ ('email', {'email': 'some@email.com'}),
+ ])
+ def test_my_account_update(self, name, attrs, user_util):
+ usr = user_util.create_user(password='qweqwe')
+ params = usr.get_api_data() # current user data
+ user_id = usr.user_id
+ self.log_user(
+ username=usr.username, password='qweqwe')
+
+ params.update({'password_confirmation': ''})
+ params.update({'new_password': ''})
+ params.update({'extern_type': 'rhodecode'})
+ params.update({'extern_name': 'rhodecode'})
+ params.update({'csrf_token': self.csrf_token})
+
+ params.update(attrs)
+ # my account page cannot set language param yet, only for admins
+ del params['language']
+ response = self.app.post(route_path('my_account_update'), params)
+
+ assert_session_flash(
+ response, 'Your account was updated successfully')
+
+ del params['csrf_token']
+
+ updated_user = User.get(user_id)
+ updated_params = updated_user.get_api_data()
+ updated_params.update({'password_confirmation': ''})
+ updated_params.update({'new_password': ''})
+
+ params['last_login'] = updated_params['last_login']
+ params['last_activity'] = updated_params['last_activity']
+ # my account page cannot set language param yet, only for admins
+ # but we get this info from API anyway
+ params['language'] = updated_params['language']
+
+ if name == 'email':
+ params['emails'] = [attrs['email']]
+ if name == 'extern_type':
+ # cannot update this via form, expected value is original one
+ params['extern_type'] = "rhodecode"
+ if name == 'extern_name':
+ # cannot update this via form, expected value is original one
+ params['extern_name'] = str(user_id)
+ if name == 'active':
+ # my account cannot deactivate account
+ params['active'] = True
+ if name == 'admin':
+ # my account cannot make you an admin !
+ params['admin'] = False
+
+ assert params == updated_params
+
+ def test_my_account_update_err_email_exists(self):
+ self.log_user()
+
+ new_email = 'test_regular@mail.com' # already existing email
+ params = {
+ 'username': 'test_admin',
+ 'new_password': 'test12',
+ 'password_confirmation': 'test122',
+ 'firstname': 'NewName',
+ 'lastname': 'NewLastname',
+ 'email': new_email,
+ 'csrf_token': self.csrf_token,
+ }
+
+ response = self.app.post(route_path('my_account_update'),
+ params=params)
+
+ response.mustcontain('This e-mail address is already taken')
+
+ def test_my_account_update_bad_email_address(self):
+ self.log_user('test_regular2', 'test12')
+
+ new_email = 'newmail.pl'
+ params = {
+ 'username': 'test_admin',
+ 'new_password': 'test12',
+ 'password_confirmation': 'test122',
+ 'firstname': 'NewName',
+ 'lastname': 'NewLastname',
+ 'email': new_email,
+ 'csrf_token': self.csrf_token,
+ }
+ response = self.app.post(route_path('my_account_update'),
+ params=params)
+
+ response.mustcontain('An email address must contain a single @')
+ from rhodecode.model import validators
+ msg = validators.ValidUsername(
+ edit=False, old_data={})._messages['username_exists']
+ msg = h.html_escape(msg % {'username': 'test_admin'})
+ response.mustcontain(u"%s" % msg)
diff --git a/rhodecode/apps/my_account/views.py b/rhodecode/apps/my_account/views.py
--- a/rhodecode/apps/my_account/views.py
+++ b/rhodecode/apps/my_account/views.py
@@ -24,8 +24,10 @@ import datetime
import formencode
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
+from pyramid.renderers import render
+from pyramid.response import Response
-from rhodecode.apps._base import BaseAppView
+from rhodecode.apps._base import BaseAppView, DataGridAppView
from rhodecode import forms
from rhodecode.lib import helpers as h
from rhodecode.lib import audit_logger
@@ -33,11 +35,16 @@ from rhodecode.lib.ext_json import json
from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
from rhodecode.lib.channelstream import channelstream_request, \
ChannelstreamException
-from rhodecode.lib.utils2 import safe_int, md5
+from rhodecode.lib.utils import PartialRenderer
+from rhodecode.lib.utils2 import safe_int, md5, str2bool
from rhodecode.model.auth_token import AuthTokenModel
+from rhodecode.model.comment import CommentsModel
from rhodecode.model.db import (
- Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
+ Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
+ PullRequest)
+from rhodecode.model.forms import UserForm
from rhodecode.model.meta import Session
+from rhodecode.model.pull_request import PullRequestModel
from rhodecode.model.scm import RepoList
from rhodecode.model.user import UserModel
from rhodecode.model.repo import RepoModel
@@ -46,7 +53,7 @@ from rhodecode.model.validation_schema.s
log = logging.getLogger(__name__)
-class MyAccountView(BaseAppView):
+class MyAccountView(BaseAppView, DataGridAppView):
ALLOW_SCOPED_TOKENS = False
"""
This view has alternative version inside EE, if modified please take a look
@@ -396,4 +403,182 @@ class MyAccountView(BaseAppView):
new_status = not user.user_data.get('notification_status', True)
user.update_userdata(notification_status=new_status)
Session().commit()
- return user.user_data['notification_status']
\ No newline at end of file
+ return user.user_data['notification_status']
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='my_account_edit',
+ request_method='GET',
+ renderer='rhodecode:templates/admin/my_account/my_account.mako')
+ def my_account_edit(self):
+ c = self.load_default_context()
+ c.active = 'profile_edit'
+
+ c.perm_user = c.auth_user
+ c.extern_type = c.user.extern_type
+ c.extern_name = c.user.extern_name
+
+ defaults = c.user.get_dict()
+
+ data = render('rhodecode:templates/admin/my_account/my_account.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=defaults,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @CSRFRequired()
+ @view_config(
+ route_name='my_account_update',
+ request_method='POST',
+ renderer='rhodecode:templates/admin/my_account/my_account.mako')
+ def my_account_update(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.active = 'profile_edit'
+
+ c.perm_user = c.auth_user
+ c.extern_type = c.user.extern_type
+ c.extern_name = c.user.extern_name
+
+ _form = UserForm(edit=True,
+ old_data={'user_id': self._rhodecode_user.user_id,
+ 'email': self._rhodecode_user.email})()
+ form_result = {}
+ try:
+ post_data = dict(self.request.POST)
+ post_data['new_password'] = ''
+ post_data['password_confirmation'] = ''
+ form_result = _form.to_python(post_data)
+ # skip updating those attrs for my account
+ skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
+ 'new_password', 'password_confirmation']
+ # TODO: plugin should define if username can be updated
+ if c.extern_type != "rhodecode":
+ # forbid updating username for external accounts
+ skip_attrs.append('username')
+
+ UserModel().update_user(
+ self._rhodecode_user.user_id, skip_attrs=skip_attrs,
+ **form_result)
+ h.flash(_('Your account was updated successfully'),
+ category='success')
+ Session().commit()
+
+ except formencode.Invalid as errors:
+ data = render(
+ 'rhodecode:templates/admin/my_account/my_account.mako',
+ self._get_template_context(c), self.request)
+
+ html = formencode.htmlfill.render(
+ data,
+ defaults=errors.value,
+ errors=errors.error_dict or {},
+ prefix_error=False,
+ encoding="UTF-8",
+ force_defaults=False)
+ return Response(html)
+
+ except Exception:
+ log.exception("Exception updating user")
+ h.flash(_('Error occurred during update of user %s')
+ % form_result.get('username'), category='error')
+ raise HTTPFound(h.route_path('my_account_profile'))
+
+ raise HTTPFound(h.route_path('my_account_profile'))
+
+ def _get_pull_requests_list(self, statuses):
+ draw, start, limit = self._extract_chunk(self.request)
+ search_q, order_by, order_dir = self._extract_ordering(self.request)
+ _render = PartialRenderer('data_table/_dt_elements.mako')
+
+ pull_requests = PullRequestModel().get_im_participating_in(
+ user_id=self._rhodecode_user.user_id,
+ statuses=statuses,
+ offset=start, length=limit, order_by=order_by,
+ order_dir=order_dir)
+
+ pull_requests_total_count = PullRequestModel().count_im_participating_in(
+ user_id=self._rhodecode_user.user_id, statuses=statuses)
+
+ data = []
+ comments_model = CommentsModel()
+ for pr in pull_requests:
+ repo_id = pr.target_repo_id
+ comments = comments_model.get_all_comments(
+ repo_id, pull_request=pr)
+ owned = pr.user_id == self._rhodecode_user.user_id
+
+ data.append({
+ 'target_repo': _render('pullrequest_target_repo',
+ pr.target_repo.repo_name),
+ 'name': _render('pullrequest_name',
+ pr.pull_request_id, pr.target_repo.repo_name,
+ short=True),
+ 'name_raw': pr.pull_request_id,
+ 'status': _render('pullrequest_status',
+ pr.calculated_review_status()),
+ 'title': _render(
+ 'pullrequest_title', pr.title, pr.description),
+ 'description': h.escape(pr.description),
+ 'updated_on': _render('pullrequest_updated_on',
+ h.datetime_to_time(pr.updated_on)),
+ 'updated_on_raw': h.datetime_to_time(pr.updated_on),
+ 'created_on': _render('pullrequest_updated_on',
+ h.datetime_to_time(pr.created_on)),
+ 'created_on_raw': h.datetime_to_time(pr.created_on),
+ 'author': _render('pullrequest_author',
+ pr.author.full_contact, ),
+ 'author_raw': pr.author.full_name,
+ 'comments': _render('pullrequest_comments', len(comments)),
+ 'comments_raw': len(comments),
+ 'closed': pr.is_closed(),
+ 'owned': owned
+ })
+
+ # json used to render the grid
+ data = ({
+ 'draw': draw,
+ 'data': data,
+ 'recordsTotal': pull_requests_total_count,
+ 'recordsFiltered': pull_requests_total_count,
+ })
+ return data
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='my_account_pullrequests',
+ request_method='GET',
+ renderer='rhodecode:templates/admin/my_account/my_account.mako')
+ def my_account_pullrequests(self):
+ c = self.load_default_context()
+ c.active = 'pullrequests'
+ req_get = self.request.GET
+
+ c.closed = str2bool(req_get.get('pr_show_closed'))
+
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @NotAnonymous()
+ @view_config(
+ route_name='my_account_pullrequests_data',
+ request_method='GET', renderer='json_ext')
+ def my_account_pullrequests_data(self):
+ req_get = self.request.GET
+ closed = str2bool(req_get.get('closed'))
+
+ statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
+ if closed:
+ statuses += [PullRequest.STATUS_CLOSED]
+
+ data = self._get_pull_requests_list(statuses=statuses)
+ return data
+
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -462,19 +462,11 @@ def make_map(config):
with rmap.submapper(path_prefix=ADMIN_PREFIX,
controller='admin/my_account') as m:
- m.connect('my_account_edit', '/my_account/edit',
- action='my_account_edit', conditions={'method': ['GET']})
- m.connect('my_account', '/my_account/update',
- action='my_account_update', conditions={'method': ['POST']})
-
# NOTE(marcink): this needs to be kept for password force flag to be
- # handler, remove after migration to pyramid
+ # handled in pylons controllers, remove after full migration to pyramid
m.connect('my_account_password', '/my_account/password',
action='my_account_password', conditions={'method': ['GET']})
- m.connect('my_account_pullrequests', '/my_account/pull_requests',
- action='my_account_pullrequests', conditions={'method': ['GET']})
-
# NOTIFICATION REST ROUTES
with rmap.submapper(path_prefix=ADMIN_PREFIX,
controller='admin/notifications') as m:
diff --git a/rhodecode/controllers/admin/my_account.py b/rhodecode/controllers/admin/my_account.py
deleted file mode 100644
--- a/rhodecode/controllers/admin/my_account.py
+++ /dev/null
@@ -1,236 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2013-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/
-
-
-"""
-my account controller for RhodeCode admin
-"""
-
-import logging
-
-import formencode
-from formencode import htmlfill
-from pyramid.httpexceptions import HTTPFound
-
-from pylons import request, tmpl_context as c
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
-
-from rhodecode.lib import helpers as h
-from rhodecode.lib import auth
-from rhodecode.lib.auth import (
- LoginRequired, NotAnonymous, AuthUser)
-from rhodecode.lib.base import BaseController, render
-from rhodecode.lib.utils2 import safe_int, str2bool
-from rhodecode.lib.ext_json import json
-
-from rhodecode.model.db import (
- Repository, PullRequest, UserEmailMap, User, UserFollowing)
-from rhodecode.model.forms import UserForm
-from rhodecode.model.user import UserModel
-from rhodecode.model.meta import Session
-from rhodecode.model.pull_request import PullRequestModel
-from rhodecode.model.comment import CommentsModel
-
-log = logging.getLogger(__name__)
-
-
-class MyAccountController(BaseController):
- """REST Controller styled on the Atom Publishing Protocol"""
- # To properly map this controller, ensure your config/routing.py
- # file has a resource setup:
- # map.resource('setting', 'settings', controller='admin/settings',
- # path_prefix='/admin', name_prefix='admin_')
-
- @LoginRequired()
- @NotAnonymous()
- def __before__(self):
- super(MyAccountController, self).__before__()
-
- def __load_data(self):
- c.user = User.get(c.rhodecode_user.user_id)
- if c.user.username == User.DEFAULT_USER:
- h.flash(_("You can't edit this user since it's"
- " crucial for entire application"), category='warning')
- return redirect(h.route_path('users'))
-
- c.auth_user = AuthUser(
- user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
-
- @auth.CSRFRequired()
- def my_account_update(self):
- """
- POST /_admin/my_account Updates info of my account
- """
- # url('my_account')
- c.active = 'profile_edit'
- self.__load_data()
- c.perm_user = c.auth_user
- c.extern_type = c.user.extern_type
- c.extern_name = c.user.extern_name
-
- defaults = c.user.get_dict()
- update = False
- _form = UserForm(edit=True,
- old_data={'user_id': c.rhodecode_user.user_id,
- 'email': c.rhodecode_user.email})()
- form_result = {}
- try:
- post_data = dict(request.POST)
- post_data['new_password'] = ''
- post_data['password_confirmation'] = ''
- form_result = _form.to_python(post_data)
- # skip updating those attrs for my account
- skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
- 'new_password', 'password_confirmation']
- # TODO: plugin should define if username can be updated
- if c.extern_type != "rhodecode":
- # forbid updating username for external accounts
- skip_attrs.append('username')
-
- UserModel().update_user(
- c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
- h.flash(_('Your account was updated successfully'),
- category='success')
- Session().commit()
- update = True
-
- except formencode.Invalid as errors:
- return htmlfill.render(
- render('admin/my_account/my_account.mako'),
- defaults=errors.value,
- errors=errors.error_dict or {},
- prefix_error=False,
- encoding="UTF-8",
- force_defaults=False)
- except Exception:
- log.exception("Exception updating user")
- h.flash(_('Error occurred during update of user %s')
- % form_result.get('username'), category='error')
-
- if update:
- raise HTTPFound(h.route_path('my_account_profile'))
-
- return htmlfill.render(
- render('admin/my_account/my_account.mako'),
- defaults=defaults,
- encoding="UTF-8",
- force_defaults=False
- )
-
- def my_account_edit(self):
- """
- GET /_admin/my_account/edit Displays edit form of my account
- """
- c.active = 'profile_edit'
- self.__load_data()
- c.perm_user = c.auth_user
- c.extern_type = c.user.extern_type
- c.extern_name = c.user.extern_name
-
- defaults = c.user.get_dict()
- return htmlfill.render(
- render('admin/my_account/my_account.mako'),
- defaults=defaults,
- encoding="UTF-8",
- force_defaults=False
- )
-
- def _extract_ordering(self, request):
- column_index = safe_int(request.GET.get('order[0][column]'))
- order_dir = request.GET.get('order[0][dir]', 'desc')
- order_by = request.GET.get(
- 'columns[%s][data][sort]' % column_index, 'name_raw')
- return order_by, order_dir
-
- def _get_pull_requests_list(self, statuses):
- start = safe_int(request.GET.get('start'), 0)
- length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
- order_by, order_dir = self._extract_ordering(request)
-
- pull_requests = PullRequestModel().get_im_participating_in(
- user_id=c.rhodecode_user.user_id,
- statuses=statuses,
- offset=start, length=length, order_by=order_by,
- order_dir=order_dir)
-
- pull_requests_total_count = PullRequestModel().count_im_participating_in(
- user_id=c.rhodecode_user.user_id, statuses=statuses)
-
- from rhodecode.lib.utils import PartialRenderer
- _render = PartialRenderer('data_table/_dt_elements.mako')
- data = []
- for pr in pull_requests:
- repo_id = pr.target_repo_id
- comments = CommentsModel().get_all_comments(
- repo_id, pull_request=pr)
- owned = pr.user_id == c.rhodecode_user.user_id
- status = pr.calculated_review_status()
-
- data.append({
- 'target_repo': _render('pullrequest_target_repo',
- pr.target_repo.repo_name),
- 'name': _render('pullrequest_name',
- pr.pull_request_id, pr.target_repo.repo_name,
- short=True),
- 'name_raw': pr.pull_request_id,
- 'status': _render('pullrequest_status', status),
- 'title': _render(
- 'pullrequest_title', pr.title, pr.description),
- 'description': h.escape(pr.description),
- 'updated_on': _render('pullrequest_updated_on',
- h.datetime_to_time(pr.updated_on)),
- 'updated_on_raw': h.datetime_to_time(pr.updated_on),
- 'created_on': _render('pullrequest_updated_on',
- h.datetime_to_time(pr.created_on)),
- 'created_on_raw': h.datetime_to_time(pr.created_on),
- 'author': _render('pullrequest_author',
- pr.author.full_contact, ),
- 'author_raw': pr.author.full_name,
- 'comments': _render('pullrequest_comments', len(comments)),
- 'comments_raw': len(comments),
- 'closed': pr.is_closed(),
- 'owned': owned
- })
- # json used to render the grid
- data = ({
- 'data': data,
- 'recordsTotal': pull_requests_total_count,
- 'recordsFiltered': pull_requests_total_count,
- })
- return data
-
- def my_account_pullrequests(self):
- c.active = 'pullrequests'
- self.__load_data()
- c.show_closed = str2bool(request.GET.get('pr_show_closed'))
-
- statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
- if c.show_closed:
- statuses += [PullRequest.STATUS_CLOSED]
- data = self._get_pull_requests_list(statuses)
- if not request.is_xhr:
- c.data_participate = json.dumps(data['data'])
- c.records_total_participate = data['recordsTotal']
- return render('admin/my_account/my_account.mako')
- else:
- return json.dumps(data)
-
-
diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js
--- a/rhodecode/public/js/rhodecode/routes.js
+++ b/rhodecode/public/js/rhodecode/routes.js
@@ -136,6 +136,8 @@ function registerRCRoutes() {
pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
+ pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
+ pyroutes.register('my_account_update', '/_admin/my_account/update', []);
pyroutes.register('my_account_password', '/_admin/my_account/password', []);
pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
@@ -149,6 +151,8 @@ function registerRCRoutes() {
pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
+ pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
+ pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
pyroutes.register('gists_show', '/_admin/gists', []);
pyroutes.register('gists_new', '/_admin/gists/new', []);
diff --git a/rhodecode/templates/admin/my_account/my_account.mako b/rhodecode/templates/admin/my_account/my_account.mako
--- a/rhodecode/templates/admin/my_account/my_account.mako
+++ b/rhodecode/templates/admin/my_account/my_account.mako
@@ -37,7 +37,7 @@
${_('Emails')}
${_('Repositories')}
${_('Watched')}
- ${_('Pull Requests')}
+ ${_('Pull Requests')}
${_('Permissions')}
${_('Live Notifications')}
diff --git a/rhodecode/templates/admin/my_account/my_account_profile.mako b/rhodecode/templates/admin/my_account/my_account_profile.mako
--- a/rhodecode/templates/admin/my_account/my_account_profile.mako
+++ b/rhodecode/templates/admin/my_account/my_account_profile.mako
@@ -2,7 +2,7 @@
diff --git a/rhodecode/templates/admin/my_account/my_account_profile_edit.mako b/rhodecode/templates/admin/my_account/my_account_profile_edit.mako
--- a/rhodecode/templates/admin/my_account/my_account_profile_edit.mako
+++ b/rhodecode/templates/admin/my_account/my_account_profile_edit.mako
@@ -6,7 +6,7 @@
- ${h.secure_form(url('my_account'), method='post', class_='form')}
+ ${h.secure_form(h.route_path('my_account_update'), class_='form', method='POST')}
<% readonly = None %>
<% disabled = "" %>
diff --git a/rhodecode/templates/admin/my_account/my_account_pullrequests.mako b/rhodecode/templates/admin/my_account/my_account_pullrequests.mako
--- a/rhodecode/templates/admin/my_account/my_account_pullrequests.mako
+++ b/rhodecode/templates/admin/my_account/my_account_pullrequests.mako
@@ -2,7 +2,7 @@
- %if c.show_closed:
+ %if c.closed:
${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
%else:
${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
@@ -12,25 +12,41 @@
-
${_('Pull Requests You Participate In')}: ${c.records_total_participate}
+ ${_('Pull Requests You Participate In')}
-
-
diff --git a/rhodecode/tests/functional/test_admin_my_account.py b/rhodecode/tests/functional/test_admin_my_account.py
deleted file mode 100644
--- a/rhodecode/tests/functional/test_admin_my_account.py
+++ /dev/null
@@ -1,181 +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/
-
-import pytest
-
-from rhodecode.lib import helpers as h
-from rhodecode.model.db import User, UserFollowing, Repository
-from rhodecode.tests import (
- TestController, url, TEST_USER_ADMIN_LOGIN, assert_session_flash)
-from rhodecode.tests.fixture import Fixture
-from rhodecode.tests.utils import AssertResponse
-
-fixture = Fixture()
-
-
-def route_path(name, **kwargs):
- return {
- 'home': '/',
- }[name].format(**kwargs)
-
-
-class TestMyAccountController(TestController):
- test_user_1 = 'testme'
- test_user_1_password = '0jd83nHNS/d23n'
- destroy_users = set()
-
- @classmethod
- def teardown_class(cls):
- fixture.destroy_users(cls.destroy_users)
-
- def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
- response = self.app.get(route_path('home'))
- assert_response = AssertResponse(response)
- element = assert_response.get_element('.logout #csrf_token')
- assert element.value == csrf_token
-
- def test_my_account_edit(self):
- self.log_user()
- response = self.app.get(url('my_account_edit'))
-
- response.mustcontain('value="test_admin')
-
- @pytest.mark.backends("git", "hg")
- def test_my_account_my_pullrequests(self, pr_util):
- self.log_user()
- response = self.app.get(url('my_account_pullrequests'))
- response.mustcontain('There are currently no open pull '
- 'requests requiring your participation.')
-
- pr = pr_util.create_pull_request(title='TestMyAccountPR')
- response = self.app.get(url('my_account_pullrequests'))
- response.mustcontain('"name_raw": %s' % pr.pull_request_id)
- response.mustcontain('TestMyAccountPR')
-
- @pytest.mark.parametrize(
- "name, attrs", [
- ('firstname', {'firstname': 'new_username'}),
- ('lastname', {'lastname': 'new_username'}),
- ('admin', {'admin': True}),
- ('admin', {'admin': False}),
- ('extern_type', {'extern_type': 'ldap'}),
- ('extern_type', {'extern_type': None}),
- # ('extern_name', {'extern_name': 'test'}),
- # ('extern_name', {'extern_name': None}),
- ('active', {'active': False}),
- ('active', {'active': True}),
- ('email', {'email': 'some@email.com'}),
- ])
- def test_my_account_update(self, name, attrs):
- usr = fixture.create_user(self.test_user_1,
- password=self.test_user_1_password,
- email='testme@rhodecode.org',
- extern_type='rhodecode',
- extern_name=self.test_user_1,
- skip_if_exists=True)
- self.destroy_users.add(self.test_user_1)
-
- params = usr.get_api_data() # current user data
- user_id = usr.user_id
- self.log_user(
- username=self.test_user_1, password=self.test_user_1_password)
-
- params.update({'password_confirmation': ''})
- params.update({'new_password': ''})
- params.update({'extern_type': 'rhodecode'})
- params.update({'extern_name': self.test_user_1})
- params.update({'csrf_token': self.csrf_token})
-
- params.update(attrs)
- # my account page cannot set language param yet, only for admins
- del params['language']
- response = self.app.post(url('my_account'), params)
-
- assert_session_flash(
- response, 'Your account was updated successfully')
-
- del params['csrf_token']
-
- updated_user = User.get_by_username(self.test_user_1)
- updated_params = updated_user.get_api_data()
- updated_params.update({'password_confirmation': ''})
- updated_params.update({'new_password': ''})
-
- params['last_login'] = updated_params['last_login']
- params['last_activity'] = updated_params['last_activity']
- # my account page cannot set language param yet, only for admins
- # but we get this info from API anyway
- params['language'] = updated_params['language']
-
- if name == 'email':
- params['emails'] = [attrs['email']]
- if name == 'extern_type':
- # cannot update this via form, expected value is original one
- params['extern_type'] = "rhodecode"
- if name == 'extern_name':
- # cannot update this via form, expected value is original one
- params['extern_name'] = str(user_id)
- if name == 'active':
- # my account cannot deactivate account
- params['active'] = True
- if name == 'admin':
- # my account cannot make you an admin !
- params['admin'] = False
-
- assert params == updated_params
-
- def test_my_account_update_err_email_exists(self):
- self.log_user()
-
- new_email = 'test_regular@mail.com' # already exisitn email
- response = self.app.post(url('my_account'),
- params={
- 'username': 'test_admin',
- 'new_password': 'test12',
- 'password_confirmation': 'test122',
- 'firstname': 'NewName',
- 'lastname': 'NewLastname',
- 'email': new_email,
- 'csrf_token': self.csrf_token,
- })
-
- response.mustcontain('This e-mail address is already taken')
-
- def test_my_account_update_err(self):
- self.log_user('test_regular2', 'test12')
-
- new_email = 'newmail.pl'
- response = self.app.post(url('my_account'),
- params={
- 'username': 'test_admin',
- 'new_password': 'test12',
- 'password_confirmation': 'test122',
- 'firstname': 'NewName',
- 'lastname': 'NewLastname',
- 'email': new_email,
- 'csrf_token': self.csrf_token,
- })
-
- response.mustcontain('An email address must contain a single @')
- from rhodecode.model import validators
- msg = validators.ValidUsername(
- edit=False, old_data={})._messages['username_exists']
- msg = h.html_escape(msg % {'username': 'test_admin'})
- response.mustcontain(u"%s" % msg)