##// END OF EJS Templates
bookmarks: cache fetching of bookmarks since this is quite expensive query to make with joinedload on repos/repo groups.
marcink -
r4143:d9dc1c76 default
parent child Browse files
Show More
@@ -1,762 +1,764 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 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
402 self._rhodecode_db_user.user_id, cache=False)
401 return self._get_template_context(c)
403 return self._get_template_context(c)
402
404
403 def _process_bookmark_entry(self, entry, user_id):
405 def _process_bookmark_entry(self, entry, user_id):
404 position = safe_int(entry.get('position'))
406 position = safe_int(entry.get('position'))
405 cur_position = safe_int(entry.get('cur_position'))
407 cur_position = safe_int(entry.get('cur_position'))
406 if position is None or cur_position is None:
408 if position is None:
407 return
409 return
408
410
409 # check if this is an existing entry
411 # check if this is an existing entry
410 is_new = False
412 is_new = False
411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
413 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
412
414
413 if db_entry and str2bool(entry.get('remove')):
415 if db_entry and str2bool(entry.get('remove')):
414 log.debug('Marked bookmark %s for deletion', db_entry)
416 log.debug('Marked bookmark %s for deletion', db_entry)
415 Session().delete(db_entry)
417 Session().delete(db_entry)
416 return
418 return
417
419
418 if not db_entry:
420 if not db_entry:
419 # new
421 # new
420 db_entry = UserBookmark()
422 db_entry = UserBookmark()
421 is_new = True
423 is_new = True
422
424
423 should_save = False
425 should_save = False
424 default_redirect_url = ''
426 default_redirect_url = ''
425
427
426 # save repo
428 # save repo
427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
429 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
428 repo = Repository.get(entry['bookmark_repo'])
430 repo = Repository.get(entry['bookmark_repo'])
429 perm_check = HasRepoPermissionAny(
431 perm_check = HasRepoPermissionAny(
430 'repository.read', 'repository.write', 'repository.admin')
432 'repository.read', 'repository.write', 'repository.admin')
431 if repo and perm_check(repo_name=repo.repo_name):
433 if repo and perm_check(repo_name=repo.repo_name):
432 db_entry.repository = repo
434 db_entry.repository = repo
433 should_save = True
435 should_save = True
434 default_redirect_url = '${repo_url}'
436 default_redirect_url = '${repo_url}'
435 # save repo group
437 # save repo group
436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
438 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
439 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
438 perm_check = HasRepoGroupPermissionAny(
440 perm_check = HasRepoGroupPermissionAny(
439 'group.read', 'group.write', 'group.admin')
441 'group.read', 'group.write', 'group.admin')
440
442
441 if repo_group and perm_check(group_name=repo_group.group_name):
443 if repo_group and perm_check(group_name=repo_group.group_name):
442 db_entry.repository_group = repo_group
444 db_entry.repository_group = repo_group
443 should_save = True
445 should_save = True
444 default_redirect_url = '${repo_group_url}'
446 default_redirect_url = '${repo_group_url}'
445 # save generic info
447 # save generic info
446 elif entry.get('title') and entry.get('redirect_url'):
448 elif entry.get('title') and entry.get('redirect_url'):
447 should_save = True
449 should_save = True
448
450
449 if should_save:
451 if should_save:
450 # mark user and position
452 # mark user and position
451 db_entry.user_id = user_id
453 db_entry.user_id = user_id
452 db_entry.position = position
454 db_entry.position = position
453 db_entry.title = entry.get('title')
455 db_entry.title = entry.get('title')
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
456 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
457 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
456
458
457 Session().add(db_entry)
459 Session().add(db_entry)
458
460
459 @LoginRequired()
461 @LoginRequired()
460 @NotAnonymous()
462 @NotAnonymous()
461 @CSRFRequired()
463 @CSRFRequired()
462 @view_config(
464 @view_config(
463 route_name='my_account_bookmarks_update', request_method='POST')
465 route_name='my_account_bookmarks_update', request_method='POST')
464 def my_account_bookmarks_update(self):
466 def my_account_bookmarks_update(self):
465 _ = self.request.translate
467 _ = self.request.translate
466 c = self.load_default_context()
468 c = self.load_default_context()
467 c.active = 'bookmarks'
469 c.active = 'bookmarks'
468
470
469 controls = peppercorn.parse(self.request.POST.items())
471 controls = peppercorn.parse(self.request.POST.items())
470 user_id = c.user.user_id
472 user_id = c.user.user_id
471
473
472 # validate positions
474 # validate positions
473 positions = {}
475 positions = {}
474 for entry in controls.get('bookmarks', []):
476 for entry in controls.get('bookmarks', []):
475 position = safe_int(entry['position'])
477 position = safe_int(entry['position'])
476 if position is None:
478 if position is None:
477 continue
479 continue
478
480
479 if position in positions:
481 if position in positions:
480 h.flash(_("Position {} is defined twice. "
482 h.flash(_("Position {} is defined twice. "
481 "Please correct this error.").format(position), category='error')
483 "Please correct this error.").format(position), category='error')
482 return HTTPFound(h.route_path('my_account_bookmarks'))
484 return HTTPFound(h.route_path('my_account_bookmarks'))
483
485
484 entry['position'] = position
486 entry['position'] = position
485 entry['cur_position'] = safe_int(entry.get('cur_position'))
487 entry['cur_position'] = safe_int(entry.get('cur_position'))
486 positions[position] = entry
488 positions[position] = entry
487
489
488 try:
490 try:
489 for entry in positions.values():
491 for entry in positions.values():
490 self._process_bookmark_entry(entry, user_id)
492 self._process_bookmark_entry(entry, user_id)
491
493
492 Session().commit()
494 Session().commit()
493 h.flash(_("Update Bookmarks"), category='success')
495 h.flash(_("Update Bookmarks"), category='success')
494 except IntegrityError:
496 except IntegrityError:
495 h.flash(_("Failed to update bookmarks. "
497 h.flash(_("Failed to update bookmarks. "
496 "Make sure an unique position is used."), category='error')
498 "Make sure an unique position is used."), category='error')
497
499
498 return HTTPFound(h.route_path('my_account_bookmarks'))
500 return HTTPFound(h.route_path('my_account_bookmarks'))
499
501
500 @LoginRequired()
502 @LoginRequired()
501 @NotAnonymous()
503 @NotAnonymous()
502 @view_config(
504 @view_config(
503 route_name='my_account_goto_bookmark', request_method='GET',
505 route_name='my_account_goto_bookmark', request_method='GET',
504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
506 renderer='rhodecode:templates/admin/my_account/my_account.mako')
505 def my_account_goto_bookmark(self):
507 def my_account_goto_bookmark(self):
506
508
507 bookmark_id = self.request.matchdict['bookmark_id']
509 bookmark_id = self.request.matchdict['bookmark_id']
508 user_bookmark = UserBookmark().query()\
510 user_bookmark = UserBookmark().query()\
509 .filter(UserBookmark.user_id == self.request.user.user_id) \
511 .filter(UserBookmark.user_id == self.request.user.user_id) \
510 .filter(UserBookmark.position == bookmark_id).scalar()
512 .filter(UserBookmark.position == bookmark_id).scalar()
511
513
512 redirect_url = h.route_path('my_account_bookmarks')
514 redirect_url = h.route_path('my_account_bookmarks')
513 if not user_bookmark:
515 if not user_bookmark:
514 raise HTTPFound(redirect_url)
516 raise HTTPFound(redirect_url)
515
517
516 # repository set
518 # repository set
517 if user_bookmark.repository:
519 if user_bookmark.repository:
518 repo_name = user_bookmark.repository.repo_name
520 repo_name = user_bookmark.repository.repo_name
519 base_redirect_url = h.route_path(
521 base_redirect_url = h.route_path(
520 'repo_summary', repo_name=repo_name)
522 'repo_summary', repo_name=repo_name)
521 if user_bookmark.redirect_url and \
523 if user_bookmark.redirect_url and \
522 '${repo_url}' in user_bookmark.redirect_url:
524 '${repo_url}' in user_bookmark.redirect_url:
523 redirect_url = string.Template(user_bookmark.redirect_url)\
525 redirect_url = string.Template(user_bookmark.redirect_url)\
524 .safe_substitute({'repo_url': base_redirect_url})
526 .safe_substitute({'repo_url': base_redirect_url})
525 else:
527 else:
526 redirect_url = base_redirect_url
528 redirect_url = base_redirect_url
527 # repository group set
529 # repository group set
528 elif user_bookmark.repository_group:
530 elif user_bookmark.repository_group:
529 repo_group_name = user_bookmark.repository_group.group_name
531 repo_group_name = user_bookmark.repository_group.group_name
530 base_redirect_url = h.route_path(
532 base_redirect_url = h.route_path(
531 'repo_group_home', repo_group_name=repo_group_name)
533 'repo_group_home', repo_group_name=repo_group_name)
532 if user_bookmark.redirect_url and \
534 if user_bookmark.redirect_url and \
533 '${repo_group_url}' in user_bookmark.redirect_url:
535 '${repo_group_url}' in user_bookmark.redirect_url:
534 redirect_url = string.Template(user_bookmark.redirect_url)\
536 redirect_url = string.Template(user_bookmark.redirect_url)\
535 .safe_substitute({'repo_group_url': base_redirect_url})
537 .safe_substitute({'repo_group_url': base_redirect_url})
536 else:
538 else:
537 redirect_url = base_redirect_url
539 redirect_url = base_redirect_url
538 # custom URL set
540 # custom URL set
539 elif user_bookmark.redirect_url:
541 elif user_bookmark.redirect_url:
540 server_url = h.route_url('home').rstrip('/')
542 server_url = h.route_url('home').rstrip('/')
541 redirect_url = string.Template(user_bookmark.redirect_url) \
543 redirect_url = string.Template(user_bookmark.redirect_url) \
542 .safe_substitute({'server_url': server_url})
544 .safe_substitute({'server_url': server_url})
543
545
544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
546 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
545 raise HTTPFound(redirect_url)
547 raise HTTPFound(redirect_url)
546
548
547 @LoginRequired()
549 @LoginRequired()
548 @NotAnonymous()
550 @NotAnonymous()
549 @view_config(
551 @view_config(
550 route_name='my_account_perms', request_method='GET',
552 route_name='my_account_perms', request_method='GET',
551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
553 renderer='rhodecode:templates/admin/my_account/my_account.mako')
552 def my_account_perms(self):
554 def my_account_perms(self):
553 c = self.load_default_context()
555 c = self.load_default_context()
554 c.active = 'perms'
556 c.active = 'perms'
555
557
556 c.perm_user = c.auth_user
558 c.perm_user = c.auth_user
557 return self._get_template_context(c)
559 return self._get_template_context(c)
558
560
559 @LoginRequired()
561 @LoginRequired()
560 @NotAnonymous()
562 @NotAnonymous()
561 @view_config(
563 @view_config(
562 route_name='my_account_notifications', request_method='GET',
564 route_name='my_account_notifications', request_method='GET',
563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
565 renderer='rhodecode:templates/admin/my_account/my_account.mako')
564 def my_notifications(self):
566 def my_notifications(self):
565 c = self.load_default_context()
567 c = self.load_default_context()
566 c.active = 'notifications'
568 c.active = 'notifications'
567
569
568 return self._get_template_context(c)
570 return self._get_template_context(c)
569
571
570 @LoginRequired()
572 @LoginRequired()
571 @NotAnonymous()
573 @NotAnonymous()
572 @CSRFRequired()
574 @CSRFRequired()
573 @view_config(
575 @view_config(
574 route_name='my_account_notifications_toggle_visibility',
576 route_name='my_account_notifications_toggle_visibility',
575 request_method='POST', renderer='json_ext')
577 request_method='POST', renderer='json_ext')
576 def my_notifications_toggle_visibility(self):
578 def my_notifications_toggle_visibility(self):
577 user = self._rhodecode_db_user
579 user = self._rhodecode_db_user
578 new_status = not user.user_data.get('notification_status', True)
580 new_status = not user.user_data.get('notification_status', True)
579 user.update_userdata(notification_status=new_status)
581 user.update_userdata(notification_status=new_status)
580 Session().commit()
582 Session().commit()
581 return user.user_data['notification_status']
583 return user.user_data['notification_status']
582
584
583 @LoginRequired()
585 @LoginRequired()
584 @NotAnonymous()
586 @NotAnonymous()
585 @view_config(
587 @view_config(
586 route_name='my_account_edit',
588 route_name='my_account_edit',
587 request_method='GET',
589 request_method='GET',
588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
590 renderer='rhodecode:templates/admin/my_account/my_account.mako')
589 def my_account_edit(self):
591 def my_account_edit(self):
590 c = self.load_default_context()
592 c = self.load_default_context()
591 c.active = 'profile_edit'
593 c.active = 'profile_edit'
592 c.extern_type = c.user.extern_type
594 c.extern_type = c.user.extern_type
593 c.extern_name = c.user.extern_name
595 c.extern_name = c.user.extern_name
594
596
595 schema = user_schema.UserProfileSchema().bind(
597 schema = user_schema.UserProfileSchema().bind(
596 username=c.user.username, user_emails=c.user.emails)
598 username=c.user.username, user_emails=c.user.emails)
597 appstruct = {
599 appstruct = {
598 'username': c.user.username,
600 'username': c.user.username,
599 'email': c.user.email,
601 'email': c.user.email,
600 'firstname': c.user.firstname,
602 'firstname': c.user.firstname,
601 'lastname': c.user.lastname,
603 'lastname': c.user.lastname,
602 'description': c.user.description,
604 'description': c.user.description,
603 }
605 }
604 c.form = forms.RcForm(
606 c.form = forms.RcForm(
605 schema, appstruct=appstruct,
607 schema, appstruct=appstruct,
606 action=h.route_path('my_account_update'),
608 action=h.route_path('my_account_update'),
607 buttons=(forms.buttons.save, forms.buttons.reset))
609 buttons=(forms.buttons.save, forms.buttons.reset))
608
610
609 return self._get_template_context(c)
611 return self._get_template_context(c)
610
612
611 @LoginRequired()
613 @LoginRequired()
612 @NotAnonymous()
614 @NotAnonymous()
613 @CSRFRequired()
615 @CSRFRequired()
614 @view_config(
616 @view_config(
615 route_name='my_account_update',
617 route_name='my_account_update',
616 request_method='POST',
618 request_method='POST',
617 renderer='rhodecode:templates/admin/my_account/my_account.mako')
619 renderer='rhodecode:templates/admin/my_account/my_account.mako')
618 def my_account_update(self):
620 def my_account_update(self):
619 _ = self.request.translate
621 _ = self.request.translate
620 c = self.load_default_context()
622 c = self.load_default_context()
621 c.active = 'profile_edit'
623 c.active = 'profile_edit'
622 c.perm_user = c.auth_user
624 c.perm_user = c.auth_user
623 c.extern_type = c.user.extern_type
625 c.extern_type = c.user.extern_type
624 c.extern_name = c.user.extern_name
626 c.extern_name = c.user.extern_name
625
627
626 schema = user_schema.UserProfileSchema().bind(
628 schema = user_schema.UserProfileSchema().bind(
627 username=c.user.username, user_emails=c.user.emails)
629 username=c.user.username, user_emails=c.user.emails)
628 form = forms.RcForm(
630 form = forms.RcForm(
629 schema, buttons=(forms.buttons.save, forms.buttons.reset))
631 schema, buttons=(forms.buttons.save, forms.buttons.reset))
630
632
631 controls = self.request.POST.items()
633 controls = self.request.POST.items()
632 try:
634 try:
633 valid_data = form.validate(controls)
635 valid_data = form.validate(controls)
634 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
636 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
635 'new_password', 'password_confirmation']
637 'new_password', 'password_confirmation']
636 if c.extern_type != "rhodecode":
638 if c.extern_type != "rhodecode":
637 # forbid updating username for external accounts
639 # forbid updating username for external accounts
638 skip_attrs.append('username')
640 skip_attrs.append('username')
639 old_email = c.user.email
641 old_email = c.user.email
640 UserModel().update_user(
642 UserModel().update_user(
641 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
643 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
642 **valid_data)
644 **valid_data)
643 if old_email != valid_data['email']:
645 if old_email != valid_data['email']:
644 old = UserEmailMap.query() \
646 old = UserEmailMap.query() \
645 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
647 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
646 old.email = old_email
648 old.email = old_email
647 h.flash(_('Your account was updated successfully'), category='success')
649 h.flash(_('Your account was updated successfully'), category='success')
648 Session().commit()
650 Session().commit()
649 except forms.ValidationFailure as e:
651 except forms.ValidationFailure as e:
650 c.form = e
652 c.form = e
651 return self._get_template_context(c)
653 return self._get_template_context(c)
652 except Exception:
654 except Exception:
653 log.exception("Exception updating user")
655 log.exception("Exception updating user")
654 h.flash(_('Error occurred during update of user'),
656 h.flash(_('Error occurred during update of user'),
655 category='error')
657 category='error')
656 raise HTTPFound(h.route_path('my_account_profile'))
658 raise HTTPFound(h.route_path('my_account_profile'))
657
659
658 def _get_pull_requests_list(self, statuses):
660 def _get_pull_requests_list(self, statuses):
659 draw, start, limit = self._extract_chunk(self.request)
661 draw, start, limit = self._extract_chunk(self.request)
660 search_q, order_by, order_dir = self._extract_ordering(self.request)
662 search_q, order_by, order_dir = self._extract_ordering(self.request)
661 _render = self.request.get_partial_renderer(
663 _render = self.request.get_partial_renderer(
662 'rhodecode:templates/data_table/_dt_elements.mako')
664 'rhodecode:templates/data_table/_dt_elements.mako')
663
665
664 pull_requests = PullRequestModel().get_im_participating_in(
666 pull_requests = PullRequestModel().get_im_participating_in(
665 user_id=self._rhodecode_user.user_id,
667 user_id=self._rhodecode_user.user_id,
666 statuses=statuses,
668 statuses=statuses,
667 offset=start, length=limit, order_by=order_by,
669 offset=start, length=limit, order_by=order_by,
668 order_dir=order_dir)
670 order_dir=order_dir)
669
671
670 pull_requests_total_count = PullRequestModel().count_im_participating_in(
672 pull_requests_total_count = PullRequestModel().count_im_participating_in(
671 user_id=self._rhodecode_user.user_id, statuses=statuses)
673 user_id=self._rhodecode_user.user_id, statuses=statuses)
672
674
673 data = []
675 data = []
674 comments_model = CommentsModel()
676 comments_model = CommentsModel()
675 for pr in pull_requests:
677 for pr in pull_requests:
676 repo_id = pr.target_repo_id
678 repo_id = pr.target_repo_id
677 comments = comments_model.get_all_comments(
679 comments = comments_model.get_all_comments(
678 repo_id, pull_request=pr)
680 repo_id, pull_request=pr)
679 owned = pr.user_id == self._rhodecode_user.user_id
681 owned = pr.user_id == self._rhodecode_user.user_id
680
682
681 data.append({
683 data.append({
682 'target_repo': _render('pullrequest_target_repo',
684 'target_repo': _render('pullrequest_target_repo',
683 pr.target_repo.repo_name),
685 pr.target_repo.repo_name),
684 'name': _render('pullrequest_name',
686 'name': _render('pullrequest_name',
685 pr.pull_request_id, pr.pull_request_state,
687 pr.pull_request_id, pr.pull_request_state,
686 pr.work_in_progress, pr.target_repo.repo_name,
688 pr.work_in_progress, pr.target_repo.repo_name,
687 short=True),
689 short=True),
688 'name_raw': pr.pull_request_id,
690 'name_raw': pr.pull_request_id,
689 'status': _render('pullrequest_status',
691 'status': _render('pullrequest_status',
690 pr.calculated_review_status()),
692 pr.calculated_review_status()),
691 'title': _render('pullrequest_title', pr.title, pr.description),
693 'title': _render('pullrequest_title', pr.title, pr.description),
692 'description': h.escape(pr.description),
694 'description': h.escape(pr.description),
693 'updated_on': _render('pullrequest_updated_on',
695 'updated_on': _render('pullrequest_updated_on',
694 h.datetime_to_time(pr.updated_on)),
696 h.datetime_to_time(pr.updated_on)),
695 'updated_on_raw': h.datetime_to_time(pr.updated_on),
697 'updated_on_raw': h.datetime_to_time(pr.updated_on),
696 'created_on': _render('pullrequest_updated_on',
698 'created_on': _render('pullrequest_updated_on',
697 h.datetime_to_time(pr.created_on)),
699 h.datetime_to_time(pr.created_on)),
698 'created_on_raw': h.datetime_to_time(pr.created_on),
700 'created_on_raw': h.datetime_to_time(pr.created_on),
699 'state': pr.pull_request_state,
701 'state': pr.pull_request_state,
700 'author': _render('pullrequest_author',
702 'author': _render('pullrequest_author',
701 pr.author.full_contact, ),
703 pr.author.full_contact, ),
702 'author_raw': pr.author.full_name,
704 'author_raw': pr.author.full_name,
703 'comments': _render('pullrequest_comments', len(comments)),
705 'comments': _render('pullrequest_comments', len(comments)),
704 'comments_raw': len(comments),
706 'comments_raw': len(comments),
705 'closed': pr.is_closed(),
707 'closed': pr.is_closed(),
706 'owned': owned
708 'owned': owned
707 })
709 })
708
710
709 # json used to render the grid
711 # json used to render the grid
710 data = ({
712 data = ({
711 'draw': draw,
713 'draw': draw,
712 'data': data,
714 'data': data,
713 'recordsTotal': pull_requests_total_count,
715 'recordsTotal': pull_requests_total_count,
714 'recordsFiltered': pull_requests_total_count,
716 'recordsFiltered': pull_requests_total_count,
715 })
717 })
716 return data
718 return data
717
719
718 @LoginRequired()
720 @LoginRequired()
719 @NotAnonymous()
721 @NotAnonymous()
720 @view_config(
722 @view_config(
721 route_name='my_account_pullrequests',
723 route_name='my_account_pullrequests',
722 request_method='GET',
724 request_method='GET',
723 renderer='rhodecode:templates/admin/my_account/my_account.mako')
725 renderer='rhodecode:templates/admin/my_account/my_account.mako')
724 def my_account_pullrequests(self):
726 def my_account_pullrequests(self):
725 c = self.load_default_context()
727 c = self.load_default_context()
726 c.active = 'pullrequests'
728 c.active = 'pullrequests'
727 req_get = self.request.GET
729 req_get = self.request.GET
728
730
729 c.closed = str2bool(req_get.get('pr_show_closed'))
731 c.closed = str2bool(req_get.get('pr_show_closed'))
730
732
731 return self._get_template_context(c)
733 return self._get_template_context(c)
732
734
733 @LoginRequired()
735 @LoginRequired()
734 @NotAnonymous()
736 @NotAnonymous()
735 @view_config(
737 @view_config(
736 route_name='my_account_pullrequests_data',
738 route_name='my_account_pullrequests_data',
737 request_method='GET', renderer='json_ext')
739 request_method='GET', renderer='json_ext')
738 def my_account_pullrequests_data(self):
740 def my_account_pullrequests_data(self):
739 self.load_default_context()
741 self.load_default_context()
740 req_get = self.request.GET
742 req_get = self.request.GET
741 closed = str2bool(req_get.get('closed'))
743 closed = str2bool(req_get.get('closed'))
742
744
743 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
745 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
744 if closed:
746 if closed:
745 statuses += [PullRequest.STATUS_CLOSED]
747 statuses += [PullRequest.STATUS_CLOSED]
746
748
747 data = self._get_pull_requests_list(statuses=statuses)
749 data = self._get_pull_requests_list(statuses=statuses)
748 return data
750 return data
749
751
750 @LoginRequired()
752 @LoginRequired()
751 @NotAnonymous()
753 @NotAnonymous()
752 @view_config(
754 @view_config(
753 route_name='my_account_user_group_membership',
755 route_name='my_account_user_group_membership',
754 request_method='GET',
756 request_method='GET',
755 renderer='rhodecode:templates/admin/my_account/my_account.mako')
757 renderer='rhodecode:templates/admin/my_account/my_account.mako')
756 def my_account_user_group_membership(self):
758 def my_account_user_group_membership(self):
757 c = self.load_default_context()
759 c = self.load_default_context()
758 c.active = 'user_group_membership'
760 c.active = 'user_group_membership'
759 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
761 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
760 for group in self._rhodecode_db_user.group_member]
762 for group in self._rhodecode_db_user.group_member]
761 c.user_groups = json.dumps(groups)
763 c.user_groups = json.dumps(groups)
762 return self._get_template_context(c)
764 return self._get_template_context(c)
@@ -1,5468 +1,5474 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_default_user(cls, cache=False, refresh=False):
1028 def get_default_user(cls, cache=False, refresh=False):
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1030 if user is None:
1030 if user is None:
1031 raise Exception('FATAL: Missing default account!')
1031 raise Exception('FATAL: Missing default account!')
1032 if refresh:
1032 if refresh:
1033 # The default user might be based on outdated state which
1033 # The default user might be based on outdated state which
1034 # has been loaded from the cache.
1034 # has been loaded from the cache.
1035 # A call to refresh() ensures that the
1035 # A call to refresh() ensures that the
1036 # latest state from the database is used.
1036 # latest state from the database is used.
1037 Session().refresh(user)
1037 Session().refresh(user)
1038 return user
1038 return user
1039
1039
1040 def _get_default_perms(self, user, suffix=''):
1040 def _get_default_perms(self, user, suffix=''):
1041 from rhodecode.model.permission import PermissionModel
1041 from rhodecode.model.permission import PermissionModel
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1043
1043
1044 def get_default_perms(self, suffix=''):
1044 def get_default_perms(self, suffix=''):
1045 return self._get_default_perms(self, suffix)
1045 return self._get_default_perms(self, suffix)
1046
1046
1047 def get_api_data(self, include_secrets=False, details='full'):
1047 def get_api_data(self, include_secrets=False, details='full'):
1048 """
1048 """
1049 Common function for generating user related data for API
1049 Common function for generating user related data for API
1050
1050
1051 :param include_secrets: By default secrets in the API data will be replaced
1051 :param include_secrets: By default secrets in the API data will be replaced
1052 by a placeholder value to prevent exposing this data by accident. In case
1052 by a placeholder value to prevent exposing this data by accident. In case
1053 this data shall be exposed, set this flag to ``True``.
1053 this data shall be exposed, set this flag to ``True``.
1054
1054
1055 :param details: details can be 'basic|full' basic gives only a subset of
1055 :param details: details can be 'basic|full' basic gives only a subset of
1056 the available user information that includes user_id, name and emails.
1056 the available user information that includes user_id, name and emails.
1057 """
1057 """
1058 user = self
1058 user = self
1059 user_data = self.user_data
1059 user_data = self.user_data
1060 data = {
1060 data = {
1061 'user_id': user.user_id,
1061 'user_id': user.user_id,
1062 'username': user.username,
1062 'username': user.username,
1063 'firstname': user.name,
1063 'firstname': user.name,
1064 'lastname': user.lastname,
1064 'lastname': user.lastname,
1065 'description': user.description,
1065 'description': user.description,
1066 'email': user.email,
1066 'email': user.email,
1067 'emails': user.emails,
1067 'emails': user.emails,
1068 }
1068 }
1069 if details == 'basic':
1069 if details == 'basic':
1070 return data
1070 return data
1071
1071
1072 auth_token_length = 40
1072 auth_token_length = 40
1073 auth_token_replacement = '*' * auth_token_length
1073 auth_token_replacement = '*' * auth_token_length
1074
1074
1075 extras = {
1075 extras = {
1076 'auth_tokens': [auth_token_replacement],
1076 'auth_tokens': [auth_token_replacement],
1077 'active': user.active,
1077 'active': user.active,
1078 'admin': user.admin,
1078 'admin': user.admin,
1079 'extern_type': user.extern_type,
1079 'extern_type': user.extern_type,
1080 'extern_name': user.extern_name,
1080 'extern_name': user.extern_name,
1081 'last_login': user.last_login,
1081 'last_login': user.last_login,
1082 'last_activity': user.last_activity,
1082 'last_activity': user.last_activity,
1083 'ip_addresses': user.ip_addresses,
1083 'ip_addresses': user.ip_addresses,
1084 'language': user_data.get('language')
1084 'language': user_data.get('language')
1085 }
1085 }
1086 data.update(extras)
1086 data.update(extras)
1087
1087
1088 if include_secrets:
1088 if include_secrets:
1089 data['auth_tokens'] = user.auth_tokens
1089 data['auth_tokens'] = user.auth_tokens
1090 return data
1090 return data
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 data = {
1093 data = {
1094 'full_name': self.full_name,
1094 'full_name': self.full_name,
1095 'full_name_or_username': self.full_name_or_username,
1095 'full_name_or_username': self.full_name_or_username,
1096 'short_contact': self.short_contact,
1096 'short_contact': self.short_contact,
1097 'full_contact': self.full_contact,
1097 'full_contact': self.full_contact,
1098 }
1098 }
1099 data.update(self.get_api_data())
1099 data.update(self.get_api_data())
1100 return data
1100 return data
1101
1101
1102
1102
1103 class UserApiKeys(Base, BaseModel):
1103 class UserApiKeys(Base, BaseModel):
1104 __tablename__ = 'user_api_keys'
1104 __tablename__ = 'user_api_keys'
1105 __table_args__ = (
1105 __table_args__ = (
1106 Index('uak_api_key_idx', 'api_key'),
1106 Index('uak_api_key_idx', 'api_key'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1108 base_table_args
1108 base_table_args
1109 )
1109 )
1110 __mapper_args__ = {}
1110 __mapper_args__ = {}
1111
1111
1112 # ApiKey role
1112 # ApiKey role
1113 ROLE_ALL = 'token_role_all'
1113 ROLE_ALL = 'token_role_all'
1114 ROLE_HTTP = 'token_role_http'
1114 ROLE_HTTP = 'token_role_http'
1115 ROLE_VCS = 'token_role_vcs'
1115 ROLE_VCS = 'token_role_vcs'
1116 ROLE_API = 'token_role_api'
1116 ROLE_API = 'token_role_api'
1117 ROLE_FEED = 'token_role_feed'
1117 ROLE_FEED = 'token_role_feed'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1120
1120
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1122
1122
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1127 expires = Column('expires', Float(53), nullable=False)
1127 expires = Column('expires', Float(53), nullable=False)
1128 role = Column('role', String(255), nullable=True)
1128 role = Column('role', String(255), nullable=True)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1130
1130
1131 # scope columns
1131 # scope columns
1132 repo_id = Column(
1132 repo_id = Column(
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1134 nullable=True, unique=None, default=None)
1134 nullable=True, unique=None, default=None)
1135 repo = relationship('Repository', lazy='joined')
1135 repo = relationship('Repository', lazy='joined')
1136
1136
1137 repo_group_id = Column(
1137 repo_group_id = Column(
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1139 nullable=True, unique=None, default=None)
1139 nullable=True, unique=None, default=None)
1140 repo_group = relationship('RepoGroup', lazy='joined')
1140 repo_group = relationship('RepoGroup', lazy='joined')
1141
1141
1142 user = relationship('User', lazy='joined')
1142 user = relationship('User', lazy='joined')
1143
1143
1144 def __unicode__(self):
1144 def __unicode__(self):
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1146
1146
1147 def __json__(self):
1147 def __json__(self):
1148 data = {
1148 data = {
1149 'auth_token': self.api_key,
1149 'auth_token': self.api_key,
1150 'role': self.role,
1150 'role': self.role,
1151 'scope': self.scope_humanized,
1151 'scope': self.scope_humanized,
1152 'expired': self.expired
1152 'expired': self.expired
1153 }
1153 }
1154 return data
1154 return data
1155
1155
1156 def get_api_data(self, include_secrets=False):
1156 def get_api_data(self, include_secrets=False):
1157 data = self.__json__()
1157 data = self.__json__()
1158 if include_secrets:
1158 if include_secrets:
1159 return data
1159 return data
1160 else:
1160 else:
1161 data['auth_token'] = self.token_obfuscated
1161 data['auth_token'] = self.token_obfuscated
1162 return data
1162 return data
1163
1163
1164 @hybrid_property
1164 @hybrid_property
1165 def description_safe(self):
1165 def description_safe(self):
1166 from rhodecode.lib import helpers as h
1166 from rhodecode.lib import helpers as h
1167 return h.escape(self.description)
1167 return h.escape(self.description)
1168
1168
1169 @property
1169 @property
1170 def expired(self):
1170 def expired(self):
1171 if self.expires == -1:
1171 if self.expires == -1:
1172 return False
1172 return False
1173 return time.time() > self.expires
1173 return time.time() > self.expires
1174
1174
1175 @classmethod
1175 @classmethod
1176 def _get_role_name(cls, role):
1176 def _get_role_name(cls, role):
1177 return {
1177 return {
1178 cls.ROLE_ALL: _('all'),
1178 cls.ROLE_ALL: _('all'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1181 cls.ROLE_API: _('api calls'),
1181 cls.ROLE_API: _('api calls'),
1182 cls.ROLE_FEED: _('feed access'),
1182 cls.ROLE_FEED: _('feed access'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1184 }.get(role, role)
1184 }.get(role, role)
1185
1185
1186 @property
1186 @property
1187 def role_humanized(self):
1187 def role_humanized(self):
1188 return self._get_role_name(self.role)
1188 return self._get_role_name(self.role)
1189
1189
1190 def _get_scope(self):
1190 def _get_scope(self):
1191 if self.repo:
1191 if self.repo:
1192 return 'Repository: {}'.format(self.repo.repo_name)
1192 return 'Repository: {}'.format(self.repo.repo_name)
1193 if self.repo_group:
1193 if self.repo_group:
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1195 return 'Global'
1195 return 'Global'
1196
1196
1197 @property
1197 @property
1198 def scope_humanized(self):
1198 def scope_humanized(self):
1199 return self._get_scope()
1199 return self._get_scope()
1200
1200
1201 @property
1201 @property
1202 def token_obfuscated(self):
1202 def token_obfuscated(self):
1203 if self.api_key:
1203 if self.api_key:
1204 return self.api_key[:4] + "****"
1204 return self.api_key[:4] + "****"
1205
1205
1206
1206
1207 class UserEmailMap(Base, BaseModel):
1207 class UserEmailMap(Base, BaseModel):
1208 __tablename__ = 'user_email_map'
1208 __tablename__ = 'user_email_map'
1209 __table_args__ = (
1209 __table_args__ = (
1210 Index('uem_email_idx', 'email'),
1210 Index('uem_email_idx', 'email'),
1211 UniqueConstraint('email'),
1211 UniqueConstraint('email'),
1212 base_table_args
1212 base_table_args
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1219 user = relationship('User', lazy='joined')
1219 user = relationship('User', lazy='joined')
1220
1220
1221 @validates('_email')
1221 @validates('_email')
1222 def validate_email(self, key, email):
1222 def validate_email(self, key, email):
1223 # check if this email is not main one
1223 # check if this email is not main one
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1225 if main_email is not None:
1225 if main_email is not None:
1226 raise AttributeError('email %s is present is user table' % email)
1226 raise AttributeError('email %s is present is user table' % email)
1227 return email
1227 return email
1228
1228
1229 @hybrid_property
1229 @hybrid_property
1230 def email(self):
1230 def email(self):
1231 return self._email
1231 return self._email
1232
1232
1233 @email.setter
1233 @email.setter
1234 def email(self, val):
1234 def email(self, val):
1235 self._email = val.lower() if val else None
1235 self._email = val.lower() if val else None
1236
1236
1237
1237
1238 class UserIpMap(Base, BaseModel):
1238 class UserIpMap(Base, BaseModel):
1239 __tablename__ = 'user_ip_map'
1239 __tablename__ = 'user_ip_map'
1240 __table_args__ = (
1240 __table_args__ = (
1241 UniqueConstraint('user_id', 'ip_addr'),
1241 UniqueConstraint('user_id', 'ip_addr'),
1242 base_table_args
1242 base_table_args
1243 )
1243 )
1244 __mapper_args__ = {}
1244 __mapper_args__ = {}
1245
1245
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 @hybrid_property
1253 @hybrid_property
1254 def description_safe(self):
1254 def description_safe(self):
1255 from rhodecode.lib import helpers as h
1255 from rhodecode.lib import helpers as h
1256 return h.escape(self.description)
1256 return h.escape(self.description)
1257
1257
1258 @classmethod
1258 @classmethod
1259 def _get_ip_range(cls, ip_addr):
1259 def _get_ip_range(cls, ip_addr):
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1261 return [str(net.network_address), str(net.broadcast_address)]
1261 return [str(net.network_address), str(net.broadcast_address)]
1262
1262
1263 def __json__(self):
1263 def __json__(self):
1264 return {
1264 return {
1265 'ip_addr': self.ip_addr,
1265 'ip_addr': self.ip_addr,
1266 'ip_range': self._get_ip_range(self.ip_addr),
1266 'ip_range': self._get_ip_range(self.ip_addr),
1267 }
1267 }
1268
1268
1269 def __unicode__(self):
1269 def __unicode__(self):
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1271 self.user_id, self.ip_addr)
1271 self.user_id, self.ip_addr)
1272
1272
1273
1273
1274 class UserSshKeys(Base, BaseModel):
1274 class UserSshKeys(Base, BaseModel):
1275 __tablename__ = 'user_ssh_keys'
1275 __tablename__ = 'user_ssh_keys'
1276 __table_args__ = (
1276 __table_args__ = (
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1278
1278
1279 UniqueConstraint('ssh_key_fingerprint'),
1279 UniqueConstraint('ssh_key_fingerprint'),
1280
1280
1281 base_table_args
1281 base_table_args
1282 )
1282 )
1283 __mapper_args__ = {}
1283 __mapper_args__ = {}
1284
1284
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1288
1288
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1290
1290
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1294
1294
1295 user = relationship('User', lazy='joined')
1295 user = relationship('User', lazy='joined')
1296
1296
1297 def __json__(self):
1297 def __json__(self):
1298 data = {
1298 data = {
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1300 'description': self.description,
1300 'description': self.description,
1301 'created_on': self.created_on
1301 'created_on': self.created_on
1302 }
1302 }
1303 return data
1303 return data
1304
1304
1305 def get_api_data(self):
1305 def get_api_data(self):
1306 data = self.__json__()
1306 data = self.__json__()
1307 return data
1307 return data
1308
1308
1309
1309
1310 class UserLog(Base, BaseModel):
1310 class UserLog(Base, BaseModel):
1311 __tablename__ = 'user_logs'
1311 __tablename__ = 'user_logs'
1312 __table_args__ = (
1312 __table_args__ = (
1313 base_table_args,
1313 base_table_args,
1314 )
1314 )
1315
1315
1316 VERSION_1 = 'v1'
1316 VERSION_1 = 'v1'
1317 VERSION_2 = 'v2'
1317 VERSION_2 = 'v2'
1318 VERSIONS = [VERSION_1, VERSION_2]
1318 VERSIONS = [VERSION_1, VERSION_2]
1319
1319
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1328
1328
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1332
1332
1333 def __unicode__(self):
1333 def __unicode__(self):
1334 return u"<%s('id:%s:%s')>" % (
1334 return u"<%s('id:%s:%s')>" % (
1335 self.__class__.__name__, self.repository_name, self.action)
1335 self.__class__.__name__, self.repository_name, self.action)
1336
1336
1337 def __json__(self):
1337 def __json__(self):
1338 return {
1338 return {
1339 'user_id': self.user_id,
1339 'user_id': self.user_id,
1340 'username': self.username,
1340 'username': self.username,
1341 'repository_id': self.repository_id,
1341 'repository_id': self.repository_id,
1342 'repository_name': self.repository_name,
1342 'repository_name': self.repository_name,
1343 'user_ip': self.user_ip,
1343 'user_ip': self.user_ip,
1344 'action_date': self.action_date,
1344 'action_date': self.action_date,
1345 'action': self.action,
1345 'action': self.action,
1346 }
1346 }
1347
1347
1348 @hybrid_property
1348 @hybrid_property
1349 def entry_id(self):
1349 def entry_id(self):
1350 return self.user_log_id
1350 return self.user_log_id
1351
1351
1352 @property
1352 @property
1353 def action_as_day(self):
1353 def action_as_day(self):
1354 return datetime.date(*self.action_date.timetuple()[:3])
1354 return datetime.date(*self.action_date.timetuple()[:3])
1355
1355
1356 user = relationship('User')
1356 user = relationship('User')
1357 repository = relationship('Repository', cascade='')
1357 repository = relationship('Repository', cascade='')
1358
1358
1359
1359
1360 class UserGroup(Base, BaseModel):
1360 class UserGroup(Base, BaseModel):
1361 __tablename__ = 'users_groups'
1361 __tablename__ = 'users_groups'
1362 __table_args__ = (
1362 __table_args__ = (
1363 base_table_args,
1363 base_table_args,
1364 )
1364 )
1365
1365
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1374
1374
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1381
1381
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1384
1384
1385 @classmethod
1385 @classmethod
1386 def _load_group_data(cls, column):
1386 def _load_group_data(cls, column):
1387 if not column:
1387 if not column:
1388 return {}
1388 return {}
1389
1389
1390 try:
1390 try:
1391 return json.loads(column) or {}
1391 return json.loads(column) or {}
1392 except TypeError:
1392 except TypeError:
1393 return {}
1393 return {}
1394
1394
1395 @hybrid_property
1395 @hybrid_property
1396 def description_safe(self):
1396 def description_safe(self):
1397 from rhodecode.lib import helpers as h
1397 from rhodecode.lib import helpers as h
1398 return h.escape(self.user_group_description)
1398 return h.escape(self.user_group_description)
1399
1399
1400 @hybrid_property
1400 @hybrid_property
1401 def group_data(self):
1401 def group_data(self):
1402 return self._load_group_data(self._group_data)
1402 return self._load_group_data(self._group_data)
1403
1403
1404 @group_data.expression
1404 @group_data.expression
1405 def group_data(self, **kwargs):
1405 def group_data(self, **kwargs):
1406 return self._group_data
1406 return self._group_data
1407
1407
1408 @group_data.setter
1408 @group_data.setter
1409 def group_data(self, val):
1409 def group_data(self, val):
1410 try:
1410 try:
1411 self._group_data = json.dumps(val)
1411 self._group_data = json.dumps(val)
1412 except Exception:
1412 except Exception:
1413 log.error(traceback.format_exc())
1413 log.error(traceback.format_exc())
1414
1414
1415 @classmethod
1415 @classmethod
1416 def _load_sync(cls, group_data):
1416 def _load_sync(cls, group_data):
1417 if group_data:
1417 if group_data:
1418 return group_data.get('extern_type')
1418 return group_data.get('extern_type')
1419
1419
1420 @property
1420 @property
1421 def sync(self):
1421 def sync(self):
1422 return self._load_sync(self.group_data)
1422 return self._load_sync(self.group_data)
1423
1423
1424 def __unicode__(self):
1424 def __unicode__(self):
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1426 self.users_group_id,
1426 self.users_group_id,
1427 self.users_group_name)
1427 self.users_group_name)
1428
1428
1429 @classmethod
1429 @classmethod
1430 def get_by_group_name(cls, group_name, cache=False,
1430 def get_by_group_name(cls, group_name, cache=False,
1431 case_insensitive=False):
1431 case_insensitive=False):
1432 if case_insensitive:
1432 if case_insensitive:
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1434 func.lower(group_name))
1434 func.lower(group_name))
1435
1435
1436 else:
1436 else:
1437 q = cls.query().filter(cls.users_group_name == group_name)
1437 q = cls.query().filter(cls.users_group_name == group_name)
1438 if cache:
1438 if cache:
1439 q = q.options(
1439 q = q.options(
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1441 return q.scalar()
1441 return q.scalar()
1442
1442
1443 @classmethod
1443 @classmethod
1444 def get(cls, user_group_id, cache=False):
1444 def get(cls, user_group_id, cache=False):
1445 if not user_group_id:
1445 if not user_group_id:
1446 return
1446 return
1447
1447
1448 user_group = cls.query()
1448 user_group = cls.query()
1449 if cache:
1449 if cache:
1450 user_group = user_group.options(
1450 user_group = user_group.options(
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1452 return user_group.get(user_group_id)
1452 return user_group.get(user_group_id)
1453
1453
1454 def permissions(self, with_admins=True, with_owner=True,
1454 def permissions(self, with_admins=True, with_owner=True,
1455 expand_from_user_groups=False):
1455 expand_from_user_groups=False):
1456 """
1456 """
1457 Permissions for user groups
1457 Permissions for user groups
1458 """
1458 """
1459 _admin_perm = 'usergroup.admin'
1459 _admin_perm = 'usergroup.admin'
1460
1460
1461 owner_row = []
1461 owner_row = []
1462 if with_owner:
1462 if with_owner:
1463 usr = AttributeDict(self.user.get_dict())
1463 usr = AttributeDict(self.user.get_dict())
1464 usr.owner_row = True
1464 usr.owner_row = True
1465 usr.permission = _admin_perm
1465 usr.permission = _admin_perm
1466 owner_row.append(usr)
1466 owner_row.append(usr)
1467
1467
1468 super_admin_ids = []
1468 super_admin_ids = []
1469 super_admin_rows = []
1469 super_admin_rows = []
1470 if with_admins:
1470 if with_admins:
1471 for usr in User.get_all_super_admins():
1471 for usr in User.get_all_super_admins():
1472 super_admin_ids.append(usr.user_id)
1472 super_admin_ids.append(usr.user_id)
1473 # if this admin is also owner, don't double the record
1473 # if this admin is also owner, don't double the record
1474 if usr.user_id == owner_row[0].user_id:
1474 if usr.user_id == owner_row[0].user_id:
1475 owner_row[0].admin_row = True
1475 owner_row[0].admin_row = True
1476 else:
1476 else:
1477 usr = AttributeDict(usr.get_dict())
1477 usr = AttributeDict(usr.get_dict())
1478 usr.admin_row = True
1478 usr.admin_row = True
1479 usr.permission = _admin_perm
1479 usr.permission = _admin_perm
1480 super_admin_rows.append(usr)
1480 super_admin_rows.append(usr)
1481
1481
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1484 joinedload(UserUserGroupToPerm.user),
1484 joinedload(UserUserGroupToPerm.user),
1485 joinedload(UserUserGroupToPerm.permission),)
1485 joinedload(UserUserGroupToPerm.permission),)
1486
1486
1487 # get owners and admins and permissions. We do a trick of re-writing
1487 # get owners and admins and permissions. We do a trick of re-writing
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1489 # has a global reference and changing one object propagates to all
1489 # has a global reference and changing one object propagates to all
1490 # others. This means if admin is also an owner admin_row that change
1490 # others. This means if admin is also an owner admin_row that change
1491 # would propagate to both objects
1491 # would propagate to both objects
1492 perm_rows = []
1492 perm_rows = []
1493 for _usr in q.all():
1493 for _usr in q.all():
1494 usr = AttributeDict(_usr.user.get_dict())
1494 usr = AttributeDict(_usr.user.get_dict())
1495 # if this user is also owner/admin, mark as duplicate record
1495 # if this user is also owner/admin, mark as duplicate record
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1497 usr.duplicate_perm = True
1497 usr.duplicate_perm = True
1498 usr.permission = _usr.permission.permission_name
1498 usr.permission = _usr.permission.permission_name
1499 perm_rows.append(usr)
1499 perm_rows.append(usr)
1500
1500
1501 # filter the perm rows by 'default' first and then sort them by
1501 # filter the perm rows by 'default' first and then sort them by
1502 # admin,write,read,none permissions sorted again alphabetically in
1502 # admin,write,read,none permissions sorted again alphabetically in
1503 # each group
1503 # each group
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1505
1505
1506 user_groups_rows = []
1506 user_groups_rows = []
1507 if expand_from_user_groups:
1507 if expand_from_user_groups:
1508 for ug in self.permission_user_groups(with_members=True):
1508 for ug in self.permission_user_groups(with_members=True):
1509 for user_data in ug.members:
1509 for user_data in ug.members:
1510 user_groups_rows.append(user_data)
1510 user_groups_rows.append(user_data)
1511
1511
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1513
1513
1514 def permission_user_groups(self, with_members=False):
1514 def permission_user_groups(self, with_members=False):
1515 q = UserGroupUserGroupToPerm.query()\
1515 q = UserGroupUserGroupToPerm.query()\
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1520
1520
1521 perm_rows = []
1521 perm_rows = []
1522 for _user_group in q.all():
1522 for _user_group in q.all():
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1524 entry.permission = _user_group.permission.permission_name
1524 entry.permission = _user_group.permission.permission_name
1525 if with_members:
1525 if with_members:
1526 entry.members = [x.user.get_dict()
1526 entry.members = [x.user.get_dict()
1527 for x in _user_group.user_group.members]
1527 for x in _user_group.user_group.members]
1528 perm_rows.append(entry)
1528 perm_rows.append(entry)
1529
1529
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1531 return perm_rows
1531 return perm_rows
1532
1532
1533 def _get_default_perms(self, user_group, suffix=''):
1533 def _get_default_perms(self, user_group, suffix=''):
1534 from rhodecode.model.permission import PermissionModel
1534 from rhodecode.model.permission import PermissionModel
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1536
1536
1537 def get_default_perms(self, suffix=''):
1537 def get_default_perms(self, suffix=''):
1538 return self._get_default_perms(self, suffix)
1538 return self._get_default_perms(self, suffix)
1539
1539
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1541 """
1541 """
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1543 basically forwarded.
1543 basically forwarded.
1544
1544
1545 """
1545 """
1546 user_group = self
1546 user_group = self
1547 data = {
1547 data = {
1548 'users_group_id': user_group.users_group_id,
1548 'users_group_id': user_group.users_group_id,
1549 'group_name': user_group.users_group_name,
1549 'group_name': user_group.users_group_name,
1550 'group_description': user_group.user_group_description,
1550 'group_description': user_group.user_group_description,
1551 'active': user_group.users_group_active,
1551 'active': user_group.users_group_active,
1552 'owner': user_group.user.username,
1552 'owner': user_group.user.username,
1553 'sync': user_group.sync,
1553 'sync': user_group.sync,
1554 'owner_email': user_group.user.email,
1554 'owner_email': user_group.user.email,
1555 }
1555 }
1556
1556
1557 if with_group_members:
1557 if with_group_members:
1558 users = []
1558 users = []
1559 for user in user_group.members:
1559 for user in user_group.members:
1560 user = user.user
1560 user = user.user
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1562 data['users'] = users
1562 data['users'] = users
1563
1563
1564 return data
1564 return data
1565
1565
1566
1566
1567 class UserGroupMember(Base, BaseModel):
1567 class UserGroupMember(Base, BaseModel):
1568 __tablename__ = 'users_groups_members'
1568 __tablename__ = 'users_groups_members'
1569 __table_args__ = (
1569 __table_args__ = (
1570 base_table_args,
1570 base_table_args,
1571 )
1571 )
1572
1572
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1576
1576
1577 user = relationship('User', lazy='joined')
1577 user = relationship('User', lazy='joined')
1578 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1579
1579
1580 def __init__(self, gr_id='', u_id=''):
1580 def __init__(self, gr_id='', u_id=''):
1581 self.users_group_id = gr_id
1581 self.users_group_id = gr_id
1582 self.user_id = u_id
1582 self.user_id = u_id
1583
1583
1584
1584
1585 class RepositoryField(Base, BaseModel):
1585 class RepositoryField(Base, BaseModel):
1586 __tablename__ = 'repositories_fields'
1586 __tablename__ = 'repositories_fields'
1587 __table_args__ = (
1587 __table_args__ = (
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1589 base_table_args,
1589 base_table_args,
1590 )
1590 )
1591
1591
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1593
1593
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1596 field_key = Column("field_key", String(250))
1596 field_key = Column("field_key", String(250))
1597 field_label = Column("field_label", String(1024), nullable=False)
1597 field_label = Column("field_label", String(1024), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1602
1602
1603 repository = relationship('Repository')
1603 repository = relationship('Repository')
1604
1604
1605 @property
1605 @property
1606 def field_key_prefixed(self):
1606 def field_key_prefixed(self):
1607 return 'ex_%s' % self.field_key
1607 return 'ex_%s' % self.field_key
1608
1608
1609 @classmethod
1609 @classmethod
1610 def un_prefix_key(cls, key):
1610 def un_prefix_key(cls, key):
1611 if key.startswith(cls.PREFIX):
1611 if key.startswith(cls.PREFIX):
1612 return key[len(cls.PREFIX):]
1612 return key[len(cls.PREFIX):]
1613 return key
1613 return key
1614
1614
1615 @classmethod
1615 @classmethod
1616 def get_by_key_name(cls, key, repo):
1616 def get_by_key_name(cls, key, repo):
1617 row = cls.query()\
1617 row = cls.query()\
1618 .filter(cls.repository == repo)\
1618 .filter(cls.repository == repo)\
1619 .filter(cls.field_key == key).scalar()
1619 .filter(cls.field_key == key).scalar()
1620 return row
1620 return row
1621
1621
1622
1622
1623 class Repository(Base, BaseModel):
1623 class Repository(Base, BaseModel):
1624 __tablename__ = 'repositories'
1624 __tablename__ = 'repositories'
1625 __table_args__ = (
1625 __table_args__ = (
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1627 base_table_args,
1627 base_table_args,
1628 )
1628 )
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1632
1632
1633 STATE_CREATED = 'repo_state_created'
1633 STATE_CREATED = 'repo_state_created'
1634 STATE_PENDING = 'repo_state_pending'
1634 STATE_PENDING = 'repo_state_pending'
1635 STATE_ERROR = 'repo_state_error'
1635 STATE_ERROR = 'repo_state_error'
1636
1636
1637 LOCK_AUTOMATIC = 'lock_auto'
1637 LOCK_AUTOMATIC = 'lock_auto'
1638 LOCK_API = 'lock_api'
1638 LOCK_API = 'lock_api'
1639 LOCK_WEB = 'lock_web'
1639 LOCK_WEB = 'lock_web'
1640 LOCK_PULL = 'lock_pull'
1640 LOCK_PULL = 'lock_pull'
1641
1641
1642 NAME_SEP = URL_SEP
1642 NAME_SEP = URL_SEP
1643
1643
1644 repo_id = Column(
1644 repo_id = Column(
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1646 primary_key=True)
1646 primary_key=True)
1647 _repo_name = Column(
1647 _repo_name = Column(
1648 "repo_name", Text(), nullable=False, default=None)
1648 "repo_name", Text(), nullable=False, default=None)
1649 _repo_name_hash = Column(
1649 _repo_name_hash = Column(
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1652
1652
1653 clone_uri = Column(
1653 clone_uri = Column(
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1655 default=None)
1655 default=None)
1656 push_uri = Column(
1656 push_uri = Column(
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1658 default=None)
1658 default=None)
1659 repo_type = Column(
1659 repo_type = Column(
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1661 user_id = Column(
1661 user_id = Column(
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1663 unique=False, default=None)
1663 unique=False, default=None)
1664 private = Column(
1664 private = Column(
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1666 archived = Column(
1666 archived = Column(
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1668 enable_statistics = Column(
1668 enable_statistics = Column(
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1670 enable_downloads = Column(
1670 enable_downloads = Column(
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1672 description = Column(
1672 description = Column(
1673 "description", String(10000), nullable=True, unique=None, default=None)
1673 "description", String(10000), nullable=True, unique=None, default=None)
1674 created_on = Column(
1674 created_on = Column(
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1676 default=datetime.datetime.now)
1676 default=datetime.datetime.now)
1677 updated_on = Column(
1677 updated_on = Column(
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1679 default=datetime.datetime.now)
1679 default=datetime.datetime.now)
1680 _landing_revision = Column(
1680 _landing_revision = Column(
1681 "landing_revision", String(255), nullable=False, unique=False,
1681 "landing_revision", String(255), nullable=False, unique=False,
1682 default=None)
1682 default=None)
1683 enable_locking = Column(
1683 enable_locking = Column(
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1685 default=False)
1685 default=False)
1686 _locked = Column(
1686 _locked = Column(
1687 "locked", String(255), nullable=True, unique=False, default=None)
1687 "locked", String(255), nullable=True, unique=False, default=None)
1688 _changeset_cache = Column(
1688 _changeset_cache = Column(
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1690
1690
1691 fork_id = Column(
1691 fork_id = Column(
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1693 nullable=True, unique=False, default=None)
1693 nullable=True, unique=False, default=None)
1694 group_id = Column(
1694 group_id = Column(
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1696 unique=False, default=None)
1696 unique=False, default=None)
1697
1697
1698 user = relationship('User', lazy='joined')
1698 user = relationship('User', lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1701 repo_to_perm = relationship(
1701 repo_to_perm = relationship(
1702 'UserRepoToPerm', cascade='all',
1702 'UserRepoToPerm', cascade='all',
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1706
1706
1707 followers = relationship(
1707 followers = relationship(
1708 'UserFollowing',
1708 'UserFollowing',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1710 cascade='all')
1710 cascade='all')
1711 extra_fields = relationship(
1711 extra_fields = relationship(
1712 'RepositoryField', cascade="all, delete-orphan")
1712 'RepositoryField', cascade="all, delete-orphan")
1713 logs = relationship('UserLog')
1713 logs = relationship('UserLog')
1714 comments = relationship(
1714 comments = relationship(
1715 'ChangesetComment', cascade="all, delete-orphan")
1715 'ChangesetComment', cascade="all, delete-orphan")
1716 pull_requests_source = relationship(
1716 pull_requests_source = relationship(
1717 'PullRequest',
1717 'PullRequest',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1719 cascade="all, delete-orphan")
1719 cascade="all, delete-orphan")
1720 pull_requests_target = relationship(
1720 pull_requests_target = relationship(
1721 'PullRequest',
1721 'PullRequest',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1723 cascade="all, delete-orphan")
1723 cascade="all, delete-orphan")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1727
1727
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1729
1729
1730 # no cascade, set NULL
1730 # no cascade, set NULL
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1732
1732
1733 def __unicode__(self):
1733 def __unicode__(self):
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1735 safe_unicode(self.repo_name))
1735 safe_unicode(self.repo_name))
1736
1736
1737 @hybrid_property
1737 @hybrid_property
1738 def description_safe(self):
1738 def description_safe(self):
1739 from rhodecode.lib import helpers as h
1739 from rhodecode.lib import helpers as h
1740 return h.escape(self.description)
1740 return h.escape(self.description)
1741
1741
1742 @hybrid_property
1742 @hybrid_property
1743 def landing_rev(self):
1743 def landing_rev(self):
1744 # always should return [rev_type, rev]
1744 # always should return [rev_type, rev]
1745 if self._landing_revision:
1745 if self._landing_revision:
1746 _rev_info = self._landing_revision.split(':')
1746 _rev_info = self._landing_revision.split(':')
1747 if len(_rev_info) < 2:
1747 if len(_rev_info) < 2:
1748 _rev_info.insert(0, 'rev')
1748 _rev_info.insert(0, 'rev')
1749 return [_rev_info[0], _rev_info[1]]
1749 return [_rev_info[0], _rev_info[1]]
1750 return [None, None]
1750 return [None, None]
1751
1751
1752 @landing_rev.setter
1752 @landing_rev.setter
1753 def landing_rev(self, val):
1753 def landing_rev(self, val):
1754 if ':' not in val:
1754 if ':' not in val:
1755 raise ValueError('value must be delimited with `:` and consist '
1755 raise ValueError('value must be delimited with `:` and consist '
1756 'of <rev_type>:<rev>, got %s instead' % val)
1756 'of <rev_type>:<rev>, got %s instead' % val)
1757 self._landing_revision = val
1757 self._landing_revision = val
1758
1758
1759 @hybrid_property
1759 @hybrid_property
1760 def locked(self):
1760 def locked(self):
1761 if self._locked:
1761 if self._locked:
1762 user_id, timelocked, reason = self._locked.split(':')
1762 user_id, timelocked, reason = self._locked.split(':')
1763 lock_values = int(user_id), timelocked, reason
1763 lock_values = int(user_id), timelocked, reason
1764 else:
1764 else:
1765 lock_values = [None, None, None]
1765 lock_values = [None, None, None]
1766 return lock_values
1766 return lock_values
1767
1767
1768 @locked.setter
1768 @locked.setter
1769 def locked(self, val):
1769 def locked(self, val):
1770 if val and isinstance(val, (list, tuple)):
1770 if val and isinstance(val, (list, tuple)):
1771 self._locked = ':'.join(map(str, val))
1771 self._locked = ':'.join(map(str, val))
1772 else:
1772 else:
1773 self._locked = None
1773 self._locked = None
1774
1774
1775 @hybrid_property
1775 @hybrid_property
1776 def changeset_cache(self):
1776 def changeset_cache(self):
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1778 dummy = EmptyCommit().__json__()
1778 dummy = EmptyCommit().__json__()
1779 if not self._changeset_cache:
1779 if not self._changeset_cache:
1780 dummy['source_repo_id'] = self.repo_id
1780 dummy['source_repo_id'] = self.repo_id
1781 return json.loads(json.dumps(dummy))
1781 return json.loads(json.dumps(dummy))
1782
1782
1783 try:
1783 try:
1784 return json.loads(self._changeset_cache)
1784 return json.loads(self._changeset_cache)
1785 except TypeError:
1785 except TypeError:
1786 return dummy
1786 return dummy
1787 except Exception:
1787 except Exception:
1788 log.error(traceback.format_exc())
1788 log.error(traceback.format_exc())
1789 return dummy
1789 return dummy
1790
1790
1791 @changeset_cache.setter
1791 @changeset_cache.setter
1792 def changeset_cache(self, val):
1792 def changeset_cache(self, val):
1793 try:
1793 try:
1794 self._changeset_cache = json.dumps(val)
1794 self._changeset_cache = json.dumps(val)
1795 except Exception:
1795 except Exception:
1796 log.error(traceback.format_exc())
1796 log.error(traceback.format_exc())
1797
1797
1798 @hybrid_property
1798 @hybrid_property
1799 def repo_name(self):
1799 def repo_name(self):
1800 return self._repo_name
1800 return self._repo_name
1801
1801
1802 @repo_name.setter
1802 @repo_name.setter
1803 def repo_name(self, value):
1803 def repo_name(self, value):
1804 self._repo_name = value
1804 self._repo_name = value
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1806
1806
1807 @classmethod
1807 @classmethod
1808 def normalize_repo_name(cls, repo_name):
1808 def normalize_repo_name(cls, repo_name):
1809 """
1809 """
1810 Normalizes os specific repo_name to the format internally stored inside
1810 Normalizes os specific repo_name to the format internally stored inside
1811 database using URL_SEP
1811 database using URL_SEP
1812
1812
1813 :param cls:
1813 :param cls:
1814 :param repo_name:
1814 :param repo_name:
1815 """
1815 """
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1817
1817
1818 @classmethod
1818 @classmethod
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1820 session = Session()
1820 session = Session()
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1822
1822
1823 if cache:
1823 if cache:
1824 if identity_cache:
1824 if identity_cache:
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1826 if val:
1826 if val:
1827 return val
1827 return val
1828 else:
1828 else:
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1830 q = q.options(
1830 q = q.options(
1831 FromCache("sql_cache_short", cache_key))
1831 FromCache("sql_cache_short", cache_key))
1832
1832
1833 return q.scalar()
1833 return q.scalar()
1834
1834
1835 @classmethod
1835 @classmethod
1836 def get_by_id_or_repo_name(cls, repoid):
1836 def get_by_id_or_repo_name(cls, repoid):
1837 if isinstance(repoid, (int, long)):
1837 if isinstance(repoid, (int, long)):
1838 try:
1838 try:
1839 repo = cls.get(repoid)
1839 repo = cls.get(repoid)
1840 except ValueError:
1840 except ValueError:
1841 repo = None
1841 repo = None
1842 else:
1842 else:
1843 repo = cls.get_by_repo_name(repoid)
1843 repo = cls.get_by_repo_name(repoid)
1844 return repo
1844 return repo
1845
1845
1846 @classmethod
1846 @classmethod
1847 def get_by_full_path(cls, repo_full_path):
1847 def get_by_full_path(cls, repo_full_path):
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1849 repo_name = cls.normalize_repo_name(repo_name)
1849 repo_name = cls.normalize_repo_name(repo_name)
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1851
1851
1852 @classmethod
1852 @classmethod
1853 def get_repo_forks(cls, repo_id):
1853 def get_repo_forks(cls, repo_id):
1854 return cls.query().filter(Repository.fork_id == repo_id)
1854 return cls.query().filter(Repository.fork_id == repo_id)
1855
1855
1856 @classmethod
1856 @classmethod
1857 def base_path(cls):
1857 def base_path(cls):
1858 """
1858 """
1859 Returns base path when all repos are stored
1859 Returns base path when all repos are stored
1860
1860
1861 :param cls:
1861 :param cls:
1862 """
1862 """
1863 q = Session().query(RhodeCodeUi)\
1863 q = Session().query(RhodeCodeUi)\
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1866 return q.one().ui_value
1866 return q.one().ui_value
1867
1867
1868 @classmethod
1868 @classmethod
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1870 case_insensitive=True, archived=False):
1870 case_insensitive=True, archived=False):
1871 q = Repository.query()
1871 q = Repository.query()
1872
1872
1873 if not archived:
1873 if not archived:
1874 q = q.filter(Repository.archived.isnot(true()))
1874 q = q.filter(Repository.archived.isnot(true()))
1875
1875
1876 if not isinstance(user_id, Optional):
1876 if not isinstance(user_id, Optional):
1877 q = q.filter(Repository.user_id == user_id)
1877 q = q.filter(Repository.user_id == user_id)
1878
1878
1879 if not isinstance(group_id, Optional):
1879 if not isinstance(group_id, Optional):
1880 q = q.filter(Repository.group_id == group_id)
1880 q = q.filter(Repository.group_id == group_id)
1881
1881
1882 if case_insensitive:
1882 if case_insensitive:
1883 q = q.order_by(func.lower(Repository.repo_name))
1883 q = q.order_by(func.lower(Repository.repo_name))
1884 else:
1884 else:
1885 q = q.order_by(Repository.repo_name)
1885 q = q.order_by(Repository.repo_name)
1886
1886
1887 return q.all()
1887 return q.all()
1888
1888
1889 @property
1889 @property
1890 def repo_uid(self):
1890 def repo_uid(self):
1891 return '_{}'.format(self.repo_id)
1891 return '_{}'.format(self.repo_id)
1892
1892
1893 @property
1893 @property
1894 def forks(self):
1894 def forks(self):
1895 """
1895 """
1896 Return forks of this repo
1896 Return forks of this repo
1897 """
1897 """
1898 return Repository.get_repo_forks(self.repo_id)
1898 return Repository.get_repo_forks(self.repo_id)
1899
1899
1900 @property
1900 @property
1901 def parent(self):
1901 def parent(self):
1902 """
1902 """
1903 Returns fork parent
1903 Returns fork parent
1904 """
1904 """
1905 return self.fork
1905 return self.fork
1906
1906
1907 @property
1907 @property
1908 def just_name(self):
1908 def just_name(self):
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1910
1910
1911 @property
1911 @property
1912 def groups_with_parents(self):
1912 def groups_with_parents(self):
1913 groups = []
1913 groups = []
1914 if self.group is None:
1914 if self.group is None:
1915 return groups
1915 return groups
1916
1916
1917 cur_gr = self.group
1917 cur_gr = self.group
1918 groups.insert(0, cur_gr)
1918 groups.insert(0, cur_gr)
1919 while 1:
1919 while 1:
1920 gr = getattr(cur_gr, 'parent_group', None)
1920 gr = getattr(cur_gr, 'parent_group', None)
1921 cur_gr = cur_gr.parent_group
1921 cur_gr = cur_gr.parent_group
1922 if gr is None:
1922 if gr is None:
1923 break
1923 break
1924 groups.insert(0, gr)
1924 groups.insert(0, gr)
1925
1925
1926 return groups
1926 return groups
1927
1927
1928 @property
1928 @property
1929 def groups_and_repo(self):
1929 def groups_and_repo(self):
1930 return self.groups_with_parents, self
1930 return self.groups_with_parents, self
1931
1931
1932 @LazyProperty
1932 @LazyProperty
1933 def repo_path(self):
1933 def repo_path(self):
1934 """
1934 """
1935 Returns base full path for that repository means where it actually
1935 Returns base full path for that repository means where it actually
1936 exists on a filesystem
1936 exists on a filesystem
1937 """
1937 """
1938 q = Session().query(RhodeCodeUi).filter(
1938 q = Session().query(RhodeCodeUi).filter(
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1941 return q.one().ui_value
1941 return q.one().ui_value
1942
1942
1943 @property
1943 @property
1944 def repo_full_path(self):
1944 def repo_full_path(self):
1945 p = [self.repo_path]
1945 p = [self.repo_path]
1946 # we need to split the name by / since this is how we store the
1946 # we need to split the name by / since this is how we store the
1947 # names in the database, but that eventually needs to be converted
1947 # names in the database, but that eventually needs to be converted
1948 # into a valid system path
1948 # into a valid system path
1949 p += self.repo_name.split(self.NAME_SEP)
1949 p += self.repo_name.split(self.NAME_SEP)
1950 return os.path.join(*map(safe_unicode, p))
1950 return os.path.join(*map(safe_unicode, p))
1951
1951
1952 @property
1952 @property
1953 def cache_keys(self):
1953 def cache_keys(self):
1954 """
1954 """
1955 Returns associated cache keys for that repo
1955 Returns associated cache keys for that repo
1956 """
1956 """
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1958 repo_id=self.repo_id)
1958 repo_id=self.repo_id)
1959 return CacheKey.query()\
1959 return CacheKey.query()\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1961 .order_by(CacheKey.cache_key)\
1961 .order_by(CacheKey.cache_key)\
1962 .all()
1962 .all()
1963
1963
1964 @property
1964 @property
1965 def cached_diffs_relative_dir(self):
1965 def cached_diffs_relative_dir(self):
1966 """
1966 """
1967 Return a relative to the repository store path of cached diffs
1967 Return a relative to the repository store path of cached diffs
1968 used for safe display for users, who shouldn't know the absolute store
1968 used for safe display for users, who shouldn't know the absolute store
1969 path
1969 path
1970 """
1970 """
1971 return os.path.join(
1971 return os.path.join(
1972 os.path.dirname(self.repo_name),
1972 os.path.dirname(self.repo_name),
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1974
1974
1975 @property
1975 @property
1976 def cached_diffs_dir(self):
1976 def cached_diffs_dir(self):
1977 path = self.repo_full_path
1977 path = self.repo_full_path
1978 return os.path.join(
1978 return os.path.join(
1979 os.path.dirname(path),
1979 os.path.dirname(path),
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1981
1981
1982 def cached_diffs(self):
1982 def cached_diffs(self):
1983 diff_cache_dir = self.cached_diffs_dir
1983 diff_cache_dir = self.cached_diffs_dir
1984 if os.path.isdir(diff_cache_dir):
1984 if os.path.isdir(diff_cache_dir):
1985 return os.listdir(diff_cache_dir)
1985 return os.listdir(diff_cache_dir)
1986 return []
1986 return []
1987
1987
1988 def shadow_repos(self):
1988 def shadow_repos(self):
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1990 return [
1990 return [
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1992 if x.startswith(shadow_repos_pattern)]
1992 if x.startswith(shadow_repos_pattern)]
1993
1993
1994 def get_new_name(self, repo_name):
1994 def get_new_name(self, repo_name):
1995 """
1995 """
1996 returns new full repository name based on assigned group and new new
1996 returns new full repository name based on assigned group and new new
1997
1997
1998 :param group_name:
1998 :param group_name:
1999 """
1999 """
2000 path_prefix = self.group.full_path_splitted if self.group else []
2000 path_prefix = self.group.full_path_splitted if self.group else []
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2002
2002
2003 @property
2003 @property
2004 def _config(self):
2004 def _config(self):
2005 """
2005 """
2006 Returns db based config object.
2006 Returns db based config object.
2007 """
2007 """
2008 from rhodecode.lib.utils import make_db_config
2008 from rhodecode.lib.utils import make_db_config
2009 return make_db_config(clear_session=False, repo=self)
2009 return make_db_config(clear_session=False, repo=self)
2010
2010
2011 def permissions(self, with_admins=True, with_owner=True,
2011 def permissions(self, with_admins=True, with_owner=True,
2012 expand_from_user_groups=False):
2012 expand_from_user_groups=False):
2013 """
2013 """
2014 Permissions for repositories
2014 Permissions for repositories
2015 """
2015 """
2016 _admin_perm = 'repository.admin'
2016 _admin_perm = 'repository.admin'
2017
2017
2018 owner_row = []
2018 owner_row = []
2019 if with_owner:
2019 if with_owner:
2020 usr = AttributeDict(self.user.get_dict())
2020 usr = AttributeDict(self.user.get_dict())
2021 usr.owner_row = True
2021 usr.owner_row = True
2022 usr.permission = _admin_perm
2022 usr.permission = _admin_perm
2023 usr.permission_id = None
2023 usr.permission_id = None
2024 owner_row.append(usr)
2024 owner_row.append(usr)
2025
2025
2026 super_admin_ids = []
2026 super_admin_ids = []
2027 super_admin_rows = []
2027 super_admin_rows = []
2028 if with_admins:
2028 if with_admins:
2029 for usr in User.get_all_super_admins():
2029 for usr in User.get_all_super_admins():
2030 super_admin_ids.append(usr.user_id)
2030 super_admin_ids.append(usr.user_id)
2031 # if this admin is also owner, don't double the record
2031 # if this admin is also owner, don't double the record
2032 if usr.user_id == owner_row[0].user_id:
2032 if usr.user_id == owner_row[0].user_id:
2033 owner_row[0].admin_row = True
2033 owner_row[0].admin_row = True
2034 else:
2034 else:
2035 usr = AttributeDict(usr.get_dict())
2035 usr = AttributeDict(usr.get_dict())
2036 usr.admin_row = True
2036 usr.admin_row = True
2037 usr.permission = _admin_perm
2037 usr.permission = _admin_perm
2038 usr.permission_id = None
2038 usr.permission_id = None
2039 super_admin_rows.append(usr)
2039 super_admin_rows.append(usr)
2040
2040
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2043 joinedload(UserRepoToPerm.user),
2043 joinedload(UserRepoToPerm.user),
2044 joinedload(UserRepoToPerm.permission),)
2044 joinedload(UserRepoToPerm.permission),)
2045
2045
2046 # get owners and admins and permissions. We do a trick of re-writing
2046 # get owners and admins and permissions. We do a trick of re-writing
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2048 # has a global reference and changing one object propagates to all
2048 # has a global reference and changing one object propagates to all
2049 # others. This means if admin is also an owner admin_row that change
2049 # others. This means if admin is also an owner admin_row that change
2050 # would propagate to both objects
2050 # would propagate to both objects
2051 perm_rows = []
2051 perm_rows = []
2052 for _usr in q.all():
2052 for _usr in q.all():
2053 usr = AttributeDict(_usr.user.get_dict())
2053 usr = AttributeDict(_usr.user.get_dict())
2054 # if this user is also owner/admin, mark as duplicate record
2054 # if this user is also owner/admin, mark as duplicate record
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2056 usr.duplicate_perm = True
2056 usr.duplicate_perm = True
2057 # also check if this permission is maybe used by branch_permissions
2057 # also check if this permission is maybe used by branch_permissions
2058 if _usr.branch_perm_entry:
2058 if _usr.branch_perm_entry:
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2060
2060
2061 usr.permission = _usr.permission.permission_name
2061 usr.permission = _usr.permission.permission_name
2062 usr.permission_id = _usr.repo_to_perm_id
2062 usr.permission_id = _usr.repo_to_perm_id
2063 perm_rows.append(usr)
2063 perm_rows.append(usr)
2064
2064
2065 # filter the perm rows by 'default' first and then sort them by
2065 # filter the perm rows by 'default' first and then sort them by
2066 # admin,write,read,none permissions sorted again alphabetically in
2066 # admin,write,read,none permissions sorted again alphabetically in
2067 # each group
2067 # each group
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2069
2069
2070 user_groups_rows = []
2070 user_groups_rows = []
2071 if expand_from_user_groups:
2071 if expand_from_user_groups:
2072 for ug in self.permission_user_groups(with_members=True):
2072 for ug in self.permission_user_groups(with_members=True):
2073 for user_data in ug.members:
2073 for user_data in ug.members:
2074 user_groups_rows.append(user_data)
2074 user_groups_rows.append(user_data)
2075
2075
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2077
2077
2078 def permission_user_groups(self, with_members=True):
2078 def permission_user_groups(self, with_members=True):
2079 q = UserGroupRepoToPerm.query()\
2079 q = UserGroupRepoToPerm.query()\
2080 .filter(UserGroupRepoToPerm.repository == self)
2080 .filter(UserGroupRepoToPerm.repository == self)
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2082 joinedload(UserGroupRepoToPerm.users_group),
2082 joinedload(UserGroupRepoToPerm.users_group),
2083 joinedload(UserGroupRepoToPerm.permission),)
2083 joinedload(UserGroupRepoToPerm.permission),)
2084
2084
2085 perm_rows = []
2085 perm_rows = []
2086 for _user_group in q.all():
2086 for _user_group in q.all():
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2088 entry.permission = _user_group.permission.permission_name
2088 entry.permission = _user_group.permission.permission_name
2089 if with_members:
2089 if with_members:
2090 entry.members = [x.user.get_dict()
2090 entry.members = [x.user.get_dict()
2091 for x in _user_group.users_group.members]
2091 for x in _user_group.users_group.members]
2092 perm_rows.append(entry)
2092 perm_rows.append(entry)
2093
2093
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2095 return perm_rows
2095 return perm_rows
2096
2096
2097 def get_api_data(self, include_secrets=False):
2097 def get_api_data(self, include_secrets=False):
2098 """
2098 """
2099 Common function for generating repo api data
2099 Common function for generating repo api data
2100
2100
2101 :param include_secrets: See :meth:`User.get_api_data`.
2101 :param include_secrets: See :meth:`User.get_api_data`.
2102
2102
2103 """
2103 """
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2105 # move this methods on models level.
2105 # move this methods on models level.
2106 from rhodecode.model.settings import SettingsModel
2106 from rhodecode.model.settings import SettingsModel
2107 from rhodecode.model.repo import RepoModel
2107 from rhodecode.model.repo import RepoModel
2108
2108
2109 repo = self
2109 repo = self
2110 _user_id, _time, _reason = self.locked
2110 _user_id, _time, _reason = self.locked
2111
2111
2112 data = {
2112 data = {
2113 'repo_id': repo.repo_id,
2113 'repo_id': repo.repo_id,
2114 'repo_name': repo.repo_name,
2114 'repo_name': repo.repo_name,
2115 'repo_type': repo.repo_type,
2115 'repo_type': repo.repo_type,
2116 'clone_uri': repo.clone_uri or '',
2116 'clone_uri': repo.clone_uri or '',
2117 'push_uri': repo.push_uri or '',
2117 'push_uri': repo.push_uri or '',
2118 'url': RepoModel().get_url(self),
2118 'url': RepoModel().get_url(self),
2119 'private': repo.private,
2119 'private': repo.private,
2120 'created_on': repo.created_on,
2120 'created_on': repo.created_on,
2121 'description': repo.description_safe,
2121 'description': repo.description_safe,
2122 'landing_rev': repo.landing_rev,
2122 'landing_rev': repo.landing_rev,
2123 'owner': repo.user.username,
2123 'owner': repo.user.username,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2126 'enable_statistics': repo.enable_statistics,
2126 'enable_statistics': repo.enable_statistics,
2127 'enable_locking': repo.enable_locking,
2127 'enable_locking': repo.enable_locking,
2128 'enable_downloads': repo.enable_downloads,
2128 'enable_downloads': repo.enable_downloads,
2129 'last_changeset': repo.changeset_cache,
2129 'last_changeset': repo.changeset_cache,
2130 'locked_by': User.get(_user_id).get_api_data(
2130 'locked_by': User.get(_user_id).get_api_data(
2131 include_secrets=include_secrets) if _user_id else None,
2131 include_secrets=include_secrets) if _user_id else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2133 'lock_reason': _reason if _reason else None,
2133 'lock_reason': _reason if _reason else None,
2134 }
2134 }
2135
2135
2136 # TODO: mikhail: should be per-repo settings here
2136 # TODO: mikhail: should be per-repo settings here
2137 rc_config = SettingsModel().get_all_settings()
2137 rc_config = SettingsModel().get_all_settings()
2138 repository_fields = str2bool(
2138 repository_fields = str2bool(
2139 rc_config.get('rhodecode_repository_fields'))
2139 rc_config.get('rhodecode_repository_fields'))
2140 if repository_fields:
2140 if repository_fields:
2141 for f in self.extra_fields:
2141 for f in self.extra_fields:
2142 data[f.field_key_prefixed] = f.field_value
2142 data[f.field_key_prefixed] = f.field_value
2143
2143
2144 return data
2144 return data
2145
2145
2146 @classmethod
2146 @classmethod
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2148 if not lock_time:
2148 if not lock_time:
2149 lock_time = time.time()
2149 lock_time = time.time()
2150 if not lock_reason:
2150 if not lock_reason:
2151 lock_reason = cls.LOCK_AUTOMATIC
2151 lock_reason = cls.LOCK_AUTOMATIC
2152 repo.locked = [user_id, lock_time, lock_reason]
2152 repo.locked = [user_id, lock_time, lock_reason]
2153 Session().add(repo)
2153 Session().add(repo)
2154 Session().commit()
2154 Session().commit()
2155
2155
2156 @classmethod
2156 @classmethod
2157 def unlock(cls, repo):
2157 def unlock(cls, repo):
2158 repo.locked = None
2158 repo.locked = None
2159 Session().add(repo)
2159 Session().add(repo)
2160 Session().commit()
2160 Session().commit()
2161
2161
2162 @classmethod
2162 @classmethod
2163 def getlock(cls, repo):
2163 def getlock(cls, repo):
2164 return repo.locked
2164 return repo.locked
2165
2165
2166 def is_user_lock(self, user_id):
2166 def is_user_lock(self, user_id):
2167 if self.lock[0]:
2167 if self.lock[0]:
2168 lock_user_id = safe_int(self.lock[0])
2168 lock_user_id = safe_int(self.lock[0])
2169 user_id = safe_int(user_id)
2169 user_id = safe_int(user_id)
2170 # both are ints, and they are equal
2170 # both are ints, and they are equal
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2172
2172
2173 return False
2173 return False
2174
2174
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2176 """
2176 """
2177 Checks locking on this repository, if locking is enabled and lock is
2177 Checks locking on this repository, if locking is enabled and lock is
2178 present returns a tuple of make_lock, locked, locked_by.
2178 present returns a tuple of make_lock, locked, locked_by.
2179 make_lock can have 3 states None (do nothing) True, make lock
2179 make_lock can have 3 states None (do nothing) True, make lock
2180 False release lock, This value is later propagated to hooks, which
2180 False release lock, This value is later propagated to hooks, which
2181 do the locking. Think about this as signals passed to hooks what to do.
2181 do the locking. Think about this as signals passed to hooks what to do.
2182
2182
2183 """
2183 """
2184 # TODO: johbo: This is part of the business logic and should be moved
2184 # TODO: johbo: This is part of the business logic and should be moved
2185 # into the RepositoryModel.
2185 # into the RepositoryModel.
2186
2186
2187 if action not in ('push', 'pull'):
2187 if action not in ('push', 'pull'):
2188 raise ValueError("Invalid action value: %s" % repr(action))
2188 raise ValueError("Invalid action value: %s" % repr(action))
2189
2189
2190 # defines if locked error should be thrown to user
2190 # defines if locked error should be thrown to user
2191 currently_locked = False
2191 currently_locked = False
2192 # defines if new lock should be made, tri-state
2192 # defines if new lock should be made, tri-state
2193 make_lock = None
2193 make_lock = None
2194 repo = self
2194 repo = self
2195 user = User.get(user_id)
2195 user = User.get(user_id)
2196
2196
2197 lock_info = repo.locked
2197 lock_info = repo.locked
2198
2198
2199 if repo and (repo.enable_locking or not only_when_enabled):
2199 if repo and (repo.enable_locking or not only_when_enabled):
2200 if action == 'push':
2200 if action == 'push':
2201 # check if it's already locked !, if it is compare users
2201 # check if it's already locked !, if it is compare users
2202 locked_by_user_id = lock_info[0]
2202 locked_by_user_id = lock_info[0]
2203 if user.user_id == locked_by_user_id:
2203 if user.user_id == locked_by_user_id:
2204 log.debug(
2204 log.debug(
2205 'Got `push` action from user %s, now unlocking', user)
2205 'Got `push` action from user %s, now unlocking', user)
2206 # unlock if we have push from user who locked
2206 # unlock if we have push from user who locked
2207 make_lock = False
2207 make_lock = False
2208 else:
2208 else:
2209 # we're not the same user who locked, ban with
2209 # we're not the same user who locked, ban with
2210 # code defined in settings (default is 423 HTTP Locked) !
2210 # code defined in settings (default is 423 HTTP Locked) !
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2212 currently_locked = True
2212 currently_locked = True
2213 elif action == 'pull':
2213 elif action == 'pull':
2214 # [0] user [1] date
2214 # [0] user [1] date
2215 if lock_info[0] and lock_info[1]:
2215 if lock_info[0] and lock_info[1]:
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2217 currently_locked = True
2217 currently_locked = True
2218 else:
2218 else:
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2220 make_lock = True
2220 make_lock = True
2221
2221
2222 else:
2222 else:
2223 log.debug('Repository %s do not have locking enabled', repo)
2223 log.debug('Repository %s do not have locking enabled', repo)
2224
2224
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2226 make_lock, currently_locked, lock_info)
2226 make_lock, currently_locked, lock_info)
2227
2227
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2231 # if we don't have at least write permission we cannot make a lock
2231 # if we don't have at least write permission we cannot make a lock
2232 log.debug('lock state reset back to FALSE due to lack '
2232 log.debug('lock state reset back to FALSE due to lack '
2233 'of at least read permission')
2233 'of at least read permission')
2234 make_lock = False
2234 make_lock = False
2235
2235
2236 return make_lock, currently_locked, lock_info
2236 return make_lock, currently_locked, lock_info
2237
2237
2238 @property
2238 @property
2239 def last_commit_cache_update_diff(self):
2239 def last_commit_cache_update_diff(self):
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2241
2241
2242 @property
2242 @property
2243 def last_commit_change(self):
2243 def last_commit_change(self):
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2245 empty_date = datetime.datetime.fromtimestamp(0)
2245 empty_date = datetime.datetime.fromtimestamp(0)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2247 try:
2247 try:
2248 return parse_datetime(date_latest)
2248 return parse_datetime(date_latest)
2249 except Exception:
2249 except Exception:
2250 return empty_date
2250 return empty_date
2251
2251
2252 @property
2252 @property
2253 def last_db_change(self):
2253 def last_db_change(self):
2254 return self.updated_on
2254 return self.updated_on
2255
2255
2256 @property
2256 @property
2257 def clone_uri_hidden(self):
2257 def clone_uri_hidden(self):
2258 clone_uri = self.clone_uri
2258 clone_uri = self.clone_uri
2259 if clone_uri:
2259 if clone_uri:
2260 import urlobject
2260 import urlobject
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2262 if url_obj.password:
2262 if url_obj.password:
2263 clone_uri = url_obj.with_password('*****')
2263 clone_uri = url_obj.with_password('*****')
2264 return clone_uri
2264 return clone_uri
2265
2265
2266 @property
2266 @property
2267 def push_uri_hidden(self):
2267 def push_uri_hidden(self):
2268 push_uri = self.push_uri
2268 push_uri = self.push_uri
2269 if push_uri:
2269 if push_uri:
2270 import urlobject
2270 import urlobject
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2272 if url_obj.password:
2272 if url_obj.password:
2273 push_uri = url_obj.with_password('*****')
2273 push_uri = url_obj.with_password('*****')
2274 return push_uri
2274 return push_uri
2275
2275
2276 def clone_url(self, **override):
2276 def clone_url(self, **override):
2277 from rhodecode.model.settings import SettingsModel
2277 from rhodecode.model.settings import SettingsModel
2278
2278
2279 uri_tmpl = None
2279 uri_tmpl = None
2280 if 'with_id' in override:
2280 if 'with_id' in override:
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2282 del override['with_id']
2282 del override['with_id']
2283
2283
2284 if 'uri_tmpl' in override:
2284 if 'uri_tmpl' in override:
2285 uri_tmpl = override['uri_tmpl']
2285 uri_tmpl = override['uri_tmpl']
2286 del override['uri_tmpl']
2286 del override['uri_tmpl']
2287
2287
2288 ssh = False
2288 ssh = False
2289 if 'ssh' in override:
2289 if 'ssh' in override:
2290 ssh = True
2290 ssh = True
2291 del override['ssh']
2291 del override['ssh']
2292
2292
2293 # we didn't override our tmpl from **overrides
2293 # we didn't override our tmpl from **overrides
2294 request = get_current_request()
2294 request = get_current_request()
2295 if not uri_tmpl:
2295 if not uri_tmpl:
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2297 rc_config = request.call_context.rc_config
2297 rc_config = request.call_context.rc_config
2298 else:
2298 else:
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2300
2300
2301 if ssh:
2301 if ssh:
2302 uri_tmpl = rc_config.get(
2302 uri_tmpl = rc_config.get(
2303 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2303 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2304
2304
2305 else:
2305 else:
2306 uri_tmpl = rc_config.get(
2306 uri_tmpl = rc_config.get(
2307 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2307 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2308
2308
2309 return get_clone_url(request=request,
2309 return get_clone_url(request=request,
2310 uri_tmpl=uri_tmpl,
2310 uri_tmpl=uri_tmpl,
2311 repo_name=self.repo_name,
2311 repo_name=self.repo_name,
2312 repo_id=self.repo_id,
2312 repo_id=self.repo_id,
2313 repo_type=self.repo_type,
2313 repo_type=self.repo_type,
2314 **override)
2314 **override)
2315
2315
2316 def set_state(self, state):
2316 def set_state(self, state):
2317 self.repo_state = state
2317 self.repo_state = state
2318 Session().add(self)
2318 Session().add(self)
2319 #==========================================================================
2319 #==========================================================================
2320 # SCM PROPERTIES
2320 # SCM PROPERTIES
2321 #==========================================================================
2321 #==========================================================================
2322
2322
2323 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2323 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2324 return get_commit_safe(
2324 return get_commit_safe(
2325 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2325 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2326
2326
2327 def get_changeset(self, rev=None, pre_load=None):
2327 def get_changeset(self, rev=None, pre_load=None):
2328 warnings.warn("Use get_commit", DeprecationWarning)
2328 warnings.warn("Use get_commit", DeprecationWarning)
2329 commit_id = None
2329 commit_id = None
2330 commit_idx = None
2330 commit_idx = None
2331 if isinstance(rev, compat.string_types):
2331 if isinstance(rev, compat.string_types):
2332 commit_id = rev
2332 commit_id = rev
2333 else:
2333 else:
2334 commit_idx = rev
2334 commit_idx = rev
2335 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2335 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2336 pre_load=pre_load)
2336 pre_load=pre_load)
2337
2337
2338 def get_landing_commit(self):
2338 def get_landing_commit(self):
2339 """
2339 """
2340 Returns landing commit, or if that doesn't exist returns the tip
2340 Returns landing commit, or if that doesn't exist returns the tip
2341 """
2341 """
2342 _rev_type, _rev = self.landing_rev
2342 _rev_type, _rev = self.landing_rev
2343 commit = self.get_commit(_rev)
2343 commit = self.get_commit(_rev)
2344 if isinstance(commit, EmptyCommit):
2344 if isinstance(commit, EmptyCommit):
2345 return self.get_commit()
2345 return self.get_commit()
2346 return commit
2346 return commit
2347
2347
2348 def flush_commit_cache(self):
2348 def flush_commit_cache(self):
2349 self.update_commit_cache(cs_cache={'raw_id':'0'})
2349 self.update_commit_cache(cs_cache={'raw_id':'0'})
2350 self.update_commit_cache()
2350 self.update_commit_cache()
2351
2351
2352 def update_commit_cache(self, cs_cache=None, config=None):
2352 def update_commit_cache(self, cs_cache=None, config=None):
2353 """
2353 """
2354 Update cache of last commit for repository, keys should be::
2354 Update cache of last commit for repository, keys should be::
2355
2355
2356 source_repo_id
2356 source_repo_id
2357 short_id
2357 short_id
2358 raw_id
2358 raw_id
2359 revision
2359 revision
2360 parents
2360 parents
2361 message
2361 message
2362 date
2362 date
2363 author
2363 author
2364 updated_on
2364 updated_on
2365
2365
2366 """
2366 """
2367 from rhodecode.lib.vcs.backends.base import BaseChangeset
2367 from rhodecode.lib.vcs.backends.base import BaseChangeset
2368 if cs_cache is None:
2368 if cs_cache is None:
2369 # use no-cache version here
2369 # use no-cache version here
2370 scm_repo = self.scm_instance(cache=False, config=config)
2370 scm_repo = self.scm_instance(cache=False, config=config)
2371
2371
2372 empty = scm_repo is None or scm_repo.is_empty()
2372 empty = scm_repo is None or scm_repo.is_empty()
2373 if not empty:
2373 if not empty:
2374 cs_cache = scm_repo.get_commit(
2374 cs_cache = scm_repo.get_commit(
2375 pre_load=["author", "date", "message", "parents", "branch"])
2375 pre_load=["author", "date", "message", "parents", "branch"])
2376 else:
2376 else:
2377 cs_cache = EmptyCommit()
2377 cs_cache = EmptyCommit()
2378
2378
2379 if isinstance(cs_cache, BaseChangeset):
2379 if isinstance(cs_cache, BaseChangeset):
2380 cs_cache = cs_cache.__json__()
2380 cs_cache = cs_cache.__json__()
2381
2381
2382 def is_outdated(new_cs_cache):
2382 def is_outdated(new_cs_cache):
2383 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2383 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2384 new_cs_cache['revision'] != self.changeset_cache['revision']):
2384 new_cs_cache['revision'] != self.changeset_cache['revision']):
2385 return True
2385 return True
2386 return False
2386 return False
2387
2387
2388 # check if we have maybe already latest cached revision
2388 # check if we have maybe already latest cached revision
2389 if is_outdated(cs_cache) or not self.changeset_cache:
2389 if is_outdated(cs_cache) or not self.changeset_cache:
2390 _default = datetime.datetime.utcnow()
2390 _default = datetime.datetime.utcnow()
2391 last_change = cs_cache.get('date') or _default
2391 last_change = cs_cache.get('date') or _default
2392 # we check if last update is newer than the new value
2392 # we check if last update is newer than the new value
2393 # if yes, we use the current timestamp instead. Imagine you get
2393 # if yes, we use the current timestamp instead. Imagine you get
2394 # old commit pushed 1y ago, we'd set last update 1y to ago.
2394 # old commit pushed 1y ago, we'd set last update 1y to ago.
2395 last_change_timestamp = datetime_to_time(last_change)
2395 last_change_timestamp = datetime_to_time(last_change)
2396 current_timestamp = datetime_to_time(last_change)
2396 current_timestamp = datetime_to_time(last_change)
2397 if last_change_timestamp > current_timestamp:
2397 if last_change_timestamp > current_timestamp:
2398 cs_cache['date'] = _default
2398 cs_cache['date'] = _default
2399
2399
2400 cs_cache['updated_on'] = time.time()
2400 cs_cache['updated_on'] = time.time()
2401 self.changeset_cache = cs_cache
2401 self.changeset_cache = cs_cache
2402 self.updated_on = last_change
2402 self.updated_on = last_change
2403 Session().add(self)
2403 Session().add(self)
2404 Session().commit()
2404 Session().commit()
2405
2405
2406 log.debug('updated repo `%s` with new commit cache %s',
2406 log.debug('updated repo `%s` with new commit cache %s',
2407 self.repo_name, cs_cache)
2407 self.repo_name, cs_cache)
2408 else:
2408 else:
2409 cs_cache = self.changeset_cache
2409 cs_cache = self.changeset_cache
2410 cs_cache['updated_on'] = time.time()
2410 cs_cache['updated_on'] = time.time()
2411 self.changeset_cache = cs_cache
2411 self.changeset_cache = cs_cache
2412 Session().add(self)
2412 Session().add(self)
2413 Session().commit()
2413 Session().commit()
2414
2414
2415 log.debug('Skipping update_commit_cache for repo:`%s` '
2415 log.debug('Skipping update_commit_cache for repo:`%s` '
2416 'commit already with latest changes', self.repo_name)
2416 'commit already with latest changes', self.repo_name)
2417
2417
2418 @property
2418 @property
2419 def tip(self):
2419 def tip(self):
2420 return self.get_commit('tip')
2420 return self.get_commit('tip')
2421
2421
2422 @property
2422 @property
2423 def author(self):
2423 def author(self):
2424 return self.tip.author
2424 return self.tip.author
2425
2425
2426 @property
2426 @property
2427 def last_change(self):
2427 def last_change(self):
2428 return self.scm_instance().last_change
2428 return self.scm_instance().last_change
2429
2429
2430 def get_comments(self, revisions=None):
2430 def get_comments(self, revisions=None):
2431 """
2431 """
2432 Returns comments for this repository grouped by revisions
2432 Returns comments for this repository grouped by revisions
2433
2433
2434 :param revisions: filter query by revisions only
2434 :param revisions: filter query by revisions only
2435 """
2435 """
2436 cmts = ChangesetComment.query()\
2436 cmts = ChangesetComment.query()\
2437 .filter(ChangesetComment.repo == self)
2437 .filter(ChangesetComment.repo == self)
2438 if revisions:
2438 if revisions:
2439 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2439 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2440 grouped = collections.defaultdict(list)
2440 grouped = collections.defaultdict(list)
2441 for cmt in cmts.all():
2441 for cmt in cmts.all():
2442 grouped[cmt.revision].append(cmt)
2442 grouped[cmt.revision].append(cmt)
2443 return grouped
2443 return grouped
2444
2444
2445 def statuses(self, revisions=None):
2445 def statuses(self, revisions=None):
2446 """
2446 """
2447 Returns statuses for this repository
2447 Returns statuses for this repository
2448
2448
2449 :param revisions: list of revisions to get statuses for
2449 :param revisions: list of revisions to get statuses for
2450 """
2450 """
2451 statuses = ChangesetStatus.query()\
2451 statuses = ChangesetStatus.query()\
2452 .filter(ChangesetStatus.repo == self)\
2452 .filter(ChangesetStatus.repo == self)\
2453 .filter(ChangesetStatus.version == 0)
2453 .filter(ChangesetStatus.version == 0)
2454
2454
2455 if revisions:
2455 if revisions:
2456 # Try doing the filtering in chunks to avoid hitting limits
2456 # Try doing the filtering in chunks to avoid hitting limits
2457 size = 500
2457 size = 500
2458 status_results = []
2458 status_results = []
2459 for chunk in xrange(0, len(revisions), size):
2459 for chunk in xrange(0, len(revisions), size):
2460 status_results += statuses.filter(
2460 status_results += statuses.filter(
2461 ChangesetStatus.revision.in_(
2461 ChangesetStatus.revision.in_(
2462 revisions[chunk: chunk+size])
2462 revisions[chunk: chunk+size])
2463 ).all()
2463 ).all()
2464 else:
2464 else:
2465 status_results = statuses.all()
2465 status_results = statuses.all()
2466
2466
2467 grouped = {}
2467 grouped = {}
2468
2468
2469 # maybe we have open new pullrequest without a status?
2469 # maybe we have open new pullrequest without a status?
2470 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2470 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2471 status_lbl = ChangesetStatus.get_status_lbl(stat)
2471 status_lbl = ChangesetStatus.get_status_lbl(stat)
2472 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2472 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2473 for rev in pr.revisions:
2473 for rev in pr.revisions:
2474 pr_id = pr.pull_request_id
2474 pr_id = pr.pull_request_id
2475 pr_repo = pr.target_repo.repo_name
2475 pr_repo = pr.target_repo.repo_name
2476 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2476 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2477
2477
2478 for stat in status_results:
2478 for stat in status_results:
2479 pr_id = pr_repo = None
2479 pr_id = pr_repo = None
2480 if stat.pull_request:
2480 if stat.pull_request:
2481 pr_id = stat.pull_request.pull_request_id
2481 pr_id = stat.pull_request.pull_request_id
2482 pr_repo = stat.pull_request.target_repo.repo_name
2482 pr_repo = stat.pull_request.target_repo.repo_name
2483 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2483 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2484 pr_id, pr_repo]
2484 pr_id, pr_repo]
2485 return grouped
2485 return grouped
2486
2486
2487 # ==========================================================================
2487 # ==========================================================================
2488 # SCM CACHE INSTANCE
2488 # SCM CACHE INSTANCE
2489 # ==========================================================================
2489 # ==========================================================================
2490
2490
2491 def scm_instance(self, **kwargs):
2491 def scm_instance(self, **kwargs):
2492 import rhodecode
2492 import rhodecode
2493
2493
2494 # Passing a config will not hit the cache currently only used
2494 # Passing a config will not hit the cache currently only used
2495 # for repo2dbmapper
2495 # for repo2dbmapper
2496 config = kwargs.pop('config', None)
2496 config = kwargs.pop('config', None)
2497 cache = kwargs.pop('cache', None)
2497 cache = kwargs.pop('cache', None)
2498 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2498 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2499 if vcs_full_cache is not None:
2499 if vcs_full_cache is not None:
2500 # allows override global config
2500 # allows override global config
2501 full_cache = vcs_full_cache
2501 full_cache = vcs_full_cache
2502 else:
2502 else:
2503 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2503 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2504 # if cache is NOT defined use default global, else we have a full
2504 # if cache is NOT defined use default global, else we have a full
2505 # control over cache behaviour
2505 # control over cache behaviour
2506 if cache is None and full_cache and not config:
2506 if cache is None and full_cache and not config:
2507 log.debug('Initializing pure cached instance for %s', self.repo_path)
2507 log.debug('Initializing pure cached instance for %s', self.repo_path)
2508 return self._get_instance_cached()
2508 return self._get_instance_cached()
2509
2509
2510 # cache here is sent to the "vcs server"
2510 # cache here is sent to the "vcs server"
2511 return self._get_instance(cache=bool(cache), config=config)
2511 return self._get_instance(cache=bool(cache), config=config)
2512
2512
2513 def _get_instance_cached(self):
2513 def _get_instance_cached(self):
2514 from rhodecode.lib import rc_cache
2514 from rhodecode.lib import rc_cache
2515
2515
2516 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2516 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2517 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2517 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2518 repo_id=self.repo_id)
2518 repo_id=self.repo_id)
2519 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2519 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2520
2520
2521 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2521 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2522 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2522 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2523 return self._get_instance(repo_state_uid=_cache_state_uid)
2523 return self._get_instance(repo_state_uid=_cache_state_uid)
2524
2524
2525 # we must use thread scoped cache here,
2525 # we must use thread scoped cache here,
2526 # because each thread of gevent needs it's own not shared connection and cache
2526 # because each thread of gevent needs it's own not shared connection and cache
2527 # we also alter `args` so the cache key is individual for every green thread.
2527 # we also alter `args` so the cache key is individual for every green thread.
2528 inv_context_manager = rc_cache.InvalidationContext(
2528 inv_context_manager = rc_cache.InvalidationContext(
2529 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2529 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2530 thread_scoped=True)
2530 thread_scoped=True)
2531 with inv_context_manager as invalidation_context:
2531 with inv_context_manager as invalidation_context:
2532 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2532 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2533 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2533 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2534
2534
2535 # re-compute and store cache if we get invalidate signal
2535 # re-compute and store cache if we get invalidate signal
2536 if invalidation_context.should_invalidate():
2536 if invalidation_context.should_invalidate():
2537 instance = get_instance_cached.refresh(*args)
2537 instance = get_instance_cached.refresh(*args)
2538 else:
2538 else:
2539 instance = get_instance_cached(*args)
2539 instance = get_instance_cached(*args)
2540
2540
2541 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2541 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2542 return instance
2542 return instance
2543
2543
2544 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2544 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2545 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2545 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2546 self.repo_type, self.repo_path, cache)
2546 self.repo_type, self.repo_path, cache)
2547 config = config or self._config
2547 config = config or self._config
2548 custom_wire = {
2548 custom_wire = {
2549 'cache': cache, # controls the vcs.remote cache
2549 'cache': cache, # controls the vcs.remote cache
2550 'repo_state_uid': repo_state_uid
2550 'repo_state_uid': repo_state_uid
2551 }
2551 }
2552 repo = get_vcs_instance(
2552 repo = get_vcs_instance(
2553 repo_path=safe_str(self.repo_full_path),
2553 repo_path=safe_str(self.repo_full_path),
2554 config=config,
2554 config=config,
2555 with_wire=custom_wire,
2555 with_wire=custom_wire,
2556 create=False,
2556 create=False,
2557 _vcs_alias=self.repo_type)
2557 _vcs_alias=self.repo_type)
2558 if repo is not None:
2558 if repo is not None:
2559 repo.count() # cache rebuild
2559 repo.count() # cache rebuild
2560 return repo
2560 return repo
2561
2561
2562 def get_shadow_repository_path(self, workspace_id):
2562 def get_shadow_repository_path(self, workspace_id):
2563 from rhodecode.lib.vcs.backends.base import BaseRepository
2563 from rhodecode.lib.vcs.backends.base import BaseRepository
2564 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2564 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2565 self.repo_full_path, self.repo_id, workspace_id)
2565 self.repo_full_path, self.repo_id, workspace_id)
2566 return shadow_repo_path
2566 return shadow_repo_path
2567
2567
2568 def __json__(self):
2568 def __json__(self):
2569 return {'landing_rev': self.landing_rev}
2569 return {'landing_rev': self.landing_rev}
2570
2570
2571 def get_dict(self):
2571 def get_dict(self):
2572
2572
2573 # Since we transformed `repo_name` to a hybrid property, we need to
2573 # Since we transformed `repo_name` to a hybrid property, we need to
2574 # keep compatibility with the code which uses `repo_name` field.
2574 # keep compatibility with the code which uses `repo_name` field.
2575
2575
2576 result = super(Repository, self).get_dict()
2576 result = super(Repository, self).get_dict()
2577 result['repo_name'] = result.pop('_repo_name', None)
2577 result['repo_name'] = result.pop('_repo_name', None)
2578 return result
2578 return result
2579
2579
2580
2580
2581 class RepoGroup(Base, BaseModel):
2581 class RepoGroup(Base, BaseModel):
2582 __tablename__ = 'groups'
2582 __tablename__ = 'groups'
2583 __table_args__ = (
2583 __table_args__ = (
2584 UniqueConstraint('group_name', 'group_parent_id'),
2584 UniqueConstraint('group_name', 'group_parent_id'),
2585 base_table_args,
2585 base_table_args,
2586 )
2586 )
2587 __mapper_args__ = {'order_by': 'group_name'}
2587 __mapper_args__ = {'order_by': 'group_name'}
2588
2588
2589 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2589 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2590
2590
2591 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2591 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2592 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2592 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2593 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2593 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2594 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2594 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2595 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2595 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2596 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2596 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2598 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2598 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2599 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2599 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2600 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2600 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2601 _changeset_cache = Column(
2601 _changeset_cache = Column(
2602 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2602 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2603
2603
2604 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2604 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2605 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2605 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2606 parent_group = relationship('RepoGroup', remote_side=group_id)
2606 parent_group = relationship('RepoGroup', remote_side=group_id)
2607 user = relationship('User')
2607 user = relationship('User')
2608 integrations = relationship('Integration', cascade="all, delete-orphan")
2608 integrations = relationship('Integration', cascade="all, delete-orphan")
2609
2609
2610 # no cascade, set NULL
2610 # no cascade, set NULL
2611 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2611 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2612
2612
2613 def __init__(self, group_name='', parent_group=None):
2613 def __init__(self, group_name='', parent_group=None):
2614 self.group_name = group_name
2614 self.group_name = group_name
2615 self.parent_group = parent_group
2615 self.parent_group = parent_group
2616
2616
2617 def __unicode__(self):
2617 def __unicode__(self):
2618 return u"<%s('id:%s:%s')>" % (
2618 return u"<%s('id:%s:%s')>" % (
2619 self.__class__.__name__, self.group_id, self.group_name)
2619 self.__class__.__name__, self.group_id, self.group_name)
2620
2620
2621 @hybrid_property
2621 @hybrid_property
2622 def group_name(self):
2622 def group_name(self):
2623 return self._group_name
2623 return self._group_name
2624
2624
2625 @group_name.setter
2625 @group_name.setter
2626 def group_name(self, value):
2626 def group_name(self, value):
2627 self._group_name = value
2627 self._group_name = value
2628 self.group_name_hash = self.hash_repo_group_name(value)
2628 self.group_name_hash = self.hash_repo_group_name(value)
2629
2629
2630 @hybrid_property
2630 @hybrid_property
2631 def changeset_cache(self):
2631 def changeset_cache(self):
2632 from rhodecode.lib.vcs.backends.base import EmptyCommit
2632 from rhodecode.lib.vcs.backends.base import EmptyCommit
2633 dummy = EmptyCommit().__json__()
2633 dummy = EmptyCommit().__json__()
2634 if not self._changeset_cache:
2634 if not self._changeset_cache:
2635 dummy['source_repo_id'] = ''
2635 dummy['source_repo_id'] = ''
2636 return json.loads(json.dumps(dummy))
2636 return json.loads(json.dumps(dummy))
2637
2637
2638 try:
2638 try:
2639 return json.loads(self._changeset_cache)
2639 return json.loads(self._changeset_cache)
2640 except TypeError:
2640 except TypeError:
2641 return dummy
2641 return dummy
2642 except Exception:
2642 except Exception:
2643 log.error(traceback.format_exc())
2643 log.error(traceback.format_exc())
2644 return dummy
2644 return dummy
2645
2645
2646 @changeset_cache.setter
2646 @changeset_cache.setter
2647 def changeset_cache(self, val):
2647 def changeset_cache(self, val):
2648 try:
2648 try:
2649 self._changeset_cache = json.dumps(val)
2649 self._changeset_cache = json.dumps(val)
2650 except Exception:
2650 except Exception:
2651 log.error(traceback.format_exc())
2651 log.error(traceback.format_exc())
2652
2652
2653 @validates('group_parent_id')
2653 @validates('group_parent_id')
2654 def validate_group_parent_id(self, key, val):
2654 def validate_group_parent_id(self, key, val):
2655 """
2655 """
2656 Check cycle references for a parent group to self
2656 Check cycle references for a parent group to self
2657 """
2657 """
2658 if self.group_id and val:
2658 if self.group_id and val:
2659 assert val != self.group_id
2659 assert val != self.group_id
2660
2660
2661 return val
2661 return val
2662
2662
2663 @hybrid_property
2663 @hybrid_property
2664 def description_safe(self):
2664 def description_safe(self):
2665 from rhodecode.lib import helpers as h
2665 from rhodecode.lib import helpers as h
2666 return h.escape(self.group_description)
2666 return h.escape(self.group_description)
2667
2667
2668 @classmethod
2668 @classmethod
2669 def hash_repo_group_name(cls, repo_group_name):
2669 def hash_repo_group_name(cls, repo_group_name):
2670 val = remove_formatting(repo_group_name)
2670 val = remove_formatting(repo_group_name)
2671 val = safe_str(val).lower()
2671 val = safe_str(val).lower()
2672 chars = []
2672 chars = []
2673 for c in val:
2673 for c in val:
2674 if c not in string.ascii_letters:
2674 if c not in string.ascii_letters:
2675 c = str(ord(c))
2675 c = str(ord(c))
2676 chars.append(c)
2676 chars.append(c)
2677
2677
2678 return ''.join(chars)
2678 return ''.join(chars)
2679
2679
2680 @classmethod
2680 @classmethod
2681 def _generate_choice(cls, repo_group):
2681 def _generate_choice(cls, repo_group):
2682 from webhelpers2.html import literal as _literal
2682 from webhelpers2.html import literal as _literal
2683 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2683 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2684 return repo_group.group_id, _name(repo_group.full_path_splitted)
2684 return repo_group.group_id, _name(repo_group.full_path_splitted)
2685
2685
2686 @classmethod
2686 @classmethod
2687 def groups_choices(cls, groups=None, show_empty_group=True):
2687 def groups_choices(cls, groups=None, show_empty_group=True):
2688 if not groups:
2688 if not groups:
2689 groups = cls.query().all()
2689 groups = cls.query().all()
2690
2690
2691 repo_groups = []
2691 repo_groups = []
2692 if show_empty_group:
2692 if show_empty_group:
2693 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2693 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2694
2694
2695 repo_groups.extend([cls._generate_choice(x) for x in groups])
2695 repo_groups.extend([cls._generate_choice(x) for x in groups])
2696
2696
2697 repo_groups = sorted(
2697 repo_groups = sorted(
2698 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2698 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2699 return repo_groups
2699 return repo_groups
2700
2700
2701 @classmethod
2701 @classmethod
2702 def url_sep(cls):
2702 def url_sep(cls):
2703 return URL_SEP
2703 return URL_SEP
2704
2704
2705 @classmethod
2705 @classmethod
2706 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2706 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2707 if case_insensitive:
2707 if case_insensitive:
2708 gr = cls.query().filter(func.lower(cls.group_name)
2708 gr = cls.query().filter(func.lower(cls.group_name)
2709 == func.lower(group_name))
2709 == func.lower(group_name))
2710 else:
2710 else:
2711 gr = cls.query().filter(cls.group_name == group_name)
2711 gr = cls.query().filter(cls.group_name == group_name)
2712 if cache:
2712 if cache:
2713 name_key = _hash_key(group_name)
2713 name_key = _hash_key(group_name)
2714 gr = gr.options(
2714 gr = gr.options(
2715 FromCache("sql_cache_short", "get_group_%s" % name_key))
2715 FromCache("sql_cache_short", "get_group_%s" % name_key))
2716 return gr.scalar()
2716 return gr.scalar()
2717
2717
2718 @classmethod
2718 @classmethod
2719 def get_user_personal_repo_group(cls, user_id):
2719 def get_user_personal_repo_group(cls, user_id):
2720 user = User.get(user_id)
2720 user = User.get(user_id)
2721 if user.username == User.DEFAULT_USER:
2721 if user.username == User.DEFAULT_USER:
2722 return None
2722 return None
2723
2723
2724 return cls.query()\
2724 return cls.query()\
2725 .filter(cls.personal == true()) \
2725 .filter(cls.personal == true()) \
2726 .filter(cls.user == user) \
2726 .filter(cls.user == user) \
2727 .order_by(cls.group_id.asc()) \
2727 .order_by(cls.group_id.asc()) \
2728 .first()
2728 .first()
2729
2729
2730 @classmethod
2730 @classmethod
2731 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2731 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2732 case_insensitive=True):
2732 case_insensitive=True):
2733 q = RepoGroup.query()
2733 q = RepoGroup.query()
2734
2734
2735 if not isinstance(user_id, Optional):
2735 if not isinstance(user_id, Optional):
2736 q = q.filter(RepoGroup.user_id == user_id)
2736 q = q.filter(RepoGroup.user_id == user_id)
2737
2737
2738 if not isinstance(group_id, Optional):
2738 if not isinstance(group_id, Optional):
2739 q = q.filter(RepoGroup.group_parent_id == group_id)
2739 q = q.filter(RepoGroup.group_parent_id == group_id)
2740
2740
2741 if case_insensitive:
2741 if case_insensitive:
2742 q = q.order_by(func.lower(RepoGroup.group_name))
2742 q = q.order_by(func.lower(RepoGroup.group_name))
2743 else:
2743 else:
2744 q = q.order_by(RepoGroup.group_name)
2744 q = q.order_by(RepoGroup.group_name)
2745 return q.all()
2745 return q.all()
2746
2746
2747 @property
2747 @property
2748 def parents(self, parents_recursion_limit = 10):
2748 def parents(self, parents_recursion_limit = 10):
2749 groups = []
2749 groups = []
2750 if self.parent_group is None:
2750 if self.parent_group is None:
2751 return groups
2751 return groups
2752 cur_gr = self.parent_group
2752 cur_gr = self.parent_group
2753 groups.insert(0, cur_gr)
2753 groups.insert(0, cur_gr)
2754 cnt = 0
2754 cnt = 0
2755 while 1:
2755 while 1:
2756 cnt += 1
2756 cnt += 1
2757 gr = getattr(cur_gr, 'parent_group', None)
2757 gr = getattr(cur_gr, 'parent_group', None)
2758 cur_gr = cur_gr.parent_group
2758 cur_gr = cur_gr.parent_group
2759 if gr is None:
2759 if gr is None:
2760 break
2760 break
2761 if cnt == parents_recursion_limit:
2761 if cnt == parents_recursion_limit:
2762 # this will prevent accidental infinit loops
2762 # this will prevent accidental infinit loops
2763 log.error('more than %s parents found for group %s, stopping '
2763 log.error('more than %s parents found for group %s, stopping '
2764 'recursive parent fetching', parents_recursion_limit, self)
2764 'recursive parent fetching', parents_recursion_limit, self)
2765 break
2765 break
2766
2766
2767 groups.insert(0, gr)
2767 groups.insert(0, gr)
2768 return groups
2768 return groups
2769
2769
2770 @property
2770 @property
2771 def last_commit_cache_update_diff(self):
2771 def last_commit_cache_update_diff(self):
2772 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2772 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2773
2773
2774 @property
2774 @property
2775 def last_commit_change(self):
2775 def last_commit_change(self):
2776 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2776 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2777 empty_date = datetime.datetime.fromtimestamp(0)
2777 empty_date = datetime.datetime.fromtimestamp(0)
2778 date_latest = self.changeset_cache.get('date', empty_date)
2778 date_latest = self.changeset_cache.get('date', empty_date)
2779 try:
2779 try:
2780 return parse_datetime(date_latest)
2780 return parse_datetime(date_latest)
2781 except Exception:
2781 except Exception:
2782 return empty_date
2782 return empty_date
2783
2783
2784 @property
2784 @property
2785 def last_db_change(self):
2785 def last_db_change(self):
2786 return self.updated_on
2786 return self.updated_on
2787
2787
2788 @property
2788 @property
2789 def children(self):
2789 def children(self):
2790 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2790 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2791
2791
2792 @property
2792 @property
2793 def name(self):
2793 def name(self):
2794 return self.group_name.split(RepoGroup.url_sep())[-1]
2794 return self.group_name.split(RepoGroup.url_sep())[-1]
2795
2795
2796 @property
2796 @property
2797 def full_path(self):
2797 def full_path(self):
2798 return self.group_name
2798 return self.group_name
2799
2799
2800 @property
2800 @property
2801 def full_path_splitted(self):
2801 def full_path_splitted(self):
2802 return self.group_name.split(RepoGroup.url_sep())
2802 return self.group_name.split(RepoGroup.url_sep())
2803
2803
2804 @property
2804 @property
2805 def repositories(self):
2805 def repositories(self):
2806 return Repository.query()\
2806 return Repository.query()\
2807 .filter(Repository.group == self)\
2807 .filter(Repository.group == self)\
2808 .order_by(Repository.repo_name)
2808 .order_by(Repository.repo_name)
2809
2809
2810 @property
2810 @property
2811 def repositories_recursive_count(self):
2811 def repositories_recursive_count(self):
2812 cnt = self.repositories.count()
2812 cnt = self.repositories.count()
2813
2813
2814 def children_count(group):
2814 def children_count(group):
2815 cnt = 0
2815 cnt = 0
2816 for child in group.children:
2816 for child in group.children:
2817 cnt += child.repositories.count()
2817 cnt += child.repositories.count()
2818 cnt += children_count(child)
2818 cnt += children_count(child)
2819 return cnt
2819 return cnt
2820
2820
2821 return cnt + children_count(self)
2821 return cnt + children_count(self)
2822
2822
2823 def _recursive_objects(self, include_repos=True, include_groups=True):
2823 def _recursive_objects(self, include_repos=True, include_groups=True):
2824 all_ = []
2824 all_ = []
2825
2825
2826 def _get_members(root_gr):
2826 def _get_members(root_gr):
2827 if include_repos:
2827 if include_repos:
2828 for r in root_gr.repositories:
2828 for r in root_gr.repositories:
2829 all_.append(r)
2829 all_.append(r)
2830 childs = root_gr.children.all()
2830 childs = root_gr.children.all()
2831 if childs:
2831 if childs:
2832 for gr in childs:
2832 for gr in childs:
2833 if include_groups:
2833 if include_groups:
2834 all_.append(gr)
2834 all_.append(gr)
2835 _get_members(gr)
2835 _get_members(gr)
2836
2836
2837 root_group = []
2837 root_group = []
2838 if include_groups:
2838 if include_groups:
2839 root_group = [self]
2839 root_group = [self]
2840
2840
2841 _get_members(self)
2841 _get_members(self)
2842 return root_group + all_
2842 return root_group + all_
2843
2843
2844 def recursive_groups_and_repos(self):
2844 def recursive_groups_and_repos(self):
2845 """
2845 """
2846 Recursive return all groups, with repositories in those groups
2846 Recursive return all groups, with repositories in those groups
2847 """
2847 """
2848 return self._recursive_objects()
2848 return self._recursive_objects()
2849
2849
2850 def recursive_groups(self):
2850 def recursive_groups(self):
2851 """
2851 """
2852 Returns all children groups for this group including children of children
2852 Returns all children groups for this group including children of children
2853 """
2853 """
2854 return self._recursive_objects(include_repos=False)
2854 return self._recursive_objects(include_repos=False)
2855
2855
2856 def recursive_repos(self):
2856 def recursive_repos(self):
2857 """
2857 """
2858 Returns all children repositories for this group
2858 Returns all children repositories for this group
2859 """
2859 """
2860 return self._recursive_objects(include_groups=False)
2860 return self._recursive_objects(include_groups=False)
2861
2861
2862 def get_new_name(self, group_name):
2862 def get_new_name(self, group_name):
2863 """
2863 """
2864 returns new full group name based on parent and new name
2864 returns new full group name based on parent and new name
2865
2865
2866 :param group_name:
2866 :param group_name:
2867 """
2867 """
2868 path_prefix = (self.parent_group.full_path_splitted if
2868 path_prefix = (self.parent_group.full_path_splitted if
2869 self.parent_group else [])
2869 self.parent_group else [])
2870 return RepoGroup.url_sep().join(path_prefix + [group_name])
2870 return RepoGroup.url_sep().join(path_prefix + [group_name])
2871
2871
2872 def update_commit_cache(self, config=None):
2872 def update_commit_cache(self, config=None):
2873 """
2873 """
2874 Update cache of last changeset for newest repository inside this group, keys should be::
2874 Update cache of last changeset for newest repository inside this group, keys should be::
2875
2875
2876 source_repo_id
2876 source_repo_id
2877 short_id
2877 short_id
2878 raw_id
2878 raw_id
2879 revision
2879 revision
2880 parents
2880 parents
2881 message
2881 message
2882 date
2882 date
2883 author
2883 author
2884
2884
2885 """
2885 """
2886 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2886 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2887
2887
2888 def repo_groups_and_repos():
2888 def repo_groups_and_repos():
2889 all_entries = OrderedDefaultDict(list)
2889 all_entries = OrderedDefaultDict(list)
2890
2890
2891 def _get_members(root_gr, pos=0):
2891 def _get_members(root_gr, pos=0):
2892
2892
2893 for repo in root_gr.repositories:
2893 for repo in root_gr.repositories:
2894 all_entries[root_gr].append(repo)
2894 all_entries[root_gr].append(repo)
2895
2895
2896 # fill in all parent positions
2896 # fill in all parent positions
2897 for parent_group in root_gr.parents:
2897 for parent_group in root_gr.parents:
2898 all_entries[parent_group].extend(all_entries[root_gr])
2898 all_entries[parent_group].extend(all_entries[root_gr])
2899
2899
2900 children_groups = root_gr.children.all()
2900 children_groups = root_gr.children.all()
2901 if children_groups:
2901 if children_groups:
2902 for cnt, gr in enumerate(children_groups, 1):
2902 for cnt, gr in enumerate(children_groups, 1):
2903 _get_members(gr, pos=pos+cnt)
2903 _get_members(gr, pos=pos+cnt)
2904
2904
2905 _get_members(root_gr=self)
2905 _get_members(root_gr=self)
2906 return all_entries
2906 return all_entries
2907
2907
2908 empty_date = datetime.datetime.fromtimestamp(0)
2908 empty_date = datetime.datetime.fromtimestamp(0)
2909 for repo_group, repos in repo_groups_and_repos().items():
2909 for repo_group, repos in repo_groups_and_repos().items():
2910
2910
2911 latest_repo_cs_cache = {}
2911 latest_repo_cs_cache = {}
2912 _date_latest = empty_date
2912 _date_latest = empty_date
2913 for repo in repos:
2913 for repo in repos:
2914 repo_cs_cache = repo.changeset_cache
2914 repo_cs_cache = repo.changeset_cache
2915 date_latest = latest_repo_cs_cache.get('date', empty_date)
2915 date_latest = latest_repo_cs_cache.get('date', empty_date)
2916 date_current = repo_cs_cache.get('date', empty_date)
2916 date_current = repo_cs_cache.get('date', empty_date)
2917 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2917 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2918 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2918 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2919 latest_repo_cs_cache = repo_cs_cache
2919 latest_repo_cs_cache = repo_cs_cache
2920 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2920 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2921 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2921 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2922
2922
2923 latest_repo_cs_cache['updated_on'] = time.time()
2923 latest_repo_cs_cache['updated_on'] = time.time()
2924 repo_group.changeset_cache = latest_repo_cs_cache
2924 repo_group.changeset_cache = latest_repo_cs_cache
2925 repo_group.updated_on = _date_latest
2925 repo_group.updated_on = _date_latest
2926 Session().add(repo_group)
2926 Session().add(repo_group)
2927 Session().commit()
2927 Session().commit()
2928
2928
2929 log.debug('updated repo group `%s` with new commit cache %s',
2929 log.debug('updated repo group `%s` with new commit cache %s',
2930 repo_group.group_name, latest_repo_cs_cache)
2930 repo_group.group_name, latest_repo_cs_cache)
2931
2931
2932 def permissions(self, with_admins=True, with_owner=True,
2932 def permissions(self, with_admins=True, with_owner=True,
2933 expand_from_user_groups=False):
2933 expand_from_user_groups=False):
2934 """
2934 """
2935 Permissions for repository groups
2935 Permissions for repository groups
2936 """
2936 """
2937 _admin_perm = 'group.admin'
2937 _admin_perm = 'group.admin'
2938
2938
2939 owner_row = []
2939 owner_row = []
2940 if with_owner:
2940 if with_owner:
2941 usr = AttributeDict(self.user.get_dict())
2941 usr = AttributeDict(self.user.get_dict())
2942 usr.owner_row = True
2942 usr.owner_row = True
2943 usr.permission = _admin_perm
2943 usr.permission = _admin_perm
2944 owner_row.append(usr)
2944 owner_row.append(usr)
2945
2945
2946 super_admin_ids = []
2946 super_admin_ids = []
2947 super_admin_rows = []
2947 super_admin_rows = []
2948 if with_admins:
2948 if with_admins:
2949 for usr in User.get_all_super_admins():
2949 for usr in User.get_all_super_admins():
2950 super_admin_ids.append(usr.user_id)
2950 super_admin_ids.append(usr.user_id)
2951 # if this admin is also owner, don't double the record
2951 # if this admin is also owner, don't double the record
2952 if usr.user_id == owner_row[0].user_id:
2952 if usr.user_id == owner_row[0].user_id:
2953 owner_row[0].admin_row = True
2953 owner_row[0].admin_row = True
2954 else:
2954 else:
2955 usr = AttributeDict(usr.get_dict())
2955 usr = AttributeDict(usr.get_dict())
2956 usr.admin_row = True
2956 usr.admin_row = True
2957 usr.permission = _admin_perm
2957 usr.permission = _admin_perm
2958 super_admin_rows.append(usr)
2958 super_admin_rows.append(usr)
2959
2959
2960 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2960 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2961 q = q.options(joinedload(UserRepoGroupToPerm.group),
2961 q = q.options(joinedload(UserRepoGroupToPerm.group),
2962 joinedload(UserRepoGroupToPerm.user),
2962 joinedload(UserRepoGroupToPerm.user),
2963 joinedload(UserRepoGroupToPerm.permission),)
2963 joinedload(UserRepoGroupToPerm.permission),)
2964
2964
2965 # get owners and admins and permissions. We do a trick of re-writing
2965 # get owners and admins and permissions. We do a trick of re-writing
2966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2967 # has a global reference and changing one object propagates to all
2967 # has a global reference and changing one object propagates to all
2968 # others. This means if admin is also an owner admin_row that change
2968 # others. This means if admin is also an owner admin_row that change
2969 # would propagate to both objects
2969 # would propagate to both objects
2970 perm_rows = []
2970 perm_rows = []
2971 for _usr in q.all():
2971 for _usr in q.all():
2972 usr = AttributeDict(_usr.user.get_dict())
2972 usr = AttributeDict(_usr.user.get_dict())
2973 # if this user is also owner/admin, mark as duplicate record
2973 # if this user is also owner/admin, mark as duplicate record
2974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2975 usr.duplicate_perm = True
2975 usr.duplicate_perm = True
2976 usr.permission = _usr.permission.permission_name
2976 usr.permission = _usr.permission.permission_name
2977 perm_rows.append(usr)
2977 perm_rows.append(usr)
2978
2978
2979 # filter the perm rows by 'default' first and then sort them by
2979 # filter the perm rows by 'default' first and then sort them by
2980 # admin,write,read,none permissions sorted again alphabetically in
2980 # admin,write,read,none permissions sorted again alphabetically in
2981 # each group
2981 # each group
2982 perm_rows = sorted(perm_rows, key=display_user_sort)
2982 perm_rows = sorted(perm_rows, key=display_user_sort)
2983
2983
2984 user_groups_rows = []
2984 user_groups_rows = []
2985 if expand_from_user_groups:
2985 if expand_from_user_groups:
2986 for ug in self.permission_user_groups(with_members=True):
2986 for ug in self.permission_user_groups(with_members=True):
2987 for user_data in ug.members:
2987 for user_data in ug.members:
2988 user_groups_rows.append(user_data)
2988 user_groups_rows.append(user_data)
2989
2989
2990 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2990 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2991
2991
2992 def permission_user_groups(self, with_members=False):
2992 def permission_user_groups(self, with_members=False):
2993 q = UserGroupRepoGroupToPerm.query()\
2993 q = UserGroupRepoGroupToPerm.query()\
2994 .filter(UserGroupRepoGroupToPerm.group == self)
2994 .filter(UserGroupRepoGroupToPerm.group == self)
2995 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2995 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2996 joinedload(UserGroupRepoGroupToPerm.users_group),
2996 joinedload(UserGroupRepoGroupToPerm.users_group),
2997 joinedload(UserGroupRepoGroupToPerm.permission),)
2997 joinedload(UserGroupRepoGroupToPerm.permission),)
2998
2998
2999 perm_rows = []
2999 perm_rows = []
3000 for _user_group in q.all():
3000 for _user_group in q.all():
3001 entry = AttributeDict(_user_group.users_group.get_dict())
3001 entry = AttributeDict(_user_group.users_group.get_dict())
3002 entry.permission = _user_group.permission.permission_name
3002 entry.permission = _user_group.permission.permission_name
3003 if with_members:
3003 if with_members:
3004 entry.members = [x.user.get_dict()
3004 entry.members = [x.user.get_dict()
3005 for x in _user_group.users_group.members]
3005 for x in _user_group.users_group.members]
3006 perm_rows.append(entry)
3006 perm_rows.append(entry)
3007
3007
3008 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3008 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3009 return perm_rows
3009 return perm_rows
3010
3010
3011 def get_api_data(self):
3011 def get_api_data(self):
3012 """
3012 """
3013 Common function for generating api data
3013 Common function for generating api data
3014
3014
3015 """
3015 """
3016 group = self
3016 group = self
3017 data = {
3017 data = {
3018 'group_id': group.group_id,
3018 'group_id': group.group_id,
3019 'group_name': group.group_name,
3019 'group_name': group.group_name,
3020 'group_description': group.description_safe,
3020 'group_description': group.description_safe,
3021 'parent_group': group.parent_group.group_name if group.parent_group else None,
3021 'parent_group': group.parent_group.group_name if group.parent_group else None,
3022 'repositories': [x.repo_name for x in group.repositories],
3022 'repositories': [x.repo_name for x in group.repositories],
3023 'owner': group.user.username,
3023 'owner': group.user.username,
3024 }
3024 }
3025 return data
3025 return data
3026
3026
3027 def get_dict(self):
3027 def get_dict(self):
3028 # Since we transformed `group_name` to a hybrid property, we need to
3028 # Since we transformed `group_name` to a hybrid property, we need to
3029 # keep compatibility with the code which uses `group_name` field.
3029 # keep compatibility with the code which uses `group_name` field.
3030 result = super(RepoGroup, self).get_dict()
3030 result = super(RepoGroup, self).get_dict()
3031 result['group_name'] = result.pop('_group_name', None)
3031 result['group_name'] = result.pop('_group_name', None)
3032 return result
3032 return result
3033
3033
3034
3034
3035 class Permission(Base, BaseModel):
3035 class Permission(Base, BaseModel):
3036 __tablename__ = 'permissions'
3036 __tablename__ = 'permissions'
3037 __table_args__ = (
3037 __table_args__ = (
3038 Index('p_perm_name_idx', 'permission_name'),
3038 Index('p_perm_name_idx', 'permission_name'),
3039 base_table_args,
3039 base_table_args,
3040 )
3040 )
3041
3041
3042 PERMS = [
3042 PERMS = [
3043 ('hg.admin', _('RhodeCode Super Administrator')),
3043 ('hg.admin', _('RhodeCode Super Administrator')),
3044
3044
3045 ('repository.none', _('Repository no access')),
3045 ('repository.none', _('Repository no access')),
3046 ('repository.read', _('Repository read access')),
3046 ('repository.read', _('Repository read access')),
3047 ('repository.write', _('Repository write access')),
3047 ('repository.write', _('Repository write access')),
3048 ('repository.admin', _('Repository admin access')),
3048 ('repository.admin', _('Repository admin access')),
3049
3049
3050 ('group.none', _('Repository group no access')),
3050 ('group.none', _('Repository group no access')),
3051 ('group.read', _('Repository group read access')),
3051 ('group.read', _('Repository group read access')),
3052 ('group.write', _('Repository group write access')),
3052 ('group.write', _('Repository group write access')),
3053 ('group.admin', _('Repository group admin access')),
3053 ('group.admin', _('Repository group admin access')),
3054
3054
3055 ('usergroup.none', _('User group no access')),
3055 ('usergroup.none', _('User group no access')),
3056 ('usergroup.read', _('User group read access')),
3056 ('usergroup.read', _('User group read access')),
3057 ('usergroup.write', _('User group write access')),
3057 ('usergroup.write', _('User group write access')),
3058 ('usergroup.admin', _('User group admin access')),
3058 ('usergroup.admin', _('User group admin access')),
3059
3059
3060 ('branch.none', _('Branch no permissions')),
3060 ('branch.none', _('Branch no permissions')),
3061 ('branch.merge', _('Branch access by web merge')),
3061 ('branch.merge', _('Branch access by web merge')),
3062 ('branch.push', _('Branch access by push')),
3062 ('branch.push', _('Branch access by push')),
3063 ('branch.push_force', _('Branch access by push with force')),
3063 ('branch.push_force', _('Branch access by push with force')),
3064
3064
3065 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3065 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3066 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3066 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3067
3067
3068 ('hg.usergroup.create.false', _('User Group creation disabled')),
3068 ('hg.usergroup.create.false', _('User Group creation disabled')),
3069 ('hg.usergroup.create.true', _('User Group creation enabled')),
3069 ('hg.usergroup.create.true', _('User Group creation enabled')),
3070
3070
3071 ('hg.create.none', _('Repository creation disabled')),
3071 ('hg.create.none', _('Repository creation disabled')),
3072 ('hg.create.repository', _('Repository creation enabled')),
3072 ('hg.create.repository', _('Repository creation enabled')),
3073 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3073 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3074 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3074 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3075
3075
3076 ('hg.fork.none', _('Repository forking disabled')),
3076 ('hg.fork.none', _('Repository forking disabled')),
3077 ('hg.fork.repository', _('Repository forking enabled')),
3077 ('hg.fork.repository', _('Repository forking enabled')),
3078
3078
3079 ('hg.register.none', _('Registration disabled')),
3079 ('hg.register.none', _('Registration disabled')),
3080 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3080 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3081 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3081 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3082
3082
3083 ('hg.password_reset.enabled', _('Password reset enabled')),
3083 ('hg.password_reset.enabled', _('Password reset enabled')),
3084 ('hg.password_reset.hidden', _('Password reset hidden')),
3084 ('hg.password_reset.hidden', _('Password reset hidden')),
3085 ('hg.password_reset.disabled', _('Password reset disabled')),
3085 ('hg.password_reset.disabled', _('Password reset disabled')),
3086
3086
3087 ('hg.extern_activate.manual', _('Manual activation of external account')),
3087 ('hg.extern_activate.manual', _('Manual activation of external account')),
3088 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3088 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3089
3089
3090 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3090 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3091 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3091 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3092 ]
3092 ]
3093
3093
3094 # definition of system default permissions for DEFAULT user, created on
3094 # definition of system default permissions for DEFAULT user, created on
3095 # system setup
3095 # system setup
3096 DEFAULT_USER_PERMISSIONS = [
3096 DEFAULT_USER_PERMISSIONS = [
3097 # object perms
3097 # object perms
3098 'repository.read',
3098 'repository.read',
3099 'group.read',
3099 'group.read',
3100 'usergroup.read',
3100 'usergroup.read',
3101 # branch, for backward compat we need same value as before so forced pushed
3101 # branch, for backward compat we need same value as before so forced pushed
3102 'branch.push_force',
3102 'branch.push_force',
3103 # global
3103 # global
3104 'hg.create.repository',
3104 'hg.create.repository',
3105 'hg.repogroup.create.false',
3105 'hg.repogroup.create.false',
3106 'hg.usergroup.create.false',
3106 'hg.usergroup.create.false',
3107 'hg.create.write_on_repogroup.true',
3107 'hg.create.write_on_repogroup.true',
3108 'hg.fork.repository',
3108 'hg.fork.repository',
3109 'hg.register.manual_activate',
3109 'hg.register.manual_activate',
3110 'hg.password_reset.enabled',
3110 'hg.password_reset.enabled',
3111 'hg.extern_activate.auto',
3111 'hg.extern_activate.auto',
3112 'hg.inherit_default_perms.true',
3112 'hg.inherit_default_perms.true',
3113 ]
3113 ]
3114
3114
3115 # defines which permissions are more important higher the more important
3115 # defines which permissions are more important higher the more important
3116 # Weight defines which permissions are more important.
3116 # Weight defines which permissions are more important.
3117 # The higher number the more important.
3117 # The higher number the more important.
3118 PERM_WEIGHTS = {
3118 PERM_WEIGHTS = {
3119 'repository.none': 0,
3119 'repository.none': 0,
3120 'repository.read': 1,
3120 'repository.read': 1,
3121 'repository.write': 3,
3121 'repository.write': 3,
3122 'repository.admin': 4,
3122 'repository.admin': 4,
3123
3123
3124 'group.none': 0,
3124 'group.none': 0,
3125 'group.read': 1,
3125 'group.read': 1,
3126 'group.write': 3,
3126 'group.write': 3,
3127 'group.admin': 4,
3127 'group.admin': 4,
3128
3128
3129 'usergroup.none': 0,
3129 'usergroup.none': 0,
3130 'usergroup.read': 1,
3130 'usergroup.read': 1,
3131 'usergroup.write': 3,
3131 'usergroup.write': 3,
3132 'usergroup.admin': 4,
3132 'usergroup.admin': 4,
3133
3133
3134 'branch.none': 0,
3134 'branch.none': 0,
3135 'branch.merge': 1,
3135 'branch.merge': 1,
3136 'branch.push': 3,
3136 'branch.push': 3,
3137 'branch.push_force': 4,
3137 'branch.push_force': 4,
3138
3138
3139 'hg.repogroup.create.false': 0,
3139 'hg.repogroup.create.false': 0,
3140 'hg.repogroup.create.true': 1,
3140 'hg.repogroup.create.true': 1,
3141
3141
3142 'hg.usergroup.create.false': 0,
3142 'hg.usergroup.create.false': 0,
3143 'hg.usergroup.create.true': 1,
3143 'hg.usergroup.create.true': 1,
3144
3144
3145 'hg.fork.none': 0,
3145 'hg.fork.none': 0,
3146 'hg.fork.repository': 1,
3146 'hg.fork.repository': 1,
3147 'hg.create.none': 0,
3147 'hg.create.none': 0,
3148 'hg.create.repository': 1
3148 'hg.create.repository': 1
3149 }
3149 }
3150
3150
3151 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3151 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3152 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3152 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3153 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3153 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3154
3154
3155 def __unicode__(self):
3155 def __unicode__(self):
3156 return u"<%s('%s:%s')>" % (
3156 return u"<%s('%s:%s')>" % (
3157 self.__class__.__name__, self.permission_id, self.permission_name
3157 self.__class__.__name__, self.permission_id, self.permission_name
3158 )
3158 )
3159
3159
3160 @classmethod
3160 @classmethod
3161 def get_by_key(cls, key):
3161 def get_by_key(cls, key):
3162 return cls.query().filter(cls.permission_name == key).scalar()
3162 return cls.query().filter(cls.permission_name == key).scalar()
3163
3163
3164 @classmethod
3164 @classmethod
3165 def get_default_repo_perms(cls, user_id, repo_id=None):
3165 def get_default_repo_perms(cls, user_id, repo_id=None):
3166 q = Session().query(UserRepoToPerm, Repository, Permission)\
3166 q = Session().query(UserRepoToPerm, Repository, Permission)\
3167 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3167 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3168 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3168 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3169 .filter(UserRepoToPerm.user_id == user_id)
3169 .filter(UserRepoToPerm.user_id == user_id)
3170 if repo_id:
3170 if repo_id:
3171 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3171 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3172 return q.all()
3172 return q.all()
3173
3173
3174 @classmethod
3174 @classmethod
3175 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3175 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3176 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3176 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3177 .join(
3177 .join(
3178 Permission,
3178 Permission,
3179 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3179 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3180 .join(
3180 .join(
3181 UserRepoToPerm,
3181 UserRepoToPerm,
3182 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3182 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3183 .filter(UserRepoToPerm.user_id == user_id)
3183 .filter(UserRepoToPerm.user_id == user_id)
3184
3184
3185 if repo_id:
3185 if repo_id:
3186 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3186 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3187 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3187 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3188
3188
3189 @classmethod
3189 @classmethod
3190 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3190 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3191 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3191 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3192 .join(
3192 .join(
3193 Permission,
3193 Permission,
3194 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3194 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3195 .join(
3195 .join(
3196 Repository,
3196 Repository,
3197 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3197 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3198 .join(
3198 .join(
3199 UserGroup,
3199 UserGroup,
3200 UserGroupRepoToPerm.users_group_id ==
3200 UserGroupRepoToPerm.users_group_id ==
3201 UserGroup.users_group_id)\
3201 UserGroup.users_group_id)\
3202 .join(
3202 .join(
3203 UserGroupMember,
3203 UserGroupMember,
3204 UserGroupRepoToPerm.users_group_id ==
3204 UserGroupRepoToPerm.users_group_id ==
3205 UserGroupMember.users_group_id)\
3205 UserGroupMember.users_group_id)\
3206 .filter(
3206 .filter(
3207 UserGroupMember.user_id == user_id,
3207 UserGroupMember.user_id == user_id,
3208 UserGroup.users_group_active == true())
3208 UserGroup.users_group_active == true())
3209 if repo_id:
3209 if repo_id:
3210 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3210 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3211 return q.all()
3211 return q.all()
3212
3212
3213 @classmethod
3213 @classmethod
3214 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3214 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3215 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3215 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3216 .join(
3216 .join(
3217 Permission,
3217 Permission,
3218 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3218 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3219 .join(
3219 .join(
3220 UserGroupRepoToPerm,
3220 UserGroupRepoToPerm,
3221 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3221 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3222 .join(
3222 .join(
3223 UserGroup,
3223 UserGroup,
3224 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3224 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3225 .join(
3225 .join(
3226 UserGroupMember,
3226 UserGroupMember,
3227 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3227 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3228 .filter(
3228 .filter(
3229 UserGroupMember.user_id == user_id,
3229 UserGroupMember.user_id == user_id,
3230 UserGroup.users_group_active == true())
3230 UserGroup.users_group_active == true())
3231
3231
3232 if repo_id:
3232 if repo_id:
3233 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3233 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3234 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3234 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3235
3235
3236 @classmethod
3236 @classmethod
3237 def get_default_group_perms(cls, user_id, repo_group_id=None):
3237 def get_default_group_perms(cls, user_id, repo_group_id=None):
3238 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3238 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3239 .join(
3239 .join(
3240 Permission,
3240 Permission,
3241 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3241 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3242 .join(
3242 .join(
3243 RepoGroup,
3243 RepoGroup,
3244 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3244 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3245 .filter(UserRepoGroupToPerm.user_id == user_id)
3245 .filter(UserRepoGroupToPerm.user_id == user_id)
3246 if repo_group_id:
3246 if repo_group_id:
3247 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3247 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3248 return q.all()
3248 return q.all()
3249
3249
3250 @classmethod
3250 @classmethod
3251 def get_default_group_perms_from_user_group(
3251 def get_default_group_perms_from_user_group(
3252 cls, user_id, repo_group_id=None):
3252 cls, user_id, repo_group_id=None):
3253 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3253 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3254 .join(
3254 .join(
3255 Permission,
3255 Permission,
3256 UserGroupRepoGroupToPerm.permission_id ==
3256 UserGroupRepoGroupToPerm.permission_id ==
3257 Permission.permission_id)\
3257 Permission.permission_id)\
3258 .join(
3258 .join(
3259 RepoGroup,
3259 RepoGroup,
3260 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3260 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3261 .join(
3261 .join(
3262 UserGroup,
3262 UserGroup,
3263 UserGroupRepoGroupToPerm.users_group_id ==
3263 UserGroupRepoGroupToPerm.users_group_id ==
3264 UserGroup.users_group_id)\
3264 UserGroup.users_group_id)\
3265 .join(
3265 .join(
3266 UserGroupMember,
3266 UserGroupMember,
3267 UserGroupRepoGroupToPerm.users_group_id ==
3267 UserGroupRepoGroupToPerm.users_group_id ==
3268 UserGroupMember.users_group_id)\
3268 UserGroupMember.users_group_id)\
3269 .filter(
3269 .filter(
3270 UserGroupMember.user_id == user_id,
3270 UserGroupMember.user_id == user_id,
3271 UserGroup.users_group_active == true())
3271 UserGroup.users_group_active == true())
3272 if repo_group_id:
3272 if repo_group_id:
3273 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3273 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3274 return q.all()
3274 return q.all()
3275
3275
3276 @classmethod
3276 @classmethod
3277 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3277 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3278 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3278 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3279 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3279 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3280 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3280 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3281 .filter(UserUserGroupToPerm.user_id == user_id)
3281 .filter(UserUserGroupToPerm.user_id == user_id)
3282 if user_group_id:
3282 if user_group_id:
3283 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3283 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3284 return q.all()
3284 return q.all()
3285
3285
3286 @classmethod
3286 @classmethod
3287 def get_default_user_group_perms_from_user_group(
3287 def get_default_user_group_perms_from_user_group(
3288 cls, user_id, user_group_id=None):
3288 cls, user_id, user_group_id=None):
3289 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3289 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3290 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3290 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3291 .join(
3291 .join(
3292 Permission,
3292 Permission,
3293 UserGroupUserGroupToPerm.permission_id ==
3293 UserGroupUserGroupToPerm.permission_id ==
3294 Permission.permission_id)\
3294 Permission.permission_id)\
3295 .join(
3295 .join(
3296 TargetUserGroup,
3296 TargetUserGroup,
3297 UserGroupUserGroupToPerm.target_user_group_id ==
3297 UserGroupUserGroupToPerm.target_user_group_id ==
3298 TargetUserGroup.users_group_id)\
3298 TargetUserGroup.users_group_id)\
3299 .join(
3299 .join(
3300 UserGroup,
3300 UserGroup,
3301 UserGroupUserGroupToPerm.user_group_id ==
3301 UserGroupUserGroupToPerm.user_group_id ==
3302 UserGroup.users_group_id)\
3302 UserGroup.users_group_id)\
3303 .join(
3303 .join(
3304 UserGroupMember,
3304 UserGroupMember,
3305 UserGroupUserGroupToPerm.user_group_id ==
3305 UserGroupUserGroupToPerm.user_group_id ==
3306 UserGroupMember.users_group_id)\
3306 UserGroupMember.users_group_id)\
3307 .filter(
3307 .filter(
3308 UserGroupMember.user_id == user_id,
3308 UserGroupMember.user_id == user_id,
3309 UserGroup.users_group_active == true())
3309 UserGroup.users_group_active == true())
3310 if user_group_id:
3310 if user_group_id:
3311 q = q.filter(
3311 q = q.filter(
3312 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3312 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3313
3313
3314 return q.all()
3314 return q.all()
3315
3315
3316
3316
3317 class UserRepoToPerm(Base, BaseModel):
3317 class UserRepoToPerm(Base, BaseModel):
3318 __tablename__ = 'repo_to_perm'
3318 __tablename__ = 'repo_to_perm'
3319 __table_args__ = (
3319 __table_args__ = (
3320 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3320 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3321 base_table_args
3321 base_table_args
3322 )
3322 )
3323
3323
3324 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3324 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3328
3328
3329 user = relationship('User')
3329 user = relationship('User')
3330 repository = relationship('Repository')
3330 repository = relationship('Repository')
3331 permission = relationship('Permission')
3331 permission = relationship('Permission')
3332
3332
3333 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3333 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3334
3334
3335 @classmethod
3335 @classmethod
3336 def create(cls, user, repository, permission):
3336 def create(cls, user, repository, permission):
3337 n = cls()
3337 n = cls()
3338 n.user = user
3338 n.user = user
3339 n.repository = repository
3339 n.repository = repository
3340 n.permission = permission
3340 n.permission = permission
3341 Session().add(n)
3341 Session().add(n)
3342 return n
3342 return n
3343
3343
3344 def __unicode__(self):
3344 def __unicode__(self):
3345 return u'<%s => %s >' % (self.user, self.repository)
3345 return u'<%s => %s >' % (self.user, self.repository)
3346
3346
3347
3347
3348 class UserUserGroupToPerm(Base, BaseModel):
3348 class UserUserGroupToPerm(Base, BaseModel):
3349 __tablename__ = 'user_user_group_to_perm'
3349 __tablename__ = 'user_user_group_to_perm'
3350 __table_args__ = (
3350 __table_args__ = (
3351 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3351 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3352 base_table_args
3352 base_table_args
3353 )
3353 )
3354
3354
3355 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3355 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3356 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3356 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3357 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3357 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3358 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3358 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3359
3359
3360 user = relationship('User')
3360 user = relationship('User')
3361 user_group = relationship('UserGroup')
3361 user_group = relationship('UserGroup')
3362 permission = relationship('Permission')
3362 permission = relationship('Permission')
3363
3363
3364 @classmethod
3364 @classmethod
3365 def create(cls, user, user_group, permission):
3365 def create(cls, user, user_group, permission):
3366 n = cls()
3366 n = cls()
3367 n.user = user
3367 n.user = user
3368 n.user_group = user_group
3368 n.user_group = user_group
3369 n.permission = permission
3369 n.permission = permission
3370 Session().add(n)
3370 Session().add(n)
3371 return n
3371 return n
3372
3372
3373 def __unicode__(self):
3373 def __unicode__(self):
3374 return u'<%s => %s >' % (self.user, self.user_group)
3374 return u'<%s => %s >' % (self.user, self.user_group)
3375
3375
3376
3376
3377 class UserToPerm(Base, BaseModel):
3377 class UserToPerm(Base, BaseModel):
3378 __tablename__ = 'user_to_perm'
3378 __tablename__ = 'user_to_perm'
3379 __table_args__ = (
3379 __table_args__ = (
3380 UniqueConstraint('user_id', 'permission_id'),
3380 UniqueConstraint('user_id', 'permission_id'),
3381 base_table_args
3381 base_table_args
3382 )
3382 )
3383
3383
3384 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3387
3387
3388 user = relationship('User')
3388 user = relationship('User')
3389 permission = relationship('Permission', lazy='joined')
3389 permission = relationship('Permission', lazy='joined')
3390
3390
3391 def __unicode__(self):
3391 def __unicode__(self):
3392 return u'<%s => %s >' % (self.user, self.permission)
3392 return u'<%s => %s >' % (self.user, self.permission)
3393
3393
3394
3394
3395 class UserGroupRepoToPerm(Base, BaseModel):
3395 class UserGroupRepoToPerm(Base, BaseModel):
3396 __tablename__ = 'users_group_repo_to_perm'
3396 __tablename__ = 'users_group_repo_to_perm'
3397 __table_args__ = (
3397 __table_args__ = (
3398 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3398 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3399 base_table_args
3399 base_table_args
3400 )
3400 )
3401
3401
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3406
3406
3407 users_group = relationship('UserGroup')
3407 users_group = relationship('UserGroup')
3408 permission = relationship('Permission')
3408 permission = relationship('Permission')
3409 repository = relationship('Repository')
3409 repository = relationship('Repository')
3410 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3410 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3411
3411
3412 @classmethod
3412 @classmethod
3413 def create(cls, users_group, repository, permission):
3413 def create(cls, users_group, repository, permission):
3414 n = cls()
3414 n = cls()
3415 n.users_group = users_group
3415 n.users_group = users_group
3416 n.repository = repository
3416 n.repository = repository
3417 n.permission = permission
3417 n.permission = permission
3418 Session().add(n)
3418 Session().add(n)
3419 return n
3419 return n
3420
3420
3421 def __unicode__(self):
3421 def __unicode__(self):
3422 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3422 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3423
3423
3424
3424
3425 class UserGroupUserGroupToPerm(Base, BaseModel):
3425 class UserGroupUserGroupToPerm(Base, BaseModel):
3426 __tablename__ = 'user_group_user_group_to_perm'
3426 __tablename__ = 'user_group_user_group_to_perm'
3427 __table_args__ = (
3427 __table_args__ = (
3428 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3428 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3429 CheckConstraint('target_user_group_id != user_group_id'),
3429 CheckConstraint('target_user_group_id != user_group_id'),
3430 base_table_args
3430 base_table_args
3431 )
3431 )
3432
3432
3433 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3433 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3434 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3434 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3436 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3436 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3437
3437
3438 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3438 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3439 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3439 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3440 permission = relationship('Permission')
3440 permission = relationship('Permission')
3441
3441
3442 @classmethod
3442 @classmethod
3443 def create(cls, target_user_group, user_group, permission):
3443 def create(cls, target_user_group, user_group, permission):
3444 n = cls()
3444 n = cls()
3445 n.target_user_group = target_user_group
3445 n.target_user_group = target_user_group
3446 n.user_group = user_group
3446 n.user_group = user_group
3447 n.permission = permission
3447 n.permission = permission
3448 Session().add(n)
3448 Session().add(n)
3449 return n
3449 return n
3450
3450
3451 def __unicode__(self):
3451 def __unicode__(self):
3452 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3452 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3453
3453
3454
3454
3455 class UserGroupToPerm(Base, BaseModel):
3455 class UserGroupToPerm(Base, BaseModel):
3456 __tablename__ = 'users_group_to_perm'
3456 __tablename__ = 'users_group_to_perm'
3457 __table_args__ = (
3457 __table_args__ = (
3458 UniqueConstraint('users_group_id', 'permission_id',),
3458 UniqueConstraint('users_group_id', 'permission_id',),
3459 base_table_args
3459 base_table_args
3460 )
3460 )
3461
3461
3462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3465
3465
3466 users_group = relationship('UserGroup')
3466 users_group = relationship('UserGroup')
3467 permission = relationship('Permission')
3467 permission = relationship('Permission')
3468
3468
3469
3469
3470 class UserRepoGroupToPerm(Base, BaseModel):
3470 class UserRepoGroupToPerm(Base, BaseModel):
3471 __tablename__ = 'user_repo_group_to_perm'
3471 __tablename__ = 'user_repo_group_to_perm'
3472 __table_args__ = (
3472 __table_args__ = (
3473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3474 base_table_args
3474 base_table_args
3475 )
3475 )
3476
3476
3477 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3477 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3479 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3479 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3480 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3480 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3481
3481
3482 user = relationship('User')
3482 user = relationship('User')
3483 group = relationship('RepoGroup')
3483 group = relationship('RepoGroup')
3484 permission = relationship('Permission')
3484 permission = relationship('Permission')
3485
3485
3486 @classmethod
3486 @classmethod
3487 def create(cls, user, repository_group, permission):
3487 def create(cls, user, repository_group, permission):
3488 n = cls()
3488 n = cls()
3489 n.user = user
3489 n.user = user
3490 n.group = repository_group
3490 n.group = repository_group
3491 n.permission = permission
3491 n.permission = permission
3492 Session().add(n)
3492 Session().add(n)
3493 return n
3493 return n
3494
3494
3495
3495
3496 class UserGroupRepoGroupToPerm(Base, BaseModel):
3496 class UserGroupRepoGroupToPerm(Base, BaseModel):
3497 __tablename__ = 'users_group_repo_group_to_perm'
3497 __tablename__ = 'users_group_repo_group_to_perm'
3498 __table_args__ = (
3498 __table_args__ = (
3499 UniqueConstraint('users_group_id', 'group_id'),
3499 UniqueConstraint('users_group_id', 'group_id'),
3500 base_table_args
3500 base_table_args
3501 )
3501 )
3502
3502
3503 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3503 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3505 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507
3507
3508 users_group = relationship('UserGroup')
3508 users_group = relationship('UserGroup')
3509 permission = relationship('Permission')
3509 permission = relationship('Permission')
3510 group = relationship('RepoGroup')
3510 group = relationship('RepoGroup')
3511
3511
3512 @classmethod
3512 @classmethod
3513 def create(cls, user_group, repository_group, permission):
3513 def create(cls, user_group, repository_group, permission):
3514 n = cls()
3514 n = cls()
3515 n.users_group = user_group
3515 n.users_group = user_group
3516 n.group = repository_group
3516 n.group = repository_group
3517 n.permission = permission
3517 n.permission = permission
3518 Session().add(n)
3518 Session().add(n)
3519 return n
3519 return n
3520
3520
3521 def __unicode__(self):
3521 def __unicode__(self):
3522 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3522 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3523
3523
3524
3524
3525 class Statistics(Base, BaseModel):
3525 class Statistics(Base, BaseModel):
3526 __tablename__ = 'statistics'
3526 __tablename__ = 'statistics'
3527 __table_args__ = (
3527 __table_args__ = (
3528 base_table_args
3528 base_table_args
3529 )
3529 )
3530
3530
3531 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3531 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3533 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3533 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3534 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3534 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3535 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3535 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3536 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3536 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3537
3537
3538 repository = relationship('Repository', single_parent=True)
3538 repository = relationship('Repository', single_parent=True)
3539
3539
3540
3540
3541 class UserFollowing(Base, BaseModel):
3541 class UserFollowing(Base, BaseModel):
3542 __tablename__ = 'user_followings'
3542 __tablename__ = 'user_followings'
3543 __table_args__ = (
3543 __table_args__ = (
3544 UniqueConstraint('user_id', 'follows_repository_id'),
3544 UniqueConstraint('user_id', 'follows_repository_id'),
3545 UniqueConstraint('user_id', 'follows_user_id'),
3545 UniqueConstraint('user_id', 'follows_user_id'),
3546 base_table_args
3546 base_table_args
3547 )
3547 )
3548
3548
3549 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3549 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3551 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3551 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3552 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3552 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3553 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3553 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3554
3554
3555 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3555 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3556
3556
3557 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3557 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3558 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3558 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3559
3559
3560 @classmethod
3560 @classmethod
3561 def get_repo_followers(cls, repo_id):
3561 def get_repo_followers(cls, repo_id):
3562 return cls.query().filter(cls.follows_repo_id == repo_id)
3562 return cls.query().filter(cls.follows_repo_id == repo_id)
3563
3563
3564
3564
3565 class CacheKey(Base, BaseModel):
3565 class CacheKey(Base, BaseModel):
3566 __tablename__ = 'cache_invalidation'
3566 __tablename__ = 'cache_invalidation'
3567 __table_args__ = (
3567 __table_args__ = (
3568 UniqueConstraint('cache_key'),
3568 UniqueConstraint('cache_key'),
3569 Index('key_idx', 'cache_key'),
3569 Index('key_idx', 'cache_key'),
3570 base_table_args,
3570 base_table_args,
3571 )
3571 )
3572
3572
3573 CACHE_TYPE_FEED = 'FEED'
3573 CACHE_TYPE_FEED = 'FEED'
3574
3574
3575 # namespaces used to register process/thread aware caches
3575 # namespaces used to register process/thread aware caches
3576 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3576 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3577 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3577 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3578
3578
3579 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3579 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3580 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3580 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3581 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3581 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3582 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3582 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3583 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3583 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3584
3584
3585 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3585 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3586 self.cache_key = cache_key
3586 self.cache_key = cache_key
3587 self.cache_args = cache_args
3587 self.cache_args = cache_args
3588 self.cache_active = False
3588 self.cache_active = False
3589 # first key should be same for all entries, since all workers should share it
3589 # first key should be same for all entries, since all workers should share it
3590 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3590 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3591
3591
3592 def __unicode__(self):
3592 def __unicode__(self):
3593 return u"<%s('%s:%s[%s]')>" % (
3593 return u"<%s('%s:%s[%s]')>" % (
3594 self.__class__.__name__,
3594 self.__class__.__name__,
3595 self.cache_id, self.cache_key, self.cache_active)
3595 self.cache_id, self.cache_key, self.cache_active)
3596
3596
3597 def _cache_key_partition(self):
3597 def _cache_key_partition(self):
3598 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3598 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3599 return prefix, repo_name, suffix
3599 return prefix, repo_name, suffix
3600
3600
3601 def get_prefix(self):
3601 def get_prefix(self):
3602 """
3602 """
3603 Try to extract prefix from existing cache key. The key could consist
3603 Try to extract prefix from existing cache key. The key could consist
3604 of prefix, repo_name, suffix
3604 of prefix, repo_name, suffix
3605 """
3605 """
3606 # this returns prefix, repo_name, suffix
3606 # this returns prefix, repo_name, suffix
3607 return self._cache_key_partition()[0]
3607 return self._cache_key_partition()[0]
3608
3608
3609 def get_suffix(self):
3609 def get_suffix(self):
3610 """
3610 """
3611 get suffix that might have been used in _get_cache_key to
3611 get suffix that might have been used in _get_cache_key to
3612 generate self.cache_key. Only used for informational purposes
3612 generate self.cache_key. Only used for informational purposes
3613 in repo_edit.mako.
3613 in repo_edit.mako.
3614 """
3614 """
3615 # prefix, repo_name, suffix
3615 # prefix, repo_name, suffix
3616 return self._cache_key_partition()[2]
3616 return self._cache_key_partition()[2]
3617
3617
3618 @classmethod
3618 @classmethod
3619 def generate_new_state_uid(cls, based_on=None):
3619 def generate_new_state_uid(cls, based_on=None):
3620 if based_on:
3620 if based_on:
3621 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3621 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3622 else:
3622 else:
3623 return str(uuid.uuid4())
3623 return str(uuid.uuid4())
3624
3624
3625 @classmethod
3625 @classmethod
3626 def delete_all_cache(cls):
3626 def delete_all_cache(cls):
3627 """
3627 """
3628 Delete all cache keys from database.
3628 Delete all cache keys from database.
3629 Should only be run when all instances are down and all entries
3629 Should only be run when all instances are down and all entries
3630 thus stale.
3630 thus stale.
3631 """
3631 """
3632 cls.query().delete()
3632 cls.query().delete()
3633 Session().commit()
3633 Session().commit()
3634
3634
3635 @classmethod
3635 @classmethod
3636 def set_invalidate(cls, cache_uid, delete=False):
3636 def set_invalidate(cls, cache_uid, delete=False):
3637 """
3637 """
3638 Mark all caches of a repo as invalid in the database.
3638 Mark all caches of a repo as invalid in the database.
3639 """
3639 """
3640
3640
3641 try:
3641 try:
3642 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3642 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3643 if delete:
3643 if delete:
3644 qry.delete()
3644 qry.delete()
3645 log.debug('cache objects deleted for cache args %s',
3645 log.debug('cache objects deleted for cache args %s',
3646 safe_str(cache_uid))
3646 safe_str(cache_uid))
3647 else:
3647 else:
3648 qry.update({"cache_active": False,
3648 qry.update({"cache_active": False,
3649 "cache_state_uid": cls.generate_new_state_uid()})
3649 "cache_state_uid": cls.generate_new_state_uid()})
3650 log.debug('cache objects marked as invalid for cache args %s',
3650 log.debug('cache objects marked as invalid for cache args %s',
3651 safe_str(cache_uid))
3651 safe_str(cache_uid))
3652
3652
3653 Session().commit()
3653 Session().commit()
3654 except Exception:
3654 except Exception:
3655 log.exception(
3655 log.exception(
3656 'Cache key invalidation failed for cache args %s',
3656 'Cache key invalidation failed for cache args %s',
3657 safe_str(cache_uid))
3657 safe_str(cache_uid))
3658 Session().rollback()
3658 Session().rollback()
3659
3659
3660 @classmethod
3660 @classmethod
3661 def get_active_cache(cls, cache_key):
3661 def get_active_cache(cls, cache_key):
3662 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3662 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3663 if inv_obj:
3663 if inv_obj:
3664 return inv_obj
3664 return inv_obj
3665 return None
3665 return None
3666
3666
3667 @classmethod
3667 @classmethod
3668 def get_namespace_map(cls, namespace):
3668 def get_namespace_map(cls, namespace):
3669 return {
3669 return {
3670 x.cache_key: x
3670 x.cache_key: x
3671 for x in cls.query().filter(cls.cache_args == namespace)}
3671 for x in cls.query().filter(cls.cache_args == namespace)}
3672
3672
3673
3673
3674 class ChangesetComment(Base, BaseModel):
3674 class ChangesetComment(Base, BaseModel):
3675 __tablename__ = 'changeset_comments'
3675 __tablename__ = 'changeset_comments'
3676 __table_args__ = (
3676 __table_args__ = (
3677 Index('cc_revision_idx', 'revision'),
3677 Index('cc_revision_idx', 'revision'),
3678 base_table_args,
3678 base_table_args,
3679 )
3679 )
3680
3680
3681 COMMENT_OUTDATED = u'comment_outdated'
3681 COMMENT_OUTDATED = u'comment_outdated'
3682 COMMENT_TYPE_NOTE = u'note'
3682 COMMENT_TYPE_NOTE = u'note'
3683 COMMENT_TYPE_TODO = u'todo'
3683 COMMENT_TYPE_TODO = u'todo'
3684 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3684 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3685
3685
3686 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3686 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3687 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3687 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3688 revision = Column('revision', String(40), nullable=True)
3688 revision = Column('revision', String(40), nullable=True)
3689 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3689 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3690 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3690 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3691 line_no = Column('line_no', Unicode(10), nullable=True)
3691 line_no = Column('line_no', Unicode(10), nullable=True)
3692 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3692 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3693 f_path = Column('f_path', Unicode(1000), nullable=True)
3693 f_path = Column('f_path', Unicode(1000), nullable=True)
3694 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3694 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3695 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3695 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3697 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3697 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3698 renderer = Column('renderer', Unicode(64), nullable=True)
3698 renderer = Column('renderer', Unicode(64), nullable=True)
3699 display_state = Column('display_state', Unicode(128), nullable=True)
3699 display_state = Column('display_state', Unicode(128), nullable=True)
3700
3700
3701 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3701 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3702 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3702 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3703
3703
3704 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3704 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3705 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3705 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3706
3706
3707 author = relationship('User', lazy='joined')
3707 author = relationship('User', lazy='joined')
3708 repo = relationship('Repository')
3708 repo = relationship('Repository')
3709 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3709 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3710 pull_request = relationship('PullRequest', lazy='joined')
3710 pull_request = relationship('PullRequest', lazy='joined')
3711 pull_request_version = relationship('PullRequestVersion')
3711 pull_request_version = relationship('PullRequestVersion')
3712
3712
3713 @classmethod
3713 @classmethod
3714 def get_users(cls, revision=None, pull_request_id=None):
3714 def get_users(cls, revision=None, pull_request_id=None):
3715 """
3715 """
3716 Returns user associated with this ChangesetComment. ie those
3716 Returns user associated with this ChangesetComment. ie those
3717 who actually commented
3717 who actually commented
3718
3718
3719 :param cls:
3719 :param cls:
3720 :param revision:
3720 :param revision:
3721 """
3721 """
3722 q = Session().query(User)\
3722 q = Session().query(User)\
3723 .join(ChangesetComment.author)
3723 .join(ChangesetComment.author)
3724 if revision:
3724 if revision:
3725 q = q.filter(cls.revision == revision)
3725 q = q.filter(cls.revision == revision)
3726 elif pull_request_id:
3726 elif pull_request_id:
3727 q = q.filter(cls.pull_request_id == pull_request_id)
3727 q = q.filter(cls.pull_request_id == pull_request_id)
3728 return q.all()
3728 return q.all()
3729
3729
3730 @classmethod
3730 @classmethod
3731 def get_index_from_version(cls, pr_version, versions):
3731 def get_index_from_version(cls, pr_version, versions):
3732 num_versions = [x.pull_request_version_id for x in versions]
3732 num_versions = [x.pull_request_version_id for x in versions]
3733 try:
3733 try:
3734 return num_versions.index(pr_version) +1
3734 return num_versions.index(pr_version) +1
3735 except (IndexError, ValueError):
3735 except (IndexError, ValueError):
3736 return
3736 return
3737
3737
3738 @property
3738 @property
3739 def outdated(self):
3739 def outdated(self):
3740 return self.display_state == self.COMMENT_OUTDATED
3740 return self.display_state == self.COMMENT_OUTDATED
3741
3741
3742 def outdated_at_version(self, version):
3742 def outdated_at_version(self, version):
3743 """
3743 """
3744 Checks if comment is outdated for given pull request version
3744 Checks if comment is outdated for given pull request version
3745 """
3745 """
3746 return self.outdated and self.pull_request_version_id != version
3746 return self.outdated and self.pull_request_version_id != version
3747
3747
3748 def older_than_version(self, version):
3748 def older_than_version(self, version):
3749 """
3749 """
3750 Checks if comment is made from previous version than given
3750 Checks if comment is made from previous version than given
3751 """
3751 """
3752 if version is None:
3752 if version is None:
3753 return self.pull_request_version_id is not None
3753 return self.pull_request_version_id is not None
3754
3754
3755 return self.pull_request_version_id < version
3755 return self.pull_request_version_id < version
3756
3756
3757 @property
3757 @property
3758 def resolved(self):
3758 def resolved(self):
3759 return self.resolved_by[0] if self.resolved_by else None
3759 return self.resolved_by[0] if self.resolved_by else None
3760
3760
3761 @property
3761 @property
3762 def is_todo(self):
3762 def is_todo(self):
3763 return self.comment_type == self.COMMENT_TYPE_TODO
3763 return self.comment_type == self.COMMENT_TYPE_TODO
3764
3764
3765 @property
3765 @property
3766 def is_inline(self):
3766 def is_inline(self):
3767 return self.line_no and self.f_path
3767 return self.line_no and self.f_path
3768
3768
3769 def get_index_version(self, versions):
3769 def get_index_version(self, versions):
3770 return self.get_index_from_version(
3770 return self.get_index_from_version(
3771 self.pull_request_version_id, versions)
3771 self.pull_request_version_id, versions)
3772
3772
3773 def __repr__(self):
3773 def __repr__(self):
3774 if self.comment_id:
3774 if self.comment_id:
3775 return '<DB:Comment #%s>' % self.comment_id
3775 return '<DB:Comment #%s>' % self.comment_id
3776 else:
3776 else:
3777 return '<DB:Comment at %#x>' % id(self)
3777 return '<DB:Comment at %#x>' % id(self)
3778
3778
3779 def get_api_data(self):
3779 def get_api_data(self):
3780 comment = self
3780 comment = self
3781 data = {
3781 data = {
3782 'comment_id': comment.comment_id,
3782 'comment_id': comment.comment_id,
3783 'comment_type': comment.comment_type,
3783 'comment_type': comment.comment_type,
3784 'comment_text': comment.text,
3784 'comment_text': comment.text,
3785 'comment_status': comment.status_change,
3785 'comment_status': comment.status_change,
3786 'comment_f_path': comment.f_path,
3786 'comment_f_path': comment.f_path,
3787 'comment_lineno': comment.line_no,
3787 'comment_lineno': comment.line_no,
3788 'comment_author': comment.author,
3788 'comment_author': comment.author,
3789 'comment_created_on': comment.created_on,
3789 'comment_created_on': comment.created_on,
3790 'comment_resolved_by': self.resolved
3790 'comment_resolved_by': self.resolved
3791 }
3791 }
3792 return data
3792 return data
3793
3793
3794 def __json__(self):
3794 def __json__(self):
3795 data = dict()
3795 data = dict()
3796 data.update(self.get_api_data())
3796 data.update(self.get_api_data())
3797 return data
3797 return data
3798
3798
3799
3799
3800 class ChangesetStatus(Base, BaseModel):
3800 class ChangesetStatus(Base, BaseModel):
3801 __tablename__ = 'changeset_statuses'
3801 __tablename__ = 'changeset_statuses'
3802 __table_args__ = (
3802 __table_args__ = (
3803 Index('cs_revision_idx', 'revision'),
3803 Index('cs_revision_idx', 'revision'),
3804 Index('cs_version_idx', 'version'),
3804 Index('cs_version_idx', 'version'),
3805 UniqueConstraint('repo_id', 'revision', 'version'),
3805 UniqueConstraint('repo_id', 'revision', 'version'),
3806 base_table_args
3806 base_table_args
3807 )
3807 )
3808
3808
3809 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3809 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3810 STATUS_APPROVED = 'approved'
3810 STATUS_APPROVED = 'approved'
3811 STATUS_REJECTED = 'rejected'
3811 STATUS_REJECTED = 'rejected'
3812 STATUS_UNDER_REVIEW = 'under_review'
3812 STATUS_UNDER_REVIEW = 'under_review'
3813
3813
3814 STATUSES = [
3814 STATUSES = [
3815 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3815 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3816 (STATUS_APPROVED, _("Approved")),
3816 (STATUS_APPROVED, _("Approved")),
3817 (STATUS_REJECTED, _("Rejected")),
3817 (STATUS_REJECTED, _("Rejected")),
3818 (STATUS_UNDER_REVIEW, _("Under Review")),
3818 (STATUS_UNDER_REVIEW, _("Under Review")),
3819 ]
3819 ]
3820
3820
3821 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3821 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3823 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3823 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3824 revision = Column('revision', String(40), nullable=False)
3824 revision = Column('revision', String(40), nullable=False)
3825 status = Column('status', String(128), nullable=False, default=DEFAULT)
3825 status = Column('status', String(128), nullable=False, default=DEFAULT)
3826 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3826 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3827 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3827 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3828 version = Column('version', Integer(), nullable=False, default=0)
3828 version = Column('version', Integer(), nullable=False, default=0)
3829 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3829 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3830
3830
3831 author = relationship('User', lazy='joined')
3831 author = relationship('User', lazy='joined')
3832 repo = relationship('Repository')
3832 repo = relationship('Repository')
3833 comment = relationship('ChangesetComment', lazy='joined')
3833 comment = relationship('ChangesetComment', lazy='joined')
3834 pull_request = relationship('PullRequest', lazy='joined')
3834 pull_request = relationship('PullRequest', lazy='joined')
3835
3835
3836 def __unicode__(self):
3836 def __unicode__(self):
3837 return u"<%s('%s[v%s]:%s')>" % (
3837 return u"<%s('%s[v%s]:%s')>" % (
3838 self.__class__.__name__,
3838 self.__class__.__name__,
3839 self.status, self.version, self.author
3839 self.status, self.version, self.author
3840 )
3840 )
3841
3841
3842 @classmethod
3842 @classmethod
3843 def get_status_lbl(cls, value):
3843 def get_status_lbl(cls, value):
3844 return dict(cls.STATUSES).get(value)
3844 return dict(cls.STATUSES).get(value)
3845
3845
3846 @property
3846 @property
3847 def status_lbl(self):
3847 def status_lbl(self):
3848 return ChangesetStatus.get_status_lbl(self.status)
3848 return ChangesetStatus.get_status_lbl(self.status)
3849
3849
3850 def get_api_data(self):
3850 def get_api_data(self):
3851 status = self
3851 status = self
3852 data = {
3852 data = {
3853 'status_id': status.changeset_status_id,
3853 'status_id': status.changeset_status_id,
3854 'status': status.status,
3854 'status': status.status,
3855 }
3855 }
3856 return data
3856 return data
3857
3857
3858 def __json__(self):
3858 def __json__(self):
3859 data = dict()
3859 data = dict()
3860 data.update(self.get_api_data())
3860 data.update(self.get_api_data())
3861 return data
3861 return data
3862
3862
3863
3863
3864 class _SetState(object):
3864 class _SetState(object):
3865 """
3865 """
3866 Context processor allowing changing state for sensitive operation such as
3866 Context processor allowing changing state for sensitive operation such as
3867 pull request update or merge
3867 pull request update or merge
3868 """
3868 """
3869
3869
3870 def __init__(self, pull_request, pr_state, back_state=None):
3870 def __init__(self, pull_request, pr_state, back_state=None):
3871 self._pr = pull_request
3871 self._pr = pull_request
3872 self._org_state = back_state or pull_request.pull_request_state
3872 self._org_state = back_state or pull_request.pull_request_state
3873 self._pr_state = pr_state
3873 self._pr_state = pr_state
3874 self._current_state = None
3874 self._current_state = None
3875
3875
3876 def __enter__(self):
3876 def __enter__(self):
3877 log.debug('StateLock: entering set state context, setting state to: `%s`',
3877 log.debug('StateLock: entering set state context, setting state to: `%s`',
3878 self._pr_state)
3878 self._pr_state)
3879 self.set_pr_state(self._pr_state)
3879 self.set_pr_state(self._pr_state)
3880 return self
3880 return self
3881
3881
3882 def __exit__(self, exc_type, exc_val, exc_tb):
3882 def __exit__(self, exc_type, exc_val, exc_tb):
3883 if exc_val is not None:
3883 if exc_val is not None:
3884 log.error(traceback.format_exc(exc_tb))
3884 log.error(traceback.format_exc(exc_tb))
3885 return None
3885 return None
3886
3886
3887 self.set_pr_state(self._org_state)
3887 self.set_pr_state(self._org_state)
3888 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3888 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3889 self._org_state)
3889 self._org_state)
3890 @property
3890 @property
3891 def state(self):
3891 def state(self):
3892 return self._current_state
3892 return self._current_state
3893
3893
3894 def set_pr_state(self, pr_state):
3894 def set_pr_state(self, pr_state):
3895 try:
3895 try:
3896 self._pr.pull_request_state = pr_state
3896 self._pr.pull_request_state = pr_state
3897 Session().add(self._pr)
3897 Session().add(self._pr)
3898 Session().commit()
3898 Session().commit()
3899 self._current_state = pr_state
3899 self._current_state = pr_state
3900 except Exception:
3900 except Exception:
3901 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3901 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3902 raise
3902 raise
3903
3903
3904
3904
3905 class _PullRequestBase(BaseModel):
3905 class _PullRequestBase(BaseModel):
3906 """
3906 """
3907 Common attributes of pull request and version entries.
3907 Common attributes of pull request and version entries.
3908 """
3908 """
3909
3909
3910 # .status values
3910 # .status values
3911 STATUS_NEW = u'new'
3911 STATUS_NEW = u'new'
3912 STATUS_OPEN = u'open'
3912 STATUS_OPEN = u'open'
3913 STATUS_CLOSED = u'closed'
3913 STATUS_CLOSED = u'closed'
3914
3914
3915 # available states
3915 # available states
3916 STATE_CREATING = u'creating'
3916 STATE_CREATING = u'creating'
3917 STATE_UPDATING = u'updating'
3917 STATE_UPDATING = u'updating'
3918 STATE_MERGING = u'merging'
3918 STATE_MERGING = u'merging'
3919 STATE_CREATED = u'created'
3919 STATE_CREATED = u'created'
3920
3920
3921 title = Column('title', Unicode(255), nullable=True)
3921 title = Column('title', Unicode(255), nullable=True)
3922 description = Column(
3922 description = Column(
3923 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3923 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3924 nullable=True)
3924 nullable=True)
3925 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3925 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3926
3926
3927 # new/open/closed status of pull request (not approve/reject/etc)
3927 # new/open/closed status of pull request (not approve/reject/etc)
3928 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3928 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3929 created_on = Column(
3929 created_on = Column(
3930 'created_on', DateTime(timezone=False), nullable=False,
3930 'created_on', DateTime(timezone=False), nullable=False,
3931 default=datetime.datetime.now)
3931 default=datetime.datetime.now)
3932 updated_on = Column(
3932 updated_on = Column(
3933 'updated_on', DateTime(timezone=False), nullable=False,
3933 'updated_on', DateTime(timezone=False), nullable=False,
3934 default=datetime.datetime.now)
3934 default=datetime.datetime.now)
3935
3935
3936 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3936 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3937
3937
3938 @declared_attr
3938 @declared_attr
3939 def user_id(cls):
3939 def user_id(cls):
3940 return Column(
3940 return Column(
3941 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3941 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3942 unique=None)
3942 unique=None)
3943
3943
3944 # 500 revisions max
3944 # 500 revisions max
3945 _revisions = Column(
3945 _revisions = Column(
3946 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3946 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3947
3947
3948 @declared_attr
3948 @declared_attr
3949 def source_repo_id(cls):
3949 def source_repo_id(cls):
3950 # TODO: dan: rename column to source_repo_id
3950 # TODO: dan: rename column to source_repo_id
3951 return Column(
3951 return Column(
3952 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3952 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3953 nullable=False)
3953 nullable=False)
3954
3954
3955 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3955 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3956
3956
3957 @hybrid_property
3957 @hybrid_property
3958 def source_ref(self):
3958 def source_ref(self):
3959 return self._source_ref
3959 return self._source_ref
3960
3960
3961 @source_ref.setter
3961 @source_ref.setter
3962 def source_ref(self, val):
3962 def source_ref(self, val):
3963 parts = (val or '').split(':')
3963 parts = (val or '').split(':')
3964 if len(parts) != 3:
3964 if len(parts) != 3:
3965 raise ValueError(
3965 raise ValueError(
3966 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3966 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3967 self._source_ref = safe_unicode(val)
3967 self._source_ref = safe_unicode(val)
3968
3968
3969 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3969 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3970
3970
3971 @hybrid_property
3971 @hybrid_property
3972 def target_ref(self):
3972 def target_ref(self):
3973 return self._target_ref
3973 return self._target_ref
3974
3974
3975 @target_ref.setter
3975 @target_ref.setter
3976 def target_ref(self, val):
3976 def target_ref(self, val):
3977 parts = (val or '').split(':')
3977 parts = (val or '').split(':')
3978 if len(parts) != 3:
3978 if len(parts) != 3:
3979 raise ValueError(
3979 raise ValueError(
3980 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3980 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3981 self._target_ref = safe_unicode(val)
3981 self._target_ref = safe_unicode(val)
3982
3982
3983 @declared_attr
3983 @declared_attr
3984 def target_repo_id(cls):
3984 def target_repo_id(cls):
3985 # TODO: dan: rename column to target_repo_id
3985 # TODO: dan: rename column to target_repo_id
3986 return Column(
3986 return Column(
3987 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3987 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3988 nullable=False)
3988 nullable=False)
3989
3989
3990 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3990 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3991
3991
3992 # TODO: dan: rename column to last_merge_source_rev
3992 # TODO: dan: rename column to last_merge_source_rev
3993 _last_merge_source_rev = Column(
3993 _last_merge_source_rev = Column(
3994 'last_merge_org_rev', String(40), nullable=True)
3994 'last_merge_org_rev', String(40), nullable=True)
3995 # TODO: dan: rename column to last_merge_target_rev
3995 # TODO: dan: rename column to last_merge_target_rev
3996 _last_merge_target_rev = Column(
3996 _last_merge_target_rev = Column(
3997 'last_merge_other_rev', String(40), nullable=True)
3997 'last_merge_other_rev', String(40), nullable=True)
3998 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3998 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3999 merge_rev = Column('merge_rev', String(40), nullable=True)
3999 merge_rev = Column('merge_rev', String(40), nullable=True)
4000
4000
4001 reviewer_data = Column(
4001 reviewer_data = Column(
4002 'reviewer_data_json', MutationObj.as_mutable(
4002 'reviewer_data_json', MutationObj.as_mutable(
4003 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4003 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4004
4004
4005 @property
4005 @property
4006 def reviewer_data_json(self):
4006 def reviewer_data_json(self):
4007 return json.dumps(self.reviewer_data)
4007 return json.dumps(self.reviewer_data)
4008
4008
4009 @property
4009 @property
4010 def work_in_progress(self):
4010 def work_in_progress(self):
4011 """checks if pull request is work in progress by checking the title"""
4011 """checks if pull request is work in progress by checking the title"""
4012 title = self.title.upper()
4012 title = self.title.upper()
4013 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4013 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4014 return True
4014 return True
4015 return False
4015 return False
4016
4016
4017 @hybrid_property
4017 @hybrid_property
4018 def description_safe(self):
4018 def description_safe(self):
4019 from rhodecode.lib import helpers as h
4019 from rhodecode.lib import helpers as h
4020 return h.escape(self.description)
4020 return h.escape(self.description)
4021
4021
4022 @hybrid_property
4022 @hybrid_property
4023 def revisions(self):
4023 def revisions(self):
4024 return self._revisions.split(':') if self._revisions else []
4024 return self._revisions.split(':') if self._revisions else []
4025
4025
4026 @revisions.setter
4026 @revisions.setter
4027 def revisions(self, val):
4027 def revisions(self, val):
4028 self._revisions = u':'.join(val)
4028 self._revisions = u':'.join(val)
4029
4029
4030 @hybrid_property
4030 @hybrid_property
4031 def last_merge_status(self):
4031 def last_merge_status(self):
4032 return safe_int(self._last_merge_status)
4032 return safe_int(self._last_merge_status)
4033
4033
4034 @last_merge_status.setter
4034 @last_merge_status.setter
4035 def last_merge_status(self, val):
4035 def last_merge_status(self, val):
4036 self._last_merge_status = val
4036 self._last_merge_status = val
4037
4037
4038 @declared_attr
4038 @declared_attr
4039 def author(cls):
4039 def author(cls):
4040 return relationship('User', lazy='joined')
4040 return relationship('User', lazy='joined')
4041
4041
4042 @declared_attr
4042 @declared_attr
4043 def source_repo(cls):
4043 def source_repo(cls):
4044 return relationship(
4044 return relationship(
4045 'Repository',
4045 'Repository',
4046 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4046 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4047
4047
4048 @property
4048 @property
4049 def source_ref_parts(self):
4049 def source_ref_parts(self):
4050 return self.unicode_to_reference(self.source_ref)
4050 return self.unicode_to_reference(self.source_ref)
4051
4051
4052 @declared_attr
4052 @declared_attr
4053 def target_repo(cls):
4053 def target_repo(cls):
4054 return relationship(
4054 return relationship(
4055 'Repository',
4055 'Repository',
4056 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4056 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4057
4057
4058 @property
4058 @property
4059 def target_ref_parts(self):
4059 def target_ref_parts(self):
4060 return self.unicode_to_reference(self.target_ref)
4060 return self.unicode_to_reference(self.target_ref)
4061
4061
4062 @property
4062 @property
4063 def shadow_merge_ref(self):
4063 def shadow_merge_ref(self):
4064 return self.unicode_to_reference(self._shadow_merge_ref)
4064 return self.unicode_to_reference(self._shadow_merge_ref)
4065
4065
4066 @shadow_merge_ref.setter
4066 @shadow_merge_ref.setter
4067 def shadow_merge_ref(self, ref):
4067 def shadow_merge_ref(self, ref):
4068 self._shadow_merge_ref = self.reference_to_unicode(ref)
4068 self._shadow_merge_ref = self.reference_to_unicode(ref)
4069
4069
4070 @staticmethod
4070 @staticmethod
4071 def unicode_to_reference(raw):
4071 def unicode_to_reference(raw):
4072 """
4072 """
4073 Convert a unicode (or string) to a reference object.
4073 Convert a unicode (or string) to a reference object.
4074 If unicode evaluates to False it returns None.
4074 If unicode evaluates to False it returns None.
4075 """
4075 """
4076 if raw:
4076 if raw:
4077 refs = raw.split(':')
4077 refs = raw.split(':')
4078 return Reference(*refs)
4078 return Reference(*refs)
4079 else:
4079 else:
4080 return None
4080 return None
4081
4081
4082 @staticmethod
4082 @staticmethod
4083 def reference_to_unicode(ref):
4083 def reference_to_unicode(ref):
4084 """
4084 """
4085 Convert a reference object to unicode.
4085 Convert a reference object to unicode.
4086 If reference is None it returns None.
4086 If reference is None it returns None.
4087 """
4087 """
4088 if ref:
4088 if ref:
4089 return u':'.join(ref)
4089 return u':'.join(ref)
4090 else:
4090 else:
4091 return None
4091 return None
4092
4092
4093 def get_api_data(self, with_merge_state=True):
4093 def get_api_data(self, with_merge_state=True):
4094 from rhodecode.model.pull_request import PullRequestModel
4094 from rhodecode.model.pull_request import PullRequestModel
4095
4095
4096 pull_request = self
4096 pull_request = self
4097 if with_merge_state:
4097 if with_merge_state:
4098 merge_status = PullRequestModel().merge_status(pull_request)
4098 merge_status = PullRequestModel().merge_status(pull_request)
4099 merge_state = {
4099 merge_state = {
4100 'status': merge_status[0],
4100 'status': merge_status[0],
4101 'message': safe_unicode(merge_status[1]),
4101 'message': safe_unicode(merge_status[1]),
4102 }
4102 }
4103 else:
4103 else:
4104 merge_state = {'status': 'not_available',
4104 merge_state = {'status': 'not_available',
4105 'message': 'not_available'}
4105 'message': 'not_available'}
4106
4106
4107 merge_data = {
4107 merge_data = {
4108 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4108 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4109 'reference': (
4109 'reference': (
4110 pull_request.shadow_merge_ref._asdict()
4110 pull_request.shadow_merge_ref._asdict()
4111 if pull_request.shadow_merge_ref else None),
4111 if pull_request.shadow_merge_ref else None),
4112 }
4112 }
4113
4113
4114 data = {
4114 data = {
4115 'pull_request_id': pull_request.pull_request_id,
4115 'pull_request_id': pull_request.pull_request_id,
4116 'url': PullRequestModel().get_url(pull_request),
4116 'url': PullRequestModel().get_url(pull_request),
4117 'title': pull_request.title,
4117 'title': pull_request.title,
4118 'description': pull_request.description,
4118 'description': pull_request.description,
4119 'status': pull_request.status,
4119 'status': pull_request.status,
4120 'state': pull_request.pull_request_state,
4120 'state': pull_request.pull_request_state,
4121 'created_on': pull_request.created_on,
4121 'created_on': pull_request.created_on,
4122 'updated_on': pull_request.updated_on,
4122 'updated_on': pull_request.updated_on,
4123 'commit_ids': pull_request.revisions,
4123 'commit_ids': pull_request.revisions,
4124 'review_status': pull_request.calculated_review_status(),
4124 'review_status': pull_request.calculated_review_status(),
4125 'mergeable': merge_state,
4125 'mergeable': merge_state,
4126 'source': {
4126 'source': {
4127 'clone_url': pull_request.source_repo.clone_url(),
4127 'clone_url': pull_request.source_repo.clone_url(),
4128 'repository': pull_request.source_repo.repo_name,
4128 'repository': pull_request.source_repo.repo_name,
4129 'reference': {
4129 'reference': {
4130 'name': pull_request.source_ref_parts.name,
4130 'name': pull_request.source_ref_parts.name,
4131 'type': pull_request.source_ref_parts.type,
4131 'type': pull_request.source_ref_parts.type,
4132 'commit_id': pull_request.source_ref_parts.commit_id,
4132 'commit_id': pull_request.source_ref_parts.commit_id,
4133 },
4133 },
4134 },
4134 },
4135 'target': {
4135 'target': {
4136 'clone_url': pull_request.target_repo.clone_url(),
4136 'clone_url': pull_request.target_repo.clone_url(),
4137 'repository': pull_request.target_repo.repo_name,
4137 'repository': pull_request.target_repo.repo_name,
4138 'reference': {
4138 'reference': {
4139 'name': pull_request.target_ref_parts.name,
4139 'name': pull_request.target_ref_parts.name,
4140 'type': pull_request.target_ref_parts.type,
4140 'type': pull_request.target_ref_parts.type,
4141 'commit_id': pull_request.target_ref_parts.commit_id,
4141 'commit_id': pull_request.target_ref_parts.commit_id,
4142 },
4142 },
4143 },
4143 },
4144 'merge': merge_data,
4144 'merge': merge_data,
4145 'author': pull_request.author.get_api_data(include_secrets=False,
4145 'author': pull_request.author.get_api_data(include_secrets=False,
4146 details='basic'),
4146 details='basic'),
4147 'reviewers': [
4147 'reviewers': [
4148 {
4148 {
4149 'user': reviewer.get_api_data(include_secrets=False,
4149 'user': reviewer.get_api_data(include_secrets=False,
4150 details='basic'),
4150 details='basic'),
4151 'reasons': reasons,
4151 'reasons': reasons,
4152 'review_status': st[0][1].status if st else 'not_reviewed',
4152 'review_status': st[0][1].status if st else 'not_reviewed',
4153 }
4153 }
4154 for obj, reviewer, reasons, mandatory, st in
4154 for obj, reviewer, reasons, mandatory, st in
4155 pull_request.reviewers_statuses()
4155 pull_request.reviewers_statuses()
4156 ]
4156 ]
4157 }
4157 }
4158
4158
4159 return data
4159 return data
4160
4160
4161 def set_state(self, pull_request_state, final_state=None):
4161 def set_state(self, pull_request_state, final_state=None):
4162 """
4162 """
4163 # goes from initial state to updating to initial state.
4163 # goes from initial state to updating to initial state.
4164 # initial state can be changed by specifying back_state=
4164 # initial state can be changed by specifying back_state=
4165 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4165 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4166 pull_request.merge()
4166 pull_request.merge()
4167
4167
4168 :param pull_request_state:
4168 :param pull_request_state:
4169 :param final_state:
4169 :param final_state:
4170
4170
4171 """
4171 """
4172
4172
4173 return _SetState(self, pull_request_state, back_state=final_state)
4173 return _SetState(self, pull_request_state, back_state=final_state)
4174
4174
4175
4175
4176 class PullRequest(Base, _PullRequestBase):
4176 class PullRequest(Base, _PullRequestBase):
4177 __tablename__ = 'pull_requests'
4177 __tablename__ = 'pull_requests'
4178 __table_args__ = (
4178 __table_args__ = (
4179 base_table_args,
4179 base_table_args,
4180 )
4180 )
4181
4181
4182 pull_request_id = Column(
4182 pull_request_id = Column(
4183 'pull_request_id', Integer(), nullable=False, primary_key=True)
4183 'pull_request_id', Integer(), nullable=False, primary_key=True)
4184
4184
4185 def __repr__(self):
4185 def __repr__(self):
4186 if self.pull_request_id:
4186 if self.pull_request_id:
4187 return '<DB:PullRequest #%s>' % self.pull_request_id
4187 return '<DB:PullRequest #%s>' % self.pull_request_id
4188 else:
4188 else:
4189 return '<DB:PullRequest at %#x>' % id(self)
4189 return '<DB:PullRequest at %#x>' % id(self)
4190
4190
4191 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4191 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4192 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4192 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4193 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4193 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4194 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4194 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4195 lazy='dynamic')
4195 lazy='dynamic')
4196
4196
4197 @classmethod
4197 @classmethod
4198 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4198 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4199 internal_methods=None):
4199 internal_methods=None):
4200
4200
4201 class PullRequestDisplay(object):
4201 class PullRequestDisplay(object):
4202 """
4202 """
4203 Special object wrapper for showing PullRequest data via Versions
4203 Special object wrapper for showing PullRequest data via Versions
4204 It mimics PR object as close as possible. This is read only object
4204 It mimics PR object as close as possible. This is read only object
4205 just for display
4205 just for display
4206 """
4206 """
4207
4207
4208 def __init__(self, attrs, internal=None):
4208 def __init__(self, attrs, internal=None):
4209 self.attrs = attrs
4209 self.attrs = attrs
4210 # internal have priority over the given ones via attrs
4210 # internal have priority over the given ones via attrs
4211 self.internal = internal or ['versions']
4211 self.internal = internal or ['versions']
4212
4212
4213 def __getattr__(self, item):
4213 def __getattr__(self, item):
4214 if item in self.internal:
4214 if item in self.internal:
4215 return getattr(self, item)
4215 return getattr(self, item)
4216 try:
4216 try:
4217 return self.attrs[item]
4217 return self.attrs[item]
4218 except KeyError:
4218 except KeyError:
4219 raise AttributeError(
4219 raise AttributeError(
4220 '%s object has no attribute %s' % (self, item))
4220 '%s object has no attribute %s' % (self, item))
4221
4221
4222 def __repr__(self):
4222 def __repr__(self):
4223 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4223 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4224
4224
4225 def versions(self):
4225 def versions(self):
4226 return pull_request_obj.versions.order_by(
4226 return pull_request_obj.versions.order_by(
4227 PullRequestVersion.pull_request_version_id).all()
4227 PullRequestVersion.pull_request_version_id).all()
4228
4228
4229 def is_closed(self):
4229 def is_closed(self):
4230 return pull_request_obj.is_closed()
4230 return pull_request_obj.is_closed()
4231
4231
4232 def is_state_changing(self):
4232 def is_state_changing(self):
4233 return pull_request_obj.is_state_changing()
4233 return pull_request_obj.is_state_changing()
4234
4234
4235 @property
4235 @property
4236 def pull_request_version_id(self):
4236 def pull_request_version_id(self):
4237 return getattr(pull_request_obj, 'pull_request_version_id', None)
4237 return getattr(pull_request_obj, 'pull_request_version_id', None)
4238
4238
4239 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4239 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4240
4240
4241 attrs.author = StrictAttributeDict(
4241 attrs.author = StrictAttributeDict(
4242 pull_request_obj.author.get_api_data())
4242 pull_request_obj.author.get_api_data())
4243 if pull_request_obj.target_repo:
4243 if pull_request_obj.target_repo:
4244 attrs.target_repo = StrictAttributeDict(
4244 attrs.target_repo = StrictAttributeDict(
4245 pull_request_obj.target_repo.get_api_data())
4245 pull_request_obj.target_repo.get_api_data())
4246 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4246 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4247
4247
4248 if pull_request_obj.source_repo:
4248 if pull_request_obj.source_repo:
4249 attrs.source_repo = StrictAttributeDict(
4249 attrs.source_repo = StrictAttributeDict(
4250 pull_request_obj.source_repo.get_api_data())
4250 pull_request_obj.source_repo.get_api_data())
4251 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4251 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4252
4252
4253 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4253 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4254 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4254 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4255 attrs.revisions = pull_request_obj.revisions
4255 attrs.revisions = pull_request_obj.revisions
4256
4256
4257 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4257 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4258 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4258 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4259 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4259 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4260
4260
4261 return PullRequestDisplay(attrs, internal=internal_methods)
4261 return PullRequestDisplay(attrs, internal=internal_methods)
4262
4262
4263 def is_closed(self):
4263 def is_closed(self):
4264 return self.status == self.STATUS_CLOSED
4264 return self.status == self.STATUS_CLOSED
4265
4265
4266 def is_state_changing(self):
4266 def is_state_changing(self):
4267 return self.pull_request_state != PullRequest.STATE_CREATED
4267 return self.pull_request_state != PullRequest.STATE_CREATED
4268
4268
4269 def __json__(self):
4269 def __json__(self):
4270 return {
4270 return {
4271 'revisions': self.revisions,
4271 'revisions': self.revisions,
4272 }
4272 }
4273
4273
4274 def calculated_review_status(self):
4274 def calculated_review_status(self):
4275 from rhodecode.model.changeset_status import ChangesetStatusModel
4275 from rhodecode.model.changeset_status import ChangesetStatusModel
4276 return ChangesetStatusModel().calculated_review_status(self)
4276 return ChangesetStatusModel().calculated_review_status(self)
4277
4277
4278 def reviewers_statuses(self):
4278 def reviewers_statuses(self):
4279 from rhodecode.model.changeset_status import ChangesetStatusModel
4279 from rhodecode.model.changeset_status import ChangesetStatusModel
4280 return ChangesetStatusModel().reviewers_statuses(self)
4280 return ChangesetStatusModel().reviewers_statuses(self)
4281
4281
4282 @property
4282 @property
4283 def workspace_id(self):
4283 def workspace_id(self):
4284 from rhodecode.model.pull_request import PullRequestModel
4284 from rhodecode.model.pull_request import PullRequestModel
4285 return PullRequestModel()._workspace_id(self)
4285 return PullRequestModel()._workspace_id(self)
4286
4286
4287 def get_shadow_repo(self):
4287 def get_shadow_repo(self):
4288 workspace_id = self.workspace_id
4288 workspace_id = self.workspace_id
4289 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4289 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4290 if os.path.isdir(shadow_repository_path):
4290 if os.path.isdir(shadow_repository_path):
4291 vcs_obj = self.target_repo.scm_instance()
4291 vcs_obj = self.target_repo.scm_instance()
4292 return vcs_obj.get_shadow_instance(shadow_repository_path)
4292 return vcs_obj.get_shadow_instance(shadow_repository_path)
4293
4293
4294
4294
4295 class PullRequestVersion(Base, _PullRequestBase):
4295 class PullRequestVersion(Base, _PullRequestBase):
4296 __tablename__ = 'pull_request_versions'
4296 __tablename__ = 'pull_request_versions'
4297 __table_args__ = (
4297 __table_args__ = (
4298 base_table_args,
4298 base_table_args,
4299 )
4299 )
4300
4300
4301 pull_request_version_id = Column(
4301 pull_request_version_id = Column(
4302 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4302 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4303 pull_request_id = Column(
4303 pull_request_id = Column(
4304 'pull_request_id', Integer(),
4304 'pull_request_id', Integer(),
4305 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4305 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4306 pull_request = relationship('PullRequest')
4306 pull_request = relationship('PullRequest')
4307
4307
4308 def __repr__(self):
4308 def __repr__(self):
4309 if self.pull_request_version_id:
4309 if self.pull_request_version_id:
4310 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4310 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4311 else:
4311 else:
4312 return '<DB:PullRequestVersion at %#x>' % id(self)
4312 return '<DB:PullRequestVersion at %#x>' % id(self)
4313
4313
4314 @property
4314 @property
4315 def reviewers(self):
4315 def reviewers(self):
4316 return self.pull_request.reviewers
4316 return self.pull_request.reviewers
4317
4317
4318 @property
4318 @property
4319 def versions(self):
4319 def versions(self):
4320 return self.pull_request.versions
4320 return self.pull_request.versions
4321
4321
4322 def is_closed(self):
4322 def is_closed(self):
4323 # calculate from original
4323 # calculate from original
4324 return self.pull_request.status == self.STATUS_CLOSED
4324 return self.pull_request.status == self.STATUS_CLOSED
4325
4325
4326 def is_state_changing(self):
4326 def is_state_changing(self):
4327 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4327 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4328
4328
4329 def calculated_review_status(self):
4329 def calculated_review_status(self):
4330 return self.pull_request.calculated_review_status()
4330 return self.pull_request.calculated_review_status()
4331
4331
4332 def reviewers_statuses(self):
4332 def reviewers_statuses(self):
4333 return self.pull_request.reviewers_statuses()
4333 return self.pull_request.reviewers_statuses()
4334
4334
4335
4335
4336 class PullRequestReviewers(Base, BaseModel):
4336 class PullRequestReviewers(Base, BaseModel):
4337 __tablename__ = 'pull_request_reviewers'
4337 __tablename__ = 'pull_request_reviewers'
4338 __table_args__ = (
4338 __table_args__ = (
4339 base_table_args,
4339 base_table_args,
4340 )
4340 )
4341
4341
4342 @hybrid_property
4342 @hybrid_property
4343 def reasons(self):
4343 def reasons(self):
4344 if not self._reasons:
4344 if not self._reasons:
4345 return []
4345 return []
4346 return self._reasons
4346 return self._reasons
4347
4347
4348 @reasons.setter
4348 @reasons.setter
4349 def reasons(self, val):
4349 def reasons(self, val):
4350 val = val or []
4350 val = val or []
4351 if any(not isinstance(x, compat.string_types) for x in val):
4351 if any(not isinstance(x, compat.string_types) for x in val):
4352 raise Exception('invalid reasons type, must be list of strings')
4352 raise Exception('invalid reasons type, must be list of strings')
4353 self._reasons = val
4353 self._reasons = val
4354
4354
4355 pull_requests_reviewers_id = Column(
4355 pull_requests_reviewers_id = Column(
4356 'pull_requests_reviewers_id', Integer(), nullable=False,
4356 'pull_requests_reviewers_id', Integer(), nullable=False,
4357 primary_key=True)
4357 primary_key=True)
4358 pull_request_id = Column(
4358 pull_request_id = Column(
4359 "pull_request_id", Integer(),
4359 "pull_request_id", Integer(),
4360 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4360 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4361 user_id = Column(
4361 user_id = Column(
4362 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4362 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4363 _reasons = Column(
4363 _reasons = Column(
4364 'reason', MutationList.as_mutable(
4364 'reason', MutationList.as_mutable(
4365 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4365 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4366
4366
4367 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4367 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4368 user = relationship('User')
4368 user = relationship('User')
4369 pull_request = relationship('PullRequest')
4369 pull_request = relationship('PullRequest')
4370
4370
4371 rule_data = Column(
4371 rule_data = Column(
4372 'rule_data_json',
4372 'rule_data_json',
4373 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4373 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4374
4374
4375 def rule_user_group_data(self):
4375 def rule_user_group_data(self):
4376 """
4376 """
4377 Returns the voting user group rule data for this reviewer
4377 Returns the voting user group rule data for this reviewer
4378 """
4378 """
4379
4379
4380 if self.rule_data and 'vote_rule' in self.rule_data:
4380 if self.rule_data and 'vote_rule' in self.rule_data:
4381 user_group_data = {}
4381 user_group_data = {}
4382 if 'rule_user_group_entry_id' in self.rule_data:
4382 if 'rule_user_group_entry_id' in self.rule_data:
4383 # means a group with voting rules !
4383 # means a group with voting rules !
4384 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4384 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4385 user_group_data['name'] = self.rule_data['rule_name']
4385 user_group_data['name'] = self.rule_data['rule_name']
4386 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4386 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4387
4387
4388 return user_group_data
4388 return user_group_data
4389
4389
4390 def __unicode__(self):
4390 def __unicode__(self):
4391 return u"<%s('id:%s')>" % (self.__class__.__name__,
4391 return u"<%s('id:%s')>" % (self.__class__.__name__,
4392 self.pull_requests_reviewers_id)
4392 self.pull_requests_reviewers_id)
4393
4393
4394
4394
4395 class Notification(Base, BaseModel):
4395 class Notification(Base, BaseModel):
4396 __tablename__ = 'notifications'
4396 __tablename__ = 'notifications'
4397 __table_args__ = (
4397 __table_args__ = (
4398 Index('notification_type_idx', 'type'),
4398 Index('notification_type_idx', 'type'),
4399 base_table_args,
4399 base_table_args,
4400 )
4400 )
4401
4401
4402 TYPE_CHANGESET_COMMENT = u'cs_comment'
4402 TYPE_CHANGESET_COMMENT = u'cs_comment'
4403 TYPE_MESSAGE = u'message'
4403 TYPE_MESSAGE = u'message'
4404 TYPE_MENTION = u'mention'
4404 TYPE_MENTION = u'mention'
4405 TYPE_REGISTRATION = u'registration'
4405 TYPE_REGISTRATION = u'registration'
4406 TYPE_PULL_REQUEST = u'pull_request'
4406 TYPE_PULL_REQUEST = u'pull_request'
4407 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4407 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4408 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4408 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4409
4409
4410 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4410 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4411 subject = Column('subject', Unicode(512), nullable=True)
4411 subject = Column('subject', Unicode(512), nullable=True)
4412 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4412 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4413 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4413 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4414 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4414 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4415 type_ = Column('type', Unicode(255))
4415 type_ = Column('type', Unicode(255))
4416
4416
4417 created_by_user = relationship('User')
4417 created_by_user = relationship('User')
4418 notifications_to_users = relationship('UserNotification', lazy='joined',
4418 notifications_to_users = relationship('UserNotification', lazy='joined',
4419 cascade="all, delete-orphan")
4419 cascade="all, delete-orphan")
4420
4420
4421 @property
4421 @property
4422 def recipients(self):
4422 def recipients(self):
4423 return [x.user for x in UserNotification.query()\
4423 return [x.user for x in UserNotification.query()\
4424 .filter(UserNotification.notification == self)\
4424 .filter(UserNotification.notification == self)\
4425 .order_by(UserNotification.user_id.asc()).all()]
4425 .order_by(UserNotification.user_id.asc()).all()]
4426
4426
4427 @classmethod
4427 @classmethod
4428 def create(cls, created_by, subject, body, recipients, type_=None):
4428 def create(cls, created_by, subject, body, recipients, type_=None):
4429 if type_ is None:
4429 if type_ is None:
4430 type_ = Notification.TYPE_MESSAGE
4430 type_ = Notification.TYPE_MESSAGE
4431
4431
4432 notification = cls()
4432 notification = cls()
4433 notification.created_by_user = created_by
4433 notification.created_by_user = created_by
4434 notification.subject = subject
4434 notification.subject = subject
4435 notification.body = body
4435 notification.body = body
4436 notification.type_ = type_
4436 notification.type_ = type_
4437 notification.created_on = datetime.datetime.now()
4437 notification.created_on = datetime.datetime.now()
4438
4438
4439 # For each recipient link the created notification to his account
4439 # For each recipient link the created notification to his account
4440 for u in recipients:
4440 for u in recipients:
4441 assoc = UserNotification()
4441 assoc = UserNotification()
4442 assoc.user_id = u.user_id
4442 assoc.user_id = u.user_id
4443 assoc.notification = notification
4443 assoc.notification = notification
4444
4444
4445 # if created_by is inside recipients mark his notification
4445 # if created_by is inside recipients mark his notification
4446 # as read
4446 # as read
4447 if u.user_id == created_by.user_id:
4447 if u.user_id == created_by.user_id:
4448 assoc.read = True
4448 assoc.read = True
4449 Session().add(assoc)
4449 Session().add(assoc)
4450
4450
4451 Session().add(notification)
4451 Session().add(notification)
4452
4452
4453 return notification
4453 return notification
4454
4454
4455
4455
4456 class UserNotification(Base, BaseModel):
4456 class UserNotification(Base, BaseModel):
4457 __tablename__ = 'user_to_notification'
4457 __tablename__ = 'user_to_notification'
4458 __table_args__ = (
4458 __table_args__ = (
4459 UniqueConstraint('user_id', 'notification_id'),
4459 UniqueConstraint('user_id', 'notification_id'),
4460 base_table_args
4460 base_table_args
4461 )
4461 )
4462
4462
4463 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4463 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4464 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4464 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4465 read = Column('read', Boolean, default=False)
4465 read = Column('read', Boolean, default=False)
4466 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4466 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4467
4467
4468 user = relationship('User', lazy="joined")
4468 user = relationship('User', lazy="joined")
4469 notification = relationship('Notification', lazy="joined",
4469 notification = relationship('Notification', lazy="joined",
4470 order_by=lambda: Notification.created_on.desc(),)
4470 order_by=lambda: Notification.created_on.desc(),)
4471
4471
4472 def mark_as_read(self):
4472 def mark_as_read(self):
4473 self.read = True
4473 self.read = True
4474 Session().add(self)
4474 Session().add(self)
4475
4475
4476
4476
4477 class Gist(Base, BaseModel):
4477 class Gist(Base, BaseModel):
4478 __tablename__ = 'gists'
4478 __tablename__ = 'gists'
4479 __table_args__ = (
4479 __table_args__ = (
4480 Index('g_gist_access_id_idx', 'gist_access_id'),
4480 Index('g_gist_access_id_idx', 'gist_access_id'),
4481 Index('g_created_on_idx', 'created_on'),
4481 Index('g_created_on_idx', 'created_on'),
4482 base_table_args
4482 base_table_args
4483 )
4483 )
4484
4484
4485 GIST_PUBLIC = u'public'
4485 GIST_PUBLIC = u'public'
4486 GIST_PRIVATE = u'private'
4486 GIST_PRIVATE = u'private'
4487 DEFAULT_FILENAME = u'gistfile1.txt'
4487 DEFAULT_FILENAME = u'gistfile1.txt'
4488
4488
4489 ACL_LEVEL_PUBLIC = u'acl_public'
4489 ACL_LEVEL_PUBLIC = u'acl_public'
4490 ACL_LEVEL_PRIVATE = u'acl_private'
4490 ACL_LEVEL_PRIVATE = u'acl_private'
4491
4491
4492 gist_id = Column('gist_id', Integer(), primary_key=True)
4492 gist_id = Column('gist_id', Integer(), primary_key=True)
4493 gist_access_id = Column('gist_access_id', Unicode(250))
4493 gist_access_id = Column('gist_access_id', Unicode(250))
4494 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4494 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4495 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4495 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4496 gist_expires = Column('gist_expires', Float(53), nullable=False)
4496 gist_expires = Column('gist_expires', Float(53), nullable=False)
4497 gist_type = Column('gist_type', Unicode(128), nullable=False)
4497 gist_type = Column('gist_type', Unicode(128), nullable=False)
4498 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4498 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4499 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4499 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4500 acl_level = Column('acl_level', Unicode(128), nullable=True)
4500 acl_level = Column('acl_level', Unicode(128), nullable=True)
4501
4501
4502 owner = relationship('User')
4502 owner = relationship('User')
4503
4503
4504 def __repr__(self):
4504 def __repr__(self):
4505 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4505 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4506
4506
4507 @hybrid_property
4507 @hybrid_property
4508 def description_safe(self):
4508 def description_safe(self):
4509 from rhodecode.lib import helpers as h
4509 from rhodecode.lib import helpers as h
4510 return h.escape(self.gist_description)
4510 return h.escape(self.gist_description)
4511
4511
4512 @classmethod
4512 @classmethod
4513 def get_or_404(cls, id_):
4513 def get_or_404(cls, id_):
4514 from pyramid.httpexceptions import HTTPNotFound
4514 from pyramid.httpexceptions import HTTPNotFound
4515
4515
4516 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4516 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4517 if not res:
4517 if not res:
4518 raise HTTPNotFound()
4518 raise HTTPNotFound()
4519 return res
4519 return res
4520
4520
4521 @classmethod
4521 @classmethod
4522 def get_by_access_id(cls, gist_access_id):
4522 def get_by_access_id(cls, gist_access_id):
4523 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4523 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4524
4524
4525 def gist_url(self):
4525 def gist_url(self):
4526 from rhodecode.model.gist import GistModel
4526 from rhodecode.model.gist import GistModel
4527 return GistModel().get_url(self)
4527 return GistModel().get_url(self)
4528
4528
4529 @classmethod
4529 @classmethod
4530 def base_path(cls):
4530 def base_path(cls):
4531 """
4531 """
4532 Returns base path when all gists are stored
4532 Returns base path when all gists are stored
4533
4533
4534 :param cls:
4534 :param cls:
4535 """
4535 """
4536 from rhodecode.model.gist import GIST_STORE_LOC
4536 from rhodecode.model.gist import GIST_STORE_LOC
4537 q = Session().query(RhodeCodeUi)\
4537 q = Session().query(RhodeCodeUi)\
4538 .filter(RhodeCodeUi.ui_key == URL_SEP)
4538 .filter(RhodeCodeUi.ui_key == URL_SEP)
4539 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4539 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4540 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4540 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4541
4541
4542 def get_api_data(self):
4542 def get_api_data(self):
4543 """
4543 """
4544 Common function for generating gist related data for API
4544 Common function for generating gist related data for API
4545 """
4545 """
4546 gist = self
4546 gist = self
4547 data = {
4547 data = {
4548 'gist_id': gist.gist_id,
4548 'gist_id': gist.gist_id,
4549 'type': gist.gist_type,
4549 'type': gist.gist_type,
4550 'access_id': gist.gist_access_id,
4550 'access_id': gist.gist_access_id,
4551 'description': gist.gist_description,
4551 'description': gist.gist_description,
4552 'url': gist.gist_url(),
4552 'url': gist.gist_url(),
4553 'expires': gist.gist_expires,
4553 'expires': gist.gist_expires,
4554 'created_on': gist.created_on,
4554 'created_on': gist.created_on,
4555 'modified_at': gist.modified_at,
4555 'modified_at': gist.modified_at,
4556 'content': None,
4556 'content': None,
4557 'acl_level': gist.acl_level,
4557 'acl_level': gist.acl_level,
4558 }
4558 }
4559 return data
4559 return data
4560
4560
4561 def __json__(self):
4561 def __json__(self):
4562 data = dict(
4562 data = dict(
4563 )
4563 )
4564 data.update(self.get_api_data())
4564 data.update(self.get_api_data())
4565 return data
4565 return data
4566 # SCM functions
4566 # SCM functions
4567
4567
4568 def scm_instance(self, **kwargs):
4568 def scm_instance(self, **kwargs):
4569 """
4569 """
4570 Get an instance of VCS Repository
4570 Get an instance of VCS Repository
4571
4571
4572 :param kwargs:
4572 :param kwargs:
4573 """
4573 """
4574 from rhodecode.model.gist import GistModel
4574 from rhodecode.model.gist import GistModel
4575 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4575 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4576 return get_vcs_instance(
4576 return get_vcs_instance(
4577 repo_path=safe_str(full_repo_path), create=False,
4577 repo_path=safe_str(full_repo_path), create=False,
4578 _vcs_alias=GistModel.vcs_backend)
4578 _vcs_alias=GistModel.vcs_backend)
4579
4579
4580
4580
4581 class ExternalIdentity(Base, BaseModel):
4581 class ExternalIdentity(Base, BaseModel):
4582 __tablename__ = 'external_identities'
4582 __tablename__ = 'external_identities'
4583 __table_args__ = (
4583 __table_args__ = (
4584 Index('local_user_id_idx', 'local_user_id'),
4584 Index('local_user_id_idx', 'local_user_id'),
4585 Index('external_id_idx', 'external_id'),
4585 Index('external_id_idx', 'external_id'),
4586 base_table_args
4586 base_table_args
4587 )
4587 )
4588
4588
4589 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4589 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4590 external_username = Column('external_username', Unicode(1024), default=u'')
4590 external_username = Column('external_username', Unicode(1024), default=u'')
4591 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4591 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4592 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4592 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4593 access_token = Column('access_token', String(1024), default=u'')
4593 access_token = Column('access_token', String(1024), default=u'')
4594 alt_token = Column('alt_token', String(1024), default=u'')
4594 alt_token = Column('alt_token', String(1024), default=u'')
4595 token_secret = Column('token_secret', String(1024), default=u'')
4595 token_secret = Column('token_secret', String(1024), default=u'')
4596
4596
4597 @classmethod
4597 @classmethod
4598 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4598 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4599 """
4599 """
4600 Returns ExternalIdentity instance based on search params
4600 Returns ExternalIdentity instance based on search params
4601
4601
4602 :param external_id:
4602 :param external_id:
4603 :param provider_name:
4603 :param provider_name:
4604 :return: ExternalIdentity
4604 :return: ExternalIdentity
4605 """
4605 """
4606 query = cls.query()
4606 query = cls.query()
4607 query = query.filter(cls.external_id == external_id)
4607 query = query.filter(cls.external_id == external_id)
4608 query = query.filter(cls.provider_name == provider_name)
4608 query = query.filter(cls.provider_name == provider_name)
4609 if local_user_id:
4609 if local_user_id:
4610 query = query.filter(cls.local_user_id == local_user_id)
4610 query = query.filter(cls.local_user_id == local_user_id)
4611 return query.first()
4611 return query.first()
4612
4612
4613 @classmethod
4613 @classmethod
4614 def user_by_external_id_and_provider(cls, external_id, provider_name):
4614 def user_by_external_id_and_provider(cls, external_id, provider_name):
4615 """
4615 """
4616 Returns User instance based on search params
4616 Returns User instance based on search params
4617
4617
4618 :param external_id:
4618 :param external_id:
4619 :param provider_name:
4619 :param provider_name:
4620 :return: User
4620 :return: User
4621 """
4621 """
4622 query = User.query()
4622 query = User.query()
4623 query = query.filter(cls.external_id == external_id)
4623 query = query.filter(cls.external_id == external_id)
4624 query = query.filter(cls.provider_name == provider_name)
4624 query = query.filter(cls.provider_name == provider_name)
4625 query = query.filter(User.user_id == cls.local_user_id)
4625 query = query.filter(User.user_id == cls.local_user_id)
4626 return query.first()
4626 return query.first()
4627
4627
4628 @classmethod
4628 @classmethod
4629 def by_local_user_id(cls, local_user_id):
4629 def by_local_user_id(cls, local_user_id):
4630 """
4630 """
4631 Returns all tokens for user
4631 Returns all tokens for user
4632
4632
4633 :param local_user_id:
4633 :param local_user_id:
4634 :return: ExternalIdentity
4634 :return: ExternalIdentity
4635 """
4635 """
4636 query = cls.query()
4636 query = cls.query()
4637 query = query.filter(cls.local_user_id == local_user_id)
4637 query = query.filter(cls.local_user_id == local_user_id)
4638 return query
4638 return query
4639
4639
4640 @classmethod
4640 @classmethod
4641 def load_provider_plugin(cls, plugin_id):
4641 def load_provider_plugin(cls, plugin_id):
4642 from rhodecode.authentication.base import loadplugin
4642 from rhodecode.authentication.base import loadplugin
4643 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4643 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4644 auth_plugin = loadplugin(_plugin_id)
4644 auth_plugin = loadplugin(_plugin_id)
4645 return auth_plugin
4645 return auth_plugin
4646
4646
4647
4647
4648 class Integration(Base, BaseModel):
4648 class Integration(Base, BaseModel):
4649 __tablename__ = 'integrations'
4649 __tablename__ = 'integrations'
4650 __table_args__ = (
4650 __table_args__ = (
4651 base_table_args
4651 base_table_args
4652 )
4652 )
4653
4653
4654 integration_id = Column('integration_id', Integer(), primary_key=True)
4654 integration_id = Column('integration_id', Integer(), primary_key=True)
4655 integration_type = Column('integration_type', String(255))
4655 integration_type = Column('integration_type', String(255))
4656 enabled = Column('enabled', Boolean(), nullable=False)
4656 enabled = Column('enabled', Boolean(), nullable=False)
4657 name = Column('name', String(255), nullable=False)
4657 name = Column('name', String(255), nullable=False)
4658 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4658 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4659 default=False)
4659 default=False)
4660
4660
4661 settings = Column(
4661 settings = Column(
4662 'settings_json', MutationObj.as_mutable(
4662 'settings_json', MutationObj.as_mutable(
4663 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4663 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4664 repo_id = Column(
4664 repo_id = Column(
4665 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4665 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4666 nullable=True, unique=None, default=None)
4666 nullable=True, unique=None, default=None)
4667 repo = relationship('Repository', lazy='joined')
4667 repo = relationship('Repository', lazy='joined')
4668
4668
4669 repo_group_id = Column(
4669 repo_group_id = Column(
4670 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4670 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4671 nullable=True, unique=None, default=None)
4671 nullable=True, unique=None, default=None)
4672 repo_group = relationship('RepoGroup', lazy='joined')
4672 repo_group = relationship('RepoGroup', lazy='joined')
4673
4673
4674 @property
4674 @property
4675 def scope(self):
4675 def scope(self):
4676 if self.repo:
4676 if self.repo:
4677 return repr(self.repo)
4677 return repr(self.repo)
4678 if self.repo_group:
4678 if self.repo_group:
4679 if self.child_repos_only:
4679 if self.child_repos_only:
4680 return repr(self.repo_group) + ' (child repos only)'
4680 return repr(self.repo_group) + ' (child repos only)'
4681 else:
4681 else:
4682 return repr(self.repo_group) + ' (recursive)'
4682 return repr(self.repo_group) + ' (recursive)'
4683 if self.child_repos_only:
4683 if self.child_repos_only:
4684 return 'root_repos'
4684 return 'root_repos'
4685 return 'global'
4685 return 'global'
4686
4686
4687 def __repr__(self):
4687 def __repr__(self):
4688 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4688 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4689
4689
4690
4690
4691 class RepoReviewRuleUser(Base, BaseModel):
4691 class RepoReviewRuleUser(Base, BaseModel):
4692 __tablename__ = 'repo_review_rules_users'
4692 __tablename__ = 'repo_review_rules_users'
4693 __table_args__ = (
4693 __table_args__ = (
4694 base_table_args
4694 base_table_args
4695 )
4695 )
4696
4696
4697 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4697 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4698 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4698 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4699 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4699 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4700 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4700 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4701 user = relationship('User')
4701 user = relationship('User')
4702
4702
4703 def rule_data(self):
4703 def rule_data(self):
4704 return {
4704 return {
4705 'mandatory': self.mandatory
4705 'mandatory': self.mandatory
4706 }
4706 }
4707
4707
4708
4708
4709 class RepoReviewRuleUserGroup(Base, BaseModel):
4709 class RepoReviewRuleUserGroup(Base, BaseModel):
4710 __tablename__ = 'repo_review_rules_users_groups'
4710 __tablename__ = 'repo_review_rules_users_groups'
4711 __table_args__ = (
4711 __table_args__ = (
4712 base_table_args
4712 base_table_args
4713 )
4713 )
4714
4714
4715 VOTE_RULE_ALL = -1
4715 VOTE_RULE_ALL = -1
4716
4716
4717 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4717 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4718 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4718 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4719 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4719 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4720 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4720 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4721 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4721 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4722 users_group = relationship('UserGroup')
4722 users_group = relationship('UserGroup')
4723
4723
4724 def rule_data(self):
4724 def rule_data(self):
4725 return {
4725 return {
4726 'mandatory': self.mandatory,
4726 'mandatory': self.mandatory,
4727 'vote_rule': self.vote_rule
4727 'vote_rule': self.vote_rule
4728 }
4728 }
4729
4729
4730 @property
4730 @property
4731 def vote_rule_label(self):
4731 def vote_rule_label(self):
4732 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4732 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4733 return 'all must vote'
4733 return 'all must vote'
4734 else:
4734 else:
4735 return 'min. vote {}'.format(self.vote_rule)
4735 return 'min. vote {}'.format(self.vote_rule)
4736
4736
4737
4737
4738 class RepoReviewRule(Base, BaseModel):
4738 class RepoReviewRule(Base, BaseModel):
4739 __tablename__ = 'repo_review_rules'
4739 __tablename__ = 'repo_review_rules'
4740 __table_args__ = (
4740 __table_args__ = (
4741 base_table_args
4741 base_table_args
4742 )
4742 )
4743
4743
4744 repo_review_rule_id = Column(
4744 repo_review_rule_id = Column(
4745 'repo_review_rule_id', Integer(), primary_key=True)
4745 'repo_review_rule_id', Integer(), primary_key=True)
4746 repo_id = Column(
4746 repo_id = Column(
4747 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4747 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4748 repo = relationship('Repository', backref='review_rules')
4748 repo = relationship('Repository', backref='review_rules')
4749
4749
4750 review_rule_name = Column('review_rule_name', String(255))
4750 review_rule_name = Column('review_rule_name', String(255))
4751 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4751 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4752 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4752 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4753 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4753 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4754
4754
4755 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4755 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4756 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4756 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4757 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4757 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4758 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4758 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4759
4759
4760 rule_users = relationship('RepoReviewRuleUser')
4760 rule_users = relationship('RepoReviewRuleUser')
4761 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4761 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4762
4762
4763 def _validate_pattern(self, value):
4763 def _validate_pattern(self, value):
4764 re.compile('^' + glob2re(value) + '$')
4764 re.compile('^' + glob2re(value) + '$')
4765
4765
4766 @hybrid_property
4766 @hybrid_property
4767 def source_branch_pattern(self):
4767 def source_branch_pattern(self):
4768 return self._branch_pattern or '*'
4768 return self._branch_pattern or '*'
4769
4769
4770 @source_branch_pattern.setter
4770 @source_branch_pattern.setter
4771 def source_branch_pattern(self, value):
4771 def source_branch_pattern(self, value):
4772 self._validate_pattern(value)
4772 self._validate_pattern(value)
4773 self._branch_pattern = value or '*'
4773 self._branch_pattern = value or '*'
4774
4774
4775 @hybrid_property
4775 @hybrid_property
4776 def target_branch_pattern(self):
4776 def target_branch_pattern(self):
4777 return self._target_branch_pattern or '*'
4777 return self._target_branch_pattern or '*'
4778
4778
4779 @target_branch_pattern.setter
4779 @target_branch_pattern.setter
4780 def target_branch_pattern(self, value):
4780 def target_branch_pattern(self, value):
4781 self._validate_pattern(value)
4781 self._validate_pattern(value)
4782 self._target_branch_pattern = value or '*'
4782 self._target_branch_pattern = value or '*'
4783
4783
4784 @hybrid_property
4784 @hybrid_property
4785 def file_pattern(self):
4785 def file_pattern(self):
4786 return self._file_pattern or '*'
4786 return self._file_pattern or '*'
4787
4787
4788 @file_pattern.setter
4788 @file_pattern.setter
4789 def file_pattern(self, value):
4789 def file_pattern(self, value):
4790 self._validate_pattern(value)
4790 self._validate_pattern(value)
4791 self._file_pattern = value or '*'
4791 self._file_pattern = value or '*'
4792
4792
4793 def matches(self, source_branch, target_branch, files_changed):
4793 def matches(self, source_branch, target_branch, files_changed):
4794 """
4794 """
4795 Check if this review rule matches a branch/files in a pull request
4795 Check if this review rule matches a branch/files in a pull request
4796
4796
4797 :param source_branch: source branch name for the commit
4797 :param source_branch: source branch name for the commit
4798 :param target_branch: target branch name for the commit
4798 :param target_branch: target branch name for the commit
4799 :param files_changed: list of file paths changed in the pull request
4799 :param files_changed: list of file paths changed in the pull request
4800 """
4800 """
4801
4801
4802 source_branch = source_branch or ''
4802 source_branch = source_branch or ''
4803 target_branch = target_branch or ''
4803 target_branch = target_branch or ''
4804 files_changed = files_changed or []
4804 files_changed = files_changed or []
4805
4805
4806 branch_matches = True
4806 branch_matches = True
4807 if source_branch or target_branch:
4807 if source_branch or target_branch:
4808 if self.source_branch_pattern == '*':
4808 if self.source_branch_pattern == '*':
4809 source_branch_match = True
4809 source_branch_match = True
4810 else:
4810 else:
4811 if self.source_branch_pattern.startswith('re:'):
4811 if self.source_branch_pattern.startswith('re:'):
4812 source_pattern = self.source_branch_pattern[3:]
4812 source_pattern = self.source_branch_pattern[3:]
4813 else:
4813 else:
4814 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4814 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4815 source_branch_regex = re.compile(source_pattern)
4815 source_branch_regex = re.compile(source_pattern)
4816 source_branch_match = bool(source_branch_regex.search(source_branch))
4816 source_branch_match = bool(source_branch_regex.search(source_branch))
4817 if self.target_branch_pattern == '*':
4817 if self.target_branch_pattern == '*':
4818 target_branch_match = True
4818 target_branch_match = True
4819 else:
4819 else:
4820 if self.target_branch_pattern.startswith('re:'):
4820 if self.target_branch_pattern.startswith('re:'):
4821 target_pattern = self.target_branch_pattern[3:]
4821 target_pattern = self.target_branch_pattern[3:]
4822 else:
4822 else:
4823 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4823 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4824 target_branch_regex = re.compile(target_pattern)
4824 target_branch_regex = re.compile(target_pattern)
4825 target_branch_match = bool(target_branch_regex.search(target_branch))
4825 target_branch_match = bool(target_branch_regex.search(target_branch))
4826
4826
4827 branch_matches = source_branch_match and target_branch_match
4827 branch_matches = source_branch_match and target_branch_match
4828
4828
4829 files_matches = True
4829 files_matches = True
4830 if self.file_pattern != '*':
4830 if self.file_pattern != '*':
4831 files_matches = False
4831 files_matches = False
4832 if self.file_pattern.startswith('re:'):
4832 if self.file_pattern.startswith('re:'):
4833 file_pattern = self.file_pattern[3:]
4833 file_pattern = self.file_pattern[3:]
4834 else:
4834 else:
4835 file_pattern = glob2re(self.file_pattern)
4835 file_pattern = glob2re(self.file_pattern)
4836 file_regex = re.compile(file_pattern)
4836 file_regex = re.compile(file_pattern)
4837 for filename in files_changed:
4837 for filename in files_changed:
4838 if file_regex.search(filename):
4838 if file_regex.search(filename):
4839 files_matches = True
4839 files_matches = True
4840 break
4840 break
4841
4841
4842 return branch_matches and files_matches
4842 return branch_matches and files_matches
4843
4843
4844 @property
4844 @property
4845 def review_users(self):
4845 def review_users(self):
4846 """ Returns the users which this rule applies to """
4846 """ Returns the users which this rule applies to """
4847
4847
4848 users = collections.OrderedDict()
4848 users = collections.OrderedDict()
4849
4849
4850 for rule_user in self.rule_users:
4850 for rule_user in self.rule_users:
4851 if rule_user.user.active:
4851 if rule_user.user.active:
4852 if rule_user.user not in users:
4852 if rule_user.user not in users:
4853 users[rule_user.user.username] = {
4853 users[rule_user.user.username] = {
4854 'user': rule_user.user,
4854 'user': rule_user.user,
4855 'source': 'user',
4855 'source': 'user',
4856 'source_data': {},
4856 'source_data': {},
4857 'data': rule_user.rule_data()
4857 'data': rule_user.rule_data()
4858 }
4858 }
4859
4859
4860 for rule_user_group in self.rule_user_groups:
4860 for rule_user_group in self.rule_user_groups:
4861 source_data = {
4861 source_data = {
4862 'user_group_id': rule_user_group.users_group.users_group_id,
4862 'user_group_id': rule_user_group.users_group.users_group_id,
4863 'name': rule_user_group.users_group.users_group_name,
4863 'name': rule_user_group.users_group.users_group_name,
4864 'members': len(rule_user_group.users_group.members)
4864 'members': len(rule_user_group.users_group.members)
4865 }
4865 }
4866 for member in rule_user_group.users_group.members:
4866 for member in rule_user_group.users_group.members:
4867 if member.user.active:
4867 if member.user.active:
4868 key = member.user.username
4868 key = member.user.username
4869 if key in users:
4869 if key in users:
4870 # skip this member as we have him already
4870 # skip this member as we have him already
4871 # this prevents from override the "first" matched
4871 # this prevents from override the "first" matched
4872 # users with duplicates in multiple groups
4872 # users with duplicates in multiple groups
4873 continue
4873 continue
4874
4874
4875 users[key] = {
4875 users[key] = {
4876 'user': member.user,
4876 'user': member.user,
4877 'source': 'user_group',
4877 'source': 'user_group',
4878 'source_data': source_data,
4878 'source_data': source_data,
4879 'data': rule_user_group.rule_data()
4879 'data': rule_user_group.rule_data()
4880 }
4880 }
4881
4881
4882 return users
4882 return users
4883
4883
4884 def user_group_vote_rule(self, user_id):
4884 def user_group_vote_rule(self, user_id):
4885
4885
4886 rules = []
4886 rules = []
4887 if not self.rule_user_groups:
4887 if not self.rule_user_groups:
4888 return rules
4888 return rules
4889
4889
4890 for user_group in self.rule_user_groups:
4890 for user_group in self.rule_user_groups:
4891 user_group_members = [x.user_id for x in user_group.users_group.members]
4891 user_group_members = [x.user_id for x in user_group.users_group.members]
4892 if user_id in user_group_members:
4892 if user_id in user_group_members:
4893 rules.append(user_group)
4893 rules.append(user_group)
4894 return rules
4894 return rules
4895
4895
4896 def __repr__(self):
4896 def __repr__(self):
4897 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4897 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4898 self.repo_review_rule_id, self.repo)
4898 self.repo_review_rule_id, self.repo)
4899
4899
4900
4900
4901 class ScheduleEntry(Base, BaseModel):
4901 class ScheduleEntry(Base, BaseModel):
4902 __tablename__ = 'schedule_entries'
4902 __tablename__ = 'schedule_entries'
4903 __table_args__ = (
4903 __table_args__ = (
4904 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4904 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4905 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4905 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4906 base_table_args,
4906 base_table_args,
4907 )
4907 )
4908
4908
4909 schedule_types = ['crontab', 'timedelta', 'integer']
4909 schedule_types = ['crontab', 'timedelta', 'integer']
4910 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4910 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4911
4911
4912 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4912 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4913 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4913 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4914 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4914 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4915
4915
4916 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4916 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4917 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4917 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4918
4918
4919 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4919 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4920 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4920 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4921
4921
4922 # task
4922 # task
4923 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4923 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4924 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4924 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4925 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4925 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4926 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4926 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4927
4927
4928 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4928 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4929 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4929 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4930
4930
4931 @hybrid_property
4931 @hybrid_property
4932 def schedule_type(self):
4932 def schedule_type(self):
4933 return self._schedule_type
4933 return self._schedule_type
4934
4934
4935 @schedule_type.setter
4935 @schedule_type.setter
4936 def schedule_type(self, val):
4936 def schedule_type(self, val):
4937 if val not in self.schedule_types:
4937 if val not in self.schedule_types:
4938 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4938 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4939 val, self.schedule_type))
4939 val, self.schedule_type))
4940
4940
4941 self._schedule_type = val
4941 self._schedule_type = val
4942
4942
4943 @classmethod
4943 @classmethod
4944 def get_uid(cls, obj):
4944 def get_uid(cls, obj):
4945 args = obj.task_args
4945 args = obj.task_args
4946 kwargs = obj.task_kwargs
4946 kwargs = obj.task_kwargs
4947 if isinstance(args, JsonRaw):
4947 if isinstance(args, JsonRaw):
4948 try:
4948 try:
4949 args = json.loads(args)
4949 args = json.loads(args)
4950 except ValueError:
4950 except ValueError:
4951 args = tuple()
4951 args = tuple()
4952
4952
4953 if isinstance(kwargs, JsonRaw):
4953 if isinstance(kwargs, JsonRaw):
4954 try:
4954 try:
4955 kwargs = json.loads(kwargs)
4955 kwargs = json.loads(kwargs)
4956 except ValueError:
4956 except ValueError:
4957 kwargs = dict()
4957 kwargs = dict()
4958
4958
4959 dot_notation = obj.task_dot_notation
4959 dot_notation = obj.task_dot_notation
4960 val = '.'.join(map(safe_str, [
4960 val = '.'.join(map(safe_str, [
4961 sorted(dot_notation), args, sorted(kwargs.items())]))
4961 sorted(dot_notation), args, sorted(kwargs.items())]))
4962 return hashlib.sha1(val).hexdigest()
4962 return hashlib.sha1(val).hexdigest()
4963
4963
4964 @classmethod
4964 @classmethod
4965 def get_by_schedule_name(cls, schedule_name):
4965 def get_by_schedule_name(cls, schedule_name):
4966 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4966 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4967
4967
4968 @classmethod
4968 @classmethod
4969 def get_by_schedule_id(cls, schedule_id):
4969 def get_by_schedule_id(cls, schedule_id):
4970 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4970 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4971
4971
4972 @property
4972 @property
4973 def task(self):
4973 def task(self):
4974 return self.task_dot_notation
4974 return self.task_dot_notation
4975
4975
4976 @property
4976 @property
4977 def schedule(self):
4977 def schedule(self):
4978 from rhodecode.lib.celerylib.utils import raw_2_schedule
4978 from rhodecode.lib.celerylib.utils import raw_2_schedule
4979 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4979 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4980 return schedule
4980 return schedule
4981
4981
4982 @property
4982 @property
4983 def args(self):
4983 def args(self):
4984 try:
4984 try:
4985 return list(self.task_args or [])
4985 return list(self.task_args or [])
4986 except ValueError:
4986 except ValueError:
4987 return list()
4987 return list()
4988
4988
4989 @property
4989 @property
4990 def kwargs(self):
4990 def kwargs(self):
4991 try:
4991 try:
4992 return dict(self.task_kwargs or {})
4992 return dict(self.task_kwargs or {})
4993 except ValueError:
4993 except ValueError:
4994 return dict()
4994 return dict()
4995
4995
4996 def _as_raw(self, val):
4996 def _as_raw(self, val):
4997 if hasattr(val, 'de_coerce'):
4997 if hasattr(val, 'de_coerce'):
4998 val = val.de_coerce()
4998 val = val.de_coerce()
4999 if val:
4999 if val:
5000 val = json.dumps(val)
5000 val = json.dumps(val)
5001
5001
5002 return val
5002 return val
5003
5003
5004 @property
5004 @property
5005 def schedule_definition_raw(self):
5005 def schedule_definition_raw(self):
5006 return self._as_raw(self.schedule_definition)
5006 return self._as_raw(self.schedule_definition)
5007
5007
5008 @property
5008 @property
5009 def args_raw(self):
5009 def args_raw(self):
5010 return self._as_raw(self.task_args)
5010 return self._as_raw(self.task_args)
5011
5011
5012 @property
5012 @property
5013 def kwargs_raw(self):
5013 def kwargs_raw(self):
5014 return self._as_raw(self.task_kwargs)
5014 return self._as_raw(self.task_kwargs)
5015
5015
5016 def __repr__(self):
5016 def __repr__(self):
5017 return '<DB:ScheduleEntry({}:{})>'.format(
5017 return '<DB:ScheduleEntry({}:{})>'.format(
5018 self.schedule_entry_id, self.schedule_name)
5018 self.schedule_entry_id, self.schedule_name)
5019
5019
5020
5020
5021 @event.listens_for(ScheduleEntry, 'before_update')
5021 @event.listens_for(ScheduleEntry, 'before_update')
5022 def update_task_uid(mapper, connection, target):
5022 def update_task_uid(mapper, connection, target):
5023 target.task_uid = ScheduleEntry.get_uid(target)
5023 target.task_uid = ScheduleEntry.get_uid(target)
5024
5024
5025
5025
5026 @event.listens_for(ScheduleEntry, 'before_insert')
5026 @event.listens_for(ScheduleEntry, 'before_insert')
5027 def set_task_uid(mapper, connection, target):
5027 def set_task_uid(mapper, connection, target):
5028 target.task_uid = ScheduleEntry.get_uid(target)
5028 target.task_uid = ScheduleEntry.get_uid(target)
5029
5029
5030
5030
5031 class _BaseBranchPerms(BaseModel):
5031 class _BaseBranchPerms(BaseModel):
5032 @classmethod
5032 @classmethod
5033 def compute_hash(cls, value):
5033 def compute_hash(cls, value):
5034 return sha1_safe(value)
5034 return sha1_safe(value)
5035
5035
5036 @hybrid_property
5036 @hybrid_property
5037 def branch_pattern(self):
5037 def branch_pattern(self):
5038 return self._branch_pattern or '*'
5038 return self._branch_pattern or '*'
5039
5039
5040 @hybrid_property
5040 @hybrid_property
5041 def branch_hash(self):
5041 def branch_hash(self):
5042 return self._branch_hash
5042 return self._branch_hash
5043
5043
5044 def _validate_glob(self, value):
5044 def _validate_glob(self, value):
5045 re.compile('^' + glob2re(value) + '$')
5045 re.compile('^' + glob2re(value) + '$')
5046
5046
5047 @branch_pattern.setter
5047 @branch_pattern.setter
5048 def branch_pattern(self, value):
5048 def branch_pattern(self, value):
5049 self._validate_glob(value)
5049 self._validate_glob(value)
5050 self._branch_pattern = value or '*'
5050 self._branch_pattern = value or '*'
5051 # set the Hash when setting the branch pattern
5051 # set the Hash when setting the branch pattern
5052 self._branch_hash = self.compute_hash(self._branch_pattern)
5052 self._branch_hash = self.compute_hash(self._branch_pattern)
5053
5053
5054 def matches(self, branch):
5054 def matches(self, branch):
5055 """
5055 """
5056 Check if this the branch matches entry
5056 Check if this the branch matches entry
5057
5057
5058 :param branch: branch name for the commit
5058 :param branch: branch name for the commit
5059 """
5059 """
5060
5060
5061 branch = branch or ''
5061 branch = branch or ''
5062
5062
5063 branch_matches = True
5063 branch_matches = True
5064 if branch:
5064 if branch:
5065 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5065 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5066 branch_matches = bool(branch_regex.search(branch))
5066 branch_matches = bool(branch_regex.search(branch))
5067
5067
5068 return branch_matches
5068 return branch_matches
5069
5069
5070
5070
5071 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5071 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5072 __tablename__ = 'user_to_repo_branch_permissions'
5072 __tablename__ = 'user_to_repo_branch_permissions'
5073 __table_args__ = (
5073 __table_args__ = (
5074 base_table_args
5074 base_table_args
5075 )
5075 )
5076
5076
5077 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5077 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5078
5078
5079 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5079 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5080 repo = relationship('Repository', backref='user_branch_perms')
5080 repo = relationship('Repository', backref='user_branch_perms')
5081
5081
5082 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5082 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5083 permission = relationship('Permission')
5083 permission = relationship('Permission')
5084
5084
5085 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5085 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5086 user_repo_to_perm = relationship('UserRepoToPerm')
5086 user_repo_to_perm = relationship('UserRepoToPerm')
5087
5087
5088 rule_order = Column('rule_order', Integer(), nullable=False)
5088 rule_order = Column('rule_order', Integer(), nullable=False)
5089 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5089 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5090 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5090 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5091
5091
5092 def __unicode__(self):
5092 def __unicode__(self):
5093 return u'<UserBranchPermission(%s => %r)>' % (
5093 return u'<UserBranchPermission(%s => %r)>' % (
5094 self.user_repo_to_perm, self.branch_pattern)
5094 self.user_repo_to_perm, self.branch_pattern)
5095
5095
5096
5096
5097 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5097 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5098 __tablename__ = 'user_group_to_repo_branch_permissions'
5098 __tablename__ = 'user_group_to_repo_branch_permissions'
5099 __table_args__ = (
5099 __table_args__ = (
5100 base_table_args
5100 base_table_args
5101 )
5101 )
5102
5102
5103 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5103 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5104
5104
5105 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5105 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5106 repo = relationship('Repository', backref='user_group_branch_perms')
5106 repo = relationship('Repository', backref='user_group_branch_perms')
5107
5107
5108 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5108 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5109 permission = relationship('Permission')
5109 permission = relationship('Permission')
5110
5110
5111 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5111 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5112 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5112 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5113
5113
5114 rule_order = Column('rule_order', Integer(), nullable=False)
5114 rule_order = Column('rule_order', Integer(), nullable=False)
5115 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5115 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5116 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5116 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5117
5117
5118 def __unicode__(self):
5118 def __unicode__(self):
5119 return u'<UserBranchPermission(%s => %r)>' % (
5119 return u'<UserBranchPermission(%s => %r)>' % (
5120 self.user_group_repo_to_perm, self.branch_pattern)
5120 self.user_group_repo_to_perm, self.branch_pattern)
5121
5121
5122
5122
5123 class UserBookmark(Base, BaseModel):
5123 class UserBookmark(Base, BaseModel):
5124 __tablename__ = 'user_bookmarks'
5124 __tablename__ = 'user_bookmarks'
5125 __table_args__ = (
5125 __table_args__ = (
5126 UniqueConstraint('user_id', 'bookmark_repo_id'),
5126 UniqueConstraint('user_id', 'bookmark_repo_id'),
5127 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5127 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5128 UniqueConstraint('user_id', 'bookmark_position'),
5128 UniqueConstraint('user_id', 'bookmark_position'),
5129 base_table_args
5129 base_table_args
5130 )
5130 )
5131
5131
5132 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5132 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5133 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5133 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5134 position = Column("bookmark_position", Integer(), nullable=False)
5134 position = Column("bookmark_position", Integer(), nullable=False)
5135 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5135 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5136 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5136 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5137 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5137 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5138
5138
5139 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5139 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5140 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5140 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5141
5141
5142 user = relationship("User")
5142 user = relationship("User")
5143
5143
5144 repository = relationship("Repository")
5144 repository = relationship("Repository")
5145 repository_group = relationship("RepoGroup")
5145 repository_group = relationship("RepoGroup")
5146
5146
5147 @classmethod
5147 @classmethod
5148 def get_by_position_for_user(cls, position, user_id):
5148 def get_by_position_for_user(cls, position, user_id):
5149 return cls.query() \
5149 return cls.query() \
5150 .filter(UserBookmark.user_id == user_id) \
5150 .filter(UserBookmark.user_id == user_id) \
5151 .filter(UserBookmark.position == position).scalar()
5151 .filter(UserBookmark.position == position).scalar()
5152
5152
5153 @classmethod
5153 @classmethod
5154 def get_bookmarks_for_user(cls, user_id):
5154 def get_bookmarks_for_user(cls, user_id, cache=True):
5155 return cls.query() \
5155 bookmarks = cls.query() \
5156 .filter(UserBookmark.user_id == user_id) \
5156 .filter(UserBookmark.user_id == user_id) \
5157 .options(joinedload(UserBookmark.repository)) \
5157 .options(joinedload(UserBookmark.repository)) \
5158 .options(joinedload(UserBookmark.repository_group)) \
5158 .options(joinedload(UserBookmark.repository_group)) \
5159 .order_by(UserBookmark.position.asc()) \
5159 .order_by(UserBookmark.position.asc())
5160 .all()
5160
5161 if cache:
5162 bookmarks = bookmarks.options(
5163 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5164 )
5165
5166 return bookmarks.all()
5161
5167
5162 def __unicode__(self):
5168 def __unicode__(self):
5163 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5169 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5164
5170
5165
5171
5166 class FileStore(Base, BaseModel):
5172 class FileStore(Base, BaseModel):
5167 __tablename__ = 'file_store'
5173 __tablename__ = 'file_store'
5168 __table_args__ = (
5174 __table_args__ = (
5169 base_table_args
5175 base_table_args
5170 )
5176 )
5171
5177
5172 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5178 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5173 file_uid = Column('file_uid', String(1024), nullable=False)
5179 file_uid = Column('file_uid', String(1024), nullable=False)
5174 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5180 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5175 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5181 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5176 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5182 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5177
5183
5178 # sha256 hash
5184 # sha256 hash
5179 file_hash = Column('file_hash', String(512), nullable=False)
5185 file_hash = Column('file_hash', String(512), nullable=False)
5180 file_size = Column('file_size', BigInteger(), nullable=False)
5186 file_size = Column('file_size', BigInteger(), nullable=False)
5181
5187
5182 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5188 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5183 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5189 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5184 accessed_count = Column('accessed_count', Integer(), default=0)
5190 accessed_count = Column('accessed_count', Integer(), default=0)
5185
5191
5186 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5192 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5187
5193
5188 # if repo/repo_group reference is set, check for permissions
5194 # if repo/repo_group reference is set, check for permissions
5189 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5195 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5190
5196
5191 # hidden defines an attachment that should be hidden from showing in artifact listing
5197 # hidden defines an attachment that should be hidden from showing in artifact listing
5192 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5198 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5193
5199
5194 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5200 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5195 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5201 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5196
5202
5197 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5203 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5198
5204
5199 # scope limited to user, which requester have access to
5205 # scope limited to user, which requester have access to
5200 scope_user_id = Column(
5206 scope_user_id = Column(
5201 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5207 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5202 nullable=True, unique=None, default=None)
5208 nullable=True, unique=None, default=None)
5203 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5209 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5204
5210
5205 # scope limited to user group, which requester have access to
5211 # scope limited to user group, which requester have access to
5206 scope_user_group_id = Column(
5212 scope_user_group_id = Column(
5207 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5213 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5208 nullable=True, unique=None, default=None)
5214 nullable=True, unique=None, default=None)
5209 user_group = relationship('UserGroup', lazy='joined')
5215 user_group = relationship('UserGroup', lazy='joined')
5210
5216
5211 # scope limited to repo, which requester have access to
5217 # scope limited to repo, which requester have access to
5212 scope_repo_id = Column(
5218 scope_repo_id = Column(
5213 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5219 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5214 nullable=True, unique=None, default=None)
5220 nullable=True, unique=None, default=None)
5215 repo = relationship('Repository', lazy='joined')
5221 repo = relationship('Repository', lazy='joined')
5216
5222
5217 # scope limited to repo group, which requester have access to
5223 # scope limited to repo group, which requester have access to
5218 scope_repo_group_id = Column(
5224 scope_repo_group_id = Column(
5219 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5225 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5220 nullable=True, unique=None, default=None)
5226 nullable=True, unique=None, default=None)
5221 repo_group = relationship('RepoGroup', lazy='joined')
5227 repo_group = relationship('RepoGroup', lazy='joined')
5222
5228
5223 @classmethod
5229 @classmethod
5224 def get_by_store_uid(cls, file_store_uid):
5230 def get_by_store_uid(cls, file_store_uid):
5225 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5231 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5226
5232
5227 @classmethod
5233 @classmethod
5228 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5234 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5229 file_description='', enabled=True, hidden=False, check_acl=True,
5235 file_description='', enabled=True, hidden=False, check_acl=True,
5230 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5236 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5231
5237
5232 store_entry = FileStore()
5238 store_entry = FileStore()
5233 store_entry.file_uid = file_uid
5239 store_entry.file_uid = file_uid
5234 store_entry.file_display_name = file_display_name
5240 store_entry.file_display_name = file_display_name
5235 store_entry.file_org_name = filename
5241 store_entry.file_org_name = filename
5236 store_entry.file_size = file_size
5242 store_entry.file_size = file_size
5237 store_entry.file_hash = file_hash
5243 store_entry.file_hash = file_hash
5238 store_entry.file_description = file_description
5244 store_entry.file_description = file_description
5239
5245
5240 store_entry.check_acl = check_acl
5246 store_entry.check_acl = check_acl
5241 store_entry.enabled = enabled
5247 store_entry.enabled = enabled
5242 store_entry.hidden = hidden
5248 store_entry.hidden = hidden
5243
5249
5244 store_entry.user_id = user_id
5250 store_entry.user_id = user_id
5245 store_entry.scope_user_id = scope_user_id
5251 store_entry.scope_user_id = scope_user_id
5246 store_entry.scope_repo_id = scope_repo_id
5252 store_entry.scope_repo_id = scope_repo_id
5247 store_entry.scope_repo_group_id = scope_repo_group_id
5253 store_entry.scope_repo_group_id = scope_repo_group_id
5248
5254
5249 return store_entry
5255 return store_entry
5250
5256
5251 @classmethod
5257 @classmethod
5252 def store_metadata(cls, file_store_id, args, commit=True):
5258 def store_metadata(cls, file_store_id, args, commit=True):
5253 file_store = FileStore.get(file_store_id)
5259 file_store = FileStore.get(file_store_id)
5254 if file_store is None:
5260 if file_store is None:
5255 return
5261 return
5256
5262
5257 for section, key, value, value_type in args:
5263 for section, key, value, value_type in args:
5258 has_key = FileStoreMetadata().query() \
5264 has_key = FileStoreMetadata().query() \
5259 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5265 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5260 .filter(FileStoreMetadata.file_store_meta_section == section) \
5266 .filter(FileStoreMetadata.file_store_meta_section == section) \
5261 .filter(FileStoreMetadata.file_store_meta_key == key) \
5267 .filter(FileStoreMetadata.file_store_meta_key == key) \
5262 .scalar()
5268 .scalar()
5263 if has_key:
5269 if has_key:
5264 msg = 'key `{}` already defined under section `{}` for this file.'\
5270 msg = 'key `{}` already defined under section `{}` for this file.'\
5265 .format(key, section)
5271 .format(key, section)
5266 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5272 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5267
5273
5268 # NOTE(marcink): raises ArtifactMetadataBadValueType
5274 # NOTE(marcink): raises ArtifactMetadataBadValueType
5269 FileStoreMetadata.valid_value_type(value_type)
5275 FileStoreMetadata.valid_value_type(value_type)
5270
5276
5271 meta_entry = FileStoreMetadata()
5277 meta_entry = FileStoreMetadata()
5272 meta_entry.file_store = file_store
5278 meta_entry.file_store = file_store
5273 meta_entry.file_store_meta_section = section
5279 meta_entry.file_store_meta_section = section
5274 meta_entry.file_store_meta_key = key
5280 meta_entry.file_store_meta_key = key
5275 meta_entry.file_store_meta_value_type = value_type
5281 meta_entry.file_store_meta_value_type = value_type
5276 meta_entry.file_store_meta_value = value
5282 meta_entry.file_store_meta_value = value
5277
5283
5278 Session().add(meta_entry)
5284 Session().add(meta_entry)
5279
5285
5280 try:
5286 try:
5281 if commit:
5287 if commit:
5282 Session().commit()
5288 Session().commit()
5283 except IntegrityError:
5289 except IntegrityError:
5284 Session().rollback()
5290 Session().rollback()
5285 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5291 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5286
5292
5287 @classmethod
5293 @classmethod
5288 def bump_access_counter(cls, file_uid, commit=True):
5294 def bump_access_counter(cls, file_uid, commit=True):
5289 FileStore().query()\
5295 FileStore().query()\
5290 .filter(FileStore.file_uid == file_uid)\
5296 .filter(FileStore.file_uid == file_uid)\
5291 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5297 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5292 FileStore.accessed_on: datetime.datetime.now()})
5298 FileStore.accessed_on: datetime.datetime.now()})
5293 if commit:
5299 if commit:
5294 Session().commit()
5300 Session().commit()
5295
5301
5296 def __json__(self):
5302 def __json__(self):
5297 data = {
5303 data = {
5298 'filename': self.file_display_name,
5304 'filename': self.file_display_name,
5299 'filename_org': self.file_org_name,
5305 'filename_org': self.file_org_name,
5300 'file_uid': self.file_uid,
5306 'file_uid': self.file_uid,
5301 'description': self.file_description,
5307 'description': self.file_description,
5302 'hidden': self.hidden,
5308 'hidden': self.hidden,
5303 'size': self.file_size,
5309 'size': self.file_size,
5304 'created_on': self.created_on,
5310 'created_on': self.created_on,
5305 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5311 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5306 'downloaded_times': self.accessed_count,
5312 'downloaded_times': self.accessed_count,
5307 'sha256': self.file_hash,
5313 'sha256': self.file_hash,
5308 'metadata': self.file_metadata,
5314 'metadata': self.file_metadata,
5309 }
5315 }
5310
5316
5311 return data
5317 return data
5312
5318
5313 def __repr__(self):
5319 def __repr__(self):
5314 return '<FileStore({})>'.format(self.file_store_id)
5320 return '<FileStore({})>'.format(self.file_store_id)
5315
5321
5316
5322
5317 class FileStoreMetadata(Base, BaseModel):
5323 class FileStoreMetadata(Base, BaseModel):
5318 __tablename__ = 'file_store_metadata'
5324 __tablename__ = 'file_store_metadata'
5319 __table_args__ = (
5325 __table_args__ = (
5320 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5326 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5321 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5327 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5322 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5328 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5323 base_table_args
5329 base_table_args
5324 )
5330 )
5325 SETTINGS_TYPES = {
5331 SETTINGS_TYPES = {
5326 'str': safe_str,
5332 'str': safe_str,
5327 'int': safe_int,
5333 'int': safe_int,
5328 'unicode': safe_unicode,
5334 'unicode': safe_unicode,
5329 'bool': str2bool,
5335 'bool': str2bool,
5330 'list': functools.partial(aslist, sep=',')
5336 'list': functools.partial(aslist, sep=',')
5331 }
5337 }
5332
5338
5333 file_store_meta_id = Column(
5339 file_store_meta_id = Column(
5334 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5340 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5335 primary_key=True)
5341 primary_key=True)
5336 _file_store_meta_section = Column(
5342 _file_store_meta_section = Column(
5337 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5343 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5338 nullable=True, unique=None, default=None)
5344 nullable=True, unique=None, default=None)
5339 _file_store_meta_section_hash = Column(
5345 _file_store_meta_section_hash = Column(
5340 "file_store_meta_section_hash", String(255),
5346 "file_store_meta_section_hash", String(255),
5341 nullable=True, unique=None, default=None)
5347 nullable=True, unique=None, default=None)
5342 _file_store_meta_key = Column(
5348 _file_store_meta_key = Column(
5343 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5349 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5344 nullable=True, unique=None, default=None)
5350 nullable=True, unique=None, default=None)
5345 _file_store_meta_key_hash = Column(
5351 _file_store_meta_key_hash = Column(
5346 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5352 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5347 _file_store_meta_value = Column(
5353 _file_store_meta_value = Column(
5348 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5354 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5349 nullable=True, unique=None, default=None)
5355 nullable=True, unique=None, default=None)
5350 _file_store_meta_value_type = Column(
5356 _file_store_meta_value_type = Column(
5351 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5357 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5352 default='unicode')
5358 default='unicode')
5353
5359
5354 file_store_id = Column(
5360 file_store_id = Column(
5355 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5361 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5356 nullable=True, unique=None, default=None)
5362 nullable=True, unique=None, default=None)
5357
5363
5358 file_store = relationship('FileStore', lazy='joined')
5364 file_store = relationship('FileStore', lazy='joined')
5359
5365
5360 @classmethod
5366 @classmethod
5361 def valid_value_type(cls, value):
5367 def valid_value_type(cls, value):
5362 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5368 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5363 raise ArtifactMetadataBadValueType(
5369 raise ArtifactMetadataBadValueType(
5364 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5370 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5365
5371
5366 @hybrid_property
5372 @hybrid_property
5367 def file_store_meta_section(self):
5373 def file_store_meta_section(self):
5368 return self._file_store_meta_section
5374 return self._file_store_meta_section
5369
5375
5370 @file_store_meta_section.setter
5376 @file_store_meta_section.setter
5371 def file_store_meta_section(self, value):
5377 def file_store_meta_section(self, value):
5372 self._file_store_meta_section = value
5378 self._file_store_meta_section = value
5373 self._file_store_meta_section_hash = _hash_key(value)
5379 self._file_store_meta_section_hash = _hash_key(value)
5374
5380
5375 @hybrid_property
5381 @hybrid_property
5376 def file_store_meta_key(self):
5382 def file_store_meta_key(self):
5377 return self._file_store_meta_key
5383 return self._file_store_meta_key
5378
5384
5379 @file_store_meta_key.setter
5385 @file_store_meta_key.setter
5380 def file_store_meta_key(self, value):
5386 def file_store_meta_key(self, value):
5381 self._file_store_meta_key = value
5387 self._file_store_meta_key = value
5382 self._file_store_meta_key_hash = _hash_key(value)
5388 self._file_store_meta_key_hash = _hash_key(value)
5383
5389
5384 @hybrid_property
5390 @hybrid_property
5385 def file_store_meta_value(self):
5391 def file_store_meta_value(self):
5386 val = self._file_store_meta_value
5392 val = self._file_store_meta_value
5387
5393
5388 if self._file_store_meta_value_type:
5394 if self._file_store_meta_value_type:
5389 # e.g unicode.encrypted == unicode
5395 # e.g unicode.encrypted == unicode
5390 _type = self._file_store_meta_value_type.split('.')[0]
5396 _type = self._file_store_meta_value_type.split('.')[0]
5391 # decode the encrypted value if it's encrypted field type
5397 # decode the encrypted value if it's encrypted field type
5392 if '.encrypted' in self._file_store_meta_value_type:
5398 if '.encrypted' in self._file_store_meta_value_type:
5393 cipher = EncryptedTextValue()
5399 cipher = EncryptedTextValue()
5394 val = safe_unicode(cipher.process_result_value(val, None))
5400 val = safe_unicode(cipher.process_result_value(val, None))
5395 # do final type conversion
5401 # do final type conversion
5396 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5402 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5397 val = converter(val)
5403 val = converter(val)
5398
5404
5399 return val
5405 return val
5400
5406
5401 @file_store_meta_value.setter
5407 @file_store_meta_value.setter
5402 def file_store_meta_value(self, val):
5408 def file_store_meta_value(self, val):
5403 val = safe_unicode(val)
5409 val = safe_unicode(val)
5404 # encode the encrypted value
5410 # encode the encrypted value
5405 if '.encrypted' in self.file_store_meta_value_type:
5411 if '.encrypted' in self.file_store_meta_value_type:
5406 cipher = EncryptedTextValue()
5412 cipher = EncryptedTextValue()
5407 val = safe_unicode(cipher.process_bind_param(val, None))
5413 val = safe_unicode(cipher.process_bind_param(val, None))
5408 self._file_store_meta_value = val
5414 self._file_store_meta_value = val
5409
5415
5410 @hybrid_property
5416 @hybrid_property
5411 def file_store_meta_value_type(self):
5417 def file_store_meta_value_type(self):
5412 return self._file_store_meta_value_type
5418 return self._file_store_meta_value_type
5413
5419
5414 @file_store_meta_value_type.setter
5420 @file_store_meta_value_type.setter
5415 def file_store_meta_value_type(self, val):
5421 def file_store_meta_value_type(self, val):
5416 # e.g unicode.encrypted
5422 # e.g unicode.encrypted
5417 self.valid_value_type(val)
5423 self.valid_value_type(val)
5418 self._file_store_meta_value_type = val
5424 self._file_store_meta_value_type = val
5419
5425
5420 def __json__(self):
5426 def __json__(self):
5421 data = {
5427 data = {
5422 'artifact': self.file_store.file_uid,
5428 'artifact': self.file_store.file_uid,
5423 'section': self.file_store_meta_section,
5429 'section': self.file_store_meta_section,
5424 'key': self.file_store_meta_key,
5430 'key': self.file_store_meta_key,
5425 'value': self.file_store_meta_value,
5431 'value': self.file_store_meta_value,
5426 }
5432 }
5427
5433
5428 return data
5434 return data
5429
5435
5430 def __repr__(self):
5436 def __repr__(self):
5431 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5437 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5432 self.file_store_meta_key, self.file_store_meta_value)
5438 self.file_store_meta_key, self.file_store_meta_value)
5433
5439
5434
5440
5435 class DbMigrateVersion(Base, BaseModel):
5441 class DbMigrateVersion(Base, BaseModel):
5436 __tablename__ = 'db_migrate_version'
5442 __tablename__ = 'db_migrate_version'
5437 __table_args__ = (
5443 __table_args__ = (
5438 base_table_args,
5444 base_table_args,
5439 )
5445 )
5440
5446
5441 repository_id = Column('repository_id', String(250), primary_key=True)
5447 repository_id = Column('repository_id', String(250), primary_key=True)
5442 repository_path = Column('repository_path', Text)
5448 repository_path = Column('repository_path', Text)
5443 version = Column('version', Integer)
5449 version = Column('version', Integer)
5444
5450
5445 @classmethod
5451 @classmethod
5446 def set_version(cls, version):
5452 def set_version(cls, version):
5447 """
5453 """
5448 Helper for forcing a different version, usually for debugging purposes via ishell.
5454 Helper for forcing a different version, usually for debugging purposes via ishell.
5449 """
5455 """
5450 ver = DbMigrateVersion.query().first()
5456 ver = DbMigrateVersion.query().first()
5451 ver.version = version
5457 ver.version = version
5452 Session().commit()
5458 Session().commit()
5453
5459
5454
5460
5455 class DbSession(Base, BaseModel):
5461 class DbSession(Base, BaseModel):
5456 __tablename__ = 'db_session'
5462 __tablename__ = 'db_session'
5457 __table_args__ = (
5463 __table_args__ = (
5458 base_table_args,
5464 base_table_args,
5459 )
5465 )
5460
5466
5461 def __repr__(self):
5467 def __repr__(self):
5462 return '<DB:DbSession({})>'.format(self.id)
5468 return '<DB:DbSession({})>'.format(self.id)
5463
5469
5464 id = Column('id', Integer())
5470 id = Column('id', Integer())
5465 namespace = Column('namespace', String(255), primary_key=True)
5471 namespace = Column('namespace', String(255), primary_key=True)
5466 accessed = Column('accessed', DateTime, nullable=False)
5472 accessed = Column('accessed', DateTime, nullable=False)
5467 created = Column('created', DateTime, nullable=False)
5473 created = Column('created', DateTime, nullable=False)
5468 data = Column('data', PickleType, nullable=False)
5474 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now