##// END OF EJS Templates
user-accounts: show info about password for external accounts.
marcink -
r4240:86414242 stable
parent child Browse files
Show More
@@ -1,800 +1,801 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 (
36 from rhodecode.lib.auth import (
37 LoginRequired, NotAnonymous, CSRFRequired,
37 LoginRequired, NotAnonymous, CSRFRequired,
38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
39 from rhodecode.lib.channelstream import (
39 from rhodecode.lib.channelstream import (
40 channelstream_request, ChannelstreamException)
40 channelstream_request, ChannelstreamException)
41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
42 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 IntegrityError, or_, in_filter_generator,
45 IntegrityError, or_, in_filter_generator,
46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
47 PullRequest, UserBookmark, RepoGroup)
47 PullRequest, UserBookmark, RepoGroup)
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.validation_schema.schemas import user_schema
52 from rhodecode.model.validation_schema.schemas import user_schema
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class MyAccountView(BaseAppView, DataGridAppView):
57 class MyAccountView(BaseAppView, DataGridAppView):
58 ALLOW_SCOPED_TOKENS = False
58 ALLOW_SCOPED_TOKENS = False
59 """
59 """
60 This view has alternative version inside EE, if modified please take a look
60 This view has alternative version inside EE, if modified please take a look
61 in there as well.
61 in there as well.
62 """
62 """
63
63
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context()
65 c = self._get_local_tmpl_context()
66 c.user = c.auth_user.get_instance()
66 c.user = c.auth_user.get_instance()
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68
68
69 return c
69 return c
70
70
71 @LoginRequired()
71 @LoginRequired()
72 @NotAnonymous()
72 @NotAnonymous()
73 @view_config(
73 @view_config(
74 route_name='my_account_profile', request_method='GET',
74 route_name='my_account_profile', request_method='GET',
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 def my_account_profile(self):
76 def my_account_profile(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78 c.active = 'profile'
78 c.active = 'profile'
79 c.extern_type = c.user.extern_type
79 return self._get_template_context(c)
80 return self._get_template_context(c)
80
81
81 @LoginRequired()
82 @LoginRequired()
82 @NotAnonymous()
83 @NotAnonymous()
83 @view_config(
84 @view_config(
84 route_name='my_account_password', request_method='GET',
85 route_name='my_account_password', request_method='GET',
85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 def my_account_password(self):
87 def my_account_password(self):
87 c = self.load_default_context()
88 c = self.load_default_context()
88 c.active = 'password'
89 c.active = 'password'
89 c.extern_type = c.user.extern_type
90 c.extern_type = c.user.extern_type
90
91
91 schema = user_schema.ChangePasswordSchema().bind(
92 schema = user_schema.ChangePasswordSchema().bind(
92 username=c.user.username)
93 username=c.user.username)
93
94
94 form = forms.Form(
95 form = forms.Form(
95 schema,
96 schema,
96 action=h.route_path('my_account_password_update'),
97 action=h.route_path('my_account_password_update'),
97 buttons=(forms.buttons.save, forms.buttons.reset))
98 buttons=(forms.buttons.save, forms.buttons.reset))
98
99
99 c.form = form
100 c.form = form
100 return self._get_template_context(c)
101 return self._get_template_context(c)
101
102
102 @LoginRequired()
103 @LoginRequired()
103 @NotAnonymous()
104 @NotAnonymous()
104 @CSRFRequired()
105 @CSRFRequired()
105 @view_config(
106 @view_config(
106 route_name='my_account_password_update', request_method='POST',
107 route_name='my_account_password_update', request_method='POST',
107 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 def my_account_password_update(self):
109 def my_account_password_update(self):
109 _ = self.request.translate
110 _ = self.request.translate
110 c = self.load_default_context()
111 c = self.load_default_context()
111 c.active = 'password'
112 c.active = 'password'
112 c.extern_type = c.user.extern_type
113 c.extern_type = c.user.extern_type
113
114
114 schema = user_schema.ChangePasswordSchema().bind(
115 schema = user_schema.ChangePasswordSchema().bind(
115 username=c.user.username)
116 username=c.user.username)
116
117
117 form = forms.Form(
118 form = forms.Form(
118 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119
120
120 if c.extern_type != 'rhodecode':
121 if c.extern_type != 'rhodecode':
121 raise HTTPFound(self.request.route_path('my_account_password'))
122 raise HTTPFound(self.request.route_path('my_account_password'))
122
123
123 controls = self.request.POST.items()
124 controls = self.request.POST.items()
124 try:
125 try:
125 valid_data = form.validate(controls)
126 valid_data = form.validate(controls)
126 UserModel().update_user(c.user.user_id, **valid_data)
127 UserModel().update_user(c.user.user_id, **valid_data)
127 c.user.update_userdata(force_password_change=False)
128 c.user.update_userdata(force_password_change=False)
128 Session().commit()
129 Session().commit()
129 except forms.ValidationFailure as e:
130 except forms.ValidationFailure as e:
130 c.form = e
131 c.form = e
131 return self._get_template_context(c)
132 return self._get_template_context(c)
132
133
133 except Exception:
134 except Exception:
134 log.exception("Exception updating password")
135 log.exception("Exception updating password")
135 h.flash(_('Error occurred during update of user password'),
136 h.flash(_('Error occurred during update of user password'),
136 category='error')
137 category='error')
137 else:
138 else:
138 instance = c.auth_user.get_instance()
139 instance = c.auth_user.get_instance()
139 self.session.setdefault('rhodecode_user', {}).update(
140 self.session.setdefault('rhodecode_user', {}).update(
140 {'password': md5(instance.password)})
141 {'password': md5(instance.password)})
141 self.session.save()
142 self.session.save()
142 h.flash(_("Successfully updated password"), category='success')
143 h.flash(_("Successfully updated password"), category='success')
143
144
144 raise HTTPFound(self.request.route_path('my_account_password'))
145 raise HTTPFound(self.request.route_path('my_account_password'))
145
146
146 @LoginRequired()
147 @LoginRequired()
147 @NotAnonymous()
148 @NotAnonymous()
148 @view_config(
149 @view_config(
149 route_name='my_account_auth_tokens', request_method='GET',
150 route_name='my_account_auth_tokens', request_method='GET',
150 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 def my_account_auth_tokens(self):
152 def my_account_auth_tokens(self):
152 _ = self.request.translate
153 _ = self.request.translate
153
154
154 c = self.load_default_context()
155 c = self.load_default_context()
155 c.active = 'auth_tokens'
156 c.active = 'auth_tokens'
156 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.role_values = [
158 c.role_values = [
158 (x, AuthTokenModel.cls._get_role_name(x))
159 (x, AuthTokenModel.cls._get_role_name(x))
159 for x in AuthTokenModel.cls.ROLES]
160 for x in AuthTokenModel.cls.ROLES]
160 c.role_options = [(c.role_values, _("Role"))]
161 c.role_options = [(c.role_values, _("Role"))]
161 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user.user_id, show_expired=True)
163 c.user.user_id, show_expired=True)
163 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 return self._get_template_context(c)
165 return self._get_template_context(c)
165
166
166 def maybe_attach_token_scope(self, token):
167 def maybe_attach_token_scope(self, token):
167 # implemented in EE edition
168 # implemented in EE edition
168 pass
169 pass
169
170
170 @LoginRequired()
171 @LoginRequired()
171 @NotAnonymous()
172 @NotAnonymous()
172 @CSRFRequired()
173 @CSRFRequired()
173 @view_config(
174 @view_config(
174 route_name='my_account_auth_tokens_add', request_method='POST',)
175 route_name='my_account_auth_tokens_add', request_method='POST',)
175 def my_account_auth_tokens_add(self):
176 def my_account_auth_tokens_add(self):
176 _ = self.request.translate
177 _ = self.request.translate
177 c = self.load_default_context()
178 c = self.load_default_context()
178
179
179 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 description = self.request.POST.get('description')
181 description = self.request.POST.get('description')
181 role = self.request.POST.get('role')
182 role = self.request.POST.get('role')
182
183
183 token = UserModel().add_auth_token(
184 token = UserModel().add_auth_token(
184 user=c.user.user_id,
185 user=c.user.user_id,
185 lifetime_minutes=lifetime, role=role, description=description,
186 lifetime_minutes=lifetime, role=role, description=description,
186 scope_callback=self.maybe_attach_token_scope)
187 scope_callback=self.maybe_attach_token_scope)
187 token_data = token.get_api_data()
188 token_data = token.get_api_data()
188
189
189 audit_logger.store_web(
190 audit_logger.store_web(
190 'user.edit.token.add', action_data={
191 'user.edit.token.add', action_data={
191 'data': {'token': token_data, 'user': 'self'}},
192 'data': {'token': token_data, 'user': 'self'}},
192 user=self._rhodecode_user, )
193 user=self._rhodecode_user, )
193 Session().commit()
194 Session().commit()
194
195
195 h.flash(_("Auth token successfully created"), category='success')
196 h.flash(_("Auth token successfully created"), category='success')
196 return HTTPFound(h.route_path('my_account_auth_tokens'))
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
197
198
198 @LoginRequired()
199 @LoginRequired()
199 @NotAnonymous()
200 @NotAnonymous()
200 @CSRFRequired()
201 @CSRFRequired()
201 @view_config(
202 @view_config(
202 route_name='my_account_auth_tokens_delete', request_method='POST')
203 route_name='my_account_auth_tokens_delete', request_method='POST')
203 def my_account_auth_tokens_delete(self):
204 def my_account_auth_tokens_delete(self):
204 _ = self.request.translate
205 _ = self.request.translate
205 c = self.load_default_context()
206 c = self.load_default_context()
206
207
207 del_auth_token = self.request.POST.get('del_auth_token')
208 del_auth_token = self.request.POST.get('del_auth_token')
208
209
209 if del_auth_token:
210 if del_auth_token:
210 token = UserApiKeys.get_or_404(del_auth_token)
211 token = UserApiKeys.get_or_404(del_auth_token)
211 token_data = token.get_api_data()
212 token_data = token.get_api_data()
212
213
213 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 audit_logger.store_web(
215 audit_logger.store_web(
215 'user.edit.token.delete', action_data={
216 'user.edit.token.delete', action_data={
216 'data': {'token': token_data, 'user': 'self'}},
217 'data': {'token': token_data, 'user': 'self'}},
217 user=self._rhodecode_user,)
218 user=self._rhodecode_user,)
218 Session().commit()
219 Session().commit()
219 h.flash(_("Auth token successfully deleted"), category='success')
220 h.flash(_("Auth token successfully deleted"), category='success')
220
221
221 return HTTPFound(h.route_path('my_account_auth_tokens'))
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
222
223
223 @LoginRequired()
224 @LoginRequired()
224 @NotAnonymous()
225 @NotAnonymous()
225 @view_config(
226 @view_config(
226 route_name='my_account_emails', request_method='GET',
227 route_name='my_account_emails', request_method='GET',
227 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 def my_account_emails(self):
229 def my_account_emails(self):
229 _ = self.request.translate
230 _ = self.request.translate
230
231
231 c = self.load_default_context()
232 c = self.load_default_context()
232 c.active = 'emails'
233 c.active = 'emails'
233
234
234 c.user_email_map = UserEmailMap.query()\
235 c.user_email_map = UserEmailMap.query()\
235 .filter(UserEmailMap.user == c.user).all()
236 .filter(UserEmailMap.user == c.user).all()
236
237
237 schema = user_schema.AddEmailSchema().bind(
238 schema = user_schema.AddEmailSchema().bind(
238 username=c.user.username, user_emails=c.user.emails)
239 username=c.user.username, user_emails=c.user.emails)
239
240
240 form = forms.RcForm(schema,
241 form = forms.RcForm(schema,
241 action=h.route_path('my_account_emails_add'),
242 action=h.route_path('my_account_emails_add'),
242 buttons=(forms.buttons.save, forms.buttons.reset))
243 buttons=(forms.buttons.save, forms.buttons.reset))
243
244
244 c.form = form
245 c.form = form
245 return self._get_template_context(c)
246 return self._get_template_context(c)
246
247
247 @LoginRequired()
248 @LoginRequired()
248 @NotAnonymous()
249 @NotAnonymous()
249 @CSRFRequired()
250 @CSRFRequired()
250 @view_config(
251 @view_config(
251 route_name='my_account_emails_add', request_method='POST',
252 route_name='my_account_emails_add', request_method='POST',
252 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 def my_account_emails_add(self):
254 def my_account_emails_add(self):
254 _ = self.request.translate
255 _ = self.request.translate
255 c = self.load_default_context()
256 c = self.load_default_context()
256 c.active = 'emails'
257 c.active = 'emails'
257
258
258 schema = user_schema.AddEmailSchema().bind(
259 schema = user_schema.AddEmailSchema().bind(
259 username=c.user.username, user_emails=c.user.emails)
260 username=c.user.username, user_emails=c.user.emails)
260
261
261 form = forms.RcForm(
262 form = forms.RcForm(
262 schema, action=h.route_path('my_account_emails_add'),
263 schema, action=h.route_path('my_account_emails_add'),
263 buttons=(forms.buttons.save, forms.buttons.reset))
264 buttons=(forms.buttons.save, forms.buttons.reset))
264
265
265 controls = self.request.POST.items()
266 controls = self.request.POST.items()
266 try:
267 try:
267 valid_data = form.validate(controls)
268 valid_data = form.validate(controls)
268 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 audit_logger.store_web(
270 audit_logger.store_web(
270 'user.edit.email.add', action_data={
271 'user.edit.email.add', action_data={
271 'data': {'email': valid_data['email'], 'user': 'self'}},
272 'data': {'email': valid_data['email'], 'user': 'self'}},
272 user=self._rhodecode_user,)
273 user=self._rhodecode_user,)
273 Session().commit()
274 Session().commit()
274 except formencode.Invalid as error:
275 except formencode.Invalid as error:
275 h.flash(h.escape(error.error_dict['email']), category='error')
276 h.flash(h.escape(error.error_dict['email']), category='error')
276 except forms.ValidationFailure as e:
277 except forms.ValidationFailure as e:
277 c.user_email_map = UserEmailMap.query() \
278 c.user_email_map = UserEmailMap.query() \
278 .filter(UserEmailMap.user == c.user).all()
279 .filter(UserEmailMap.user == c.user).all()
279 c.form = e
280 c.form = e
280 return self._get_template_context(c)
281 return self._get_template_context(c)
281 except Exception:
282 except Exception:
282 log.exception("Exception adding email")
283 log.exception("Exception adding email")
283 h.flash(_('Error occurred during adding email'),
284 h.flash(_('Error occurred during adding email'),
284 category='error')
285 category='error')
285 else:
286 else:
286 h.flash(_("Successfully added email"), category='success')
287 h.flash(_("Successfully added email"), category='success')
287
288
288 raise HTTPFound(self.request.route_path('my_account_emails'))
289 raise HTTPFound(self.request.route_path('my_account_emails'))
289
290
290 @LoginRequired()
291 @LoginRequired()
291 @NotAnonymous()
292 @NotAnonymous()
292 @CSRFRequired()
293 @CSRFRequired()
293 @view_config(
294 @view_config(
294 route_name='my_account_emails_delete', request_method='POST')
295 route_name='my_account_emails_delete', request_method='POST')
295 def my_account_emails_delete(self):
296 def my_account_emails_delete(self):
296 _ = self.request.translate
297 _ = self.request.translate
297 c = self.load_default_context()
298 c = self.load_default_context()
298
299
299 del_email_id = self.request.POST.get('del_email_id')
300 del_email_id = self.request.POST.get('del_email_id')
300 if del_email_id:
301 if del_email_id:
301 email = UserEmailMap.get_or_404(del_email_id).email
302 email = UserEmailMap.get_or_404(del_email_id).email
302 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 audit_logger.store_web(
304 audit_logger.store_web(
304 'user.edit.email.delete', action_data={
305 'user.edit.email.delete', action_data={
305 'data': {'email': email, 'user': 'self'}},
306 'data': {'email': email, 'user': 'self'}},
306 user=self._rhodecode_user,)
307 user=self._rhodecode_user,)
307 Session().commit()
308 Session().commit()
308 h.flash(_("Email successfully deleted"),
309 h.flash(_("Email successfully deleted"),
309 category='success')
310 category='success')
310 return HTTPFound(h.route_path('my_account_emails'))
311 return HTTPFound(h.route_path('my_account_emails'))
311
312
312 @LoginRequired()
313 @LoginRequired()
313 @NotAnonymous()
314 @NotAnonymous()
314 @CSRFRequired()
315 @CSRFRequired()
315 @view_config(
316 @view_config(
316 route_name='my_account_notifications_test_channelstream',
317 route_name='my_account_notifications_test_channelstream',
317 request_method='POST', renderer='json_ext')
318 request_method='POST', renderer='json_ext')
318 def my_account_notifications_test_channelstream(self):
319 def my_account_notifications_test_channelstream(self):
319 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 self._rhodecode_user.username, datetime.datetime.now())
321 self._rhodecode_user.username, datetime.datetime.now())
321 payload = {
322 payload = {
322 # 'channel': 'broadcast',
323 # 'channel': 'broadcast',
323 'type': 'message',
324 'type': 'message',
324 'timestamp': datetime.datetime.utcnow(),
325 'timestamp': datetime.datetime.utcnow(),
325 'user': 'system',
326 'user': 'system',
326 'pm_users': [self._rhodecode_user.username],
327 'pm_users': [self._rhodecode_user.username],
327 'message': {
328 'message': {
328 'message': message,
329 'message': message,
329 'level': 'info',
330 'level': 'info',
330 'topic': '/notifications'
331 'topic': '/notifications'
331 }
332 }
332 }
333 }
333
334
334 registry = self.request.registry
335 registry = self.request.registry
335 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 channelstream_config = rhodecode_plugins.get('channelstream', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
337
338
338 try:
339 try:
339 channelstream_request(channelstream_config, [payload], '/message')
340 channelstream_request(channelstream_config, [payload], '/message')
340 except ChannelstreamException as e:
341 except ChannelstreamException as e:
341 log.exception('Failed to send channelstream data')
342 log.exception('Failed to send channelstream data')
342 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'Channelstream data sent. '
344 return {"response": 'Channelstream data sent. '
344 'You should see a new live message now.'}
345 'You should see a new live message now.'}
345
346
346 def _load_my_repos_data(self, watched=False):
347 def _load_my_repos_data(self, watched=False):
347
348
348 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
349 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
349
350
350 if watched:
351 if watched:
351 # repos user watch
352 # repos user watch
352 repo_list = Session().query(
353 repo_list = Session().query(
353 Repository
354 Repository
354 ) \
355 ) \
355 .join(
356 .join(
356 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
357 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
357 ) \
358 ) \
358 .filter(
359 .filter(
359 UserFollowing.user_id == self._rhodecode_user.user_id
360 UserFollowing.user_id == self._rhodecode_user.user_id
360 ) \
361 ) \
361 .filter(or_(
362 .filter(or_(
362 # generate multiple IN to fix limitation problems
363 # generate multiple IN to fix limitation problems
363 *in_filter_generator(Repository.repo_id, allowed_ids))
364 *in_filter_generator(Repository.repo_id, allowed_ids))
364 ) \
365 ) \
365 .order_by(Repository.repo_name) \
366 .order_by(Repository.repo_name) \
366 .all()
367 .all()
367
368
368 else:
369 else:
369 # repos user is owner of
370 # repos user is owner of
370 repo_list = Session().query(
371 repo_list = Session().query(
371 Repository
372 Repository
372 ) \
373 ) \
373 .filter(
374 .filter(
374 Repository.user_id == self._rhodecode_user.user_id
375 Repository.user_id == self._rhodecode_user.user_id
375 ) \
376 ) \
376 .filter(or_(
377 .filter(or_(
377 # generate multiple IN to fix limitation problems
378 # generate multiple IN to fix limitation problems
378 *in_filter_generator(Repository.repo_id, allowed_ids))
379 *in_filter_generator(Repository.repo_id, allowed_ids))
379 ) \
380 ) \
380 .order_by(Repository.repo_name) \
381 .order_by(Repository.repo_name) \
381 .all()
382 .all()
382
383
383 _render = self.request.get_partial_renderer(
384 _render = self.request.get_partial_renderer(
384 'rhodecode:templates/data_table/_dt_elements.mako')
385 'rhodecode:templates/data_table/_dt_elements.mako')
385
386
386 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
387 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
387 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
388 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
388 short_name=False, admin=False)
389 short_name=False, admin=False)
389
390
390 repos_data = []
391 repos_data = []
391 for repo in repo_list:
392 for repo in repo_list:
392 row = {
393 row = {
393 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
394 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
394 repo.private, repo.archived, repo.fork),
395 repo.private, repo.archived, repo.fork),
395 "name_raw": repo.repo_name.lower(),
396 "name_raw": repo.repo_name.lower(),
396 }
397 }
397
398
398 repos_data.append(row)
399 repos_data.append(row)
399
400
400 # json used to render the grid
401 # json used to render the grid
401 return json.dumps(repos_data)
402 return json.dumps(repos_data)
402
403
403 @LoginRequired()
404 @LoginRequired()
404 @NotAnonymous()
405 @NotAnonymous()
405 @view_config(
406 @view_config(
406 route_name='my_account_repos', request_method='GET',
407 route_name='my_account_repos', request_method='GET',
407 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 def my_account_repos(self):
409 def my_account_repos(self):
409 c = self.load_default_context()
410 c = self.load_default_context()
410 c.active = 'repos'
411 c.active = 'repos'
411
412
412 # json used to render the grid
413 # json used to render the grid
413 c.data = self._load_my_repos_data()
414 c.data = self._load_my_repos_data()
414 return self._get_template_context(c)
415 return self._get_template_context(c)
415
416
416 @LoginRequired()
417 @LoginRequired()
417 @NotAnonymous()
418 @NotAnonymous()
418 @view_config(
419 @view_config(
419 route_name='my_account_watched', request_method='GET',
420 route_name='my_account_watched', request_method='GET',
420 renderer='rhodecode:templates/admin/my_account/my_account.mako')
421 renderer='rhodecode:templates/admin/my_account/my_account.mako')
421 def my_account_watched(self):
422 def my_account_watched(self):
422 c = self.load_default_context()
423 c = self.load_default_context()
423 c.active = 'watched'
424 c.active = 'watched'
424
425
425 # json used to render the grid
426 # json used to render the grid
426 c.data = self._load_my_repos_data(watched=True)
427 c.data = self._load_my_repos_data(watched=True)
427 return self._get_template_context(c)
428 return self._get_template_context(c)
428
429
429 @LoginRequired()
430 @LoginRequired()
430 @NotAnonymous()
431 @NotAnonymous()
431 @view_config(
432 @view_config(
432 route_name='my_account_bookmarks', request_method='GET',
433 route_name='my_account_bookmarks', request_method='GET',
433 renderer='rhodecode:templates/admin/my_account/my_account.mako')
434 renderer='rhodecode:templates/admin/my_account/my_account.mako')
434 def my_account_bookmarks(self):
435 def my_account_bookmarks(self):
435 c = self.load_default_context()
436 c = self.load_default_context()
436 c.active = 'bookmarks'
437 c.active = 'bookmarks'
437 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
438 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
438 self._rhodecode_db_user.user_id, cache=False)
439 self._rhodecode_db_user.user_id, cache=False)
439 return self._get_template_context(c)
440 return self._get_template_context(c)
440
441
441 def _process_bookmark_entry(self, entry, user_id):
442 def _process_bookmark_entry(self, entry, user_id):
442 position = safe_int(entry.get('position'))
443 position = safe_int(entry.get('position'))
443 cur_position = safe_int(entry.get('cur_position'))
444 cur_position = safe_int(entry.get('cur_position'))
444 if position is None:
445 if position is None:
445 return
446 return
446
447
447 # check if this is an existing entry
448 # check if this is an existing entry
448 is_new = False
449 is_new = False
449 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
450 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
450
451
451 if db_entry and str2bool(entry.get('remove')):
452 if db_entry and str2bool(entry.get('remove')):
452 log.debug('Marked bookmark %s for deletion', db_entry)
453 log.debug('Marked bookmark %s for deletion', db_entry)
453 Session().delete(db_entry)
454 Session().delete(db_entry)
454 return
455 return
455
456
456 if not db_entry:
457 if not db_entry:
457 # new
458 # new
458 db_entry = UserBookmark()
459 db_entry = UserBookmark()
459 is_new = True
460 is_new = True
460
461
461 should_save = False
462 should_save = False
462 default_redirect_url = ''
463 default_redirect_url = ''
463
464
464 # save repo
465 # save repo
465 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
466 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
466 repo = Repository.get(entry['bookmark_repo'])
467 repo = Repository.get(entry['bookmark_repo'])
467 perm_check = HasRepoPermissionAny(
468 perm_check = HasRepoPermissionAny(
468 'repository.read', 'repository.write', 'repository.admin')
469 'repository.read', 'repository.write', 'repository.admin')
469 if repo and perm_check(repo_name=repo.repo_name):
470 if repo and perm_check(repo_name=repo.repo_name):
470 db_entry.repository = repo
471 db_entry.repository = repo
471 should_save = True
472 should_save = True
472 default_redirect_url = '${repo_url}'
473 default_redirect_url = '${repo_url}'
473 # save repo group
474 # save repo group
474 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
475 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
475 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
476 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
476 perm_check = HasRepoGroupPermissionAny(
477 perm_check = HasRepoGroupPermissionAny(
477 'group.read', 'group.write', 'group.admin')
478 'group.read', 'group.write', 'group.admin')
478
479
479 if repo_group and perm_check(group_name=repo_group.group_name):
480 if repo_group and perm_check(group_name=repo_group.group_name):
480 db_entry.repository_group = repo_group
481 db_entry.repository_group = repo_group
481 should_save = True
482 should_save = True
482 default_redirect_url = '${repo_group_url}'
483 default_redirect_url = '${repo_group_url}'
483 # save generic info
484 # save generic info
484 elif entry.get('title') and entry.get('redirect_url'):
485 elif entry.get('title') and entry.get('redirect_url'):
485 should_save = True
486 should_save = True
486
487
487 if should_save:
488 if should_save:
488 # mark user and position
489 # mark user and position
489 db_entry.user_id = user_id
490 db_entry.user_id = user_id
490 db_entry.position = position
491 db_entry.position = position
491 db_entry.title = entry.get('title')
492 db_entry.title = entry.get('title')
492 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
493 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
493 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
494 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
494
495
495 Session().add(db_entry)
496 Session().add(db_entry)
496
497
497 @LoginRequired()
498 @LoginRequired()
498 @NotAnonymous()
499 @NotAnonymous()
499 @CSRFRequired()
500 @CSRFRequired()
500 @view_config(
501 @view_config(
501 route_name='my_account_bookmarks_update', request_method='POST')
502 route_name='my_account_bookmarks_update', request_method='POST')
502 def my_account_bookmarks_update(self):
503 def my_account_bookmarks_update(self):
503 _ = self.request.translate
504 _ = self.request.translate
504 c = self.load_default_context()
505 c = self.load_default_context()
505 c.active = 'bookmarks'
506 c.active = 'bookmarks'
506
507
507 controls = peppercorn.parse(self.request.POST.items())
508 controls = peppercorn.parse(self.request.POST.items())
508 user_id = c.user.user_id
509 user_id = c.user.user_id
509
510
510 # validate positions
511 # validate positions
511 positions = {}
512 positions = {}
512 for entry in controls.get('bookmarks', []):
513 for entry in controls.get('bookmarks', []):
513 position = safe_int(entry['position'])
514 position = safe_int(entry['position'])
514 if position is None:
515 if position is None:
515 continue
516 continue
516
517
517 if position in positions:
518 if position in positions:
518 h.flash(_("Position {} is defined twice. "
519 h.flash(_("Position {} is defined twice. "
519 "Please correct this error.").format(position), category='error')
520 "Please correct this error.").format(position), category='error')
520 return HTTPFound(h.route_path('my_account_bookmarks'))
521 return HTTPFound(h.route_path('my_account_bookmarks'))
521
522
522 entry['position'] = position
523 entry['position'] = position
523 entry['cur_position'] = safe_int(entry.get('cur_position'))
524 entry['cur_position'] = safe_int(entry.get('cur_position'))
524 positions[position] = entry
525 positions[position] = entry
525
526
526 try:
527 try:
527 for entry in positions.values():
528 for entry in positions.values():
528 self._process_bookmark_entry(entry, user_id)
529 self._process_bookmark_entry(entry, user_id)
529
530
530 Session().commit()
531 Session().commit()
531 h.flash(_("Update Bookmarks"), category='success')
532 h.flash(_("Update Bookmarks"), category='success')
532 except IntegrityError:
533 except IntegrityError:
533 h.flash(_("Failed to update bookmarks. "
534 h.flash(_("Failed to update bookmarks. "
534 "Make sure an unique position is used."), category='error')
535 "Make sure an unique position is used."), category='error')
535
536
536 return HTTPFound(h.route_path('my_account_bookmarks'))
537 return HTTPFound(h.route_path('my_account_bookmarks'))
537
538
538 @LoginRequired()
539 @LoginRequired()
539 @NotAnonymous()
540 @NotAnonymous()
540 @view_config(
541 @view_config(
541 route_name='my_account_goto_bookmark', request_method='GET',
542 route_name='my_account_goto_bookmark', request_method='GET',
542 renderer='rhodecode:templates/admin/my_account/my_account.mako')
543 renderer='rhodecode:templates/admin/my_account/my_account.mako')
543 def my_account_goto_bookmark(self):
544 def my_account_goto_bookmark(self):
544
545
545 bookmark_id = self.request.matchdict['bookmark_id']
546 bookmark_id = self.request.matchdict['bookmark_id']
546 user_bookmark = UserBookmark().query()\
547 user_bookmark = UserBookmark().query()\
547 .filter(UserBookmark.user_id == self.request.user.user_id) \
548 .filter(UserBookmark.user_id == self.request.user.user_id) \
548 .filter(UserBookmark.position == bookmark_id).scalar()
549 .filter(UserBookmark.position == bookmark_id).scalar()
549
550
550 redirect_url = h.route_path('my_account_bookmarks')
551 redirect_url = h.route_path('my_account_bookmarks')
551 if not user_bookmark:
552 if not user_bookmark:
552 raise HTTPFound(redirect_url)
553 raise HTTPFound(redirect_url)
553
554
554 # repository set
555 # repository set
555 if user_bookmark.repository:
556 if user_bookmark.repository:
556 repo_name = user_bookmark.repository.repo_name
557 repo_name = user_bookmark.repository.repo_name
557 base_redirect_url = h.route_path(
558 base_redirect_url = h.route_path(
558 'repo_summary', repo_name=repo_name)
559 'repo_summary', repo_name=repo_name)
559 if user_bookmark.redirect_url and \
560 if user_bookmark.redirect_url and \
560 '${repo_url}' in user_bookmark.redirect_url:
561 '${repo_url}' in user_bookmark.redirect_url:
561 redirect_url = string.Template(user_bookmark.redirect_url)\
562 redirect_url = string.Template(user_bookmark.redirect_url)\
562 .safe_substitute({'repo_url': base_redirect_url})
563 .safe_substitute({'repo_url': base_redirect_url})
563 else:
564 else:
564 redirect_url = base_redirect_url
565 redirect_url = base_redirect_url
565 # repository group set
566 # repository group set
566 elif user_bookmark.repository_group:
567 elif user_bookmark.repository_group:
567 repo_group_name = user_bookmark.repository_group.group_name
568 repo_group_name = user_bookmark.repository_group.group_name
568 base_redirect_url = h.route_path(
569 base_redirect_url = h.route_path(
569 'repo_group_home', repo_group_name=repo_group_name)
570 'repo_group_home', repo_group_name=repo_group_name)
570 if user_bookmark.redirect_url and \
571 if user_bookmark.redirect_url and \
571 '${repo_group_url}' in user_bookmark.redirect_url:
572 '${repo_group_url}' in user_bookmark.redirect_url:
572 redirect_url = string.Template(user_bookmark.redirect_url)\
573 redirect_url = string.Template(user_bookmark.redirect_url)\
573 .safe_substitute({'repo_group_url': base_redirect_url})
574 .safe_substitute({'repo_group_url': base_redirect_url})
574 else:
575 else:
575 redirect_url = base_redirect_url
576 redirect_url = base_redirect_url
576 # custom URL set
577 # custom URL set
577 elif user_bookmark.redirect_url:
578 elif user_bookmark.redirect_url:
578 server_url = h.route_url('home').rstrip('/')
579 server_url = h.route_url('home').rstrip('/')
579 redirect_url = string.Template(user_bookmark.redirect_url) \
580 redirect_url = string.Template(user_bookmark.redirect_url) \
580 .safe_substitute({'server_url': server_url})
581 .safe_substitute({'server_url': server_url})
581
582
582 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
583 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
583 raise HTTPFound(redirect_url)
584 raise HTTPFound(redirect_url)
584
585
585 @LoginRequired()
586 @LoginRequired()
586 @NotAnonymous()
587 @NotAnonymous()
587 @view_config(
588 @view_config(
588 route_name='my_account_perms', request_method='GET',
589 route_name='my_account_perms', request_method='GET',
589 renderer='rhodecode:templates/admin/my_account/my_account.mako')
590 renderer='rhodecode:templates/admin/my_account/my_account.mako')
590 def my_account_perms(self):
591 def my_account_perms(self):
591 c = self.load_default_context()
592 c = self.load_default_context()
592 c.active = 'perms'
593 c.active = 'perms'
593
594
594 c.perm_user = c.auth_user
595 c.perm_user = c.auth_user
595 return self._get_template_context(c)
596 return self._get_template_context(c)
596
597
597 @LoginRequired()
598 @LoginRequired()
598 @NotAnonymous()
599 @NotAnonymous()
599 @view_config(
600 @view_config(
600 route_name='my_account_notifications', request_method='GET',
601 route_name='my_account_notifications', request_method='GET',
601 renderer='rhodecode:templates/admin/my_account/my_account.mako')
602 renderer='rhodecode:templates/admin/my_account/my_account.mako')
602 def my_notifications(self):
603 def my_notifications(self):
603 c = self.load_default_context()
604 c = self.load_default_context()
604 c.active = 'notifications'
605 c.active = 'notifications'
605
606
606 return self._get_template_context(c)
607 return self._get_template_context(c)
607
608
608 @LoginRequired()
609 @LoginRequired()
609 @NotAnonymous()
610 @NotAnonymous()
610 @CSRFRequired()
611 @CSRFRequired()
611 @view_config(
612 @view_config(
612 route_name='my_account_notifications_toggle_visibility',
613 route_name='my_account_notifications_toggle_visibility',
613 request_method='POST', renderer='json_ext')
614 request_method='POST', renderer='json_ext')
614 def my_notifications_toggle_visibility(self):
615 def my_notifications_toggle_visibility(self):
615 user = self._rhodecode_db_user
616 user = self._rhodecode_db_user
616 new_status = not user.user_data.get('notification_status', True)
617 new_status = not user.user_data.get('notification_status', True)
617 user.update_userdata(notification_status=new_status)
618 user.update_userdata(notification_status=new_status)
618 Session().commit()
619 Session().commit()
619 return user.user_data['notification_status']
620 return user.user_data['notification_status']
620
621
621 @LoginRequired()
622 @LoginRequired()
622 @NotAnonymous()
623 @NotAnonymous()
623 @view_config(
624 @view_config(
624 route_name='my_account_edit',
625 route_name='my_account_edit',
625 request_method='GET',
626 request_method='GET',
626 renderer='rhodecode:templates/admin/my_account/my_account.mako')
627 renderer='rhodecode:templates/admin/my_account/my_account.mako')
627 def my_account_edit(self):
628 def my_account_edit(self):
628 c = self.load_default_context()
629 c = self.load_default_context()
629 c.active = 'profile_edit'
630 c.active = 'profile_edit'
630 c.extern_type = c.user.extern_type
631 c.extern_type = c.user.extern_type
631 c.extern_name = c.user.extern_name
632 c.extern_name = c.user.extern_name
632
633
633 schema = user_schema.UserProfileSchema().bind(
634 schema = user_schema.UserProfileSchema().bind(
634 username=c.user.username, user_emails=c.user.emails)
635 username=c.user.username, user_emails=c.user.emails)
635 appstruct = {
636 appstruct = {
636 'username': c.user.username,
637 'username': c.user.username,
637 'email': c.user.email,
638 'email': c.user.email,
638 'firstname': c.user.firstname,
639 'firstname': c.user.firstname,
639 'lastname': c.user.lastname,
640 'lastname': c.user.lastname,
640 'description': c.user.description,
641 'description': c.user.description,
641 }
642 }
642 c.form = forms.RcForm(
643 c.form = forms.RcForm(
643 schema, appstruct=appstruct,
644 schema, appstruct=appstruct,
644 action=h.route_path('my_account_update'),
645 action=h.route_path('my_account_update'),
645 buttons=(forms.buttons.save, forms.buttons.reset))
646 buttons=(forms.buttons.save, forms.buttons.reset))
646
647
647 return self._get_template_context(c)
648 return self._get_template_context(c)
648
649
649 @LoginRequired()
650 @LoginRequired()
650 @NotAnonymous()
651 @NotAnonymous()
651 @CSRFRequired()
652 @CSRFRequired()
652 @view_config(
653 @view_config(
653 route_name='my_account_update',
654 route_name='my_account_update',
654 request_method='POST',
655 request_method='POST',
655 renderer='rhodecode:templates/admin/my_account/my_account.mako')
656 renderer='rhodecode:templates/admin/my_account/my_account.mako')
656 def my_account_update(self):
657 def my_account_update(self):
657 _ = self.request.translate
658 _ = self.request.translate
658 c = self.load_default_context()
659 c = self.load_default_context()
659 c.active = 'profile_edit'
660 c.active = 'profile_edit'
660 c.perm_user = c.auth_user
661 c.perm_user = c.auth_user
661 c.extern_type = c.user.extern_type
662 c.extern_type = c.user.extern_type
662 c.extern_name = c.user.extern_name
663 c.extern_name = c.user.extern_name
663
664
664 schema = user_schema.UserProfileSchema().bind(
665 schema = user_schema.UserProfileSchema().bind(
665 username=c.user.username, user_emails=c.user.emails)
666 username=c.user.username, user_emails=c.user.emails)
666 form = forms.RcForm(
667 form = forms.RcForm(
667 schema, buttons=(forms.buttons.save, forms.buttons.reset))
668 schema, buttons=(forms.buttons.save, forms.buttons.reset))
668
669
669 controls = self.request.POST.items()
670 controls = self.request.POST.items()
670 try:
671 try:
671 valid_data = form.validate(controls)
672 valid_data = form.validate(controls)
672 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
673 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
673 'new_password', 'password_confirmation']
674 'new_password', 'password_confirmation']
674 if c.extern_type != "rhodecode":
675 if c.extern_type != "rhodecode":
675 # forbid updating username for external accounts
676 # forbid updating username for external accounts
676 skip_attrs.append('username')
677 skip_attrs.append('username')
677 old_email = c.user.email
678 old_email = c.user.email
678 UserModel().update_user(
679 UserModel().update_user(
679 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
680 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
680 **valid_data)
681 **valid_data)
681 if old_email != valid_data['email']:
682 if old_email != valid_data['email']:
682 old = UserEmailMap.query() \
683 old = UserEmailMap.query() \
683 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
684 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
684 old.email = old_email
685 old.email = old_email
685 h.flash(_('Your account was updated successfully'), category='success')
686 h.flash(_('Your account was updated successfully'), category='success')
686 Session().commit()
687 Session().commit()
687 except forms.ValidationFailure as e:
688 except forms.ValidationFailure as e:
688 c.form = e
689 c.form = e
689 return self._get_template_context(c)
690 return self._get_template_context(c)
690 except Exception:
691 except Exception:
691 log.exception("Exception updating user")
692 log.exception("Exception updating user")
692 h.flash(_('Error occurred during update of user'),
693 h.flash(_('Error occurred during update of user'),
693 category='error')
694 category='error')
694 raise HTTPFound(h.route_path('my_account_profile'))
695 raise HTTPFound(h.route_path('my_account_profile'))
695
696
696 def _get_pull_requests_list(self, statuses):
697 def _get_pull_requests_list(self, statuses):
697 draw, start, limit = self._extract_chunk(self.request)
698 draw, start, limit = self._extract_chunk(self.request)
698 search_q, order_by, order_dir = self._extract_ordering(self.request)
699 search_q, order_by, order_dir = self._extract_ordering(self.request)
699 _render = self.request.get_partial_renderer(
700 _render = self.request.get_partial_renderer(
700 'rhodecode:templates/data_table/_dt_elements.mako')
701 'rhodecode:templates/data_table/_dt_elements.mako')
701
702
702 pull_requests = PullRequestModel().get_im_participating_in(
703 pull_requests = PullRequestModel().get_im_participating_in(
703 user_id=self._rhodecode_user.user_id,
704 user_id=self._rhodecode_user.user_id,
704 statuses=statuses,
705 statuses=statuses,
705 offset=start, length=limit, order_by=order_by,
706 offset=start, length=limit, order_by=order_by,
706 order_dir=order_dir)
707 order_dir=order_dir)
707
708
708 pull_requests_total_count = PullRequestModel().count_im_participating_in(
709 pull_requests_total_count = PullRequestModel().count_im_participating_in(
709 user_id=self._rhodecode_user.user_id, statuses=statuses)
710 user_id=self._rhodecode_user.user_id, statuses=statuses)
710
711
711 data = []
712 data = []
712 comments_model = CommentsModel()
713 comments_model = CommentsModel()
713 for pr in pull_requests:
714 for pr in pull_requests:
714 repo_id = pr.target_repo_id
715 repo_id = pr.target_repo_id
715 comments = comments_model.get_all_comments(
716 comments = comments_model.get_all_comments(
716 repo_id, pull_request=pr)
717 repo_id, pull_request=pr)
717 owned = pr.user_id == self._rhodecode_user.user_id
718 owned = pr.user_id == self._rhodecode_user.user_id
718
719
719 data.append({
720 data.append({
720 'target_repo': _render('pullrequest_target_repo',
721 'target_repo': _render('pullrequest_target_repo',
721 pr.target_repo.repo_name),
722 pr.target_repo.repo_name),
722 'name': _render('pullrequest_name',
723 'name': _render('pullrequest_name',
723 pr.pull_request_id, pr.pull_request_state,
724 pr.pull_request_id, pr.pull_request_state,
724 pr.work_in_progress, pr.target_repo.repo_name,
725 pr.work_in_progress, pr.target_repo.repo_name,
725 short=True),
726 short=True),
726 'name_raw': pr.pull_request_id,
727 'name_raw': pr.pull_request_id,
727 'status': _render('pullrequest_status',
728 'status': _render('pullrequest_status',
728 pr.calculated_review_status()),
729 pr.calculated_review_status()),
729 'title': _render('pullrequest_title', pr.title, pr.description),
730 'title': _render('pullrequest_title', pr.title, pr.description),
730 'description': h.escape(pr.description),
731 'description': h.escape(pr.description),
731 'updated_on': _render('pullrequest_updated_on',
732 'updated_on': _render('pullrequest_updated_on',
732 h.datetime_to_time(pr.updated_on)),
733 h.datetime_to_time(pr.updated_on)),
733 'updated_on_raw': h.datetime_to_time(pr.updated_on),
734 'updated_on_raw': h.datetime_to_time(pr.updated_on),
734 'created_on': _render('pullrequest_updated_on',
735 'created_on': _render('pullrequest_updated_on',
735 h.datetime_to_time(pr.created_on)),
736 h.datetime_to_time(pr.created_on)),
736 'created_on_raw': h.datetime_to_time(pr.created_on),
737 'created_on_raw': h.datetime_to_time(pr.created_on),
737 'state': pr.pull_request_state,
738 'state': pr.pull_request_state,
738 'author': _render('pullrequest_author',
739 'author': _render('pullrequest_author',
739 pr.author.full_contact, ),
740 pr.author.full_contact, ),
740 'author_raw': pr.author.full_name,
741 'author_raw': pr.author.full_name,
741 'comments': _render('pullrequest_comments', len(comments)),
742 'comments': _render('pullrequest_comments', len(comments)),
742 'comments_raw': len(comments),
743 'comments_raw': len(comments),
743 'closed': pr.is_closed(),
744 'closed': pr.is_closed(),
744 'owned': owned
745 'owned': owned
745 })
746 })
746
747
747 # json used to render the grid
748 # json used to render the grid
748 data = ({
749 data = ({
749 'draw': draw,
750 'draw': draw,
750 'data': data,
751 'data': data,
751 'recordsTotal': pull_requests_total_count,
752 'recordsTotal': pull_requests_total_count,
752 'recordsFiltered': pull_requests_total_count,
753 'recordsFiltered': pull_requests_total_count,
753 })
754 })
754 return data
755 return data
755
756
756 @LoginRequired()
757 @LoginRequired()
757 @NotAnonymous()
758 @NotAnonymous()
758 @view_config(
759 @view_config(
759 route_name='my_account_pullrequests',
760 route_name='my_account_pullrequests',
760 request_method='GET',
761 request_method='GET',
761 renderer='rhodecode:templates/admin/my_account/my_account.mako')
762 renderer='rhodecode:templates/admin/my_account/my_account.mako')
762 def my_account_pullrequests(self):
763 def my_account_pullrequests(self):
763 c = self.load_default_context()
764 c = self.load_default_context()
764 c.active = 'pullrequests'
765 c.active = 'pullrequests'
765 req_get = self.request.GET
766 req_get = self.request.GET
766
767
767 c.closed = str2bool(req_get.get('pr_show_closed'))
768 c.closed = str2bool(req_get.get('pr_show_closed'))
768
769
769 return self._get_template_context(c)
770 return self._get_template_context(c)
770
771
771 @LoginRequired()
772 @LoginRequired()
772 @NotAnonymous()
773 @NotAnonymous()
773 @view_config(
774 @view_config(
774 route_name='my_account_pullrequests_data',
775 route_name='my_account_pullrequests_data',
775 request_method='GET', renderer='json_ext')
776 request_method='GET', renderer='json_ext')
776 def my_account_pullrequests_data(self):
777 def my_account_pullrequests_data(self):
777 self.load_default_context()
778 self.load_default_context()
778 req_get = self.request.GET
779 req_get = self.request.GET
779 closed = str2bool(req_get.get('closed'))
780 closed = str2bool(req_get.get('closed'))
780
781
781 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
782 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
782 if closed:
783 if closed:
783 statuses += [PullRequest.STATUS_CLOSED]
784 statuses += [PullRequest.STATUS_CLOSED]
784
785
785 data = self._get_pull_requests_list(statuses=statuses)
786 data = self._get_pull_requests_list(statuses=statuses)
786 return data
787 return data
787
788
788 @LoginRequired()
789 @LoginRequired()
789 @NotAnonymous()
790 @NotAnonymous()
790 @view_config(
791 @view_config(
791 route_name='my_account_user_group_membership',
792 route_name='my_account_user_group_membership',
792 request_method='GET',
793 request_method='GET',
793 renderer='rhodecode:templates/admin/my_account/my_account.mako')
794 renderer='rhodecode:templates/admin/my_account/my_account.mako')
794 def my_account_user_group_membership(self):
795 def my_account_user_group_membership(self):
795 c = self.load_default_context()
796 c = self.load_default_context()
796 c.active = 'user_group_membership'
797 c.active = 'user_group_membership'
797 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
798 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
798 for group in self._rhodecode_db_user.group_member]
799 for group in self._rhodecode_db_user.group_member]
799 c.user_groups = json.dumps(groups)
800 c.user_groups = json.dumps(groups)
800 return self._get_template_context(c)
801 return self._get_template_context(c)
@@ -1,190 +1,190 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
8 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
9 'Each token can have a role. Token with a role can be used only in given context, '
9 'Each token can have a role. Token with a role can be used only in given context, '
10 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
11 </p>
11 </p>
12 <table class="rctable auth_tokens">
12 <table class="rctable auth_tokens">
13 <tr>
13 <tr>
14 <th>${_('Token')}</th>
14 <th>${_('Token')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Repository Scope')}</th>
17 <th>${_('Repository Scope')}</th>
18 <th>${_('Expiration')}</th>
18 <th>${_('Expiration')}</th>
19 <th>${_('Action')}</th>
19 <th>${_('Action')}</th>
20 </tr>
20 </tr>
21 %if c.user_auth_tokens:
21 %if c.user_auth_tokens:
22 %for auth_token in c.user_auth_tokens:
22 %for auth_token in c.user_auth_tokens:
23 <tr class="${('expired' if auth_token.expired else '')}">
23 <tr class="${('expired' if auth_token.expired else '')}">
24 <td class="truncate-wrap td-authtoken">
24 <td class="truncate-wrap td-authtoken">
25 <div class="user_auth_tokens truncate autoexpand">
25 <div class="user_auth_tokens truncate autoexpand">
26 <code>${auth_token.api_key}</code>
26 <code>${auth_token.api_key}</code>
27 </div>
27 </div>
28 </td>
28 </td>
29 <td class="td-wrap">${auth_token.description}</td>
29 <td class="td-wrap">${auth_token.description}</td>
30 <td class="td-tags">
30 <td class="td-tags">
31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 </td>
32 </td>
33 <td class="td">${auth_token.scope_humanized}</td>
33 <td class="td">${auth_token.scope_humanized}</td>
34 <td class="td-exp">
34 <td class="td-exp">
35 %if auth_token.expires == -1:
35 %if auth_token.expires == -1:
36 ${_('never')}
36 ${_('never')}
37 %else:
37 %else:
38 %if auth_token.expired:
38 %if auth_token.expired:
39 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
40 %else:
40 %else:
41 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
42 %endif
42 %endif
43 %endif
43 %endif
44 </td>
44 </td>
45 <td class="td-action">
45 <td class="td-action">
46 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
46 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
47 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
48 <button class="btn btn-link btn-danger" type="submit"
48 <button class="btn btn-link btn-danger" type="submit"
49 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
50 ${_('Delete')}
50 ${_('Delete')}
51 </button>
51 </button>
52 ${h.end_form()}
52 ${h.end_form()}
53 </td>
53 </td>
54 </tr>
54 </tr>
55 %endfor
55 %endfor
56 %else:
56 %else:
57 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
57 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
58 %endif
58 %endif
59 </table>
59 </table>
60 </div>
60 </div>
61
61
62 <div class="user_auth_tokens">
62 <div class="user_auth_tokens">
63 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
63 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
64 <div class="form form-vertical">
64 <div class="form form-vertical">
65 <!-- fields -->
65 <!-- fields -->
66 <div class="fields">
66 <div class="fields">
67 <div class="field">
67 <div class="field">
68 <div class="label">
68 <div class="label">
69 <label for="new_email">${_('New authentication token')}:</label>
69 <label for="new_email">${_('New authentication token')}:</label>
70 </div>
70 </div>
71 <div class="input">
71 <div class="input">
72 ${h.text('description', class_='medium', placeholder=_('Description'))}
72 ${h.text('description', class_='medium', placeholder=_('Description'))}
73 ${h.hidden('lifetime')}
73 ${h.hidden('lifetime')}
74 ${h.select('role', '', c.role_options)}
74 ${h.select('role', request.GET.get('token_role', ''), c.role_options)}
75
75
76 % if c.allow_scoped_tokens:
76 % if c.allow_scoped_tokens:
77 ${h.hidden('scope_repo_id')}
77 ${h.hidden('scope_repo_id')}
78 % else:
78 % else:
79 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
79 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
80 % endif
80 % endif
81 </div>
81 </div>
82 <p class="help-block">
82 <p class="help-block">
83 ${_('Repository scope works only with tokens with VCS type.')}
83 ${_('Repository scope works only with tokens with VCS type.')}
84 </p>
84 </p>
85 </div>
85 </div>
86 <div class="buttons">
86 <div class="buttons">
87 ${h.submit('save',_('Add'),class_="btn")}
87 ${h.submit('save',_('Add'),class_="btn")}
88 ${h.reset('reset',_('Reset'),class_="btn")}
88 ${h.reset('reset',_('Reset'),class_="btn")}
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92 ${h.end_form()}
92 ${h.end_form()}
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96
96
97 <script>
97 <script>
98 $(document).ready(function(){
98 $(document).ready(function(){
99
99
100 var select2Options = {
100 var select2Options = {
101 'containerCssClass': "drop-menu",
101 'containerCssClass': "drop-menu",
102 'dropdownCssClass': "drop-menu-dropdown",
102 'dropdownCssClass': "drop-menu-dropdown",
103 'dropdownAutoWidth': true
103 'dropdownAutoWidth': true
104 };
104 };
105 $("#role").select2(select2Options);
105 $("#role").select2(select2Options);
106
106
107 var preloadData = {
107 var preloadData = {
108 results: [
108 results: [
109 % for entry in c.lifetime_values:
109 % for entry in c.lifetime_values:
110 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
110 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
111 % endfor
111 % endfor
112 ]
112 ]
113 };
113 };
114
114
115 $("#lifetime").select2({
115 $("#lifetime").select2({
116 containerCssClass: "drop-menu",
116 containerCssClass: "drop-menu",
117 dropdownCssClass: "drop-menu-dropdown",
117 dropdownCssClass: "drop-menu-dropdown",
118 dropdownAutoWidth: true,
118 dropdownAutoWidth: true,
119 data: preloadData,
119 data: preloadData,
120 placeholder: "${_('Select or enter expiration date')}",
120 placeholder: "${_('Select or enter expiration date')}",
121 query: function(query) {
121 query: function(query) {
122 feedLifetimeOptions(query, preloadData);
122 feedLifetimeOptions(query, preloadData);
123 }
123 }
124 });
124 });
125
125
126
126
127 var repoFilter = function(data) {
127 var repoFilter = function(data) {
128 var results = [];
128 var results = [];
129
129
130 if (!data.results[0]) {
130 if (!data.results[0]) {
131 return data
131 return data
132 }
132 }
133
133
134 $.each(data.results[0].children, function() {
134 $.each(data.results[0].children, function() {
135 // replace name to ID for submision
135 // replace name to ID for submision
136 this.id = this.repo_id;
136 this.id = this.repo_id;
137 results.push(this);
137 results.push(this);
138 });
138 });
139
139
140 data.results[0].children = results;
140 data.results[0].children = results;
141 return data;
141 return data;
142 };
142 };
143
143
144 $("#scope_repo_id_disabled").select2(select2Options);
144 $("#scope_repo_id_disabled").select2(select2Options);
145
145
146 var selectVcsScope = function() {
146 var selectVcsScope = function() {
147 // select vcs scope and disable input
147 // select vcs scope and disable input
148 $("#role").select2("val", "${c.role_vcs}").trigger('change');
148 $("#role").select2("val", "${c.role_vcs}").trigger('change');
149 $("#role").select2("readonly", true)
149 $("#role").select2("readonly", true)
150 };
150 };
151
151
152 $("#scope_repo_id").select2({
152 $("#scope_repo_id").select2({
153 cachedDataSource: {},
153 cachedDataSource: {},
154 minimumInputLength: 2,
154 minimumInputLength: 2,
155 placeholder: "${_('repository scope')}",
155 placeholder: "${_('repository scope')}",
156 dropdownAutoWidth: true,
156 dropdownAutoWidth: true,
157 containerCssClass: "drop-menu",
157 containerCssClass: "drop-menu",
158 dropdownCssClass: "drop-menu-dropdown",
158 dropdownCssClass: "drop-menu-dropdown",
159 formatResult: formatRepoResult,
159 formatResult: formatRepoResult,
160 query: $.debounce(250, function(query){
160 query: $.debounce(250, function(query){
161 self = this;
161 self = this;
162 var cacheKey = query.term;
162 var cacheKey = query.term;
163 var cachedData = self.cachedDataSource[cacheKey];
163 var cachedData = self.cachedDataSource[cacheKey];
164
164
165 if (cachedData) {
165 if (cachedData) {
166 query.callback({results: cachedData.results});
166 query.callback({results: cachedData.results});
167 } else {
167 } else {
168 $.ajax({
168 $.ajax({
169 url: pyroutes.url('repo_list_data'),
169 url: pyroutes.url('repo_list_data'),
170 data: {'query': query.term},
170 data: {'query': query.term},
171 dataType: 'json',
171 dataType: 'json',
172 type: 'GET',
172 type: 'GET',
173 success: function(data) {
173 success: function(data) {
174 data = repoFilter(data);
174 data = repoFilter(data);
175 self.cachedDataSource[cacheKey] = data;
175 self.cachedDataSource[cacheKey] = data;
176 query.callback({results: data.results});
176 query.callback({results: data.results});
177 },
177 },
178 error: function(data, textStatus, errorThrown) {
178 error: function(data, textStatus, errorThrown) {
179 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
179 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
180 }
180 }
181 })
181 })
182 }
182 }
183 })
183 })
184 });
184 });
185 $("#scope_repo_id").on('select2-selecting', function(e){
185 $("#scope_repo_id").on('select2-selecting', function(e){
186 selectVcsScope()
186 selectVcsScope()
187 });
187 });
188
188
189 });
189 });
190 </script>
190 </script>
@@ -1,13 +1,15 b''
1 <%namespace name="widgets" file="/widgets.mako"/>
1 <%namespace name="widgets" file="/widgets.mako"/>
2
2
3 <%widgets:panel title="${_('Change Your Account Password')}">
3 <%widgets:panel title="${_('Change Your Account Password')}">
4
4
5 % if c.extern_type != 'rhodecode':
5 % if c.extern_type != 'rhodecode':
6 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
6 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
7 <br/>${_('For VCS access please generate')}
8 <a href="${h.route_path('my_account_auth_tokens', _query={'token_role':'token_role_vcs'})}">Authentication Token</a> or <a href="${h.route_path('my_account_ssh_keys_generate')}">SSH Key</a>.
7 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
9 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
8 </p>
10 </p>
9 % else:
11 % else:
10 ${c.form.render() | n}
12 ${c.form.render() | n}
11 % endif
13 % endif
12
14
13 </%widgets:panel>
15 </%widgets:panel>
@@ -1,76 +1,87 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <div class="panel panel-default user-profile">
4 <div class="panel panel-default user-profile">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('My Profile')}</h3>
6 <h3 class="panel-title">${_('My Profile')}</h3>
7 <a href="${h.route_path('my_account_edit')}" class="panel-edit">${_('Edit')}</a>
7 <a href="${h.route_path('my_account_edit')}" class="panel-edit">${_('Edit')}</a>
8 </div>
8 </div>
9
9
10 <div class="panel-body fields">
10 <div class="panel-body fields">
11 %if c.extern_type != 'rhodecode':
12 <% readonly = "readonly" %>
13 <% disabled = " disabled" %>
14 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
15 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
16 </div>
17 <div style="margin:-10px 0px 20px 0px;">
18 ${_('For VCS access please generate')}
19 <a href="${h.route_path('my_account_auth_tokens', _query={'token_role':'token_role_vcs'})}">Authentication Token</a> or <a href="${h.route_path('my_account_ssh_keys_generate')}">SSH Key</a>.
20 </div>
21 %endif
11 <div class="field">
22 <div class="field">
12 <div class="label">
23 <div class="label">
13 ${_('Photo')}:
24 ${_('Photo')}:
14 </div>
25 </div>
15 <div class="input">
26 <div class="input">
16 <div class="text-as-placeholder">
27 <div class="text-as-placeholder">
17 %if c.visual.use_gravatar:
28 %if c.visual.use_gravatar:
18 ${base.gravatar(c.user.email, 100)}
29 ${base.gravatar(c.user.email, 100)}
19 %else:
30 %else:
20 ${base.gravatar(c.user.email, 100)}
31 ${base.gravatar(c.user.email, 100)}
21 %endif
32 %endif
22 </div>
33 </div>
23 </div>
34 </div>
24 </div>
35 </div>
25 <div class="field">
36 <div class="field">
26 <div class="label">
37 <div class="label">
27 ${_('Username')}:
38 ${_('Username')}:
28 </div>
39 </div>
29 <div class="input">
40 <div class="input">
30 <div class="text-as-placeholder">
41 <div class="text-as-placeholder">
31 ${c.user.username}
42 ${c.user.username}
32 </div>
43 </div>
33 </div>
44 </div>
34 </div>
45 </div>
35 <div class="field">
46 <div class="field">
36 <div class="label">
47 <div class="label">
37 ${_('First Name')}:
48 ${_('First Name')}:
38 </div>
49 </div>
39 <div class="input">
50 <div class="input">
40 <div class="text-as-placeholder">
51 <div class="text-as-placeholder">
41 ${c.user.first_name}
52 ${c.user.first_name}
42 </div>
53 </div>
43 </div>
54 </div>
44 </div>
55 </div>
45 <div class="field">
56 <div class="field">
46 <div class="label">
57 <div class="label">
47 ${_('Last Name')}:
58 ${_('Last Name')}:
48 </div>
59 </div>
49 <div class="input">
60 <div class="input">
50 <div class="text-as-placeholder">
61 <div class="text-as-placeholder">
51 ${c.user.last_name}
62 ${c.user.last_name}
52 </div>
63 </div>
53 </div>
64 </div>
54 </div>
65 </div>
55 <div class="field">
66 <div class="field">
56 <div class="label">
67 <div class="label">
57 ${_('Description')}:
68 ${_('Description')}:
58 </div>
69 </div>
59 <div class="input">
70 <div class="input">
60 <div class="text-as-placeholder">
71 <div class="text-as-placeholder">
61 ${dt.render_description(c.user.description, c.visual.stylify_metatags)}
72 ${dt.render_description(c.user.description, c.visual.stylify_metatags)}
62 </div>
73 </div>
63 </div>
74 </div>
64 </div>
75 </div>
65 <div class="field">
76 <div class="field">
66 <div class="label">
77 <div class="label">
67 ${_('Email')}:
78 ${_('Email')}:
68 </div>
79 </div>
69 <div class="input">
80 <div class="input">
70 <div class="text-as-placeholder">
81 <div class="text-as-placeholder">
71 ${c.user.email or _('Missing email, please update your user email address.')}
82 ${c.user.email or _('Missing email, please update your user email address.')}
72 </div>
83 </div>
73 </div>
84 </div>
74 </div>
85 </div>
75 </div>
86 </div>
76 </div> No newline at end of file
87 </div>
@@ -1,70 +1,76 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <div class="panel panel-default user-profile">
2 <div class="panel panel-default user-profile">
3 <div class="panel-heading">
3 <div class="panel-heading">
4 <h3 class="panel-title">${_('My Profile')}</h3>
4 <h3 class="panel-title">${_('My Profile')}</h3>
5 <a href="${h.route_path('my_account_profile')}" class="panel-edit">Close</a>
5 <a href="${h.route_path('my_account_profile')}" class="panel-edit">Close</a>
6 </div>
6 </div>
7
7
8 <div class="panel-body">
8 <div class="panel-body">
9 <% readonly = None %>
9 <% readonly = None %>
10 <% disabled = "" %>
10 <% disabled = "" %>
11
11
12 %if c.extern_type != 'rhodecode':
12 %if c.extern_type != 'rhodecode':
13 <% readonly = "readonly" %>
13 <% readonly = "readonly" %>
14 <% disabled = "disabled" %>
14 <% disabled = " disabled" %>
15 <div class="alert-warning" style="margin:0px 0px 20px 0px; padding: 10px">
16 <strong>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</strong>
17 </div>
18 <div style="margin:-10px 0px 20px 0px;">
19 ${_('For VCS access please generate')}
20 <a href="${h.route_path('my_account_auth_tokens', _query={'token_role':'token_role_vcs'})}">Authentication Token</a> or <a href="${h.route_path('my_account_ssh_keys_generate')}">SSH Key</a>.
21 </div>
22 %endif
23
24 %if c.extern_type != 'rhodecode':
15 <div class="infoform">
25 <div class="infoform">
16 <div class="fields">
26 <div class="fields">
17 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
18 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
19 </p>
20
21 <div class="field">
27 <div class="field">
22 <div class="label">
28 <div class="label">
23 <label for="username">${_('Username')}:</label>
29 <label for="username">${_('Username')}:</label>
24 </div>
30 </div>
25 <div class="input">
31 <div class="input">
26 ${c.user.username}
32 ${c.user.username}
27 </div>
33 </div>
28 </div>
34 </div>
29
35
30 <div class="field">
36 <div class="field">
31 <div class="label">
37 <div class="label">
32 <label for="name">${_('First Name')}:</label>
38 <label for="name">${_('First Name')}:</label>
33 </div>
39 </div>
34 <div class="input">
40 <div class="input">
35 ${c.user.firstname}
41 ${c.user.firstname}
36 </div>
42 </div>
37 </div>
43 </div>
38
44
39 <div class="field">
45 <div class="field">
40 <div class="label">
46 <div class="label">
41 <label for="lastname">${_('Last Name')}:</label>
47 <label for="lastname">${_('Last Name')}:</label>
42 </div>
48 </div>
43 <div class="input-valuedisplay">
49 <div class="input-valuedisplay">
44 ${c.user.lastname}
50 ${c.user.lastname}
45 </div>
51 </div>
46 </div>
52 </div>
47 </div>
53 </div>
48 </div>
54 </div>
49 % else:
55 % else:
50 <div class="form">
56 <div class="form">
51 <div class="fields">
57 <div class="fields">
52 <div class="field">
58 <div class="field">
53 <div class="label photo">
59 <div class="label photo">
54 ${_('Photo')}:
60 ${_('Photo')}:
55 </div>
61 </div>
56 <div class="input profile">
62 <div class="input profile">
57 %if c.visual.use_gravatar:
63 %if c.visual.use_gravatar:
58 ${base.gravatar(c.user.email, 100)}
64 ${base.gravatar(c.user.email, 100)}
59 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
65 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
60 %else:
66 %else:
61 ${base.gravatar(c.user.email, 100)}
67 ${base.gravatar(c.user.email, 100)}
62 %endif
68 %endif
63 </div>
69 </div>
64 </div>
70 </div>
65 ${c.form.render()| n}
71 ${c.form.render()| n}
66 </div>
72 </div>
67 </div>
73 </div>
68 % endif
74 % endif
69 </div>
75 </div>
70 </div> No newline at end of file
76 </div>
General Comments 0
You need to be logged in to leave comments. Login now