##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import string
23 import string
24
24
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 from rhodecode.lib.channelstream import (
38 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
39 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 IntegrityError, joinedload,
44 IntegrityError, joinedload,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 PullRequest, UserBookmark, RepoGroup)
46 PullRequest, UserBookmark, RepoGroup)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.scm import RepoList
49 from rhodecode.model.scm import RepoList
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.validation_schema.schemas import user_schema
53 from rhodecode.model.validation_schema.schemas import user_schema
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class MyAccountView(BaseAppView, DataGridAppView):
58 class MyAccountView(BaseAppView, DataGridAppView):
59 ALLOW_SCOPED_TOKENS = False
59 ALLOW_SCOPED_TOKENS = False
60 """
60 """
61 This view has alternative version inside EE, if modified please take a look
61 This view has alternative version inside EE, if modified please take a look
62 in there as well.
62 in there as well.
63 """
63 """
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.user = c.auth_user.get_instance()
67 c.user = c.auth_user.get_instance()
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69
69
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='my_account_profile', request_method='GET',
75 route_name='my_account_profile', request_method='GET',
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 def my_account_profile(self):
77 def my_account_profile(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.active = 'profile'
79 c.active = 'profile'
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @NotAnonymous()
83 @NotAnonymous()
84 @view_config(
84 @view_config(
85 route_name='my_account_password', request_method='GET',
85 route_name='my_account_password', request_method='GET',
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 def my_account_password(self):
87 def my_account_password(self):
88 c = self.load_default_context()
88 c = self.load_default_context()
89 c.active = 'password'
89 c.active = 'password'
90 c.extern_type = c.user.extern_type
90 c.extern_type = c.user.extern_type
91
91
92 schema = user_schema.ChangePasswordSchema().bind(
92 schema = user_schema.ChangePasswordSchema().bind(
93 username=c.user.username)
93 username=c.user.username)
94
94
95 form = forms.Form(
95 form = forms.Form(
96 schema,
96 schema,
97 action=h.route_path('my_account_password_update'),
97 action=h.route_path('my_account_password_update'),
98 buttons=(forms.buttons.save, forms.buttons.reset))
98 buttons=(forms.buttons.save, forms.buttons.reset))
99
99
100 c.form = form
100 c.form = form
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 @LoginRequired()
103 @LoginRequired()
104 @NotAnonymous()
104 @NotAnonymous()
105 @CSRFRequired()
105 @CSRFRequired()
106 @view_config(
106 @view_config(
107 route_name='my_account_password_update', request_method='POST',
107 route_name='my_account_password_update', request_method='POST',
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 def my_account_password_update(self):
109 def my_account_password_update(self):
110 _ = self.request.translate
110 _ = self.request.translate
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'password'
112 c.active = 'password'
113 c.extern_type = c.user.extern_type
113 c.extern_type = c.user.extern_type
114
114
115 schema = user_schema.ChangePasswordSchema().bind(
115 schema = user_schema.ChangePasswordSchema().bind(
116 username=c.user.username)
116 username=c.user.username)
117
117
118 form = forms.Form(
118 form = forms.Form(
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120
120
121 if c.extern_type != 'rhodecode':
121 if c.extern_type != 'rhodecode':
122 raise HTTPFound(self.request.route_path('my_account_password'))
122 raise HTTPFound(self.request.route_path('my_account_password'))
123
123
124 controls = self.request.POST.items()
124 controls = self.request.POST.items()
125 try:
125 try:
126 valid_data = form.validate(controls)
126 valid_data = form.validate(controls)
127 UserModel().update_user(c.user.user_id, **valid_data)
127 UserModel().update_user(c.user.user_id, **valid_data)
128 c.user.update_userdata(force_password_change=False)
128 c.user.update_userdata(force_password_change=False)
129 Session().commit()
129 Session().commit()
130 except forms.ValidationFailure as e:
130 except forms.ValidationFailure as e:
131 c.form = e
131 c.form = e
132 return self._get_template_context(c)
132 return self._get_template_context(c)
133
133
134 except Exception:
134 except Exception:
135 log.exception("Exception updating password")
135 log.exception("Exception updating password")
136 h.flash(_('Error occurred during update of user password'),
136 h.flash(_('Error occurred during update of user password'),
137 category='error')
137 category='error')
138 else:
138 else:
139 instance = c.auth_user.get_instance()
139 instance = c.auth_user.get_instance()
140 self.session.setdefault('rhodecode_user', {}).update(
140 self.session.setdefault('rhodecode_user', {}).update(
141 {'password': md5(instance.password)})
141 {'password': md5(instance.password)})
142 self.session.save()
142 self.session.save()
143 h.flash(_("Successfully updated password"), category='success')
143 h.flash(_("Successfully updated password"), category='success')
144
144
145 raise HTTPFound(self.request.route_path('my_account_password'))
145 raise HTTPFound(self.request.route_path('my_account_password'))
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @NotAnonymous()
148 @NotAnonymous()
149 @view_config(
149 @view_config(
150 route_name='my_account_auth_tokens', request_method='GET',
150 route_name='my_account_auth_tokens', request_method='GET',
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 def my_account_auth_tokens(self):
152 def my_account_auth_tokens(self):
153 _ = self.request.translate
153 _ = self.request.translate
154
154
155 c = self.load_default_context()
155 c = self.load_default_context()
156 c.active = 'auth_tokens'
156 c.active = 'auth_tokens'
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 c.role_values = [
158 c.role_values = [
159 (x, AuthTokenModel.cls._get_role_name(x))
159 (x, AuthTokenModel.cls._get_role_name(x))
160 for x in AuthTokenModel.cls.ROLES]
160 for x in AuthTokenModel.cls.ROLES]
161 c.role_options = [(c.role_values, _("Role"))]
161 c.role_options = [(c.role_values, _("Role"))]
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 c.user.user_id, show_expired=True)
163 c.user.user_id, show_expired=True)
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 return self._get_template_context(c)
165 return self._get_template_context(c)
166
166
167 def maybe_attach_token_scope(self, token):
167 def maybe_attach_token_scope(self, token):
168 # implemented in EE edition
168 # implemented in EE edition
169 pass
169 pass
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @NotAnonymous()
172 @NotAnonymous()
173 @CSRFRequired()
173 @CSRFRequired()
174 @view_config(
174 @view_config(
175 route_name='my_account_auth_tokens_add', request_method='POST',)
175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 def my_account_auth_tokens_add(self):
176 def my_account_auth_tokens_add(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179
179
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 description = self.request.POST.get('description')
181 description = self.request.POST.get('description')
182 role = self.request.POST.get('role')
182 role = self.request.POST.get('role')
183
183
184 token = UserModel().add_auth_token(
184 token = UserModel().add_auth_token(
185 user=c.user.user_id,
185 user=c.user.user_id,
186 lifetime_minutes=lifetime, role=role, description=description,
186 lifetime_minutes=lifetime, role=role, description=description,
187 scope_callback=self.maybe_attach_token_scope)
187 scope_callback=self.maybe_attach_token_scope)
188 token_data = token.get_api_data()
188 token_data = token.get_api_data()
189
189
190 audit_logger.store_web(
190 audit_logger.store_web(
191 'user.edit.token.add', action_data={
191 'user.edit.token.add', action_data={
192 'data': {'token': token_data, 'user': 'self'}},
192 'data': {'token': token_data, 'user': 'self'}},
193 user=self._rhodecode_user, )
193 user=self._rhodecode_user, )
194 Session().commit()
194 Session().commit()
195
195
196 h.flash(_("Auth token successfully created"), category='success')
196 h.flash(_("Auth token successfully created"), category='success')
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @NotAnonymous()
200 @NotAnonymous()
201 @CSRFRequired()
201 @CSRFRequired()
202 @view_config(
202 @view_config(
203 route_name='my_account_auth_tokens_delete', request_method='POST')
203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 def my_account_auth_tokens_delete(self):
204 def my_account_auth_tokens_delete(self):
205 _ = self.request.translate
205 _ = self.request.translate
206 c = self.load_default_context()
206 c = self.load_default_context()
207
207
208 del_auth_token = self.request.POST.get('del_auth_token')
208 del_auth_token = self.request.POST.get('del_auth_token')
209
209
210 if del_auth_token:
210 if del_auth_token:
211 token = UserApiKeys.get_or_404(del_auth_token)
211 token = UserApiKeys.get_or_404(del_auth_token)
212 token_data = token.get_api_data()
212 token_data = token.get_api_data()
213
213
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.edit.token.delete', action_data={
216 'user.edit.token.delete', action_data={
217 'data': {'token': token_data, 'user': 'self'}},
217 'data': {'token': token_data, 'user': 'self'}},
218 user=self._rhodecode_user,)
218 user=self._rhodecode_user,)
219 Session().commit()
219 Session().commit()
220 h.flash(_("Auth token successfully deleted"), category='success')
220 h.flash(_("Auth token successfully deleted"), category='success')
221
221
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223
223
224 @LoginRequired()
224 @LoginRequired()
225 @NotAnonymous()
225 @NotAnonymous()
226 @view_config(
226 @view_config(
227 route_name='my_account_emails', request_method='GET',
227 route_name='my_account_emails', request_method='GET',
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 def my_account_emails(self):
229 def my_account_emails(self):
230 _ = self.request.translate
230 _ = self.request.translate
231
231
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'emails'
233 c.active = 'emails'
234
234
235 c.user_email_map = UserEmailMap.query()\
235 c.user_email_map = UserEmailMap.query()\
236 .filter(UserEmailMap.user == c.user).all()
236 .filter(UserEmailMap.user == c.user).all()
237
237
238 schema = user_schema.AddEmailSchema().bind(
238 schema = user_schema.AddEmailSchema().bind(
239 username=c.user.username, user_emails=c.user.emails)
239 username=c.user.username, user_emails=c.user.emails)
240
240
241 form = forms.RcForm(schema,
241 form = forms.RcForm(schema,
242 action=h.route_path('my_account_emails_add'),
242 action=h.route_path('my_account_emails_add'),
243 buttons=(forms.buttons.save, forms.buttons.reset))
243 buttons=(forms.buttons.save, forms.buttons.reset))
244
244
245 c.form = form
245 c.form = form
246 return self._get_template_context(c)
246 return self._get_template_context(c)
247
247
248 @LoginRequired()
248 @LoginRequired()
249 @NotAnonymous()
249 @NotAnonymous()
250 @CSRFRequired()
250 @CSRFRequired()
251 @view_config(
251 @view_config(
252 route_name='my_account_emails_add', request_method='POST',
252 route_name='my_account_emails_add', request_method='POST',
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 def my_account_emails_add(self):
254 def my_account_emails_add(self):
255 _ = self.request.translate
255 _ = self.request.translate
256 c = self.load_default_context()
256 c = self.load_default_context()
257 c.active = 'emails'
257 c.active = 'emails'
258
258
259 schema = user_schema.AddEmailSchema().bind(
259 schema = user_schema.AddEmailSchema().bind(
260 username=c.user.username, user_emails=c.user.emails)
260 username=c.user.username, user_emails=c.user.emails)
261
261
262 form = forms.RcForm(
262 form = forms.RcForm(
263 schema, action=h.route_path('my_account_emails_add'),
263 schema, action=h.route_path('my_account_emails_add'),
264 buttons=(forms.buttons.save, forms.buttons.reset))
264 buttons=(forms.buttons.save, forms.buttons.reset))
265
265
266 controls = self.request.POST.items()
266 controls = self.request.POST.items()
267 try:
267 try:
268 valid_data = form.validate(controls)
268 valid_data = form.validate(controls)
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 audit_logger.store_web(
270 audit_logger.store_web(
271 'user.edit.email.add', action_data={
271 'user.edit.email.add', action_data={
272 'data': {'email': valid_data['email'], 'user': 'self'}},
272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 user=self._rhodecode_user,)
273 user=self._rhodecode_user,)
274 Session().commit()
274 Session().commit()
275 except formencode.Invalid as error:
275 except formencode.Invalid as error:
276 h.flash(h.escape(error.error_dict['email']), category='error')
276 h.flash(h.escape(error.error_dict['email']), category='error')
277 except forms.ValidationFailure as e:
277 except forms.ValidationFailure as e:
278 c.user_email_map = UserEmailMap.query() \
278 c.user_email_map = UserEmailMap.query() \
279 .filter(UserEmailMap.user == c.user).all()
279 .filter(UserEmailMap.user == c.user).all()
280 c.form = e
280 c.form = e
281 return self._get_template_context(c)
281 return self._get_template_context(c)
282 except Exception:
282 except Exception:
283 log.exception("Exception adding email")
283 log.exception("Exception adding email")
284 h.flash(_('Error occurred during adding email'),
284 h.flash(_('Error occurred during adding email'),
285 category='error')
285 category='error')
286 else:
286 else:
287 h.flash(_("Successfully added email"), category='success')
287 h.flash(_("Successfully added email"), category='success')
288
288
289 raise HTTPFound(self.request.route_path('my_account_emails'))
289 raise HTTPFound(self.request.route_path('my_account_emails'))
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @NotAnonymous()
292 @NotAnonymous()
293 @CSRFRequired()
293 @CSRFRequired()
294 @view_config(
294 @view_config(
295 route_name='my_account_emails_delete', request_method='POST')
295 route_name='my_account_emails_delete', request_method='POST')
296 def my_account_emails_delete(self):
296 def my_account_emails_delete(self):
297 _ = self.request.translate
297 _ = self.request.translate
298 c = self.load_default_context()
298 c = self.load_default_context()
299
299
300 del_email_id = self.request.POST.get('del_email_id')
300 del_email_id = self.request.POST.get('del_email_id')
301 if del_email_id:
301 if del_email_id:
302 email = UserEmailMap.get_or_404(del_email_id).email
302 email = UserEmailMap.get_or_404(del_email_id).email
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 audit_logger.store_web(
304 audit_logger.store_web(
305 'user.edit.email.delete', action_data={
305 'user.edit.email.delete', action_data={
306 'data': {'email': email, 'user': 'self'}},
306 'data': {'email': email, 'user': 'self'}},
307 user=self._rhodecode_user,)
307 user=self._rhodecode_user,)
308 Session().commit()
308 Session().commit()
309 h.flash(_("Email successfully deleted"),
309 h.flash(_("Email successfully deleted"),
310 category='success')
310 category='success')
311 return HTTPFound(h.route_path('my_account_emails'))
311 return HTTPFound(h.route_path('my_account_emails'))
312
312
313 @LoginRequired()
313 @LoginRequired()
314 @NotAnonymous()
314 @NotAnonymous()
315 @CSRFRequired()
315 @CSRFRequired()
316 @view_config(
316 @view_config(
317 route_name='my_account_notifications_test_channelstream',
317 route_name='my_account_notifications_test_channelstream',
318 request_method='POST', renderer='json_ext')
318 request_method='POST', renderer='json_ext')
319 def my_account_notifications_test_channelstream(self):
319 def my_account_notifications_test_channelstream(self):
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 self._rhodecode_user.username, datetime.datetime.now())
321 self._rhodecode_user.username, datetime.datetime.now())
322 payload = {
322 payload = {
323 # 'channel': 'broadcast',
323 # 'channel': 'broadcast',
324 'type': 'message',
324 'type': 'message',
325 'timestamp': datetime.datetime.utcnow(),
325 'timestamp': datetime.datetime.utcnow(),
326 'user': 'system',
326 'user': 'system',
327 'pm_users': [self._rhodecode_user.username],
327 'pm_users': [self._rhodecode_user.username],
328 'message': {
328 'message': {
329 'message': message,
329 'message': message,
330 'level': 'info',
330 'level': 'info',
331 'topic': '/notifications'
331 'topic': '/notifications'
332 }
332 }
333 }
333 }
334
334
335 registry = self.request.registry
335 registry = self.request.registry
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338
338
339 try:
339 try:
340 channelstream_request(channelstream_config, [payload], '/message')
340 channelstream_request(channelstream_config, [payload], '/message')
341 except ChannelstreamException as e:
341 except ChannelstreamException as e:
342 log.exception('Failed to send channelstream data')
342 log.exception('Failed to send channelstream data')
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 return {"response": 'Channelstream data sent. '
344 return {"response": 'Channelstream data sent. '
345 'You should see a new live message now.'}
345 'You should see a new live message now.'}
346
346
347 def _load_my_repos_data(self, watched=False):
347 def _load_my_repos_data(self, watched=False):
348 if watched:
348 if watched:
349 admin = False
349 admin = False
350 follows_repos = Session().query(UserFollowing)\
350 follows_repos = Session().query(UserFollowing)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 .options(joinedload(UserFollowing.follows_repository))\
352 .options(joinedload(UserFollowing.follows_repository))\
353 .all()
353 .all()
354 repo_list = [x.follows_repository for x in follows_repos]
354 repo_list = [x.follows_repository for x in follows_repos]
355 else:
355 else:
356 admin = True
356 admin = True
357 repo_list = Repository.get_all_repos(
357 repo_list = Repository.get_all_repos(
358 user_id=self._rhodecode_user.user_id)
358 user_id=self._rhodecode_user.user_id)
359 repo_list = RepoList(repo_list, perm_set=[
359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'])
360 'repository.read', 'repository.write', 'repository.admin'])
361
361
362 repos_data = RepoModel().get_repos_as_dict(
362 repos_data = RepoModel().get_repos_as_dict(
363 repo_list=repo_list, admin=admin, short_name=False)
363 repo_list=repo_list, admin=admin, short_name=False)
364 # json used to render the grid
364 # json used to render the grid
365 return json.dumps(repos_data)
365 return json.dumps(repos_data)
366
366
367 @LoginRequired()
367 @LoginRequired()
368 @NotAnonymous()
368 @NotAnonymous()
369 @view_config(
369 @view_config(
370 route_name='my_account_repos', request_method='GET',
370 route_name='my_account_repos', request_method='GET',
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 def my_account_repos(self):
372 def my_account_repos(self):
373 c = self.load_default_context()
373 c = self.load_default_context()
374 c.active = 'repos'
374 c.active = 'repos'
375
375
376 # json used to render the grid
376 # json used to render the grid
377 c.data = self._load_my_repos_data()
377 c.data = self._load_my_repos_data()
378 return self._get_template_context(c)
378 return self._get_template_context(c)
379
379
380 @LoginRequired()
380 @LoginRequired()
381 @NotAnonymous()
381 @NotAnonymous()
382 @view_config(
382 @view_config(
383 route_name='my_account_watched', request_method='GET',
383 route_name='my_account_watched', request_method='GET',
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 def my_account_watched(self):
385 def my_account_watched(self):
386 c = self.load_default_context()
386 c = self.load_default_context()
387 c.active = 'watched'
387 c.active = 'watched'
388
388
389 # json used to render the grid
389 # json used to render the grid
390 c.data = self._load_my_repos_data(watched=True)
390 c.data = self._load_my_repos_data(watched=True)
391 return self._get_template_context(c)
391 return self._get_template_context(c)
392
392
393 @LoginRequired()
393 @LoginRequired()
394 @NotAnonymous()
394 @NotAnonymous()
395 @view_config(
395 @view_config(
396 route_name='my_account_bookmarks', request_method='GET',
396 route_name='my_account_bookmarks', request_method='GET',
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 def my_account_bookmarks(self):
398 def my_account_bookmarks(self):
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.active = 'bookmarks'
400 c.active = 'bookmarks'
401 return self._get_template_context(c)
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 position = safe_int(entry.get('position'))
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 return
407 return
407
408
408 # check if this is an existing entry
409 # check if this is an existing entry
409 is_new = False
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 if db_entry and str2bool(entry.get('remove')):
413 if db_entry and str2bool(entry.get('remove')):
413 log.debug('Marked bookmark %s for deletion', db_entry)
414 log.debug('Marked bookmark %s for deletion', db_entry)
414 Session().delete(db_entry)
415 Session().delete(db_entry)
415 return
416 return
416
417
417 if not db_entry:
418 if not db_entry:
418 # new
419 # new
419 db_entry = UserBookmark()
420 db_entry = UserBookmark()
420 is_new = True
421 is_new = True
421
422
422 should_save = False
423 should_save = False
423 default_redirect_url = ''
424 default_redirect_url = ''
424
425
425 # save repo
426 # save repo
426 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
427 repo = Repository.get(entry['bookmark_repo'])
428 repo = Repository.get(entry['bookmark_repo'])
428 perm_check = HasRepoPermissionAny(
429 perm_check = HasRepoPermissionAny(
429 'repository.read', 'repository.write', 'repository.admin')
430 'repository.read', 'repository.write', 'repository.admin')
430 if repo and perm_check(repo_name=repo.repo_name):
431 if repo and perm_check(repo_name=repo.repo_name):
431 db_entry.repository = repo
432 db_entry.repository = repo
432 should_save = True
433 should_save = True
433 default_redirect_url = '${repo_url}'
434 default_redirect_url = '${repo_url}'
434 # save repo group
435 # save repo group
435 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
436 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 perm_check = HasRepoGroupPermissionAny(
438 perm_check = HasRepoGroupPermissionAny(
438 'group.read', 'group.write', 'group.admin')
439 'group.read', 'group.write', 'group.admin')
439
440
440 if repo_group and perm_check(group_name=repo_group.group_name):
441 if repo_group and perm_check(group_name=repo_group.group_name):
441 db_entry.repository_group = repo_group
442 db_entry.repository_group = repo_group
442 should_save = True
443 should_save = True
443 default_redirect_url = '${repo_group_url}'
444 default_redirect_url = '${repo_group_url}'
444 # save generic info
445 # save generic info
445 elif entry.get('title') and entry.get('redirect_url'):
446 elif entry.get('title') and entry.get('redirect_url'):
446 should_save = True
447 should_save = True
447
448
448 if should_save:
449 if should_save:
449 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
450 # mark user and position
450 # mark user and position
451 db_entry.user_id = user_id
451 db_entry.user_id = user_id
452 db_entry.position = position
452 db_entry.position = position
453 db_entry.title = entry.get('title')
453 db_entry.title = entry.get('title')
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
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 Session().add(db_entry)
457 Session().add(db_entry)
457
458
458 @LoginRequired()
459 @LoginRequired()
459 @NotAnonymous()
460 @NotAnonymous()
460 @CSRFRequired()
461 @CSRFRequired()
461 @view_config(
462 @view_config(
462 route_name='my_account_bookmarks_update', request_method='POST')
463 route_name='my_account_bookmarks_update', request_method='POST')
463 def my_account_bookmarks_update(self):
464 def my_account_bookmarks_update(self):
464 _ = self.request.translate
465 _ = self.request.translate
465 c = self.load_default_context()
466 c = self.load_default_context()
466 c.active = 'bookmarks'
467 c.active = 'bookmarks'
467
468
468 controls = peppercorn.parse(self.request.POST.items())
469 controls = peppercorn.parse(self.request.POST.items())
469 user_id = c.user.user_id
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 try:
488 try:
472 for entry in controls.get('bookmarks', []):
489 for entry in positions.values():
473 self._process_entry(entry, user_id)
490 self._process_bookmark_entry(entry, user_id)
474
491
475 Session().commit()
492 Session().commit()
476 h.flash(_("Update Bookmarks"), category='success')
493 h.flash(_("Update Bookmarks"), category='success')
477 except IntegrityError:
494 except IntegrityError:
478 h.flash(_("Failed to update bookmarks. "
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 return HTTPFound(h.route_path('my_account_bookmarks'))
498 return HTTPFound(h.route_path('my_account_bookmarks'))
482
499
483 @LoginRequired()
500 @LoginRequired()
484 @NotAnonymous()
501 @NotAnonymous()
485 @view_config(
502 @view_config(
486 route_name='my_account_goto_bookmark', request_method='GET',
503 route_name='my_account_goto_bookmark', request_method='GET',
487 renderer='rhodecode:templates/admin/my_account/my_account.mako')
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
488 def my_account_goto_bookmark(self):
505 def my_account_goto_bookmark(self):
489
506
490 bookmark_id = self.request.matchdict['bookmark_id']
507 bookmark_id = self.request.matchdict['bookmark_id']
491 user_bookmark = UserBookmark().query()\
508 user_bookmark = UserBookmark().query()\
492 .filter(UserBookmark.user_id == self.request.user.user_id) \
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
493 .filter(UserBookmark.position == bookmark_id).scalar()
510 .filter(UserBookmark.position == bookmark_id).scalar()
494
511
495 redirect_url = h.route_path('my_account_bookmarks')
512 redirect_url = h.route_path('my_account_bookmarks')
496 if not user_bookmark:
513 if not user_bookmark:
497 raise HTTPFound(redirect_url)
514 raise HTTPFound(redirect_url)
498
515
499 # repository set
516 # repository set
500 if user_bookmark.repository:
517 if user_bookmark.repository:
501 repo_name = user_bookmark.repository.repo_name
518 repo_name = user_bookmark.repository.repo_name
502 base_redirect_url = h.route_path(
519 base_redirect_url = h.route_path(
503 'repo_summary', repo_name=repo_name)
520 'repo_summary', repo_name=repo_name)
504 if user_bookmark.redirect_url and \
521 if user_bookmark.redirect_url and \
505 '${repo_url}' in user_bookmark.redirect_url:
522 '${repo_url}' in user_bookmark.redirect_url:
506 redirect_url = string.Template(user_bookmark.redirect_url)\
523 redirect_url = string.Template(user_bookmark.redirect_url)\
507 .safe_substitute({'repo_url': base_redirect_url})
524 .safe_substitute({'repo_url': base_redirect_url})
508 else:
525 else:
509 redirect_url = base_redirect_url
526 redirect_url = base_redirect_url
510 # repository group set
527 # repository group set
511 elif user_bookmark.repository_group:
528 elif user_bookmark.repository_group:
512 repo_group_name = user_bookmark.repository_group.group_name
529 repo_group_name = user_bookmark.repository_group.group_name
513 base_redirect_url = h.route_path(
530 base_redirect_url = h.route_path(
514 'repo_group_home', repo_group_name=repo_group_name)
531 'repo_group_home', repo_group_name=repo_group_name)
515 if user_bookmark.redirect_url and \
532 if user_bookmark.redirect_url and \
516 '${repo_group_url}' in user_bookmark.redirect_url:
533 '${repo_group_url}' in user_bookmark.redirect_url:
517 redirect_url = string.Template(user_bookmark.redirect_url)\
534 redirect_url = string.Template(user_bookmark.redirect_url)\
518 .safe_substitute({'repo_group_url': base_redirect_url})
535 .safe_substitute({'repo_group_url': base_redirect_url})
519 else:
536 else:
520 redirect_url = base_redirect_url
537 redirect_url = base_redirect_url
521 # custom URL set
538 # custom URL set
522 elif user_bookmark.redirect_url:
539 elif user_bookmark.redirect_url:
523 server_url = h.route_url('home').rstrip('/')
540 server_url = h.route_url('home').rstrip('/')
524 redirect_url = string.Template(user_bookmark.redirect_url) \
541 redirect_url = string.Template(user_bookmark.redirect_url) \
525 .safe_substitute({'server_url': server_url})
542 .safe_substitute({'server_url': server_url})
526
543
527 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
528 raise HTTPFound(redirect_url)
545 raise HTTPFound(redirect_url)
529
546
530 @LoginRequired()
547 @LoginRequired()
531 @NotAnonymous()
548 @NotAnonymous()
532 @view_config(
549 @view_config(
533 route_name='my_account_perms', request_method='GET',
550 route_name='my_account_perms', request_method='GET',
534 renderer='rhodecode:templates/admin/my_account/my_account.mako')
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
535 def my_account_perms(self):
552 def my_account_perms(self):
536 c = self.load_default_context()
553 c = self.load_default_context()
537 c.active = 'perms'
554 c.active = 'perms'
538
555
539 c.perm_user = c.auth_user
556 c.perm_user = c.auth_user
540 return self._get_template_context(c)
557 return self._get_template_context(c)
541
558
542 @LoginRequired()
559 @LoginRequired()
543 @NotAnonymous()
560 @NotAnonymous()
544 @view_config(
561 @view_config(
545 route_name='my_account_notifications', request_method='GET',
562 route_name='my_account_notifications', request_method='GET',
546 renderer='rhodecode:templates/admin/my_account/my_account.mako')
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
547 def my_notifications(self):
564 def my_notifications(self):
548 c = self.load_default_context()
565 c = self.load_default_context()
549 c.active = 'notifications'
566 c.active = 'notifications'
550
567
551 return self._get_template_context(c)
568 return self._get_template_context(c)
552
569
553 @LoginRequired()
570 @LoginRequired()
554 @NotAnonymous()
571 @NotAnonymous()
555 @CSRFRequired()
572 @CSRFRequired()
556 @view_config(
573 @view_config(
557 route_name='my_account_notifications_toggle_visibility',
574 route_name='my_account_notifications_toggle_visibility',
558 request_method='POST', renderer='json_ext')
575 request_method='POST', renderer='json_ext')
559 def my_notifications_toggle_visibility(self):
576 def my_notifications_toggle_visibility(self):
560 user = self._rhodecode_db_user
577 user = self._rhodecode_db_user
561 new_status = not user.user_data.get('notification_status', True)
578 new_status = not user.user_data.get('notification_status', True)
562 user.update_userdata(notification_status=new_status)
579 user.update_userdata(notification_status=new_status)
563 Session().commit()
580 Session().commit()
564 return user.user_data['notification_status']
581 return user.user_data['notification_status']
565
582
566 @LoginRequired()
583 @LoginRequired()
567 @NotAnonymous()
584 @NotAnonymous()
568 @view_config(
585 @view_config(
569 route_name='my_account_edit',
586 route_name='my_account_edit',
570 request_method='GET',
587 request_method='GET',
571 renderer='rhodecode:templates/admin/my_account/my_account.mako')
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
572 def my_account_edit(self):
589 def my_account_edit(self):
573 c = self.load_default_context()
590 c = self.load_default_context()
574 c.active = 'profile_edit'
591 c.active = 'profile_edit'
575 c.extern_type = c.user.extern_type
592 c.extern_type = c.user.extern_type
576 c.extern_name = c.user.extern_name
593 c.extern_name = c.user.extern_name
577
594
578 schema = user_schema.UserProfileSchema().bind(
595 schema = user_schema.UserProfileSchema().bind(
579 username=c.user.username, user_emails=c.user.emails)
596 username=c.user.username, user_emails=c.user.emails)
580 appstruct = {
597 appstruct = {
581 'username': c.user.username,
598 'username': c.user.username,
582 'email': c.user.email,
599 'email': c.user.email,
583 'firstname': c.user.firstname,
600 'firstname': c.user.firstname,
584 'lastname': c.user.lastname,
601 'lastname': c.user.lastname,
585 }
602 }
586 c.form = forms.RcForm(
603 c.form = forms.RcForm(
587 schema, appstruct=appstruct,
604 schema, appstruct=appstruct,
588 action=h.route_path('my_account_update'),
605 action=h.route_path('my_account_update'),
589 buttons=(forms.buttons.save, forms.buttons.reset))
606 buttons=(forms.buttons.save, forms.buttons.reset))
590
607
591 return self._get_template_context(c)
608 return self._get_template_context(c)
592
609
593 @LoginRequired()
610 @LoginRequired()
594 @NotAnonymous()
611 @NotAnonymous()
595 @CSRFRequired()
612 @CSRFRequired()
596 @view_config(
613 @view_config(
597 route_name='my_account_update',
614 route_name='my_account_update',
598 request_method='POST',
615 request_method='POST',
599 renderer='rhodecode:templates/admin/my_account/my_account.mako')
616 renderer='rhodecode:templates/admin/my_account/my_account.mako')
600 def my_account_update(self):
617 def my_account_update(self):
601 _ = self.request.translate
618 _ = self.request.translate
602 c = self.load_default_context()
619 c = self.load_default_context()
603 c.active = 'profile_edit'
620 c.active = 'profile_edit'
604 c.perm_user = c.auth_user
621 c.perm_user = c.auth_user
605 c.extern_type = c.user.extern_type
622 c.extern_type = c.user.extern_type
606 c.extern_name = c.user.extern_name
623 c.extern_name = c.user.extern_name
607
624
608 schema = user_schema.UserProfileSchema().bind(
625 schema = user_schema.UserProfileSchema().bind(
609 username=c.user.username, user_emails=c.user.emails)
626 username=c.user.username, user_emails=c.user.emails)
610 form = forms.RcForm(
627 form = forms.RcForm(
611 schema, buttons=(forms.buttons.save, forms.buttons.reset))
628 schema, buttons=(forms.buttons.save, forms.buttons.reset))
612
629
613 controls = self.request.POST.items()
630 controls = self.request.POST.items()
614 try:
631 try:
615 valid_data = form.validate(controls)
632 valid_data = form.validate(controls)
616 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
633 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
617 'new_password', 'password_confirmation']
634 'new_password', 'password_confirmation']
618 if c.extern_type != "rhodecode":
635 if c.extern_type != "rhodecode":
619 # forbid updating username for external accounts
636 # forbid updating username for external accounts
620 skip_attrs.append('username')
637 skip_attrs.append('username')
621 old_email = c.user.email
638 old_email = c.user.email
622 UserModel().update_user(
639 UserModel().update_user(
623 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
640 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
624 **valid_data)
641 **valid_data)
625 if old_email != valid_data['email']:
642 if old_email != valid_data['email']:
626 old = UserEmailMap.query() \
643 old = UserEmailMap.query() \
627 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
644 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
628 old.email = old_email
645 old.email = old_email
629 h.flash(_('Your account was updated successfully'), category='success')
646 h.flash(_('Your account was updated successfully'), category='success')
630 Session().commit()
647 Session().commit()
631 except forms.ValidationFailure as e:
648 except forms.ValidationFailure as e:
632 c.form = e
649 c.form = e
633 return self._get_template_context(c)
650 return self._get_template_context(c)
634 except Exception:
651 except Exception:
635 log.exception("Exception updating user")
652 log.exception("Exception updating user")
636 h.flash(_('Error occurred during update of user'),
653 h.flash(_('Error occurred during update of user'),
637 category='error')
654 category='error')
638 raise HTTPFound(h.route_path('my_account_profile'))
655 raise HTTPFound(h.route_path('my_account_profile'))
639
656
640 def _get_pull_requests_list(self, statuses):
657 def _get_pull_requests_list(self, statuses):
641 draw, start, limit = self._extract_chunk(self.request)
658 draw, start, limit = self._extract_chunk(self.request)
642 search_q, order_by, order_dir = self._extract_ordering(self.request)
659 search_q, order_by, order_dir = self._extract_ordering(self.request)
643 _render = self.request.get_partial_renderer(
660 _render = self.request.get_partial_renderer(
644 'rhodecode:templates/data_table/_dt_elements.mako')
661 'rhodecode:templates/data_table/_dt_elements.mako')
645
662
646 pull_requests = PullRequestModel().get_im_participating_in(
663 pull_requests = PullRequestModel().get_im_participating_in(
647 user_id=self._rhodecode_user.user_id,
664 user_id=self._rhodecode_user.user_id,
648 statuses=statuses,
665 statuses=statuses,
649 offset=start, length=limit, order_by=order_by,
666 offset=start, length=limit, order_by=order_by,
650 order_dir=order_dir)
667 order_dir=order_dir)
651
668
652 pull_requests_total_count = PullRequestModel().count_im_participating_in(
669 pull_requests_total_count = PullRequestModel().count_im_participating_in(
653 user_id=self._rhodecode_user.user_id, statuses=statuses)
670 user_id=self._rhodecode_user.user_id, statuses=statuses)
654
671
655 data = []
672 data = []
656 comments_model = CommentsModel()
673 comments_model = CommentsModel()
657 for pr in pull_requests:
674 for pr in pull_requests:
658 repo_id = pr.target_repo_id
675 repo_id = pr.target_repo_id
659 comments = comments_model.get_all_comments(
676 comments = comments_model.get_all_comments(
660 repo_id, pull_request=pr)
677 repo_id, pull_request=pr)
661 owned = pr.user_id == self._rhodecode_user.user_id
678 owned = pr.user_id == self._rhodecode_user.user_id
662
679
663 data.append({
680 data.append({
664 'target_repo': _render('pullrequest_target_repo',
681 'target_repo': _render('pullrequest_target_repo',
665 pr.target_repo.repo_name),
682 pr.target_repo.repo_name),
666 'name': _render('pullrequest_name',
683 'name': _render('pullrequest_name',
667 pr.pull_request_id, pr.target_repo.repo_name,
684 pr.pull_request_id, pr.target_repo.repo_name,
668 short=True),
685 short=True),
669 'name_raw': pr.pull_request_id,
686 'name_raw': pr.pull_request_id,
670 'status': _render('pullrequest_status',
687 'status': _render('pullrequest_status',
671 pr.calculated_review_status()),
688 pr.calculated_review_status()),
672 'title': _render('pullrequest_title', pr.title, pr.description),
689 'title': _render('pullrequest_title', pr.title, pr.description),
673 'description': h.escape(pr.description),
690 'description': h.escape(pr.description),
674 'updated_on': _render('pullrequest_updated_on',
691 'updated_on': _render('pullrequest_updated_on',
675 h.datetime_to_time(pr.updated_on)),
692 h.datetime_to_time(pr.updated_on)),
676 'updated_on_raw': h.datetime_to_time(pr.updated_on),
693 'updated_on_raw': h.datetime_to_time(pr.updated_on),
677 'created_on': _render('pullrequest_updated_on',
694 'created_on': _render('pullrequest_updated_on',
678 h.datetime_to_time(pr.created_on)),
695 h.datetime_to_time(pr.created_on)),
679 'created_on_raw': h.datetime_to_time(pr.created_on),
696 'created_on_raw': h.datetime_to_time(pr.created_on),
680 'state': pr.pull_request_state,
697 'state': pr.pull_request_state,
681 'author': _render('pullrequest_author',
698 'author': _render('pullrequest_author',
682 pr.author.full_contact, ),
699 pr.author.full_contact, ),
683 'author_raw': pr.author.full_name,
700 'author_raw': pr.author.full_name,
684 'comments': _render('pullrequest_comments', len(comments)),
701 'comments': _render('pullrequest_comments', len(comments)),
685 'comments_raw': len(comments),
702 'comments_raw': len(comments),
686 'closed': pr.is_closed(),
703 'closed': pr.is_closed(),
687 'owned': owned
704 'owned': owned
688 })
705 })
689
706
690 # json used to render the grid
707 # json used to render the grid
691 data = ({
708 data = ({
692 'draw': draw,
709 'draw': draw,
693 'data': data,
710 'data': data,
694 'recordsTotal': pull_requests_total_count,
711 'recordsTotal': pull_requests_total_count,
695 'recordsFiltered': pull_requests_total_count,
712 'recordsFiltered': pull_requests_total_count,
696 })
713 })
697 return data
714 return data
698
715
699 @LoginRequired()
716 @LoginRequired()
700 @NotAnonymous()
717 @NotAnonymous()
701 @view_config(
718 @view_config(
702 route_name='my_account_pullrequests',
719 route_name='my_account_pullrequests',
703 request_method='GET',
720 request_method='GET',
704 renderer='rhodecode:templates/admin/my_account/my_account.mako')
721 renderer='rhodecode:templates/admin/my_account/my_account.mako')
705 def my_account_pullrequests(self):
722 def my_account_pullrequests(self):
706 c = self.load_default_context()
723 c = self.load_default_context()
707 c.active = 'pullrequests'
724 c.active = 'pullrequests'
708 req_get = self.request.GET
725 req_get = self.request.GET
709
726
710 c.closed = str2bool(req_get.get('pr_show_closed'))
727 c.closed = str2bool(req_get.get('pr_show_closed'))
711
728
712 return self._get_template_context(c)
729 return self._get_template_context(c)
713
730
714 @LoginRequired()
731 @LoginRequired()
715 @NotAnonymous()
732 @NotAnonymous()
716 @view_config(
733 @view_config(
717 route_name='my_account_pullrequests_data',
734 route_name='my_account_pullrequests_data',
718 request_method='GET', renderer='json_ext')
735 request_method='GET', renderer='json_ext')
719 def my_account_pullrequests_data(self):
736 def my_account_pullrequests_data(self):
720 self.load_default_context()
737 self.load_default_context()
721 req_get = self.request.GET
738 req_get = self.request.GET
722 closed = str2bool(req_get.get('closed'))
739 closed = str2bool(req_get.get('closed'))
723
740
724 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
741 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
725 if closed:
742 if closed:
726 statuses += [PullRequest.STATUS_CLOSED]
743 statuses += [PullRequest.STATUS_CLOSED]
727
744
728 data = self._get_pull_requests_list(statuses=statuses)
745 data = self._get_pull_requests_list(statuses=statuses)
729 return data
746 return data
730
747
731 @LoginRequired()
748 @LoginRequired()
732 @NotAnonymous()
749 @NotAnonymous()
733 @view_config(
750 @view_config(
734 route_name='my_account_user_group_membership',
751 route_name='my_account_user_group_membership',
735 request_method='GET',
752 request_method='GET',
736 renderer='rhodecode:templates/admin/my_account/my_account.mako')
753 renderer='rhodecode:templates/admin/my_account/my_account.mako')
737 def my_account_user_group_membership(self):
754 def my_account_user_group_membership(self):
738 c = self.load_default_context()
755 c = self.load_default_context()
739 c.active = 'user_group_membership'
756 c.active = 'user_group_membership'
740 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
757 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
741 for group in self._rhodecode_db_user.group_member]
758 for group in self._rhodecode_db_user.group_member]
742 c.user_groups = json.dumps(groups)
759 c.user_groups = json.dumps(groups)
743 return self._get_template_context(c)
760 return self._get_template_context(c)
1 NO CONTENT: modified file
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 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
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 <tr>
4 <tr>
5 <td class="td-align-top" >
5 <td class="td-align-top" >
6 <div class="label">
6 <div class="label">
7 <label for="position">${_('Position')}:</label>
7 <label for="position">${_('Position')}:</label>
8 </div>
8 </div>
9 <div class="input">
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 </div>
12 </div>
12 </td>
13 </td>
13
14
14 <td>
15 <td>
15 <div class="label">
16 <div class="label">
16 <label for="title">${_('Bookmark title (max 30 characters, optional)')}:</label>
17 <label for="title">${_('Bookmark title (max 30 characters, optional)')}:</label>
17 </div>
18 </div>
18 <div class="input">
19 <div class="input">
19 <input type="text" name="title" value="${title}" style="width: 300px" maxlength="30"/>
20 <input type="text" name="title" value="${title}" style="width: 300px" maxlength="30"/>
20
21
21 <div class="field pull-right">
22 <div class="field pull-right">
22 <div>
23 <div>
23 <label class="btn-link btn-danger">${_('Clear')}:</label>
24 <label class="btn-link btn-danger">${_('Clear')}:</label>
24 ${h.checkbox('remove', value=True)}
25 ${h.checkbox('remove', value=True)}
25 </div>
26 </div>
26 </div>
27 </div>
27 </div>
28 </div>
28 <p class="help-block help-block-inline" >
29 <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 ${_('Server URL is available as ${server_url} variable. E.g. Redirect url: ${server_url}/_admin/exception_tracker')}
30 </p>
31 </p>
31
32
32 <div class="label">
33 <div class="label">
33 <label for="redirect_url">${_('Redirect URL')}:</label>
34 <label for="redirect_url">${_('Redirect URL')}:</label>
34 </div>
35 </div>
35 <div class="input">
36 <div class="input">
36 <input type="text" name="redirect_url" value="${redirect_url}" style="width: 600px"/>
37 <input type="text" name="redirect_url" value="${redirect_url}" style="width: 600px"/>
37 </div>
38 </div>
38
39
39
40
40 <div class="select">
41 <div class="select">
41 % if repo:
42 % if repo:
42 <div class="label">
43 <div class="label">
43 <label for="redirect_url">${_('Repository template')}:</label>
44 <label for="redirect_url">${_('Repository template')}:</label>
44 </div>
45 </div>
45 ${dt.repo_name(name=repo.repo_name, rtype=repo.repo_type,rstate=None,private=None,archived=False,fork_of=False)}
46 ${dt.repo_name(name=repo.repo_name, rtype=repo.repo_type,rstate=None,private=None,archived=False,fork_of=False)}
46 ${h.hidden('bookmark_repo', repo.repo_id)}
47 ${h.hidden('bookmark_repo', repo.repo_id)}
47 % elif repo_group:
48 % elif repo_group:
48 <div class="label">
49 <div class="label">
49 <label for="redirect_url">${_('Repository group template')}:</label>
50 <label for="redirect_url">${_('Repository group template')}:</label>
50 </div>
51 </div>
51 ${dt.repo_group_name(repo_group.group_name)}
52 ${dt.repo_group_name(repo_group.group_name)}
52 ${h.hidden('bookmark_repo_group', repo_group.group_id)}
53 ${h.hidden('bookmark_repo_group', repo_group.group_id)}
53 % else:
54 % else:
54 <div class="label">
55 <div class="label">
55 <label for="redirect_url">${_('Template Repository or Repository group')}:</label>
56 <label for="redirect_url">${_('Template Repository or Repository group')}:</label>
56 </div>
57 </div>
57 ${h.hidden('bookmark_repo', class_='bookmark_repo')}
58 ${h.hidden('bookmark_repo', class_='bookmark_repo')}
58 <span style="padding-right:15px">OR</span>
59 <span style="padding-right:15px">OR</span>
59 ${h.hidden('bookmark_repo_group', class_='bookmark_repo_group')}
60 ${h.hidden('bookmark_repo_group', class_='bookmark_repo_group')}
60 % endif
61 % endif
61 </div>
62 </div>
62
63
63 <p class="help-block help-block-inline" >
64 <p class="help-block help-block-inline" >
64 % if repo:
65 % if repo:
65 ${_('Available as ${repo_url} e.g. Redirect url: ${repo_url}/changelog')}
66 ${_('Available as ${repo_url} e.g. Redirect url: ${repo_url}/changelog')}
66 % elif repo_group:
67 % elif repo_group:
67 ${_('Available as ${repo_group_url} e.g. Redirect url: ${repo_group_url}')}
68 ${_('Available as ${repo_group_url} e.g. Redirect url: ${repo_group_url}')}
68 % else:
69 % else:
69 ${_('Available as full url variables in redirect url. i.e: ${repo_url}, ${repo_group_url}.')}
70 ${_('Available as full url variables in redirect url. i.e: ${repo_url}, ${repo_group_url}.')}
70 % endif
71 % endif
71 </p>
72 </p>
72 </td>
73 </td>
73
74
74 </tr>
75 </tr>
75 </%def>
76 </%def>
76
77
77 <div class="panel panel-default">
78 <div class="panel panel-default">
78 <div class="panel-heading">
79 <div class="panel-heading">
79 <h3 class="panel-title">${_('Your Bookmarks')}</h3>
80 <h3 class="panel-title">${_('Your Bookmarks')}</h3>
80 </div>
81 </div>
81
82
82 <div class="panel-body">
83 <div class="panel-body">
83 <p>
84 <p>
84 ${_('Store upto 10 bookmark links to favorite repositories, external issue tracker or CI server. ')}
85 ${_('Store upto 10 bookmark links to favorite repositories, external issue tracker or CI server. ')}
85 <br/>
86 <br/>
86 ${_('Bookmarks are accessible from your username dropdown or by keyboard shortcut `g 0-9`')}
87 ${_('Bookmarks are accessible from your username dropdown or by keyboard shortcut `g 0-9`')}
87 </p>
88 </p>
88
89
89 ${h.secure_form(h.route_path('my_account_bookmarks_update'), request=request)}
90 ${h.secure_form(h.route_path('my_account_bookmarks_update'), request=request)}
90 <div class="form-vertical">
91 <div class="form-vertical">
91 <table class="rctable">
92 <table class="rctable">
92 ## generate always 10 entries
93 ## generate always 10 entries
93 <input type="hidden" name="__start__" value="bookmarks:sequence"/>
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 <input type="hidden" name="__start__" value="bookmark:mapping"/>
96 <input type="hidden" name="__start__" value="bookmark:mapping"/>
96 % if item is None:
97 % if item is None:
97 ## empty placehodlder
98 ## empty placehodlder
98 ${form_item(cnt)}
99 ${form_item()}
99 % else:
100 % else:
100 ## actual entry
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 % endif
103 % endif
103 <input type="hidden" name="__end__" value="bookmark:mapping"/>
104 <input type="hidden" name="__end__" value="bookmark:mapping"/>
104 % endfor
105 % endfor
105 <input type="hidden" name="__end__" value="bookmarks:sequence"/>
106 <input type="hidden" name="__end__" value="bookmarks:sequence"/>
106 </table>
107 </table>
107 <div class="buttons">
108 <div class="buttons">
108 ${h.submit('save',_('Save'),class_="btn")}
109 ${h.submit('save',_('Save'),class_="btn")}
109 </div>
110 </div>
110 </div>
111 </div>
111 ${h.end_form()}
112 ${h.end_form()}
112 </div>
113 </div>
113 </div>
114 </div>
114
115
115 <script>
116 <script>
116 $(document).ready(function(){
117 $(document).ready(function(){
117
118
118
119
119 var repoFilter = function (data) {
120 var repoFilter = function (data) {
120 var results = [];
121 var results = [];
121
122
122 if (!data.results[0]) {
123 if (!data.results[0]) {
123 return data
124 return data
124 }
125 }
125
126
126 $.each(data.results[0].children, function () {
127 $.each(data.results[0].children, function () {
127 // replace name to ID for submision
128 // replace name to ID for submision
128 this.id = this.repo_id;
129 this.id = this.repo_id;
129 results.push(this);
130 results.push(this);
130 });
131 });
131
132
132 data.results[0].children = results;
133 data.results[0].children = results;
133 return data;
134 return data;
134 };
135 };
135
136
136
137
137 $(".bookmark_repo").select2({
138 $(".bookmark_repo").select2({
138 cachedDataSource: {},
139 cachedDataSource: {},
139 minimumInputLength: 2,
140 minimumInputLength: 2,
140 placeholder: "${_('repository')}",
141 placeholder: "${_('repository')}",
141 dropdownAutoWidth: true,
142 dropdownAutoWidth: true,
142 containerCssClass: "drop-menu",
143 containerCssClass: "drop-menu",
143 dropdownCssClass: "drop-menu-dropdown",
144 dropdownCssClass: "drop-menu-dropdown",
144 formatResult: formatRepoResult,
145 formatResult: formatRepoResult,
145 query: $.debounce(250, function (query) {
146 query: $.debounce(250, function (query) {
146 self = this;
147 self = this;
147 var cacheKey = query.term;
148 var cacheKey = query.term;
148 var cachedData = self.cachedDataSource[cacheKey];
149 var cachedData = self.cachedDataSource[cacheKey];
149
150
150 if (cachedData) {
151 if (cachedData) {
151 query.callback({results: cachedData.results});
152 query.callback({results: cachedData.results});
152 } else {
153 } else {
153 $.ajax({
154 $.ajax({
154 url: pyroutes.url('repo_list_data'),
155 url: pyroutes.url('repo_list_data'),
155 data: {'query': query.term},
156 data: {'query': query.term},
156 dataType: 'json',
157 dataType: 'json',
157 type: 'GET',
158 type: 'GET',
158 success: function (data) {
159 success: function (data) {
159 data = repoFilter(data);
160 data = repoFilter(data);
160 self.cachedDataSource[cacheKey] = data;
161 self.cachedDataSource[cacheKey] = data;
161 query.callback({results: data.results});
162 query.callback({results: data.results});
162 },
163 },
163 error: function (data, textStatus, errorThrown) {
164 error: function (data, textStatus, errorThrown) {
164 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
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 var repoGroupFilter = function (data) {
172 var repoGroupFilter = function (data) {
172 var results = [];
173 var results = [];
173
174
174 if (!data.results[0]) {
175 if (!data.results[0]) {
175 return data
176 return data
176 }
177 }
177
178
178 $.each(data.results[0].children, function () {
179 $.each(data.results[0].children, function () {
179 // replace name to ID for submision
180 // replace name to ID for submision
180 this.id = this.repo_group_id;
181 this.id = this.repo_group_id;
181 results.push(this);
182 results.push(this);
182 });
183 });
183
184
184 data.results[0].children = results;
185 data.results[0].children = results;
185 return data;
186 return data;
186 };
187 };
187
188
188 $(".bookmark_repo_group").select2({
189 $(".bookmark_repo_group").select2({
189 cachedDataSource: {},
190 cachedDataSource: {},
190 minimumInputLength: 2,
191 minimumInputLength: 2,
191 placeholder: "${_('repository group')}",
192 placeholder: "${_('repository group')}",
192 dropdownAutoWidth: true,
193 dropdownAutoWidth: true,
193 containerCssClass: "drop-menu",
194 containerCssClass: "drop-menu",
194 dropdownCssClass: "drop-menu-dropdown",
195 dropdownCssClass: "drop-menu-dropdown",
195 formatResult: formatRepoGroupResult,
196 formatResult: formatRepoGroupResult,
196 query: $.debounce(250, function (query) {
197 query: $.debounce(250, function (query) {
197 self = this;
198 self = this;
198 var cacheKey = query.term;
199 var cacheKey = query.term;
199 var cachedData = self.cachedDataSource[cacheKey];
200 var cachedData = self.cachedDataSource[cacheKey];
200
201
201 if (cachedData) {
202 if (cachedData) {
202 query.callback({results: cachedData.results});
203 query.callback({results: cachedData.results});
203 } else {
204 } else {
204 $.ajax({
205 $.ajax({
205 url: pyroutes.url('repo_group_list_data'),
206 url: pyroutes.url('repo_group_list_data'),
206 data: {'query': query.term},
207 data: {'query': query.term},
207 dataType: 'json',
208 dataType: 'json',
208 type: 'GET',
209 type: 'GET',
209 success: function (data) {
210 success: function (data) {
210 data = repoGroupFilter(data);
211 data = repoGroupFilter(data);
211 self.cachedDataSource[cacheKey] = data;
212 self.cachedDataSource[cacheKey] = data;
212 query.callback({results: data.results});
213 query.callback({results: data.results});
213 },
214 },
214 error: function (data, textStatus, errorThrown) {
215 error: function (data, textStatus, errorThrown) {
215 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
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 </script>
226 </script>
General Comments 0
You need to be logged in to leave comments. Login now