# HG changeset patch # User Daniel Dourvaris # Date 2017-07-07 21:22:59 # Node ID 89ddfad219680495f8912eebb817b646b5da470d # Parent 485023e66b4a20abf2f10257747ded6ee34603aa my-account: migrated left over controller functions into pyramid views. - pull requests are now lazy loaded via dataGrid 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 @@