##// END OF EJS Templates
user-bookmarks: make it easier to re-organize entries.
ergo -
r3995:daa7294b default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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