##// END OF EJS Templates
my-account: speed up fetching permissions in my account repos grid.
marcink -
r4144:6a12088f default
parent child Browse files
Show More
@@ -1,764 +1,765 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 import string
24 24
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import peppercorn
28 28 from pyramid.httpexceptions import HTTPFound
29 29 from pyramid.view import view_config
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 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 38 from rhodecode.lib.channelstream import (
39 39 channelstream_request, ChannelstreamException)
40 40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.model.comment import CommentsModel
43 43 from rhodecode.model.db import (
44 44 IntegrityError, joinedload,
45 45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 46 PullRequest, UserBookmark, RepoGroup)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.pull_request import PullRequestModel
49 49 from rhodecode.model.scm import RepoList
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.user_group import UserGroupModel
53 53 from rhodecode.model.validation_schema.schemas import user_schema
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class MyAccountView(BaseAppView, DataGridAppView):
59 59 ALLOW_SCOPED_TOKENS = False
60 60 """
61 61 This view has alternative version inside EE, if modified please take a look
62 62 in there as well.
63 63 """
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 c.user = c.auth_user.get_instance()
68 68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69 69
70 70 return c
71 71
72 72 @LoginRequired()
73 73 @NotAnonymous()
74 74 @view_config(
75 75 route_name='my_account_profile', request_method='GET',
76 76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 77 def my_account_profile(self):
78 78 c = self.load_default_context()
79 79 c.active = 'profile'
80 80 return self._get_template_context(c)
81 81
82 82 @LoginRequired()
83 83 @NotAnonymous()
84 84 @view_config(
85 85 route_name='my_account_password', request_method='GET',
86 86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 87 def my_account_password(self):
88 88 c = self.load_default_context()
89 89 c.active = 'password'
90 90 c.extern_type = c.user.extern_type
91 91
92 92 schema = user_schema.ChangePasswordSchema().bind(
93 93 username=c.user.username)
94 94
95 95 form = forms.Form(
96 96 schema,
97 97 action=h.route_path('my_account_password_update'),
98 98 buttons=(forms.buttons.save, forms.buttons.reset))
99 99
100 100 c.form = form
101 101 return self._get_template_context(c)
102 102
103 103 @LoginRequired()
104 104 @NotAnonymous()
105 105 @CSRFRequired()
106 106 @view_config(
107 107 route_name='my_account_password_update', request_method='POST',
108 108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 109 def my_account_password_update(self):
110 110 _ = self.request.translate
111 111 c = self.load_default_context()
112 112 c.active = 'password'
113 113 c.extern_type = c.user.extern_type
114 114
115 115 schema = user_schema.ChangePasswordSchema().bind(
116 116 username=c.user.username)
117 117
118 118 form = forms.Form(
119 119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120 120
121 121 if c.extern_type != 'rhodecode':
122 122 raise HTTPFound(self.request.route_path('my_account_password'))
123 123
124 124 controls = self.request.POST.items()
125 125 try:
126 126 valid_data = form.validate(controls)
127 127 UserModel().update_user(c.user.user_id, **valid_data)
128 128 c.user.update_userdata(force_password_change=False)
129 129 Session().commit()
130 130 except forms.ValidationFailure as e:
131 131 c.form = e
132 132 return self._get_template_context(c)
133 133
134 134 except Exception:
135 135 log.exception("Exception updating password")
136 136 h.flash(_('Error occurred during update of user password'),
137 137 category='error')
138 138 else:
139 139 instance = c.auth_user.get_instance()
140 140 self.session.setdefault('rhodecode_user', {}).update(
141 141 {'password': md5(instance.password)})
142 142 self.session.save()
143 143 h.flash(_("Successfully updated password"), category='success')
144 144
145 145 raise HTTPFound(self.request.route_path('my_account_password'))
146 146
147 147 @LoginRequired()
148 148 @NotAnonymous()
149 149 @view_config(
150 150 route_name='my_account_auth_tokens', request_method='GET',
151 151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 152 def my_account_auth_tokens(self):
153 153 _ = self.request.translate
154 154
155 155 c = self.load_default_context()
156 156 c.active = 'auth_tokens'
157 157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 158 c.role_values = [
159 159 (x, AuthTokenModel.cls._get_role_name(x))
160 160 for x in AuthTokenModel.cls.ROLES]
161 161 c.role_options = [(c.role_values, _("Role"))]
162 162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 163 c.user.user_id, show_expired=True)
164 164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 165 return self._get_template_context(c)
166 166
167 167 def maybe_attach_token_scope(self, token):
168 168 # implemented in EE edition
169 169 pass
170 170
171 171 @LoginRequired()
172 172 @NotAnonymous()
173 173 @CSRFRequired()
174 174 @view_config(
175 175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 176 def my_account_auth_tokens_add(self):
177 177 _ = self.request.translate
178 178 c = self.load_default_context()
179 179
180 180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 181 description = self.request.POST.get('description')
182 182 role = self.request.POST.get('role')
183 183
184 184 token = UserModel().add_auth_token(
185 185 user=c.user.user_id,
186 186 lifetime_minutes=lifetime, role=role, description=description,
187 187 scope_callback=self.maybe_attach_token_scope)
188 188 token_data = token.get_api_data()
189 189
190 190 audit_logger.store_web(
191 191 'user.edit.token.add', action_data={
192 192 'data': {'token': token_data, 'user': 'self'}},
193 193 user=self._rhodecode_user, )
194 194 Session().commit()
195 195
196 196 h.flash(_("Auth token successfully created"), category='success')
197 197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198 198
199 199 @LoginRequired()
200 200 @NotAnonymous()
201 201 @CSRFRequired()
202 202 @view_config(
203 203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 204 def my_account_auth_tokens_delete(self):
205 205 _ = self.request.translate
206 206 c = self.load_default_context()
207 207
208 208 del_auth_token = self.request.POST.get('del_auth_token')
209 209
210 210 if del_auth_token:
211 211 token = UserApiKeys.get_or_404(del_auth_token)
212 212 token_data = token.get_api_data()
213 213
214 214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 215 audit_logger.store_web(
216 216 'user.edit.token.delete', action_data={
217 217 'data': {'token': token_data, 'user': 'self'}},
218 218 user=self._rhodecode_user,)
219 219 Session().commit()
220 220 h.flash(_("Auth token successfully deleted"), category='success')
221 221
222 222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223 223
224 224 @LoginRequired()
225 225 @NotAnonymous()
226 226 @view_config(
227 227 route_name='my_account_emails', request_method='GET',
228 228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 229 def my_account_emails(self):
230 230 _ = self.request.translate
231 231
232 232 c = self.load_default_context()
233 233 c.active = 'emails'
234 234
235 235 c.user_email_map = UserEmailMap.query()\
236 236 .filter(UserEmailMap.user == c.user).all()
237 237
238 238 schema = user_schema.AddEmailSchema().bind(
239 239 username=c.user.username, user_emails=c.user.emails)
240 240
241 241 form = forms.RcForm(schema,
242 242 action=h.route_path('my_account_emails_add'),
243 243 buttons=(forms.buttons.save, forms.buttons.reset))
244 244
245 245 c.form = form
246 246 return self._get_template_context(c)
247 247
248 248 @LoginRequired()
249 249 @NotAnonymous()
250 250 @CSRFRequired()
251 251 @view_config(
252 252 route_name='my_account_emails_add', request_method='POST',
253 253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 254 def my_account_emails_add(self):
255 255 _ = self.request.translate
256 256 c = self.load_default_context()
257 257 c.active = 'emails'
258 258
259 259 schema = user_schema.AddEmailSchema().bind(
260 260 username=c.user.username, user_emails=c.user.emails)
261 261
262 262 form = forms.RcForm(
263 263 schema, action=h.route_path('my_account_emails_add'),
264 264 buttons=(forms.buttons.save, forms.buttons.reset))
265 265
266 266 controls = self.request.POST.items()
267 267 try:
268 268 valid_data = form.validate(controls)
269 269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 270 audit_logger.store_web(
271 271 'user.edit.email.add', action_data={
272 272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 273 user=self._rhodecode_user,)
274 274 Session().commit()
275 275 except formencode.Invalid as error:
276 276 h.flash(h.escape(error.error_dict['email']), category='error')
277 277 except forms.ValidationFailure as e:
278 278 c.user_email_map = UserEmailMap.query() \
279 279 .filter(UserEmailMap.user == c.user).all()
280 280 c.form = e
281 281 return self._get_template_context(c)
282 282 except Exception:
283 283 log.exception("Exception adding email")
284 284 h.flash(_('Error occurred during adding email'),
285 285 category='error')
286 286 else:
287 287 h.flash(_("Successfully added email"), category='success')
288 288
289 289 raise HTTPFound(self.request.route_path('my_account_emails'))
290 290
291 291 @LoginRequired()
292 292 @NotAnonymous()
293 293 @CSRFRequired()
294 294 @view_config(
295 295 route_name='my_account_emails_delete', request_method='POST')
296 296 def my_account_emails_delete(self):
297 297 _ = self.request.translate
298 298 c = self.load_default_context()
299 299
300 300 del_email_id = self.request.POST.get('del_email_id')
301 301 if del_email_id:
302 302 email = UserEmailMap.get_or_404(del_email_id).email
303 303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 304 audit_logger.store_web(
305 305 'user.edit.email.delete', action_data={
306 306 'data': {'email': email, 'user': 'self'}},
307 307 user=self._rhodecode_user,)
308 308 Session().commit()
309 309 h.flash(_("Email successfully deleted"),
310 310 category='success')
311 311 return HTTPFound(h.route_path('my_account_emails'))
312 312
313 313 @LoginRequired()
314 314 @NotAnonymous()
315 315 @CSRFRequired()
316 316 @view_config(
317 317 route_name='my_account_notifications_test_channelstream',
318 318 request_method='POST', renderer='json_ext')
319 319 def my_account_notifications_test_channelstream(self):
320 320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 321 self._rhodecode_user.username, datetime.datetime.now())
322 322 payload = {
323 323 # 'channel': 'broadcast',
324 324 'type': 'message',
325 325 'timestamp': datetime.datetime.utcnow(),
326 326 'user': 'system',
327 327 'pm_users': [self._rhodecode_user.username],
328 328 'message': {
329 329 'message': message,
330 330 'level': 'info',
331 331 'topic': '/notifications'
332 332 }
333 333 }
334 334
335 335 registry = self.request.registry
336 336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338 338
339 339 try:
340 340 channelstream_request(channelstream_config, [payload], '/message')
341 341 except ChannelstreamException as e:
342 342 log.exception('Failed to send channelstream data')
343 343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 344 return {"response": 'Channelstream data sent. '
345 345 'You should see a new live message now.'}
346 346
347 347 def _load_my_repos_data(self, watched=False):
348 348 if watched:
349 349 admin = False
350 350 follows_repos = Session().query(UserFollowing)\
351 351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 352 .options(joinedload(UserFollowing.follows_repository))\
353 353 .all()
354 354 repo_list = [x.follows_repository for x in follows_repos]
355 355 else:
356 356 admin = True
357 357 repo_list = Repository.get_all_repos(
358 358 user_id=self._rhodecode_user.user_id)
359 359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'])
360 'repository.read', 'repository.write', 'repository.admin'],
361 extra_kwargs=dict(user=self._rhodecode_user))
361 362
362 363 repos_data = RepoModel().get_repos_as_dict(
363 364 repo_list=repo_list, admin=admin, short_name=False)
364 365 # json used to render the grid
365 366 return json.dumps(repos_data)
366 367
367 368 @LoginRequired()
368 369 @NotAnonymous()
369 370 @view_config(
370 371 route_name='my_account_repos', request_method='GET',
371 372 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 373 def my_account_repos(self):
373 374 c = self.load_default_context()
374 375 c.active = 'repos'
375 376
376 377 # json used to render the grid
377 378 c.data = self._load_my_repos_data()
378 379 return self._get_template_context(c)
379 380
380 381 @LoginRequired()
381 382 @NotAnonymous()
382 383 @view_config(
383 384 route_name='my_account_watched', request_method='GET',
384 385 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 386 def my_account_watched(self):
386 387 c = self.load_default_context()
387 388 c.active = 'watched'
388 389
389 390 # json used to render the grid
390 391 c.data = self._load_my_repos_data(watched=True)
391 392 return self._get_template_context(c)
392 393
393 394 @LoginRequired()
394 395 @NotAnonymous()
395 396 @view_config(
396 397 route_name='my_account_bookmarks', request_method='GET',
397 398 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 399 def my_account_bookmarks(self):
399 400 c = self.load_default_context()
400 401 c.active = 'bookmarks'
401 402 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
402 403 self._rhodecode_db_user.user_id, cache=False)
403 404 return self._get_template_context(c)
404 405
405 406 def _process_bookmark_entry(self, entry, user_id):
406 407 position = safe_int(entry.get('position'))
407 408 cur_position = safe_int(entry.get('cur_position'))
408 409 if position is None:
409 410 return
410 411
411 412 # check if this is an existing entry
412 413 is_new = False
413 414 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
414 415
415 416 if db_entry and str2bool(entry.get('remove')):
416 417 log.debug('Marked bookmark %s for deletion', db_entry)
417 418 Session().delete(db_entry)
418 419 return
419 420
420 421 if not db_entry:
421 422 # new
422 423 db_entry = UserBookmark()
423 424 is_new = True
424 425
425 426 should_save = False
426 427 default_redirect_url = ''
427 428
428 429 # save repo
429 430 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
430 431 repo = Repository.get(entry['bookmark_repo'])
431 432 perm_check = HasRepoPermissionAny(
432 433 'repository.read', 'repository.write', 'repository.admin')
433 434 if repo and perm_check(repo_name=repo.repo_name):
434 435 db_entry.repository = repo
435 436 should_save = True
436 437 default_redirect_url = '${repo_url}'
437 438 # save repo group
438 439 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
439 440 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
440 441 perm_check = HasRepoGroupPermissionAny(
441 442 'group.read', 'group.write', 'group.admin')
442 443
443 444 if repo_group and perm_check(group_name=repo_group.group_name):
444 445 db_entry.repository_group = repo_group
445 446 should_save = True
446 447 default_redirect_url = '${repo_group_url}'
447 448 # save generic info
448 449 elif entry.get('title') and entry.get('redirect_url'):
449 450 should_save = True
450 451
451 452 if should_save:
452 453 # mark user and position
453 454 db_entry.user_id = user_id
454 455 db_entry.position = position
455 456 db_entry.title = entry.get('title')
456 457 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
457 458 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
458 459
459 460 Session().add(db_entry)
460 461
461 462 @LoginRequired()
462 463 @NotAnonymous()
463 464 @CSRFRequired()
464 465 @view_config(
465 466 route_name='my_account_bookmarks_update', request_method='POST')
466 467 def my_account_bookmarks_update(self):
467 468 _ = self.request.translate
468 469 c = self.load_default_context()
469 470 c.active = 'bookmarks'
470 471
471 472 controls = peppercorn.parse(self.request.POST.items())
472 473 user_id = c.user.user_id
473 474
474 475 # validate positions
475 476 positions = {}
476 477 for entry in controls.get('bookmarks', []):
477 478 position = safe_int(entry['position'])
478 479 if position is None:
479 480 continue
480 481
481 482 if position in positions:
482 483 h.flash(_("Position {} is defined twice. "
483 484 "Please correct this error.").format(position), category='error')
484 485 return HTTPFound(h.route_path('my_account_bookmarks'))
485 486
486 487 entry['position'] = position
487 488 entry['cur_position'] = safe_int(entry.get('cur_position'))
488 489 positions[position] = entry
489 490
490 491 try:
491 492 for entry in positions.values():
492 493 self._process_bookmark_entry(entry, user_id)
493 494
494 495 Session().commit()
495 496 h.flash(_("Update Bookmarks"), category='success')
496 497 except IntegrityError:
497 498 h.flash(_("Failed to update bookmarks. "
498 499 "Make sure an unique position is used."), category='error')
499 500
500 501 return HTTPFound(h.route_path('my_account_bookmarks'))
501 502
502 503 @LoginRequired()
503 504 @NotAnonymous()
504 505 @view_config(
505 506 route_name='my_account_goto_bookmark', request_method='GET',
506 507 renderer='rhodecode:templates/admin/my_account/my_account.mako')
507 508 def my_account_goto_bookmark(self):
508 509
509 510 bookmark_id = self.request.matchdict['bookmark_id']
510 511 user_bookmark = UserBookmark().query()\
511 512 .filter(UserBookmark.user_id == self.request.user.user_id) \
512 513 .filter(UserBookmark.position == bookmark_id).scalar()
513 514
514 515 redirect_url = h.route_path('my_account_bookmarks')
515 516 if not user_bookmark:
516 517 raise HTTPFound(redirect_url)
517 518
518 519 # repository set
519 520 if user_bookmark.repository:
520 521 repo_name = user_bookmark.repository.repo_name
521 522 base_redirect_url = h.route_path(
522 523 'repo_summary', repo_name=repo_name)
523 524 if user_bookmark.redirect_url and \
524 525 '${repo_url}' in user_bookmark.redirect_url:
525 526 redirect_url = string.Template(user_bookmark.redirect_url)\
526 527 .safe_substitute({'repo_url': base_redirect_url})
527 528 else:
528 529 redirect_url = base_redirect_url
529 530 # repository group set
530 531 elif user_bookmark.repository_group:
531 532 repo_group_name = user_bookmark.repository_group.group_name
532 533 base_redirect_url = h.route_path(
533 534 'repo_group_home', repo_group_name=repo_group_name)
534 535 if user_bookmark.redirect_url and \
535 536 '${repo_group_url}' in user_bookmark.redirect_url:
536 537 redirect_url = string.Template(user_bookmark.redirect_url)\
537 538 .safe_substitute({'repo_group_url': base_redirect_url})
538 539 else:
539 540 redirect_url = base_redirect_url
540 541 # custom URL set
541 542 elif user_bookmark.redirect_url:
542 543 server_url = h.route_url('home').rstrip('/')
543 544 redirect_url = string.Template(user_bookmark.redirect_url) \
544 545 .safe_substitute({'server_url': server_url})
545 546
546 547 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
547 548 raise HTTPFound(redirect_url)
548 549
549 550 @LoginRequired()
550 551 @NotAnonymous()
551 552 @view_config(
552 553 route_name='my_account_perms', request_method='GET',
553 554 renderer='rhodecode:templates/admin/my_account/my_account.mako')
554 555 def my_account_perms(self):
555 556 c = self.load_default_context()
556 557 c.active = 'perms'
557 558
558 559 c.perm_user = c.auth_user
559 560 return self._get_template_context(c)
560 561
561 562 @LoginRequired()
562 563 @NotAnonymous()
563 564 @view_config(
564 565 route_name='my_account_notifications', request_method='GET',
565 566 renderer='rhodecode:templates/admin/my_account/my_account.mako')
566 567 def my_notifications(self):
567 568 c = self.load_default_context()
568 569 c.active = 'notifications'
569 570
570 571 return self._get_template_context(c)
571 572
572 573 @LoginRequired()
573 574 @NotAnonymous()
574 575 @CSRFRequired()
575 576 @view_config(
576 577 route_name='my_account_notifications_toggle_visibility',
577 578 request_method='POST', renderer='json_ext')
578 579 def my_notifications_toggle_visibility(self):
579 580 user = self._rhodecode_db_user
580 581 new_status = not user.user_data.get('notification_status', True)
581 582 user.update_userdata(notification_status=new_status)
582 583 Session().commit()
583 584 return user.user_data['notification_status']
584 585
585 586 @LoginRequired()
586 587 @NotAnonymous()
587 588 @view_config(
588 589 route_name='my_account_edit',
589 590 request_method='GET',
590 591 renderer='rhodecode:templates/admin/my_account/my_account.mako')
591 592 def my_account_edit(self):
592 593 c = self.load_default_context()
593 594 c.active = 'profile_edit'
594 595 c.extern_type = c.user.extern_type
595 596 c.extern_name = c.user.extern_name
596 597
597 598 schema = user_schema.UserProfileSchema().bind(
598 599 username=c.user.username, user_emails=c.user.emails)
599 600 appstruct = {
600 601 'username': c.user.username,
601 602 'email': c.user.email,
602 603 'firstname': c.user.firstname,
603 604 'lastname': c.user.lastname,
604 605 'description': c.user.description,
605 606 }
606 607 c.form = forms.RcForm(
607 608 schema, appstruct=appstruct,
608 609 action=h.route_path('my_account_update'),
609 610 buttons=(forms.buttons.save, forms.buttons.reset))
610 611
611 612 return self._get_template_context(c)
612 613
613 614 @LoginRequired()
614 615 @NotAnonymous()
615 616 @CSRFRequired()
616 617 @view_config(
617 618 route_name='my_account_update',
618 619 request_method='POST',
619 620 renderer='rhodecode:templates/admin/my_account/my_account.mako')
620 621 def my_account_update(self):
621 622 _ = self.request.translate
622 623 c = self.load_default_context()
623 624 c.active = 'profile_edit'
624 625 c.perm_user = c.auth_user
625 626 c.extern_type = c.user.extern_type
626 627 c.extern_name = c.user.extern_name
627 628
628 629 schema = user_schema.UserProfileSchema().bind(
629 630 username=c.user.username, user_emails=c.user.emails)
630 631 form = forms.RcForm(
631 632 schema, buttons=(forms.buttons.save, forms.buttons.reset))
632 633
633 634 controls = self.request.POST.items()
634 635 try:
635 636 valid_data = form.validate(controls)
636 637 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
637 638 'new_password', 'password_confirmation']
638 639 if c.extern_type != "rhodecode":
639 640 # forbid updating username for external accounts
640 641 skip_attrs.append('username')
641 642 old_email = c.user.email
642 643 UserModel().update_user(
643 644 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
644 645 **valid_data)
645 646 if old_email != valid_data['email']:
646 647 old = UserEmailMap.query() \
647 648 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
648 649 old.email = old_email
649 650 h.flash(_('Your account was updated successfully'), category='success')
650 651 Session().commit()
651 652 except forms.ValidationFailure as e:
652 653 c.form = e
653 654 return self._get_template_context(c)
654 655 except Exception:
655 656 log.exception("Exception updating user")
656 657 h.flash(_('Error occurred during update of user'),
657 658 category='error')
658 659 raise HTTPFound(h.route_path('my_account_profile'))
659 660
660 661 def _get_pull_requests_list(self, statuses):
661 662 draw, start, limit = self._extract_chunk(self.request)
662 663 search_q, order_by, order_dir = self._extract_ordering(self.request)
663 664 _render = self.request.get_partial_renderer(
664 665 'rhodecode:templates/data_table/_dt_elements.mako')
665 666
666 667 pull_requests = PullRequestModel().get_im_participating_in(
667 668 user_id=self._rhodecode_user.user_id,
668 669 statuses=statuses,
669 670 offset=start, length=limit, order_by=order_by,
670 671 order_dir=order_dir)
671 672
672 673 pull_requests_total_count = PullRequestModel().count_im_participating_in(
673 674 user_id=self._rhodecode_user.user_id, statuses=statuses)
674 675
675 676 data = []
676 677 comments_model = CommentsModel()
677 678 for pr in pull_requests:
678 679 repo_id = pr.target_repo_id
679 680 comments = comments_model.get_all_comments(
680 681 repo_id, pull_request=pr)
681 682 owned = pr.user_id == self._rhodecode_user.user_id
682 683
683 684 data.append({
684 685 'target_repo': _render('pullrequest_target_repo',
685 686 pr.target_repo.repo_name),
686 687 'name': _render('pullrequest_name',
687 688 pr.pull_request_id, pr.pull_request_state,
688 689 pr.work_in_progress, pr.target_repo.repo_name,
689 690 short=True),
690 691 'name_raw': pr.pull_request_id,
691 692 'status': _render('pullrequest_status',
692 693 pr.calculated_review_status()),
693 694 'title': _render('pullrequest_title', pr.title, pr.description),
694 695 'description': h.escape(pr.description),
695 696 'updated_on': _render('pullrequest_updated_on',
696 697 h.datetime_to_time(pr.updated_on)),
697 698 'updated_on_raw': h.datetime_to_time(pr.updated_on),
698 699 'created_on': _render('pullrequest_updated_on',
699 700 h.datetime_to_time(pr.created_on)),
700 701 'created_on_raw': h.datetime_to_time(pr.created_on),
701 702 'state': pr.pull_request_state,
702 703 'author': _render('pullrequest_author',
703 704 pr.author.full_contact, ),
704 705 'author_raw': pr.author.full_name,
705 706 'comments': _render('pullrequest_comments', len(comments)),
706 707 'comments_raw': len(comments),
707 708 'closed': pr.is_closed(),
708 709 'owned': owned
709 710 })
710 711
711 712 # json used to render the grid
712 713 data = ({
713 714 'draw': draw,
714 715 'data': data,
715 716 'recordsTotal': pull_requests_total_count,
716 717 'recordsFiltered': pull_requests_total_count,
717 718 })
718 719 return data
719 720
720 721 @LoginRequired()
721 722 @NotAnonymous()
722 723 @view_config(
723 724 route_name='my_account_pullrequests',
724 725 request_method='GET',
725 726 renderer='rhodecode:templates/admin/my_account/my_account.mako')
726 727 def my_account_pullrequests(self):
727 728 c = self.load_default_context()
728 729 c.active = 'pullrequests'
729 730 req_get = self.request.GET
730 731
731 732 c.closed = str2bool(req_get.get('pr_show_closed'))
732 733
733 734 return self._get_template_context(c)
734 735
735 736 @LoginRequired()
736 737 @NotAnonymous()
737 738 @view_config(
738 739 route_name='my_account_pullrequests_data',
739 740 request_method='GET', renderer='json_ext')
740 741 def my_account_pullrequests_data(self):
741 742 self.load_default_context()
742 743 req_get = self.request.GET
743 744 closed = str2bool(req_get.get('closed'))
744 745
745 746 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
746 747 if closed:
747 748 statuses += [PullRequest.STATUS_CLOSED]
748 749
749 750 data = self._get_pull_requests_list(statuses=statuses)
750 751 return data
751 752
752 753 @LoginRequired()
753 754 @NotAnonymous()
754 755 @view_config(
755 756 route_name='my_account_user_group_membership',
756 757 request_method='GET',
757 758 renderer='rhodecode:templates/admin/my_account/my_account.mako')
758 759 def my_account_user_group_membership(self):
759 760 c = self.load_default_context()
760 761 c.active = 'user_group_membership'
761 762 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
762 763 for group in self._rhodecode_db_user.group_member]
763 764 c.user_groups = json.dumps(groups)
764 765 return self._get_template_context(c)
General Comments 0
You need to be logged in to leave comments. Login now