##// END OF EJS Templates
user-account: expose membership of user groups to users....
marcink -
r2496:3250285e default
parent child Browse files
Show More
@@ -0,0 +1,75 b''
1 ## -*- coding: utf-8 -*-
2
3 <div class="panel panel-default">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('User Group Membership')}</h3>
6 </div>
7
8 <div class="panel-body">
9 <div class="groups_management">
10 <div id="repos_list_wrap">
11 <table id="user_group_list_table" class="display"></table>
12 </div>
13 </div>
14 </div>
15 </div>
16
17
18 <script>
19 var api;
20 $(document).ready(function() {
21
22 var get_datatable_count = function(){
23 $('#user_group_count').text(api.page.info().recordsDisplay);
24 };
25
26 $('#user_group_list_table').on('click', 'a.editor_remove', function (e) {
27 e.preventDefault();
28 var row = api.row($(this).closest('tr'));
29 row.remove().draw();
30 } );
31
32 $('#user_group_list_table').DataTable({
33 data: ${c.user_groups|n},
34 dom: 'rtp',
35 pageLength: ${c.visual.admin_grid_items},
36 order: [[ 0, "asc" ]],
37 columns: [
38 { data: {"_": "group_name",
39 "sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," ,
40 render: function (data,type,full,meta)
41 {return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}},
42
43 { data: {"_": "group_description",
44 "sort": "group_description"}, title: "${_('Description')}", className: "td-description" },
45 { data: {"_": "users_group_id"}, className: "td-user",
46 render: function (data,type,full,meta)
47 {return '<input type="hidden" name="users_group_id" value="'+data+'">'}},
48 { data: {"_": "active",
49 "sort": "active"}, title: "${_('Active')}", className: "td-active"},
50 { data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user",
51 render: function (data,type,full,meta)
52 {return '<div class="rc-user tooltip">'+
53 '<img class="gravatar" src="'+ data.owner_icon +'" height="16" width="16">'+
54 data.owner +'</div>'
55 }
56 }
57 ],
58 language: {
59 paginate: DEFAULT_GRID_PAGINATION,
60 emptyTable: _gettext("No user groups available yet.")
61 },
62 "initComplete": function( settings, json ) {
63 var data_grid = $('#user_group_list_table').dataTable();
64 api = data_grid.api();
65 get_datatable_count();
66 }
67 });
68
69 // update the counter when doing search
70 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
71 get_datatable_count();
72 });
73
74 });
75 </script> No newline at end of file
@@ -1,140 +1,145 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def includeme(config):
26 26
27 27 config.add_route(
28 28 name='my_account_profile',
29 29 pattern=ADMIN_PREFIX + '/my_account/profile')
30 30
31 31 # my account edit details
32 32 config.add_route(
33 33 name='my_account_edit',
34 34 pattern=ADMIN_PREFIX + '/my_account/edit')
35 35 config.add_route(
36 36 name='my_account_update',
37 37 pattern=ADMIN_PREFIX + '/my_account/update')
38 38
39 39 # my account password
40 40 config.add_route(
41 41 name='my_account_password',
42 42 pattern=ADMIN_PREFIX + '/my_account/password')
43 43
44 44 config.add_route(
45 45 name='my_account_password_update',
46 46 pattern=ADMIN_PREFIX + '/my_account/password/update')
47 47
48 48 # my account tokens
49 49 config.add_route(
50 50 name='my_account_auth_tokens',
51 51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 52 config.add_route(
53 53 name='my_account_auth_tokens_add',
54 54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 55 config.add_route(
56 56 name='my_account_auth_tokens_delete',
57 57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
58 58
59 59 # my account ssh keys
60 60 config.add_route(
61 61 name='my_account_ssh_keys',
62 62 pattern=ADMIN_PREFIX + '/my_account/ssh_keys')
63 63 config.add_route(
64 64 name='my_account_ssh_keys_generate',
65 65 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/generate')
66 66 config.add_route(
67 67 name='my_account_ssh_keys_add',
68 68 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/new')
69 69 config.add_route(
70 70 name='my_account_ssh_keys_delete',
71 71 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/delete')
72 72
73 # my account user group membership
74 config.add_route(
75 name='my_account_user_group_membership',
76 pattern=ADMIN_PREFIX + '/my_account/user_group_membership')
77
73 78 # my account emails
74 79 config.add_route(
75 80 name='my_account_emails',
76 81 pattern=ADMIN_PREFIX + '/my_account/emails')
77 82 config.add_route(
78 83 name='my_account_emails_add',
79 84 pattern=ADMIN_PREFIX + '/my_account/emails/new')
80 85 config.add_route(
81 86 name='my_account_emails_delete',
82 87 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
83 88
84 89 config.add_route(
85 90 name='my_account_repos',
86 91 pattern=ADMIN_PREFIX + '/my_account/repos')
87 92
88 93 config.add_route(
89 94 name='my_account_watched',
90 95 pattern=ADMIN_PREFIX + '/my_account/watched')
91 96
92 97 config.add_route(
93 98 name='my_account_perms',
94 99 pattern=ADMIN_PREFIX + '/my_account/perms')
95 100
96 101 config.add_route(
97 102 name='my_account_notifications',
98 103 pattern=ADMIN_PREFIX + '/my_account/notifications')
99 104
100 105 config.add_route(
101 106 name='my_account_notifications_toggle_visibility',
102 107 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
103 108
104 109 # my account pull requests
105 110 config.add_route(
106 111 name='my_account_pullrequests',
107 112 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
108 113 config.add_route(
109 114 name='my_account_pullrequests_data',
110 115 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
111 116
112 117 # notifications
113 118 config.add_route(
114 119 name='notifications_show_all',
115 120 pattern=ADMIN_PREFIX + '/notifications')
116 121
117 122 # notifications
118 123 config.add_route(
119 124 name='notifications_mark_all_read',
120 125 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
121 126
122 127 config.add_route(
123 128 name='notifications_show',
124 129 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
125 130
126 131 config.add_route(
127 132 name='notifications_update',
128 133 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
129 134
130 135 config.add_route(
131 136 name='notifications_delete',
132 137 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
133 138
134 139 # channelstream test
135 140 config.add_route(
136 141 name='my_account_notifications_test_channelstream',
137 142 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
138 143
139 144 # Scan module for configuration decorators.
140 145 config.scan('.views', ignore='.tests')
@@ -1,585 +1,599 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode import forms
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
37 37 from rhodecode.lib.channelstream import (
38 38 channelstream_request, ChannelstreamException)
39 39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 40 from rhodecode.model.auth_token import AuthTokenModel
41 41 from rhodecode.model.comment import CommentsModel
42 42 from rhodecode.model.db import (
43 43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 44 PullRequest)
45 45 from rhodecode.model.forms import UserForm, UserExtraEmailForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.pull_request import PullRequestModel
48 48 from rhodecode.model.scm import RepoList
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.user_group import UserGroupModel
51 52 from rhodecode.model.validation_schema.schemas import user_schema
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56
56 57 class MyAccountView(BaseAppView, DataGridAppView):
57 58 ALLOW_SCOPED_TOKENS = False
58 59 """
59 60 This view has alternative version inside EE, if modified please take a look
60 61 in there as well.
61 62 """
62 63
63 64 def load_default_context(self):
64 65 c = self._get_local_tmpl_context()
65 66 c.user = c.auth_user.get_instance()
66 67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 68
68 69 return c
69 70
70 71 @LoginRequired()
71 72 @NotAnonymous()
72 73 @view_config(
73 74 route_name='my_account_profile', request_method='GET',
74 75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 76 def my_account_profile(self):
76 77 c = self.load_default_context()
77 78 c.active = 'profile'
78 79 return self._get_template_context(c)
79 80
80 81 @LoginRequired()
81 82 @NotAnonymous()
82 83 @view_config(
83 84 route_name='my_account_password', request_method='GET',
84 85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
85 86 def my_account_password(self):
86 87 c = self.load_default_context()
87 88 c.active = 'password'
88 89 c.extern_type = c.user.extern_type
89 90
90 91 schema = user_schema.ChangePasswordSchema().bind(
91 92 username=c.user.username)
92 93
93 94 form = forms.Form(
94 95 schema,
95 96 action=h.route_path('my_account_password_update'),
96 97 buttons=(forms.buttons.save, forms.buttons.reset))
97 98
98 99 c.form = form
99 100 return self._get_template_context(c)
100 101
101 102 @LoginRequired()
102 103 @NotAnonymous()
103 104 @CSRFRequired()
104 105 @view_config(
105 106 route_name='my_account_password_update', request_method='POST',
106 107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
107 108 def my_account_password_update(self):
108 109 _ = self.request.translate
109 110 c = self.load_default_context()
110 111 c.active = 'password'
111 112 c.extern_type = c.user.extern_type
112 113
113 114 schema = user_schema.ChangePasswordSchema().bind(
114 115 username=c.user.username)
115 116
116 117 form = forms.Form(
117 118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
118 119
119 120 if c.extern_type != 'rhodecode':
120 121 raise HTTPFound(self.request.route_path('my_account_password'))
121 122
122 123 controls = self.request.POST.items()
123 124 try:
124 125 valid_data = form.validate(controls)
125 126 UserModel().update_user(c.user.user_id, **valid_data)
126 127 c.user.update_userdata(force_password_change=False)
127 128 Session().commit()
128 129 except forms.ValidationFailure as e:
129 130 c.form = e
130 131 return self._get_template_context(c)
131 132
132 133 except Exception:
133 134 log.exception("Exception updating password")
134 135 h.flash(_('Error occurred during update of user password'),
135 136 category='error')
136 137 else:
137 138 instance = c.auth_user.get_instance()
138 139 self.session.setdefault('rhodecode_user', {}).update(
139 140 {'password': md5(instance.password)})
140 141 self.session.save()
141 142 h.flash(_("Successfully updated password"), category='success')
142 143
143 144 raise HTTPFound(self.request.route_path('my_account_password'))
144 145
145 146 @LoginRequired()
146 147 @NotAnonymous()
147 148 @view_config(
148 149 route_name='my_account_auth_tokens', request_method='GET',
149 150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
150 151 def my_account_auth_tokens(self):
151 152 _ = self.request.translate
152 153
153 154 c = self.load_default_context()
154 155 c.active = 'auth_tokens'
155 156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
156 157 c.role_values = [
157 158 (x, AuthTokenModel.cls._get_role_name(x))
158 159 for x in AuthTokenModel.cls.ROLES]
159 160 c.role_options = [(c.role_values, _("Role"))]
160 161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
161 162 c.user.user_id, show_expired=True)
162 163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
163 164 return self._get_template_context(c)
164 165
165 166 def maybe_attach_token_scope(self, token):
166 167 # implemented in EE edition
167 168 pass
168 169
169 170 @LoginRequired()
170 171 @NotAnonymous()
171 172 @CSRFRequired()
172 173 @view_config(
173 174 route_name='my_account_auth_tokens_add', request_method='POST',)
174 175 def my_account_auth_tokens_add(self):
175 176 _ = self.request.translate
176 177 c = self.load_default_context()
177 178
178 179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
179 180 description = self.request.POST.get('description')
180 181 role = self.request.POST.get('role')
181 182
182 183 token = AuthTokenModel().create(
183 184 c.user.user_id, description, lifetime, role)
184 185 token_data = token.get_api_data()
185 186
186 187 self.maybe_attach_token_scope(token)
187 188 audit_logger.store_web(
188 189 'user.edit.token.add', action_data={
189 190 'data': {'token': token_data, 'user': 'self'}},
190 191 user=self._rhodecode_user, )
191 192 Session().commit()
192 193
193 194 h.flash(_("Auth token successfully created"), category='success')
194 195 return HTTPFound(h.route_path('my_account_auth_tokens'))
195 196
196 197 @LoginRequired()
197 198 @NotAnonymous()
198 199 @CSRFRequired()
199 200 @view_config(
200 201 route_name='my_account_auth_tokens_delete', request_method='POST')
201 202 def my_account_auth_tokens_delete(self):
202 203 _ = self.request.translate
203 204 c = self.load_default_context()
204 205
205 206 del_auth_token = self.request.POST.get('del_auth_token')
206 207
207 208 if del_auth_token:
208 209 token = UserApiKeys.get_or_404(del_auth_token)
209 210 token_data = token.get_api_data()
210 211
211 212 AuthTokenModel().delete(del_auth_token, c.user.user_id)
212 213 audit_logger.store_web(
213 214 'user.edit.token.delete', action_data={
214 215 'data': {'token': token_data, 'user': 'self'}},
215 216 user=self._rhodecode_user,)
216 217 Session().commit()
217 218 h.flash(_("Auth token successfully deleted"), category='success')
218 219
219 220 return HTTPFound(h.route_path('my_account_auth_tokens'))
220 221
221 222 @LoginRequired()
222 223 @NotAnonymous()
223 224 @view_config(
224 225 route_name='my_account_emails', request_method='GET',
225 226 renderer='rhodecode:templates/admin/my_account/my_account.mako')
226 227 def my_account_emails(self):
227 228 _ = self.request.translate
228 229
229 230 c = self.load_default_context()
230 231 c.active = 'emails'
231 232
232 233 c.user_email_map = UserEmailMap.query()\
233 234 .filter(UserEmailMap.user == c.user).all()
234 235 return self._get_template_context(c)
235 236
236 237 @LoginRequired()
237 238 @NotAnonymous()
238 239 @CSRFRequired()
239 240 @view_config(
240 241 route_name='my_account_emails_add', request_method='POST')
241 242 def my_account_emails_add(self):
242 243 _ = self.request.translate
243 244 c = self.load_default_context()
244 245
245 246 email = self.request.POST.get('new_email')
246 247
247 248 try:
248 249 form = UserExtraEmailForm(self.request.translate)()
249 250 data = form.to_python({'email': email})
250 251 email = data['email']
251 252
252 253 UserModel().add_extra_email(c.user.user_id, email)
253 254 audit_logger.store_web(
254 255 'user.edit.email.add', action_data={
255 256 'data': {'email': email, 'user': 'self'}},
256 257 user=self._rhodecode_user,)
257 258
258 259 Session().commit()
259 260 h.flash(_("Added new email address `%s` for user account") % email,
260 261 category='success')
261 262 except formencode.Invalid as error:
262 263 h.flash(h.escape(error.error_dict['email']), category='error')
263 264 except Exception:
264 265 log.exception("Exception in my_account_emails")
265 266 h.flash(_('An error occurred during email saving'),
266 267 category='error')
267 268 return HTTPFound(h.route_path('my_account_emails'))
268 269
269 270 @LoginRequired()
270 271 @NotAnonymous()
271 272 @CSRFRequired()
272 273 @view_config(
273 274 route_name='my_account_emails_delete', request_method='POST')
274 275 def my_account_emails_delete(self):
275 276 _ = self.request.translate
276 277 c = self.load_default_context()
277 278
278 279 del_email_id = self.request.POST.get('del_email_id')
279 280 if del_email_id:
280 281 email = UserEmailMap.get_or_404(del_email_id).email
281 282 UserModel().delete_extra_email(c.user.user_id, del_email_id)
282 283 audit_logger.store_web(
283 284 'user.edit.email.delete', action_data={
284 285 'data': {'email': email, 'user': 'self'}},
285 286 user=self._rhodecode_user,)
286 287 Session().commit()
287 288 h.flash(_("Email successfully deleted"),
288 289 category='success')
289 290 return HTTPFound(h.route_path('my_account_emails'))
290 291
291 292 @LoginRequired()
292 293 @NotAnonymous()
293 294 @CSRFRequired()
294 295 @view_config(
295 296 route_name='my_account_notifications_test_channelstream',
296 297 request_method='POST', renderer='json_ext')
297 298 def my_account_notifications_test_channelstream(self):
298 299 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
299 300 self._rhodecode_user.username, datetime.datetime.now())
300 301 payload = {
301 302 # 'channel': 'broadcast',
302 303 'type': 'message',
303 304 'timestamp': datetime.datetime.utcnow(),
304 305 'user': 'system',
305 306 'pm_users': [self._rhodecode_user.username],
306 307 'message': {
307 308 'message': message,
308 309 'level': 'info',
309 310 'topic': '/notifications'
310 311 }
311 312 }
312 313
313 314 registry = self.request.registry
314 315 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
315 316 channelstream_config = rhodecode_plugins.get('channelstream', {})
316 317
317 318 try:
318 319 channelstream_request(channelstream_config, [payload], '/message')
319 320 except ChannelstreamException as e:
320 321 log.exception('Failed to send channelstream data')
321 322 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
322 323 return {"response": 'Channelstream data sent. '
323 324 'You should see a new live message now.'}
324 325
325 326 def _load_my_repos_data(self, watched=False):
326 327 if watched:
327 328 admin = False
328 329 follows_repos = Session().query(UserFollowing)\
329 330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
330 331 .options(joinedload(UserFollowing.follows_repository))\
331 332 .all()
332 333 repo_list = [x.follows_repository for x in follows_repos]
333 334 else:
334 335 admin = True
335 336 repo_list = Repository.get_all_repos(
336 337 user_id=self._rhodecode_user.user_id)
337 338 repo_list = RepoList(repo_list, perm_set=[
338 339 'repository.read', 'repository.write', 'repository.admin'])
339 340
340 341 repos_data = RepoModel().get_repos_as_dict(
341 342 repo_list=repo_list, admin=admin)
342 343 # json used to render the grid
343 344 return json.dumps(repos_data)
344 345
345 346 @LoginRequired()
346 347 @NotAnonymous()
347 348 @view_config(
348 349 route_name='my_account_repos', request_method='GET',
349 350 renderer='rhodecode:templates/admin/my_account/my_account.mako')
350 351 def my_account_repos(self):
351 352 c = self.load_default_context()
352 353 c.active = 'repos'
353 354
354 355 # json used to render the grid
355 356 c.data = self._load_my_repos_data()
356 357 return self._get_template_context(c)
357 358
358 359 @LoginRequired()
359 360 @NotAnonymous()
360 361 @view_config(
361 362 route_name='my_account_watched', request_method='GET',
362 363 renderer='rhodecode:templates/admin/my_account/my_account.mako')
363 364 def my_account_watched(self):
364 365 c = self.load_default_context()
365 366 c.active = 'watched'
366 367
367 368 # json used to render the grid
368 369 c.data = self._load_my_repos_data(watched=True)
369 370 return self._get_template_context(c)
370 371
371 372 @LoginRequired()
372 373 @NotAnonymous()
373 374 @view_config(
374 375 route_name='my_account_perms', request_method='GET',
375 376 renderer='rhodecode:templates/admin/my_account/my_account.mako')
376 377 def my_account_perms(self):
377 378 c = self.load_default_context()
378 379 c.active = 'perms'
379 380
380 381 c.perm_user = c.auth_user
381 382 return self._get_template_context(c)
382 383
383 384 @LoginRequired()
384 385 @NotAnonymous()
385 386 @view_config(
386 387 route_name='my_account_notifications', request_method='GET',
387 388 renderer='rhodecode:templates/admin/my_account/my_account.mako')
388 389 def my_notifications(self):
389 390 c = self.load_default_context()
390 391 c.active = 'notifications'
391 392
392 393 return self._get_template_context(c)
393 394
394 395 @LoginRequired()
395 396 @NotAnonymous()
396 397 @CSRFRequired()
397 398 @view_config(
398 399 route_name='my_account_notifications_toggle_visibility',
399 400 request_method='POST', renderer='json_ext')
400 401 def my_notifications_toggle_visibility(self):
401 402 user = self._rhodecode_db_user
402 403 new_status = not user.user_data.get('notification_status', True)
403 404 user.update_userdata(notification_status=new_status)
404 405 Session().commit()
405 406 return user.user_data['notification_status']
406 407
407 408 @LoginRequired()
408 409 @NotAnonymous()
409 410 @view_config(
410 411 route_name='my_account_edit',
411 412 request_method='GET',
412 413 renderer='rhodecode:templates/admin/my_account/my_account.mako')
413 414 def my_account_edit(self):
414 415 c = self.load_default_context()
415 416 c.active = 'profile_edit'
416 417
417 418 c.perm_user = c.auth_user
418 419 c.extern_type = c.user.extern_type
419 420 c.extern_name = c.user.extern_name
420 421
421 422 defaults = c.user.get_dict()
422 423
423 424 data = render('rhodecode:templates/admin/my_account/my_account.mako',
424 425 self._get_template_context(c), self.request)
425 426 html = formencode.htmlfill.render(
426 427 data,
427 428 defaults=defaults,
428 429 encoding="UTF-8",
429 430 force_defaults=False
430 431 )
431 432 return Response(html)
432 433
433 434 @LoginRequired()
434 435 @NotAnonymous()
435 436 @CSRFRequired()
436 437 @view_config(
437 438 route_name='my_account_update',
438 439 request_method='POST',
439 440 renderer='rhodecode:templates/admin/my_account/my_account.mako')
440 441 def my_account_update(self):
441 442 _ = self.request.translate
442 443 c = self.load_default_context()
443 444 c.active = 'profile_edit'
444 445
445 446 c.perm_user = c.auth_user
446 447 c.extern_type = c.user.extern_type
447 448 c.extern_name = c.user.extern_name
448 449
449 450 _form = UserForm(self.request.translate, edit=True,
450 451 old_data={'user_id': self._rhodecode_user.user_id,
451 452 'email': self._rhodecode_user.email})()
452 453 form_result = {}
453 454 try:
454 455 post_data = dict(self.request.POST)
455 456 post_data['new_password'] = ''
456 457 post_data['password_confirmation'] = ''
457 458 form_result = _form.to_python(post_data)
458 459 # skip updating those attrs for my account
459 460 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
460 461 'new_password', 'password_confirmation']
461 462 # TODO: plugin should define if username can be updated
462 463 if c.extern_type != "rhodecode":
463 464 # forbid updating username for external accounts
464 465 skip_attrs.append('username')
465 466
466 467 UserModel().update_user(
467 468 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
468 469 **form_result)
469 470 h.flash(_('Your account was updated successfully'),
470 471 category='success')
471 472 Session().commit()
472 473
473 474 except formencode.Invalid as errors:
474 475 data = render(
475 476 'rhodecode:templates/admin/my_account/my_account.mako',
476 477 self._get_template_context(c), self.request)
477 478
478 479 html = formencode.htmlfill.render(
479 480 data,
480 481 defaults=errors.value,
481 482 errors=errors.error_dict or {},
482 483 prefix_error=False,
483 484 encoding="UTF-8",
484 485 force_defaults=False)
485 486 return Response(html)
486 487
487 488 except Exception:
488 489 log.exception("Exception updating user")
489 490 h.flash(_('Error occurred during update of user %s')
490 491 % form_result.get('username'), category='error')
491 492 raise HTTPFound(h.route_path('my_account_profile'))
492 493
493 494 raise HTTPFound(h.route_path('my_account_profile'))
494 495
495 496 def _get_pull_requests_list(self, statuses):
496 497 draw, start, limit = self._extract_chunk(self.request)
497 498 search_q, order_by, order_dir = self._extract_ordering(self.request)
498 499 _render = self.request.get_partial_renderer(
499 500 'rhodecode:templates/data_table/_dt_elements.mako')
500 501
501 502 pull_requests = PullRequestModel().get_im_participating_in(
502 503 user_id=self._rhodecode_user.user_id,
503 504 statuses=statuses,
504 505 offset=start, length=limit, order_by=order_by,
505 506 order_dir=order_dir)
506 507
507 508 pull_requests_total_count = PullRequestModel().count_im_participating_in(
508 509 user_id=self._rhodecode_user.user_id, statuses=statuses)
509 510
510 511 data = []
511 512 comments_model = CommentsModel()
512 513 for pr in pull_requests:
513 514 repo_id = pr.target_repo_id
514 515 comments = comments_model.get_all_comments(
515 516 repo_id, pull_request=pr)
516 517 owned = pr.user_id == self._rhodecode_user.user_id
517 518
518 519 data.append({
519 520 'target_repo': _render('pullrequest_target_repo',
520 521 pr.target_repo.repo_name),
521 522 'name': _render('pullrequest_name',
522 523 pr.pull_request_id, pr.target_repo.repo_name,
523 524 short=True),
524 525 'name_raw': pr.pull_request_id,
525 526 'status': _render('pullrequest_status',
526 527 pr.calculated_review_status()),
527 528 'title': _render(
528 529 'pullrequest_title', pr.title, pr.description),
529 530 'description': h.escape(pr.description),
530 531 'updated_on': _render('pullrequest_updated_on',
531 532 h.datetime_to_time(pr.updated_on)),
532 533 'updated_on_raw': h.datetime_to_time(pr.updated_on),
533 534 'created_on': _render('pullrequest_updated_on',
534 535 h.datetime_to_time(pr.created_on)),
535 536 'created_on_raw': h.datetime_to_time(pr.created_on),
536 537 'author': _render('pullrequest_author',
537 538 pr.author.full_contact, ),
538 539 'author_raw': pr.author.full_name,
539 540 'comments': _render('pullrequest_comments', len(comments)),
540 541 'comments_raw': len(comments),
541 542 'closed': pr.is_closed(),
542 543 'owned': owned
543 544 })
544 545
545 546 # json used to render the grid
546 547 data = ({
547 548 'draw': draw,
548 549 'data': data,
549 550 'recordsTotal': pull_requests_total_count,
550 551 'recordsFiltered': pull_requests_total_count,
551 552 })
552 553 return data
553 554
554 555 @LoginRequired()
555 556 @NotAnonymous()
556 557 @view_config(
557 558 route_name='my_account_pullrequests',
558 559 request_method='GET',
559 560 renderer='rhodecode:templates/admin/my_account/my_account.mako')
560 561 def my_account_pullrequests(self):
561 562 c = self.load_default_context()
562 563 c.active = 'pullrequests'
563 564 req_get = self.request.GET
564 565
565 566 c.closed = str2bool(req_get.get('pr_show_closed'))
566 567
567 568 return self._get_template_context(c)
568 569
569 570 @LoginRequired()
570 571 @NotAnonymous()
571 572 @view_config(
572 573 route_name='my_account_pullrequests_data',
573 574 request_method='GET', renderer='json_ext')
574 575 def my_account_pullrequests_data(self):
575 576 self.load_default_context()
576 577 req_get = self.request.GET
577 578 closed = str2bool(req_get.get('closed'))
578 579
579 580 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
580 581 if closed:
581 582 statuses += [PullRequest.STATUS_CLOSED]
582 583
583 584 data = self._get_pull_requests_list(statuses=statuses)
584 585 return data
585 586
587 @LoginRequired()
588 @NotAnonymous()
589 @view_config(
590 route_name='my_account_user_group_membership',
591 request_method='GET',
592 renderer='rhodecode:templates/admin/my_account/my_account.mako')
593 def my_account_user_group_membership(self):
594 c = self.load_default_context()
595 c.active = 'user_group_membership'
596 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
597 for group in self._rhodecode_db_user.group_member]
598 c.user_groups = json.dumps(groups)
599 return self._get_template_context(c)
@@ -1,53 +1,55 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('My Account')}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='my_account')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24
25 25 <div class="sidebar-col-wrapper scw-small">
26 26 ##main
27 27 <div class="sidebar">
28 28 <ul class="nav nav-pills nav-stacked">
29 29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
30 30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
31 31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 32 <li class="${'active' if c.active in ['ssh_keys', 'ssh_keys_generate'] else ''}"><a href="${h.route_path('my_account_ssh_keys')}">${_('SSH Keys')}</a></li>
33 <li class="${'active' if c.active=='user_group_membership' else ''}"><a href="${h.route_path('my_account_user_group_membership')}">${_('User Group Membership')}</a></li>
34
33 35 ## TODO: Find a better integration of oauth views into navigation.
34 36 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
35 37 % if my_account_oauth_url:
36 38 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
37 39 % endif
38 40 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
39 41 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
40 42 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
41 43 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
42 44 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
43 45 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
44 46 </ul>
45 47 </div>
46 48
47 49 <div class="main-content-full-width">
48 50 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
49 51 </div>
50 52 </div>
51 53 </div>
52 54
53 55 </%def>
General Comments 0
You need to be logged in to leave comments. Login now