Show More
@@ -0,0 +1,205 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | # -*- coding: utf-8 -*- | |||
|
21 | ||||
|
22 | # Copyright (C) 2016-2017 RhodeCode GmbH | |||
|
23 | # | |||
|
24 | # This program is free software: you can redistribute it and/or modify | |||
|
25 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
26 | # (only), as published by the Free Software Foundation. | |||
|
27 | # | |||
|
28 | # This program is distributed in the hope that it will be useful, | |||
|
29 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
30 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
31 | # GNU General Public License for more details. | |||
|
32 | # | |||
|
33 | # You should have received a copy of the GNU Affero General Public License | |||
|
34 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
35 | # | |||
|
36 | # This program is dual-licensed. If you wish to learn more about the | |||
|
37 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
38 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
39 | ||||
|
40 | import pytest | |||
|
41 | ||||
|
42 | from rhodecode.model.db import User | |||
|
43 | from rhodecode.tests import TestController, assert_session_flash | |||
|
44 | from rhodecode.lib import helpers as h | |||
|
45 | ||||
|
46 | ||||
|
47 | def route_path(name, params=None, **kwargs): | |||
|
48 | import urllib | |||
|
49 | from rhodecode.apps._base import ADMIN_PREFIX | |||
|
50 | ||||
|
51 | base_url = { | |||
|
52 | 'my_account_edit': ADMIN_PREFIX + '/my_account/edit', | |||
|
53 | 'my_account_update': ADMIN_PREFIX + '/my_account/update', | |||
|
54 | 'my_account_pullrequests': ADMIN_PREFIX + '/my_account/pull_requests', | |||
|
55 | 'my_account_pullrequests_data': ADMIN_PREFIX + '/my_account/pull_requests/data', | |||
|
56 | }[name].format(**kwargs) | |||
|
57 | ||||
|
58 | if params: | |||
|
59 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |||
|
60 | return base_url | |||
|
61 | ||||
|
62 | ||||
|
63 | class TestMyAccountEdit(TestController): | |||
|
64 | ||||
|
65 | def test_my_account_edit(self): | |||
|
66 | self.log_user() | |||
|
67 | response = self.app.get(route_path('my_account_edit')) | |||
|
68 | ||||
|
69 | response.mustcontain('value="test_admin') | |||
|
70 | ||||
|
71 | @pytest.mark.backends("git", "hg") | |||
|
72 | def test_my_account_my_pullrequests(self, pr_util): | |||
|
73 | self.log_user() | |||
|
74 | response = self.app.get(route_path('my_account_pullrequests')) | |||
|
75 | response.mustcontain('There are currently no open pull ' | |||
|
76 | 'requests requiring your participation.') | |||
|
77 | ||||
|
78 | @pytest.mark.backends("git", "hg") | |||
|
79 | def test_my_account_my_pullrequests_data(self, pr_util, xhr_header): | |||
|
80 | self.log_user() | |||
|
81 | response = self.app.get(route_path('my_account_pullrequests_data'), | |||
|
82 | extra_environ=xhr_header) | |||
|
83 | assert response.json == { | |||
|
84 | u'data': [], u'draw': None, | |||
|
85 | u'recordsFiltered': 0, u'recordsTotal': 0} | |||
|
86 | ||||
|
87 | pr = pr_util.create_pull_request(title='TestMyAccountPR') | |||
|
88 | expected = { | |||
|
89 | 'author_raw': 'RhodeCode Admin', | |||
|
90 | 'name_raw': pr.pull_request_id | |||
|
91 | } | |||
|
92 | response = self.app.get(route_path('my_account_pullrequests_data'), | |||
|
93 | extra_environ=xhr_header) | |||
|
94 | assert response.json['recordsTotal'] == 1 | |||
|
95 | assert response.json['data'][0]['author_raw'] == expected['author_raw'] | |||
|
96 | ||||
|
97 | assert response.json['data'][0]['author_raw'] == expected['author_raw'] | |||
|
98 | assert response.json['data'][0]['name_raw'] == expected['name_raw'] | |||
|
99 | ||||
|
100 | @pytest.mark.parametrize( | |||
|
101 | "name, attrs", [ | |||
|
102 | ('firstname', {'firstname': 'new_username'}), | |||
|
103 | ('lastname', {'lastname': 'new_username'}), | |||
|
104 | ('admin', {'admin': True}), | |||
|
105 | ('admin', {'admin': False}), | |||
|
106 | ('extern_type', {'extern_type': 'ldap'}), | |||
|
107 | ('extern_type', {'extern_type': None}), | |||
|
108 | # ('extern_name', {'extern_name': 'test'}), | |||
|
109 | # ('extern_name', {'extern_name': None}), | |||
|
110 | ('active', {'active': False}), | |||
|
111 | ('active', {'active': True}), | |||
|
112 | ('email', {'email': 'some@email.com'}), | |||
|
113 | ]) | |||
|
114 | def test_my_account_update(self, name, attrs, user_util): | |||
|
115 | usr = user_util.create_user(password='qweqwe') | |||
|
116 | params = usr.get_api_data() # current user data | |||
|
117 | user_id = usr.user_id | |||
|
118 | self.log_user( | |||
|
119 | username=usr.username, password='qweqwe') | |||
|
120 | ||||
|
121 | params.update({'password_confirmation': ''}) | |||
|
122 | params.update({'new_password': ''}) | |||
|
123 | params.update({'extern_type': 'rhodecode'}) | |||
|
124 | params.update({'extern_name': 'rhodecode'}) | |||
|
125 | params.update({'csrf_token': self.csrf_token}) | |||
|
126 | ||||
|
127 | params.update(attrs) | |||
|
128 | # my account page cannot set language param yet, only for admins | |||
|
129 | del params['language'] | |||
|
130 | response = self.app.post(route_path('my_account_update'), params) | |||
|
131 | ||||
|
132 | assert_session_flash( | |||
|
133 | response, 'Your account was updated successfully') | |||
|
134 | ||||
|
135 | del params['csrf_token'] | |||
|
136 | ||||
|
137 | updated_user = User.get(user_id) | |||
|
138 | updated_params = updated_user.get_api_data() | |||
|
139 | updated_params.update({'password_confirmation': ''}) | |||
|
140 | updated_params.update({'new_password': ''}) | |||
|
141 | ||||
|
142 | params['last_login'] = updated_params['last_login'] | |||
|
143 | params['last_activity'] = updated_params['last_activity'] | |||
|
144 | # my account page cannot set language param yet, only for admins | |||
|
145 | # but we get this info from API anyway | |||
|
146 | params['language'] = updated_params['language'] | |||
|
147 | ||||
|
148 | if name == 'email': | |||
|
149 | params['emails'] = [attrs['email']] | |||
|
150 | if name == 'extern_type': | |||
|
151 | # cannot update this via form, expected value is original one | |||
|
152 | params['extern_type'] = "rhodecode" | |||
|
153 | if name == 'extern_name': | |||
|
154 | # cannot update this via form, expected value is original one | |||
|
155 | params['extern_name'] = str(user_id) | |||
|
156 | if name == 'active': | |||
|
157 | # my account cannot deactivate account | |||
|
158 | params['active'] = True | |||
|
159 | if name == 'admin': | |||
|
160 | # my account cannot make you an admin ! | |||
|
161 | params['admin'] = False | |||
|
162 | ||||
|
163 | assert params == updated_params | |||
|
164 | ||||
|
165 | def test_my_account_update_err_email_exists(self): | |||
|
166 | self.log_user() | |||
|
167 | ||||
|
168 | new_email = 'test_regular@mail.com' # already existing email | |||
|
169 | params = { | |||
|
170 | 'username': 'test_admin', | |||
|
171 | 'new_password': 'test12', | |||
|
172 | 'password_confirmation': 'test122', | |||
|
173 | 'firstname': 'NewName', | |||
|
174 | 'lastname': 'NewLastname', | |||
|
175 | 'email': new_email, | |||
|
176 | 'csrf_token': self.csrf_token, | |||
|
177 | } | |||
|
178 | ||||
|
179 | response = self.app.post(route_path('my_account_update'), | |||
|
180 | params=params) | |||
|
181 | ||||
|
182 | response.mustcontain('This e-mail address is already taken') | |||
|
183 | ||||
|
184 | def test_my_account_update_bad_email_address(self): | |||
|
185 | self.log_user('test_regular2', 'test12') | |||
|
186 | ||||
|
187 | new_email = 'newmail.pl' | |||
|
188 | params = { | |||
|
189 | 'username': 'test_admin', | |||
|
190 | 'new_password': 'test12', | |||
|
191 | 'password_confirmation': 'test122', | |||
|
192 | 'firstname': 'NewName', | |||
|
193 | 'lastname': 'NewLastname', | |||
|
194 | 'email': new_email, | |||
|
195 | 'csrf_token': self.csrf_token, | |||
|
196 | } | |||
|
197 | response = self.app.post(route_path('my_account_update'), | |||
|
198 | params=params) | |||
|
199 | ||||
|
200 | response.mustcontain('An email address must contain a single @') | |||
|
201 | from rhodecode.model import validators | |||
|
202 | msg = validators.ValidUsername( | |||
|
203 | edit=False, old_data={})._messages['username_exists'] | |||
|
204 | msg = h.html_escape(msg % {'username': 'test_admin'}) | |||
|
205 | response.mustcontain(u"%s" % msg) |
@@ -132,3 +132,9 b' class TestHomeController(TestController)' | |||||
132 | response.mustcontain(version_string) |
|
132 | response.mustcontain(version_string) | |
133 | if state is False: |
|
133 | if state is False: | |
134 | response.mustcontain(no=[version_string]) |
|
134 | response.mustcontain(no=[version_string]) | |
|
135 | ||||
|
136 | def test_logout_form_contains_csrf(self, autologin_user, csrf_token): | |||
|
137 | response = self.app.get(route_path('home')) | |||
|
138 | assert_response = response.assert_response() | |||
|
139 | element = assert_response.get_element('.logout #csrf_token') | |||
|
140 | assert element.value == csrf_token |
@@ -28,6 +28,15 b' def includeme(config):' | |||||
28 | name='my_account_profile', |
|
28 | name='my_account_profile', | |
29 | pattern=ADMIN_PREFIX + '/my_account/profile') |
|
29 | pattern=ADMIN_PREFIX + '/my_account/profile') | |
30 |
|
30 | |||
|
31 | # my account edit details | |||
|
32 | config.add_route( | |||
|
33 | name='my_account_edit', | |||
|
34 | pattern=ADMIN_PREFIX + '/my_account/edit') | |||
|
35 | config.add_route( | |||
|
36 | name='my_account_update', | |||
|
37 | pattern=ADMIN_PREFIX + '/my_account/update') | |||
|
38 | ||||
|
39 | # my account password | |||
31 | config.add_route( |
|
40 | config.add_route( | |
32 | name='my_account_password', |
|
41 | name='my_account_password', | |
33 | pattern=ADMIN_PREFIX + '/my_account/password') |
|
42 | pattern=ADMIN_PREFIX + '/my_account/password') | |
@@ -36,6 +45,7 b' def includeme(config):' | |||||
36 | name='my_account_password_update', |
|
45 | name='my_account_password_update', | |
37 | pattern=ADMIN_PREFIX + '/my_account/password') |
|
46 | pattern=ADMIN_PREFIX + '/my_account/password') | |
38 |
|
47 | |||
|
48 | # my account tokens | |||
39 | config.add_route( |
|
49 | config.add_route( | |
40 | name='my_account_auth_tokens', |
|
50 | name='my_account_auth_tokens', | |
41 | pattern=ADMIN_PREFIX + '/my_account/auth_tokens') |
|
51 | pattern=ADMIN_PREFIX + '/my_account/auth_tokens') | |
@@ -46,6 +56,7 b' def includeme(config):' | |||||
46 | name='my_account_auth_tokens_delete', |
|
56 | name='my_account_auth_tokens_delete', | |
47 | pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete') |
|
57 | pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete') | |
48 |
|
58 | |||
|
59 | # my account emails | |||
49 | config.add_route( |
|
60 | config.add_route( | |
50 | name='my_account_emails', |
|
61 | name='my_account_emails', | |
51 | pattern=ADMIN_PREFIX + '/my_account/emails') |
|
62 | pattern=ADMIN_PREFIX + '/my_account/emails') | |
@@ -76,6 +87,14 b' def includeme(config):' | |||||
76 | name='my_account_notifications_toggle_visibility', |
|
87 | name='my_account_notifications_toggle_visibility', | |
77 | pattern=ADMIN_PREFIX + '/my_account/toggle_visibility') |
|
88 | pattern=ADMIN_PREFIX + '/my_account/toggle_visibility') | |
78 |
|
89 | |||
|
90 | # my account pull requests | |||
|
91 | config.add_route( | |||
|
92 | name='my_account_pullrequests', | |||
|
93 | pattern=ADMIN_PREFIX + '/my_account/pull_requests') | |||
|
94 | config.add_route( | |||
|
95 | name='my_account_pullrequests_data', | |||
|
96 | pattern=ADMIN_PREFIX + '/my_account/pull_requests/data') | |||
|
97 | ||||
79 | # channelstream test |
|
98 | # channelstream test | |
80 | config.add_route( |
|
99 | config.add_route( | |
81 | name='my_account_notifications_test_channelstream', |
|
100 | name='my_account_notifications_test_channelstream', |
@@ -24,8 +24,10 b' import datetime' | |||||
24 | import formencode |
|
24 | import formencode | |
25 | from pyramid.httpexceptions import HTTPFound |
|
25 | from pyramid.httpexceptions import HTTPFound | |
26 | from pyramid.view import view_config |
|
26 | from pyramid.view import view_config | |
|
27 | from pyramid.renderers import render | |||
|
28 | from pyramid.response import Response | |||
27 |
|
29 | |||
28 | from rhodecode.apps._base import BaseAppView |
|
30 | from rhodecode.apps._base import BaseAppView, DataGridAppView | |
29 | from rhodecode import forms |
|
31 | from rhodecode import forms | |
30 | from rhodecode.lib import helpers as h |
|
32 | from rhodecode.lib import helpers as h | |
31 | from rhodecode.lib import audit_logger |
|
33 | from rhodecode.lib import audit_logger | |
@@ -33,11 +35,16 b' from rhodecode.lib.ext_json import json' | |||||
33 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
35 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired | |
34 | from rhodecode.lib.channelstream import channelstream_request, \ |
|
36 | from rhodecode.lib.channelstream import channelstream_request, \ | |
35 | ChannelstreamException |
|
37 | ChannelstreamException | |
36 |
from rhodecode.lib.utils |
|
38 | from rhodecode.lib.utils import PartialRenderer | |
|
39 | from rhodecode.lib.utils2 import safe_int, md5, str2bool | |||
37 | from rhodecode.model.auth_token import AuthTokenModel |
|
40 | from rhodecode.model.auth_token import AuthTokenModel | |
|
41 | from rhodecode.model.comment import CommentsModel | |||
38 | from rhodecode.model.db import ( |
|
42 | from rhodecode.model.db import ( | |
39 |
Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload |
|
43 | Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload, | |
|
44 | PullRequest) | |||
|
45 | from rhodecode.model.forms import UserForm | |||
40 | from rhodecode.model.meta import Session |
|
46 | from rhodecode.model.meta import Session | |
|
47 | from rhodecode.model.pull_request import PullRequestModel | |||
41 | from rhodecode.model.scm import RepoList |
|
48 | from rhodecode.model.scm import RepoList | |
42 | from rhodecode.model.user import UserModel |
|
49 | from rhodecode.model.user import UserModel | |
43 | from rhodecode.model.repo import RepoModel |
|
50 | from rhodecode.model.repo import RepoModel | |
@@ -46,7 +53,7 b' from rhodecode.model.validation_schema.s' | |||||
46 | log = logging.getLogger(__name__) |
|
53 | log = logging.getLogger(__name__) | |
47 |
|
54 | |||
48 |
|
55 | |||
49 | class MyAccountView(BaseAppView): |
|
56 | class MyAccountView(BaseAppView, DataGridAppView): | |
50 | ALLOW_SCOPED_TOKENS = False |
|
57 | ALLOW_SCOPED_TOKENS = False | |
51 | """ |
|
58 | """ | |
52 | This view has alternative version inside EE, if modified please take a look |
|
59 | This view has alternative version inside EE, if modified please take a look | |
@@ -396,4 +403,182 b' class MyAccountView(BaseAppView):' | |||||
396 | new_status = not user.user_data.get('notification_status', True) |
|
403 | new_status = not user.user_data.get('notification_status', True) | |
397 | user.update_userdata(notification_status=new_status) |
|
404 | user.update_userdata(notification_status=new_status) | |
398 | Session().commit() |
|
405 | Session().commit() | |
399 | return user.user_data['notification_status'] No newline at end of file |
|
406 | return user.user_data['notification_status'] | |
|
407 | ||||
|
408 | @LoginRequired() | |||
|
409 | @NotAnonymous() | |||
|
410 | @view_config( | |||
|
411 | route_name='my_account_edit', | |||
|
412 | request_method='GET', | |||
|
413 | renderer='rhodecode:templates/admin/my_account/my_account.mako') | |||
|
414 | def my_account_edit(self): | |||
|
415 | c = self.load_default_context() | |||
|
416 | c.active = 'profile_edit' | |||
|
417 | ||||
|
418 | c.perm_user = c.auth_user | |||
|
419 | c.extern_type = c.user.extern_type | |||
|
420 | c.extern_name = c.user.extern_name | |||
|
421 | ||||
|
422 | defaults = c.user.get_dict() | |||
|
423 | ||||
|
424 | data = render('rhodecode:templates/admin/my_account/my_account.mako', | |||
|
425 | self._get_template_context(c), self.request) | |||
|
426 | html = formencode.htmlfill.render( | |||
|
427 | data, | |||
|
428 | defaults=defaults, | |||
|
429 | encoding="UTF-8", | |||
|
430 | force_defaults=False | |||
|
431 | ) | |||
|
432 | return Response(html) | |||
|
433 | ||||
|
434 | @LoginRequired() | |||
|
435 | @NotAnonymous() | |||
|
436 | @CSRFRequired() | |||
|
437 | @view_config( | |||
|
438 | route_name='my_account_update', | |||
|
439 | request_method='POST', | |||
|
440 | renderer='rhodecode:templates/admin/my_account/my_account.mako') | |||
|
441 | def my_account_update(self): | |||
|
442 | _ = self.request.translate | |||
|
443 | c = self.load_default_context() | |||
|
444 | c.active = 'profile_edit' | |||
|
445 | ||||
|
446 | c.perm_user = c.auth_user | |||
|
447 | c.extern_type = c.user.extern_type | |||
|
448 | c.extern_name = c.user.extern_name | |||
|
449 | ||||
|
450 | _form = UserForm(edit=True, | |||
|
451 | old_data={'user_id': self._rhodecode_user.user_id, | |||
|
452 | 'email': self._rhodecode_user.email})() | |||
|
453 | form_result = {} | |||
|
454 | try: | |||
|
455 | post_data = dict(self.request.POST) | |||
|
456 | post_data['new_password'] = '' | |||
|
457 | post_data['password_confirmation'] = '' | |||
|
458 | form_result = _form.to_python(post_data) | |||
|
459 | # skip updating those attrs for my account | |||
|
460 | skip_attrs = ['admin', 'active', 'extern_type', 'extern_name', | |||
|
461 | 'new_password', 'password_confirmation'] | |||
|
462 | # TODO: plugin should define if username can be updated | |||
|
463 | if c.extern_type != "rhodecode": | |||
|
464 | # forbid updating username for external accounts | |||
|
465 | skip_attrs.append('username') | |||
|
466 | ||||
|
467 | UserModel().update_user( | |||
|
468 | self._rhodecode_user.user_id, skip_attrs=skip_attrs, | |||
|
469 | **form_result) | |||
|
470 | h.flash(_('Your account was updated successfully'), | |||
|
471 | category='success') | |||
|
472 | Session().commit() | |||
|
473 | ||||
|
474 | except formencode.Invalid as errors: | |||
|
475 | data = render( | |||
|
476 | 'rhodecode:templates/admin/my_account/my_account.mako', | |||
|
477 | self._get_template_context(c), self.request) | |||
|
478 | ||||
|
479 | html = formencode.htmlfill.render( | |||
|
480 | data, | |||
|
481 | defaults=errors.value, | |||
|
482 | errors=errors.error_dict or {}, | |||
|
483 | prefix_error=False, | |||
|
484 | encoding="UTF-8", | |||
|
485 | force_defaults=False) | |||
|
486 | return Response(html) | |||
|
487 | ||||
|
488 | except Exception: | |||
|
489 | log.exception("Exception updating user") | |||
|
490 | h.flash(_('Error occurred during update of user %s') | |||
|
491 | % form_result.get('username'), category='error') | |||
|
492 | raise HTTPFound(h.route_path('my_account_profile')) | |||
|
493 | ||||
|
494 | raise HTTPFound(h.route_path('my_account_profile')) | |||
|
495 | ||||
|
496 | def _get_pull_requests_list(self, statuses): | |||
|
497 | draw, start, limit = self._extract_chunk(self.request) | |||
|
498 | search_q, order_by, order_dir = self._extract_ordering(self.request) | |||
|
499 | _render = PartialRenderer('data_table/_dt_elements.mako') | |||
|
500 | ||||
|
501 | pull_requests = PullRequestModel().get_im_participating_in( | |||
|
502 | user_id=self._rhodecode_user.user_id, | |||
|
503 | statuses=statuses, | |||
|
504 | offset=start, length=limit, order_by=order_by, | |||
|
505 | order_dir=order_dir) | |||
|
506 | ||||
|
507 | pull_requests_total_count = PullRequestModel().count_im_participating_in( | |||
|
508 | user_id=self._rhodecode_user.user_id, statuses=statuses) | |||
|
509 | ||||
|
510 | data = [] | |||
|
511 | comments_model = CommentsModel() | |||
|
512 | for pr in pull_requests: | |||
|
513 | repo_id = pr.target_repo_id | |||
|
514 | comments = comments_model.get_all_comments( | |||
|
515 | repo_id, pull_request=pr) | |||
|
516 | owned = pr.user_id == self._rhodecode_user.user_id | |||
|
517 | ||||
|
518 | data.append({ | |||
|
519 | 'target_repo': _render('pullrequest_target_repo', | |||
|
520 | pr.target_repo.repo_name), | |||
|
521 | 'name': _render('pullrequest_name', | |||
|
522 | pr.pull_request_id, pr.target_repo.repo_name, | |||
|
523 | short=True), | |||
|
524 | 'name_raw': pr.pull_request_id, | |||
|
525 | 'status': _render('pullrequest_status', | |||
|
526 | pr.calculated_review_status()), | |||
|
527 | 'title': _render( | |||
|
528 | 'pullrequest_title', pr.title, pr.description), | |||
|
529 | 'description': h.escape(pr.description), | |||
|
530 | 'updated_on': _render('pullrequest_updated_on', | |||
|
531 | h.datetime_to_time(pr.updated_on)), | |||
|
532 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), | |||
|
533 | 'created_on': _render('pullrequest_updated_on', | |||
|
534 | h.datetime_to_time(pr.created_on)), | |||
|
535 | 'created_on_raw': h.datetime_to_time(pr.created_on), | |||
|
536 | 'author': _render('pullrequest_author', | |||
|
537 | pr.author.full_contact, ), | |||
|
538 | 'author_raw': pr.author.full_name, | |||
|
539 | 'comments': _render('pullrequest_comments', len(comments)), | |||
|
540 | 'comments_raw': len(comments), | |||
|
541 | 'closed': pr.is_closed(), | |||
|
542 | 'owned': owned | |||
|
543 | }) | |||
|
544 | ||||
|
545 | # json used to render the grid | |||
|
546 | data = ({ | |||
|
547 | 'draw': draw, | |||
|
548 | 'data': data, | |||
|
549 | 'recordsTotal': pull_requests_total_count, | |||
|
550 | 'recordsFiltered': pull_requests_total_count, | |||
|
551 | }) | |||
|
552 | return data | |||
|
553 | ||||
|
554 | @LoginRequired() | |||
|
555 | @NotAnonymous() | |||
|
556 | @view_config( | |||
|
557 | route_name='my_account_pullrequests', | |||
|
558 | request_method='GET', | |||
|
559 | renderer='rhodecode:templates/admin/my_account/my_account.mako') | |||
|
560 | def my_account_pullrequests(self): | |||
|
561 | c = self.load_default_context() | |||
|
562 | c.active = 'pullrequests' | |||
|
563 | req_get = self.request.GET | |||
|
564 | ||||
|
565 | c.closed = str2bool(req_get.get('pr_show_closed')) | |||
|
566 | ||||
|
567 | return self._get_template_context(c) | |||
|
568 | ||||
|
569 | @LoginRequired() | |||
|
570 | @NotAnonymous() | |||
|
571 | @view_config( | |||
|
572 | route_name='my_account_pullrequests_data', | |||
|
573 | request_method='GET', renderer='json_ext') | |||
|
574 | def my_account_pullrequests_data(self): | |||
|
575 | req_get = self.request.GET | |||
|
576 | closed = str2bool(req_get.get('closed')) | |||
|
577 | ||||
|
578 | statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN] | |||
|
579 | if closed: | |||
|
580 | statuses += [PullRequest.STATUS_CLOSED] | |||
|
581 | ||||
|
582 | data = self._get_pull_requests_list(statuses=statuses) | |||
|
583 | return data | |||
|
584 |
@@ -462,19 +462,11 b' def make_map(config):' | |||||
462 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
462 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
463 | controller='admin/my_account') as m: |
|
463 | controller='admin/my_account') as m: | |
464 |
|
464 | |||
465 | m.connect('my_account_edit', '/my_account/edit', |
|
|||
466 | action='my_account_edit', conditions={'method': ['GET']}) |
|
|||
467 | m.connect('my_account', '/my_account/update', |
|
|||
468 | action='my_account_update', conditions={'method': ['POST']}) |
|
|||
469 |
|
||||
470 | # NOTE(marcink): this needs to be kept for password force flag to be |
|
465 | # NOTE(marcink): this needs to be kept for password force flag to be | |
471 | # handler, remove after migration to pyramid |
|
466 | # handled in pylons controllers, remove after full migration to pyramid | |
472 | m.connect('my_account_password', '/my_account/password', |
|
467 | m.connect('my_account_password', '/my_account/password', | |
473 | action='my_account_password', conditions={'method': ['GET']}) |
|
468 | action='my_account_password', conditions={'method': ['GET']}) | |
474 |
|
469 | |||
475 | m.connect('my_account_pullrequests', '/my_account/pull_requests', |
|
|||
476 | action='my_account_pullrequests', conditions={'method': ['GET']}) |
|
|||
477 |
|
||||
478 | # NOTIFICATION REST ROUTES |
|
470 | # NOTIFICATION REST ROUTES | |
479 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
471 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
480 | controller='admin/notifications') as m: |
|
472 | controller='admin/notifications') as m: |
@@ -136,6 +136,8 b' function registerRCRoutes() {' | |||||
136 | pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']); |
|
136 | pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']); | |
137 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); |
|
137 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); | |
138 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); |
|
138 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); | |
|
139 | pyroutes.register('my_account_edit', '/_admin/my_account/edit', []); | |||
|
140 | pyroutes.register('my_account_update', '/_admin/my_account/update', []); | |||
139 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); |
|
141 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); | |
140 | pyroutes.register('my_account_password_update', '/_admin/my_account/password', []); |
|
142 | pyroutes.register('my_account_password_update', '/_admin/my_account/password', []); | |
141 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); |
|
143 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); | |
@@ -149,6 +151,8 b' function registerRCRoutes() {' | |||||
149 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); |
|
151 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); | |
150 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
152 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); | |
151 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
|
153 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); | |
|
154 | pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []); | |||
|
155 | pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []); | |||
152 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); |
|
156 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); | |
153 | pyroutes.register('gists_show', '/_admin/gists', []); |
|
157 | pyroutes.register('gists_show', '/_admin/gists', []); | |
154 | pyroutes.register('gists_new', '/_admin/gists/new', []); |
|
158 | pyroutes.register('gists_new', '/_admin/gists/new', []); |
@@ -37,7 +37,7 b'' | |||||
37 | <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li> |
|
37 | <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li> | |
38 | <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li> |
|
38 | <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li> | |
39 | <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li> |
|
39 | <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li> | |
40 |
<li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h. |
|
40 | <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li> | |
41 | <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li> |
|
41 | <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li> | |
42 | <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li> |
|
42 | <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li> | |
43 | </ul> |
|
43 | </ul> |
@@ -2,7 +2,7 b'' | |||||
2 | <div class="panel panel-default user-profile"> |
|
2 | <div class="panel panel-default user-profile"> | |
3 | <div class="panel-heading"> |
|
3 | <div class="panel-heading"> | |
4 | <h3 class="panel-title">${_('My Profile')}</h3> |
|
4 | <h3 class="panel-title">${_('My Profile')}</h3> | |
5 |
<a href="${ |
|
5 | <a href="${h.route_path('my_account_edit')}" class="panel-edit">${_('Edit')}</a> | |
6 | </div> |
|
6 | </div> | |
7 |
|
7 | |||
8 | <div class="panel-body"> |
|
8 | <div class="panel-body"> |
@@ -6,7 +6,7 b'' | |||||
6 | </div> |
|
6 | </div> | |
7 |
|
7 | |||
8 | <div class="panel-body"> |
|
8 | <div class="panel-body"> | |
9 |
${h.secure_form( |
|
9 | ${h.secure_form(h.route_path('my_account_update'), class_='form', method='POST')} | |
10 | <% readonly = None %> |
|
10 | <% readonly = None %> | |
11 | <% disabled = "" %> |
|
11 | <% disabled = "" %> | |
12 |
|
12 |
@@ -2,7 +2,7 b'' | |||||
2 |
|
2 | |||
3 | <div class="panel panel-default"> |
|
3 | <div class="panel panel-default"> | |
4 | <div class="panel-body"> |
|
4 | <div class="panel-body"> | |
5 |
%if c. |
|
5 | %if c.closed: | |
6 | ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))} |
|
6 | ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))} | |
7 | %else: |
|
7 | %else: | |
8 | ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))} |
|
8 | ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))} | |
@@ -12,25 +12,41 b'' | |||||
12 |
|
12 | |||
13 | <div class="panel panel-default"> |
|
13 | <div class="panel panel-default"> | |
14 | <div class="panel-heading"> |
|
14 | <div class="panel-heading"> | |
15 |
<h3 class="panel-title">${_('Pull Requests You Participate In')} |
|
15 | <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3> | |
16 | </div> |
|
16 | </div> | |
17 | <div class="panel-body"> |
|
17 | <div class="panel-body panel-body-min-height"> | |
18 |
<table id="pull_request_list_table |
|
18 | <table id="pull_request_list_table" class="display"></table> | |
19 | </div> |
|
19 | </div> | |
20 | </div> |
|
20 | </div> | |
21 |
|
21 | |||
22 | <script> |
|
22 | <script type="text/javascript"> | |
|
23 | $(document).ready(function() { | |||
|
24 | ||||
23 | $('#show_closed').on('click', function(e){ |
|
25 | $('#show_closed').on('click', function(e){ | |
24 | if($(this).is(":checked")){ |
|
26 | if($(this).is(":checked")){ | |
25 |
window.location = "${h. |
|
27 | window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}"; | |
26 | } |
|
28 | } | |
27 | else{ |
|
29 | else{ | |
28 |
window.location = "${h. |
|
30 | window.location = "${h.route_path('my_account_pullrequests')}"; | |
29 | } |
|
31 | } | |
30 | }); |
|
32 | }); | |
31 | $(document).ready(function() { |
|
33 | ||
|
34 | var $pullRequestListTable = $('#pull_request_list_table'); | |||
32 |
|
35 | |||
33 | var columnsDefs = [ |
|
36 | // participating object list | |
|
37 | $pullRequestListTable.DataTable({ | |||
|
38 | processing: true, | |||
|
39 | serverSide: true, | |||
|
40 | ajax: { | |||
|
41 | "url": "${h.route_path('my_account_pullrequests_data')}", | |||
|
42 | "data": function (d) { | |||
|
43 | d.closed = "${c.closed}"; | |||
|
44 | } | |||
|
45 | }, | |||
|
46 | dom: 'rtp', | |||
|
47 | pageLength: ${c.visual.dashboard_items}, | |||
|
48 | order: [[ 2, "desc" ]], | |||
|
49 | columns: [ | |||
34 | { data: {"_": "status", |
|
50 | { data: {"_": "status", | |
35 | "sort": "status"}, title: "", className: "td-status", orderable: false}, |
|
51 | "sort": "status"}, title: "", className: "td-status", orderable: false}, | |
36 | { data: {"_": "target_repo", |
|
52 | { data: {"_": "target_repo", | |
@@ -45,21 +61,10 b'' | |||||
45 | "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false}, |
|
61 | "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false}, | |
46 | { data: {"_": "updated_on", |
|
62 | { data: {"_": "updated_on", | |
47 | "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" } |
|
63 | "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" } | |
48 |
] |
|
64 | ], | |
49 |
|
||||
50 | // participating object list |
|
|||
51 | $('#pull_request_list_table_participate').DataTable({ |
|
|||
52 | data: ${c.data_participate|n}, |
|
|||
53 | processing: true, |
|
|||
54 | serverSide: true, |
|
|||
55 | deferLoading: ${c.records_total_participate}, |
|
|||
56 | ajax: "", |
|
|||
57 | dom: 'tp', |
|
|||
58 | pageLength: ${c.visual.dashboard_items}, |
|
|||
59 | order: [[ 2, "desc" ]], |
|
|||
60 | columns: columnsDefs, |
|
|||
61 | language: { |
|
65 | language: { | |
62 | paginate: DEFAULT_GRID_PAGINATION, |
|
66 | paginate: DEFAULT_GRID_PAGINATION, | |
|
67 | sProcessing: _gettext('loading...'), | |||
63 | emptyTable: _gettext("There are currently no open pull requests requiring your participation.") |
|
68 | emptyTable: _gettext("There are currently no open pull requests requiring your participation.") | |
64 | }, |
|
69 | }, | |
65 | "drawCallback": function( settings, json ) { |
|
70 | "drawCallback": function( settings, json ) { | |
@@ -74,5 +79,12 b'' | |||||
74 | } |
|
79 | } | |
75 | } |
|
80 | } | |
76 | }); |
|
81 | }); | |
|
82 | $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){ | |||
|
83 | $pullRequestListTable.css('opacity', 1); | |||
|
84 | }); | |||
|
85 | ||||
|
86 | $pullRequestListTable.on('preXhr.dt', function(e, settings, data){ | |||
|
87 | $pullRequestListTable.css('opacity', 0.3); | |||
|
88 | }); | |||
77 | }); |
|
89 | }); | |
78 | </script> |
|
90 | </script> |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now