##// END OF EJS Templates
user-bookmarks: add ${server_url} template to be used in the links.
dan -
r3526:0c9e5d56 default
parent child Browse files
Show More
@@ -1,740 +1,743 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 360 'repository.read', 'repository.write', 'repository.admin'])
361 361
362 362 repos_data = RepoModel().get_repos_as_dict(
363 363 repo_list=repo_list, admin=admin)
364 364 # json used to render the grid
365 365 return json.dumps(repos_data)
366 366
367 367 @LoginRequired()
368 368 @NotAnonymous()
369 369 @view_config(
370 370 route_name='my_account_repos', request_method='GET',
371 371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 372 def my_account_repos(self):
373 373 c = self.load_default_context()
374 374 c.active = 'repos'
375 375
376 376 # json used to render the grid
377 377 c.data = self._load_my_repos_data()
378 378 return self._get_template_context(c)
379 379
380 380 @LoginRequired()
381 381 @NotAnonymous()
382 382 @view_config(
383 383 route_name='my_account_watched', request_method='GET',
384 384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 385 def my_account_watched(self):
386 386 c = self.load_default_context()
387 387 c.active = 'watched'
388 388
389 389 # json used to render the grid
390 390 c.data = self._load_my_repos_data(watched=True)
391 391 return self._get_template_context(c)
392 392
393 393 @LoginRequired()
394 394 @NotAnonymous()
395 395 @view_config(
396 396 route_name='my_account_bookmarks', request_method='GET',
397 397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 398 def my_account_bookmarks(self):
399 399 c = self.load_default_context()
400 400 c.active = 'bookmarks'
401 401 return self._get_template_context(c)
402 402
403 403 def _process_entry(self, entry, user_id):
404 404 position = safe_int(entry.get('position'))
405 405 if position is None:
406 406 return
407 407
408 408 # check if this is an existing entry
409 409 is_new = False
410 410 db_entry = UserBookmark().get_by_position_for_user(position, user_id)
411 411
412 412 if db_entry and str2bool(entry.get('remove')):
413 413 log.debug('Marked bookmark %s for deletion', db_entry)
414 414 Session().delete(db_entry)
415 415 return
416 416
417 417 if not db_entry:
418 418 # new
419 419 db_entry = UserBookmark()
420 420 is_new = True
421 421
422 422 should_save = False
423 423 default_redirect_url = ''
424 424
425 425 # save repo
426 426 if entry.get('bookmark_repo'):
427 427 repo = Repository.get(entry['bookmark_repo'])
428 428 perm_check = HasRepoPermissionAny(
429 429 'repository.read', 'repository.write', 'repository.admin')
430 430 if repo and perm_check(repo_name=repo.repo_name):
431 431 db_entry.repository = repo
432 432 should_save = True
433 433 default_redirect_url = '${repo_url}'
434 434 # save repo group
435 435 elif entry.get('bookmark_repo_group'):
436 436 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 437 perm_check = HasRepoGroupPermissionAny(
438 438 'group.read', 'group.write', 'group.admin')
439 439
440 440 if repo_group and perm_check(group_name=repo_group.group_name):
441 441 db_entry.repository_group = repo_group
442 442 should_save = True
443 443 default_redirect_url = '${repo_group_url}'
444 444 # save generic info
445 445 elif entry.get('title') and entry.get('redirect_url'):
446 446 should_save = True
447 447
448 448 if should_save:
449 449 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
450 450 # mark user and position
451 451 db_entry.user_id = user_id
452 452 db_entry.position = position
453 453 db_entry.title = entry.get('title')
454 454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455 455
456 456 Session().add(db_entry)
457 457
458 458 @LoginRequired()
459 459 @NotAnonymous()
460 460 @CSRFRequired()
461 461 @view_config(
462 462 route_name='my_account_bookmarks_update', request_method='POST')
463 463 def my_account_bookmarks_update(self):
464 464 _ = self.request.translate
465 465 c = self.load_default_context()
466 466 c.active = 'bookmarks'
467 467
468 468 controls = peppercorn.parse(self.request.POST.items())
469 469 user_id = c.user.user_id
470 470
471 471 try:
472 472 for entry in controls.get('bookmarks', []):
473 473 self._process_entry(entry, user_id)
474 474
475 475 Session().commit()
476 476 h.flash(_("Update Bookmarks"), category='success')
477 477 except IntegrityError:
478 478 h.flash(_("Failed to update bookmarks. "
479 479 "Make sure an unique position is used"), category='error')
480 480
481 481 return HTTPFound(h.route_path('my_account_bookmarks'))
482 482
483 483 @LoginRequired()
484 484 @NotAnonymous()
485 485 @view_config(
486 486 route_name='my_account_goto_bookmark', request_method='GET',
487 487 renderer='rhodecode:templates/admin/my_account/my_account.mako')
488 488 def my_account_goto_bookmark(self):
489 489
490 490 bookmark_id = self.request.matchdict['bookmark_id']
491 491 user_bookmark = UserBookmark().query()\
492 492 .filter(UserBookmark.user_id == self.request.user.user_id) \
493 493 .filter(UserBookmark.position == bookmark_id).scalar()
494 494
495 495 redirect_url = h.route_path('my_account_bookmarks')
496 496 if not user_bookmark:
497 497 raise HTTPFound(redirect_url)
498 498
499 # repository set
499 500 if user_bookmark.repository:
500 501 repo_name = user_bookmark.repository.repo_name
501 502 base_redirect_url = h.route_path(
502 503 'repo_summary', repo_name=repo_name)
503 504 if user_bookmark.redirect_url and \
504 505 '${repo_url}' in user_bookmark.redirect_url:
505 506 redirect_url = string.Template(user_bookmark.redirect_url)\
506 507 .safe_substitute({'repo_url': base_redirect_url})
507 508 else:
508 509 redirect_url = base_redirect_url
509
510 # repository group set
510 511 elif user_bookmark.repository_group:
511 512 repo_group_name = user_bookmark.repository_group.group_name
512 513 base_redirect_url = h.route_path(
513 514 'repo_group_home', repo_group_name=repo_group_name)
514 515 if user_bookmark.redirect_url and \
515 516 '${repo_group_url}' in user_bookmark.redirect_url:
516 517 redirect_url = string.Template(user_bookmark.redirect_url)\
517 518 .safe_substitute({'repo_group_url': base_redirect_url})
518 519 else:
519 520 redirect_url = base_redirect_url
520
521 # custom URL set
521 522 elif user_bookmark.redirect_url:
522 redirect_url = user_bookmark.redirect_url
523 server_url = h.route_url('home').rstrip('/')
524 redirect_url = string.Template(user_bookmark.redirect_url) \
525 .safe_substitute({'server_url': server_url})
523 526
524 527 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
525 528 raise HTTPFound(redirect_url)
526 529
527 530 @LoginRequired()
528 531 @NotAnonymous()
529 532 @view_config(
530 533 route_name='my_account_perms', request_method='GET',
531 534 renderer='rhodecode:templates/admin/my_account/my_account.mako')
532 535 def my_account_perms(self):
533 536 c = self.load_default_context()
534 537 c.active = 'perms'
535 538
536 539 c.perm_user = c.auth_user
537 540 return self._get_template_context(c)
538 541
539 542 @LoginRequired()
540 543 @NotAnonymous()
541 544 @view_config(
542 545 route_name='my_account_notifications', request_method='GET',
543 546 renderer='rhodecode:templates/admin/my_account/my_account.mako')
544 547 def my_notifications(self):
545 548 c = self.load_default_context()
546 549 c.active = 'notifications'
547 550
548 551 return self._get_template_context(c)
549 552
550 553 @LoginRequired()
551 554 @NotAnonymous()
552 555 @CSRFRequired()
553 556 @view_config(
554 557 route_name='my_account_notifications_toggle_visibility',
555 558 request_method='POST', renderer='json_ext')
556 559 def my_notifications_toggle_visibility(self):
557 560 user = self._rhodecode_db_user
558 561 new_status = not user.user_data.get('notification_status', True)
559 562 user.update_userdata(notification_status=new_status)
560 563 Session().commit()
561 564 return user.user_data['notification_status']
562 565
563 566 @LoginRequired()
564 567 @NotAnonymous()
565 568 @view_config(
566 569 route_name='my_account_edit',
567 570 request_method='GET',
568 571 renderer='rhodecode:templates/admin/my_account/my_account.mako')
569 572 def my_account_edit(self):
570 573 c = self.load_default_context()
571 574 c.active = 'profile_edit'
572 575 c.extern_type = c.user.extern_type
573 576 c.extern_name = c.user.extern_name
574 577
575 578 schema = user_schema.UserProfileSchema().bind(
576 579 username=c.user.username, user_emails=c.user.emails)
577 580 appstruct = {
578 581 'username': c.user.username,
579 582 'email': c.user.email,
580 583 'firstname': c.user.firstname,
581 584 'lastname': c.user.lastname,
582 585 }
583 586 c.form = forms.RcForm(
584 587 schema, appstruct=appstruct,
585 588 action=h.route_path('my_account_update'),
586 589 buttons=(forms.buttons.save, forms.buttons.reset))
587 590
588 591 return self._get_template_context(c)
589 592
590 593 @LoginRequired()
591 594 @NotAnonymous()
592 595 @CSRFRequired()
593 596 @view_config(
594 597 route_name='my_account_update',
595 598 request_method='POST',
596 599 renderer='rhodecode:templates/admin/my_account/my_account.mako')
597 600 def my_account_update(self):
598 601 _ = self.request.translate
599 602 c = self.load_default_context()
600 603 c.active = 'profile_edit'
601 604 c.perm_user = c.auth_user
602 605 c.extern_type = c.user.extern_type
603 606 c.extern_name = c.user.extern_name
604 607
605 608 schema = user_schema.UserProfileSchema().bind(
606 609 username=c.user.username, user_emails=c.user.emails)
607 610 form = forms.RcForm(
608 611 schema, buttons=(forms.buttons.save, forms.buttons.reset))
609 612
610 613 controls = self.request.POST.items()
611 614 try:
612 615 valid_data = form.validate(controls)
613 616 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
614 617 'new_password', 'password_confirmation']
615 618 if c.extern_type != "rhodecode":
616 619 # forbid updating username for external accounts
617 620 skip_attrs.append('username')
618 621 old_email = c.user.email
619 622 UserModel().update_user(
620 623 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
621 624 **valid_data)
622 625 if old_email != valid_data['email']:
623 626 old = UserEmailMap.query() \
624 627 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
625 628 old.email = old_email
626 629 h.flash(_('Your account was updated successfully'), category='success')
627 630 Session().commit()
628 631 except forms.ValidationFailure as e:
629 632 c.form = e
630 633 return self._get_template_context(c)
631 634 except Exception:
632 635 log.exception("Exception updating user")
633 636 h.flash(_('Error occurred during update of user'),
634 637 category='error')
635 638 raise HTTPFound(h.route_path('my_account_profile'))
636 639
637 640 def _get_pull_requests_list(self, statuses):
638 641 draw, start, limit = self._extract_chunk(self.request)
639 642 search_q, order_by, order_dir = self._extract_ordering(self.request)
640 643 _render = self.request.get_partial_renderer(
641 644 'rhodecode:templates/data_table/_dt_elements.mako')
642 645
643 646 pull_requests = PullRequestModel().get_im_participating_in(
644 647 user_id=self._rhodecode_user.user_id,
645 648 statuses=statuses,
646 649 offset=start, length=limit, order_by=order_by,
647 650 order_dir=order_dir)
648 651
649 652 pull_requests_total_count = PullRequestModel().count_im_participating_in(
650 653 user_id=self._rhodecode_user.user_id, statuses=statuses)
651 654
652 655 data = []
653 656 comments_model = CommentsModel()
654 657 for pr in pull_requests:
655 658 repo_id = pr.target_repo_id
656 659 comments = comments_model.get_all_comments(
657 660 repo_id, pull_request=pr)
658 661 owned = pr.user_id == self._rhodecode_user.user_id
659 662
660 663 data.append({
661 664 'target_repo': _render('pullrequest_target_repo',
662 665 pr.target_repo.repo_name),
663 666 'name': _render('pullrequest_name',
664 667 pr.pull_request_id, pr.target_repo.repo_name,
665 668 short=True),
666 669 'name_raw': pr.pull_request_id,
667 670 'status': _render('pullrequest_status',
668 671 pr.calculated_review_status()),
669 672 'title': _render(
670 673 'pullrequest_title', pr.title, pr.description),
671 674 'description': h.escape(pr.description),
672 675 'updated_on': _render('pullrequest_updated_on',
673 676 h.datetime_to_time(pr.updated_on)),
674 677 'updated_on_raw': h.datetime_to_time(pr.updated_on),
675 678 'created_on': _render('pullrequest_updated_on',
676 679 h.datetime_to_time(pr.created_on)),
677 680 'created_on_raw': h.datetime_to_time(pr.created_on),
678 681 'author': _render('pullrequest_author',
679 682 pr.author.full_contact, ),
680 683 'author_raw': pr.author.full_name,
681 684 'comments': _render('pullrequest_comments', len(comments)),
682 685 'comments_raw': len(comments),
683 686 'closed': pr.is_closed(),
684 687 'owned': owned
685 688 })
686 689
687 690 # json used to render the grid
688 691 data = ({
689 692 'draw': draw,
690 693 'data': data,
691 694 'recordsTotal': pull_requests_total_count,
692 695 'recordsFiltered': pull_requests_total_count,
693 696 })
694 697 return data
695 698
696 699 @LoginRequired()
697 700 @NotAnonymous()
698 701 @view_config(
699 702 route_name='my_account_pullrequests',
700 703 request_method='GET',
701 704 renderer='rhodecode:templates/admin/my_account/my_account.mako')
702 705 def my_account_pullrequests(self):
703 706 c = self.load_default_context()
704 707 c.active = 'pullrequests'
705 708 req_get = self.request.GET
706 709
707 710 c.closed = str2bool(req_get.get('pr_show_closed'))
708 711
709 712 return self._get_template_context(c)
710 713
711 714 @LoginRequired()
712 715 @NotAnonymous()
713 716 @view_config(
714 717 route_name='my_account_pullrequests_data',
715 718 request_method='GET', renderer='json_ext')
716 719 def my_account_pullrequests_data(self):
717 720 self.load_default_context()
718 721 req_get = self.request.GET
719 722 closed = str2bool(req_get.get('closed'))
720 723
721 724 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
722 725 if closed:
723 726 statuses += [PullRequest.STATUS_CLOSED]
724 727
725 728 data = self._get_pull_requests_list(statuses=statuses)
726 729 return data
727 730
728 731 @LoginRequired()
729 732 @NotAnonymous()
730 733 @view_config(
731 734 route_name='my_account_user_group_membership',
732 735 request_method='GET',
733 736 renderer='rhodecode:templates/admin/my_account/my_account.mako')
734 737 def my_account_user_group_membership(self):
735 738 c = self.load_default_context()
736 739 c.active = 'user_group_membership'
737 740 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
738 741 for group in self._rhodecode_db_user.group_member]
739 742 c.user_groups = json.dumps(groups)
740 743 return self._get_template_context(c)
@@ -1,222 +1,225 b''
1 1 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 2
3 3 <%def name="form_item(count, position=None, title=None, redirect_url=None, repo=None, repo_group=None)">
4 4 <tr>
5 5 <td class="td-align-top" >
6 6 <div class="label">
7 7 <label for="position">${_('Position')}:</label>
8 8 </div>
9 9 <div class="input">
10 10 <input type="text" name="position" value="${position or count}" style="width: 40px"/>
11 11 </div>
12 12 </td>
13 13
14 14 <td>
15 15 <div class="label">
16 16 <label for="title">${_('Bookmark title (max 30 characters, optional)')}:</label>
17 17 </div>
18 18 <div class="input">
19 19 <input type="text" name="title" value="${title}" style="width: 300px" maxlength="30"/>
20 20
21 21 <div class="field pull-right">
22 22 <div>
23 23 <label class="btn-link btn-danger">${_('Clear')}:</label>
24 24 ${h.checkbox('remove', value=True)}
25 25 </div>
26 </div>
26 </div>
27 27 </div>
28 <p class="help-block help-block-inline" >
29 ${_('Server URL is available as ${server_url} variable. E.g. Redirect url: ${server_url}/_admin/exception_tracker')}
30 </p>
28 31
29 32 <div class="label">
30 33 <label for="redirect_url">${_('Redirect URL')}:</label>
31 34 </div>
32 35 <div class="input">
33 36 <input type="text" name="redirect_url" value="${redirect_url}" style="width: 600px"/>
34 37 </div>
35 38
36 39
37 40 <div class="select">
38 41 % if repo:
39 42 <div class="label">
40 43 <label for="redirect_url">${_('Repository template')}:</label>
41 44 </div>
42 45 ${dt.repo_name(name=repo.repo_name, rtype=repo.repo_type,rstate=None,private=None,archived=False,fork_of=False)}
43 46 ${h.hidden('bookmark_repo', repo.repo_id)}
44 47 % elif repo_group:
45 48 <div class="label">
46 49 <label for="redirect_url">${_('Repository group template')}:</label>
47 50 </div>
48 51 ${dt.repo_group_name(repo_group.group_name)}
49 52 ${h.hidden('bookmark_repo_group', repo_group.group_id)}
50 53 % else:
51 54 <div class="label">
52 55 <label for="redirect_url">${_('Template Repository or Repository group')}:</label>
53 56 </div>
54 57 ${h.hidden('bookmark_repo', class_='bookmark_repo')}
55 58 <span style="padding-right:15px">OR</span>
56 59 ${h.hidden('bookmark_repo_group', class_='bookmark_repo_group')}
57 60 % endif
58 61 </div>
59 62
60 63 <p class="help-block help-block-inline" >
61 64 % if repo:
62 65 ${_('Available as ${repo_url} e.g. Redirect url: ${repo_url}/changelog')}
63 66 % elif repo_group:
64 67 ${_('Available as ${repo_group_url} e.g. Redirect url: ${repo_group_url}')}
65 68 % else:
66 69 ${_('Available as full url variables in redirect url. i.e: ${repo_url}, ${repo_group_url}.')}
67 70 % endif
68 71 </p>
69 72 </td>
70 73
71 74 </tr>
72 75 </%def>
73 76
74 77 <div class="panel panel-default">
75 78 <div class="panel-heading">
76 79 <h3 class="panel-title">${_('Your Bookmarks')}</h3>
77 80 </div>
78 81
79 82 <div class="panel-body">
80 83 <p>
81 84 ${_('Store upto 10 bookmark links to favorite repositories, external issue tracker or CI server. ')}
82 85 <br/>
83 86 ${_('Bookmarks are accessible from your username dropdown or by keyboard shortcut `g 0-9`')}
84 87 </p>
85 88
86 89 ${h.secure_form(h.route_path('my_account_bookmarks_update'), request=request)}
87 90 <div class="form-vertical">
88 91 <table class="rctable">
89 92 ## generate always 10 entries
90 93 <input type="hidden" name="__start__" value="bookmarks:sequence"/>
91 94 % for cnt, item in enumerate((c.bookmark_items + [None for i in range(10)])[:10]):
92 95 <input type="hidden" name="__start__" value="bookmark:mapping"/>
93 96 % if item is None:
94 97 ## empty placehodlder
95 98 ${form_item(cnt)}
96 99 % else:
97 100 ## actual entry
98 101 ${form_item(cnt, position=item.position, title=item.title, redirect_url=item.redirect_url, repo=item.repository, repo_group=item.repository_group)}
99 102 % endif
100 103 <input type="hidden" name="__end__" value="bookmark:mapping"/>
101 104 % endfor
102 105 <input type="hidden" name="__end__" value="bookmarks:sequence"/>
103 106 </table>
104 107 <div class="buttons">
105 108 ${h.submit('save',_('Save'),class_="btn")}
106 109 </div>
107 110 </div>
108 111 ${h.end_form()}
109 112 </div>
110 113 </div>
111 114
112 115 <script>
113 116 $(document).ready(function(){
114 117
115 118
116 119 var repoFilter = function (data) {
117 120 var results = [];
118 121
119 122 if (!data.results[0]) {
120 123 return data
121 124 }
122 125
123 126 $.each(data.results[0].children, function () {
124 127 // replace name to ID for submision
125 128 this.id = this.repo_id;
126 129 results.push(this);
127 130 });
128 131
129 132 data.results[0].children = results;
130 133 return data;
131 134 };
132 135
133 136
134 137 $(".bookmark_repo").select2({
135 138 cachedDataSource: {},
136 139 minimumInputLength: 2,
137 140 placeholder: "${_('repository')}",
138 141 dropdownAutoWidth: true,
139 142 containerCssClass: "drop-menu",
140 143 dropdownCssClass: "drop-menu-dropdown",
141 144 formatResult: formatRepoResult,
142 145 query: $.debounce(250, function (query) {
143 146 self = this;
144 147 var cacheKey = query.term;
145 148 var cachedData = self.cachedDataSource[cacheKey];
146 149
147 150 if (cachedData) {
148 151 query.callback({results: cachedData.results});
149 152 } else {
150 153 $.ajax({
151 154 url: pyroutes.url('repo_list_data'),
152 155 data: {'query': query.term},
153 156 dataType: 'json',
154 157 type: 'GET',
155 158 success: function (data) {
156 159 data = repoFilter(data);
157 160 self.cachedDataSource[cacheKey] = data;
158 161 query.callback({results: data.results});
159 162 },
160 163 error: function (data, textStatus, errorThrown) {
161 164 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
162 165 }
163 166 })
164 167 }
165 168 }),
166 169 });
167 170
168 171 var repoGroupFilter = function (data) {
169 172 var results = [];
170 173
171 174 if (!data.results[0]) {
172 175 return data
173 176 }
174 177
175 178 $.each(data.results[0].children, function () {
176 179 // replace name to ID for submision
177 180 this.id = this.repo_group_id;
178 181 results.push(this);
179 182 });
180 183
181 184 data.results[0].children = results;
182 185 return data;
183 186 };
184 187
185 188 $(".bookmark_repo_group").select2({
186 189 cachedDataSource: {},
187 190 minimumInputLength: 2,
188 191 placeholder: "${_('repository group')}",
189 192 dropdownAutoWidth: true,
190 193 containerCssClass: "drop-menu",
191 194 dropdownCssClass: "drop-menu-dropdown",
192 195 formatResult: formatRepoGroupResult,
193 196 query: $.debounce(250, function (query) {
194 197 self = this;
195 198 var cacheKey = query.term;
196 199 var cachedData = self.cachedDataSource[cacheKey];
197 200
198 201 if (cachedData) {
199 202 query.callback({results: cachedData.results});
200 203 } else {
201 204 $.ajax({
202 205 url: pyroutes.url('repo_group_list_data'),
203 206 data: {'query': query.term},
204 207 dataType: 'json',
205 208 type: 'GET',
206 209 success: function (data) {
207 210 data = repoGroupFilter(data);
208 211 self.cachedDataSource[cacheKey] = data;
209 212 query.callback({results: data.results});
210 213 },
211 214 error: function (data, textStatus, errorThrown) {
212 215 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
213 216 }
214 217 })
215 218 }
216 219 })
217 220 });
218 221
219 222
220 223 });
221 224
222 225 </script>
General Comments 0
You need to be logged in to leave comments. Login now