##// END OF EJS Templates
users: add additional information why user with pending reviews shouldn't be deleted.
dan -
r1923:4a76cf6b default
parent child Browse files
Show More
@@ -1,493 +1,503 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Users crud controller for pylons
22 Users crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 from formencode import htmlfill
28 from formencode import htmlfill
29 from pylons import request, tmpl_context as c, url, config
29 from pylons import request, tmpl_context as c, url, config
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, AuthUser)
39 LoginRequired, HasPermissionAllDecorator, AuthUser)
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.exceptions import (
41 from rhodecode.lib.exceptions import (
42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
43 UserOwnsUserGroupsException, UserCreationError)
43 UserOwnsUserGroupsException, UserCreationError)
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45
45
46 from rhodecode.model.db import (
46 from rhodecode.model.db import (
47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
48 from rhodecode.model.forms import (
48 from rhodecode.model.forms import (
49 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
49 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.permission import PermissionModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UsersController(BaseController):
58 class UsersController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60
60
61 @LoginRequired()
61 @LoginRequired()
62 def __before__(self):
62 def __before__(self):
63 super(UsersController, self).__before__()
63 super(UsersController, self).__before__()
64 c.available_permissions = config['available_permissions']
64 c.available_permissions = config['available_permissions']
65 c.allowed_languages = [
65 c.allowed_languages = [
66 ('en', 'English (en)'),
66 ('en', 'English (en)'),
67 ('de', 'German (de)'),
67 ('de', 'German (de)'),
68 ('fr', 'French (fr)'),
68 ('fr', 'French (fr)'),
69 ('it', 'Italian (it)'),
69 ('it', 'Italian (it)'),
70 ('ja', 'Japanese (ja)'),
70 ('ja', 'Japanese (ja)'),
71 ('pl', 'Polish (pl)'),
71 ('pl', 'Polish (pl)'),
72 ('pt', 'Portuguese (pt)'),
72 ('pt', 'Portuguese (pt)'),
73 ('ru', 'Russian (ru)'),
73 ('ru', 'Russian (ru)'),
74 ('zh', 'Chinese (zh)'),
74 ('zh', 'Chinese (zh)'),
75 ]
75 ]
76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
77
77
78 def _get_personal_repo_group_template_vars(self):
78 def _get_personal_repo_group_template_vars(self):
79 DummyUser = AttributeDict({
79 DummyUser = AttributeDict({
80 'username': '${username}',
80 'username': '${username}',
81 'user_id': '${user_id}',
81 'user_id': '${user_id}',
82 })
82 })
83 c.default_create_repo_group = RepoGroupModel() \
83 c.default_create_repo_group = RepoGroupModel() \
84 .get_default_create_personal_repo_group()
84 .get_default_create_personal_repo_group()
85 c.personal_repo_group_name = RepoGroupModel() \
85 c.personal_repo_group_name = RepoGroupModel() \
86 .get_personal_group_name(DummyUser)
86 .get_personal_group_name(DummyUser)
87
87
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 @auth.CSRFRequired()
89 @auth.CSRFRequired()
90 def create(self):
90 def create(self):
91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
92 user_model = UserModel()
92 user_model = UserModel()
93 user_form = UserForm()()
93 user_form = UserForm()()
94 try:
94 try:
95 form_result = user_form.to_python(dict(request.POST))
95 form_result = user_form.to_python(dict(request.POST))
96 user = user_model.create(form_result)
96 user = user_model.create(form_result)
97 Session().flush()
97 Session().flush()
98 creation_data = user.get_api_data()
98 creation_data = user.get_api_data()
99 username = form_result['username']
99 username = form_result['username']
100
100
101 audit_logger.store_web(
101 audit_logger.store_web(
102 'user.create', action_data={'data': creation_data},
102 'user.create', action_data={'data': creation_data},
103 user=c.rhodecode_user)
103 user=c.rhodecode_user)
104
104
105 user_link = h.link_to(h.escape(username),
105 user_link = h.link_to(h.escape(username),
106 url('edit_user',
106 url('edit_user',
107 user_id=user.user_id))
107 user_id=user.user_id))
108 h.flash(h.literal(_('Created user %(user_link)s')
108 h.flash(h.literal(_('Created user %(user_link)s')
109 % {'user_link': user_link}), category='success')
109 % {'user_link': user_link}), category='success')
110 Session().commit()
110 Session().commit()
111 except formencode.Invalid as errors:
111 except formencode.Invalid as errors:
112 self._get_personal_repo_group_template_vars()
112 self._get_personal_repo_group_template_vars()
113 return htmlfill.render(
113 return htmlfill.render(
114 render('admin/users/user_add.mako'),
114 render('admin/users/user_add.mako'),
115 defaults=errors.value,
115 defaults=errors.value,
116 errors=errors.error_dict or {},
116 errors=errors.error_dict or {},
117 prefix_error=False,
117 prefix_error=False,
118 encoding="UTF-8",
118 encoding="UTF-8",
119 force_defaults=False)
119 force_defaults=False)
120 except UserCreationError as e:
120 except UserCreationError as e:
121 h.flash(e, 'error')
121 h.flash(e, 'error')
122 except Exception:
122 except Exception:
123 log.exception("Exception creation of user")
123 log.exception("Exception creation of user")
124 h.flash(_('Error occurred during creation of user %s')
124 h.flash(_('Error occurred during creation of user %s')
125 % request.POST.get('username'), category='error')
125 % request.POST.get('username'), category='error')
126 return redirect(h.route_path('users'))
126 return redirect(h.route_path('users'))
127
127
128 @HasPermissionAllDecorator('hg.admin')
128 @HasPermissionAllDecorator('hg.admin')
129 def new(self):
129 def new(self):
130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 self._get_personal_repo_group_template_vars()
131 self._get_personal_repo_group_template_vars()
132 return render('admin/users/user_add.mako')
132 return render('admin/users/user_add.mako')
133
133
134 @HasPermissionAllDecorator('hg.admin')
134 @HasPermissionAllDecorator('hg.admin')
135 @auth.CSRFRequired()
135 @auth.CSRFRequired()
136 def update(self, user_id):
136 def update(self, user_id):
137
137
138 user_id = safe_int(user_id)
138 user_id = safe_int(user_id)
139 c.user = User.get_or_404(user_id)
139 c.user = User.get_or_404(user_id)
140 c.active = 'profile'
140 c.active = 'profile'
141 c.extern_type = c.user.extern_type
141 c.extern_type = c.user.extern_type
142 c.extern_name = c.user.extern_name
142 c.extern_name = c.user.extern_name
143 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
143 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
144 available_languages = [x[0] for x in c.allowed_languages]
144 available_languages = [x[0] for x in c.allowed_languages]
145 _form = UserForm(edit=True, available_languages=available_languages,
145 _form = UserForm(edit=True, available_languages=available_languages,
146 old_data={'user_id': user_id,
146 old_data={'user_id': user_id,
147 'email': c.user.email})()
147 'email': c.user.email})()
148 form_result = {}
148 form_result = {}
149 old_values = c.user.get_api_data()
149 old_values = c.user.get_api_data()
150 try:
150 try:
151 form_result = _form.to_python(dict(request.POST))
151 form_result = _form.to_python(dict(request.POST))
152 skip_attrs = ['extern_type', 'extern_name']
152 skip_attrs = ['extern_type', 'extern_name']
153 # TODO: plugin should define if username can be updated
153 # TODO: plugin should define if username can be updated
154 if c.extern_type != "rhodecode":
154 if c.extern_type != "rhodecode":
155 # forbid updating username for external accounts
155 # forbid updating username for external accounts
156 skip_attrs.append('username')
156 skip_attrs.append('username')
157
157
158 UserModel().update_user(
158 UserModel().update_user(
159 user_id, skip_attrs=skip_attrs, **form_result)
159 user_id, skip_attrs=skip_attrs, **form_result)
160
160
161 audit_logger.store_web(
161 audit_logger.store_web(
162 'user.edit', action_data={'old_data': old_values},
162 'user.edit', action_data={'old_data': old_values},
163 user=c.rhodecode_user)
163 user=c.rhodecode_user)
164
164
165 Session().commit()
165 Session().commit()
166 h.flash(_('User updated successfully'), category='success')
166 h.flash(_('User updated successfully'), category='success')
167 except formencode.Invalid as errors:
167 except formencode.Invalid as errors:
168 defaults = errors.value
168 defaults = errors.value
169 e = errors.error_dict or {}
169 e = errors.error_dict or {}
170
170
171 return htmlfill.render(
171 return htmlfill.render(
172 render('admin/users/user_edit.mako'),
172 render('admin/users/user_edit.mako'),
173 defaults=defaults,
173 defaults=defaults,
174 errors=e,
174 errors=e,
175 prefix_error=False,
175 prefix_error=False,
176 encoding="UTF-8",
176 encoding="UTF-8",
177 force_defaults=False)
177 force_defaults=False)
178 except UserCreationError as e:
178 except UserCreationError as e:
179 h.flash(e, 'error')
179 h.flash(e, 'error')
180 except Exception:
180 except Exception:
181 log.exception("Exception updating user")
181 log.exception("Exception updating user")
182 h.flash(_('Error occurred during update of user %s')
182 h.flash(_('Error occurred during update of user %s')
183 % form_result.get('username'), category='error')
183 % form_result.get('username'), category='error')
184 return redirect(url('edit_user', user_id=user_id))
184 return redirect(url('edit_user', user_id=user_id))
185
185
186 @HasPermissionAllDecorator('hg.admin')
186 @HasPermissionAllDecorator('hg.admin')
187 @auth.CSRFRequired()
187 @auth.CSRFRequired()
188 def delete(self, user_id):
188 def delete(self, user_id):
189 user_id = safe_int(user_id)
189 user_id = safe_int(user_id)
190 c.user = User.get_or_404(user_id)
190 c.user = User.get_or_404(user_id)
191
191
192 _repos = c.user.repositories
192 _repos = c.user.repositories
193 _repo_groups = c.user.repository_groups
193 _repo_groups = c.user.repository_groups
194 _user_groups = c.user.user_groups
194 _user_groups = c.user.user_groups
195
195
196 handle_repos = None
196 handle_repos = None
197 handle_repo_groups = None
197 handle_repo_groups = None
198 handle_user_groups = None
198 handle_user_groups = None
199 # dummy call for flash of handle
199 # dummy call for flash of handle
200 set_handle_flash_repos = lambda: None
200 set_handle_flash_repos = lambda: None
201 set_handle_flash_repo_groups = lambda: None
201 set_handle_flash_repo_groups = lambda: None
202 set_handle_flash_user_groups = lambda: None
202 set_handle_flash_user_groups = lambda: None
203
203
204 if _repos and request.POST.get('user_repos'):
204 if _repos and request.POST.get('user_repos'):
205 do = request.POST['user_repos']
205 do = request.POST['user_repos']
206 if do == 'detach':
206 if do == 'detach':
207 handle_repos = 'detach'
207 handle_repos = 'detach'
208 set_handle_flash_repos = lambda: h.flash(
208 set_handle_flash_repos = lambda: h.flash(
209 _('Detached %s repositories') % len(_repos),
209 _('Detached %s repositories') % len(_repos),
210 category='success')
210 category='success')
211 elif do == 'delete':
211 elif do == 'delete':
212 handle_repos = 'delete'
212 handle_repos = 'delete'
213 set_handle_flash_repos = lambda: h.flash(
213 set_handle_flash_repos = lambda: h.flash(
214 _('Deleted %s repositories') % len(_repos),
214 _('Deleted %s repositories') % len(_repos),
215 category='success')
215 category='success')
216
216
217 if _repo_groups and request.POST.get('user_repo_groups'):
217 if _repo_groups and request.POST.get('user_repo_groups'):
218 do = request.POST['user_repo_groups']
218 do = request.POST['user_repo_groups']
219 if do == 'detach':
219 if do == 'detach':
220 handle_repo_groups = 'detach'
220 handle_repo_groups = 'detach'
221 set_handle_flash_repo_groups = lambda: h.flash(
221 set_handle_flash_repo_groups = lambda: h.flash(
222 _('Detached %s repository groups') % len(_repo_groups),
222 _('Detached %s repository groups') % len(_repo_groups),
223 category='success')
223 category='success')
224 elif do == 'delete':
224 elif do == 'delete':
225 handle_repo_groups = 'delete'
225 handle_repo_groups = 'delete'
226 set_handle_flash_repo_groups = lambda: h.flash(
226 set_handle_flash_repo_groups = lambda: h.flash(
227 _('Deleted %s repository groups') % len(_repo_groups),
227 _('Deleted %s repository groups') % len(_repo_groups),
228 category='success')
228 category='success')
229
229
230 if _user_groups and request.POST.get('user_user_groups'):
230 if _user_groups and request.POST.get('user_user_groups'):
231 do = request.POST['user_user_groups']
231 do = request.POST['user_user_groups']
232 if do == 'detach':
232 if do == 'detach':
233 handle_user_groups = 'detach'
233 handle_user_groups = 'detach'
234 set_handle_flash_user_groups = lambda: h.flash(
234 set_handle_flash_user_groups = lambda: h.flash(
235 _('Detached %s user groups') % len(_user_groups),
235 _('Detached %s user groups') % len(_user_groups),
236 category='success')
236 category='success')
237 elif do == 'delete':
237 elif do == 'delete':
238 handle_user_groups = 'delete'
238 handle_user_groups = 'delete'
239 set_handle_flash_user_groups = lambda: h.flash(
239 set_handle_flash_user_groups = lambda: h.flash(
240 _('Deleted %s user groups') % len(_user_groups),
240 _('Deleted %s user groups') % len(_user_groups),
241 category='success')
241 category='success')
242
242
243 old_values = c.user.get_api_data()
243 old_values = c.user.get_api_data()
244 try:
244 try:
245 UserModel().delete(c.user, handle_repos=handle_repos,
245 UserModel().delete(c.user, handle_repos=handle_repos,
246 handle_repo_groups=handle_repo_groups,
246 handle_repo_groups=handle_repo_groups,
247 handle_user_groups=handle_user_groups)
247 handle_user_groups=handle_user_groups)
248
248
249 audit_logger.store_web(
249 audit_logger.store_web(
250 'user.delete', action_data={'old_data': old_values},
250 'user.delete', action_data={'old_data': old_values},
251 user=c.rhodecode_user)
251 user=c.rhodecode_user)
252
252
253 Session().commit()
253 Session().commit()
254 set_handle_flash_repos()
254 set_handle_flash_repos()
255 set_handle_flash_repo_groups()
255 set_handle_flash_repo_groups()
256 set_handle_flash_user_groups()
256 set_handle_flash_user_groups()
257 h.flash(_('Successfully deleted user'), category='success')
257 h.flash(_('Successfully deleted user'), category='success')
258 except (UserOwnsReposException, UserOwnsRepoGroupsException,
258 except (UserOwnsReposException, UserOwnsRepoGroupsException,
259 UserOwnsUserGroupsException, DefaultUserException) as e:
259 UserOwnsUserGroupsException, DefaultUserException) as e:
260 h.flash(e, category='warning')
260 h.flash(e, category='warning')
261 except Exception:
261 except Exception:
262 log.exception("Exception during deletion of user")
262 log.exception("Exception during deletion of user")
263 h.flash(_('An error occurred during deletion of user'),
263 h.flash(_('An error occurred during deletion of user'),
264 category='error')
264 category='error')
265 return redirect(h.route_path('users'))
265 return redirect(h.route_path('users'))
266
266
267 @HasPermissionAllDecorator('hg.admin')
267 @HasPermissionAllDecorator('hg.admin')
268 @auth.CSRFRequired()
268 @auth.CSRFRequired()
269 def reset_password(self, user_id):
269 def reset_password(self, user_id):
270 """
270 """
271 toggle reset password flag for this user
271 toggle reset password flag for this user
272 """
272 """
273 user_id = safe_int(user_id)
273 user_id = safe_int(user_id)
274 c.user = User.get_or_404(user_id)
274 c.user = User.get_or_404(user_id)
275 try:
275 try:
276 old_value = c.user.user_data.get('force_password_change')
276 old_value = c.user.user_data.get('force_password_change')
277 c.user.update_userdata(force_password_change=not old_value)
277 c.user.update_userdata(force_password_change=not old_value)
278
278
279 if old_value:
279 if old_value:
280 msg = _('Force password change disabled for user')
280 msg = _('Force password change disabled for user')
281 audit_logger.store_web(
281 audit_logger.store_web(
282 'user.edit.password_reset.disabled',
282 'user.edit.password_reset.disabled',
283 user=c.rhodecode_user)
283 user=c.rhodecode_user)
284 else:
284 else:
285 msg = _('Force password change enabled for user')
285 msg = _('Force password change enabled for user')
286 audit_logger.store_web(
286 audit_logger.store_web(
287 'user.edit.password_reset.enabled',
287 'user.edit.password_reset.enabled',
288 user=c.rhodecode_user)
288 user=c.rhodecode_user)
289
289
290 Session().commit()
290 Session().commit()
291 h.flash(msg, category='success')
291 h.flash(msg, category='success')
292 except Exception:
292 except Exception:
293 log.exception("Exception during password reset for user")
293 log.exception("Exception during password reset for user")
294 h.flash(_('An error occurred during password reset for user'),
294 h.flash(_('An error occurred during password reset for user'),
295 category='error')
295 category='error')
296
296
297 return redirect(url('edit_user_advanced', user_id=user_id))
297 return redirect(url('edit_user_advanced', user_id=user_id))
298
298
299 @HasPermissionAllDecorator('hg.admin')
299 @HasPermissionAllDecorator('hg.admin')
300 @auth.CSRFRequired()
300 @auth.CSRFRequired()
301 def create_personal_repo_group(self, user_id):
301 def create_personal_repo_group(self, user_id):
302 """
302 """
303 Create personal repository group for this user
303 Create personal repository group for this user
304 """
304 """
305 from rhodecode.model.repo_group import RepoGroupModel
305 from rhodecode.model.repo_group import RepoGroupModel
306
306
307 user_id = safe_int(user_id)
307 user_id = safe_int(user_id)
308 c.user = User.get_or_404(user_id)
308 c.user = User.get_or_404(user_id)
309 personal_repo_group = RepoGroup.get_user_personal_repo_group(
309 personal_repo_group = RepoGroup.get_user_personal_repo_group(
310 c.user.user_id)
310 c.user.user_id)
311 if personal_repo_group:
311 if personal_repo_group:
312 return redirect(url('edit_user_advanced', user_id=user_id))
312 return redirect(url('edit_user_advanced', user_id=user_id))
313
313
314 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
314 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
315 c.user)
315 c.user)
316 named_personal_group = RepoGroup.get_by_group_name(
316 named_personal_group = RepoGroup.get_by_group_name(
317 personal_repo_group_name)
317 personal_repo_group_name)
318 try:
318 try:
319
319
320 if named_personal_group and named_personal_group.user_id == c.user.user_id:
320 if named_personal_group and named_personal_group.user_id == c.user.user_id:
321 # migrate the same named group, and mark it as personal
321 # migrate the same named group, and mark it as personal
322 named_personal_group.personal = True
322 named_personal_group.personal = True
323 Session().add(named_personal_group)
323 Session().add(named_personal_group)
324 Session().commit()
324 Session().commit()
325 msg = _('Linked repository group `%s` as personal' % (
325 msg = _('Linked repository group `%s` as personal' % (
326 personal_repo_group_name,))
326 personal_repo_group_name,))
327 h.flash(msg, category='success')
327 h.flash(msg, category='success')
328 elif not named_personal_group:
328 elif not named_personal_group:
329 RepoGroupModel().create_personal_repo_group(c.user)
329 RepoGroupModel().create_personal_repo_group(c.user)
330
330
331 msg = _('Created repository group `%s`' % (
331 msg = _('Created repository group `%s`' % (
332 personal_repo_group_name,))
332 personal_repo_group_name,))
333 h.flash(msg, category='success')
333 h.flash(msg, category='success')
334 else:
334 else:
335 msg = _('Repository group `%s` is already taken' % (
335 msg = _('Repository group `%s` is already taken' % (
336 personal_repo_group_name,))
336 personal_repo_group_name,))
337 h.flash(msg, category='warning')
337 h.flash(msg, category='warning')
338 except Exception:
338 except Exception:
339 log.exception("Exception during repository group creation")
339 log.exception("Exception during repository group creation")
340 msg = _(
340 msg = _(
341 'An error occurred during repository group creation for user')
341 'An error occurred during repository group creation for user')
342 h.flash(msg, category='error')
342 h.flash(msg, category='error')
343 Session().rollback()
343 Session().rollback()
344
344
345 return redirect(url('edit_user_advanced', user_id=user_id))
345 return redirect(url('edit_user_advanced', user_id=user_id))
346
346
347 @HasPermissionAllDecorator('hg.admin')
347 @HasPermissionAllDecorator('hg.admin')
348 def show(self, user_id):
348 def show(self, user_id):
349 """GET /users/user_id: Show a specific item"""
349 """GET /users/user_id: Show a specific item"""
350 # url('user', user_id=ID)
350 # url('user', user_id=ID)
351 User.get_or_404(-1)
351 User.get_or_404(-1)
352
352
353 @HasPermissionAllDecorator('hg.admin')
353 @HasPermissionAllDecorator('hg.admin')
354 def edit(self, user_id):
354 def edit(self, user_id):
355 """GET /users/user_id/edit: Form to edit an existing item"""
355 """GET /users/user_id/edit: Form to edit an existing item"""
356 # url('edit_user', user_id=ID)
356 # url('edit_user', user_id=ID)
357 user_id = safe_int(user_id)
357 user_id = safe_int(user_id)
358 c.user = User.get_or_404(user_id)
358 c.user = User.get_or_404(user_id)
359 if c.user.username == User.DEFAULT_USER:
359 if c.user.username == User.DEFAULT_USER:
360 h.flash(_("You can't edit this user"), category='warning')
360 h.flash(_("You can't edit this user"), category='warning')
361 return redirect(h.route_path('users'))
361 return redirect(h.route_path('users'))
362
362
363 c.active = 'profile'
363 c.active = 'profile'
364 c.extern_type = c.user.extern_type
364 c.extern_type = c.user.extern_type
365 c.extern_name = c.user.extern_name
365 c.extern_name = c.user.extern_name
366 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
366 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
367
367
368 defaults = c.user.get_dict()
368 defaults = c.user.get_dict()
369 defaults.update({'language': c.user.user_data.get('language')})
369 defaults.update({'language': c.user.user_data.get('language')})
370 return htmlfill.render(
370 return htmlfill.render(
371 render('admin/users/user_edit.mako'),
371 render('admin/users/user_edit.mako'),
372 defaults=defaults,
372 defaults=defaults,
373 encoding="UTF-8",
373 encoding="UTF-8",
374 force_defaults=False)
374 force_defaults=False)
375
375
376 @HasPermissionAllDecorator('hg.admin')
376 @HasPermissionAllDecorator('hg.admin')
377 def edit_advanced(self, user_id):
377 def edit_advanced(self, user_id):
378 user_id = safe_int(user_id)
378 user_id = safe_int(user_id)
379 user = c.user = User.get_or_404(user_id)
379 user = c.user = User.get_or_404(user_id)
380 if user.username == User.DEFAULT_USER:
380 if user.username == User.DEFAULT_USER:
381 h.flash(_("You can't edit this user"), category='warning')
381 h.flash(_("You can't edit this user"), category='warning')
382 return redirect(h.route_path('users'))
382 return redirect(h.route_path('users'))
383
383
384 c.active = 'advanced'
384 c.active = 'advanced'
385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
386 c.personal_repo_group_name = RepoGroupModel()\
386 c.personal_repo_group_name = RepoGroupModel()\
387 .get_personal_group_name(user)
387 .get_personal_group_name(user)
388 c.first_admin = User.get_first_super_admin()
388 c.first_admin = User.get_first_super_admin()
389 defaults = user.get_dict()
389 defaults = user.get_dict()
390
390
391 # Interim workaround if the user participated on any pull requests as a
391 # Interim workaround if the user participated on any pull requests as a
392 # reviewer.
392 # reviewer.
393 has_review = bool(PullRequestReviewers.query().filter(
393 has_review = len(user.reviewer_pull_requests)
394 PullRequestReviewers.user_id == user_id).first())
395 c.can_delete_user = not has_review
394 c.can_delete_user = not has_review
396 c.can_delete_user_message = _(
395 c.can_delete_user_message = ''
397 'The user participates as reviewer in pull requests and '
396 inactive_link = h.link_to(
398 'cannot be deleted. You can set the user to '
397 'inactive', h.url('edit_user', user_id=user_id, anchor='active'))
399 '"inactive" instead of deleting it.') if has_review else ''
398 if has_review == 1:
399 c.can_delete_user_message = h.literal(_(
400 'The user participates as reviewer in {} pull request and '
401 'cannot be deleted. \nYou can set the user to '
402 '"{}" instead of deleting it.').format(
403 has_review, inactive_link))
404 elif has_review:
405 c.can_delete_user_message = h.literal(_(
406 'The user participates as reviewer in {} pull requests and '
407 'cannot be deleted. \nYou can set the user to '
408 '"{}" instead of deleting it.').format(
409 has_review, inactive_link))
400
410
401 return htmlfill.render(
411 return htmlfill.render(
402 render('admin/users/user_edit.mako'),
412 render('admin/users/user_edit.mako'),
403 defaults=defaults,
413 defaults=defaults,
404 encoding="UTF-8",
414 encoding="UTF-8",
405 force_defaults=False)
415 force_defaults=False)
406
416
407 @HasPermissionAllDecorator('hg.admin')
417 @HasPermissionAllDecorator('hg.admin')
408 def edit_global_perms(self, user_id):
418 def edit_global_perms(self, user_id):
409 user_id = safe_int(user_id)
419 user_id = safe_int(user_id)
410 c.user = User.get_or_404(user_id)
420 c.user = User.get_or_404(user_id)
411 if c.user.username == User.DEFAULT_USER:
421 if c.user.username == User.DEFAULT_USER:
412 h.flash(_("You can't edit this user"), category='warning')
422 h.flash(_("You can't edit this user"), category='warning')
413 return redirect(h.route_path('users'))
423 return redirect(h.route_path('users'))
414
424
415 c.active = 'global_perms'
425 c.active = 'global_perms'
416
426
417 c.default_user = User.get_default_user()
427 c.default_user = User.get_default_user()
418 defaults = c.user.get_dict()
428 defaults = c.user.get_dict()
419 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
429 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
420 defaults.update(c.default_user.get_default_perms())
430 defaults.update(c.default_user.get_default_perms())
421 defaults.update(c.user.get_default_perms())
431 defaults.update(c.user.get_default_perms())
422
432
423 return htmlfill.render(
433 return htmlfill.render(
424 render('admin/users/user_edit.mako'),
434 render('admin/users/user_edit.mako'),
425 defaults=defaults,
435 defaults=defaults,
426 encoding="UTF-8",
436 encoding="UTF-8",
427 force_defaults=False)
437 force_defaults=False)
428
438
429 @HasPermissionAllDecorator('hg.admin')
439 @HasPermissionAllDecorator('hg.admin')
430 @auth.CSRFRequired()
440 @auth.CSRFRequired()
431 def update_global_perms(self, user_id):
441 def update_global_perms(self, user_id):
432 user_id = safe_int(user_id)
442 user_id = safe_int(user_id)
433 user = User.get_or_404(user_id)
443 user = User.get_or_404(user_id)
434 c.active = 'global_perms'
444 c.active = 'global_perms'
435 try:
445 try:
436 # first stage that verifies the checkbox
446 # first stage that verifies the checkbox
437 _form = UserIndividualPermissionsForm()
447 _form = UserIndividualPermissionsForm()
438 form_result = _form.to_python(dict(request.POST))
448 form_result = _form.to_python(dict(request.POST))
439 inherit_perms = form_result['inherit_default_permissions']
449 inherit_perms = form_result['inherit_default_permissions']
440 user.inherit_default_permissions = inherit_perms
450 user.inherit_default_permissions = inherit_perms
441 Session().add(user)
451 Session().add(user)
442
452
443 if not inherit_perms:
453 if not inherit_perms:
444 # only update the individual ones if we un check the flag
454 # only update the individual ones if we un check the flag
445 _form = UserPermissionsForm(
455 _form = UserPermissionsForm(
446 [x[0] for x in c.repo_create_choices],
456 [x[0] for x in c.repo_create_choices],
447 [x[0] for x in c.repo_create_on_write_choices],
457 [x[0] for x in c.repo_create_on_write_choices],
448 [x[0] for x in c.repo_group_create_choices],
458 [x[0] for x in c.repo_group_create_choices],
449 [x[0] for x in c.user_group_create_choices],
459 [x[0] for x in c.user_group_create_choices],
450 [x[0] for x in c.fork_choices],
460 [x[0] for x in c.fork_choices],
451 [x[0] for x in c.inherit_default_permission_choices])()
461 [x[0] for x in c.inherit_default_permission_choices])()
452
462
453 form_result = _form.to_python(dict(request.POST))
463 form_result = _form.to_python(dict(request.POST))
454 form_result.update({'perm_user_id': user.user_id})
464 form_result.update({'perm_user_id': user.user_id})
455
465
456 PermissionModel().update_user_permissions(form_result)
466 PermissionModel().update_user_permissions(form_result)
457
467
458 # TODO(marcink): implement global permissions
468 # TODO(marcink): implement global permissions
459 # audit_log.store_web('user.edit.permissions')
469 # audit_log.store_web('user.edit.permissions')
460
470
461 Session().commit()
471 Session().commit()
462 h.flash(_('User global permissions updated successfully'),
472 h.flash(_('User global permissions updated successfully'),
463 category='success')
473 category='success')
464
474
465 except formencode.Invalid as errors:
475 except formencode.Invalid as errors:
466 defaults = errors.value
476 defaults = errors.value
467 c.user = user
477 c.user = user
468 return htmlfill.render(
478 return htmlfill.render(
469 render('admin/users/user_edit.mako'),
479 render('admin/users/user_edit.mako'),
470 defaults=defaults,
480 defaults=defaults,
471 errors=errors.error_dict or {},
481 errors=errors.error_dict or {},
472 prefix_error=False,
482 prefix_error=False,
473 encoding="UTF-8",
483 encoding="UTF-8",
474 force_defaults=False)
484 force_defaults=False)
475 except Exception:
485 except Exception:
476 log.exception("Exception during permissions saving")
486 log.exception("Exception during permissions saving")
477 h.flash(_('An error occurred during permissions saving'),
487 h.flash(_('An error occurred during permissions saving'),
478 category='error')
488 category='error')
479 return redirect(url('edit_user_global_perms', user_id=user_id))
489 return redirect(url('edit_user_global_perms', user_id=user_id))
480
490
481 @HasPermissionAllDecorator('hg.admin')
491 @HasPermissionAllDecorator('hg.admin')
482 def edit_perms_summary(self, user_id):
492 def edit_perms_summary(self, user_id):
483 user_id = safe_int(user_id)
493 user_id = safe_int(user_id)
484 c.user = User.get_or_404(user_id)
494 c.user = User.get_or_404(user_id)
485 if c.user.username == User.DEFAULT_USER:
495 if c.user.username == User.DEFAULT_USER:
486 h.flash(_("You can't edit this user"), category='warning')
496 h.flash(_("You can't edit this user"), category='warning')
487 return redirect(h.route_path('users'))
497 return redirect(h.route_path('users'))
488
498
489 c.active = 'perms_summary'
499 c.active = 'perms_summary'
490 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
500 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
491
501
492 return render('admin/users/user_edit.mako')
502 return render('admin/users/user_edit.mako')
493
503
@@ -1,4112 +1,4119 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pyramid.threadlocal import get_current_request
47 from pyramid.threadlocal import get_current_request
48
48
49 from rhodecode.translation import _
49 from rhodecode.translation import _
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
362
362
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
364 # git part is currently hardcoded.
364 # git part is currently hardcoded.
365
365
366 # SVN PATTERNS
366 # SVN PATTERNS
367 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_BRANCH_ID = 'vcs_svn_branch'
368 SVN_TAG_ID = 'vcs_svn_tag'
368 SVN_TAG_ID = 'vcs_svn_tag'
369
369
370 ui_id = Column(
370 ui_id = Column(
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
372 primary_key=True)
372 primary_key=True)
373 ui_section = Column(
373 ui_section = Column(
374 "ui_section", String(255), nullable=True, unique=None, default=None)
374 "ui_section", String(255), nullable=True, unique=None, default=None)
375 ui_key = Column(
375 ui_key = Column(
376 "ui_key", String(255), nullable=True, unique=None, default=None)
376 "ui_key", String(255), nullable=True, unique=None, default=None)
377 ui_value = Column(
377 ui_value = Column(
378 "ui_value", String(255), nullable=True, unique=None, default=None)
378 "ui_value", String(255), nullable=True, unique=None, default=None)
379 ui_active = Column(
379 ui_active = Column(
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
384 self.ui_key, self.ui_value)
384 self.ui_key, self.ui_value)
385
385
386
386
387 class RepoRhodeCodeSetting(Base, BaseModel):
387 class RepoRhodeCodeSetting(Base, BaseModel):
388 __tablename__ = 'repo_rhodecode_settings'
388 __tablename__ = 'repo_rhodecode_settings'
389 __table_args__ = (
389 __table_args__ = (
390 UniqueConstraint(
390 UniqueConstraint(
391 'app_settings_name', 'repository_id',
391 'app_settings_name', 'repository_id',
392 name='uq_repo_rhodecode_setting_name_repo_id'),
392 name='uq_repo_rhodecode_setting_name_repo_id'),
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
395 )
395 )
396
396
397 repository_id = Column(
397 repository_id = Column(
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
399 nullable=False)
399 nullable=False)
400 app_settings_id = Column(
400 app_settings_id = Column(
401 "app_settings_id", Integer(), nullable=False, unique=True,
401 "app_settings_id", Integer(), nullable=False, unique=True,
402 default=None, primary_key=True)
402 default=None, primary_key=True)
403 app_settings_name = Column(
403 app_settings_name = Column(
404 "app_settings_name", String(255), nullable=True, unique=None,
404 "app_settings_name", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406 _app_settings_value = Column(
406 _app_settings_value = Column(
407 "app_settings_value", String(4096), nullable=True, unique=None,
407 "app_settings_value", String(4096), nullable=True, unique=None,
408 default=None)
408 default=None)
409 _app_settings_type = Column(
409 _app_settings_type = Column(
410 "app_settings_type", String(255), nullable=True, unique=None,
410 "app_settings_type", String(255), nullable=True, unique=None,
411 default=None)
411 default=None)
412
412
413 repository = relationship('Repository')
413 repository = relationship('Repository')
414
414
415 def __init__(self, repository_id, key='', val='', type='unicode'):
415 def __init__(self, repository_id, key='', val='', type='unicode'):
416 self.repository_id = repository_id
416 self.repository_id = repository_id
417 self.app_settings_name = key
417 self.app_settings_name = key
418 self.app_settings_type = type
418 self.app_settings_type = type
419 self.app_settings_value = val
419 self.app_settings_value = val
420
420
421 @validates('_app_settings_value')
421 @validates('_app_settings_value')
422 def validate_settings_value(self, key, val):
422 def validate_settings_value(self, key, val):
423 assert type(val) == unicode
423 assert type(val) == unicode
424 return val
424 return val
425
425
426 @hybrid_property
426 @hybrid_property
427 def app_settings_value(self):
427 def app_settings_value(self):
428 v = self._app_settings_value
428 v = self._app_settings_value
429 type_ = self.app_settings_type
429 type_ = self.app_settings_type
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
432 return converter(v)
432 return converter(v)
433
433
434 @app_settings_value.setter
434 @app_settings_value.setter
435 def app_settings_value(self, val):
435 def app_settings_value(self, val):
436 """
436 """
437 Setter that will always make sure we use unicode in app_settings_value
437 Setter that will always make sure we use unicode in app_settings_value
438
438
439 :param val:
439 :param val:
440 """
440 """
441 self._app_settings_value = safe_unicode(val)
441 self._app_settings_value = safe_unicode(val)
442
442
443 @hybrid_property
443 @hybrid_property
444 def app_settings_type(self):
444 def app_settings_type(self):
445 return self._app_settings_type
445 return self._app_settings_type
446
446
447 @app_settings_type.setter
447 @app_settings_type.setter
448 def app_settings_type(self, val):
448 def app_settings_type(self, val):
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
450 if val not in SETTINGS_TYPES:
450 if val not in SETTINGS_TYPES:
451 raise Exception('type must be one of %s got %s'
451 raise Exception('type must be one of %s got %s'
452 % (SETTINGS_TYPES.keys(), val))
452 % (SETTINGS_TYPES.keys(), val))
453 self._app_settings_type = val
453 self._app_settings_type = val
454
454
455 def __unicode__(self):
455 def __unicode__(self):
456 return u"<%s('%s:%s:%s[%s]')>" % (
456 return u"<%s('%s:%s:%s[%s]')>" % (
457 self.__class__.__name__, self.repository.repo_name,
457 self.__class__.__name__, self.repository.repo_name,
458 self.app_settings_name, self.app_settings_value,
458 self.app_settings_name, self.app_settings_value,
459 self.app_settings_type
459 self.app_settings_type
460 )
460 )
461
461
462
462
463 class RepoRhodeCodeUi(Base, BaseModel):
463 class RepoRhodeCodeUi(Base, BaseModel):
464 __tablename__ = 'repo_rhodecode_ui'
464 __tablename__ = 'repo_rhodecode_ui'
465 __table_args__ = (
465 __table_args__ = (
466 UniqueConstraint(
466 UniqueConstraint(
467 'repository_id', 'ui_section', 'ui_key',
467 'repository_id', 'ui_section', 'ui_key',
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
471 )
471 )
472
472
473 repository_id = Column(
473 repository_id = Column(
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
475 nullable=False)
475 nullable=False)
476 ui_id = Column(
476 ui_id = Column(
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
478 primary_key=True)
478 primary_key=True)
479 ui_section = Column(
479 ui_section = Column(
480 "ui_section", String(255), nullable=True, unique=None, default=None)
480 "ui_section", String(255), nullable=True, unique=None, default=None)
481 ui_key = Column(
481 ui_key = Column(
482 "ui_key", String(255), nullable=True, unique=None, default=None)
482 "ui_key", String(255), nullable=True, unique=None, default=None)
483 ui_value = Column(
483 ui_value = Column(
484 "ui_value", String(255), nullable=True, unique=None, default=None)
484 "ui_value", String(255), nullable=True, unique=None, default=None)
485 ui_active = Column(
485 ui_active = Column(
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
487
487
488 repository = relationship('Repository')
488 repository = relationship('Repository')
489
489
490 def __repr__(self):
490 def __repr__(self):
491 return '<%s[%s:%s]%s=>%s]>' % (
491 return '<%s[%s:%s]%s=>%s]>' % (
492 self.__class__.__name__, self.repository.repo_name,
492 self.__class__.__name__, self.repository.repo_name,
493 self.ui_section, self.ui_key, self.ui_value)
493 self.ui_section, self.ui_key, self.ui_value)
494
494
495
495
496 class User(Base, BaseModel):
496 class User(Base, BaseModel):
497 __tablename__ = 'users'
497 __tablename__ = 'users'
498 __table_args__ = (
498 __table_args__ = (
499 UniqueConstraint('username'), UniqueConstraint('email'),
499 UniqueConstraint('username'), UniqueConstraint('email'),
500 Index('u_username_idx', 'username'),
500 Index('u_username_idx', 'username'),
501 Index('u_email_idx', 'email'),
501 Index('u_email_idx', 'email'),
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
504 )
504 )
505 DEFAULT_USER = 'default'
505 DEFAULT_USER = 'default'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
508
508
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
519
519
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
526
526
527 user_log = relationship('UserLog')
527 user_log = relationship('UserLog')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
529
529
530 repositories = relationship('Repository')
530 repositories = relationship('Repository')
531 repository_groups = relationship('RepoGroup')
531 repository_groups = relationship('RepoGroup')
532 user_groups = relationship('UserGroup')
532 user_groups = relationship('UserGroup')
533
533
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
536
536
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
540
540
541 group_member = relationship('UserGroupMember', cascade='all')
541 group_member = relationship('UserGroupMember', cascade='all')
542
542
543 notifications = relationship('UserNotification', cascade='all')
543 notifications = relationship('UserNotification', cascade='all')
544 # notifications assigned to this user
544 # notifications assigned to this user
545 user_created_notifications = relationship('Notification', cascade='all')
545 user_created_notifications = relationship('Notification', cascade='all')
546 # comments created by this user
546 # comments created by this user
547 user_comments = relationship('ChangesetComment', cascade='all')
547 user_comments = relationship('ChangesetComment', cascade='all')
548 # user profile extra info
548 # user profile extra info
549 user_emails = relationship('UserEmailMap', cascade='all')
549 user_emails = relationship('UserEmailMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
552 # gists
552 # gists
553 user_gists = relationship('Gist', cascade='all')
553 user_gists = relationship('Gist', cascade='all')
554 # user pull requests
554 # user pull requests
555 user_pull_requests = relationship('PullRequest', cascade='all')
555 user_pull_requests = relationship('PullRequest', cascade='all')
556 # external identities
556 # external identities
557 extenal_identities = relationship(
557 extenal_identities = relationship(
558 'ExternalIdentity',
558 'ExternalIdentity',
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
560 cascade='all')
560 cascade='all')
561
561
562 def __unicode__(self):
562 def __unicode__(self):
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
564 self.user_id, self.username)
564 self.user_id, self.username)
565
565
566 @hybrid_property
566 @hybrid_property
567 def email(self):
567 def email(self):
568 return self._email
568 return self._email
569
569
570 @email.setter
570 @email.setter
571 def email(self, val):
571 def email(self, val):
572 self._email = val.lower() if val else None
572 self._email = val.lower() if val else None
573
573
574 @hybrid_property
574 @hybrid_property
575 def first_name(self):
575 def first_name(self):
576 from rhodecode.lib import helpers as h
576 from rhodecode.lib import helpers as h
577 if self.name:
577 if self.name:
578 return h.escape(self.name)
578 return h.escape(self.name)
579 return self.name
579 return self.name
580
580
581 @hybrid_property
581 @hybrid_property
582 def last_name(self):
582 def last_name(self):
583 from rhodecode.lib import helpers as h
583 from rhodecode.lib import helpers as h
584 if self.lastname:
584 if self.lastname:
585 return h.escape(self.lastname)
585 return h.escape(self.lastname)
586 return self.lastname
586 return self.lastname
587
587
588 @hybrid_property
588 @hybrid_property
589 def api_key(self):
589 def api_key(self):
590 """
590 """
591 Fetch if exist an auth-token with role ALL connected to this user
591 Fetch if exist an auth-token with role ALL connected to this user
592 """
592 """
593 user_auth_token = UserApiKeys.query()\
593 user_auth_token = UserApiKeys.query()\
594 .filter(UserApiKeys.user_id == self.user_id)\
594 .filter(UserApiKeys.user_id == self.user_id)\
595 .filter(or_(UserApiKeys.expires == -1,
595 .filter(or_(UserApiKeys.expires == -1,
596 UserApiKeys.expires >= time.time()))\
596 UserApiKeys.expires >= time.time()))\
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
598 if user_auth_token:
598 if user_auth_token:
599 user_auth_token = user_auth_token.api_key
599 user_auth_token = user_auth_token.api_key
600
600
601 return user_auth_token
601 return user_auth_token
602
602
603 @api_key.setter
603 @api_key.setter
604 def api_key(self, val):
604 def api_key(self, val):
605 # don't allow to set API key this is deprecated for now
605 # don't allow to set API key this is deprecated for now
606 self._api_key = None
606 self._api_key = None
607
607
608 @property
608 @property
609 def reviewer_pull_requests(self):
610 return PullRequestReviewers.query() \
611 .options(joinedload(PullRequestReviewers.pull_request)) \
612 .filter(PullRequestReviewers.user_id == self.user_id) \
613 .all()
614
615 @property
609 def firstname(self):
616 def firstname(self):
610 # alias for future
617 # alias for future
611 return self.name
618 return self.name
612
619
613 @property
620 @property
614 def emails(self):
621 def emails(self):
615 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
622 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
616 return [self.email] + [x.email for x in other]
623 return [self.email] + [x.email for x in other]
617
624
618 @property
625 @property
619 def auth_tokens(self):
626 def auth_tokens(self):
620 return [x.api_key for x in self.extra_auth_tokens]
627 return [x.api_key for x in self.extra_auth_tokens]
621
628
622 @property
629 @property
623 def extra_auth_tokens(self):
630 def extra_auth_tokens(self):
624 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
631 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
625
632
626 @property
633 @property
627 def feed_token(self):
634 def feed_token(self):
628 return self.get_feed_token()
635 return self.get_feed_token()
629
636
630 def get_feed_token(self):
637 def get_feed_token(self):
631 feed_tokens = UserApiKeys.query()\
638 feed_tokens = UserApiKeys.query()\
632 .filter(UserApiKeys.user == self)\
639 .filter(UserApiKeys.user == self)\
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
640 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
634 .all()
641 .all()
635 if feed_tokens:
642 if feed_tokens:
636 return feed_tokens[0].api_key
643 return feed_tokens[0].api_key
637 return 'NO_FEED_TOKEN_AVAILABLE'
644 return 'NO_FEED_TOKEN_AVAILABLE'
638
645
639 @classmethod
646 @classmethod
640 def extra_valid_auth_tokens(cls, user, role=None):
647 def extra_valid_auth_tokens(cls, user, role=None):
641 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
648 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
642 .filter(or_(UserApiKeys.expires == -1,
649 .filter(or_(UserApiKeys.expires == -1,
643 UserApiKeys.expires >= time.time()))
650 UserApiKeys.expires >= time.time()))
644 if role:
651 if role:
645 tokens = tokens.filter(or_(UserApiKeys.role == role,
652 tokens = tokens.filter(or_(UserApiKeys.role == role,
646 UserApiKeys.role == UserApiKeys.ROLE_ALL))
653 UserApiKeys.role == UserApiKeys.ROLE_ALL))
647 return tokens.all()
654 return tokens.all()
648
655
649 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
656 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
650 from rhodecode.lib import auth
657 from rhodecode.lib import auth
651
658
652 log.debug('Trying to authenticate user: %s via auth-token, '
659 log.debug('Trying to authenticate user: %s via auth-token, '
653 'and roles: %s', self, roles)
660 'and roles: %s', self, roles)
654
661
655 if not auth_token:
662 if not auth_token:
656 return False
663 return False
657
664
658 crypto_backend = auth.crypto_backend()
665 crypto_backend = auth.crypto_backend()
659
666
660 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
667 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
661 tokens_q = UserApiKeys.query()\
668 tokens_q = UserApiKeys.query()\
662 .filter(UserApiKeys.user_id == self.user_id)\
669 .filter(UserApiKeys.user_id == self.user_id)\
663 .filter(or_(UserApiKeys.expires == -1,
670 .filter(or_(UserApiKeys.expires == -1,
664 UserApiKeys.expires >= time.time()))
671 UserApiKeys.expires >= time.time()))
665
672
666 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
673 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
667
674
668 plain_tokens = []
675 plain_tokens = []
669 hash_tokens = []
676 hash_tokens = []
670
677
671 for token in tokens_q.all():
678 for token in tokens_q.all():
672 # verify scope first
679 # verify scope first
673 if token.repo_id:
680 if token.repo_id:
674 # token has a scope, we need to verify it
681 # token has a scope, we need to verify it
675 if scope_repo_id != token.repo_id:
682 if scope_repo_id != token.repo_id:
676 log.debug(
683 log.debug(
677 'Scope mismatch: token has a set repo scope: %s, '
684 'Scope mismatch: token has a set repo scope: %s, '
678 'and calling scope is:%s, skipping further checks',
685 'and calling scope is:%s, skipping further checks',
679 token.repo, scope_repo_id)
686 token.repo, scope_repo_id)
680 # token has a scope, and it doesn't match, skip token
687 # token has a scope, and it doesn't match, skip token
681 continue
688 continue
682
689
683 if token.api_key.startswith(crypto_backend.ENC_PREF):
690 if token.api_key.startswith(crypto_backend.ENC_PREF):
684 hash_tokens.append(token.api_key)
691 hash_tokens.append(token.api_key)
685 else:
692 else:
686 plain_tokens.append(token.api_key)
693 plain_tokens.append(token.api_key)
687
694
688 is_plain_match = auth_token in plain_tokens
695 is_plain_match = auth_token in plain_tokens
689 if is_plain_match:
696 if is_plain_match:
690 return True
697 return True
691
698
692 for hashed in hash_tokens:
699 for hashed in hash_tokens:
693 # TODO(marcink): this is expensive to calculate, but most secure
700 # TODO(marcink): this is expensive to calculate, but most secure
694 match = crypto_backend.hash_check(auth_token, hashed)
701 match = crypto_backend.hash_check(auth_token, hashed)
695 if match:
702 if match:
696 return True
703 return True
697
704
698 return False
705 return False
699
706
700 @property
707 @property
701 def ip_addresses(self):
708 def ip_addresses(self):
702 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
709 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
703 return [x.ip_addr for x in ret]
710 return [x.ip_addr for x in ret]
704
711
705 @property
712 @property
706 def username_and_name(self):
713 def username_and_name(self):
707 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
714 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
708
715
709 @property
716 @property
710 def username_or_name_or_email(self):
717 def username_or_name_or_email(self):
711 full_name = self.full_name if self.full_name is not ' ' else None
718 full_name = self.full_name if self.full_name is not ' ' else None
712 return self.username or full_name or self.email
719 return self.username or full_name or self.email
713
720
714 @property
721 @property
715 def full_name(self):
722 def full_name(self):
716 return '%s %s' % (self.first_name, self.last_name)
723 return '%s %s' % (self.first_name, self.last_name)
717
724
718 @property
725 @property
719 def full_name_or_username(self):
726 def full_name_or_username(self):
720 return ('%s %s' % (self.first_name, self.last_name)
727 return ('%s %s' % (self.first_name, self.last_name)
721 if (self.first_name and self.last_name) else self.username)
728 if (self.first_name and self.last_name) else self.username)
722
729
723 @property
730 @property
724 def full_contact(self):
731 def full_contact(self):
725 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
732 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
726
733
727 @property
734 @property
728 def short_contact(self):
735 def short_contact(self):
729 return '%s %s' % (self.first_name, self.last_name)
736 return '%s %s' % (self.first_name, self.last_name)
730
737
731 @property
738 @property
732 def is_admin(self):
739 def is_admin(self):
733 return self.admin
740 return self.admin
734
741
735 @property
742 @property
736 def AuthUser(self):
743 def AuthUser(self):
737 """
744 """
738 Returns instance of AuthUser for this user
745 Returns instance of AuthUser for this user
739 """
746 """
740 from rhodecode.lib.auth import AuthUser
747 from rhodecode.lib.auth import AuthUser
741 return AuthUser(user_id=self.user_id, username=self.username)
748 return AuthUser(user_id=self.user_id, username=self.username)
742
749
743 @hybrid_property
750 @hybrid_property
744 def user_data(self):
751 def user_data(self):
745 if not self._user_data:
752 if not self._user_data:
746 return {}
753 return {}
747
754
748 try:
755 try:
749 return json.loads(self._user_data)
756 return json.loads(self._user_data)
750 except TypeError:
757 except TypeError:
751 return {}
758 return {}
752
759
753 @user_data.setter
760 @user_data.setter
754 def user_data(self, val):
761 def user_data(self, val):
755 if not isinstance(val, dict):
762 if not isinstance(val, dict):
756 raise Exception('user_data must be dict, got %s' % type(val))
763 raise Exception('user_data must be dict, got %s' % type(val))
757 try:
764 try:
758 self._user_data = json.dumps(val)
765 self._user_data = json.dumps(val)
759 except Exception:
766 except Exception:
760 log.error(traceback.format_exc())
767 log.error(traceback.format_exc())
761
768
762 @classmethod
769 @classmethod
763 def get_by_username(cls, username, case_insensitive=False,
770 def get_by_username(cls, username, case_insensitive=False,
764 cache=False, identity_cache=False):
771 cache=False, identity_cache=False):
765 session = Session()
772 session = Session()
766
773
767 if case_insensitive:
774 if case_insensitive:
768 q = cls.query().filter(
775 q = cls.query().filter(
769 func.lower(cls.username) == func.lower(username))
776 func.lower(cls.username) == func.lower(username))
770 else:
777 else:
771 q = cls.query().filter(cls.username == username)
778 q = cls.query().filter(cls.username == username)
772
779
773 if cache:
780 if cache:
774 if identity_cache:
781 if identity_cache:
775 val = cls.identity_cache(session, 'username', username)
782 val = cls.identity_cache(session, 'username', username)
776 if val:
783 if val:
777 return val
784 return val
778 else:
785 else:
779 cache_key = "get_user_by_name_%s" % _hash_key(username)
786 cache_key = "get_user_by_name_%s" % _hash_key(username)
780 q = q.options(
787 q = q.options(
781 FromCache("sql_cache_short", cache_key))
788 FromCache("sql_cache_short", cache_key))
782
789
783 return q.scalar()
790 return q.scalar()
784
791
785 @classmethod
792 @classmethod
786 def get_by_auth_token(cls, auth_token, cache=False):
793 def get_by_auth_token(cls, auth_token, cache=False):
787 q = UserApiKeys.query()\
794 q = UserApiKeys.query()\
788 .filter(UserApiKeys.api_key == auth_token)\
795 .filter(UserApiKeys.api_key == auth_token)\
789 .filter(or_(UserApiKeys.expires == -1,
796 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
797 UserApiKeys.expires >= time.time()))
791 if cache:
798 if cache:
792 q = q.options(
799 q = q.options(
793 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
800 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
794
801
795 match = q.first()
802 match = q.first()
796 if match:
803 if match:
797 return match.user
804 return match.user
798
805
799 @classmethod
806 @classmethod
800 def get_by_email(cls, email, case_insensitive=False, cache=False):
807 def get_by_email(cls, email, case_insensitive=False, cache=False):
801
808
802 if case_insensitive:
809 if case_insensitive:
803 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
810 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
804
811
805 else:
812 else:
806 q = cls.query().filter(cls.email == email)
813 q = cls.query().filter(cls.email == email)
807
814
808 email_key = _hash_key(email)
815 email_key = _hash_key(email)
809 if cache:
816 if cache:
810 q = q.options(
817 q = q.options(
811 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
818 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
812
819
813 ret = q.scalar()
820 ret = q.scalar()
814 if ret is None:
821 if ret is None:
815 q = UserEmailMap.query()
822 q = UserEmailMap.query()
816 # try fetching in alternate email map
823 # try fetching in alternate email map
817 if case_insensitive:
824 if case_insensitive:
818 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
825 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
819 else:
826 else:
820 q = q.filter(UserEmailMap.email == email)
827 q = q.filter(UserEmailMap.email == email)
821 q = q.options(joinedload(UserEmailMap.user))
828 q = q.options(joinedload(UserEmailMap.user))
822 if cache:
829 if cache:
823 q = q.options(
830 q = q.options(
824 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
831 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
825 ret = getattr(q.scalar(), 'user', None)
832 ret = getattr(q.scalar(), 'user', None)
826
833
827 return ret
834 return ret
828
835
829 @classmethod
836 @classmethod
830 def get_from_cs_author(cls, author):
837 def get_from_cs_author(cls, author):
831 """
838 """
832 Tries to get User objects out of commit author string
839 Tries to get User objects out of commit author string
833
840
834 :param author:
841 :param author:
835 """
842 """
836 from rhodecode.lib.helpers import email, author_name
843 from rhodecode.lib.helpers import email, author_name
837 # Valid email in the attribute passed, see if they're in the system
844 # Valid email in the attribute passed, see if they're in the system
838 _email = email(author)
845 _email = email(author)
839 if _email:
846 if _email:
840 user = cls.get_by_email(_email, case_insensitive=True)
847 user = cls.get_by_email(_email, case_insensitive=True)
841 if user:
848 if user:
842 return user
849 return user
843 # Maybe we can match by username?
850 # Maybe we can match by username?
844 _author = author_name(author)
851 _author = author_name(author)
845 user = cls.get_by_username(_author, case_insensitive=True)
852 user = cls.get_by_username(_author, case_insensitive=True)
846 if user:
853 if user:
847 return user
854 return user
848
855
849 def update_userdata(self, **kwargs):
856 def update_userdata(self, **kwargs):
850 usr = self
857 usr = self
851 old = usr.user_data
858 old = usr.user_data
852 old.update(**kwargs)
859 old.update(**kwargs)
853 usr.user_data = old
860 usr.user_data = old
854 Session().add(usr)
861 Session().add(usr)
855 log.debug('updated userdata with ', kwargs)
862 log.debug('updated userdata with ', kwargs)
856
863
857 def update_lastlogin(self):
864 def update_lastlogin(self):
858 """Update user lastlogin"""
865 """Update user lastlogin"""
859 self.last_login = datetime.datetime.now()
866 self.last_login = datetime.datetime.now()
860 Session().add(self)
867 Session().add(self)
861 log.debug('updated user %s lastlogin', self.username)
868 log.debug('updated user %s lastlogin', self.username)
862
869
863 def update_lastactivity(self):
870 def update_lastactivity(self):
864 """Update user lastactivity"""
871 """Update user lastactivity"""
865 self.last_activity = datetime.datetime.now()
872 self.last_activity = datetime.datetime.now()
866 Session().add(self)
873 Session().add(self)
867 log.debug('updated user %s lastactivity', self.username)
874 log.debug('updated user %s lastactivity', self.username)
868
875
869 def update_password(self, new_password):
876 def update_password(self, new_password):
870 from rhodecode.lib.auth import get_crypt_password
877 from rhodecode.lib.auth import get_crypt_password
871
878
872 self.password = get_crypt_password(new_password)
879 self.password = get_crypt_password(new_password)
873 Session().add(self)
880 Session().add(self)
874
881
875 @classmethod
882 @classmethod
876 def get_first_super_admin(cls):
883 def get_first_super_admin(cls):
877 user = User.query().filter(User.admin == true()).first()
884 user = User.query().filter(User.admin == true()).first()
878 if user is None:
885 if user is None:
879 raise Exception('FATAL: Missing administrative account!')
886 raise Exception('FATAL: Missing administrative account!')
880 return user
887 return user
881
888
882 @classmethod
889 @classmethod
883 def get_all_super_admins(cls):
890 def get_all_super_admins(cls):
884 """
891 """
885 Returns all admin accounts sorted by username
892 Returns all admin accounts sorted by username
886 """
893 """
887 return User.query().filter(User.admin == true())\
894 return User.query().filter(User.admin == true())\
888 .order_by(User.username.asc()).all()
895 .order_by(User.username.asc()).all()
889
896
890 @classmethod
897 @classmethod
891 def get_default_user(cls, cache=False, refresh=False):
898 def get_default_user(cls, cache=False, refresh=False):
892 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
899 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
893 if user is None:
900 if user is None:
894 raise Exception('FATAL: Missing default account!')
901 raise Exception('FATAL: Missing default account!')
895 if refresh:
902 if refresh:
896 # The default user might be based on outdated state which
903 # The default user might be based on outdated state which
897 # has been loaded from the cache.
904 # has been loaded from the cache.
898 # A call to refresh() ensures that the
905 # A call to refresh() ensures that the
899 # latest state from the database is used.
906 # latest state from the database is used.
900 Session().refresh(user)
907 Session().refresh(user)
901 return user
908 return user
902
909
903 def _get_default_perms(self, user, suffix=''):
910 def _get_default_perms(self, user, suffix=''):
904 from rhodecode.model.permission import PermissionModel
911 from rhodecode.model.permission import PermissionModel
905 return PermissionModel().get_default_perms(user.user_perms, suffix)
912 return PermissionModel().get_default_perms(user.user_perms, suffix)
906
913
907 def get_default_perms(self, suffix=''):
914 def get_default_perms(self, suffix=''):
908 return self._get_default_perms(self, suffix)
915 return self._get_default_perms(self, suffix)
909
916
910 def get_api_data(self, include_secrets=False, details='full'):
917 def get_api_data(self, include_secrets=False, details='full'):
911 """
918 """
912 Common function for generating user related data for API
919 Common function for generating user related data for API
913
920
914 :param include_secrets: By default secrets in the API data will be replaced
921 :param include_secrets: By default secrets in the API data will be replaced
915 by a placeholder value to prevent exposing this data by accident. In case
922 by a placeholder value to prevent exposing this data by accident. In case
916 this data shall be exposed, set this flag to ``True``.
923 this data shall be exposed, set this flag to ``True``.
917
924
918 :param details: details can be 'basic|full' basic gives only a subset of
925 :param details: details can be 'basic|full' basic gives only a subset of
919 the available user information that includes user_id, name and emails.
926 the available user information that includes user_id, name and emails.
920 """
927 """
921 user = self
928 user = self
922 user_data = self.user_data
929 user_data = self.user_data
923 data = {
930 data = {
924 'user_id': user.user_id,
931 'user_id': user.user_id,
925 'username': user.username,
932 'username': user.username,
926 'firstname': user.name,
933 'firstname': user.name,
927 'lastname': user.lastname,
934 'lastname': user.lastname,
928 'email': user.email,
935 'email': user.email,
929 'emails': user.emails,
936 'emails': user.emails,
930 }
937 }
931 if details == 'basic':
938 if details == 'basic':
932 return data
939 return data
933
940
934 api_key_length = 40
941 api_key_length = 40
935 api_key_replacement = '*' * api_key_length
942 api_key_replacement = '*' * api_key_length
936
943
937 extras = {
944 extras = {
938 'api_keys': [api_key_replacement],
945 'api_keys': [api_key_replacement],
939 'auth_tokens': [api_key_replacement],
946 'auth_tokens': [api_key_replacement],
940 'active': user.active,
947 'active': user.active,
941 'admin': user.admin,
948 'admin': user.admin,
942 'extern_type': user.extern_type,
949 'extern_type': user.extern_type,
943 'extern_name': user.extern_name,
950 'extern_name': user.extern_name,
944 'last_login': user.last_login,
951 'last_login': user.last_login,
945 'last_activity': user.last_activity,
952 'last_activity': user.last_activity,
946 'ip_addresses': user.ip_addresses,
953 'ip_addresses': user.ip_addresses,
947 'language': user_data.get('language')
954 'language': user_data.get('language')
948 }
955 }
949 data.update(extras)
956 data.update(extras)
950
957
951 if include_secrets:
958 if include_secrets:
952 data['api_keys'] = user.auth_tokens
959 data['api_keys'] = user.auth_tokens
953 data['auth_tokens'] = user.extra_auth_tokens
960 data['auth_tokens'] = user.extra_auth_tokens
954 return data
961 return data
955
962
956 def __json__(self):
963 def __json__(self):
957 data = {
964 data = {
958 'full_name': self.full_name,
965 'full_name': self.full_name,
959 'full_name_or_username': self.full_name_or_username,
966 'full_name_or_username': self.full_name_or_username,
960 'short_contact': self.short_contact,
967 'short_contact': self.short_contact,
961 'full_contact': self.full_contact,
968 'full_contact': self.full_contact,
962 }
969 }
963 data.update(self.get_api_data())
970 data.update(self.get_api_data())
964 return data
971 return data
965
972
966
973
967 class UserApiKeys(Base, BaseModel):
974 class UserApiKeys(Base, BaseModel):
968 __tablename__ = 'user_api_keys'
975 __tablename__ = 'user_api_keys'
969 __table_args__ = (
976 __table_args__ = (
970 Index('uak_api_key_idx', 'api_key'),
977 Index('uak_api_key_idx', 'api_key'),
971 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
978 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
972 UniqueConstraint('api_key'),
979 UniqueConstraint('api_key'),
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
975 )
982 )
976 __mapper_args__ = {}
983 __mapper_args__ = {}
977
984
978 # ApiKey role
985 # ApiKey role
979 ROLE_ALL = 'token_role_all'
986 ROLE_ALL = 'token_role_all'
980 ROLE_HTTP = 'token_role_http'
987 ROLE_HTTP = 'token_role_http'
981 ROLE_VCS = 'token_role_vcs'
988 ROLE_VCS = 'token_role_vcs'
982 ROLE_API = 'token_role_api'
989 ROLE_API = 'token_role_api'
983 ROLE_FEED = 'token_role_feed'
990 ROLE_FEED = 'token_role_feed'
984 ROLE_PASSWORD_RESET = 'token_password_reset'
991 ROLE_PASSWORD_RESET = 'token_password_reset'
985
992
986 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
993 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
987
994
988 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
989 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
990 api_key = Column("api_key", String(255), nullable=False, unique=True)
997 api_key = Column("api_key", String(255), nullable=False, unique=True)
991 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
998 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
992 expires = Column('expires', Float(53), nullable=False)
999 expires = Column('expires', Float(53), nullable=False)
993 role = Column('role', String(255), nullable=True)
1000 role = Column('role', String(255), nullable=True)
994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1001 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
995
1002
996 # scope columns
1003 # scope columns
997 repo_id = Column(
1004 repo_id = Column(
998 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1005 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
999 nullable=True, unique=None, default=None)
1006 nullable=True, unique=None, default=None)
1000 repo = relationship('Repository', lazy='joined')
1007 repo = relationship('Repository', lazy='joined')
1001
1008
1002 repo_group_id = Column(
1009 repo_group_id = Column(
1003 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1010 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1004 nullable=True, unique=None, default=None)
1011 nullable=True, unique=None, default=None)
1005 repo_group = relationship('RepoGroup', lazy='joined')
1012 repo_group = relationship('RepoGroup', lazy='joined')
1006
1013
1007 user = relationship('User', lazy='joined')
1014 user = relationship('User', lazy='joined')
1008
1015
1009 def __unicode__(self):
1016 def __unicode__(self):
1010 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1017 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1011
1018
1012 def __json__(self):
1019 def __json__(self):
1013 data = {
1020 data = {
1014 'auth_token': self.api_key,
1021 'auth_token': self.api_key,
1015 'role': self.role,
1022 'role': self.role,
1016 'scope': self.scope_humanized,
1023 'scope': self.scope_humanized,
1017 'expired': self.expired
1024 'expired': self.expired
1018 }
1025 }
1019 return data
1026 return data
1020
1027
1021 def get_api_data(self, include_secrets=False):
1028 def get_api_data(self, include_secrets=False):
1022 data = self.__json__()
1029 data = self.__json__()
1023 if include_secrets:
1030 if include_secrets:
1024 return data
1031 return data
1025 else:
1032 else:
1026 data['auth_token'] = self.token_obfuscated
1033 data['auth_token'] = self.token_obfuscated
1027 return data
1034 return data
1028
1035
1029 @hybrid_property
1036 @hybrid_property
1030 def description_safe(self):
1037 def description_safe(self):
1031 from rhodecode.lib import helpers as h
1038 from rhodecode.lib import helpers as h
1032 return h.escape(self.description)
1039 return h.escape(self.description)
1033
1040
1034 @property
1041 @property
1035 def expired(self):
1042 def expired(self):
1036 if self.expires == -1:
1043 if self.expires == -1:
1037 return False
1044 return False
1038 return time.time() > self.expires
1045 return time.time() > self.expires
1039
1046
1040 @classmethod
1047 @classmethod
1041 def _get_role_name(cls, role):
1048 def _get_role_name(cls, role):
1042 return {
1049 return {
1043 cls.ROLE_ALL: _('all'),
1050 cls.ROLE_ALL: _('all'),
1044 cls.ROLE_HTTP: _('http/web interface'),
1051 cls.ROLE_HTTP: _('http/web interface'),
1045 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1052 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1046 cls.ROLE_API: _('api calls'),
1053 cls.ROLE_API: _('api calls'),
1047 cls.ROLE_FEED: _('feed access'),
1054 cls.ROLE_FEED: _('feed access'),
1048 }.get(role, role)
1055 }.get(role, role)
1049
1056
1050 @property
1057 @property
1051 def role_humanized(self):
1058 def role_humanized(self):
1052 return self._get_role_name(self.role)
1059 return self._get_role_name(self.role)
1053
1060
1054 def _get_scope(self):
1061 def _get_scope(self):
1055 if self.repo:
1062 if self.repo:
1056 return repr(self.repo)
1063 return repr(self.repo)
1057 if self.repo_group:
1064 if self.repo_group:
1058 return repr(self.repo_group) + ' (recursive)'
1065 return repr(self.repo_group) + ' (recursive)'
1059 return 'global'
1066 return 'global'
1060
1067
1061 @property
1068 @property
1062 def scope_humanized(self):
1069 def scope_humanized(self):
1063 return self._get_scope()
1070 return self._get_scope()
1064
1071
1065 @property
1072 @property
1066 def token_obfuscated(self):
1073 def token_obfuscated(self):
1067 if self.api_key:
1074 if self.api_key:
1068 return self.api_key[:4] + "****"
1075 return self.api_key[:4] + "****"
1069
1076
1070
1077
1071 class UserEmailMap(Base, BaseModel):
1078 class UserEmailMap(Base, BaseModel):
1072 __tablename__ = 'user_email_map'
1079 __tablename__ = 'user_email_map'
1073 __table_args__ = (
1080 __table_args__ = (
1074 Index('uem_email_idx', 'email'),
1081 Index('uem_email_idx', 'email'),
1075 UniqueConstraint('email'),
1082 UniqueConstraint('email'),
1076 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1077 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1078 )
1085 )
1079 __mapper_args__ = {}
1086 __mapper_args__ = {}
1080
1087
1081 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1088 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1083 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1090 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1084 user = relationship('User', lazy='joined')
1091 user = relationship('User', lazy='joined')
1085
1092
1086 @validates('_email')
1093 @validates('_email')
1087 def validate_email(self, key, email):
1094 def validate_email(self, key, email):
1088 # check if this email is not main one
1095 # check if this email is not main one
1089 main_email = Session().query(User).filter(User.email == email).scalar()
1096 main_email = Session().query(User).filter(User.email == email).scalar()
1090 if main_email is not None:
1097 if main_email is not None:
1091 raise AttributeError('email %s is present is user table' % email)
1098 raise AttributeError('email %s is present is user table' % email)
1092 return email
1099 return email
1093
1100
1094 @hybrid_property
1101 @hybrid_property
1095 def email(self):
1102 def email(self):
1096 return self._email
1103 return self._email
1097
1104
1098 @email.setter
1105 @email.setter
1099 def email(self, val):
1106 def email(self, val):
1100 self._email = val.lower() if val else None
1107 self._email = val.lower() if val else None
1101
1108
1102
1109
1103 class UserIpMap(Base, BaseModel):
1110 class UserIpMap(Base, BaseModel):
1104 __tablename__ = 'user_ip_map'
1111 __tablename__ = 'user_ip_map'
1105 __table_args__ = (
1112 __table_args__ = (
1106 UniqueConstraint('user_id', 'ip_addr'),
1113 UniqueConstraint('user_id', 'ip_addr'),
1107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1108 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1109 )
1116 )
1110 __mapper_args__ = {}
1117 __mapper_args__ = {}
1111
1118
1112 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1119 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1113 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1120 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1114 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1121 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1115 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1122 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1116 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1123 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1117 user = relationship('User', lazy='joined')
1124 user = relationship('User', lazy='joined')
1118
1125
1119 @hybrid_property
1126 @hybrid_property
1120 def description_safe(self):
1127 def description_safe(self):
1121 from rhodecode.lib import helpers as h
1128 from rhodecode.lib import helpers as h
1122 return h.escape(self.description)
1129 return h.escape(self.description)
1123
1130
1124 @classmethod
1131 @classmethod
1125 def _get_ip_range(cls, ip_addr):
1132 def _get_ip_range(cls, ip_addr):
1126 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1133 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1127 return [str(net.network_address), str(net.broadcast_address)]
1134 return [str(net.network_address), str(net.broadcast_address)]
1128
1135
1129 def __json__(self):
1136 def __json__(self):
1130 return {
1137 return {
1131 'ip_addr': self.ip_addr,
1138 'ip_addr': self.ip_addr,
1132 'ip_range': self._get_ip_range(self.ip_addr),
1139 'ip_range': self._get_ip_range(self.ip_addr),
1133 }
1140 }
1134
1141
1135 def __unicode__(self):
1142 def __unicode__(self):
1136 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1143 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1137 self.user_id, self.ip_addr)
1144 self.user_id, self.ip_addr)
1138
1145
1139
1146
1140 class UserLog(Base, BaseModel):
1147 class UserLog(Base, BaseModel):
1141 __tablename__ = 'user_logs'
1148 __tablename__ = 'user_logs'
1142 __table_args__ = (
1149 __table_args__ = (
1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1145 )
1152 )
1146 VERSION_1 = 'v1'
1153 VERSION_1 = 'v1'
1147 VERSION_2 = 'v2'
1154 VERSION_2 = 'v2'
1148 VERSIONS = [VERSION_1, VERSION_2]
1155 VERSIONS = [VERSION_1, VERSION_2]
1149
1156
1150 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1151 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1158 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1152 username = Column("username", String(255), nullable=True, unique=None, default=None)
1159 username = Column("username", String(255), nullable=True, unique=None, default=None)
1153 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1154 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1161 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1155 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1162 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1156 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1163 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1157 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1164 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1158
1165
1159 version = Column("version", String(255), nullable=True, default=VERSION_1)
1166 version = Column("version", String(255), nullable=True, default=VERSION_1)
1160 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1167 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1161 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1168 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1162
1169
1163 def __unicode__(self):
1170 def __unicode__(self):
1164 return u"<%s('id:%s:%s')>" % (
1171 return u"<%s('id:%s:%s')>" % (
1165 self.__class__.__name__, self.repository_name, self.action)
1172 self.__class__.__name__, self.repository_name, self.action)
1166
1173
1167 def __json__(self):
1174 def __json__(self):
1168 return {
1175 return {
1169 'user_id': self.user_id,
1176 'user_id': self.user_id,
1170 'username': self.username,
1177 'username': self.username,
1171 'repository_id': self.repository_id,
1178 'repository_id': self.repository_id,
1172 'repository_name': self.repository_name,
1179 'repository_name': self.repository_name,
1173 'user_ip': self.user_ip,
1180 'user_ip': self.user_ip,
1174 'action_date': self.action_date,
1181 'action_date': self.action_date,
1175 'action': self.action,
1182 'action': self.action,
1176 }
1183 }
1177
1184
1178 @property
1185 @property
1179 def action_as_day(self):
1186 def action_as_day(self):
1180 return datetime.date(*self.action_date.timetuple()[:3])
1187 return datetime.date(*self.action_date.timetuple()[:3])
1181
1188
1182 user = relationship('User')
1189 user = relationship('User')
1183 repository = relationship('Repository', cascade='')
1190 repository = relationship('Repository', cascade='')
1184
1191
1185
1192
1186 class UserGroup(Base, BaseModel):
1193 class UserGroup(Base, BaseModel):
1187 __tablename__ = 'users_groups'
1194 __tablename__ = 'users_groups'
1188 __table_args__ = (
1195 __table_args__ = (
1189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1196 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1197 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1191 )
1198 )
1192
1199
1193 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1200 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1194 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1201 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1195 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1202 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1196 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1203 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1197 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1204 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1198 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1205 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1200 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1207 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1201
1208
1202 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1209 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1203 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1210 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1204 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1211 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1205 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1212 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1206 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1213 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1207 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1214 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1208
1215
1209 user = relationship('User')
1216 user = relationship('User')
1210
1217
1211 @hybrid_property
1218 @hybrid_property
1212 def description_safe(self):
1219 def description_safe(self):
1213 from rhodecode.lib import helpers as h
1220 from rhodecode.lib import helpers as h
1214 return h.escape(self.description)
1221 return h.escape(self.description)
1215
1222
1216 @hybrid_property
1223 @hybrid_property
1217 def group_data(self):
1224 def group_data(self):
1218 if not self._group_data:
1225 if not self._group_data:
1219 return {}
1226 return {}
1220
1227
1221 try:
1228 try:
1222 return json.loads(self._group_data)
1229 return json.loads(self._group_data)
1223 except TypeError:
1230 except TypeError:
1224 return {}
1231 return {}
1225
1232
1226 @group_data.setter
1233 @group_data.setter
1227 def group_data(self, val):
1234 def group_data(self, val):
1228 try:
1235 try:
1229 self._group_data = json.dumps(val)
1236 self._group_data = json.dumps(val)
1230 except Exception:
1237 except Exception:
1231 log.error(traceback.format_exc())
1238 log.error(traceback.format_exc())
1232
1239
1233 def __unicode__(self):
1240 def __unicode__(self):
1234 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1241 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1235 self.users_group_id,
1242 self.users_group_id,
1236 self.users_group_name)
1243 self.users_group_name)
1237
1244
1238 @classmethod
1245 @classmethod
1239 def get_by_group_name(cls, group_name, cache=False,
1246 def get_by_group_name(cls, group_name, cache=False,
1240 case_insensitive=False):
1247 case_insensitive=False):
1241 if case_insensitive:
1248 if case_insensitive:
1242 q = cls.query().filter(func.lower(cls.users_group_name) ==
1249 q = cls.query().filter(func.lower(cls.users_group_name) ==
1243 func.lower(group_name))
1250 func.lower(group_name))
1244
1251
1245 else:
1252 else:
1246 q = cls.query().filter(cls.users_group_name == group_name)
1253 q = cls.query().filter(cls.users_group_name == group_name)
1247 if cache:
1254 if cache:
1248 q = q.options(
1255 q = q.options(
1249 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1256 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1250 return q.scalar()
1257 return q.scalar()
1251
1258
1252 @classmethod
1259 @classmethod
1253 def get(cls, user_group_id, cache=False):
1260 def get(cls, user_group_id, cache=False):
1254 user_group = cls.query()
1261 user_group = cls.query()
1255 if cache:
1262 if cache:
1256 user_group = user_group.options(
1263 user_group = user_group.options(
1257 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1264 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1258 return user_group.get(user_group_id)
1265 return user_group.get(user_group_id)
1259
1266
1260 def permissions(self, with_admins=True, with_owner=True):
1267 def permissions(self, with_admins=True, with_owner=True):
1261 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1268 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1262 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1269 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1263 joinedload(UserUserGroupToPerm.user),
1270 joinedload(UserUserGroupToPerm.user),
1264 joinedload(UserUserGroupToPerm.permission),)
1271 joinedload(UserUserGroupToPerm.permission),)
1265
1272
1266 # get owners and admins and permissions. We do a trick of re-writing
1273 # get owners and admins and permissions. We do a trick of re-writing
1267 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1274 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1268 # has a global reference and changing one object propagates to all
1275 # has a global reference and changing one object propagates to all
1269 # others. This means if admin is also an owner admin_row that change
1276 # others. This means if admin is also an owner admin_row that change
1270 # would propagate to both objects
1277 # would propagate to both objects
1271 perm_rows = []
1278 perm_rows = []
1272 for _usr in q.all():
1279 for _usr in q.all():
1273 usr = AttributeDict(_usr.user.get_dict())
1280 usr = AttributeDict(_usr.user.get_dict())
1274 usr.permission = _usr.permission.permission_name
1281 usr.permission = _usr.permission.permission_name
1275 perm_rows.append(usr)
1282 perm_rows.append(usr)
1276
1283
1277 # filter the perm rows by 'default' first and then sort them by
1284 # filter the perm rows by 'default' first and then sort them by
1278 # admin,write,read,none permissions sorted again alphabetically in
1285 # admin,write,read,none permissions sorted again alphabetically in
1279 # each group
1286 # each group
1280 perm_rows = sorted(perm_rows, key=display_sort)
1287 perm_rows = sorted(perm_rows, key=display_sort)
1281
1288
1282 _admin_perm = 'usergroup.admin'
1289 _admin_perm = 'usergroup.admin'
1283 owner_row = []
1290 owner_row = []
1284 if with_owner:
1291 if with_owner:
1285 usr = AttributeDict(self.user.get_dict())
1292 usr = AttributeDict(self.user.get_dict())
1286 usr.owner_row = True
1293 usr.owner_row = True
1287 usr.permission = _admin_perm
1294 usr.permission = _admin_perm
1288 owner_row.append(usr)
1295 owner_row.append(usr)
1289
1296
1290 super_admin_rows = []
1297 super_admin_rows = []
1291 if with_admins:
1298 if with_admins:
1292 for usr in User.get_all_super_admins():
1299 for usr in User.get_all_super_admins():
1293 # if this admin is also owner, don't double the record
1300 # if this admin is also owner, don't double the record
1294 if usr.user_id == owner_row[0].user_id:
1301 if usr.user_id == owner_row[0].user_id:
1295 owner_row[0].admin_row = True
1302 owner_row[0].admin_row = True
1296 else:
1303 else:
1297 usr = AttributeDict(usr.get_dict())
1304 usr = AttributeDict(usr.get_dict())
1298 usr.admin_row = True
1305 usr.admin_row = True
1299 usr.permission = _admin_perm
1306 usr.permission = _admin_perm
1300 super_admin_rows.append(usr)
1307 super_admin_rows.append(usr)
1301
1308
1302 return super_admin_rows + owner_row + perm_rows
1309 return super_admin_rows + owner_row + perm_rows
1303
1310
1304 def permission_user_groups(self):
1311 def permission_user_groups(self):
1305 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1312 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1306 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1313 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1307 joinedload(UserGroupUserGroupToPerm.target_user_group),
1314 joinedload(UserGroupUserGroupToPerm.target_user_group),
1308 joinedload(UserGroupUserGroupToPerm.permission),)
1315 joinedload(UserGroupUserGroupToPerm.permission),)
1309
1316
1310 perm_rows = []
1317 perm_rows = []
1311 for _user_group in q.all():
1318 for _user_group in q.all():
1312 usr = AttributeDict(_user_group.user_group.get_dict())
1319 usr = AttributeDict(_user_group.user_group.get_dict())
1313 usr.permission = _user_group.permission.permission_name
1320 usr.permission = _user_group.permission.permission_name
1314 perm_rows.append(usr)
1321 perm_rows.append(usr)
1315
1322
1316 return perm_rows
1323 return perm_rows
1317
1324
1318 def _get_default_perms(self, user_group, suffix=''):
1325 def _get_default_perms(self, user_group, suffix=''):
1319 from rhodecode.model.permission import PermissionModel
1326 from rhodecode.model.permission import PermissionModel
1320 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1327 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1321
1328
1322 def get_default_perms(self, suffix=''):
1329 def get_default_perms(self, suffix=''):
1323 return self._get_default_perms(self, suffix)
1330 return self._get_default_perms(self, suffix)
1324
1331
1325 def get_api_data(self, with_group_members=True, include_secrets=False):
1332 def get_api_data(self, with_group_members=True, include_secrets=False):
1326 """
1333 """
1327 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1334 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1328 basically forwarded.
1335 basically forwarded.
1329
1336
1330 """
1337 """
1331 user_group = self
1338 user_group = self
1332 data = {
1339 data = {
1333 'users_group_id': user_group.users_group_id,
1340 'users_group_id': user_group.users_group_id,
1334 'group_name': user_group.users_group_name,
1341 'group_name': user_group.users_group_name,
1335 'group_description': user_group.user_group_description,
1342 'group_description': user_group.user_group_description,
1336 'active': user_group.users_group_active,
1343 'active': user_group.users_group_active,
1337 'owner': user_group.user.username,
1344 'owner': user_group.user.username,
1338 'owner_email': user_group.user.email,
1345 'owner_email': user_group.user.email,
1339 }
1346 }
1340
1347
1341 if with_group_members:
1348 if with_group_members:
1342 users = []
1349 users = []
1343 for user in user_group.members:
1350 for user in user_group.members:
1344 user = user.user
1351 user = user.user
1345 users.append(user.get_api_data(include_secrets=include_secrets))
1352 users.append(user.get_api_data(include_secrets=include_secrets))
1346 data['users'] = users
1353 data['users'] = users
1347
1354
1348 return data
1355 return data
1349
1356
1350
1357
1351 class UserGroupMember(Base, BaseModel):
1358 class UserGroupMember(Base, BaseModel):
1352 __tablename__ = 'users_groups_members'
1359 __tablename__ = 'users_groups_members'
1353 __table_args__ = (
1360 __table_args__ = (
1354 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1355 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1362 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1356 )
1363 )
1357
1364
1358 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1359 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1366 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1360 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1361
1368
1362 user = relationship('User', lazy='joined')
1369 user = relationship('User', lazy='joined')
1363 users_group = relationship('UserGroup')
1370 users_group = relationship('UserGroup')
1364
1371
1365 def __init__(self, gr_id='', u_id=''):
1372 def __init__(self, gr_id='', u_id=''):
1366 self.users_group_id = gr_id
1373 self.users_group_id = gr_id
1367 self.user_id = u_id
1374 self.user_id = u_id
1368
1375
1369
1376
1370 class RepositoryField(Base, BaseModel):
1377 class RepositoryField(Base, BaseModel):
1371 __tablename__ = 'repositories_fields'
1378 __tablename__ = 'repositories_fields'
1372 __table_args__ = (
1379 __table_args__ = (
1373 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1380 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1374 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1375 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1382 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1376 )
1383 )
1377 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1384 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1378
1385
1379 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1386 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1380 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1387 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1381 field_key = Column("field_key", String(250))
1388 field_key = Column("field_key", String(250))
1382 field_label = Column("field_label", String(1024), nullable=False)
1389 field_label = Column("field_label", String(1024), nullable=False)
1383 field_value = Column("field_value", String(10000), nullable=False)
1390 field_value = Column("field_value", String(10000), nullable=False)
1384 field_desc = Column("field_desc", String(1024), nullable=False)
1391 field_desc = Column("field_desc", String(1024), nullable=False)
1385 field_type = Column("field_type", String(255), nullable=False, unique=None)
1392 field_type = Column("field_type", String(255), nullable=False, unique=None)
1386 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1393 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1387
1394
1388 repository = relationship('Repository')
1395 repository = relationship('Repository')
1389
1396
1390 @property
1397 @property
1391 def field_key_prefixed(self):
1398 def field_key_prefixed(self):
1392 return 'ex_%s' % self.field_key
1399 return 'ex_%s' % self.field_key
1393
1400
1394 @classmethod
1401 @classmethod
1395 def un_prefix_key(cls, key):
1402 def un_prefix_key(cls, key):
1396 if key.startswith(cls.PREFIX):
1403 if key.startswith(cls.PREFIX):
1397 return key[len(cls.PREFIX):]
1404 return key[len(cls.PREFIX):]
1398 return key
1405 return key
1399
1406
1400 @classmethod
1407 @classmethod
1401 def get_by_key_name(cls, key, repo):
1408 def get_by_key_name(cls, key, repo):
1402 row = cls.query()\
1409 row = cls.query()\
1403 .filter(cls.repository == repo)\
1410 .filter(cls.repository == repo)\
1404 .filter(cls.field_key == key).scalar()
1411 .filter(cls.field_key == key).scalar()
1405 return row
1412 return row
1406
1413
1407
1414
1408 class Repository(Base, BaseModel):
1415 class Repository(Base, BaseModel):
1409 __tablename__ = 'repositories'
1416 __tablename__ = 'repositories'
1410 __table_args__ = (
1417 __table_args__ = (
1411 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1418 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1412 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1413 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1420 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1414 )
1421 )
1415 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1422 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1416 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1423 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1417
1424
1418 STATE_CREATED = 'repo_state_created'
1425 STATE_CREATED = 'repo_state_created'
1419 STATE_PENDING = 'repo_state_pending'
1426 STATE_PENDING = 'repo_state_pending'
1420 STATE_ERROR = 'repo_state_error'
1427 STATE_ERROR = 'repo_state_error'
1421
1428
1422 LOCK_AUTOMATIC = 'lock_auto'
1429 LOCK_AUTOMATIC = 'lock_auto'
1423 LOCK_API = 'lock_api'
1430 LOCK_API = 'lock_api'
1424 LOCK_WEB = 'lock_web'
1431 LOCK_WEB = 'lock_web'
1425 LOCK_PULL = 'lock_pull'
1432 LOCK_PULL = 'lock_pull'
1426
1433
1427 NAME_SEP = URL_SEP
1434 NAME_SEP = URL_SEP
1428
1435
1429 repo_id = Column(
1436 repo_id = Column(
1430 "repo_id", Integer(), nullable=False, unique=True, default=None,
1437 "repo_id", Integer(), nullable=False, unique=True, default=None,
1431 primary_key=True)
1438 primary_key=True)
1432 _repo_name = Column(
1439 _repo_name = Column(
1433 "repo_name", Text(), nullable=False, default=None)
1440 "repo_name", Text(), nullable=False, default=None)
1434 _repo_name_hash = Column(
1441 _repo_name_hash = Column(
1435 "repo_name_hash", String(255), nullable=False, unique=True)
1442 "repo_name_hash", String(255), nullable=False, unique=True)
1436 repo_state = Column("repo_state", String(255), nullable=True)
1443 repo_state = Column("repo_state", String(255), nullable=True)
1437
1444
1438 clone_uri = Column(
1445 clone_uri = Column(
1439 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1446 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1440 default=None)
1447 default=None)
1441 repo_type = Column(
1448 repo_type = Column(
1442 "repo_type", String(255), nullable=False, unique=False, default=None)
1449 "repo_type", String(255), nullable=False, unique=False, default=None)
1443 user_id = Column(
1450 user_id = Column(
1444 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1451 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1445 unique=False, default=None)
1452 unique=False, default=None)
1446 private = Column(
1453 private = Column(
1447 "private", Boolean(), nullable=True, unique=None, default=None)
1454 "private", Boolean(), nullable=True, unique=None, default=None)
1448 enable_statistics = Column(
1455 enable_statistics = Column(
1449 "statistics", Boolean(), nullable=True, unique=None, default=True)
1456 "statistics", Boolean(), nullable=True, unique=None, default=True)
1450 enable_downloads = Column(
1457 enable_downloads = Column(
1451 "downloads", Boolean(), nullable=True, unique=None, default=True)
1458 "downloads", Boolean(), nullable=True, unique=None, default=True)
1452 description = Column(
1459 description = Column(
1453 "description", String(10000), nullable=True, unique=None, default=None)
1460 "description", String(10000), nullable=True, unique=None, default=None)
1454 created_on = Column(
1461 created_on = Column(
1455 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1462 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1456 default=datetime.datetime.now)
1463 default=datetime.datetime.now)
1457 updated_on = Column(
1464 updated_on = Column(
1458 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1465 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1459 default=datetime.datetime.now)
1466 default=datetime.datetime.now)
1460 _landing_revision = Column(
1467 _landing_revision = Column(
1461 "landing_revision", String(255), nullable=False, unique=False,
1468 "landing_revision", String(255), nullable=False, unique=False,
1462 default=None)
1469 default=None)
1463 enable_locking = Column(
1470 enable_locking = Column(
1464 "enable_locking", Boolean(), nullable=False, unique=None,
1471 "enable_locking", Boolean(), nullable=False, unique=None,
1465 default=False)
1472 default=False)
1466 _locked = Column(
1473 _locked = Column(
1467 "locked", String(255), nullable=True, unique=False, default=None)
1474 "locked", String(255), nullable=True, unique=False, default=None)
1468 _changeset_cache = Column(
1475 _changeset_cache = Column(
1469 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1476 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1470
1477
1471 fork_id = Column(
1478 fork_id = Column(
1472 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1479 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1473 nullable=True, unique=False, default=None)
1480 nullable=True, unique=False, default=None)
1474 group_id = Column(
1481 group_id = Column(
1475 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1482 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1476 unique=False, default=None)
1483 unique=False, default=None)
1477
1484
1478 user = relationship('User', lazy='joined')
1485 user = relationship('User', lazy='joined')
1479 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1486 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1480 group = relationship('RepoGroup', lazy='joined')
1487 group = relationship('RepoGroup', lazy='joined')
1481 repo_to_perm = relationship(
1488 repo_to_perm = relationship(
1482 'UserRepoToPerm', cascade='all',
1489 'UserRepoToPerm', cascade='all',
1483 order_by='UserRepoToPerm.repo_to_perm_id')
1490 order_by='UserRepoToPerm.repo_to_perm_id')
1484 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1491 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1485 stats = relationship('Statistics', cascade='all', uselist=False)
1492 stats = relationship('Statistics', cascade='all', uselist=False)
1486
1493
1487 followers = relationship(
1494 followers = relationship(
1488 'UserFollowing',
1495 'UserFollowing',
1489 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1496 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1490 cascade='all')
1497 cascade='all')
1491 extra_fields = relationship(
1498 extra_fields = relationship(
1492 'RepositoryField', cascade="all, delete, delete-orphan")
1499 'RepositoryField', cascade="all, delete, delete-orphan")
1493 logs = relationship('UserLog')
1500 logs = relationship('UserLog')
1494 comments = relationship(
1501 comments = relationship(
1495 'ChangesetComment', cascade="all, delete, delete-orphan")
1502 'ChangesetComment', cascade="all, delete, delete-orphan")
1496 pull_requests_source = relationship(
1503 pull_requests_source = relationship(
1497 'PullRequest',
1504 'PullRequest',
1498 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1505 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1499 cascade="all, delete, delete-orphan")
1506 cascade="all, delete, delete-orphan")
1500 pull_requests_target = relationship(
1507 pull_requests_target = relationship(
1501 'PullRequest',
1508 'PullRequest',
1502 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1509 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1503 cascade="all, delete, delete-orphan")
1510 cascade="all, delete, delete-orphan")
1504 ui = relationship('RepoRhodeCodeUi', cascade="all")
1511 ui = relationship('RepoRhodeCodeUi', cascade="all")
1505 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1512 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1506 integrations = relationship('Integration',
1513 integrations = relationship('Integration',
1507 cascade="all, delete, delete-orphan")
1514 cascade="all, delete, delete-orphan")
1508
1515
1509 def __unicode__(self):
1516 def __unicode__(self):
1510 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1517 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1511 safe_unicode(self.repo_name))
1518 safe_unicode(self.repo_name))
1512
1519
1513 @hybrid_property
1520 @hybrid_property
1514 def description_safe(self):
1521 def description_safe(self):
1515 from rhodecode.lib import helpers as h
1522 from rhodecode.lib import helpers as h
1516 return h.escape(self.description)
1523 return h.escape(self.description)
1517
1524
1518 @hybrid_property
1525 @hybrid_property
1519 def landing_rev(self):
1526 def landing_rev(self):
1520 # always should return [rev_type, rev]
1527 # always should return [rev_type, rev]
1521 if self._landing_revision:
1528 if self._landing_revision:
1522 _rev_info = self._landing_revision.split(':')
1529 _rev_info = self._landing_revision.split(':')
1523 if len(_rev_info) < 2:
1530 if len(_rev_info) < 2:
1524 _rev_info.insert(0, 'rev')
1531 _rev_info.insert(0, 'rev')
1525 return [_rev_info[0], _rev_info[1]]
1532 return [_rev_info[0], _rev_info[1]]
1526 return [None, None]
1533 return [None, None]
1527
1534
1528 @landing_rev.setter
1535 @landing_rev.setter
1529 def landing_rev(self, val):
1536 def landing_rev(self, val):
1530 if ':' not in val:
1537 if ':' not in val:
1531 raise ValueError('value must be delimited with `:` and consist '
1538 raise ValueError('value must be delimited with `:` and consist '
1532 'of <rev_type>:<rev>, got %s instead' % val)
1539 'of <rev_type>:<rev>, got %s instead' % val)
1533 self._landing_revision = val
1540 self._landing_revision = val
1534
1541
1535 @hybrid_property
1542 @hybrid_property
1536 def locked(self):
1543 def locked(self):
1537 if self._locked:
1544 if self._locked:
1538 user_id, timelocked, reason = self._locked.split(':')
1545 user_id, timelocked, reason = self._locked.split(':')
1539 lock_values = int(user_id), timelocked, reason
1546 lock_values = int(user_id), timelocked, reason
1540 else:
1547 else:
1541 lock_values = [None, None, None]
1548 lock_values = [None, None, None]
1542 return lock_values
1549 return lock_values
1543
1550
1544 @locked.setter
1551 @locked.setter
1545 def locked(self, val):
1552 def locked(self, val):
1546 if val and isinstance(val, (list, tuple)):
1553 if val and isinstance(val, (list, tuple)):
1547 self._locked = ':'.join(map(str, val))
1554 self._locked = ':'.join(map(str, val))
1548 else:
1555 else:
1549 self._locked = None
1556 self._locked = None
1550
1557
1551 @hybrid_property
1558 @hybrid_property
1552 def changeset_cache(self):
1559 def changeset_cache(self):
1553 from rhodecode.lib.vcs.backends.base import EmptyCommit
1560 from rhodecode.lib.vcs.backends.base import EmptyCommit
1554 dummy = EmptyCommit().__json__()
1561 dummy = EmptyCommit().__json__()
1555 if not self._changeset_cache:
1562 if not self._changeset_cache:
1556 return dummy
1563 return dummy
1557 try:
1564 try:
1558 return json.loads(self._changeset_cache)
1565 return json.loads(self._changeset_cache)
1559 except TypeError:
1566 except TypeError:
1560 return dummy
1567 return dummy
1561 except Exception:
1568 except Exception:
1562 log.error(traceback.format_exc())
1569 log.error(traceback.format_exc())
1563 return dummy
1570 return dummy
1564
1571
1565 @changeset_cache.setter
1572 @changeset_cache.setter
1566 def changeset_cache(self, val):
1573 def changeset_cache(self, val):
1567 try:
1574 try:
1568 self._changeset_cache = json.dumps(val)
1575 self._changeset_cache = json.dumps(val)
1569 except Exception:
1576 except Exception:
1570 log.error(traceback.format_exc())
1577 log.error(traceback.format_exc())
1571
1578
1572 @hybrid_property
1579 @hybrid_property
1573 def repo_name(self):
1580 def repo_name(self):
1574 return self._repo_name
1581 return self._repo_name
1575
1582
1576 @repo_name.setter
1583 @repo_name.setter
1577 def repo_name(self, value):
1584 def repo_name(self, value):
1578 self._repo_name = value
1585 self._repo_name = value
1579 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1586 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1580
1587
1581 @classmethod
1588 @classmethod
1582 def normalize_repo_name(cls, repo_name):
1589 def normalize_repo_name(cls, repo_name):
1583 """
1590 """
1584 Normalizes os specific repo_name to the format internally stored inside
1591 Normalizes os specific repo_name to the format internally stored inside
1585 database using URL_SEP
1592 database using URL_SEP
1586
1593
1587 :param cls:
1594 :param cls:
1588 :param repo_name:
1595 :param repo_name:
1589 """
1596 """
1590 return cls.NAME_SEP.join(repo_name.split(os.sep))
1597 return cls.NAME_SEP.join(repo_name.split(os.sep))
1591
1598
1592 @classmethod
1599 @classmethod
1593 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1600 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1594 session = Session()
1601 session = Session()
1595 q = session.query(cls).filter(cls.repo_name == repo_name)
1602 q = session.query(cls).filter(cls.repo_name == repo_name)
1596
1603
1597 if cache:
1604 if cache:
1598 if identity_cache:
1605 if identity_cache:
1599 val = cls.identity_cache(session, 'repo_name', repo_name)
1606 val = cls.identity_cache(session, 'repo_name', repo_name)
1600 if val:
1607 if val:
1601 return val
1608 return val
1602 else:
1609 else:
1603 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1610 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1604 q = q.options(
1611 q = q.options(
1605 FromCache("sql_cache_short", cache_key))
1612 FromCache("sql_cache_short", cache_key))
1606
1613
1607 return q.scalar()
1614 return q.scalar()
1608
1615
1609 @classmethod
1616 @classmethod
1610 def get_by_full_path(cls, repo_full_path):
1617 def get_by_full_path(cls, repo_full_path):
1611 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1618 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1612 repo_name = cls.normalize_repo_name(repo_name)
1619 repo_name = cls.normalize_repo_name(repo_name)
1613 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1620 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1614
1621
1615 @classmethod
1622 @classmethod
1616 def get_repo_forks(cls, repo_id):
1623 def get_repo_forks(cls, repo_id):
1617 return cls.query().filter(Repository.fork_id == repo_id)
1624 return cls.query().filter(Repository.fork_id == repo_id)
1618
1625
1619 @classmethod
1626 @classmethod
1620 def base_path(cls):
1627 def base_path(cls):
1621 """
1628 """
1622 Returns base path when all repos are stored
1629 Returns base path when all repos are stored
1623
1630
1624 :param cls:
1631 :param cls:
1625 """
1632 """
1626 q = Session().query(RhodeCodeUi)\
1633 q = Session().query(RhodeCodeUi)\
1627 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1634 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1628 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1635 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1629 return q.one().ui_value
1636 return q.one().ui_value
1630
1637
1631 @classmethod
1638 @classmethod
1632 def is_valid(cls, repo_name):
1639 def is_valid(cls, repo_name):
1633 """
1640 """
1634 returns True if given repo name is a valid filesystem repository
1641 returns True if given repo name is a valid filesystem repository
1635
1642
1636 :param cls:
1643 :param cls:
1637 :param repo_name:
1644 :param repo_name:
1638 """
1645 """
1639 from rhodecode.lib.utils import is_valid_repo
1646 from rhodecode.lib.utils import is_valid_repo
1640
1647
1641 return is_valid_repo(repo_name, cls.base_path())
1648 return is_valid_repo(repo_name, cls.base_path())
1642
1649
1643 @classmethod
1650 @classmethod
1644 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1651 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1645 case_insensitive=True):
1652 case_insensitive=True):
1646 q = Repository.query()
1653 q = Repository.query()
1647
1654
1648 if not isinstance(user_id, Optional):
1655 if not isinstance(user_id, Optional):
1649 q = q.filter(Repository.user_id == user_id)
1656 q = q.filter(Repository.user_id == user_id)
1650
1657
1651 if not isinstance(group_id, Optional):
1658 if not isinstance(group_id, Optional):
1652 q = q.filter(Repository.group_id == group_id)
1659 q = q.filter(Repository.group_id == group_id)
1653
1660
1654 if case_insensitive:
1661 if case_insensitive:
1655 q = q.order_by(func.lower(Repository.repo_name))
1662 q = q.order_by(func.lower(Repository.repo_name))
1656 else:
1663 else:
1657 q = q.order_by(Repository.repo_name)
1664 q = q.order_by(Repository.repo_name)
1658 return q.all()
1665 return q.all()
1659
1666
1660 @property
1667 @property
1661 def forks(self):
1668 def forks(self):
1662 """
1669 """
1663 Return forks of this repo
1670 Return forks of this repo
1664 """
1671 """
1665 return Repository.get_repo_forks(self.repo_id)
1672 return Repository.get_repo_forks(self.repo_id)
1666
1673
1667 @property
1674 @property
1668 def parent(self):
1675 def parent(self):
1669 """
1676 """
1670 Returns fork parent
1677 Returns fork parent
1671 """
1678 """
1672 return self.fork
1679 return self.fork
1673
1680
1674 @property
1681 @property
1675 def just_name(self):
1682 def just_name(self):
1676 return self.repo_name.split(self.NAME_SEP)[-1]
1683 return self.repo_name.split(self.NAME_SEP)[-1]
1677
1684
1678 @property
1685 @property
1679 def groups_with_parents(self):
1686 def groups_with_parents(self):
1680 groups = []
1687 groups = []
1681 if self.group is None:
1688 if self.group is None:
1682 return groups
1689 return groups
1683
1690
1684 cur_gr = self.group
1691 cur_gr = self.group
1685 groups.insert(0, cur_gr)
1692 groups.insert(0, cur_gr)
1686 while 1:
1693 while 1:
1687 gr = getattr(cur_gr, 'parent_group', None)
1694 gr = getattr(cur_gr, 'parent_group', None)
1688 cur_gr = cur_gr.parent_group
1695 cur_gr = cur_gr.parent_group
1689 if gr is None:
1696 if gr is None:
1690 break
1697 break
1691 groups.insert(0, gr)
1698 groups.insert(0, gr)
1692
1699
1693 return groups
1700 return groups
1694
1701
1695 @property
1702 @property
1696 def groups_and_repo(self):
1703 def groups_and_repo(self):
1697 return self.groups_with_parents, self
1704 return self.groups_with_parents, self
1698
1705
1699 @LazyProperty
1706 @LazyProperty
1700 def repo_path(self):
1707 def repo_path(self):
1701 """
1708 """
1702 Returns base full path for that repository means where it actually
1709 Returns base full path for that repository means where it actually
1703 exists on a filesystem
1710 exists on a filesystem
1704 """
1711 """
1705 q = Session().query(RhodeCodeUi).filter(
1712 q = Session().query(RhodeCodeUi).filter(
1706 RhodeCodeUi.ui_key == self.NAME_SEP)
1713 RhodeCodeUi.ui_key == self.NAME_SEP)
1707 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1714 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1708 return q.one().ui_value
1715 return q.one().ui_value
1709
1716
1710 @property
1717 @property
1711 def repo_full_path(self):
1718 def repo_full_path(self):
1712 p = [self.repo_path]
1719 p = [self.repo_path]
1713 # we need to split the name by / since this is how we store the
1720 # we need to split the name by / since this is how we store the
1714 # names in the database, but that eventually needs to be converted
1721 # names in the database, but that eventually needs to be converted
1715 # into a valid system path
1722 # into a valid system path
1716 p += self.repo_name.split(self.NAME_SEP)
1723 p += self.repo_name.split(self.NAME_SEP)
1717 return os.path.join(*map(safe_unicode, p))
1724 return os.path.join(*map(safe_unicode, p))
1718
1725
1719 @property
1726 @property
1720 def cache_keys(self):
1727 def cache_keys(self):
1721 """
1728 """
1722 Returns associated cache keys for that repo
1729 Returns associated cache keys for that repo
1723 """
1730 """
1724 return CacheKey.query()\
1731 return CacheKey.query()\
1725 .filter(CacheKey.cache_args == self.repo_name)\
1732 .filter(CacheKey.cache_args == self.repo_name)\
1726 .order_by(CacheKey.cache_key)\
1733 .order_by(CacheKey.cache_key)\
1727 .all()
1734 .all()
1728
1735
1729 def get_new_name(self, repo_name):
1736 def get_new_name(self, repo_name):
1730 """
1737 """
1731 returns new full repository name based on assigned group and new new
1738 returns new full repository name based on assigned group and new new
1732
1739
1733 :param group_name:
1740 :param group_name:
1734 """
1741 """
1735 path_prefix = self.group.full_path_splitted if self.group else []
1742 path_prefix = self.group.full_path_splitted if self.group else []
1736 return self.NAME_SEP.join(path_prefix + [repo_name])
1743 return self.NAME_SEP.join(path_prefix + [repo_name])
1737
1744
1738 @property
1745 @property
1739 def _config(self):
1746 def _config(self):
1740 """
1747 """
1741 Returns db based config object.
1748 Returns db based config object.
1742 """
1749 """
1743 from rhodecode.lib.utils import make_db_config
1750 from rhodecode.lib.utils import make_db_config
1744 return make_db_config(clear_session=False, repo=self)
1751 return make_db_config(clear_session=False, repo=self)
1745
1752
1746 def permissions(self, with_admins=True, with_owner=True):
1753 def permissions(self, with_admins=True, with_owner=True):
1747 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1754 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1748 q = q.options(joinedload(UserRepoToPerm.repository),
1755 q = q.options(joinedload(UserRepoToPerm.repository),
1749 joinedload(UserRepoToPerm.user),
1756 joinedload(UserRepoToPerm.user),
1750 joinedload(UserRepoToPerm.permission),)
1757 joinedload(UserRepoToPerm.permission),)
1751
1758
1752 # get owners and admins and permissions. We do a trick of re-writing
1759 # get owners and admins and permissions. We do a trick of re-writing
1753 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1760 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1754 # has a global reference and changing one object propagates to all
1761 # has a global reference and changing one object propagates to all
1755 # others. This means if admin is also an owner admin_row that change
1762 # others. This means if admin is also an owner admin_row that change
1756 # would propagate to both objects
1763 # would propagate to both objects
1757 perm_rows = []
1764 perm_rows = []
1758 for _usr in q.all():
1765 for _usr in q.all():
1759 usr = AttributeDict(_usr.user.get_dict())
1766 usr = AttributeDict(_usr.user.get_dict())
1760 usr.permission = _usr.permission.permission_name
1767 usr.permission = _usr.permission.permission_name
1761 perm_rows.append(usr)
1768 perm_rows.append(usr)
1762
1769
1763 # filter the perm rows by 'default' first and then sort them by
1770 # filter the perm rows by 'default' first and then sort them by
1764 # admin,write,read,none permissions sorted again alphabetically in
1771 # admin,write,read,none permissions sorted again alphabetically in
1765 # each group
1772 # each group
1766 perm_rows = sorted(perm_rows, key=display_sort)
1773 perm_rows = sorted(perm_rows, key=display_sort)
1767
1774
1768 _admin_perm = 'repository.admin'
1775 _admin_perm = 'repository.admin'
1769 owner_row = []
1776 owner_row = []
1770 if with_owner:
1777 if with_owner:
1771 usr = AttributeDict(self.user.get_dict())
1778 usr = AttributeDict(self.user.get_dict())
1772 usr.owner_row = True
1779 usr.owner_row = True
1773 usr.permission = _admin_perm
1780 usr.permission = _admin_perm
1774 owner_row.append(usr)
1781 owner_row.append(usr)
1775
1782
1776 super_admin_rows = []
1783 super_admin_rows = []
1777 if with_admins:
1784 if with_admins:
1778 for usr in User.get_all_super_admins():
1785 for usr in User.get_all_super_admins():
1779 # if this admin is also owner, don't double the record
1786 # if this admin is also owner, don't double the record
1780 if usr.user_id == owner_row[0].user_id:
1787 if usr.user_id == owner_row[0].user_id:
1781 owner_row[0].admin_row = True
1788 owner_row[0].admin_row = True
1782 else:
1789 else:
1783 usr = AttributeDict(usr.get_dict())
1790 usr = AttributeDict(usr.get_dict())
1784 usr.admin_row = True
1791 usr.admin_row = True
1785 usr.permission = _admin_perm
1792 usr.permission = _admin_perm
1786 super_admin_rows.append(usr)
1793 super_admin_rows.append(usr)
1787
1794
1788 return super_admin_rows + owner_row + perm_rows
1795 return super_admin_rows + owner_row + perm_rows
1789
1796
1790 def permission_user_groups(self):
1797 def permission_user_groups(self):
1791 q = UserGroupRepoToPerm.query().filter(
1798 q = UserGroupRepoToPerm.query().filter(
1792 UserGroupRepoToPerm.repository == self)
1799 UserGroupRepoToPerm.repository == self)
1793 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1800 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1794 joinedload(UserGroupRepoToPerm.users_group),
1801 joinedload(UserGroupRepoToPerm.users_group),
1795 joinedload(UserGroupRepoToPerm.permission),)
1802 joinedload(UserGroupRepoToPerm.permission),)
1796
1803
1797 perm_rows = []
1804 perm_rows = []
1798 for _user_group in q.all():
1805 for _user_group in q.all():
1799 usr = AttributeDict(_user_group.users_group.get_dict())
1806 usr = AttributeDict(_user_group.users_group.get_dict())
1800 usr.permission = _user_group.permission.permission_name
1807 usr.permission = _user_group.permission.permission_name
1801 perm_rows.append(usr)
1808 perm_rows.append(usr)
1802
1809
1803 return perm_rows
1810 return perm_rows
1804
1811
1805 def get_api_data(self, include_secrets=False):
1812 def get_api_data(self, include_secrets=False):
1806 """
1813 """
1807 Common function for generating repo api data
1814 Common function for generating repo api data
1808
1815
1809 :param include_secrets: See :meth:`User.get_api_data`.
1816 :param include_secrets: See :meth:`User.get_api_data`.
1810
1817
1811 """
1818 """
1812 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1819 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1813 # move this methods on models level.
1820 # move this methods on models level.
1814 from rhodecode.model.settings import SettingsModel
1821 from rhodecode.model.settings import SettingsModel
1815 from rhodecode.model.repo import RepoModel
1822 from rhodecode.model.repo import RepoModel
1816
1823
1817 repo = self
1824 repo = self
1818 _user_id, _time, _reason = self.locked
1825 _user_id, _time, _reason = self.locked
1819
1826
1820 data = {
1827 data = {
1821 'repo_id': repo.repo_id,
1828 'repo_id': repo.repo_id,
1822 'repo_name': repo.repo_name,
1829 'repo_name': repo.repo_name,
1823 'repo_type': repo.repo_type,
1830 'repo_type': repo.repo_type,
1824 'clone_uri': repo.clone_uri or '',
1831 'clone_uri': repo.clone_uri or '',
1825 'url': RepoModel().get_url(self),
1832 'url': RepoModel().get_url(self),
1826 'private': repo.private,
1833 'private': repo.private,
1827 'created_on': repo.created_on,
1834 'created_on': repo.created_on,
1828 'description': repo.description_safe,
1835 'description': repo.description_safe,
1829 'landing_rev': repo.landing_rev,
1836 'landing_rev': repo.landing_rev,
1830 'owner': repo.user.username,
1837 'owner': repo.user.username,
1831 'fork_of': repo.fork.repo_name if repo.fork else None,
1838 'fork_of': repo.fork.repo_name if repo.fork else None,
1832 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1839 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1833 'enable_statistics': repo.enable_statistics,
1840 'enable_statistics': repo.enable_statistics,
1834 'enable_locking': repo.enable_locking,
1841 'enable_locking': repo.enable_locking,
1835 'enable_downloads': repo.enable_downloads,
1842 'enable_downloads': repo.enable_downloads,
1836 'last_changeset': repo.changeset_cache,
1843 'last_changeset': repo.changeset_cache,
1837 'locked_by': User.get(_user_id).get_api_data(
1844 'locked_by': User.get(_user_id).get_api_data(
1838 include_secrets=include_secrets) if _user_id else None,
1845 include_secrets=include_secrets) if _user_id else None,
1839 'locked_date': time_to_datetime(_time) if _time else None,
1846 'locked_date': time_to_datetime(_time) if _time else None,
1840 'lock_reason': _reason if _reason else None,
1847 'lock_reason': _reason if _reason else None,
1841 }
1848 }
1842
1849
1843 # TODO: mikhail: should be per-repo settings here
1850 # TODO: mikhail: should be per-repo settings here
1844 rc_config = SettingsModel().get_all_settings()
1851 rc_config = SettingsModel().get_all_settings()
1845 repository_fields = str2bool(
1852 repository_fields = str2bool(
1846 rc_config.get('rhodecode_repository_fields'))
1853 rc_config.get('rhodecode_repository_fields'))
1847 if repository_fields:
1854 if repository_fields:
1848 for f in self.extra_fields:
1855 for f in self.extra_fields:
1849 data[f.field_key_prefixed] = f.field_value
1856 data[f.field_key_prefixed] = f.field_value
1850
1857
1851 return data
1858 return data
1852
1859
1853 @classmethod
1860 @classmethod
1854 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1861 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1855 if not lock_time:
1862 if not lock_time:
1856 lock_time = time.time()
1863 lock_time = time.time()
1857 if not lock_reason:
1864 if not lock_reason:
1858 lock_reason = cls.LOCK_AUTOMATIC
1865 lock_reason = cls.LOCK_AUTOMATIC
1859 repo.locked = [user_id, lock_time, lock_reason]
1866 repo.locked = [user_id, lock_time, lock_reason]
1860 Session().add(repo)
1867 Session().add(repo)
1861 Session().commit()
1868 Session().commit()
1862
1869
1863 @classmethod
1870 @classmethod
1864 def unlock(cls, repo):
1871 def unlock(cls, repo):
1865 repo.locked = None
1872 repo.locked = None
1866 Session().add(repo)
1873 Session().add(repo)
1867 Session().commit()
1874 Session().commit()
1868
1875
1869 @classmethod
1876 @classmethod
1870 def getlock(cls, repo):
1877 def getlock(cls, repo):
1871 return repo.locked
1878 return repo.locked
1872
1879
1873 def is_user_lock(self, user_id):
1880 def is_user_lock(self, user_id):
1874 if self.lock[0]:
1881 if self.lock[0]:
1875 lock_user_id = safe_int(self.lock[0])
1882 lock_user_id = safe_int(self.lock[0])
1876 user_id = safe_int(user_id)
1883 user_id = safe_int(user_id)
1877 # both are ints, and they are equal
1884 # both are ints, and they are equal
1878 return all([lock_user_id, user_id]) and lock_user_id == user_id
1885 return all([lock_user_id, user_id]) and lock_user_id == user_id
1879
1886
1880 return False
1887 return False
1881
1888
1882 def get_locking_state(self, action, user_id, only_when_enabled=True):
1889 def get_locking_state(self, action, user_id, only_when_enabled=True):
1883 """
1890 """
1884 Checks locking on this repository, if locking is enabled and lock is
1891 Checks locking on this repository, if locking is enabled and lock is
1885 present returns a tuple of make_lock, locked, locked_by.
1892 present returns a tuple of make_lock, locked, locked_by.
1886 make_lock can have 3 states None (do nothing) True, make lock
1893 make_lock can have 3 states None (do nothing) True, make lock
1887 False release lock, This value is later propagated to hooks, which
1894 False release lock, This value is later propagated to hooks, which
1888 do the locking. Think about this as signals passed to hooks what to do.
1895 do the locking. Think about this as signals passed to hooks what to do.
1889
1896
1890 """
1897 """
1891 # TODO: johbo: This is part of the business logic and should be moved
1898 # TODO: johbo: This is part of the business logic and should be moved
1892 # into the RepositoryModel.
1899 # into the RepositoryModel.
1893
1900
1894 if action not in ('push', 'pull'):
1901 if action not in ('push', 'pull'):
1895 raise ValueError("Invalid action value: %s" % repr(action))
1902 raise ValueError("Invalid action value: %s" % repr(action))
1896
1903
1897 # defines if locked error should be thrown to user
1904 # defines if locked error should be thrown to user
1898 currently_locked = False
1905 currently_locked = False
1899 # defines if new lock should be made, tri-state
1906 # defines if new lock should be made, tri-state
1900 make_lock = None
1907 make_lock = None
1901 repo = self
1908 repo = self
1902 user = User.get(user_id)
1909 user = User.get(user_id)
1903
1910
1904 lock_info = repo.locked
1911 lock_info = repo.locked
1905
1912
1906 if repo and (repo.enable_locking or not only_when_enabled):
1913 if repo and (repo.enable_locking or not only_when_enabled):
1907 if action == 'push':
1914 if action == 'push':
1908 # check if it's already locked !, if it is compare users
1915 # check if it's already locked !, if it is compare users
1909 locked_by_user_id = lock_info[0]
1916 locked_by_user_id = lock_info[0]
1910 if user.user_id == locked_by_user_id:
1917 if user.user_id == locked_by_user_id:
1911 log.debug(
1918 log.debug(
1912 'Got `push` action from user %s, now unlocking', user)
1919 'Got `push` action from user %s, now unlocking', user)
1913 # unlock if we have push from user who locked
1920 # unlock if we have push from user who locked
1914 make_lock = False
1921 make_lock = False
1915 else:
1922 else:
1916 # we're not the same user who locked, ban with
1923 # we're not the same user who locked, ban with
1917 # code defined in settings (default is 423 HTTP Locked) !
1924 # code defined in settings (default is 423 HTTP Locked) !
1918 log.debug('Repo %s is currently locked by %s', repo, user)
1925 log.debug('Repo %s is currently locked by %s', repo, user)
1919 currently_locked = True
1926 currently_locked = True
1920 elif action == 'pull':
1927 elif action == 'pull':
1921 # [0] user [1] date
1928 # [0] user [1] date
1922 if lock_info[0] and lock_info[1]:
1929 if lock_info[0] and lock_info[1]:
1923 log.debug('Repo %s is currently locked by %s', repo, user)
1930 log.debug('Repo %s is currently locked by %s', repo, user)
1924 currently_locked = True
1931 currently_locked = True
1925 else:
1932 else:
1926 log.debug('Setting lock on repo %s by %s', repo, user)
1933 log.debug('Setting lock on repo %s by %s', repo, user)
1927 make_lock = True
1934 make_lock = True
1928
1935
1929 else:
1936 else:
1930 log.debug('Repository %s do not have locking enabled', repo)
1937 log.debug('Repository %s do not have locking enabled', repo)
1931
1938
1932 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1939 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1933 make_lock, currently_locked, lock_info)
1940 make_lock, currently_locked, lock_info)
1934
1941
1935 from rhodecode.lib.auth import HasRepoPermissionAny
1942 from rhodecode.lib.auth import HasRepoPermissionAny
1936 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1943 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1937 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1944 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1938 # if we don't have at least write permission we cannot make a lock
1945 # if we don't have at least write permission we cannot make a lock
1939 log.debug('lock state reset back to FALSE due to lack '
1946 log.debug('lock state reset back to FALSE due to lack '
1940 'of at least read permission')
1947 'of at least read permission')
1941 make_lock = False
1948 make_lock = False
1942
1949
1943 return make_lock, currently_locked, lock_info
1950 return make_lock, currently_locked, lock_info
1944
1951
1945 @property
1952 @property
1946 def last_db_change(self):
1953 def last_db_change(self):
1947 return self.updated_on
1954 return self.updated_on
1948
1955
1949 @property
1956 @property
1950 def clone_uri_hidden(self):
1957 def clone_uri_hidden(self):
1951 clone_uri = self.clone_uri
1958 clone_uri = self.clone_uri
1952 if clone_uri:
1959 if clone_uri:
1953 import urlobject
1960 import urlobject
1954 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1961 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1955 if url_obj.password:
1962 if url_obj.password:
1956 clone_uri = url_obj.with_password('*****')
1963 clone_uri = url_obj.with_password('*****')
1957 return clone_uri
1964 return clone_uri
1958
1965
1959 def clone_url(self, **override):
1966 def clone_url(self, **override):
1960 from rhodecode.model.settings import SettingsModel
1967 from rhodecode.model.settings import SettingsModel
1961
1968
1962 uri_tmpl = None
1969 uri_tmpl = None
1963 if 'with_id' in override:
1970 if 'with_id' in override:
1964 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1971 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1965 del override['with_id']
1972 del override['with_id']
1966
1973
1967 if 'uri_tmpl' in override:
1974 if 'uri_tmpl' in override:
1968 uri_tmpl = override['uri_tmpl']
1975 uri_tmpl = override['uri_tmpl']
1969 del override['uri_tmpl']
1976 del override['uri_tmpl']
1970
1977
1971 # we didn't override our tmpl from **overrides
1978 # we didn't override our tmpl from **overrides
1972 if not uri_tmpl:
1979 if not uri_tmpl:
1973 rc_config = SettingsModel().get_all_settings(cache=True)
1980 rc_config = SettingsModel().get_all_settings(cache=True)
1974 uri_tmpl = rc_config.get(
1981 uri_tmpl = rc_config.get(
1975 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1982 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1976
1983
1977 request = get_current_request()
1984 request = get_current_request()
1978 return get_clone_url(request=request,
1985 return get_clone_url(request=request,
1979 uri_tmpl=uri_tmpl,
1986 uri_tmpl=uri_tmpl,
1980 repo_name=self.repo_name,
1987 repo_name=self.repo_name,
1981 repo_id=self.repo_id, **override)
1988 repo_id=self.repo_id, **override)
1982
1989
1983 def set_state(self, state):
1990 def set_state(self, state):
1984 self.repo_state = state
1991 self.repo_state = state
1985 Session().add(self)
1992 Session().add(self)
1986 #==========================================================================
1993 #==========================================================================
1987 # SCM PROPERTIES
1994 # SCM PROPERTIES
1988 #==========================================================================
1995 #==========================================================================
1989
1996
1990 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1997 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1991 return get_commit_safe(
1998 return get_commit_safe(
1992 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1999 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1993
2000
1994 def get_changeset(self, rev=None, pre_load=None):
2001 def get_changeset(self, rev=None, pre_load=None):
1995 warnings.warn("Use get_commit", DeprecationWarning)
2002 warnings.warn("Use get_commit", DeprecationWarning)
1996 commit_id = None
2003 commit_id = None
1997 commit_idx = None
2004 commit_idx = None
1998 if isinstance(rev, basestring):
2005 if isinstance(rev, basestring):
1999 commit_id = rev
2006 commit_id = rev
2000 else:
2007 else:
2001 commit_idx = rev
2008 commit_idx = rev
2002 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2009 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2003 pre_load=pre_load)
2010 pre_load=pre_load)
2004
2011
2005 def get_landing_commit(self):
2012 def get_landing_commit(self):
2006 """
2013 """
2007 Returns landing commit, or if that doesn't exist returns the tip
2014 Returns landing commit, or if that doesn't exist returns the tip
2008 """
2015 """
2009 _rev_type, _rev = self.landing_rev
2016 _rev_type, _rev = self.landing_rev
2010 commit = self.get_commit(_rev)
2017 commit = self.get_commit(_rev)
2011 if isinstance(commit, EmptyCommit):
2018 if isinstance(commit, EmptyCommit):
2012 return self.get_commit()
2019 return self.get_commit()
2013 return commit
2020 return commit
2014
2021
2015 def update_commit_cache(self, cs_cache=None, config=None):
2022 def update_commit_cache(self, cs_cache=None, config=None):
2016 """
2023 """
2017 Update cache of last changeset for repository, keys should be::
2024 Update cache of last changeset for repository, keys should be::
2018
2025
2019 short_id
2026 short_id
2020 raw_id
2027 raw_id
2021 revision
2028 revision
2022 parents
2029 parents
2023 message
2030 message
2024 date
2031 date
2025 author
2032 author
2026
2033
2027 :param cs_cache:
2034 :param cs_cache:
2028 """
2035 """
2029 from rhodecode.lib.vcs.backends.base import BaseChangeset
2036 from rhodecode.lib.vcs.backends.base import BaseChangeset
2030 if cs_cache is None:
2037 if cs_cache is None:
2031 # use no-cache version here
2038 # use no-cache version here
2032 scm_repo = self.scm_instance(cache=False, config=config)
2039 scm_repo = self.scm_instance(cache=False, config=config)
2033 if scm_repo:
2040 if scm_repo:
2034 cs_cache = scm_repo.get_commit(
2041 cs_cache = scm_repo.get_commit(
2035 pre_load=["author", "date", "message", "parents"])
2042 pre_load=["author", "date", "message", "parents"])
2036 else:
2043 else:
2037 cs_cache = EmptyCommit()
2044 cs_cache = EmptyCommit()
2038
2045
2039 if isinstance(cs_cache, BaseChangeset):
2046 if isinstance(cs_cache, BaseChangeset):
2040 cs_cache = cs_cache.__json__()
2047 cs_cache = cs_cache.__json__()
2041
2048
2042 def is_outdated(new_cs_cache):
2049 def is_outdated(new_cs_cache):
2043 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2050 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2044 new_cs_cache['revision'] != self.changeset_cache['revision']):
2051 new_cs_cache['revision'] != self.changeset_cache['revision']):
2045 return True
2052 return True
2046 return False
2053 return False
2047
2054
2048 # check if we have maybe already latest cached revision
2055 # check if we have maybe already latest cached revision
2049 if is_outdated(cs_cache) or not self.changeset_cache:
2056 if is_outdated(cs_cache) or not self.changeset_cache:
2050 _default = datetime.datetime.fromtimestamp(0)
2057 _default = datetime.datetime.fromtimestamp(0)
2051 last_change = cs_cache.get('date') or _default
2058 last_change = cs_cache.get('date') or _default
2052 log.debug('updated repo %s with new cs cache %s',
2059 log.debug('updated repo %s with new cs cache %s',
2053 self.repo_name, cs_cache)
2060 self.repo_name, cs_cache)
2054 self.updated_on = last_change
2061 self.updated_on = last_change
2055 self.changeset_cache = cs_cache
2062 self.changeset_cache = cs_cache
2056 Session().add(self)
2063 Session().add(self)
2057 Session().commit()
2064 Session().commit()
2058 else:
2065 else:
2059 log.debug('Skipping update_commit_cache for repo:`%s` '
2066 log.debug('Skipping update_commit_cache for repo:`%s` '
2060 'commit already with latest changes', self.repo_name)
2067 'commit already with latest changes', self.repo_name)
2061
2068
2062 @property
2069 @property
2063 def tip(self):
2070 def tip(self):
2064 return self.get_commit('tip')
2071 return self.get_commit('tip')
2065
2072
2066 @property
2073 @property
2067 def author(self):
2074 def author(self):
2068 return self.tip.author
2075 return self.tip.author
2069
2076
2070 @property
2077 @property
2071 def last_change(self):
2078 def last_change(self):
2072 return self.scm_instance().last_change
2079 return self.scm_instance().last_change
2073
2080
2074 def get_comments(self, revisions=None):
2081 def get_comments(self, revisions=None):
2075 """
2082 """
2076 Returns comments for this repository grouped by revisions
2083 Returns comments for this repository grouped by revisions
2077
2084
2078 :param revisions: filter query by revisions only
2085 :param revisions: filter query by revisions only
2079 """
2086 """
2080 cmts = ChangesetComment.query()\
2087 cmts = ChangesetComment.query()\
2081 .filter(ChangesetComment.repo == self)
2088 .filter(ChangesetComment.repo == self)
2082 if revisions:
2089 if revisions:
2083 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2090 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2084 grouped = collections.defaultdict(list)
2091 grouped = collections.defaultdict(list)
2085 for cmt in cmts.all():
2092 for cmt in cmts.all():
2086 grouped[cmt.revision].append(cmt)
2093 grouped[cmt.revision].append(cmt)
2087 return grouped
2094 return grouped
2088
2095
2089 def statuses(self, revisions=None):
2096 def statuses(self, revisions=None):
2090 """
2097 """
2091 Returns statuses for this repository
2098 Returns statuses for this repository
2092
2099
2093 :param revisions: list of revisions to get statuses for
2100 :param revisions: list of revisions to get statuses for
2094 """
2101 """
2095 statuses = ChangesetStatus.query()\
2102 statuses = ChangesetStatus.query()\
2096 .filter(ChangesetStatus.repo == self)\
2103 .filter(ChangesetStatus.repo == self)\
2097 .filter(ChangesetStatus.version == 0)
2104 .filter(ChangesetStatus.version == 0)
2098
2105
2099 if revisions:
2106 if revisions:
2100 # Try doing the filtering in chunks to avoid hitting limits
2107 # Try doing the filtering in chunks to avoid hitting limits
2101 size = 500
2108 size = 500
2102 status_results = []
2109 status_results = []
2103 for chunk in xrange(0, len(revisions), size):
2110 for chunk in xrange(0, len(revisions), size):
2104 status_results += statuses.filter(
2111 status_results += statuses.filter(
2105 ChangesetStatus.revision.in_(
2112 ChangesetStatus.revision.in_(
2106 revisions[chunk: chunk+size])
2113 revisions[chunk: chunk+size])
2107 ).all()
2114 ).all()
2108 else:
2115 else:
2109 status_results = statuses.all()
2116 status_results = statuses.all()
2110
2117
2111 grouped = {}
2118 grouped = {}
2112
2119
2113 # maybe we have open new pullrequest without a status?
2120 # maybe we have open new pullrequest without a status?
2114 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2121 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2115 status_lbl = ChangesetStatus.get_status_lbl(stat)
2122 status_lbl = ChangesetStatus.get_status_lbl(stat)
2116 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2123 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2117 for rev in pr.revisions:
2124 for rev in pr.revisions:
2118 pr_id = pr.pull_request_id
2125 pr_id = pr.pull_request_id
2119 pr_repo = pr.target_repo.repo_name
2126 pr_repo = pr.target_repo.repo_name
2120 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2127 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2121
2128
2122 for stat in status_results:
2129 for stat in status_results:
2123 pr_id = pr_repo = None
2130 pr_id = pr_repo = None
2124 if stat.pull_request:
2131 if stat.pull_request:
2125 pr_id = stat.pull_request.pull_request_id
2132 pr_id = stat.pull_request.pull_request_id
2126 pr_repo = stat.pull_request.target_repo.repo_name
2133 pr_repo = stat.pull_request.target_repo.repo_name
2127 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2134 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2128 pr_id, pr_repo]
2135 pr_id, pr_repo]
2129 return grouped
2136 return grouped
2130
2137
2131 # ==========================================================================
2138 # ==========================================================================
2132 # SCM CACHE INSTANCE
2139 # SCM CACHE INSTANCE
2133 # ==========================================================================
2140 # ==========================================================================
2134
2141
2135 def scm_instance(self, **kwargs):
2142 def scm_instance(self, **kwargs):
2136 import rhodecode
2143 import rhodecode
2137
2144
2138 # Passing a config will not hit the cache currently only used
2145 # Passing a config will not hit the cache currently only used
2139 # for repo2dbmapper
2146 # for repo2dbmapper
2140 config = kwargs.pop('config', None)
2147 config = kwargs.pop('config', None)
2141 cache = kwargs.pop('cache', None)
2148 cache = kwargs.pop('cache', None)
2142 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2149 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2143 # if cache is NOT defined use default global, else we have a full
2150 # if cache is NOT defined use default global, else we have a full
2144 # control over cache behaviour
2151 # control over cache behaviour
2145 if cache is None and full_cache and not config:
2152 if cache is None and full_cache and not config:
2146 return self._get_instance_cached()
2153 return self._get_instance_cached()
2147 return self._get_instance(cache=bool(cache), config=config)
2154 return self._get_instance(cache=bool(cache), config=config)
2148
2155
2149 def _get_instance_cached(self):
2156 def _get_instance_cached(self):
2150 @cache_region('long_term')
2157 @cache_region('long_term')
2151 def _get_repo(cache_key):
2158 def _get_repo(cache_key):
2152 return self._get_instance()
2159 return self._get_instance()
2153
2160
2154 invalidator_context = CacheKey.repo_context_cache(
2161 invalidator_context = CacheKey.repo_context_cache(
2155 _get_repo, self.repo_name, None, thread_scoped=True)
2162 _get_repo, self.repo_name, None, thread_scoped=True)
2156
2163
2157 with invalidator_context as context:
2164 with invalidator_context as context:
2158 context.invalidate()
2165 context.invalidate()
2159 repo = context.compute()
2166 repo = context.compute()
2160
2167
2161 return repo
2168 return repo
2162
2169
2163 def _get_instance(self, cache=True, config=None):
2170 def _get_instance(self, cache=True, config=None):
2164 config = config or self._config
2171 config = config or self._config
2165 custom_wire = {
2172 custom_wire = {
2166 'cache': cache # controls the vcs.remote cache
2173 'cache': cache # controls the vcs.remote cache
2167 }
2174 }
2168 repo = get_vcs_instance(
2175 repo = get_vcs_instance(
2169 repo_path=safe_str(self.repo_full_path),
2176 repo_path=safe_str(self.repo_full_path),
2170 config=config,
2177 config=config,
2171 with_wire=custom_wire,
2178 with_wire=custom_wire,
2172 create=False,
2179 create=False,
2173 _vcs_alias=self.repo_type)
2180 _vcs_alias=self.repo_type)
2174
2181
2175 return repo
2182 return repo
2176
2183
2177 def __json__(self):
2184 def __json__(self):
2178 return {'landing_rev': self.landing_rev}
2185 return {'landing_rev': self.landing_rev}
2179
2186
2180 def get_dict(self):
2187 def get_dict(self):
2181
2188
2182 # Since we transformed `repo_name` to a hybrid property, we need to
2189 # Since we transformed `repo_name` to a hybrid property, we need to
2183 # keep compatibility with the code which uses `repo_name` field.
2190 # keep compatibility with the code which uses `repo_name` field.
2184
2191
2185 result = super(Repository, self).get_dict()
2192 result = super(Repository, self).get_dict()
2186 result['repo_name'] = result.pop('_repo_name', None)
2193 result['repo_name'] = result.pop('_repo_name', None)
2187 return result
2194 return result
2188
2195
2189
2196
2190 class RepoGroup(Base, BaseModel):
2197 class RepoGroup(Base, BaseModel):
2191 __tablename__ = 'groups'
2198 __tablename__ = 'groups'
2192 __table_args__ = (
2199 __table_args__ = (
2193 UniqueConstraint('group_name', 'group_parent_id'),
2200 UniqueConstraint('group_name', 'group_parent_id'),
2194 CheckConstraint('group_id != group_parent_id'),
2201 CheckConstraint('group_id != group_parent_id'),
2195 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2196 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2197 )
2204 )
2198 __mapper_args__ = {'order_by': 'group_name'}
2205 __mapper_args__ = {'order_by': 'group_name'}
2199
2206
2200 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2207 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2201
2208
2202 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2209 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2203 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2210 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2204 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2211 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2205 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2212 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2206 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2213 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2207 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2214 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2208 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2215 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2209 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2216 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2210
2217
2211 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2218 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2212 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2219 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2213 parent_group = relationship('RepoGroup', remote_side=group_id)
2220 parent_group = relationship('RepoGroup', remote_side=group_id)
2214 user = relationship('User')
2221 user = relationship('User')
2215 integrations = relationship('Integration',
2222 integrations = relationship('Integration',
2216 cascade="all, delete, delete-orphan")
2223 cascade="all, delete, delete-orphan")
2217
2224
2218 def __init__(self, group_name='', parent_group=None):
2225 def __init__(self, group_name='', parent_group=None):
2219 self.group_name = group_name
2226 self.group_name = group_name
2220 self.parent_group = parent_group
2227 self.parent_group = parent_group
2221
2228
2222 def __unicode__(self):
2229 def __unicode__(self):
2223 return u"<%s('id:%s:%s')>" % (
2230 return u"<%s('id:%s:%s')>" % (
2224 self.__class__.__name__, self.group_id, self.group_name)
2231 self.__class__.__name__, self.group_id, self.group_name)
2225
2232
2226 @hybrid_property
2233 @hybrid_property
2227 def description_safe(self):
2234 def description_safe(self):
2228 from rhodecode.lib import helpers as h
2235 from rhodecode.lib import helpers as h
2229 return h.escape(self.group_description)
2236 return h.escape(self.group_description)
2230
2237
2231 @classmethod
2238 @classmethod
2232 def _generate_choice(cls, repo_group):
2239 def _generate_choice(cls, repo_group):
2233 from webhelpers.html import literal as _literal
2240 from webhelpers.html import literal as _literal
2234 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2241 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2235 return repo_group.group_id, _name(repo_group.full_path_splitted)
2242 return repo_group.group_id, _name(repo_group.full_path_splitted)
2236
2243
2237 @classmethod
2244 @classmethod
2238 def groups_choices(cls, groups=None, show_empty_group=True):
2245 def groups_choices(cls, groups=None, show_empty_group=True):
2239 if not groups:
2246 if not groups:
2240 groups = cls.query().all()
2247 groups = cls.query().all()
2241
2248
2242 repo_groups = []
2249 repo_groups = []
2243 if show_empty_group:
2250 if show_empty_group:
2244 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2251 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2245
2252
2246 repo_groups.extend([cls._generate_choice(x) for x in groups])
2253 repo_groups.extend([cls._generate_choice(x) for x in groups])
2247
2254
2248 repo_groups = sorted(
2255 repo_groups = sorted(
2249 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2256 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2250 return repo_groups
2257 return repo_groups
2251
2258
2252 @classmethod
2259 @classmethod
2253 def url_sep(cls):
2260 def url_sep(cls):
2254 return URL_SEP
2261 return URL_SEP
2255
2262
2256 @classmethod
2263 @classmethod
2257 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2264 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2258 if case_insensitive:
2265 if case_insensitive:
2259 gr = cls.query().filter(func.lower(cls.group_name)
2266 gr = cls.query().filter(func.lower(cls.group_name)
2260 == func.lower(group_name))
2267 == func.lower(group_name))
2261 else:
2268 else:
2262 gr = cls.query().filter(cls.group_name == group_name)
2269 gr = cls.query().filter(cls.group_name == group_name)
2263 if cache:
2270 if cache:
2264 name_key = _hash_key(group_name)
2271 name_key = _hash_key(group_name)
2265 gr = gr.options(
2272 gr = gr.options(
2266 FromCache("sql_cache_short", "get_group_%s" % name_key))
2273 FromCache("sql_cache_short", "get_group_%s" % name_key))
2267 return gr.scalar()
2274 return gr.scalar()
2268
2275
2269 @classmethod
2276 @classmethod
2270 def get_user_personal_repo_group(cls, user_id):
2277 def get_user_personal_repo_group(cls, user_id):
2271 user = User.get(user_id)
2278 user = User.get(user_id)
2272 if user.username == User.DEFAULT_USER:
2279 if user.username == User.DEFAULT_USER:
2273 return None
2280 return None
2274
2281
2275 return cls.query()\
2282 return cls.query()\
2276 .filter(cls.personal == true()) \
2283 .filter(cls.personal == true()) \
2277 .filter(cls.user == user).scalar()
2284 .filter(cls.user == user).scalar()
2278
2285
2279 @classmethod
2286 @classmethod
2280 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2287 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2281 case_insensitive=True):
2288 case_insensitive=True):
2282 q = RepoGroup.query()
2289 q = RepoGroup.query()
2283
2290
2284 if not isinstance(user_id, Optional):
2291 if not isinstance(user_id, Optional):
2285 q = q.filter(RepoGroup.user_id == user_id)
2292 q = q.filter(RepoGroup.user_id == user_id)
2286
2293
2287 if not isinstance(group_id, Optional):
2294 if not isinstance(group_id, Optional):
2288 q = q.filter(RepoGroup.group_parent_id == group_id)
2295 q = q.filter(RepoGroup.group_parent_id == group_id)
2289
2296
2290 if case_insensitive:
2297 if case_insensitive:
2291 q = q.order_by(func.lower(RepoGroup.group_name))
2298 q = q.order_by(func.lower(RepoGroup.group_name))
2292 else:
2299 else:
2293 q = q.order_by(RepoGroup.group_name)
2300 q = q.order_by(RepoGroup.group_name)
2294 return q.all()
2301 return q.all()
2295
2302
2296 @property
2303 @property
2297 def parents(self):
2304 def parents(self):
2298 parents_recursion_limit = 10
2305 parents_recursion_limit = 10
2299 groups = []
2306 groups = []
2300 if self.parent_group is None:
2307 if self.parent_group is None:
2301 return groups
2308 return groups
2302 cur_gr = self.parent_group
2309 cur_gr = self.parent_group
2303 groups.insert(0, cur_gr)
2310 groups.insert(0, cur_gr)
2304 cnt = 0
2311 cnt = 0
2305 while 1:
2312 while 1:
2306 cnt += 1
2313 cnt += 1
2307 gr = getattr(cur_gr, 'parent_group', None)
2314 gr = getattr(cur_gr, 'parent_group', None)
2308 cur_gr = cur_gr.parent_group
2315 cur_gr = cur_gr.parent_group
2309 if gr is None:
2316 if gr is None:
2310 break
2317 break
2311 if cnt == parents_recursion_limit:
2318 if cnt == parents_recursion_limit:
2312 # this will prevent accidental infinit loops
2319 # this will prevent accidental infinit loops
2313 log.error(('more than %s parents found for group %s, stopping '
2320 log.error(('more than %s parents found for group %s, stopping '
2314 'recursive parent fetching' % (parents_recursion_limit, self)))
2321 'recursive parent fetching' % (parents_recursion_limit, self)))
2315 break
2322 break
2316
2323
2317 groups.insert(0, gr)
2324 groups.insert(0, gr)
2318 return groups
2325 return groups
2319
2326
2320 @property
2327 @property
2321 def children(self):
2328 def children(self):
2322 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2329 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2323
2330
2324 @property
2331 @property
2325 def name(self):
2332 def name(self):
2326 return self.group_name.split(RepoGroup.url_sep())[-1]
2333 return self.group_name.split(RepoGroup.url_sep())[-1]
2327
2334
2328 @property
2335 @property
2329 def full_path(self):
2336 def full_path(self):
2330 return self.group_name
2337 return self.group_name
2331
2338
2332 @property
2339 @property
2333 def full_path_splitted(self):
2340 def full_path_splitted(self):
2334 return self.group_name.split(RepoGroup.url_sep())
2341 return self.group_name.split(RepoGroup.url_sep())
2335
2342
2336 @property
2343 @property
2337 def repositories(self):
2344 def repositories(self):
2338 return Repository.query()\
2345 return Repository.query()\
2339 .filter(Repository.group == self)\
2346 .filter(Repository.group == self)\
2340 .order_by(Repository.repo_name)
2347 .order_by(Repository.repo_name)
2341
2348
2342 @property
2349 @property
2343 def repositories_recursive_count(self):
2350 def repositories_recursive_count(self):
2344 cnt = self.repositories.count()
2351 cnt = self.repositories.count()
2345
2352
2346 def children_count(group):
2353 def children_count(group):
2347 cnt = 0
2354 cnt = 0
2348 for child in group.children:
2355 for child in group.children:
2349 cnt += child.repositories.count()
2356 cnt += child.repositories.count()
2350 cnt += children_count(child)
2357 cnt += children_count(child)
2351 return cnt
2358 return cnt
2352
2359
2353 return cnt + children_count(self)
2360 return cnt + children_count(self)
2354
2361
2355 def _recursive_objects(self, include_repos=True):
2362 def _recursive_objects(self, include_repos=True):
2356 all_ = []
2363 all_ = []
2357
2364
2358 def _get_members(root_gr):
2365 def _get_members(root_gr):
2359 if include_repos:
2366 if include_repos:
2360 for r in root_gr.repositories:
2367 for r in root_gr.repositories:
2361 all_.append(r)
2368 all_.append(r)
2362 childs = root_gr.children.all()
2369 childs = root_gr.children.all()
2363 if childs:
2370 if childs:
2364 for gr in childs:
2371 for gr in childs:
2365 all_.append(gr)
2372 all_.append(gr)
2366 _get_members(gr)
2373 _get_members(gr)
2367
2374
2368 _get_members(self)
2375 _get_members(self)
2369 return [self] + all_
2376 return [self] + all_
2370
2377
2371 def recursive_groups_and_repos(self):
2378 def recursive_groups_and_repos(self):
2372 """
2379 """
2373 Recursive return all groups, with repositories in those groups
2380 Recursive return all groups, with repositories in those groups
2374 """
2381 """
2375 return self._recursive_objects()
2382 return self._recursive_objects()
2376
2383
2377 def recursive_groups(self):
2384 def recursive_groups(self):
2378 """
2385 """
2379 Returns all children groups for this group including children of children
2386 Returns all children groups for this group including children of children
2380 """
2387 """
2381 return self._recursive_objects(include_repos=False)
2388 return self._recursive_objects(include_repos=False)
2382
2389
2383 def get_new_name(self, group_name):
2390 def get_new_name(self, group_name):
2384 """
2391 """
2385 returns new full group name based on parent and new name
2392 returns new full group name based on parent and new name
2386
2393
2387 :param group_name:
2394 :param group_name:
2388 """
2395 """
2389 path_prefix = (self.parent_group.full_path_splitted if
2396 path_prefix = (self.parent_group.full_path_splitted if
2390 self.parent_group else [])
2397 self.parent_group else [])
2391 return RepoGroup.url_sep().join(path_prefix + [group_name])
2398 return RepoGroup.url_sep().join(path_prefix + [group_name])
2392
2399
2393 def permissions(self, with_admins=True, with_owner=True):
2400 def permissions(self, with_admins=True, with_owner=True):
2394 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2401 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2395 q = q.options(joinedload(UserRepoGroupToPerm.group),
2402 q = q.options(joinedload(UserRepoGroupToPerm.group),
2396 joinedload(UserRepoGroupToPerm.user),
2403 joinedload(UserRepoGroupToPerm.user),
2397 joinedload(UserRepoGroupToPerm.permission),)
2404 joinedload(UserRepoGroupToPerm.permission),)
2398
2405
2399 # get owners and admins and permissions. We do a trick of re-writing
2406 # get owners and admins and permissions. We do a trick of re-writing
2400 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2407 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2401 # has a global reference and changing one object propagates to all
2408 # has a global reference and changing one object propagates to all
2402 # others. This means if admin is also an owner admin_row that change
2409 # others. This means if admin is also an owner admin_row that change
2403 # would propagate to both objects
2410 # would propagate to both objects
2404 perm_rows = []
2411 perm_rows = []
2405 for _usr in q.all():
2412 for _usr in q.all():
2406 usr = AttributeDict(_usr.user.get_dict())
2413 usr = AttributeDict(_usr.user.get_dict())
2407 usr.permission = _usr.permission.permission_name
2414 usr.permission = _usr.permission.permission_name
2408 perm_rows.append(usr)
2415 perm_rows.append(usr)
2409
2416
2410 # filter the perm rows by 'default' first and then sort them by
2417 # filter the perm rows by 'default' first and then sort them by
2411 # admin,write,read,none permissions sorted again alphabetically in
2418 # admin,write,read,none permissions sorted again alphabetically in
2412 # each group
2419 # each group
2413 perm_rows = sorted(perm_rows, key=display_sort)
2420 perm_rows = sorted(perm_rows, key=display_sort)
2414
2421
2415 _admin_perm = 'group.admin'
2422 _admin_perm = 'group.admin'
2416 owner_row = []
2423 owner_row = []
2417 if with_owner:
2424 if with_owner:
2418 usr = AttributeDict(self.user.get_dict())
2425 usr = AttributeDict(self.user.get_dict())
2419 usr.owner_row = True
2426 usr.owner_row = True
2420 usr.permission = _admin_perm
2427 usr.permission = _admin_perm
2421 owner_row.append(usr)
2428 owner_row.append(usr)
2422
2429
2423 super_admin_rows = []
2430 super_admin_rows = []
2424 if with_admins:
2431 if with_admins:
2425 for usr in User.get_all_super_admins():
2432 for usr in User.get_all_super_admins():
2426 # if this admin is also owner, don't double the record
2433 # if this admin is also owner, don't double the record
2427 if usr.user_id == owner_row[0].user_id:
2434 if usr.user_id == owner_row[0].user_id:
2428 owner_row[0].admin_row = True
2435 owner_row[0].admin_row = True
2429 else:
2436 else:
2430 usr = AttributeDict(usr.get_dict())
2437 usr = AttributeDict(usr.get_dict())
2431 usr.admin_row = True
2438 usr.admin_row = True
2432 usr.permission = _admin_perm
2439 usr.permission = _admin_perm
2433 super_admin_rows.append(usr)
2440 super_admin_rows.append(usr)
2434
2441
2435 return super_admin_rows + owner_row + perm_rows
2442 return super_admin_rows + owner_row + perm_rows
2436
2443
2437 def permission_user_groups(self):
2444 def permission_user_groups(self):
2438 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2445 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2439 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2446 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2440 joinedload(UserGroupRepoGroupToPerm.users_group),
2447 joinedload(UserGroupRepoGroupToPerm.users_group),
2441 joinedload(UserGroupRepoGroupToPerm.permission),)
2448 joinedload(UserGroupRepoGroupToPerm.permission),)
2442
2449
2443 perm_rows = []
2450 perm_rows = []
2444 for _user_group in q.all():
2451 for _user_group in q.all():
2445 usr = AttributeDict(_user_group.users_group.get_dict())
2452 usr = AttributeDict(_user_group.users_group.get_dict())
2446 usr.permission = _user_group.permission.permission_name
2453 usr.permission = _user_group.permission.permission_name
2447 perm_rows.append(usr)
2454 perm_rows.append(usr)
2448
2455
2449 return perm_rows
2456 return perm_rows
2450
2457
2451 def get_api_data(self):
2458 def get_api_data(self):
2452 """
2459 """
2453 Common function for generating api data
2460 Common function for generating api data
2454
2461
2455 """
2462 """
2456 group = self
2463 group = self
2457 data = {
2464 data = {
2458 'group_id': group.group_id,
2465 'group_id': group.group_id,
2459 'group_name': group.group_name,
2466 'group_name': group.group_name,
2460 'group_description': group.description_safe,
2467 'group_description': group.description_safe,
2461 'parent_group': group.parent_group.group_name if group.parent_group else None,
2468 'parent_group': group.parent_group.group_name if group.parent_group else None,
2462 'repositories': [x.repo_name for x in group.repositories],
2469 'repositories': [x.repo_name for x in group.repositories],
2463 'owner': group.user.username,
2470 'owner': group.user.username,
2464 }
2471 }
2465 return data
2472 return data
2466
2473
2467
2474
2468 class Permission(Base, BaseModel):
2475 class Permission(Base, BaseModel):
2469 __tablename__ = 'permissions'
2476 __tablename__ = 'permissions'
2470 __table_args__ = (
2477 __table_args__ = (
2471 Index('p_perm_name_idx', 'permission_name'),
2478 Index('p_perm_name_idx', 'permission_name'),
2472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2474 )
2481 )
2475 PERMS = [
2482 PERMS = [
2476 ('hg.admin', _('RhodeCode Super Administrator')),
2483 ('hg.admin', _('RhodeCode Super Administrator')),
2477
2484
2478 ('repository.none', _('Repository no access')),
2485 ('repository.none', _('Repository no access')),
2479 ('repository.read', _('Repository read access')),
2486 ('repository.read', _('Repository read access')),
2480 ('repository.write', _('Repository write access')),
2487 ('repository.write', _('Repository write access')),
2481 ('repository.admin', _('Repository admin access')),
2488 ('repository.admin', _('Repository admin access')),
2482
2489
2483 ('group.none', _('Repository group no access')),
2490 ('group.none', _('Repository group no access')),
2484 ('group.read', _('Repository group read access')),
2491 ('group.read', _('Repository group read access')),
2485 ('group.write', _('Repository group write access')),
2492 ('group.write', _('Repository group write access')),
2486 ('group.admin', _('Repository group admin access')),
2493 ('group.admin', _('Repository group admin access')),
2487
2494
2488 ('usergroup.none', _('User group no access')),
2495 ('usergroup.none', _('User group no access')),
2489 ('usergroup.read', _('User group read access')),
2496 ('usergroup.read', _('User group read access')),
2490 ('usergroup.write', _('User group write access')),
2497 ('usergroup.write', _('User group write access')),
2491 ('usergroup.admin', _('User group admin access')),
2498 ('usergroup.admin', _('User group admin access')),
2492
2499
2493 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2500 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2494 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2501 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2495
2502
2496 ('hg.usergroup.create.false', _('User Group creation disabled')),
2503 ('hg.usergroup.create.false', _('User Group creation disabled')),
2497 ('hg.usergroup.create.true', _('User Group creation enabled')),
2504 ('hg.usergroup.create.true', _('User Group creation enabled')),
2498
2505
2499 ('hg.create.none', _('Repository creation disabled')),
2506 ('hg.create.none', _('Repository creation disabled')),
2500 ('hg.create.repository', _('Repository creation enabled')),
2507 ('hg.create.repository', _('Repository creation enabled')),
2501 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2508 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2502 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2509 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2503
2510
2504 ('hg.fork.none', _('Repository forking disabled')),
2511 ('hg.fork.none', _('Repository forking disabled')),
2505 ('hg.fork.repository', _('Repository forking enabled')),
2512 ('hg.fork.repository', _('Repository forking enabled')),
2506
2513
2507 ('hg.register.none', _('Registration disabled')),
2514 ('hg.register.none', _('Registration disabled')),
2508 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2515 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2509 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2516 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2510
2517
2511 ('hg.password_reset.enabled', _('Password reset enabled')),
2518 ('hg.password_reset.enabled', _('Password reset enabled')),
2512 ('hg.password_reset.hidden', _('Password reset hidden')),
2519 ('hg.password_reset.hidden', _('Password reset hidden')),
2513 ('hg.password_reset.disabled', _('Password reset disabled')),
2520 ('hg.password_reset.disabled', _('Password reset disabled')),
2514
2521
2515 ('hg.extern_activate.manual', _('Manual activation of external account')),
2522 ('hg.extern_activate.manual', _('Manual activation of external account')),
2516 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2523 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2517
2524
2518 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2525 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2519 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2526 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2520 ]
2527 ]
2521
2528
2522 # definition of system default permissions for DEFAULT user
2529 # definition of system default permissions for DEFAULT user
2523 DEFAULT_USER_PERMISSIONS = [
2530 DEFAULT_USER_PERMISSIONS = [
2524 'repository.read',
2531 'repository.read',
2525 'group.read',
2532 'group.read',
2526 'usergroup.read',
2533 'usergroup.read',
2527 'hg.create.repository',
2534 'hg.create.repository',
2528 'hg.repogroup.create.false',
2535 'hg.repogroup.create.false',
2529 'hg.usergroup.create.false',
2536 'hg.usergroup.create.false',
2530 'hg.create.write_on_repogroup.true',
2537 'hg.create.write_on_repogroup.true',
2531 'hg.fork.repository',
2538 'hg.fork.repository',
2532 'hg.register.manual_activate',
2539 'hg.register.manual_activate',
2533 'hg.password_reset.enabled',
2540 'hg.password_reset.enabled',
2534 'hg.extern_activate.auto',
2541 'hg.extern_activate.auto',
2535 'hg.inherit_default_perms.true',
2542 'hg.inherit_default_perms.true',
2536 ]
2543 ]
2537
2544
2538 # defines which permissions are more important higher the more important
2545 # defines which permissions are more important higher the more important
2539 # Weight defines which permissions are more important.
2546 # Weight defines which permissions are more important.
2540 # The higher number the more important.
2547 # The higher number the more important.
2541 PERM_WEIGHTS = {
2548 PERM_WEIGHTS = {
2542 'repository.none': 0,
2549 'repository.none': 0,
2543 'repository.read': 1,
2550 'repository.read': 1,
2544 'repository.write': 3,
2551 'repository.write': 3,
2545 'repository.admin': 4,
2552 'repository.admin': 4,
2546
2553
2547 'group.none': 0,
2554 'group.none': 0,
2548 'group.read': 1,
2555 'group.read': 1,
2549 'group.write': 3,
2556 'group.write': 3,
2550 'group.admin': 4,
2557 'group.admin': 4,
2551
2558
2552 'usergroup.none': 0,
2559 'usergroup.none': 0,
2553 'usergroup.read': 1,
2560 'usergroup.read': 1,
2554 'usergroup.write': 3,
2561 'usergroup.write': 3,
2555 'usergroup.admin': 4,
2562 'usergroup.admin': 4,
2556
2563
2557 'hg.repogroup.create.false': 0,
2564 'hg.repogroup.create.false': 0,
2558 'hg.repogroup.create.true': 1,
2565 'hg.repogroup.create.true': 1,
2559
2566
2560 'hg.usergroup.create.false': 0,
2567 'hg.usergroup.create.false': 0,
2561 'hg.usergroup.create.true': 1,
2568 'hg.usergroup.create.true': 1,
2562
2569
2563 'hg.fork.none': 0,
2570 'hg.fork.none': 0,
2564 'hg.fork.repository': 1,
2571 'hg.fork.repository': 1,
2565 'hg.create.none': 0,
2572 'hg.create.none': 0,
2566 'hg.create.repository': 1
2573 'hg.create.repository': 1
2567 }
2574 }
2568
2575
2569 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2576 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2570 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2577 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2571 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2578 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2572
2579
2573 def __unicode__(self):
2580 def __unicode__(self):
2574 return u"<%s('%s:%s')>" % (
2581 return u"<%s('%s:%s')>" % (
2575 self.__class__.__name__, self.permission_id, self.permission_name
2582 self.__class__.__name__, self.permission_id, self.permission_name
2576 )
2583 )
2577
2584
2578 @classmethod
2585 @classmethod
2579 def get_by_key(cls, key):
2586 def get_by_key(cls, key):
2580 return cls.query().filter(cls.permission_name == key).scalar()
2587 return cls.query().filter(cls.permission_name == key).scalar()
2581
2588
2582 @classmethod
2589 @classmethod
2583 def get_default_repo_perms(cls, user_id, repo_id=None):
2590 def get_default_repo_perms(cls, user_id, repo_id=None):
2584 q = Session().query(UserRepoToPerm, Repository, Permission)\
2591 q = Session().query(UserRepoToPerm, Repository, Permission)\
2585 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2592 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2586 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2593 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2587 .filter(UserRepoToPerm.user_id == user_id)
2594 .filter(UserRepoToPerm.user_id == user_id)
2588 if repo_id:
2595 if repo_id:
2589 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2596 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2590 return q.all()
2597 return q.all()
2591
2598
2592 @classmethod
2599 @classmethod
2593 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2600 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2594 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2601 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2595 .join(
2602 .join(
2596 Permission,
2603 Permission,
2597 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2604 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2598 .join(
2605 .join(
2599 Repository,
2606 Repository,
2600 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2607 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2601 .join(
2608 .join(
2602 UserGroup,
2609 UserGroup,
2603 UserGroupRepoToPerm.users_group_id ==
2610 UserGroupRepoToPerm.users_group_id ==
2604 UserGroup.users_group_id)\
2611 UserGroup.users_group_id)\
2605 .join(
2612 .join(
2606 UserGroupMember,
2613 UserGroupMember,
2607 UserGroupRepoToPerm.users_group_id ==
2614 UserGroupRepoToPerm.users_group_id ==
2608 UserGroupMember.users_group_id)\
2615 UserGroupMember.users_group_id)\
2609 .filter(
2616 .filter(
2610 UserGroupMember.user_id == user_id,
2617 UserGroupMember.user_id == user_id,
2611 UserGroup.users_group_active == true())
2618 UserGroup.users_group_active == true())
2612 if repo_id:
2619 if repo_id:
2613 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2620 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2614 return q.all()
2621 return q.all()
2615
2622
2616 @classmethod
2623 @classmethod
2617 def get_default_group_perms(cls, user_id, repo_group_id=None):
2624 def get_default_group_perms(cls, user_id, repo_group_id=None):
2618 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2625 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2619 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2626 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2620 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2627 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2621 .filter(UserRepoGroupToPerm.user_id == user_id)
2628 .filter(UserRepoGroupToPerm.user_id == user_id)
2622 if repo_group_id:
2629 if repo_group_id:
2623 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2630 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2624 return q.all()
2631 return q.all()
2625
2632
2626 @classmethod
2633 @classmethod
2627 def get_default_group_perms_from_user_group(
2634 def get_default_group_perms_from_user_group(
2628 cls, user_id, repo_group_id=None):
2635 cls, user_id, repo_group_id=None):
2629 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2636 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2630 .join(
2637 .join(
2631 Permission,
2638 Permission,
2632 UserGroupRepoGroupToPerm.permission_id ==
2639 UserGroupRepoGroupToPerm.permission_id ==
2633 Permission.permission_id)\
2640 Permission.permission_id)\
2634 .join(
2641 .join(
2635 RepoGroup,
2642 RepoGroup,
2636 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2643 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2637 .join(
2644 .join(
2638 UserGroup,
2645 UserGroup,
2639 UserGroupRepoGroupToPerm.users_group_id ==
2646 UserGroupRepoGroupToPerm.users_group_id ==
2640 UserGroup.users_group_id)\
2647 UserGroup.users_group_id)\
2641 .join(
2648 .join(
2642 UserGroupMember,
2649 UserGroupMember,
2643 UserGroupRepoGroupToPerm.users_group_id ==
2650 UserGroupRepoGroupToPerm.users_group_id ==
2644 UserGroupMember.users_group_id)\
2651 UserGroupMember.users_group_id)\
2645 .filter(
2652 .filter(
2646 UserGroupMember.user_id == user_id,
2653 UserGroupMember.user_id == user_id,
2647 UserGroup.users_group_active == true())
2654 UserGroup.users_group_active == true())
2648 if repo_group_id:
2655 if repo_group_id:
2649 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2656 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2650 return q.all()
2657 return q.all()
2651
2658
2652 @classmethod
2659 @classmethod
2653 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2660 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2654 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2661 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2655 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2662 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2656 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2663 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2657 .filter(UserUserGroupToPerm.user_id == user_id)
2664 .filter(UserUserGroupToPerm.user_id == user_id)
2658 if user_group_id:
2665 if user_group_id:
2659 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2666 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2660 return q.all()
2667 return q.all()
2661
2668
2662 @classmethod
2669 @classmethod
2663 def get_default_user_group_perms_from_user_group(
2670 def get_default_user_group_perms_from_user_group(
2664 cls, user_id, user_group_id=None):
2671 cls, user_id, user_group_id=None):
2665 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2672 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2666 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2673 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2667 .join(
2674 .join(
2668 Permission,
2675 Permission,
2669 UserGroupUserGroupToPerm.permission_id ==
2676 UserGroupUserGroupToPerm.permission_id ==
2670 Permission.permission_id)\
2677 Permission.permission_id)\
2671 .join(
2678 .join(
2672 TargetUserGroup,
2679 TargetUserGroup,
2673 UserGroupUserGroupToPerm.target_user_group_id ==
2680 UserGroupUserGroupToPerm.target_user_group_id ==
2674 TargetUserGroup.users_group_id)\
2681 TargetUserGroup.users_group_id)\
2675 .join(
2682 .join(
2676 UserGroup,
2683 UserGroup,
2677 UserGroupUserGroupToPerm.user_group_id ==
2684 UserGroupUserGroupToPerm.user_group_id ==
2678 UserGroup.users_group_id)\
2685 UserGroup.users_group_id)\
2679 .join(
2686 .join(
2680 UserGroupMember,
2687 UserGroupMember,
2681 UserGroupUserGroupToPerm.user_group_id ==
2688 UserGroupUserGroupToPerm.user_group_id ==
2682 UserGroupMember.users_group_id)\
2689 UserGroupMember.users_group_id)\
2683 .filter(
2690 .filter(
2684 UserGroupMember.user_id == user_id,
2691 UserGroupMember.user_id == user_id,
2685 UserGroup.users_group_active == true())
2692 UserGroup.users_group_active == true())
2686 if user_group_id:
2693 if user_group_id:
2687 q = q.filter(
2694 q = q.filter(
2688 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2695 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2689
2696
2690 return q.all()
2697 return q.all()
2691
2698
2692
2699
2693 class UserRepoToPerm(Base, BaseModel):
2700 class UserRepoToPerm(Base, BaseModel):
2694 __tablename__ = 'repo_to_perm'
2701 __tablename__ = 'repo_to_perm'
2695 __table_args__ = (
2702 __table_args__ = (
2696 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2703 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2697 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2704 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2698 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2705 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2699 )
2706 )
2700 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2707 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2701 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2708 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2702 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2709 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2703 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2710 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2704
2711
2705 user = relationship('User')
2712 user = relationship('User')
2706 repository = relationship('Repository')
2713 repository = relationship('Repository')
2707 permission = relationship('Permission')
2714 permission = relationship('Permission')
2708
2715
2709 @classmethod
2716 @classmethod
2710 def create(cls, user, repository, permission):
2717 def create(cls, user, repository, permission):
2711 n = cls()
2718 n = cls()
2712 n.user = user
2719 n.user = user
2713 n.repository = repository
2720 n.repository = repository
2714 n.permission = permission
2721 n.permission = permission
2715 Session().add(n)
2722 Session().add(n)
2716 return n
2723 return n
2717
2724
2718 def __unicode__(self):
2725 def __unicode__(self):
2719 return u'<%s => %s >' % (self.user, self.repository)
2726 return u'<%s => %s >' % (self.user, self.repository)
2720
2727
2721
2728
2722 class UserUserGroupToPerm(Base, BaseModel):
2729 class UserUserGroupToPerm(Base, BaseModel):
2723 __tablename__ = 'user_user_group_to_perm'
2730 __tablename__ = 'user_user_group_to_perm'
2724 __table_args__ = (
2731 __table_args__ = (
2725 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2732 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2726 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2733 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2727 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2734 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2728 )
2735 )
2729 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2736 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2730 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2737 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2731 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2738 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2732 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2739 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2733
2740
2734 user = relationship('User')
2741 user = relationship('User')
2735 user_group = relationship('UserGroup')
2742 user_group = relationship('UserGroup')
2736 permission = relationship('Permission')
2743 permission = relationship('Permission')
2737
2744
2738 @classmethod
2745 @classmethod
2739 def create(cls, user, user_group, permission):
2746 def create(cls, user, user_group, permission):
2740 n = cls()
2747 n = cls()
2741 n.user = user
2748 n.user = user
2742 n.user_group = user_group
2749 n.user_group = user_group
2743 n.permission = permission
2750 n.permission = permission
2744 Session().add(n)
2751 Session().add(n)
2745 return n
2752 return n
2746
2753
2747 def __unicode__(self):
2754 def __unicode__(self):
2748 return u'<%s => %s >' % (self.user, self.user_group)
2755 return u'<%s => %s >' % (self.user, self.user_group)
2749
2756
2750
2757
2751 class UserToPerm(Base, BaseModel):
2758 class UserToPerm(Base, BaseModel):
2752 __tablename__ = 'user_to_perm'
2759 __tablename__ = 'user_to_perm'
2753 __table_args__ = (
2760 __table_args__ = (
2754 UniqueConstraint('user_id', 'permission_id'),
2761 UniqueConstraint('user_id', 'permission_id'),
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2762 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2763 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2757 )
2764 )
2758 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2765 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2759 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2766 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2760 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2767 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2761
2768
2762 user = relationship('User')
2769 user = relationship('User')
2763 permission = relationship('Permission', lazy='joined')
2770 permission = relationship('Permission', lazy='joined')
2764
2771
2765 def __unicode__(self):
2772 def __unicode__(self):
2766 return u'<%s => %s >' % (self.user, self.permission)
2773 return u'<%s => %s >' % (self.user, self.permission)
2767
2774
2768
2775
2769 class UserGroupRepoToPerm(Base, BaseModel):
2776 class UserGroupRepoToPerm(Base, BaseModel):
2770 __tablename__ = 'users_group_repo_to_perm'
2777 __tablename__ = 'users_group_repo_to_perm'
2771 __table_args__ = (
2778 __table_args__ = (
2772 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2779 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2773 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2774 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2775 )
2782 )
2776 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2783 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2777 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2784 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2778 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2785 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2779 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2786 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2780
2787
2781 users_group = relationship('UserGroup')
2788 users_group = relationship('UserGroup')
2782 permission = relationship('Permission')
2789 permission = relationship('Permission')
2783 repository = relationship('Repository')
2790 repository = relationship('Repository')
2784
2791
2785 @classmethod
2792 @classmethod
2786 def create(cls, users_group, repository, permission):
2793 def create(cls, users_group, repository, permission):
2787 n = cls()
2794 n = cls()
2788 n.users_group = users_group
2795 n.users_group = users_group
2789 n.repository = repository
2796 n.repository = repository
2790 n.permission = permission
2797 n.permission = permission
2791 Session().add(n)
2798 Session().add(n)
2792 return n
2799 return n
2793
2800
2794 def __unicode__(self):
2801 def __unicode__(self):
2795 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2802 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2796
2803
2797
2804
2798 class UserGroupUserGroupToPerm(Base, BaseModel):
2805 class UserGroupUserGroupToPerm(Base, BaseModel):
2799 __tablename__ = 'user_group_user_group_to_perm'
2806 __tablename__ = 'user_group_user_group_to_perm'
2800 __table_args__ = (
2807 __table_args__ = (
2801 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2808 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2802 CheckConstraint('target_user_group_id != user_group_id'),
2809 CheckConstraint('target_user_group_id != user_group_id'),
2803 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2804 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2811 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2805 )
2812 )
2806 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)
2813 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)
2807 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2814 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2808 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2815 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2809 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2816 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2810
2817
2811 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2818 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2812 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2819 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2813 permission = relationship('Permission')
2820 permission = relationship('Permission')
2814
2821
2815 @classmethod
2822 @classmethod
2816 def create(cls, target_user_group, user_group, permission):
2823 def create(cls, target_user_group, user_group, permission):
2817 n = cls()
2824 n = cls()
2818 n.target_user_group = target_user_group
2825 n.target_user_group = target_user_group
2819 n.user_group = user_group
2826 n.user_group = user_group
2820 n.permission = permission
2827 n.permission = permission
2821 Session().add(n)
2828 Session().add(n)
2822 return n
2829 return n
2823
2830
2824 def __unicode__(self):
2831 def __unicode__(self):
2825 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2832 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2826
2833
2827
2834
2828 class UserGroupToPerm(Base, BaseModel):
2835 class UserGroupToPerm(Base, BaseModel):
2829 __tablename__ = 'users_group_to_perm'
2836 __tablename__ = 'users_group_to_perm'
2830 __table_args__ = (
2837 __table_args__ = (
2831 UniqueConstraint('users_group_id', 'permission_id',),
2838 UniqueConstraint('users_group_id', 'permission_id',),
2832 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2833 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2834 )
2841 )
2835 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2842 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2836 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2843 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2837 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2844 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2838
2845
2839 users_group = relationship('UserGroup')
2846 users_group = relationship('UserGroup')
2840 permission = relationship('Permission')
2847 permission = relationship('Permission')
2841
2848
2842
2849
2843 class UserRepoGroupToPerm(Base, BaseModel):
2850 class UserRepoGroupToPerm(Base, BaseModel):
2844 __tablename__ = 'user_repo_group_to_perm'
2851 __tablename__ = 'user_repo_group_to_perm'
2845 __table_args__ = (
2852 __table_args__ = (
2846 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2853 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2848 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2849 )
2856 )
2850
2857
2851 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2858 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2852 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2853 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2860 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2855
2862
2856 user = relationship('User')
2863 user = relationship('User')
2857 group = relationship('RepoGroup')
2864 group = relationship('RepoGroup')
2858 permission = relationship('Permission')
2865 permission = relationship('Permission')
2859
2866
2860 @classmethod
2867 @classmethod
2861 def create(cls, user, repository_group, permission):
2868 def create(cls, user, repository_group, permission):
2862 n = cls()
2869 n = cls()
2863 n.user = user
2870 n.user = user
2864 n.group = repository_group
2871 n.group = repository_group
2865 n.permission = permission
2872 n.permission = permission
2866 Session().add(n)
2873 Session().add(n)
2867 return n
2874 return n
2868
2875
2869
2876
2870 class UserGroupRepoGroupToPerm(Base, BaseModel):
2877 class UserGroupRepoGroupToPerm(Base, BaseModel):
2871 __tablename__ = 'users_group_repo_group_to_perm'
2878 __tablename__ = 'users_group_repo_group_to_perm'
2872 __table_args__ = (
2879 __table_args__ = (
2873 UniqueConstraint('users_group_id', 'group_id'),
2880 UniqueConstraint('users_group_id', 'group_id'),
2874 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2875 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2876 )
2883 )
2877
2884
2878 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)
2885 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)
2879 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2886 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2880 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2887 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2881 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2882
2889
2883 users_group = relationship('UserGroup')
2890 users_group = relationship('UserGroup')
2884 permission = relationship('Permission')
2891 permission = relationship('Permission')
2885 group = relationship('RepoGroup')
2892 group = relationship('RepoGroup')
2886
2893
2887 @classmethod
2894 @classmethod
2888 def create(cls, user_group, repository_group, permission):
2895 def create(cls, user_group, repository_group, permission):
2889 n = cls()
2896 n = cls()
2890 n.users_group = user_group
2897 n.users_group = user_group
2891 n.group = repository_group
2898 n.group = repository_group
2892 n.permission = permission
2899 n.permission = permission
2893 Session().add(n)
2900 Session().add(n)
2894 return n
2901 return n
2895
2902
2896 def __unicode__(self):
2903 def __unicode__(self):
2897 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2904 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2898
2905
2899
2906
2900 class Statistics(Base, BaseModel):
2907 class Statistics(Base, BaseModel):
2901 __tablename__ = 'statistics'
2908 __tablename__ = 'statistics'
2902 __table_args__ = (
2909 __table_args__ = (
2903 UniqueConstraint('repository_id'),
2910 UniqueConstraint('repository_id'),
2904 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2911 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2905 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2912 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2906 )
2913 )
2907 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2914 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2908 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2909 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2916 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2910 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2917 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2911 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2918 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2912 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2919 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2913
2920
2914 repository = relationship('Repository', single_parent=True)
2921 repository = relationship('Repository', single_parent=True)
2915
2922
2916
2923
2917 class UserFollowing(Base, BaseModel):
2924 class UserFollowing(Base, BaseModel):
2918 __tablename__ = 'user_followings'
2925 __tablename__ = 'user_followings'
2919 __table_args__ = (
2926 __table_args__ = (
2920 UniqueConstraint('user_id', 'follows_repository_id'),
2927 UniqueConstraint('user_id', 'follows_repository_id'),
2921 UniqueConstraint('user_id', 'follows_user_id'),
2928 UniqueConstraint('user_id', 'follows_user_id'),
2922 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2923 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2924 )
2931 )
2925
2932
2926 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2933 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2927 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2934 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2928 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2935 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2929 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2936 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2930 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2937 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2931
2938
2932 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2939 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2933
2940
2934 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2941 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2935 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2942 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2936
2943
2937 @classmethod
2944 @classmethod
2938 def get_repo_followers(cls, repo_id):
2945 def get_repo_followers(cls, repo_id):
2939 return cls.query().filter(cls.follows_repo_id == repo_id)
2946 return cls.query().filter(cls.follows_repo_id == repo_id)
2940
2947
2941
2948
2942 class CacheKey(Base, BaseModel):
2949 class CacheKey(Base, BaseModel):
2943 __tablename__ = 'cache_invalidation'
2950 __tablename__ = 'cache_invalidation'
2944 __table_args__ = (
2951 __table_args__ = (
2945 UniqueConstraint('cache_key'),
2952 UniqueConstraint('cache_key'),
2946 Index('key_idx', 'cache_key'),
2953 Index('key_idx', 'cache_key'),
2947 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2954 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2948 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2955 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2949 )
2956 )
2950 CACHE_TYPE_ATOM = 'ATOM'
2957 CACHE_TYPE_ATOM = 'ATOM'
2951 CACHE_TYPE_RSS = 'RSS'
2958 CACHE_TYPE_RSS = 'RSS'
2952 CACHE_TYPE_README = 'README'
2959 CACHE_TYPE_README = 'README'
2953
2960
2954 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2961 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2955 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2962 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2956 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2963 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2957 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2964 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2958
2965
2959 def __init__(self, cache_key, cache_args=''):
2966 def __init__(self, cache_key, cache_args=''):
2960 self.cache_key = cache_key
2967 self.cache_key = cache_key
2961 self.cache_args = cache_args
2968 self.cache_args = cache_args
2962 self.cache_active = False
2969 self.cache_active = False
2963
2970
2964 def __unicode__(self):
2971 def __unicode__(self):
2965 return u"<%s('%s:%s[%s]')>" % (
2972 return u"<%s('%s:%s[%s]')>" % (
2966 self.__class__.__name__,
2973 self.__class__.__name__,
2967 self.cache_id, self.cache_key, self.cache_active)
2974 self.cache_id, self.cache_key, self.cache_active)
2968
2975
2969 def _cache_key_partition(self):
2976 def _cache_key_partition(self):
2970 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2977 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2971 return prefix, repo_name, suffix
2978 return prefix, repo_name, suffix
2972
2979
2973 def get_prefix(self):
2980 def get_prefix(self):
2974 """
2981 """
2975 Try to extract prefix from existing cache key. The key could consist
2982 Try to extract prefix from existing cache key. The key could consist
2976 of prefix, repo_name, suffix
2983 of prefix, repo_name, suffix
2977 """
2984 """
2978 # this returns prefix, repo_name, suffix
2985 # this returns prefix, repo_name, suffix
2979 return self._cache_key_partition()[0]
2986 return self._cache_key_partition()[0]
2980
2987
2981 def get_suffix(self):
2988 def get_suffix(self):
2982 """
2989 """
2983 get suffix that might have been used in _get_cache_key to
2990 get suffix that might have been used in _get_cache_key to
2984 generate self.cache_key. Only used for informational purposes
2991 generate self.cache_key. Only used for informational purposes
2985 in repo_edit.mako.
2992 in repo_edit.mako.
2986 """
2993 """
2987 # prefix, repo_name, suffix
2994 # prefix, repo_name, suffix
2988 return self._cache_key_partition()[2]
2995 return self._cache_key_partition()[2]
2989
2996
2990 @classmethod
2997 @classmethod
2991 def delete_all_cache(cls):
2998 def delete_all_cache(cls):
2992 """
2999 """
2993 Delete all cache keys from database.
3000 Delete all cache keys from database.
2994 Should only be run when all instances are down and all entries
3001 Should only be run when all instances are down and all entries
2995 thus stale.
3002 thus stale.
2996 """
3003 """
2997 cls.query().delete()
3004 cls.query().delete()
2998 Session().commit()
3005 Session().commit()
2999
3006
3000 @classmethod
3007 @classmethod
3001 def get_cache_key(cls, repo_name, cache_type):
3008 def get_cache_key(cls, repo_name, cache_type):
3002 """
3009 """
3003
3010
3004 Generate a cache key for this process of RhodeCode instance.
3011 Generate a cache key for this process of RhodeCode instance.
3005 Prefix most likely will be process id or maybe explicitly set
3012 Prefix most likely will be process id or maybe explicitly set
3006 instance_id from .ini file.
3013 instance_id from .ini file.
3007 """
3014 """
3008 import rhodecode
3015 import rhodecode
3009 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3016 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3010
3017
3011 repo_as_unicode = safe_unicode(repo_name)
3018 repo_as_unicode = safe_unicode(repo_name)
3012 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3019 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3013 if cache_type else repo_as_unicode
3020 if cache_type else repo_as_unicode
3014
3021
3015 return u'{}{}'.format(prefix, key)
3022 return u'{}{}'.format(prefix, key)
3016
3023
3017 @classmethod
3024 @classmethod
3018 def set_invalidate(cls, repo_name, delete=False):
3025 def set_invalidate(cls, repo_name, delete=False):
3019 """
3026 """
3020 Mark all caches of a repo as invalid in the database.
3027 Mark all caches of a repo as invalid in the database.
3021 """
3028 """
3022
3029
3023 try:
3030 try:
3024 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3031 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3025 if delete:
3032 if delete:
3026 log.debug('cache objects deleted for repo %s',
3033 log.debug('cache objects deleted for repo %s',
3027 safe_str(repo_name))
3034 safe_str(repo_name))
3028 qry.delete()
3035 qry.delete()
3029 else:
3036 else:
3030 log.debug('cache objects marked as invalid for repo %s',
3037 log.debug('cache objects marked as invalid for repo %s',
3031 safe_str(repo_name))
3038 safe_str(repo_name))
3032 qry.update({"cache_active": False})
3039 qry.update({"cache_active": False})
3033
3040
3034 Session().commit()
3041 Session().commit()
3035 except Exception:
3042 except Exception:
3036 log.exception(
3043 log.exception(
3037 'Cache key invalidation failed for repository %s',
3044 'Cache key invalidation failed for repository %s',
3038 safe_str(repo_name))
3045 safe_str(repo_name))
3039 Session().rollback()
3046 Session().rollback()
3040
3047
3041 @classmethod
3048 @classmethod
3042 def get_active_cache(cls, cache_key):
3049 def get_active_cache(cls, cache_key):
3043 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3050 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3044 if inv_obj:
3051 if inv_obj:
3045 return inv_obj
3052 return inv_obj
3046 return None
3053 return None
3047
3054
3048 @classmethod
3055 @classmethod
3049 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3056 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3050 thread_scoped=False):
3057 thread_scoped=False):
3051 """
3058 """
3052 @cache_region('long_term')
3059 @cache_region('long_term')
3053 def _heavy_calculation(cache_key):
3060 def _heavy_calculation(cache_key):
3054 return 'result'
3061 return 'result'
3055
3062
3056 cache_context = CacheKey.repo_context_cache(
3063 cache_context = CacheKey.repo_context_cache(
3057 _heavy_calculation, repo_name, cache_type)
3064 _heavy_calculation, repo_name, cache_type)
3058
3065
3059 with cache_context as context:
3066 with cache_context as context:
3060 context.invalidate()
3067 context.invalidate()
3061 computed = context.compute()
3068 computed = context.compute()
3062
3069
3063 assert computed == 'result'
3070 assert computed == 'result'
3064 """
3071 """
3065 from rhodecode.lib import caches
3072 from rhodecode.lib import caches
3066 return caches.InvalidationContext(
3073 return caches.InvalidationContext(
3067 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3074 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3068
3075
3069
3076
3070 class ChangesetComment(Base, BaseModel):
3077 class ChangesetComment(Base, BaseModel):
3071 __tablename__ = 'changeset_comments'
3078 __tablename__ = 'changeset_comments'
3072 __table_args__ = (
3079 __table_args__ = (
3073 Index('cc_revision_idx', 'revision'),
3080 Index('cc_revision_idx', 'revision'),
3074 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3075 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3076 )
3083 )
3077
3084
3078 COMMENT_OUTDATED = u'comment_outdated'
3085 COMMENT_OUTDATED = u'comment_outdated'
3079 COMMENT_TYPE_NOTE = u'note'
3086 COMMENT_TYPE_NOTE = u'note'
3080 COMMENT_TYPE_TODO = u'todo'
3087 COMMENT_TYPE_TODO = u'todo'
3081 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3088 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3082
3089
3083 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3090 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3084 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3091 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3085 revision = Column('revision', String(40), nullable=True)
3092 revision = Column('revision', String(40), nullable=True)
3086 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3093 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3087 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3094 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3088 line_no = Column('line_no', Unicode(10), nullable=True)
3095 line_no = Column('line_no', Unicode(10), nullable=True)
3089 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3096 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3090 f_path = Column('f_path', Unicode(1000), nullable=True)
3097 f_path = Column('f_path', Unicode(1000), nullable=True)
3091 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3098 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3092 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3099 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3093 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3100 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3094 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3101 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3095 renderer = Column('renderer', Unicode(64), nullable=True)
3102 renderer = Column('renderer', Unicode(64), nullable=True)
3096 display_state = Column('display_state', Unicode(128), nullable=True)
3103 display_state = Column('display_state', Unicode(128), nullable=True)
3097
3104
3098 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3105 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3099 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3106 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3100 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3107 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3101 author = relationship('User', lazy='joined')
3108 author = relationship('User', lazy='joined')
3102 repo = relationship('Repository')
3109 repo = relationship('Repository')
3103 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3110 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3104 pull_request = relationship('PullRequest', lazy='joined')
3111 pull_request = relationship('PullRequest', lazy='joined')
3105 pull_request_version = relationship('PullRequestVersion')
3112 pull_request_version = relationship('PullRequestVersion')
3106
3113
3107 @classmethod
3114 @classmethod
3108 def get_users(cls, revision=None, pull_request_id=None):
3115 def get_users(cls, revision=None, pull_request_id=None):
3109 """
3116 """
3110 Returns user associated with this ChangesetComment. ie those
3117 Returns user associated with this ChangesetComment. ie those
3111 who actually commented
3118 who actually commented
3112
3119
3113 :param cls:
3120 :param cls:
3114 :param revision:
3121 :param revision:
3115 """
3122 """
3116 q = Session().query(User)\
3123 q = Session().query(User)\
3117 .join(ChangesetComment.author)
3124 .join(ChangesetComment.author)
3118 if revision:
3125 if revision:
3119 q = q.filter(cls.revision == revision)
3126 q = q.filter(cls.revision == revision)
3120 elif pull_request_id:
3127 elif pull_request_id:
3121 q = q.filter(cls.pull_request_id == pull_request_id)
3128 q = q.filter(cls.pull_request_id == pull_request_id)
3122 return q.all()
3129 return q.all()
3123
3130
3124 @classmethod
3131 @classmethod
3125 def get_index_from_version(cls, pr_version, versions):
3132 def get_index_from_version(cls, pr_version, versions):
3126 num_versions = [x.pull_request_version_id for x in versions]
3133 num_versions = [x.pull_request_version_id for x in versions]
3127 try:
3134 try:
3128 return num_versions.index(pr_version) +1
3135 return num_versions.index(pr_version) +1
3129 except (IndexError, ValueError):
3136 except (IndexError, ValueError):
3130 return
3137 return
3131
3138
3132 @property
3139 @property
3133 def outdated(self):
3140 def outdated(self):
3134 return self.display_state == self.COMMENT_OUTDATED
3141 return self.display_state == self.COMMENT_OUTDATED
3135
3142
3136 def outdated_at_version(self, version):
3143 def outdated_at_version(self, version):
3137 """
3144 """
3138 Checks if comment is outdated for given pull request version
3145 Checks if comment is outdated for given pull request version
3139 """
3146 """
3140 return self.outdated and self.pull_request_version_id != version
3147 return self.outdated and self.pull_request_version_id != version
3141
3148
3142 def older_than_version(self, version):
3149 def older_than_version(self, version):
3143 """
3150 """
3144 Checks if comment is made from previous version than given
3151 Checks if comment is made from previous version than given
3145 """
3152 """
3146 if version is None:
3153 if version is None:
3147 return self.pull_request_version_id is not None
3154 return self.pull_request_version_id is not None
3148
3155
3149 return self.pull_request_version_id < version
3156 return self.pull_request_version_id < version
3150
3157
3151 @property
3158 @property
3152 def resolved(self):
3159 def resolved(self):
3153 return self.resolved_by[0] if self.resolved_by else None
3160 return self.resolved_by[0] if self.resolved_by else None
3154
3161
3155 @property
3162 @property
3156 def is_todo(self):
3163 def is_todo(self):
3157 return self.comment_type == self.COMMENT_TYPE_TODO
3164 return self.comment_type == self.COMMENT_TYPE_TODO
3158
3165
3159 @property
3166 @property
3160 def is_inline(self):
3167 def is_inline(self):
3161 return self.line_no and self.f_path
3168 return self.line_no and self.f_path
3162
3169
3163 def get_index_version(self, versions):
3170 def get_index_version(self, versions):
3164 return self.get_index_from_version(
3171 return self.get_index_from_version(
3165 self.pull_request_version_id, versions)
3172 self.pull_request_version_id, versions)
3166
3173
3167 def __repr__(self):
3174 def __repr__(self):
3168 if self.comment_id:
3175 if self.comment_id:
3169 return '<DB:Comment #%s>' % self.comment_id
3176 return '<DB:Comment #%s>' % self.comment_id
3170 else:
3177 else:
3171 return '<DB:Comment at %#x>' % id(self)
3178 return '<DB:Comment at %#x>' % id(self)
3172
3179
3173 def get_api_data(self):
3180 def get_api_data(self):
3174 comment = self
3181 comment = self
3175 data = {
3182 data = {
3176 'comment_id': comment.comment_id,
3183 'comment_id': comment.comment_id,
3177 'comment_type': comment.comment_type,
3184 'comment_type': comment.comment_type,
3178 'comment_text': comment.text,
3185 'comment_text': comment.text,
3179 'comment_status': comment.status_change,
3186 'comment_status': comment.status_change,
3180 'comment_f_path': comment.f_path,
3187 'comment_f_path': comment.f_path,
3181 'comment_lineno': comment.line_no,
3188 'comment_lineno': comment.line_no,
3182 'comment_author': comment.author,
3189 'comment_author': comment.author,
3183 'comment_created_on': comment.created_on
3190 'comment_created_on': comment.created_on
3184 }
3191 }
3185 return data
3192 return data
3186
3193
3187 def __json__(self):
3194 def __json__(self):
3188 data = dict()
3195 data = dict()
3189 data.update(self.get_api_data())
3196 data.update(self.get_api_data())
3190 return data
3197 return data
3191
3198
3192
3199
3193 class ChangesetStatus(Base, BaseModel):
3200 class ChangesetStatus(Base, BaseModel):
3194 __tablename__ = 'changeset_statuses'
3201 __tablename__ = 'changeset_statuses'
3195 __table_args__ = (
3202 __table_args__ = (
3196 Index('cs_revision_idx', 'revision'),
3203 Index('cs_revision_idx', 'revision'),
3197 Index('cs_version_idx', 'version'),
3204 Index('cs_version_idx', 'version'),
3198 UniqueConstraint('repo_id', 'revision', 'version'),
3205 UniqueConstraint('repo_id', 'revision', 'version'),
3199 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3206 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3200 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3207 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3201 )
3208 )
3202 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3209 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3203 STATUS_APPROVED = 'approved'
3210 STATUS_APPROVED = 'approved'
3204 STATUS_REJECTED = 'rejected'
3211 STATUS_REJECTED = 'rejected'
3205 STATUS_UNDER_REVIEW = 'under_review'
3212 STATUS_UNDER_REVIEW = 'under_review'
3206
3213
3207 STATUSES = [
3214 STATUSES = [
3208 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3215 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3209 (STATUS_APPROVED, _("Approved")),
3216 (STATUS_APPROVED, _("Approved")),
3210 (STATUS_REJECTED, _("Rejected")),
3217 (STATUS_REJECTED, _("Rejected")),
3211 (STATUS_UNDER_REVIEW, _("Under Review")),
3218 (STATUS_UNDER_REVIEW, _("Under Review")),
3212 ]
3219 ]
3213
3220
3214 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3221 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3215 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3222 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3216 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3223 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3217 revision = Column('revision', String(40), nullable=False)
3224 revision = Column('revision', String(40), nullable=False)
3218 status = Column('status', String(128), nullable=False, default=DEFAULT)
3225 status = Column('status', String(128), nullable=False, default=DEFAULT)
3219 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3226 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3220 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3227 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3221 version = Column('version', Integer(), nullable=False, default=0)
3228 version = Column('version', Integer(), nullable=False, default=0)
3222 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3229 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3223
3230
3224 author = relationship('User', lazy='joined')
3231 author = relationship('User', lazy='joined')
3225 repo = relationship('Repository')
3232 repo = relationship('Repository')
3226 comment = relationship('ChangesetComment', lazy='joined')
3233 comment = relationship('ChangesetComment', lazy='joined')
3227 pull_request = relationship('PullRequest', lazy='joined')
3234 pull_request = relationship('PullRequest', lazy='joined')
3228
3235
3229 def __unicode__(self):
3236 def __unicode__(self):
3230 return u"<%s('%s[v%s]:%s')>" % (
3237 return u"<%s('%s[v%s]:%s')>" % (
3231 self.__class__.__name__,
3238 self.__class__.__name__,
3232 self.status, self.version, self.author
3239 self.status, self.version, self.author
3233 )
3240 )
3234
3241
3235 @classmethod
3242 @classmethod
3236 def get_status_lbl(cls, value):
3243 def get_status_lbl(cls, value):
3237 return dict(cls.STATUSES).get(value)
3244 return dict(cls.STATUSES).get(value)
3238
3245
3239 @property
3246 @property
3240 def status_lbl(self):
3247 def status_lbl(self):
3241 return ChangesetStatus.get_status_lbl(self.status)
3248 return ChangesetStatus.get_status_lbl(self.status)
3242
3249
3243 def get_api_data(self):
3250 def get_api_data(self):
3244 status = self
3251 status = self
3245 data = {
3252 data = {
3246 'status_id': status.changeset_status_id,
3253 'status_id': status.changeset_status_id,
3247 'status': status.status,
3254 'status': status.status,
3248 }
3255 }
3249 return data
3256 return data
3250
3257
3251 def __json__(self):
3258 def __json__(self):
3252 data = dict()
3259 data = dict()
3253 data.update(self.get_api_data())
3260 data.update(self.get_api_data())
3254 return data
3261 return data
3255
3262
3256
3263
3257 class _PullRequestBase(BaseModel):
3264 class _PullRequestBase(BaseModel):
3258 """
3265 """
3259 Common attributes of pull request and version entries.
3266 Common attributes of pull request and version entries.
3260 """
3267 """
3261
3268
3262 # .status values
3269 # .status values
3263 STATUS_NEW = u'new'
3270 STATUS_NEW = u'new'
3264 STATUS_OPEN = u'open'
3271 STATUS_OPEN = u'open'
3265 STATUS_CLOSED = u'closed'
3272 STATUS_CLOSED = u'closed'
3266
3273
3267 title = Column('title', Unicode(255), nullable=True)
3274 title = Column('title', Unicode(255), nullable=True)
3268 description = Column(
3275 description = Column(
3269 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3276 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3270 nullable=True)
3277 nullable=True)
3271 # new/open/closed status of pull request (not approve/reject/etc)
3278 # new/open/closed status of pull request (not approve/reject/etc)
3272 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3279 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3273 created_on = Column(
3280 created_on = Column(
3274 'created_on', DateTime(timezone=False), nullable=False,
3281 'created_on', DateTime(timezone=False), nullable=False,
3275 default=datetime.datetime.now)
3282 default=datetime.datetime.now)
3276 updated_on = Column(
3283 updated_on = Column(
3277 'updated_on', DateTime(timezone=False), nullable=False,
3284 'updated_on', DateTime(timezone=False), nullable=False,
3278 default=datetime.datetime.now)
3285 default=datetime.datetime.now)
3279
3286
3280 @declared_attr
3287 @declared_attr
3281 def user_id(cls):
3288 def user_id(cls):
3282 return Column(
3289 return Column(
3283 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3290 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3284 unique=None)
3291 unique=None)
3285
3292
3286 # 500 revisions max
3293 # 500 revisions max
3287 _revisions = Column(
3294 _revisions = Column(
3288 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3295 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3289
3296
3290 @declared_attr
3297 @declared_attr
3291 def source_repo_id(cls):
3298 def source_repo_id(cls):
3292 # TODO: dan: rename column to source_repo_id
3299 # TODO: dan: rename column to source_repo_id
3293 return Column(
3300 return Column(
3294 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3301 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3295 nullable=False)
3302 nullable=False)
3296
3303
3297 source_ref = Column('org_ref', Unicode(255), nullable=False)
3304 source_ref = Column('org_ref', Unicode(255), nullable=False)
3298
3305
3299 @declared_attr
3306 @declared_attr
3300 def target_repo_id(cls):
3307 def target_repo_id(cls):
3301 # TODO: dan: rename column to target_repo_id
3308 # TODO: dan: rename column to target_repo_id
3302 return Column(
3309 return Column(
3303 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3310 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3304 nullable=False)
3311 nullable=False)
3305
3312
3306 target_ref = Column('other_ref', Unicode(255), nullable=False)
3313 target_ref = Column('other_ref', Unicode(255), nullable=False)
3307 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3314 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3308
3315
3309 # TODO: dan: rename column to last_merge_source_rev
3316 # TODO: dan: rename column to last_merge_source_rev
3310 _last_merge_source_rev = Column(
3317 _last_merge_source_rev = Column(
3311 'last_merge_org_rev', String(40), nullable=True)
3318 'last_merge_org_rev', String(40), nullable=True)
3312 # TODO: dan: rename column to last_merge_target_rev
3319 # TODO: dan: rename column to last_merge_target_rev
3313 _last_merge_target_rev = Column(
3320 _last_merge_target_rev = Column(
3314 'last_merge_other_rev', String(40), nullable=True)
3321 'last_merge_other_rev', String(40), nullable=True)
3315 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3322 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3316 merge_rev = Column('merge_rev', String(40), nullable=True)
3323 merge_rev = Column('merge_rev', String(40), nullable=True)
3317
3324
3318 reviewer_data = Column(
3325 reviewer_data = Column(
3319 'reviewer_data_json', MutationObj.as_mutable(
3326 'reviewer_data_json', MutationObj.as_mutable(
3320 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3327 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3321
3328
3322 @property
3329 @property
3323 def reviewer_data_json(self):
3330 def reviewer_data_json(self):
3324 return json.dumps(self.reviewer_data)
3331 return json.dumps(self.reviewer_data)
3325
3332
3326 @hybrid_property
3333 @hybrid_property
3327 def description_safe(self):
3334 def description_safe(self):
3328 from rhodecode.lib import helpers as h
3335 from rhodecode.lib import helpers as h
3329 return h.escape(self.description)
3336 return h.escape(self.description)
3330
3337
3331 @hybrid_property
3338 @hybrid_property
3332 def revisions(self):
3339 def revisions(self):
3333 return self._revisions.split(':') if self._revisions else []
3340 return self._revisions.split(':') if self._revisions else []
3334
3341
3335 @revisions.setter
3342 @revisions.setter
3336 def revisions(self, val):
3343 def revisions(self, val):
3337 self._revisions = ':'.join(val)
3344 self._revisions = ':'.join(val)
3338
3345
3339 @declared_attr
3346 @declared_attr
3340 def author(cls):
3347 def author(cls):
3341 return relationship('User', lazy='joined')
3348 return relationship('User', lazy='joined')
3342
3349
3343 @declared_attr
3350 @declared_attr
3344 def source_repo(cls):
3351 def source_repo(cls):
3345 return relationship(
3352 return relationship(
3346 'Repository',
3353 'Repository',
3347 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3354 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3348
3355
3349 @property
3356 @property
3350 def source_ref_parts(self):
3357 def source_ref_parts(self):
3351 return self.unicode_to_reference(self.source_ref)
3358 return self.unicode_to_reference(self.source_ref)
3352
3359
3353 @declared_attr
3360 @declared_attr
3354 def target_repo(cls):
3361 def target_repo(cls):
3355 return relationship(
3362 return relationship(
3356 'Repository',
3363 'Repository',
3357 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3364 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3358
3365
3359 @property
3366 @property
3360 def target_ref_parts(self):
3367 def target_ref_parts(self):
3361 return self.unicode_to_reference(self.target_ref)
3368 return self.unicode_to_reference(self.target_ref)
3362
3369
3363 @property
3370 @property
3364 def shadow_merge_ref(self):
3371 def shadow_merge_ref(self):
3365 return self.unicode_to_reference(self._shadow_merge_ref)
3372 return self.unicode_to_reference(self._shadow_merge_ref)
3366
3373
3367 @shadow_merge_ref.setter
3374 @shadow_merge_ref.setter
3368 def shadow_merge_ref(self, ref):
3375 def shadow_merge_ref(self, ref):
3369 self._shadow_merge_ref = self.reference_to_unicode(ref)
3376 self._shadow_merge_ref = self.reference_to_unicode(ref)
3370
3377
3371 def unicode_to_reference(self, raw):
3378 def unicode_to_reference(self, raw):
3372 """
3379 """
3373 Convert a unicode (or string) to a reference object.
3380 Convert a unicode (or string) to a reference object.
3374 If unicode evaluates to False it returns None.
3381 If unicode evaluates to False it returns None.
3375 """
3382 """
3376 if raw:
3383 if raw:
3377 refs = raw.split(':')
3384 refs = raw.split(':')
3378 return Reference(*refs)
3385 return Reference(*refs)
3379 else:
3386 else:
3380 return None
3387 return None
3381
3388
3382 def reference_to_unicode(self, ref):
3389 def reference_to_unicode(self, ref):
3383 """
3390 """
3384 Convert a reference object to unicode.
3391 Convert a reference object to unicode.
3385 If reference is None it returns None.
3392 If reference is None it returns None.
3386 """
3393 """
3387 if ref:
3394 if ref:
3388 return u':'.join(ref)
3395 return u':'.join(ref)
3389 else:
3396 else:
3390 return None
3397 return None
3391
3398
3392 def get_api_data(self, with_merge_state=True):
3399 def get_api_data(self, with_merge_state=True):
3393 from rhodecode.model.pull_request import PullRequestModel
3400 from rhodecode.model.pull_request import PullRequestModel
3394
3401
3395 pull_request = self
3402 pull_request = self
3396 if with_merge_state:
3403 if with_merge_state:
3397 merge_status = PullRequestModel().merge_status(pull_request)
3404 merge_status = PullRequestModel().merge_status(pull_request)
3398 merge_state = {
3405 merge_state = {
3399 'status': merge_status[0],
3406 'status': merge_status[0],
3400 'message': safe_unicode(merge_status[1]),
3407 'message': safe_unicode(merge_status[1]),
3401 }
3408 }
3402 else:
3409 else:
3403 merge_state = {'status': 'not_available',
3410 merge_state = {'status': 'not_available',
3404 'message': 'not_available'}
3411 'message': 'not_available'}
3405
3412
3406 merge_data = {
3413 merge_data = {
3407 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3414 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3408 'reference': (
3415 'reference': (
3409 pull_request.shadow_merge_ref._asdict()
3416 pull_request.shadow_merge_ref._asdict()
3410 if pull_request.shadow_merge_ref else None),
3417 if pull_request.shadow_merge_ref else None),
3411 }
3418 }
3412
3419
3413 data = {
3420 data = {
3414 'pull_request_id': pull_request.pull_request_id,
3421 'pull_request_id': pull_request.pull_request_id,
3415 'url': PullRequestModel().get_url(pull_request),
3422 'url': PullRequestModel().get_url(pull_request),
3416 'title': pull_request.title,
3423 'title': pull_request.title,
3417 'description': pull_request.description,
3424 'description': pull_request.description,
3418 'status': pull_request.status,
3425 'status': pull_request.status,
3419 'created_on': pull_request.created_on,
3426 'created_on': pull_request.created_on,
3420 'updated_on': pull_request.updated_on,
3427 'updated_on': pull_request.updated_on,
3421 'commit_ids': pull_request.revisions,
3428 'commit_ids': pull_request.revisions,
3422 'review_status': pull_request.calculated_review_status(),
3429 'review_status': pull_request.calculated_review_status(),
3423 'mergeable': merge_state,
3430 'mergeable': merge_state,
3424 'source': {
3431 'source': {
3425 'clone_url': pull_request.source_repo.clone_url(),
3432 'clone_url': pull_request.source_repo.clone_url(),
3426 'repository': pull_request.source_repo.repo_name,
3433 'repository': pull_request.source_repo.repo_name,
3427 'reference': {
3434 'reference': {
3428 'name': pull_request.source_ref_parts.name,
3435 'name': pull_request.source_ref_parts.name,
3429 'type': pull_request.source_ref_parts.type,
3436 'type': pull_request.source_ref_parts.type,
3430 'commit_id': pull_request.source_ref_parts.commit_id,
3437 'commit_id': pull_request.source_ref_parts.commit_id,
3431 },
3438 },
3432 },
3439 },
3433 'target': {
3440 'target': {
3434 'clone_url': pull_request.target_repo.clone_url(),
3441 'clone_url': pull_request.target_repo.clone_url(),
3435 'repository': pull_request.target_repo.repo_name,
3442 'repository': pull_request.target_repo.repo_name,
3436 'reference': {
3443 'reference': {
3437 'name': pull_request.target_ref_parts.name,
3444 'name': pull_request.target_ref_parts.name,
3438 'type': pull_request.target_ref_parts.type,
3445 'type': pull_request.target_ref_parts.type,
3439 'commit_id': pull_request.target_ref_parts.commit_id,
3446 'commit_id': pull_request.target_ref_parts.commit_id,
3440 },
3447 },
3441 },
3448 },
3442 'merge': merge_data,
3449 'merge': merge_data,
3443 'author': pull_request.author.get_api_data(include_secrets=False,
3450 'author': pull_request.author.get_api_data(include_secrets=False,
3444 details='basic'),
3451 details='basic'),
3445 'reviewers': [
3452 'reviewers': [
3446 {
3453 {
3447 'user': reviewer.get_api_data(include_secrets=False,
3454 'user': reviewer.get_api_data(include_secrets=False,
3448 details='basic'),
3455 details='basic'),
3449 'reasons': reasons,
3456 'reasons': reasons,
3450 'review_status': st[0][1].status if st else 'not_reviewed',
3457 'review_status': st[0][1].status if st else 'not_reviewed',
3451 }
3458 }
3452 for reviewer, reasons, mandatory, st in
3459 for reviewer, reasons, mandatory, st in
3453 pull_request.reviewers_statuses()
3460 pull_request.reviewers_statuses()
3454 ]
3461 ]
3455 }
3462 }
3456
3463
3457 return data
3464 return data
3458
3465
3459
3466
3460 class PullRequest(Base, _PullRequestBase):
3467 class PullRequest(Base, _PullRequestBase):
3461 __tablename__ = 'pull_requests'
3468 __tablename__ = 'pull_requests'
3462 __table_args__ = (
3469 __table_args__ = (
3463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3470 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3471 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3465 )
3472 )
3466
3473
3467 pull_request_id = Column(
3474 pull_request_id = Column(
3468 'pull_request_id', Integer(), nullable=False, primary_key=True)
3475 'pull_request_id', Integer(), nullable=False, primary_key=True)
3469
3476
3470 def __repr__(self):
3477 def __repr__(self):
3471 if self.pull_request_id:
3478 if self.pull_request_id:
3472 return '<DB:PullRequest #%s>' % self.pull_request_id
3479 return '<DB:PullRequest #%s>' % self.pull_request_id
3473 else:
3480 else:
3474 return '<DB:PullRequest at %#x>' % id(self)
3481 return '<DB:PullRequest at %#x>' % id(self)
3475
3482
3476 reviewers = relationship('PullRequestReviewers',
3483 reviewers = relationship('PullRequestReviewers',
3477 cascade="all, delete, delete-orphan")
3484 cascade="all, delete, delete-orphan")
3478 statuses = relationship('ChangesetStatus',
3485 statuses = relationship('ChangesetStatus',
3479 cascade="all, delete, delete-orphan")
3486 cascade="all, delete, delete-orphan")
3480 comments = relationship('ChangesetComment',
3487 comments = relationship('ChangesetComment',
3481 cascade="all, delete, delete-orphan")
3488 cascade="all, delete, delete-orphan")
3482 versions = relationship('PullRequestVersion',
3489 versions = relationship('PullRequestVersion',
3483 cascade="all, delete, delete-orphan",
3490 cascade="all, delete, delete-orphan",
3484 lazy='dynamic')
3491 lazy='dynamic')
3485
3492
3486 @classmethod
3493 @classmethod
3487 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3494 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3488 internal_methods=None):
3495 internal_methods=None):
3489
3496
3490 class PullRequestDisplay(object):
3497 class PullRequestDisplay(object):
3491 """
3498 """
3492 Special object wrapper for showing PullRequest data via Versions
3499 Special object wrapper for showing PullRequest data via Versions
3493 It mimics PR object as close as possible. This is read only object
3500 It mimics PR object as close as possible. This is read only object
3494 just for display
3501 just for display
3495 """
3502 """
3496
3503
3497 def __init__(self, attrs, internal=None):
3504 def __init__(self, attrs, internal=None):
3498 self.attrs = attrs
3505 self.attrs = attrs
3499 # internal have priority over the given ones via attrs
3506 # internal have priority over the given ones via attrs
3500 self.internal = internal or ['versions']
3507 self.internal = internal or ['versions']
3501
3508
3502 def __getattr__(self, item):
3509 def __getattr__(self, item):
3503 if item in self.internal:
3510 if item in self.internal:
3504 return getattr(self, item)
3511 return getattr(self, item)
3505 try:
3512 try:
3506 return self.attrs[item]
3513 return self.attrs[item]
3507 except KeyError:
3514 except KeyError:
3508 raise AttributeError(
3515 raise AttributeError(
3509 '%s object has no attribute %s' % (self, item))
3516 '%s object has no attribute %s' % (self, item))
3510
3517
3511 def __repr__(self):
3518 def __repr__(self):
3512 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3519 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3513
3520
3514 def versions(self):
3521 def versions(self):
3515 return pull_request_obj.versions.order_by(
3522 return pull_request_obj.versions.order_by(
3516 PullRequestVersion.pull_request_version_id).all()
3523 PullRequestVersion.pull_request_version_id).all()
3517
3524
3518 def is_closed(self):
3525 def is_closed(self):
3519 return pull_request_obj.is_closed()
3526 return pull_request_obj.is_closed()
3520
3527
3521 @property
3528 @property
3522 def pull_request_version_id(self):
3529 def pull_request_version_id(self):
3523 return getattr(pull_request_obj, 'pull_request_version_id', None)
3530 return getattr(pull_request_obj, 'pull_request_version_id', None)
3524
3531
3525 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3532 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3526
3533
3527 attrs.author = StrictAttributeDict(
3534 attrs.author = StrictAttributeDict(
3528 pull_request_obj.author.get_api_data())
3535 pull_request_obj.author.get_api_data())
3529 if pull_request_obj.target_repo:
3536 if pull_request_obj.target_repo:
3530 attrs.target_repo = StrictAttributeDict(
3537 attrs.target_repo = StrictAttributeDict(
3531 pull_request_obj.target_repo.get_api_data())
3538 pull_request_obj.target_repo.get_api_data())
3532 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3539 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3533
3540
3534 if pull_request_obj.source_repo:
3541 if pull_request_obj.source_repo:
3535 attrs.source_repo = StrictAttributeDict(
3542 attrs.source_repo = StrictAttributeDict(
3536 pull_request_obj.source_repo.get_api_data())
3543 pull_request_obj.source_repo.get_api_data())
3537 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3544 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3538
3545
3539 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3546 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3540 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3547 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3541 attrs.revisions = pull_request_obj.revisions
3548 attrs.revisions = pull_request_obj.revisions
3542
3549
3543 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3550 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3544 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3551 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3545 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3552 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3546
3553
3547 return PullRequestDisplay(attrs, internal=internal_methods)
3554 return PullRequestDisplay(attrs, internal=internal_methods)
3548
3555
3549 def is_closed(self):
3556 def is_closed(self):
3550 return self.status == self.STATUS_CLOSED
3557 return self.status == self.STATUS_CLOSED
3551
3558
3552 def __json__(self):
3559 def __json__(self):
3553 return {
3560 return {
3554 'revisions': self.revisions,
3561 'revisions': self.revisions,
3555 }
3562 }
3556
3563
3557 def calculated_review_status(self):
3564 def calculated_review_status(self):
3558 from rhodecode.model.changeset_status import ChangesetStatusModel
3565 from rhodecode.model.changeset_status import ChangesetStatusModel
3559 return ChangesetStatusModel().calculated_review_status(self)
3566 return ChangesetStatusModel().calculated_review_status(self)
3560
3567
3561 def reviewers_statuses(self):
3568 def reviewers_statuses(self):
3562 from rhodecode.model.changeset_status import ChangesetStatusModel
3569 from rhodecode.model.changeset_status import ChangesetStatusModel
3563 return ChangesetStatusModel().reviewers_statuses(self)
3570 return ChangesetStatusModel().reviewers_statuses(self)
3564
3571
3565 @property
3572 @property
3566 def workspace_id(self):
3573 def workspace_id(self):
3567 from rhodecode.model.pull_request import PullRequestModel
3574 from rhodecode.model.pull_request import PullRequestModel
3568 return PullRequestModel()._workspace_id(self)
3575 return PullRequestModel()._workspace_id(self)
3569
3576
3570 def get_shadow_repo(self):
3577 def get_shadow_repo(self):
3571 workspace_id = self.workspace_id
3578 workspace_id = self.workspace_id
3572 vcs_obj = self.target_repo.scm_instance()
3579 vcs_obj = self.target_repo.scm_instance()
3573 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3580 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3574 workspace_id)
3581 workspace_id)
3575 return vcs_obj._get_shadow_instance(shadow_repository_path)
3582 return vcs_obj._get_shadow_instance(shadow_repository_path)
3576
3583
3577
3584
3578 class PullRequestVersion(Base, _PullRequestBase):
3585 class PullRequestVersion(Base, _PullRequestBase):
3579 __tablename__ = 'pull_request_versions'
3586 __tablename__ = 'pull_request_versions'
3580 __table_args__ = (
3587 __table_args__ = (
3581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3589 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3583 )
3590 )
3584
3591
3585 pull_request_version_id = Column(
3592 pull_request_version_id = Column(
3586 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3593 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3587 pull_request_id = Column(
3594 pull_request_id = Column(
3588 'pull_request_id', Integer(),
3595 'pull_request_id', Integer(),
3589 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3596 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3590 pull_request = relationship('PullRequest')
3597 pull_request = relationship('PullRequest')
3591
3598
3592 def __repr__(self):
3599 def __repr__(self):
3593 if self.pull_request_version_id:
3600 if self.pull_request_version_id:
3594 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3601 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3595 else:
3602 else:
3596 return '<DB:PullRequestVersion at %#x>' % id(self)
3603 return '<DB:PullRequestVersion at %#x>' % id(self)
3597
3604
3598 @property
3605 @property
3599 def reviewers(self):
3606 def reviewers(self):
3600 return self.pull_request.reviewers
3607 return self.pull_request.reviewers
3601
3608
3602 @property
3609 @property
3603 def versions(self):
3610 def versions(self):
3604 return self.pull_request.versions
3611 return self.pull_request.versions
3605
3612
3606 def is_closed(self):
3613 def is_closed(self):
3607 # calculate from original
3614 # calculate from original
3608 return self.pull_request.status == self.STATUS_CLOSED
3615 return self.pull_request.status == self.STATUS_CLOSED
3609
3616
3610 def calculated_review_status(self):
3617 def calculated_review_status(self):
3611 return self.pull_request.calculated_review_status()
3618 return self.pull_request.calculated_review_status()
3612
3619
3613 def reviewers_statuses(self):
3620 def reviewers_statuses(self):
3614 return self.pull_request.reviewers_statuses()
3621 return self.pull_request.reviewers_statuses()
3615
3622
3616
3623
3617 class PullRequestReviewers(Base, BaseModel):
3624 class PullRequestReviewers(Base, BaseModel):
3618 __tablename__ = 'pull_request_reviewers'
3625 __tablename__ = 'pull_request_reviewers'
3619 __table_args__ = (
3626 __table_args__ = (
3620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3628 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3622 )
3629 )
3623
3630
3624 @hybrid_property
3631 @hybrid_property
3625 def reasons(self):
3632 def reasons(self):
3626 if not self._reasons:
3633 if not self._reasons:
3627 return []
3634 return []
3628 return self._reasons
3635 return self._reasons
3629
3636
3630 @reasons.setter
3637 @reasons.setter
3631 def reasons(self, val):
3638 def reasons(self, val):
3632 val = val or []
3639 val = val or []
3633 if any(not isinstance(x, basestring) for x in val):
3640 if any(not isinstance(x, basestring) for x in val):
3634 raise Exception('invalid reasons type, must be list of strings')
3641 raise Exception('invalid reasons type, must be list of strings')
3635 self._reasons = val
3642 self._reasons = val
3636
3643
3637 pull_requests_reviewers_id = Column(
3644 pull_requests_reviewers_id = Column(
3638 'pull_requests_reviewers_id', Integer(), nullable=False,
3645 'pull_requests_reviewers_id', Integer(), nullable=False,
3639 primary_key=True)
3646 primary_key=True)
3640 pull_request_id = Column(
3647 pull_request_id = Column(
3641 "pull_request_id", Integer(),
3648 "pull_request_id", Integer(),
3642 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3649 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3643 user_id = Column(
3650 user_id = Column(
3644 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3651 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3645 _reasons = Column(
3652 _reasons = Column(
3646 'reason', MutationList.as_mutable(
3653 'reason', MutationList.as_mutable(
3647 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3654 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3648 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3655 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3649 user = relationship('User')
3656 user = relationship('User')
3650 pull_request = relationship('PullRequest')
3657 pull_request = relationship('PullRequest')
3651
3658
3652
3659
3653 class Notification(Base, BaseModel):
3660 class Notification(Base, BaseModel):
3654 __tablename__ = 'notifications'
3661 __tablename__ = 'notifications'
3655 __table_args__ = (
3662 __table_args__ = (
3656 Index('notification_type_idx', 'type'),
3663 Index('notification_type_idx', 'type'),
3657 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3658 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3659 )
3666 )
3660
3667
3661 TYPE_CHANGESET_COMMENT = u'cs_comment'
3668 TYPE_CHANGESET_COMMENT = u'cs_comment'
3662 TYPE_MESSAGE = u'message'
3669 TYPE_MESSAGE = u'message'
3663 TYPE_MENTION = u'mention'
3670 TYPE_MENTION = u'mention'
3664 TYPE_REGISTRATION = u'registration'
3671 TYPE_REGISTRATION = u'registration'
3665 TYPE_PULL_REQUEST = u'pull_request'
3672 TYPE_PULL_REQUEST = u'pull_request'
3666 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3673 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3667
3674
3668 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3675 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3669 subject = Column('subject', Unicode(512), nullable=True)
3676 subject = Column('subject', Unicode(512), nullable=True)
3670 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3677 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3671 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3678 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3672 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3679 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3673 type_ = Column('type', Unicode(255))
3680 type_ = Column('type', Unicode(255))
3674
3681
3675 created_by_user = relationship('User')
3682 created_by_user = relationship('User')
3676 notifications_to_users = relationship('UserNotification', lazy='joined',
3683 notifications_to_users = relationship('UserNotification', lazy='joined',
3677 cascade="all, delete, delete-orphan")
3684 cascade="all, delete, delete-orphan")
3678
3685
3679 @property
3686 @property
3680 def recipients(self):
3687 def recipients(self):
3681 return [x.user for x in UserNotification.query()\
3688 return [x.user for x in UserNotification.query()\
3682 .filter(UserNotification.notification == self)\
3689 .filter(UserNotification.notification == self)\
3683 .order_by(UserNotification.user_id.asc()).all()]
3690 .order_by(UserNotification.user_id.asc()).all()]
3684
3691
3685 @classmethod
3692 @classmethod
3686 def create(cls, created_by, subject, body, recipients, type_=None):
3693 def create(cls, created_by, subject, body, recipients, type_=None):
3687 if type_ is None:
3694 if type_ is None:
3688 type_ = Notification.TYPE_MESSAGE
3695 type_ = Notification.TYPE_MESSAGE
3689
3696
3690 notification = cls()
3697 notification = cls()
3691 notification.created_by_user = created_by
3698 notification.created_by_user = created_by
3692 notification.subject = subject
3699 notification.subject = subject
3693 notification.body = body
3700 notification.body = body
3694 notification.type_ = type_
3701 notification.type_ = type_
3695 notification.created_on = datetime.datetime.now()
3702 notification.created_on = datetime.datetime.now()
3696
3703
3697 for u in recipients:
3704 for u in recipients:
3698 assoc = UserNotification()
3705 assoc = UserNotification()
3699 assoc.notification = notification
3706 assoc.notification = notification
3700
3707
3701 # if created_by is inside recipients mark his notification
3708 # if created_by is inside recipients mark his notification
3702 # as read
3709 # as read
3703 if u.user_id == created_by.user_id:
3710 if u.user_id == created_by.user_id:
3704 assoc.read = True
3711 assoc.read = True
3705
3712
3706 u.notifications.append(assoc)
3713 u.notifications.append(assoc)
3707 Session().add(notification)
3714 Session().add(notification)
3708
3715
3709 return notification
3716 return notification
3710
3717
3711
3718
3712 class UserNotification(Base, BaseModel):
3719 class UserNotification(Base, BaseModel):
3713 __tablename__ = 'user_to_notification'
3720 __tablename__ = 'user_to_notification'
3714 __table_args__ = (
3721 __table_args__ = (
3715 UniqueConstraint('user_id', 'notification_id'),
3722 UniqueConstraint('user_id', 'notification_id'),
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3718 )
3725 )
3719 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3726 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3720 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3727 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3721 read = Column('read', Boolean, default=False)
3728 read = Column('read', Boolean, default=False)
3722 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3729 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3723
3730
3724 user = relationship('User', lazy="joined")
3731 user = relationship('User', lazy="joined")
3725 notification = relationship('Notification', lazy="joined",
3732 notification = relationship('Notification', lazy="joined",
3726 order_by=lambda: Notification.created_on.desc(),)
3733 order_by=lambda: Notification.created_on.desc(),)
3727
3734
3728 def mark_as_read(self):
3735 def mark_as_read(self):
3729 self.read = True
3736 self.read = True
3730 Session().add(self)
3737 Session().add(self)
3731
3738
3732
3739
3733 class Gist(Base, BaseModel):
3740 class Gist(Base, BaseModel):
3734 __tablename__ = 'gists'
3741 __tablename__ = 'gists'
3735 __table_args__ = (
3742 __table_args__ = (
3736 Index('g_gist_access_id_idx', 'gist_access_id'),
3743 Index('g_gist_access_id_idx', 'gist_access_id'),
3737 Index('g_created_on_idx', 'created_on'),
3744 Index('g_created_on_idx', 'created_on'),
3738 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3745 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3739 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3746 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3740 )
3747 )
3741 GIST_PUBLIC = u'public'
3748 GIST_PUBLIC = u'public'
3742 GIST_PRIVATE = u'private'
3749 GIST_PRIVATE = u'private'
3743 DEFAULT_FILENAME = u'gistfile1.txt'
3750 DEFAULT_FILENAME = u'gistfile1.txt'
3744
3751
3745 ACL_LEVEL_PUBLIC = u'acl_public'
3752 ACL_LEVEL_PUBLIC = u'acl_public'
3746 ACL_LEVEL_PRIVATE = u'acl_private'
3753 ACL_LEVEL_PRIVATE = u'acl_private'
3747
3754
3748 gist_id = Column('gist_id', Integer(), primary_key=True)
3755 gist_id = Column('gist_id', Integer(), primary_key=True)
3749 gist_access_id = Column('gist_access_id', Unicode(250))
3756 gist_access_id = Column('gist_access_id', Unicode(250))
3750 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3757 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3751 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3758 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3752 gist_expires = Column('gist_expires', Float(53), nullable=False)
3759 gist_expires = Column('gist_expires', Float(53), nullable=False)
3753 gist_type = Column('gist_type', Unicode(128), nullable=False)
3760 gist_type = Column('gist_type', Unicode(128), nullable=False)
3754 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3761 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3755 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3762 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3756 acl_level = Column('acl_level', Unicode(128), nullable=True)
3763 acl_level = Column('acl_level', Unicode(128), nullable=True)
3757
3764
3758 owner = relationship('User')
3765 owner = relationship('User')
3759
3766
3760 def __repr__(self):
3767 def __repr__(self):
3761 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3768 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3762
3769
3763 @hybrid_property
3770 @hybrid_property
3764 def description_safe(self):
3771 def description_safe(self):
3765 from rhodecode.lib import helpers as h
3772 from rhodecode.lib import helpers as h
3766 return h.escape(self.gist_description)
3773 return h.escape(self.gist_description)
3767
3774
3768 @classmethod
3775 @classmethod
3769 def get_or_404(cls, id_, pyramid_exc=False):
3776 def get_or_404(cls, id_, pyramid_exc=False):
3770
3777
3771 if pyramid_exc:
3778 if pyramid_exc:
3772 from pyramid.httpexceptions import HTTPNotFound
3779 from pyramid.httpexceptions import HTTPNotFound
3773 else:
3780 else:
3774 from webob.exc import HTTPNotFound
3781 from webob.exc import HTTPNotFound
3775
3782
3776 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3783 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3777 if not res:
3784 if not res:
3778 raise HTTPNotFound
3785 raise HTTPNotFound
3779 return res
3786 return res
3780
3787
3781 @classmethod
3788 @classmethod
3782 def get_by_access_id(cls, gist_access_id):
3789 def get_by_access_id(cls, gist_access_id):
3783 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3790 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3784
3791
3785 def gist_url(self):
3792 def gist_url(self):
3786 from rhodecode.model.gist import GistModel
3793 from rhodecode.model.gist import GistModel
3787 return GistModel().get_url(self)
3794 return GistModel().get_url(self)
3788
3795
3789 @classmethod
3796 @classmethod
3790 def base_path(cls):
3797 def base_path(cls):
3791 """
3798 """
3792 Returns base path when all gists are stored
3799 Returns base path when all gists are stored
3793
3800
3794 :param cls:
3801 :param cls:
3795 """
3802 """
3796 from rhodecode.model.gist import GIST_STORE_LOC
3803 from rhodecode.model.gist import GIST_STORE_LOC
3797 q = Session().query(RhodeCodeUi)\
3804 q = Session().query(RhodeCodeUi)\
3798 .filter(RhodeCodeUi.ui_key == URL_SEP)
3805 .filter(RhodeCodeUi.ui_key == URL_SEP)
3799 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3806 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3800 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3807 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3801
3808
3802 def get_api_data(self):
3809 def get_api_data(self):
3803 """
3810 """
3804 Common function for generating gist related data for API
3811 Common function for generating gist related data for API
3805 """
3812 """
3806 gist = self
3813 gist = self
3807 data = {
3814 data = {
3808 'gist_id': gist.gist_id,
3815 'gist_id': gist.gist_id,
3809 'type': gist.gist_type,
3816 'type': gist.gist_type,
3810 'access_id': gist.gist_access_id,
3817 'access_id': gist.gist_access_id,
3811 'description': gist.gist_description,
3818 'description': gist.gist_description,
3812 'url': gist.gist_url(),
3819 'url': gist.gist_url(),
3813 'expires': gist.gist_expires,
3820 'expires': gist.gist_expires,
3814 'created_on': gist.created_on,
3821 'created_on': gist.created_on,
3815 'modified_at': gist.modified_at,
3822 'modified_at': gist.modified_at,
3816 'content': None,
3823 'content': None,
3817 'acl_level': gist.acl_level,
3824 'acl_level': gist.acl_level,
3818 }
3825 }
3819 return data
3826 return data
3820
3827
3821 def __json__(self):
3828 def __json__(self):
3822 data = dict(
3829 data = dict(
3823 )
3830 )
3824 data.update(self.get_api_data())
3831 data.update(self.get_api_data())
3825 return data
3832 return data
3826 # SCM functions
3833 # SCM functions
3827
3834
3828 def scm_instance(self, **kwargs):
3835 def scm_instance(self, **kwargs):
3829 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3836 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3830 return get_vcs_instance(
3837 return get_vcs_instance(
3831 repo_path=safe_str(full_repo_path), create=False)
3838 repo_path=safe_str(full_repo_path), create=False)
3832
3839
3833
3840
3834 class ExternalIdentity(Base, BaseModel):
3841 class ExternalIdentity(Base, BaseModel):
3835 __tablename__ = 'external_identities'
3842 __tablename__ = 'external_identities'
3836 __table_args__ = (
3843 __table_args__ = (
3837 Index('local_user_id_idx', 'local_user_id'),
3844 Index('local_user_id_idx', 'local_user_id'),
3838 Index('external_id_idx', 'external_id'),
3845 Index('external_id_idx', 'external_id'),
3839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3846 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3840 'mysql_charset': 'utf8'})
3847 'mysql_charset': 'utf8'})
3841
3848
3842 external_id = Column('external_id', Unicode(255), default=u'',
3849 external_id = Column('external_id', Unicode(255), default=u'',
3843 primary_key=True)
3850 primary_key=True)
3844 external_username = Column('external_username', Unicode(1024), default=u'')
3851 external_username = Column('external_username', Unicode(1024), default=u'')
3845 local_user_id = Column('local_user_id', Integer(),
3852 local_user_id = Column('local_user_id', Integer(),
3846 ForeignKey('users.user_id'), primary_key=True)
3853 ForeignKey('users.user_id'), primary_key=True)
3847 provider_name = Column('provider_name', Unicode(255), default=u'',
3854 provider_name = Column('provider_name', Unicode(255), default=u'',
3848 primary_key=True)
3855 primary_key=True)
3849 access_token = Column('access_token', String(1024), default=u'')
3856 access_token = Column('access_token', String(1024), default=u'')
3850 alt_token = Column('alt_token', String(1024), default=u'')
3857 alt_token = Column('alt_token', String(1024), default=u'')
3851 token_secret = Column('token_secret', String(1024), default=u'')
3858 token_secret = Column('token_secret', String(1024), default=u'')
3852
3859
3853 @classmethod
3860 @classmethod
3854 def by_external_id_and_provider(cls, external_id, provider_name,
3861 def by_external_id_and_provider(cls, external_id, provider_name,
3855 local_user_id=None):
3862 local_user_id=None):
3856 """
3863 """
3857 Returns ExternalIdentity instance based on search params
3864 Returns ExternalIdentity instance based on search params
3858
3865
3859 :param external_id:
3866 :param external_id:
3860 :param provider_name:
3867 :param provider_name:
3861 :return: ExternalIdentity
3868 :return: ExternalIdentity
3862 """
3869 """
3863 query = cls.query()
3870 query = cls.query()
3864 query = query.filter(cls.external_id == external_id)
3871 query = query.filter(cls.external_id == external_id)
3865 query = query.filter(cls.provider_name == provider_name)
3872 query = query.filter(cls.provider_name == provider_name)
3866 if local_user_id:
3873 if local_user_id:
3867 query = query.filter(cls.local_user_id == local_user_id)
3874 query = query.filter(cls.local_user_id == local_user_id)
3868 return query.first()
3875 return query.first()
3869
3876
3870 @classmethod
3877 @classmethod
3871 def user_by_external_id_and_provider(cls, external_id, provider_name):
3878 def user_by_external_id_and_provider(cls, external_id, provider_name):
3872 """
3879 """
3873 Returns User instance based on search params
3880 Returns User instance based on search params
3874
3881
3875 :param external_id:
3882 :param external_id:
3876 :param provider_name:
3883 :param provider_name:
3877 :return: User
3884 :return: User
3878 """
3885 """
3879 query = User.query()
3886 query = User.query()
3880 query = query.filter(cls.external_id == external_id)
3887 query = query.filter(cls.external_id == external_id)
3881 query = query.filter(cls.provider_name == provider_name)
3888 query = query.filter(cls.provider_name == provider_name)
3882 query = query.filter(User.user_id == cls.local_user_id)
3889 query = query.filter(User.user_id == cls.local_user_id)
3883 return query.first()
3890 return query.first()
3884
3891
3885 @classmethod
3892 @classmethod
3886 def by_local_user_id(cls, local_user_id):
3893 def by_local_user_id(cls, local_user_id):
3887 """
3894 """
3888 Returns all tokens for user
3895 Returns all tokens for user
3889
3896
3890 :param local_user_id:
3897 :param local_user_id:
3891 :return: ExternalIdentity
3898 :return: ExternalIdentity
3892 """
3899 """
3893 query = cls.query()
3900 query = cls.query()
3894 query = query.filter(cls.local_user_id == local_user_id)
3901 query = query.filter(cls.local_user_id == local_user_id)
3895 return query
3902 return query
3896
3903
3897
3904
3898 class Integration(Base, BaseModel):
3905 class Integration(Base, BaseModel):
3899 __tablename__ = 'integrations'
3906 __tablename__ = 'integrations'
3900 __table_args__ = (
3907 __table_args__ = (
3901 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3908 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3902 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3909 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3903 )
3910 )
3904
3911
3905 integration_id = Column('integration_id', Integer(), primary_key=True)
3912 integration_id = Column('integration_id', Integer(), primary_key=True)
3906 integration_type = Column('integration_type', String(255))
3913 integration_type = Column('integration_type', String(255))
3907 enabled = Column('enabled', Boolean(), nullable=False)
3914 enabled = Column('enabled', Boolean(), nullable=False)
3908 name = Column('name', String(255), nullable=False)
3915 name = Column('name', String(255), nullable=False)
3909 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3916 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3910 default=False)
3917 default=False)
3911
3918
3912 settings = Column(
3919 settings = Column(
3913 'settings_json', MutationObj.as_mutable(
3920 'settings_json', MutationObj.as_mutable(
3914 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3921 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3915 repo_id = Column(
3922 repo_id = Column(
3916 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3923 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3917 nullable=True, unique=None, default=None)
3924 nullable=True, unique=None, default=None)
3918 repo = relationship('Repository', lazy='joined')
3925 repo = relationship('Repository', lazy='joined')
3919
3926
3920 repo_group_id = Column(
3927 repo_group_id = Column(
3921 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3928 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3922 nullable=True, unique=None, default=None)
3929 nullable=True, unique=None, default=None)
3923 repo_group = relationship('RepoGroup', lazy='joined')
3930 repo_group = relationship('RepoGroup', lazy='joined')
3924
3931
3925 @property
3932 @property
3926 def scope(self):
3933 def scope(self):
3927 if self.repo:
3934 if self.repo:
3928 return repr(self.repo)
3935 return repr(self.repo)
3929 if self.repo_group:
3936 if self.repo_group:
3930 if self.child_repos_only:
3937 if self.child_repos_only:
3931 return repr(self.repo_group) + ' (child repos only)'
3938 return repr(self.repo_group) + ' (child repos only)'
3932 else:
3939 else:
3933 return repr(self.repo_group) + ' (recursive)'
3940 return repr(self.repo_group) + ' (recursive)'
3934 if self.child_repos_only:
3941 if self.child_repos_only:
3935 return 'root_repos'
3942 return 'root_repos'
3936 return 'global'
3943 return 'global'
3937
3944
3938 def __repr__(self):
3945 def __repr__(self):
3939 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3946 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3940
3947
3941
3948
3942 class RepoReviewRuleUser(Base, BaseModel):
3949 class RepoReviewRuleUser(Base, BaseModel):
3943 __tablename__ = 'repo_review_rules_users'
3950 __tablename__ = 'repo_review_rules_users'
3944 __table_args__ = (
3951 __table_args__ = (
3945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3952 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3953 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3947 )
3954 )
3948 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3955 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3949 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3956 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3957 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3951 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3958 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3952 user = relationship('User')
3959 user = relationship('User')
3953
3960
3954 def rule_data(self):
3961 def rule_data(self):
3955 return {
3962 return {
3956 'mandatory': self.mandatory
3963 'mandatory': self.mandatory
3957 }
3964 }
3958
3965
3959
3966
3960 class RepoReviewRuleUserGroup(Base, BaseModel):
3967 class RepoReviewRuleUserGroup(Base, BaseModel):
3961 __tablename__ = 'repo_review_rules_users_groups'
3968 __tablename__ = 'repo_review_rules_users_groups'
3962 __table_args__ = (
3969 __table_args__ = (
3963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3971 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3965 )
3972 )
3966 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3973 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3967 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3974 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3968 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3975 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3969 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3976 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3970 users_group = relationship('UserGroup')
3977 users_group = relationship('UserGroup')
3971
3978
3972 def rule_data(self):
3979 def rule_data(self):
3973 return {
3980 return {
3974 'mandatory': self.mandatory
3981 'mandatory': self.mandatory
3975 }
3982 }
3976
3983
3977
3984
3978 class RepoReviewRule(Base, BaseModel):
3985 class RepoReviewRule(Base, BaseModel):
3979 __tablename__ = 'repo_review_rules'
3986 __tablename__ = 'repo_review_rules'
3980 __table_args__ = (
3987 __table_args__ = (
3981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3982 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3989 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3983 )
3990 )
3984
3991
3985 repo_review_rule_id = Column(
3992 repo_review_rule_id = Column(
3986 'repo_review_rule_id', Integer(), primary_key=True)
3993 'repo_review_rule_id', Integer(), primary_key=True)
3987 repo_id = Column(
3994 repo_id = Column(
3988 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3995 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3989 repo = relationship('Repository', backref='review_rules')
3996 repo = relationship('Repository', backref='review_rules')
3990
3997
3991 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3998 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3992 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3999 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3993
4000
3994 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4001 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3995 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4002 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3996 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4003 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
3997 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4004 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
3998
4005
3999 rule_users = relationship('RepoReviewRuleUser')
4006 rule_users = relationship('RepoReviewRuleUser')
4000 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4007 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4001
4008
4002 @hybrid_property
4009 @hybrid_property
4003 def branch_pattern(self):
4010 def branch_pattern(self):
4004 return self._branch_pattern or '*'
4011 return self._branch_pattern or '*'
4005
4012
4006 def _validate_glob(self, value):
4013 def _validate_glob(self, value):
4007 re.compile('^' + glob2re(value) + '$')
4014 re.compile('^' + glob2re(value) + '$')
4008
4015
4009 @branch_pattern.setter
4016 @branch_pattern.setter
4010 def branch_pattern(self, value):
4017 def branch_pattern(self, value):
4011 self._validate_glob(value)
4018 self._validate_glob(value)
4012 self._branch_pattern = value or '*'
4019 self._branch_pattern = value or '*'
4013
4020
4014 @hybrid_property
4021 @hybrid_property
4015 def file_pattern(self):
4022 def file_pattern(self):
4016 return self._file_pattern or '*'
4023 return self._file_pattern or '*'
4017
4024
4018 @file_pattern.setter
4025 @file_pattern.setter
4019 def file_pattern(self, value):
4026 def file_pattern(self, value):
4020 self._validate_glob(value)
4027 self._validate_glob(value)
4021 self._file_pattern = value or '*'
4028 self._file_pattern = value or '*'
4022
4029
4023 def matches(self, branch, files_changed):
4030 def matches(self, branch, files_changed):
4024 """
4031 """
4025 Check if this review rule matches a branch/files in a pull request
4032 Check if this review rule matches a branch/files in a pull request
4026
4033
4027 :param branch: branch name for the commit
4034 :param branch: branch name for the commit
4028 :param files_changed: list of file paths changed in the pull request
4035 :param files_changed: list of file paths changed in the pull request
4029 """
4036 """
4030
4037
4031 branch = branch or ''
4038 branch = branch or ''
4032 files_changed = files_changed or []
4039 files_changed = files_changed or []
4033
4040
4034 branch_matches = True
4041 branch_matches = True
4035 if branch:
4042 if branch:
4036 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4043 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4037 branch_matches = bool(branch_regex.search(branch))
4044 branch_matches = bool(branch_regex.search(branch))
4038
4045
4039 files_matches = True
4046 files_matches = True
4040 if self.file_pattern != '*':
4047 if self.file_pattern != '*':
4041 files_matches = False
4048 files_matches = False
4042 file_regex = re.compile(glob2re(self.file_pattern))
4049 file_regex = re.compile(glob2re(self.file_pattern))
4043 for filename in files_changed:
4050 for filename in files_changed:
4044 if file_regex.search(filename):
4051 if file_regex.search(filename):
4045 files_matches = True
4052 files_matches = True
4046 break
4053 break
4047
4054
4048 return branch_matches and files_matches
4055 return branch_matches and files_matches
4049
4056
4050 @property
4057 @property
4051 def review_users(self):
4058 def review_users(self):
4052 """ Returns the users which this rule applies to """
4059 """ Returns the users which this rule applies to """
4053
4060
4054 users = collections.OrderedDict()
4061 users = collections.OrderedDict()
4055
4062
4056 for rule_user in self.rule_users:
4063 for rule_user in self.rule_users:
4057 if rule_user.user.active:
4064 if rule_user.user.active:
4058 if rule_user.user not in users:
4065 if rule_user.user not in users:
4059 users[rule_user.user.username] = {
4066 users[rule_user.user.username] = {
4060 'user': rule_user.user,
4067 'user': rule_user.user,
4061 'source': 'user',
4068 'source': 'user',
4062 'source_data': {},
4069 'source_data': {},
4063 'data': rule_user.rule_data()
4070 'data': rule_user.rule_data()
4064 }
4071 }
4065
4072
4066 for rule_user_group in self.rule_user_groups:
4073 for rule_user_group in self.rule_user_groups:
4067 source_data = {
4074 source_data = {
4068 'name': rule_user_group.users_group.users_group_name,
4075 'name': rule_user_group.users_group.users_group_name,
4069 'members': len(rule_user_group.users_group.members)
4076 'members': len(rule_user_group.users_group.members)
4070 }
4077 }
4071 for member in rule_user_group.users_group.members:
4078 for member in rule_user_group.users_group.members:
4072 if member.user.active:
4079 if member.user.active:
4073 users[member.user.username] = {
4080 users[member.user.username] = {
4074 'user': member.user,
4081 'user': member.user,
4075 'source': 'user_group',
4082 'source': 'user_group',
4076 'source_data': source_data,
4083 'source_data': source_data,
4077 'data': rule_user_group.rule_data()
4084 'data': rule_user_group.rule_data()
4078 }
4085 }
4079
4086
4080 return users
4087 return users
4081
4088
4082 def __repr__(self):
4089 def __repr__(self):
4083 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4090 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4084 self.repo_review_rule_id, self.repo)
4091 self.repo_review_rule_id, self.repo)
4085
4092
4086
4093
4087 class DbMigrateVersion(Base, BaseModel):
4094 class DbMigrateVersion(Base, BaseModel):
4088 __tablename__ = 'db_migrate_version'
4095 __tablename__ = 'db_migrate_version'
4089 __table_args__ = (
4096 __table_args__ = (
4090 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4091 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4092 )
4099 )
4093 repository_id = Column('repository_id', String(250), primary_key=True)
4100 repository_id = Column('repository_id', String(250), primary_key=True)
4094 repository_path = Column('repository_path', Text)
4101 repository_path = Column('repository_path', Text)
4095 version = Column('version', Integer)
4102 version = Column('version', Integer)
4096
4103
4097
4104
4098 class DbSession(Base, BaseModel):
4105 class DbSession(Base, BaseModel):
4099 __tablename__ = 'db_session'
4106 __tablename__ = 'db_session'
4100 __table_args__ = (
4107 __table_args__ = (
4101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4109 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4103 )
4110 )
4104
4111
4105 def __repr__(self):
4112 def __repr__(self):
4106 return '<DB:DbSession({})>'.format(self.id)
4113 return '<DB:DbSession({})>'.format(self.id)
4107
4114
4108 id = Column('id', Integer())
4115 id = Column('id', Integer())
4109 namespace = Column('namespace', String(255), primary_key=True)
4116 namespace = Column('namespace', String(255), primary_key=True)
4110 accessed = Column('accessed', DateTime, nullable=False)
4117 accessed = Column('accessed', DateTime, nullable=False)
4111 created = Column('created', DateTime, nullable=False)
4118 created = Column('created', DateTime, nullable=False)
4112 data = Column('data', PickleType, nullable=False)
4119 data = Column('data', PickleType, nullable=False)
@@ -1,2385 +1,2391 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 @import 'readme-box';
15 @import 'progress-bar';
15 @import 'progress-bar';
16
16
17 @import 'type';
17 @import 'type';
18 @import 'alerts';
18 @import 'alerts';
19 @import 'buttons';
19 @import 'buttons';
20 @import 'tags';
20 @import 'tags';
21 @import 'code-block';
21 @import 'code-block';
22 @import 'examples';
22 @import 'examples';
23 @import 'login';
23 @import 'login';
24 @import 'main-content';
24 @import 'main-content';
25 @import 'select2';
25 @import 'select2';
26 @import 'comments';
26 @import 'comments';
27 @import 'panels-bootstrap';
27 @import 'panels-bootstrap';
28 @import 'panels';
28 @import 'panels';
29 @import 'deform';
29 @import 'deform';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-family: @text-semibold;
38 font-family: @text-semibold;
39 font-size: 120%;
39 font-size: 120%;
40 color: white;
40 color: white;
41 background-color: @alert2;
41 background-color: @alert2;
42 padding: 5px 0 5px 0;
42 padding: 5px 0 5px 0;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 ul.simple-list{
103 ul.simple-list{
104 list-style: none;
104 list-style: none;
105 margin: 0;
105 margin: 0;
106 padding: 0;
106 padding: 0;
107 }
107 }
108
108
109 .main-content {
109 .main-content {
110 padding-bottom: @pagepadding;
110 padding-bottom: @pagepadding;
111 }
111 }
112
112
113 .wide-mode-wrapper {
113 .wide-mode-wrapper {
114 max-width:4000px !important;
114 max-width:4000px !important;
115 }
115 }
116
116
117 .wrapper {
117 .wrapper {
118 position: relative;
118 position: relative;
119 max-width: @wrapper-maxwidth;
119 max-width: @wrapper-maxwidth;
120 margin: 0 auto;
120 margin: 0 auto;
121 }
121 }
122
122
123 #content {
123 #content {
124 clear: both;
124 clear: both;
125 padding: 0 @contentpadding;
125 padding: 0 @contentpadding;
126 }
126 }
127
127
128 .advanced-settings-fields{
128 .advanced-settings-fields{
129 input{
129 input{
130 margin-left: @textmargin;
130 margin-left: @textmargin;
131 margin-right: @padding/2;
131 margin-right: @padding/2;
132 }
132 }
133 }
133 }
134
134
135 .cs_files_title {
135 .cs_files_title {
136 margin: @pagepadding 0 0;
136 margin: @pagepadding 0 0;
137 }
137 }
138
138
139 input.inline[type="file"] {
139 input.inline[type="file"] {
140 display: inline;
140 display: inline;
141 }
141 }
142
142
143 .error_page {
143 .error_page {
144 margin: 10% auto;
144 margin: 10% auto;
145
145
146 h1 {
146 h1 {
147 color: @grey2;
147 color: @grey2;
148 }
148 }
149
149
150 .alert {
150 .alert {
151 margin: @padding 0;
151 margin: @padding 0;
152 }
152 }
153
153
154 .error-branding {
154 .error-branding {
155 font-family: @text-semibold;
155 font-family: @text-semibold;
156 color: @grey4;
156 color: @grey4;
157 }
157 }
158
158
159 .error_message {
159 .error_message {
160 font-family: @text-regular;
160 font-family: @text-regular;
161 }
161 }
162
162
163 .sidebar {
163 .sidebar {
164 min-height: 275px;
164 min-height: 275px;
165 margin: 0;
165 margin: 0;
166 padding: 0 0 @sidebarpadding @sidebarpadding;
166 padding: 0 0 @sidebarpadding @sidebarpadding;
167 border: none;
167 border: none;
168 }
168 }
169
169
170 .main-content {
170 .main-content {
171 position: relative;
171 position: relative;
172 margin: 0 @sidebarpadding @sidebarpadding;
172 margin: 0 @sidebarpadding @sidebarpadding;
173 padding: 0 0 0 @sidebarpadding;
173 padding: 0 0 0 @sidebarpadding;
174 border-left: @border-thickness solid @grey5;
174 border-left: @border-thickness solid @grey5;
175
175
176 @media (max-width:767px) {
176 @media (max-width:767px) {
177 clear: both;
177 clear: both;
178 width: 100%;
178 width: 100%;
179 margin: 0;
179 margin: 0;
180 border: none;
180 border: none;
181 }
181 }
182 }
182 }
183
183
184 .inner-column {
184 .inner-column {
185 float: left;
185 float: left;
186 width: 29.75%;
186 width: 29.75%;
187 min-height: 150px;
187 min-height: 150px;
188 margin: @sidebarpadding 2% 0 0;
188 margin: @sidebarpadding 2% 0 0;
189 padding: 0 2% 0 0;
189 padding: 0 2% 0 0;
190 border-right: @border-thickness solid @grey5;
190 border-right: @border-thickness solid @grey5;
191
191
192 @media (max-width:767px) {
192 @media (max-width:767px) {
193 clear: both;
193 clear: both;
194 width: 100%;
194 width: 100%;
195 border: none;
195 border: none;
196 }
196 }
197
197
198 ul {
198 ul {
199 padding-left: 1.25em;
199 padding-left: 1.25em;
200 }
200 }
201
201
202 &:last-child {
202 &:last-child {
203 margin: @sidebarpadding 0 0;
203 margin: @sidebarpadding 0 0;
204 border: none;
204 border: none;
205 }
205 }
206
206
207 h4 {
207 h4 {
208 margin: 0 0 @padding;
208 margin: 0 0 @padding;
209 font-family: @text-semibold;
209 font-family: @text-semibold;
210 }
210 }
211 }
211 }
212 }
212 }
213 .error-page-logo {
213 .error-page-logo {
214 width: 130px;
214 width: 130px;
215 height: 160px;
215 height: 160px;
216 }
216 }
217
217
218 // HEADER
218 // HEADER
219 .header {
219 .header {
220
220
221 // TODO: johbo: Fix login pages, so that they work without a min-height
221 // TODO: johbo: Fix login pages, so that they work without a min-height
222 // for the header and then remove the min-height. I chose a smaller value
222 // for the header and then remove the min-height. I chose a smaller value
223 // intentionally here to avoid rendering issues in the main navigation.
223 // intentionally here to avoid rendering issues in the main navigation.
224 min-height: 49px;
224 min-height: 49px;
225
225
226 position: relative;
226 position: relative;
227 vertical-align: bottom;
227 vertical-align: bottom;
228 padding: 0 @header-padding;
228 padding: 0 @header-padding;
229 background-color: @grey2;
229 background-color: @grey2;
230 color: @grey5;
230 color: @grey5;
231
231
232 .title {
232 .title {
233 overflow: visible;
233 overflow: visible;
234 }
234 }
235
235
236 &:before,
236 &:before,
237 &:after {
237 &:after {
238 content: "";
238 content: "";
239 clear: both;
239 clear: both;
240 width: 100%;
240 width: 100%;
241 }
241 }
242
242
243 // TODO: johbo: Avoids breaking "Repositories" chooser
243 // TODO: johbo: Avoids breaking "Repositories" chooser
244 .select2-container .select2-choice .select2-arrow {
244 .select2-container .select2-choice .select2-arrow {
245 display: none;
245 display: none;
246 }
246 }
247 }
247 }
248
248
249 #header-inner {
249 #header-inner {
250 &.title {
250 &.title {
251 margin: 0;
251 margin: 0;
252 }
252 }
253 &:before,
253 &:before,
254 &:after {
254 &:after {
255 content: "";
255 content: "";
256 clear: both;
256 clear: both;
257 }
257 }
258 }
258 }
259
259
260 // Gists
260 // Gists
261 #files_data {
261 #files_data {
262 clear: both; //for firefox
262 clear: both; //for firefox
263 }
263 }
264 #gistid {
264 #gistid {
265 margin-right: @padding;
265 margin-right: @padding;
266 }
266 }
267
267
268 // Global Settings Editor
268 // Global Settings Editor
269 .textarea.editor {
269 .textarea.editor {
270 float: left;
270 float: left;
271 position: relative;
271 position: relative;
272 max-width: @texteditor-width;
272 max-width: @texteditor-width;
273
273
274 select {
274 select {
275 position: absolute;
275 position: absolute;
276 top:10px;
276 top:10px;
277 right:0;
277 right:0;
278 }
278 }
279
279
280 .CodeMirror {
280 .CodeMirror {
281 margin: 0;
281 margin: 0;
282 }
282 }
283
283
284 .help-block {
284 .help-block {
285 margin: 0 0 @padding;
285 margin: 0 0 @padding;
286 padding:.5em;
286 padding:.5em;
287 background-color: @grey6;
287 background-color: @grey6;
288 &.pre-formatting {
289 white-space: pre;
290 }
288 }
291 }
289 }
292 }
290
293
291 ul.auth_plugins {
294 ul.auth_plugins {
292 margin: @padding 0 @padding @legend-width;
295 margin: @padding 0 @padding @legend-width;
293 padding: 0;
296 padding: 0;
294
297
295 li {
298 li {
296 margin-bottom: @padding;
299 margin-bottom: @padding;
297 line-height: 1em;
300 line-height: 1em;
298 list-style-type: none;
301 list-style-type: none;
299
302
300 .auth_buttons .btn {
303 .auth_buttons .btn {
301 margin-right: @padding;
304 margin-right: @padding;
302 }
305 }
303
306
304 &:before { content: none; }
307 &:before { content: none; }
305 }
308 }
306 }
309 }
307
310
308
311
309 // My Account PR list
312 // My Account PR list
310
313
311 #show_closed {
314 #show_closed {
312 margin: 0 1em 0 0;
315 margin: 0 1em 0 0;
313 }
316 }
314
317
315 .pullrequestlist {
318 .pullrequestlist {
316 .closed {
319 .closed {
317 background-color: @grey6;
320 background-color: @grey6;
318 }
321 }
319 .td-status {
322 .td-status {
320 padding-left: .5em;
323 padding-left: .5em;
321 }
324 }
322 .log-container .truncate {
325 .log-container .truncate {
323 height: 2.75em;
326 height: 2.75em;
324 white-space: pre-line;
327 white-space: pre-line;
325 }
328 }
326 table.rctable .user {
329 table.rctable .user {
327 padding-left: 0;
330 padding-left: 0;
328 }
331 }
329 table.rctable {
332 table.rctable {
330 td.td-description,
333 td.td-description,
331 .rc-user {
334 .rc-user {
332 min-width: auto;
335 min-width: auto;
333 }
336 }
334 }
337 }
335 }
338 }
336
339
337 // Pull Requests
340 // Pull Requests
338
341
339 .pullrequests_section_head {
342 .pullrequests_section_head {
340 display: block;
343 display: block;
341 clear: both;
344 clear: both;
342 margin: @padding 0;
345 margin: @padding 0;
343 font-family: @text-bold;
346 font-family: @text-bold;
344 }
347 }
345
348
346 .pr-origininfo, .pr-targetinfo {
349 .pr-origininfo, .pr-targetinfo {
347 position: relative;
350 position: relative;
348
351
349 .tag {
352 .tag {
350 display: inline-block;
353 display: inline-block;
351 margin: 0 1em .5em 0;
354 margin: 0 1em .5em 0;
352 }
355 }
353
356
354 .clone-url {
357 .clone-url {
355 display: inline-block;
358 display: inline-block;
356 margin: 0 0 .5em 0;
359 margin: 0 0 .5em 0;
357 padding: 0;
360 padding: 0;
358 line-height: 1.2em;
361 line-height: 1.2em;
359 }
362 }
360 }
363 }
361
364
362 .pr-mergeinfo {
365 .pr-mergeinfo {
363 clear: both;
366 clear: both;
364 margin: .5em 0;
367 margin: .5em 0;
365
368
366 input {
369 input {
367 min-width: 100% !important;
370 min-width: 100% !important;
368 padding: 0 !important;
371 padding: 0 !important;
369 border: 0;
372 border: 0;
370 }
373 }
371 }
374 }
372
375
373 .pr-pullinfo {
376 .pr-pullinfo {
374 clear: both;
377 clear: both;
375 margin: .5em 0;
378 margin: .5em 0;
376
379
377 input {
380 input {
378 min-width: 100% !important;
381 min-width: 100% !important;
379 padding: 0 !important;
382 padding: 0 !important;
380 border: 0;
383 border: 0;
381 }
384 }
382 }
385 }
383
386
384 #pr-title-input {
387 #pr-title-input {
385 width: 72%;
388 width: 72%;
386 font-size: 1em;
389 font-size: 1em;
387 font-family: @text-bold;
390 font-family: @text-bold;
388 margin: 0;
391 margin: 0;
389 padding: 0 0 0 @padding/4;
392 padding: 0 0 0 @padding/4;
390 line-height: 1.7em;
393 line-height: 1.7em;
391 color: @text-color;
394 color: @text-color;
392 letter-spacing: .02em;
395 letter-spacing: .02em;
393 }
396 }
394
397
395 #pullrequest_title {
398 #pullrequest_title {
396 width: 100%;
399 width: 100%;
397 box-sizing: border-box;
400 box-sizing: border-box;
398 }
401 }
399
402
400 #pr_open_message {
403 #pr_open_message {
401 border: @border-thickness solid #fff;
404 border: @border-thickness solid #fff;
402 border-radius: @border-radius;
405 border-radius: @border-radius;
403 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
406 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
404 text-align: left;
407 text-align: left;
405 overflow: hidden;
408 overflow: hidden;
406 }
409 }
407
410
408 .pr-submit-button {
411 .pr-submit-button {
409 float: right;
412 float: right;
410 margin: 0 0 0 5px;
413 margin: 0 0 0 5px;
411 }
414 }
412
415
413 .pr-spacing-container {
416 .pr-spacing-container {
414 padding: 20px;
417 padding: 20px;
415 clear: both
418 clear: both
416 }
419 }
417
420
418 #pr-description-input {
421 #pr-description-input {
419 margin-bottom: 0;
422 margin-bottom: 0;
420 }
423 }
421
424
422 .pr-description-label {
425 .pr-description-label {
423 vertical-align: top;
426 vertical-align: top;
424 }
427 }
425
428
426 .perms_section_head {
429 .perms_section_head {
427 min-width: 625px;
430 min-width: 625px;
428
431
429 h2 {
432 h2 {
430 margin-bottom: 0;
433 margin-bottom: 0;
431 }
434 }
432
435
433 .label-checkbox {
436 .label-checkbox {
434 float: left;
437 float: left;
435 }
438 }
436
439
437 &.field {
440 &.field {
438 margin: @space 0 @padding;
441 margin: @space 0 @padding;
439 }
442 }
440
443
441 &:first-child.field {
444 &:first-child.field {
442 margin-top: 0;
445 margin-top: 0;
443
446
444 .label {
447 .label {
445 margin-top: 0;
448 margin-top: 0;
446 padding-top: 0;
449 padding-top: 0;
447 }
450 }
448
451
449 .radios {
452 .radios {
450 padding-top: 0;
453 padding-top: 0;
451 }
454 }
452 }
455 }
453
456
454 .radios {
457 .radios {
455 float: right;
458 float: right;
456 position: relative;
459 position: relative;
457 width: 405px;
460 width: 405px;
458 }
461 }
459 }
462 }
460
463
461 //--- MODULES ------------------//
464 //--- MODULES ------------------//
462
465
463
466
464 // Server Announcement
467 // Server Announcement
465 #server-announcement {
468 #server-announcement {
466 width: 95%;
469 width: 95%;
467 margin: @padding auto;
470 margin: @padding auto;
468 padding: @padding;
471 padding: @padding;
469 border-width: 2px;
472 border-width: 2px;
470 border-style: solid;
473 border-style: solid;
471 .border-radius(2px);
474 .border-radius(2px);
472 font-family: @text-bold;
475 font-family: @text-bold;
473
476
474 &.info { border-color: @alert4; background-color: @alert4-inner; }
477 &.info { border-color: @alert4; background-color: @alert4-inner; }
475 &.warning { border-color: @alert3; background-color: @alert3-inner; }
478 &.warning { border-color: @alert3; background-color: @alert3-inner; }
476 &.error { border-color: @alert2; background-color: @alert2-inner; }
479 &.error { border-color: @alert2; background-color: @alert2-inner; }
477 &.success { border-color: @alert1; background-color: @alert1-inner; }
480 &.success { border-color: @alert1; background-color: @alert1-inner; }
478 &.neutral { border-color: @grey3; background-color: @grey6; }
481 &.neutral { border-color: @grey3; background-color: @grey6; }
479 }
482 }
480
483
481 // Fixed Sidebar Column
484 // Fixed Sidebar Column
482 .sidebar-col-wrapper {
485 .sidebar-col-wrapper {
483 padding-left: @sidebar-all-width;
486 padding-left: @sidebar-all-width;
484
487
485 .sidebar {
488 .sidebar {
486 width: @sidebar-width;
489 width: @sidebar-width;
487 margin-left: -@sidebar-all-width;
490 margin-left: -@sidebar-all-width;
488 }
491 }
489 }
492 }
490
493
491 .sidebar-col-wrapper.scw-small {
494 .sidebar-col-wrapper.scw-small {
492 padding-left: @sidebar-small-all-width;
495 padding-left: @sidebar-small-all-width;
493
496
494 .sidebar {
497 .sidebar {
495 width: @sidebar-small-width;
498 width: @sidebar-small-width;
496 margin-left: -@sidebar-small-all-width;
499 margin-left: -@sidebar-small-all-width;
497 }
500 }
498 }
501 }
499
502
500
503
501 // FOOTER
504 // FOOTER
502 #footer {
505 #footer {
503 padding: 0;
506 padding: 0;
504 text-align: center;
507 text-align: center;
505 vertical-align: middle;
508 vertical-align: middle;
506 color: @grey2;
509 color: @grey2;
507 background-color: @grey6;
510 background-color: @grey6;
508
511
509 p {
512 p {
510 margin: 0;
513 margin: 0;
511 padding: 1em;
514 padding: 1em;
512 line-height: 1em;
515 line-height: 1em;
513 }
516 }
514
517
515 .server-instance { //server instance
518 .server-instance { //server instance
516 display: none;
519 display: none;
517 }
520 }
518
521
519 .title {
522 .title {
520 float: none;
523 float: none;
521 margin: 0 auto;
524 margin: 0 auto;
522 }
525 }
523 }
526 }
524
527
525 button.close {
528 button.close {
526 padding: 0;
529 padding: 0;
527 cursor: pointer;
530 cursor: pointer;
528 background: transparent;
531 background: transparent;
529 border: 0;
532 border: 0;
530 .box-shadow(none);
533 .box-shadow(none);
531 -webkit-appearance: none;
534 -webkit-appearance: none;
532 }
535 }
533
536
534 .close {
537 .close {
535 float: right;
538 float: right;
536 font-size: 21px;
539 font-size: 21px;
537 font-family: @text-bootstrap;
540 font-family: @text-bootstrap;
538 line-height: 1em;
541 line-height: 1em;
539 font-weight: bold;
542 font-weight: bold;
540 color: @grey2;
543 color: @grey2;
541
544
542 &:hover,
545 &:hover,
543 &:focus {
546 &:focus {
544 color: @grey1;
547 color: @grey1;
545 text-decoration: none;
548 text-decoration: none;
546 cursor: pointer;
549 cursor: pointer;
547 }
550 }
548 }
551 }
549
552
550 // GRID
553 // GRID
551 .sorting,
554 .sorting,
552 .sorting_desc,
555 .sorting_desc,
553 .sorting_asc {
556 .sorting_asc {
554 cursor: pointer;
557 cursor: pointer;
555 }
558 }
556 .sorting_desc:after {
559 .sorting_desc:after {
557 content: "\00A0\25B2";
560 content: "\00A0\25B2";
558 font-size: .75em;
561 font-size: .75em;
559 }
562 }
560 .sorting_asc:after {
563 .sorting_asc:after {
561 content: "\00A0\25BC";
564 content: "\00A0\25BC";
562 font-size: .68em;
565 font-size: .68em;
563 }
566 }
564
567
565
568
566 .user_auth_tokens {
569 .user_auth_tokens {
567
570
568 &.truncate {
571 &.truncate {
569 white-space: nowrap;
572 white-space: nowrap;
570 overflow: hidden;
573 overflow: hidden;
571 text-overflow: ellipsis;
574 text-overflow: ellipsis;
572 }
575 }
573
576
574 .fields .field .input {
577 .fields .field .input {
575 margin: 0;
578 margin: 0;
576 }
579 }
577
580
578 input#description {
581 input#description {
579 width: 100px;
582 width: 100px;
580 margin: 0;
583 margin: 0;
581 }
584 }
582
585
583 .drop-menu {
586 .drop-menu {
584 // TODO: johbo: Remove this, should work out of the box when
587 // TODO: johbo: Remove this, should work out of the box when
585 // having multiple inputs inline
588 // having multiple inputs inline
586 margin: 0 0 0 5px;
589 margin: 0 0 0 5px;
587 }
590 }
588 }
591 }
589 #user_list_table {
592 #user_list_table {
590 .closed {
593 .closed {
591 background-color: @grey6;
594 background-color: @grey6;
592 }
595 }
593 }
596 }
594
597
595
598
596 input {
599 input {
597 &.disabled {
600 &.disabled {
598 opacity: .5;
601 opacity: .5;
599 }
602 }
600 }
603 }
601
604
602 // remove extra padding in firefox
605 // remove extra padding in firefox
603 input::-moz-focus-inner { border:0; padding:0 }
606 input::-moz-focus-inner { border:0; padding:0 }
604
607
605 .adjacent input {
608 .adjacent input {
606 margin-bottom: @padding;
609 margin-bottom: @padding;
607 }
610 }
608
611
609 .permissions_boxes {
612 .permissions_boxes {
610 display: block;
613 display: block;
611 }
614 }
612
615
613 //TODO: lisa: this should be in tables
616 //TODO: lisa: this should be in tables
614 .show_more_col {
617 .show_more_col {
615 width: 20px;
618 width: 20px;
616 }
619 }
617
620
618 //FORMS
621 //FORMS
619
622
620 .medium-inline,
623 .medium-inline,
621 input#description.medium-inline {
624 input#description.medium-inline {
622 display: inline;
625 display: inline;
623 width: @medium-inline-input-width;
626 width: @medium-inline-input-width;
624 min-width: 100px;
627 min-width: 100px;
625 }
628 }
626
629
627 select {
630 select {
628 //reset
631 //reset
629 -webkit-appearance: none;
632 -webkit-appearance: none;
630 -moz-appearance: none;
633 -moz-appearance: none;
631
634
632 display: inline-block;
635 display: inline-block;
633 height: 28px;
636 height: 28px;
634 width: auto;
637 width: auto;
635 margin: 0 @padding @padding 0;
638 margin: 0 @padding @padding 0;
636 padding: 0 18px 0 8px;
639 padding: 0 18px 0 8px;
637 line-height:1em;
640 line-height:1em;
638 font-size: @basefontsize;
641 font-size: @basefontsize;
639 border: @border-thickness solid @rcblue;
642 border: @border-thickness solid @rcblue;
640 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
643 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
641 color: @rcblue;
644 color: @rcblue;
642
645
643 &:after {
646 &:after {
644 content: "\00A0\25BE";
647 content: "\00A0\25BE";
645 }
648 }
646
649
647 &:focus {
650 &:focus {
648 outline: none;
651 outline: none;
649 }
652 }
650 }
653 }
651
654
652 option {
655 option {
653 &:focus {
656 &:focus {
654 outline: none;
657 outline: none;
655 }
658 }
656 }
659 }
657
660
658 input,
661 input,
659 textarea {
662 textarea {
660 padding: @input-padding;
663 padding: @input-padding;
661 border: @input-border-thickness solid @border-highlight-color;
664 border: @input-border-thickness solid @border-highlight-color;
662 .border-radius (@border-radius);
665 .border-radius (@border-radius);
663 font-family: @text-light;
666 font-family: @text-light;
664 font-size: @basefontsize;
667 font-size: @basefontsize;
665
668
666 &.input-sm {
669 &.input-sm {
667 padding: 5px;
670 padding: 5px;
668 }
671 }
669
672
670 &#description {
673 &#description {
671 min-width: @input-description-minwidth;
674 min-width: @input-description-minwidth;
672 min-height: 1em;
675 min-height: 1em;
673 padding: 10px;
676 padding: 10px;
674 }
677 }
675 }
678 }
676
679
677 .field-sm {
680 .field-sm {
678 input,
681 input,
679 textarea {
682 textarea {
680 padding: 5px;
683 padding: 5px;
681 }
684 }
682 }
685 }
683
686
684 textarea {
687 textarea {
685 display: block;
688 display: block;
686 clear: both;
689 clear: both;
687 width: 100%;
690 width: 100%;
688 min-height: 100px;
691 min-height: 100px;
689 margin-bottom: @padding;
692 margin-bottom: @padding;
690 .box-sizing(border-box);
693 .box-sizing(border-box);
691 overflow: auto;
694 overflow: auto;
692 }
695 }
693
696
694 label {
697 label {
695 font-family: @text-light;
698 font-family: @text-light;
696 }
699 }
697
700
698 // GRAVATARS
701 // GRAVATARS
699 // centers gravatar on username to the right
702 // centers gravatar on username to the right
700
703
701 .gravatar {
704 .gravatar {
702 display: inline;
705 display: inline;
703 min-width: 16px;
706 min-width: 16px;
704 min-height: 16px;
707 min-height: 16px;
705 margin: -5px 0;
708 margin: -5px 0;
706 padding: 0;
709 padding: 0;
707 line-height: 1em;
710 line-height: 1em;
708 border: 1px solid @grey4;
711 border: 1px solid @grey4;
709 box-sizing: content-box;
712 box-sizing: content-box;
710
713
711 &.gravatar-large {
714 &.gravatar-large {
712 margin: -0.5em .25em -0.5em 0;
715 margin: -0.5em .25em -0.5em 0;
713 }
716 }
714
717
715 & + .user {
718 & + .user {
716 display: inline;
719 display: inline;
717 margin: 0;
720 margin: 0;
718 padding: 0 0 0 .17em;
721 padding: 0 0 0 .17em;
719 line-height: 1em;
722 line-height: 1em;
720 }
723 }
721 }
724 }
722
725
723 .user-inline-data {
726 .user-inline-data {
724 display: inline-block;
727 display: inline-block;
725 float: left;
728 float: left;
726 padding-left: .5em;
729 padding-left: .5em;
727 line-height: 1.3em;
730 line-height: 1.3em;
728 }
731 }
729
732
730 .rc-user { // gravatar + user wrapper
733 .rc-user { // gravatar + user wrapper
731 float: left;
734 float: left;
732 position: relative;
735 position: relative;
733 min-width: 100px;
736 min-width: 100px;
734 max-width: 200px;
737 max-width: 200px;
735 min-height: (@gravatar-size + @border-thickness * 2); // account for border
738 min-height: (@gravatar-size + @border-thickness * 2); // account for border
736 display: block;
739 display: block;
737 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
740 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
738
741
739
742
740 .gravatar {
743 .gravatar {
741 display: block;
744 display: block;
742 position: absolute;
745 position: absolute;
743 top: 0;
746 top: 0;
744 left: 0;
747 left: 0;
745 min-width: @gravatar-size;
748 min-width: @gravatar-size;
746 min-height: @gravatar-size;
749 min-height: @gravatar-size;
747 margin: 0;
750 margin: 0;
748 }
751 }
749
752
750 .user {
753 .user {
751 display: block;
754 display: block;
752 max-width: 175px;
755 max-width: 175px;
753 padding-top: 2px;
756 padding-top: 2px;
754 overflow: hidden;
757 overflow: hidden;
755 text-overflow: ellipsis;
758 text-overflow: ellipsis;
756 }
759 }
757 }
760 }
758
761
759 .gist-gravatar,
762 .gist-gravatar,
760 .journal_container {
763 .journal_container {
761 .gravatar-large {
764 .gravatar-large {
762 margin: 0 .5em -10px 0;
765 margin: 0 .5em -10px 0;
763 }
766 }
764 }
767 }
765
768
766
769
767 // ADMIN SETTINGS
770 // ADMIN SETTINGS
768
771
769 // Tag Patterns
772 // Tag Patterns
770 .tag_patterns {
773 .tag_patterns {
771 .tag_input {
774 .tag_input {
772 margin-bottom: @padding;
775 margin-bottom: @padding;
773 }
776 }
774 }
777 }
775
778
776 .locked_input {
779 .locked_input {
777 position: relative;
780 position: relative;
778
781
779 input {
782 input {
780 display: inline;
783 display: inline;
781 margin: 3px 5px 0px 0px;
784 margin: 3px 5px 0px 0px;
782 }
785 }
783
786
784 br {
787 br {
785 display: none;
788 display: none;
786 }
789 }
787
790
788 .error-message {
791 .error-message {
789 float: left;
792 float: left;
790 width: 100%;
793 width: 100%;
791 }
794 }
792
795
793 .lock_input_button {
796 .lock_input_button {
794 display: inline;
797 display: inline;
795 }
798 }
796
799
797 .help-block {
800 .help-block {
798 clear: both;
801 clear: both;
799 }
802 }
800 }
803 }
801
804
802 // Notifications
805 // Notifications
803
806
804 .notifications_buttons {
807 .notifications_buttons {
805 margin: 0 0 @space 0;
808 margin: 0 0 @space 0;
806 padding: 0;
809 padding: 0;
807
810
808 .btn {
811 .btn {
809 display: inline-block;
812 display: inline-block;
810 }
813 }
811 }
814 }
812
815
813 .notification-list {
816 .notification-list {
814
817
815 div {
818 div {
816 display: inline-block;
819 display: inline-block;
817 vertical-align: middle;
820 vertical-align: middle;
818 }
821 }
819
822
820 .container {
823 .container {
821 display: block;
824 display: block;
822 margin: 0 0 @padding 0;
825 margin: 0 0 @padding 0;
823 }
826 }
824
827
825 .delete-notifications {
828 .delete-notifications {
826 margin-left: @padding;
829 margin-left: @padding;
827 text-align: right;
830 text-align: right;
828 cursor: pointer;
831 cursor: pointer;
829 }
832 }
830
833
831 .read-notifications {
834 .read-notifications {
832 margin-left: @padding/2;
835 margin-left: @padding/2;
833 text-align: right;
836 text-align: right;
834 width: 35px;
837 width: 35px;
835 cursor: pointer;
838 cursor: pointer;
836 }
839 }
837
840
838 .icon-minus-sign {
841 .icon-minus-sign {
839 color: @alert2;
842 color: @alert2;
840 }
843 }
841
844
842 .icon-ok-sign {
845 .icon-ok-sign {
843 color: @alert1;
846 color: @alert1;
844 }
847 }
845 }
848 }
846
849
847 .user_settings {
850 .user_settings {
848 float: left;
851 float: left;
849 clear: both;
852 clear: both;
850 display: block;
853 display: block;
851 width: 100%;
854 width: 100%;
852
855
853 .gravatar_box {
856 .gravatar_box {
854 margin-bottom: @padding;
857 margin-bottom: @padding;
855
858
856 &:after {
859 &:after {
857 content: " ";
860 content: " ";
858 clear: both;
861 clear: both;
859 width: 100%;
862 width: 100%;
860 }
863 }
861 }
864 }
862
865
863 .fields .field {
866 .fields .field {
864 clear: both;
867 clear: both;
865 }
868 }
866 }
869 }
867
870
868 .advanced_settings {
871 .advanced_settings {
869 margin-bottom: @space;
872 margin-bottom: @space;
870
873
871 .help-block {
874 .help-block {
872 margin-left: 0;
875 margin-left: 0;
873 }
876 }
874
877
875 button + .help-block {
878 button + .help-block {
876 margin-top: @padding;
879 margin-top: @padding;
877 }
880 }
878 }
881 }
879
882
880 // admin settings radio buttons and labels
883 // admin settings radio buttons and labels
881 .label-2 {
884 .label-2 {
882 float: left;
885 float: left;
883 width: @label2-width;
886 width: @label2-width;
884
887
885 label {
888 label {
886 color: @grey1;
889 color: @grey1;
887 }
890 }
888 }
891 }
889 .checkboxes {
892 .checkboxes {
890 float: left;
893 float: left;
891 width: @checkboxes-width;
894 width: @checkboxes-width;
892 margin-bottom: @padding;
895 margin-bottom: @padding;
893
896
894 .checkbox {
897 .checkbox {
895 width: 100%;
898 width: 100%;
896
899
897 label {
900 label {
898 margin: 0;
901 margin: 0;
899 padding: 0;
902 padding: 0;
900 }
903 }
901 }
904 }
902
905
903 .checkbox + .checkbox {
906 .checkbox + .checkbox {
904 display: inline-block;
907 display: inline-block;
905 }
908 }
906
909
907 label {
910 label {
908 margin-right: 1em;
911 margin-right: 1em;
909 }
912 }
910 }
913 }
911
914
912 // CHANGELOG
915 // CHANGELOG
913 .container_header {
916 .container_header {
914 float: left;
917 float: left;
915 display: block;
918 display: block;
916 width: 100%;
919 width: 100%;
917 margin: @padding 0 @padding;
920 margin: @padding 0 @padding;
918
921
919 #filter_changelog {
922 #filter_changelog {
920 float: left;
923 float: left;
921 margin-right: @padding;
924 margin-right: @padding;
922 }
925 }
923
926
924 .breadcrumbs_light {
927 .breadcrumbs_light {
925 display: inline-block;
928 display: inline-block;
926 }
929 }
927 }
930 }
928
931
929 .info_box {
932 .info_box {
930 float: right;
933 float: right;
931 }
934 }
932
935
933
936
934 #graph_nodes {
937 #graph_nodes {
935 padding-top: 43px;
938 padding-top: 43px;
936 }
939 }
937
940
938 #graph_content{
941 #graph_content{
939
942
940 // adjust for table headers so that graph renders properly
943 // adjust for table headers so that graph renders properly
941 // #graph_nodes padding - table cell padding
944 // #graph_nodes padding - table cell padding
942 padding-top: (@space - (@basefontsize * 2.4));
945 padding-top: (@space - (@basefontsize * 2.4));
943
946
944 &.graph_full_width {
947 &.graph_full_width {
945 width: 100%;
948 width: 100%;
946 max-width: 100%;
949 max-width: 100%;
947 }
950 }
948 }
951 }
949
952
950 #graph {
953 #graph {
951 .flag_status {
954 .flag_status {
952 margin: 0;
955 margin: 0;
953 }
956 }
954
957
955 .pagination-left {
958 .pagination-left {
956 float: left;
959 float: left;
957 clear: both;
960 clear: both;
958 }
961 }
959
962
960 .log-container {
963 .log-container {
961 max-width: 345px;
964 max-width: 345px;
962
965
963 .message{
966 .message{
964 max-width: 340px;
967 max-width: 340px;
965 }
968 }
966 }
969 }
967
970
968 .graph-col-wrapper {
971 .graph-col-wrapper {
969 padding-left: 110px;
972 padding-left: 110px;
970
973
971 #graph_nodes {
974 #graph_nodes {
972 width: 100px;
975 width: 100px;
973 margin-left: -110px;
976 margin-left: -110px;
974 float: left;
977 float: left;
975 clear: left;
978 clear: left;
976 }
979 }
977 }
980 }
978
981
979 .load-more-commits {
982 .load-more-commits {
980 text-align: center;
983 text-align: center;
981 }
984 }
982 .load-more-commits:hover {
985 .load-more-commits:hover {
983 background-color: @grey7;
986 background-color: @grey7;
984 }
987 }
985 .load-more-commits {
988 .load-more-commits {
986 a {
989 a {
987 display: block;
990 display: block;
988 }
991 }
989 }
992 }
990 }
993 }
991
994
992 #filter_changelog {
995 #filter_changelog {
993 float: left;
996 float: left;
994 }
997 }
995
998
996
999
997 //--- THEME ------------------//
1000 //--- THEME ------------------//
998
1001
999 #logo {
1002 #logo {
1000 float: left;
1003 float: left;
1001 margin: 9px 0 0 0;
1004 margin: 9px 0 0 0;
1002
1005
1003 .header {
1006 .header {
1004 background-color: transparent;
1007 background-color: transparent;
1005 }
1008 }
1006
1009
1007 a {
1010 a {
1008 display: inline-block;
1011 display: inline-block;
1009 }
1012 }
1010
1013
1011 img {
1014 img {
1012 height:30px;
1015 height:30px;
1013 }
1016 }
1014 }
1017 }
1015
1018
1016 .logo-wrapper {
1019 .logo-wrapper {
1017 float:left;
1020 float:left;
1018 }
1021 }
1019
1022
1020 .branding{
1023 .branding{
1021 float: left;
1024 float: left;
1022 padding: 9px 2px;
1025 padding: 9px 2px;
1023 line-height: 1em;
1026 line-height: 1em;
1024 font-size: @navigation-fontsize;
1027 font-size: @navigation-fontsize;
1025 }
1028 }
1026
1029
1027 img {
1030 img {
1028 border: none;
1031 border: none;
1029 outline: none;
1032 outline: none;
1030 }
1033 }
1031 user-profile-header
1034 user-profile-header
1032 label {
1035 label {
1033
1036
1034 input[type="checkbox"] {
1037 input[type="checkbox"] {
1035 margin-right: 1em;
1038 margin-right: 1em;
1036 }
1039 }
1037 input[type="radio"] {
1040 input[type="radio"] {
1038 margin-right: 1em;
1041 margin-right: 1em;
1039 }
1042 }
1040 }
1043 }
1041
1044
1042 .flag_status {
1045 .flag_status {
1043 margin: 2px 8px 6px 2px;
1046 margin: 2px 8px 6px 2px;
1044 &.under_review {
1047 &.under_review {
1045 .circle(5px, @alert3);
1048 .circle(5px, @alert3);
1046 }
1049 }
1047 &.approved {
1050 &.approved {
1048 .circle(5px, @alert1);
1051 .circle(5px, @alert1);
1049 }
1052 }
1050 &.rejected,
1053 &.rejected,
1051 &.forced_closed{
1054 &.forced_closed{
1052 .circle(5px, @alert2);
1055 .circle(5px, @alert2);
1053 }
1056 }
1054 &.not_reviewed {
1057 &.not_reviewed {
1055 .circle(5px, @grey5);
1058 .circle(5px, @grey5);
1056 }
1059 }
1057 }
1060 }
1058
1061
1059 .flag_status_comment_box {
1062 .flag_status_comment_box {
1060 margin: 5px 6px 0px 2px;
1063 margin: 5px 6px 0px 2px;
1061 }
1064 }
1062 .test_pattern_preview {
1065 .test_pattern_preview {
1063 margin: @space 0;
1066 margin: @space 0;
1064
1067
1065 p {
1068 p {
1066 margin-bottom: 0;
1069 margin-bottom: 0;
1067 border-bottom: @border-thickness solid @border-default-color;
1070 border-bottom: @border-thickness solid @border-default-color;
1068 color: @grey3;
1071 color: @grey3;
1069 }
1072 }
1070
1073
1071 .btn {
1074 .btn {
1072 margin-bottom: @padding;
1075 margin-bottom: @padding;
1073 }
1076 }
1074 }
1077 }
1075 #test_pattern_result {
1078 #test_pattern_result {
1076 display: none;
1079 display: none;
1077 &:extend(pre);
1080 &:extend(pre);
1078 padding: .9em;
1081 padding: .9em;
1079 color: @grey3;
1082 color: @grey3;
1080 background-color: @grey7;
1083 background-color: @grey7;
1081 border-right: @border-thickness solid @border-default-color;
1084 border-right: @border-thickness solid @border-default-color;
1082 border-bottom: @border-thickness solid @border-default-color;
1085 border-bottom: @border-thickness solid @border-default-color;
1083 border-left: @border-thickness solid @border-default-color;
1086 border-left: @border-thickness solid @border-default-color;
1084 }
1087 }
1085
1088
1086 #repo_vcs_settings {
1089 #repo_vcs_settings {
1087 #inherit_overlay_vcs_default {
1090 #inherit_overlay_vcs_default {
1088 display: none;
1091 display: none;
1089 }
1092 }
1090 #inherit_overlay_vcs_custom {
1093 #inherit_overlay_vcs_custom {
1091 display: custom;
1094 display: custom;
1092 }
1095 }
1093 &.inherited {
1096 &.inherited {
1094 #inherit_overlay_vcs_default {
1097 #inherit_overlay_vcs_default {
1095 display: block;
1098 display: block;
1096 }
1099 }
1097 #inherit_overlay_vcs_custom {
1100 #inherit_overlay_vcs_custom {
1098 display: none;
1101 display: none;
1099 }
1102 }
1100 }
1103 }
1101 }
1104 }
1102
1105
1103 .issue-tracker-link {
1106 .issue-tracker-link {
1104 color: @rcblue;
1107 color: @rcblue;
1105 }
1108 }
1106
1109
1107 // Issue Tracker Table Show/Hide
1110 // Issue Tracker Table Show/Hide
1108 #repo_issue_tracker {
1111 #repo_issue_tracker {
1109 #inherit_overlay {
1112 #inherit_overlay {
1110 display: none;
1113 display: none;
1111 }
1114 }
1112 #custom_overlay {
1115 #custom_overlay {
1113 display: custom;
1116 display: custom;
1114 }
1117 }
1115 &.inherited {
1118 &.inherited {
1116 #inherit_overlay {
1119 #inherit_overlay {
1117 display: block;
1120 display: block;
1118 }
1121 }
1119 #custom_overlay {
1122 #custom_overlay {
1120 display: none;
1123 display: none;
1121 }
1124 }
1122 }
1125 }
1123 }
1126 }
1124 table.issuetracker {
1127 table.issuetracker {
1125 &.readonly {
1128 &.readonly {
1126 tr, td {
1129 tr, td {
1127 color: @grey3;
1130 color: @grey3;
1128 }
1131 }
1129 }
1132 }
1130 .edit {
1133 .edit {
1131 display: none;
1134 display: none;
1132 }
1135 }
1133 .editopen {
1136 .editopen {
1134 .edit {
1137 .edit {
1135 display: inline;
1138 display: inline;
1136 }
1139 }
1137 .entry {
1140 .entry {
1138 display: none;
1141 display: none;
1139 }
1142 }
1140 }
1143 }
1141 tr td.td-action {
1144 tr td.td-action {
1142 min-width: 117px;
1145 min-width: 117px;
1143 }
1146 }
1144 td input {
1147 td input {
1145 max-width: none;
1148 max-width: none;
1146 min-width: 30px;
1149 min-width: 30px;
1147 width: 80%;
1150 width: 80%;
1148 }
1151 }
1149 .issuetracker_pref input {
1152 .issuetracker_pref input {
1150 width: 40%;
1153 width: 40%;
1151 }
1154 }
1152 input.edit_issuetracker_update {
1155 input.edit_issuetracker_update {
1153 margin-right: 0;
1156 margin-right: 0;
1154 width: auto;
1157 width: auto;
1155 }
1158 }
1156 }
1159 }
1157
1160
1158 table.integrations {
1161 table.integrations {
1159 .td-icon {
1162 .td-icon {
1160 width: 20px;
1163 width: 20px;
1161 .integration-icon {
1164 .integration-icon {
1162 height: 20px;
1165 height: 20px;
1163 width: 20px;
1166 width: 20px;
1164 }
1167 }
1165 }
1168 }
1166 }
1169 }
1167
1170
1168 .integrations {
1171 .integrations {
1169 a.integration-box {
1172 a.integration-box {
1170 color: @text-color;
1173 color: @text-color;
1171 &:hover {
1174 &:hover {
1172 .panel {
1175 .panel {
1173 background: #fbfbfb;
1176 background: #fbfbfb;
1174 }
1177 }
1175 }
1178 }
1176 .integration-icon {
1179 .integration-icon {
1177 width: 30px;
1180 width: 30px;
1178 height: 30px;
1181 height: 30px;
1179 margin-right: 20px;
1182 margin-right: 20px;
1180 float: left;
1183 float: left;
1181 }
1184 }
1182
1185
1183 .panel-body {
1186 .panel-body {
1184 padding: 10px;
1187 padding: 10px;
1185 }
1188 }
1186 .panel {
1189 .panel {
1187 margin-bottom: 10px;
1190 margin-bottom: 10px;
1188 }
1191 }
1189 h2 {
1192 h2 {
1190 display: inline-block;
1193 display: inline-block;
1191 margin: 0;
1194 margin: 0;
1192 min-width: 140px;
1195 min-width: 140px;
1193 }
1196 }
1194 }
1197 }
1195 }
1198 }
1196
1199
1197 //Permissions Settings
1200 //Permissions Settings
1198 #add_perm {
1201 #add_perm {
1199 margin: 0 0 @padding;
1202 margin: 0 0 @padding;
1200 cursor: pointer;
1203 cursor: pointer;
1201 }
1204 }
1202
1205
1203 .perm_ac {
1206 .perm_ac {
1204 input {
1207 input {
1205 width: 95%;
1208 width: 95%;
1206 }
1209 }
1207 }
1210 }
1208
1211
1209 .autocomplete-suggestions {
1212 .autocomplete-suggestions {
1210 width: auto !important; // overrides autocomplete.js
1213 width: auto !important; // overrides autocomplete.js
1211 margin: 0;
1214 margin: 0;
1212 border: @border-thickness solid @rcblue;
1215 border: @border-thickness solid @rcblue;
1213 border-radius: @border-radius;
1216 border-radius: @border-radius;
1214 color: @rcblue;
1217 color: @rcblue;
1215 background-color: white;
1218 background-color: white;
1216 }
1219 }
1217 .autocomplete-selected {
1220 .autocomplete-selected {
1218 background: #F0F0F0;
1221 background: #F0F0F0;
1219 }
1222 }
1220 .ac-container-wrap {
1223 .ac-container-wrap {
1221 margin: 0;
1224 margin: 0;
1222 padding: 8px;
1225 padding: 8px;
1223 border-bottom: @border-thickness solid @rclightblue;
1226 border-bottom: @border-thickness solid @rclightblue;
1224 list-style-type: none;
1227 list-style-type: none;
1225 cursor: pointer;
1228 cursor: pointer;
1226
1229
1227 &:hover {
1230 &:hover {
1228 background-color: @rclightblue;
1231 background-color: @rclightblue;
1229 }
1232 }
1230
1233
1231 img {
1234 img {
1232 height: @gravatar-size;
1235 height: @gravatar-size;
1233 width: @gravatar-size;
1236 width: @gravatar-size;
1234 margin-right: 1em;
1237 margin-right: 1em;
1235 }
1238 }
1236
1239
1237 strong {
1240 strong {
1238 font-weight: normal;
1241 font-weight: normal;
1239 }
1242 }
1240 }
1243 }
1241
1244
1242 // Settings Dropdown
1245 // Settings Dropdown
1243 .user-menu .container {
1246 .user-menu .container {
1244 padding: 0 4px;
1247 padding: 0 4px;
1245 margin: 0;
1248 margin: 0;
1246 }
1249 }
1247
1250
1248 .user-menu .gravatar {
1251 .user-menu .gravatar {
1249 cursor: pointer;
1252 cursor: pointer;
1250 }
1253 }
1251
1254
1252 .codeblock {
1255 .codeblock {
1253 margin-bottom: @padding;
1256 margin-bottom: @padding;
1254 clear: both;
1257 clear: both;
1255
1258
1256 .stats{
1259 .stats{
1257 overflow: hidden;
1260 overflow: hidden;
1258 }
1261 }
1259
1262
1260 .message{
1263 .message{
1261 textarea{
1264 textarea{
1262 margin: 0;
1265 margin: 0;
1263 }
1266 }
1264 }
1267 }
1265
1268
1266 .code-header {
1269 .code-header {
1267 .stats {
1270 .stats {
1268 line-height: 2em;
1271 line-height: 2em;
1269
1272
1270 .revision_id {
1273 .revision_id {
1271 margin-left: 0;
1274 margin-left: 0;
1272 }
1275 }
1273 .buttons {
1276 .buttons {
1274 padding-right: 0;
1277 padding-right: 0;
1275 }
1278 }
1276 }
1279 }
1277
1280
1278 .item{
1281 .item{
1279 margin-right: 0.5em;
1282 margin-right: 0.5em;
1280 }
1283 }
1281 }
1284 }
1282
1285
1283 #editor_container{
1286 #editor_container{
1284 position: relative;
1287 position: relative;
1285 margin: @padding;
1288 margin: @padding;
1286 }
1289 }
1287 }
1290 }
1288
1291
1289 #file_history_container {
1292 #file_history_container {
1290 display: none;
1293 display: none;
1291 }
1294 }
1292
1295
1293 .file-history-inner {
1296 .file-history-inner {
1294 margin-bottom: 10px;
1297 margin-bottom: 10px;
1295 }
1298 }
1296
1299
1297 // Pull Requests
1300 // Pull Requests
1298 .summary-details {
1301 .summary-details {
1299 width: 72%;
1302 width: 72%;
1300 }
1303 }
1301 .pr-summary {
1304 .pr-summary {
1302 border-bottom: @border-thickness solid @grey5;
1305 border-bottom: @border-thickness solid @grey5;
1303 margin-bottom: @space;
1306 margin-bottom: @space;
1304 }
1307 }
1305 .reviewers-title {
1308 .reviewers-title {
1306 width: 25%;
1309 width: 25%;
1307 min-width: 200px;
1310 min-width: 200px;
1308 }
1311 }
1309 .reviewers {
1312 .reviewers {
1310 width: 25%;
1313 width: 25%;
1311 min-width: 200px;
1314 min-width: 200px;
1312 }
1315 }
1313 .reviewers ul li {
1316 .reviewers ul li {
1314 position: relative;
1317 position: relative;
1315 width: 100%;
1318 width: 100%;
1316 margin-bottom: 8px;
1319 margin-bottom: 8px;
1317 }
1320 }
1318
1321
1319 .reviewer_entry {
1322 .reviewer_entry {
1320 min-height: 55px;
1323 min-height: 55px;
1321 }
1324 }
1322
1325
1323 .reviewers_member {
1326 .reviewers_member {
1324 width: 100%;
1327 width: 100%;
1325 overflow: auto;
1328 overflow: auto;
1326 }
1329 }
1327 .reviewer_reason {
1330 .reviewer_reason {
1328 padding-left: 20px;
1331 padding-left: 20px;
1329 }
1332 }
1330 .reviewer_status {
1333 .reviewer_status {
1331 display: inline-block;
1334 display: inline-block;
1332 vertical-align: top;
1335 vertical-align: top;
1333 width: 7%;
1336 width: 7%;
1334 min-width: 20px;
1337 min-width: 20px;
1335 height: 1.2em;
1338 height: 1.2em;
1336 margin-top: 3px;
1339 margin-top: 3px;
1337 line-height: 1em;
1340 line-height: 1em;
1338 }
1341 }
1339
1342
1340 .reviewer_name {
1343 .reviewer_name {
1341 display: inline-block;
1344 display: inline-block;
1342 max-width: 83%;
1345 max-width: 83%;
1343 padding-right: 20px;
1346 padding-right: 20px;
1344 vertical-align: middle;
1347 vertical-align: middle;
1345 line-height: 1;
1348 line-height: 1;
1346
1349
1347 .rc-user {
1350 .rc-user {
1348 min-width: 0;
1351 min-width: 0;
1349 margin: -2px 1em 0 0;
1352 margin: -2px 1em 0 0;
1350 }
1353 }
1351
1354
1352 .reviewer {
1355 .reviewer {
1353 float: left;
1356 float: left;
1354 }
1357 }
1355 }
1358 }
1356
1359
1357 .reviewer_member_mandatory,
1360 .reviewer_member_mandatory,
1358 .reviewer_member_mandatory_remove,
1361 .reviewer_member_mandatory_remove,
1359 .reviewer_member_remove {
1362 .reviewer_member_remove {
1360 position: absolute;
1363 position: absolute;
1361 right: 0;
1364 right: 0;
1362 top: 0;
1365 top: 0;
1363 width: 16px;
1366 width: 16px;
1364 margin-bottom: 10px;
1367 margin-bottom: 10px;
1365 padding: 0;
1368 padding: 0;
1366 color: black;
1369 color: black;
1367 }
1370 }
1368
1371
1369 .reviewer_member_mandatory_remove {
1372 .reviewer_member_mandatory_remove {
1370 color: @grey4;
1373 color: @grey4;
1371 }
1374 }
1372
1375
1373 .reviewer_member_mandatory {
1376 .reviewer_member_mandatory {
1374 padding-top:20px;
1377 padding-top:20px;
1375 }
1378 }
1376
1379
1377 .reviewer_member_status {
1380 .reviewer_member_status {
1378 margin-top: 5px;
1381 margin-top: 5px;
1379 }
1382 }
1380 .pr-summary #summary{
1383 .pr-summary #summary{
1381 width: 100%;
1384 width: 100%;
1382 }
1385 }
1383 .pr-summary .action_button:hover {
1386 .pr-summary .action_button:hover {
1384 border: 0;
1387 border: 0;
1385 cursor: pointer;
1388 cursor: pointer;
1386 }
1389 }
1387 .pr-details-title {
1390 .pr-details-title {
1388 padding-bottom: 8px;
1391 padding-bottom: 8px;
1389 border-bottom: @border-thickness solid @grey5;
1392 border-bottom: @border-thickness solid @grey5;
1390
1393
1391 .action_button.disabled {
1394 .action_button.disabled {
1392 color: @grey4;
1395 color: @grey4;
1393 cursor: inherit;
1396 cursor: inherit;
1394 }
1397 }
1395 .action_button {
1398 .action_button {
1396 color: @rcblue;
1399 color: @rcblue;
1397 }
1400 }
1398 }
1401 }
1399 .pr-details-content {
1402 .pr-details-content {
1400 margin-top: @textmargin;
1403 margin-top: @textmargin;
1401 margin-bottom: @textmargin;
1404 margin-bottom: @textmargin;
1402 }
1405 }
1403 .pr-description {
1406 .pr-description {
1404 white-space:pre-wrap;
1407 white-space:pre-wrap;
1405 }
1408 }
1406
1409
1407 .pr-reviewer-rules {
1410 .pr-reviewer-rules {
1408 padding: 10px 0px 20px 0px;
1411 padding: 10px 0px 20px 0px;
1409 }
1412 }
1410
1413
1411 .group_members {
1414 .group_members {
1412 margin-top: 0;
1415 margin-top: 0;
1413 padding: 0;
1416 padding: 0;
1414 list-style: outside none none;
1417 list-style: outside none none;
1415
1418
1416 img {
1419 img {
1417 height: @gravatar-size;
1420 height: @gravatar-size;
1418 width: @gravatar-size;
1421 width: @gravatar-size;
1419 margin-right: .5em;
1422 margin-right: .5em;
1420 margin-left: 3px;
1423 margin-left: 3px;
1421 }
1424 }
1422
1425
1423 .to-delete {
1426 .to-delete {
1424 .user {
1427 .user {
1425 text-decoration: line-through;
1428 text-decoration: line-through;
1426 }
1429 }
1427 }
1430 }
1428 }
1431 }
1429
1432
1430 .compare_view_commits_title {
1433 .compare_view_commits_title {
1431 .disabled {
1434 .disabled {
1432 cursor: inherit;
1435 cursor: inherit;
1433 &:hover{
1436 &:hover{
1434 background-color: inherit;
1437 background-color: inherit;
1435 color: inherit;
1438 color: inherit;
1436 }
1439 }
1437 }
1440 }
1438 }
1441 }
1439
1442
1440 .subtitle-compare {
1443 .subtitle-compare {
1441 margin: -15px 0px 0px 0px;
1444 margin: -15px 0px 0px 0px;
1442 }
1445 }
1443
1446
1444 .comments-summary-td {
1447 .comments-summary-td {
1445 border-top: 1px dashed @grey5;
1448 border-top: 1px dashed @grey5;
1446 }
1449 }
1447
1450
1448 // new entry in group_members
1451 // new entry in group_members
1449 .td-author-new-entry {
1452 .td-author-new-entry {
1450 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1453 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1451 }
1454 }
1452
1455
1453 .usergroup_member_remove {
1456 .usergroup_member_remove {
1454 width: 16px;
1457 width: 16px;
1455 margin-bottom: 10px;
1458 margin-bottom: 10px;
1456 padding: 0;
1459 padding: 0;
1457 color: black !important;
1460 color: black !important;
1458 cursor: pointer;
1461 cursor: pointer;
1459 }
1462 }
1460
1463
1461 .reviewer_ac .ac-input {
1464 .reviewer_ac .ac-input {
1462 width: 92%;
1465 width: 92%;
1463 margin-bottom: 1em;
1466 margin-bottom: 1em;
1464 }
1467 }
1465
1468
1466 .compare_view_commits tr{
1469 .compare_view_commits tr{
1467 height: 20px;
1470 height: 20px;
1468 }
1471 }
1469 .compare_view_commits td {
1472 .compare_view_commits td {
1470 vertical-align: top;
1473 vertical-align: top;
1471 padding-top: 10px;
1474 padding-top: 10px;
1472 }
1475 }
1473 .compare_view_commits .author {
1476 .compare_view_commits .author {
1474 margin-left: 5px;
1477 margin-left: 5px;
1475 }
1478 }
1476
1479
1477 .compare_view_commits {
1480 .compare_view_commits {
1478 .color-a {
1481 .color-a {
1479 color: @alert1;
1482 color: @alert1;
1480 }
1483 }
1481
1484
1482 .color-c {
1485 .color-c {
1483 color: @color3;
1486 color: @color3;
1484 }
1487 }
1485
1488
1486 .color-r {
1489 .color-r {
1487 color: @color5;
1490 color: @color5;
1488 }
1491 }
1489
1492
1490 .color-a-bg {
1493 .color-a-bg {
1491 background-color: @alert1;
1494 background-color: @alert1;
1492 }
1495 }
1493
1496
1494 .color-c-bg {
1497 .color-c-bg {
1495 background-color: @alert3;
1498 background-color: @alert3;
1496 }
1499 }
1497
1500
1498 .color-r-bg {
1501 .color-r-bg {
1499 background-color: @alert2;
1502 background-color: @alert2;
1500 }
1503 }
1501
1504
1502 .color-a-border {
1505 .color-a-border {
1503 border: 1px solid @alert1;
1506 border: 1px solid @alert1;
1504 }
1507 }
1505
1508
1506 .color-c-border {
1509 .color-c-border {
1507 border: 1px solid @alert3;
1510 border: 1px solid @alert3;
1508 }
1511 }
1509
1512
1510 .color-r-border {
1513 .color-r-border {
1511 border: 1px solid @alert2;
1514 border: 1px solid @alert2;
1512 }
1515 }
1513
1516
1514 .commit-change-indicator {
1517 .commit-change-indicator {
1515 width: 15px;
1518 width: 15px;
1516 height: 15px;
1519 height: 15px;
1517 position: relative;
1520 position: relative;
1518 left: 15px;
1521 left: 15px;
1519 }
1522 }
1520
1523
1521 .commit-change-content {
1524 .commit-change-content {
1522 text-align: center;
1525 text-align: center;
1523 vertical-align: middle;
1526 vertical-align: middle;
1524 line-height: 15px;
1527 line-height: 15px;
1525 }
1528 }
1526 }
1529 }
1527
1530
1528 .compare_view_files {
1531 .compare_view_files {
1529 width: 100%;
1532 width: 100%;
1530
1533
1531 td {
1534 td {
1532 vertical-align: middle;
1535 vertical-align: middle;
1533 }
1536 }
1534 }
1537 }
1535
1538
1536 .compare_view_filepath {
1539 .compare_view_filepath {
1537 color: @grey1;
1540 color: @grey1;
1538 }
1541 }
1539
1542
1540 .show_more {
1543 .show_more {
1541 display: inline-block;
1544 display: inline-block;
1542 position: relative;
1545 position: relative;
1543 vertical-align: middle;
1546 vertical-align: middle;
1544 width: 4px;
1547 width: 4px;
1545 height: @basefontsize;
1548 height: @basefontsize;
1546
1549
1547 &:after {
1550 &:after {
1548 content: "\00A0\25BE";
1551 content: "\00A0\25BE";
1549 display: inline-block;
1552 display: inline-block;
1550 width:10px;
1553 width:10px;
1551 line-height: 5px;
1554 line-height: 5px;
1552 font-size: 12px;
1555 font-size: 12px;
1553 cursor: pointer;
1556 cursor: pointer;
1554 }
1557 }
1555 }
1558 }
1556
1559
1557 .journal_more .show_more {
1560 .journal_more .show_more {
1558 display: inline;
1561 display: inline;
1559
1562
1560 &:after {
1563 &:after {
1561 content: none;
1564 content: none;
1562 }
1565 }
1563 }
1566 }
1564
1567
1565 .open .show_more:after,
1568 .open .show_more:after,
1566 .select2-dropdown-open .show_more:after {
1569 .select2-dropdown-open .show_more:after {
1567 .rotate(180deg);
1570 .rotate(180deg);
1568 margin-left: 4px;
1571 margin-left: 4px;
1569 }
1572 }
1570
1573
1571
1574
1572 .compare_view_commits .collapse_commit:after {
1575 .compare_view_commits .collapse_commit:after {
1573 cursor: pointer;
1576 cursor: pointer;
1574 content: "\00A0\25B4";
1577 content: "\00A0\25B4";
1575 margin-left: -3px;
1578 margin-left: -3px;
1576 font-size: 17px;
1579 font-size: 17px;
1577 color: @grey4;
1580 color: @grey4;
1578 }
1581 }
1579
1582
1580 .diff_links {
1583 .diff_links {
1581 margin-left: 8px;
1584 margin-left: 8px;
1582 }
1585 }
1583
1586
1584 div.ancestor {
1587 div.ancestor {
1585 margin: -30px 0px;
1588 margin: -30px 0px;
1586 }
1589 }
1587
1590
1588 .cs_icon_td input[type="checkbox"] {
1591 .cs_icon_td input[type="checkbox"] {
1589 display: none;
1592 display: none;
1590 }
1593 }
1591
1594
1592 .cs_icon_td .expand_file_icon:after {
1595 .cs_icon_td .expand_file_icon:after {
1593 cursor: pointer;
1596 cursor: pointer;
1594 content: "\00A0\25B6";
1597 content: "\00A0\25B6";
1595 font-size: 12px;
1598 font-size: 12px;
1596 color: @grey4;
1599 color: @grey4;
1597 }
1600 }
1598
1601
1599 .cs_icon_td .collapse_file_icon:after {
1602 .cs_icon_td .collapse_file_icon:after {
1600 cursor: pointer;
1603 cursor: pointer;
1601 content: "\00A0\25BC";
1604 content: "\00A0\25BC";
1602 font-size: 12px;
1605 font-size: 12px;
1603 color: @grey4;
1606 color: @grey4;
1604 }
1607 }
1605
1608
1606 /*new binary
1609 /*new binary
1607 NEW_FILENODE = 1
1610 NEW_FILENODE = 1
1608 DEL_FILENODE = 2
1611 DEL_FILENODE = 2
1609 MOD_FILENODE = 3
1612 MOD_FILENODE = 3
1610 RENAMED_FILENODE = 4
1613 RENAMED_FILENODE = 4
1611 COPIED_FILENODE = 5
1614 COPIED_FILENODE = 5
1612 CHMOD_FILENODE = 6
1615 CHMOD_FILENODE = 6
1613 BIN_FILENODE = 7
1616 BIN_FILENODE = 7
1614 */
1617 */
1615 .cs_files_expand {
1618 .cs_files_expand {
1616 font-size: @basefontsize + 5px;
1619 font-size: @basefontsize + 5px;
1617 line-height: 1.8em;
1620 line-height: 1.8em;
1618 float: right;
1621 float: right;
1619 }
1622 }
1620
1623
1621 .cs_files_expand span{
1624 .cs_files_expand span{
1622 color: @rcblue;
1625 color: @rcblue;
1623 cursor: pointer;
1626 cursor: pointer;
1624 }
1627 }
1625 .cs_files {
1628 .cs_files {
1626 clear: both;
1629 clear: both;
1627 padding-bottom: @padding;
1630 padding-bottom: @padding;
1628
1631
1629 .cur_cs {
1632 .cur_cs {
1630 margin: 10px 2px;
1633 margin: 10px 2px;
1631 font-weight: bold;
1634 font-weight: bold;
1632 }
1635 }
1633
1636
1634 .node {
1637 .node {
1635 float: left;
1638 float: left;
1636 }
1639 }
1637
1640
1638 .changes {
1641 .changes {
1639 float: right;
1642 float: right;
1640 color: white;
1643 color: white;
1641 font-size: @basefontsize - 4px;
1644 font-size: @basefontsize - 4px;
1642 margin-top: 4px;
1645 margin-top: 4px;
1643 opacity: 0.6;
1646 opacity: 0.6;
1644 filter: Alpha(opacity=60); /* IE8 and earlier */
1647 filter: Alpha(opacity=60); /* IE8 and earlier */
1645
1648
1646 .added {
1649 .added {
1647 background-color: @alert1;
1650 background-color: @alert1;
1648 float: left;
1651 float: left;
1649 text-align: center;
1652 text-align: center;
1650 }
1653 }
1651
1654
1652 .deleted {
1655 .deleted {
1653 background-color: @alert2;
1656 background-color: @alert2;
1654 float: left;
1657 float: left;
1655 text-align: center;
1658 text-align: center;
1656 }
1659 }
1657
1660
1658 .bin {
1661 .bin {
1659 background-color: @alert1;
1662 background-color: @alert1;
1660 text-align: center;
1663 text-align: center;
1661 }
1664 }
1662
1665
1663 /*new binary*/
1666 /*new binary*/
1664 .bin.bin1 {
1667 .bin.bin1 {
1665 background-color: @alert1;
1668 background-color: @alert1;
1666 text-align: center;
1669 text-align: center;
1667 }
1670 }
1668
1671
1669 /*deleted binary*/
1672 /*deleted binary*/
1670 .bin.bin2 {
1673 .bin.bin2 {
1671 background-color: @alert2;
1674 background-color: @alert2;
1672 text-align: center;
1675 text-align: center;
1673 }
1676 }
1674
1677
1675 /*mod binary*/
1678 /*mod binary*/
1676 .bin.bin3 {
1679 .bin.bin3 {
1677 background-color: @grey2;
1680 background-color: @grey2;
1678 text-align: center;
1681 text-align: center;
1679 }
1682 }
1680
1683
1681 /*rename file*/
1684 /*rename file*/
1682 .bin.bin4 {
1685 .bin.bin4 {
1683 background-color: @alert4;
1686 background-color: @alert4;
1684 text-align: center;
1687 text-align: center;
1685 }
1688 }
1686
1689
1687 /*copied file*/
1690 /*copied file*/
1688 .bin.bin5 {
1691 .bin.bin5 {
1689 background-color: @alert4;
1692 background-color: @alert4;
1690 text-align: center;
1693 text-align: center;
1691 }
1694 }
1692
1695
1693 /*chmod file*/
1696 /*chmod file*/
1694 .bin.bin6 {
1697 .bin.bin6 {
1695 background-color: @grey2;
1698 background-color: @grey2;
1696 text-align: center;
1699 text-align: center;
1697 }
1700 }
1698 }
1701 }
1699 }
1702 }
1700
1703
1701 .cs_files .cs_added, .cs_files .cs_A,
1704 .cs_files .cs_added, .cs_files .cs_A,
1702 .cs_files .cs_added, .cs_files .cs_M,
1705 .cs_files .cs_added, .cs_files .cs_M,
1703 .cs_files .cs_added, .cs_files .cs_D {
1706 .cs_files .cs_added, .cs_files .cs_D {
1704 height: 16px;
1707 height: 16px;
1705 padding-right: 10px;
1708 padding-right: 10px;
1706 margin-top: 7px;
1709 margin-top: 7px;
1707 text-align: left;
1710 text-align: left;
1708 }
1711 }
1709
1712
1710 .cs_icon_td {
1713 .cs_icon_td {
1711 min-width: 16px;
1714 min-width: 16px;
1712 width: 16px;
1715 width: 16px;
1713 }
1716 }
1714
1717
1715 .pull-request-merge {
1718 .pull-request-merge {
1716 border: 1px solid @grey5;
1719 border: 1px solid @grey5;
1717 padding: 10px 0px 20px;
1720 padding: 10px 0px 20px;
1718 margin-top: 10px;
1721 margin-top: 10px;
1719 margin-bottom: 20px;
1722 margin-bottom: 20px;
1720 }
1723 }
1721
1724
1722 .pull-request-merge ul {
1725 .pull-request-merge ul {
1723 padding: 0px 0px;
1726 padding: 0px 0px;
1724 }
1727 }
1725
1728
1726 .pull-request-merge li:before{
1729 .pull-request-merge li:before{
1727 content:none;
1730 content:none;
1728 }
1731 }
1729
1732
1730 .pull-request-merge .pull-request-wrap {
1733 .pull-request-merge .pull-request-wrap {
1731 height: auto;
1734 height: auto;
1732 padding: 0px 0px;
1735 padding: 0px 0px;
1733 text-align: right;
1736 text-align: right;
1734 }
1737 }
1735
1738
1736 .pull-request-merge span {
1739 .pull-request-merge span {
1737 margin-right: 5px;
1740 margin-right: 5px;
1738 }
1741 }
1739
1742
1740 .pull-request-merge-actions {
1743 .pull-request-merge-actions {
1741 height: 30px;
1744 height: 30px;
1742 padding: 0px 0px;
1745 padding: 0px 0px;
1743 }
1746 }
1744
1747
1745 .merge-status {
1748 .merge-status {
1746 margin-right: 5px;
1749 margin-right: 5px;
1747 }
1750 }
1748
1751
1749 .merge-message {
1752 .merge-message {
1750 font-size: 1.2em
1753 font-size: 1.2em
1751 }
1754 }
1752
1755
1753 .merge-message.success i,
1756 .merge-message.success i,
1754 .merge-icon.success i {
1757 .merge-icon.success i {
1755 color:@alert1;
1758 color:@alert1;
1756 }
1759 }
1757
1760
1758 .merge-message.warning i,
1761 .merge-message.warning i,
1759 .merge-icon.warning i {
1762 .merge-icon.warning i {
1760 color: @alert3;
1763 color: @alert3;
1761 }
1764 }
1762
1765
1763 .merge-message.error i,
1766 .merge-message.error i,
1764 .merge-icon.error i {
1767 .merge-icon.error i {
1765 color:@alert2;
1768 color:@alert2;
1766 }
1769 }
1767
1770
1768 .pr-versions {
1771 .pr-versions {
1769 font-size: 1.1em;
1772 font-size: 1.1em;
1770
1773
1771 table {
1774 table {
1772 padding: 0px 5px;
1775 padding: 0px 5px;
1773 }
1776 }
1774
1777
1775 td {
1778 td {
1776 line-height: 15px;
1779 line-height: 15px;
1777 }
1780 }
1778
1781
1779 .flag_status {
1782 .flag_status {
1780 margin: 0;
1783 margin: 0;
1781 }
1784 }
1782
1785
1783 .compare-radio-button {
1786 .compare-radio-button {
1784 position: relative;
1787 position: relative;
1785 top: -3px;
1788 top: -3px;
1786 }
1789 }
1787 }
1790 }
1788
1791
1789
1792
1790 #close_pull_request {
1793 #close_pull_request {
1791 margin-right: 0px;
1794 margin-right: 0px;
1792 }
1795 }
1793
1796
1794 .empty_data {
1797 .empty_data {
1795 color: @grey4;
1798 color: @grey4;
1796 }
1799 }
1797
1800
1798 #changeset_compare_view_content {
1801 #changeset_compare_view_content {
1799 margin-bottom: @space;
1802 margin-bottom: @space;
1800 clear: both;
1803 clear: both;
1801 width: 100%;
1804 width: 100%;
1802 box-sizing: border-box;
1805 box-sizing: border-box;
1803 .border-radius(@border-radius);
1806 .border-radius(@border-radius);
1804
1807
1805 .help-block {
1808 .help-block {
1806 margin: @padding 0;
1809 margin: @padding 0;
1807 color: @text-color;
1810 color: @text-color;
1811 &.pre-formatting {
1812 white-space: pre;
1813 }
1808 }
1814 }
1809
1815
1810 .empty_data {
1816 .empty_data {
1811 margin: @padding 0;
1817 margin: @padding 0;
1812 }
1818 }
1813
1819
1814 .alert {
1820 .alert {
1815 margin-bottom: @space;
1821 margin-bottom: @space;
1816 }
1822 }
1817 }
1823 }
1818
1824
1819 .table_disp {
1825 .table_disp {
1820 .status {
1826 .status {
1821 width: auto;
1827 width: auto;
1822
1828
1823 .flag_status {
1829 .flag_status {
1824 float: left;
1830 float: left;
1825 }
1831 }
1826 }
1832 }
1827 }
1833 }
1828
1834
1829 .status_box_menu {
1835 .status_box_menu {
1830 margin: 0;
1836 margin: 0;
1831 }
1837 }
1832
1838
1833 .notification-table{
1839 .notification-table{
1834 margin-bottom: @space;
1840 margin-bottom: @space;
1835 display: table;
1841 display: table;
1836 width: 100%;
1842 width: 100%;
1837
1843
1838 .container{
1844 .container{
1839 display: table-row;
1845 display: table-row;
1840
1846
1841 .notification-header{
1847 .notification-header{
1842 border-bottom: @border-thickness solid @border-default-color;
1848 border-bottom: @border-thickness solid @border-default-color;
1843 }
1849 }
1844
1850
1845 .notification-subject{
1851 .notification-subject{
1846 display: table-cell;
1852 display: table-cell;
1847 }
1853 }
1848 }
1854 }
1849 }
1855 }
1850
1856
1851 // Notifications
1857 // Notifications
1852 .notification-header{
1858 .notification-header{
1853 display: table;
1859 display: table;
1854 width: 100%;
1860 width: 100%;
1855 padding: floor(@basefontsize/2) 0;
1861 padding: floor(@basefontsize/2) 0;
1856 line-height: 1em;
1862 line-height: 1em;
1857
1863
1858 .desc, .delete-notifications, .read-notifications{
1864 .desc, .delete-notifications, .read-notifications{
1859 display: table-cell;
1865 display: table-cell;
1860 text-align: left;
1866 text-align: left;
1861 }
1867 }
1862
1868
1863 .desc{
1869 .desc{
1864 width: 1163px;
1870 width: 1163px;
1865 }
1871 }
1866
1872
1867 .delete-notifications, .read-notifications{
1873 .delete-notifications, .read-notifications{
1868 width: 35px;
1874 width: 35px;
1869 min-width: 35px; //fixes when only one button is displayed
1875 min-width: 35px; //fixes when only one button is displayed
1870 }
1876 }
1871 }
1877 }
1872
1878
1873 .notification-body {
1879 .notification-body {
1874 .markdown-block,
1880 .markdown-block,
1875 .rst-block {
1881 .rst-block {
1876 padding: @padding 0;
1882 padding: @padding 0;
1877 }
1883 }
1878
1884
1879 .notification-subject {
1885 .notification-subject {
1880 padding: @textmargin 0;
1886 padding: @textmargin 0;
1881 border-bottom: @border-thickness solid @border-default-color;
1887 border-bottom: @border-thickness solid @border-default-color;
1882 }
1888 }
1883 }
1889 }
1884
1890
1885
1891
1886 .notifications_buttons{
1892 .notifications_buttons{
1887 float: right;
1893 float: right;
1888 }
1894 }
1889
1895
1890 #notification-status{
1896 #notification-status{
1891 display: inline;
1897 display: inline;
1892 }
1898 }
1893
1899
1894 // Repositories
1900 // Repositories
1895
1901
1896 #summary.fields{
1902 #summary.fields{
1897 display: table;
1903 display: table;
1898
1904
1899 .field{
1905 .field{
1900 display: table-row;
1906 display: table-row;
1901
1907
1902 .label-summary{
1908 .label-summary{
1903 display: table-cell;
1909 display: table-cell;
1904 min-width: @label-summary-minwidth;
1910 min-width: @label-summary-minwidth;
1905 padding-top: @padding/2;
1911 padding-top: @padding/2;
1906 padding-bottom: @padding/2;
1912 padding-bottom: @padding/2;
1907 padding-right: @padding/2;
1913 padding-right: @padding/2;
1908 }
1914 }
1909
1915
1910 .input{
1916 .input{
1911 display: table-cell;
1917 display: table-cell;
1912 padding: @padding/2;
1918 padding: @padding/2;
1913
1919
1914 input{
1920 input{
1915 min-width: 29em;
1921 min-width: 29em;
1916 padding: @padding/4;
1922 padding: @padding/4;
1917 }
1923 }
1918 }
1924 }
1919 .statistics, .downloads{
1925 .statistics, .downloads{
1920 .disabled{
1926 .disabled{
1921 color: @grey4;
1927 color: @grey4;
1922 }
1928 }
1923 }
1929 }
1924 }
1930 }
1925 }
1931 }
1926
1932
1927 #summary{
1933 #summary{
1928 width: 70%;
1934 width: 70%;
1929 }
1935 }
1930
1936
1931
1937
1932 // Journal
1938 // Journal
1933 .journal.title {
1939 .journal.title {
1934 h5 {
1940 h5 {
1935 float: left;
1941 float: left;
1936 margin: 0;
1942 margin: 0;
1937 width: 70%;
1943 width: 70%;
1938 }
1944 }
1939
1945
1940 ul {
1946 ul {
1941 float: right;
1947 float: right;
1942 display: inline-block;
1948 display: inline-block;
1943 margin: 0;
1949 margin: 0;
1944 width: 30%;
1950 width: 30%;
1945 text-align: right;
1951 text-align: right;
1946
1952
1947 li {
1953 li {
1948 display: inline;
1954 display: inline;
1949 font-size: @journal-fontsize;
1955 font-size: @journal-fontsize;
1950 line-height: 1em;
1956 line-height: 1em;
1951
1957
1952 &:before { content: none; }
1958 &:before { content: none; }
1953 }
1959 }
1954 }
1960 }
1955 }
1961 }
1956
1962
1957 .filterexample {
1963 .filterexample {
1958 position: absolute;
1964 position: absolute;
1959 top: 95px;
1965 top: 95px;
1960 left: @contentpadding;
1966 left: @contentpadding;
1961 color: @rcblue;
1967 color: @rcblue;
1962 font-size: 11px;
1968 font-size: 11px;
1963 font-family: @text-regular;
1969 font-family: @text-regular;
1964 cursor: help;
1970 cursor: help;
1965
1971
1966 &:hover {
1972 &:hover {
1967 color: @rcdarkblue;
1973 color: @rcdarkblue;
1968 }
1974 }
1969
1975
1970 @media (max-width:768px) {
1976 @media (max-width:768px) {
1971 position: relative;
1977 position: relative;
1972 top: auto;
1978 top: auto;
1973 left: auto;
1979 left: auto;
1974 display: block;
1980 display: block;
1975 }
1981 }
1976 }
1982 }
1977
1983
1978
1984
1979 #journal{
1985 #journal{
1980 margin-bottom: @space;
1986 margin-bottom: @space;
1981
1987
1982 .journal_day{
1988 .journal_day{
1983 margin-bottom: @textmargin/2;
1989 margin-bottom: @textmargin/2;
1984 padding-bottom: @textmargin/2;
1990 padding-bottom: @textmargin/2;
1985 font-size: @journal-fontsize;
1991 font-size: @journal-fontsize;
1986 border-bottom: @border-thickness solid @border-default-color;
1992 border-bottom: @border-thickness solid @border-default-color;
1987 }
1993 }
1988
1994
1989 .journal_container{
1995 .journal_container{
1990 margin-bottom: @space;
1996 margin-bottom: @space;
1991
1997
1992 .journal_user{
1998 .journal_user{
1993 display: inline-block;
1999 display: inline-block;
1994 }
2000 }
1995 .journal_action_container{
2001 .journal_action_container{
1996 display: block;
2002 display: block;
1997 margin-top: @textmargin;
2003 margin-top: @textmargin;
1998
2004
1999 div{
2005 div{
2000 display: inline;
2006 display: inline;
2001 }
2007 }
2002
2008
2003 div.journal_action_params{
2009 div.journal_action_params{
2004 display: block;
2010 display: block;
2005 }
2011 }
2006
2012
2007 div.journal_repo:after{
2013 div.journal_repo:after{
2008 content: "\A";
2014 content: "\A";
2009 white-space: pre;
2015 white-space: pre;
2010 }
2016 }
2011
2017
2012 div.date{
2018 div.date{
2013 display: block;
2019 display: block;
2014 margin-bottom: @textmargin;
2020 margin-bottom: @textmargin;
2015 }
2021 }
2016 }
2022 }
2017 }
2023 }
2018 }
2024 }
2019
2025
2020 // Files
2026 // Files
2021 .edit-file-title {
2027 .edit-file-title {
2022 border-bottom: @border-thickness solid @border-default-color;
2028 border-bottom: @border-thickness solid @border-default-color;
2023
2029
2024 .breadcrumbs {
2030 .breadcrumbs {
2025 margin-bottom: 0;
2031 margin-bottom: 0;
2026 }
2032 }
2027 }
2033 }
2028
2034
2029 .edit-file-fieldset {
2035 .edit-file-fieldset {
2030 margin-top: @sidebarpadding;
2036 margin-top: @sidebarpadding;
2031
2037
2032 .fieldset {
2038 .fieldset {
2033 .left-label {
2039 .left-label {
2034 width: 13%;
2040 width: 13%;
2035 }
2041 }
2036 .right-content {
2042 .right-content {
2037 width: 87%;
2043 width: 87%;
2038 max-width: 100%;
2044 max-width: 100%;
2039 }
2045 }
2040 .filename-label {
2046 .filename-label {
2041 margin-top: 13px;
2047 margin-top: 13px;
2042 }
2048 }
2043 .commit-message-label {
2049 .commit-message-label {
2044 margin-top: 4px;
2050 margin-top: 4px;
2045 }
2051 }
2046 .file-upload-input {
2052 .file-upload-input {
2047 input {
2053 input {
2048 display: none;
2054 display: none;
2049 }
2055 }
2050 margin-top: 10px;
2056 margin-top: 10px;
2051 }
2057 }
2052 .file-upload-label {
2058 .file-upload-label {
2053 margin-top: 10px;
2059 margin-top: 10px;
2054 }
2060 }
2055 p {
2061 p {
2056 margin-top: 5px;
2062 margin-top: 5px;
2057 }
2063 }
2058
2064
2059 }
2065 }
2060 .custom-path-link {
2066 .custom-path-link {
2061 margin-left: 5px;
2067 margin-left: 5px;
2062 }
2068 }
2063 #commit {
2069 #commit {
2064 resize: vertical;
2070 resize: vertical;
2065 }
2071 }
2066 }
2072 }
2067
2073
2068 .delete-file-preview {
2074 .delete-file-preview {
2069 max-height: 250px;
2075 max-height: 250px;
2070 }
2076 }
2071
2077
2072 .new-file,
2078 .new-file,
2073 #filter_activate,
2079 #filter_activate,
2074 #filter_deactivate {
2080 #filter_deactivate {
2075 float: left;
2081 float: left;
2076 margin: 0 0 0 15px;
2082 margin: 0 0 0 15px;
2077 }
2083 }
2078
2084
2079 h3.files_location{
2085 h3.files_location{
2080 line-height: 2.4em;
2086 line-height: 2.4em;
2081 }
2087 }
2082
2088
2083 .browser-nav {
2089 .browser-nav {
2084 display: table;
2090 display: table;
2085 margin-bottom: @space;
2091 margin-bottom: @space;
2086
2092
2087
2093
2088 .info_box {
2094 .info_box {
2089 display: inline-table;
2095 display: inline-table;
2090 height: 2.5em;
2096 height: 2.5em;
2091
2097
2092 .browser-cur-rev, .info_box_elem {
2098 .browser-cur-rev, .info_box_elem {
2093 display: table-cell;
2099 display: table-cell;
2094 vertical-align: middle;
2100 vertical-align: middle;
2095 }
2101 }
2096
2102
2097 .info_box_elem {
2103 .info_box_elem {
2098 border-top: @border-thickness solid @rcblue;
2104 border-top: @border-thickness solid @rcblue;
2099 border-bottom: @border-thickness solid @rcblue;
2105 border-bottom: @border-thickness solid @rcblue;
2100
2106
2101 #at_rev, a {
2107 #at_rev, a {
2102 padding: 0.6em 0.9em;
2108 padding: 0.6em 0.9em;
2103 margin: 0;
2109 margin: 0;
2104 .box-shadow(none);
2110 .box-shadow(none);
2105 border: 0;
2111 border: 0;
2106 height: 12px;
2112 height: 12px;
2107 }
2113 }
2108
2114
2109 input#at_rev {
2115 input#at_rev {
2110 max-width: 50px;
2116 max-width: 50px;
2111 text-align: right;
2117 text-align: right;
2112 }
2118 }
2113
2119
2114 &.previous {
2120 &.previous {
2115 border: @border-thickness solid @rcblue;
2121 border: @border-thickness solid @rcblue;
2116 .disabled {
2122 .disabled {
2117 color: @grey4;
2123 color: @grey4;
2118 cursor: not-allowed;
2124 cursor: not-allowed;
2119 }
2125 }
2120 }
2126 }
2121
2127
2122 &.next {
2128 &.next {
2123 border: @border-thickness solid @rcblue;
2129 border: @border-thickness solid @rcblue;
2124 .disabled {
2130 .disabled {
2125 color: @grey4;
2131 color: @grey4;
2126 cursor: not-allowed;
2132 cursor: not-allowed;
2127 }
2133 }
2128 }
2134 }
2129 }
2135 }
2130
2136
2131 .browser-cur-rev {
2137 .browser-cur-rev {
2132
2138
2133 span{
2139 span{
2134 margin: 0;
2140 margin: 0;
2135 color: @rcblue;
2141 color: @rcblue;
2136 height: 12px;
2142 height: 12px;
2137 display: inline-block;
2143 display: inline-block;
2138 padding: 0.7em 1em ;
2144 padding: 0.7em 1em ;
2139 border: @border-thickness solid @rcblue;
2145 border: @border-thickness solid @rcblue;
2140 margin-right: @padding;
2146 margin-right: @padding;
2141 }
2147 }
2142 }
2148 }
2143 }
2149 }
2144
2150
2145 .search_activate {
2151 .search_activate {
2146 display: table-cell;
2152 display: table-cell;
2147 vertical-align: middle;
2153 vertical-align: middle;
2148
2154
2149 input, label{
2155 input, label{
2150 margin: 0;
2156 margin: 0;
2151 padding: 0;
2157 padding: 0;
2152 }
2158 }
2153
2159
2154 input{
2160 input{
2155 margin-left: @textmargin;
2161 margin-left: @textmargin;
2156 }
2162 }
2157
2163
2158 }
2164 }
2159 }
2165 }
2160
2166
2161 .browser-cur-rev{
2167 .browser-cur-rev{
2162 margin-bottom: @textmargin;
2168 margin-bottom: @textmargin;
2163 }
2169 }
2164
2170
2165 #node_filter_box_loading{
2171 #node_filter_box_loading{
2166 .info_text;
2172 .info_text;
2167 }
2173 }
2168
2174
2169 .browser-search {
2175 .browser-search {
2170 margin: -25px 0px 5px 0px;
2176 margin: -25px 0px 5px 0px;
2171 }
2177 }
2172
2178
2173 .node-filter {
2179 .node-filter {
2174 font-size: @repo-title-fontsize;
2180 font-size: @repo-title-fontsize;
2175 padding: 4px 0px 0px 0px;
2181 padding: 4px 0px 0px 0px;
2176
2182
2177 .node-filter-path {
2183 .node-filter-path {
2178 float: left;
2184 float: left;
2179 color: @grey4;
2185 color: @grey4;
2180 }
2186 }
2181 .node-filter-input {
2187 .node-filter-input {
2182 float: left;
2188 float: left;
2183 margin: -2px 0px 0px 2px;
2189 margin: -2px 0px 0px 2px;
2184 input {
2190 input {
2185 padding: 2px;
2191 padding: 2px;
2186 border: none;
2192 border: none;
2187 font-size: @repo-title-fontsize;
2193 font-size: @repo-title-fontsize;
2188 }
2194 }
2189 }
2195 }
2190 }
2196 }
2191
2197
2192
2198
2193 .browser-result{
2199 .browser-result{
2194 td a{
2200 td a{
2195 margin-left: 0.5em;
2201 margin-left: 0.5em;
2196 display: inline-block;
2202 display: inline-block;
2197
2203
2198 em{
2204 em{
2199 font-family: @text-bold;
2205 font-family: @text-bold;
2200 }
2206 }
2201 }
2207 }
2202 }
2208 }
2203
2209
2204 .browser-highlight{
2210 .browser-highlight{
2205 background-color: @grey5-alpha;
2211 background-color: @grey5-alpha;
2206 }
2212 }
2207
2213
2208
2214
2209 // Search
2215 // Search
2210
2216
2211 .search-form{
2217 .search-form{
2212 #q {
2218 #q {
2213 width: @search-form-width;
2219 width: @search-form-width;
2214 }
2220 }
2215 .fields{
2221 .fields{
2216 margin: 0 0 @space;
2222 margin: 0 0 @space;
2217 }
2223 }
2218
2224
2219 label{
2225 label{
2220 display: inline-block;
2226 display: inline-block;
2221 margin-right: @textmargin;
2227 margin-right: @textmargin;
2222 padding-top: 0.25em;
2228 padding-top: 0.25em;
2223 }
2229 }
2224
2230
2225
2231
2226 .results{
2232 .results{
2227 clear: both;
2233 clear: both;
2228 margin: 0 0 @padding;
2234 margin: 0 0 @padding;
2229 }
2235 }
2230 }
2236 }
2231
2237
2232 div.search-feedback-items {
2238 div.search-feedback-items {
2233 display: inline-block;
2239 display: inline-block;
2234 padding:0px 0px 0px 96px;
2240 padding:0px 0px 0px 96px;
2235 }
2241 }
2236
2242
2237 div.search-code-body {
2243 div.search-code-body {
2238 background-color: #ffffff; padding: 5px 0 5px 10px;
2244 background-color: #ffffff; padding: 5px 0 5px 10px;
2239 pre {
2245 pre {
2240 .match { background-color: #faffa6;}
2246 .match { background-color: #faffa6;}
2241 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2247 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2242 }
2248 }
2243 }
2249 }
2244
2250
2245 .expand_commit.search {
2251 .expand_commit.search {
2246 .show_more.open {
2252 .show_more.open {
2247 height: auto;
2253 height: auto;
2248 max-height: none;
2254 max-height: none;
2249 }
2255 }
2250 }
2256 }
2251
2257
2252 .search-results {
2258 .search-results {
2253
2259
2254 h2 {
2260 h2 {
2255 margin-bottom: 0;
2261 margin-bottom: 0;
2256 }
2262 }
2257 .codeblock {
2263 .codeblock {
2258 border: none;
2264 border: none;
2259 background: transparent;
2265 background: transparent;
2260 }
2266 }
2261
2267
2262 .codeblock-header {
2268 .codeblock-header {
2263 border: none;
2269 border: none;
2264 background: transparent;
2270 background: transparent;
2265 }
2271 }
2266
2272
2267 .code-body {
2273 .code-body {
2268 border: @border-thickness solid @border-default-color;
2274 border: @border-thickness solid @border-default-color;
2269 .border-radius(@border-radius);
2275 .border-radius(@border-radius);
2270 }
2276 }
2271
2277
2272 .td-commit {
2278 .td-commit {
2273 &:extend(pre);
2279 &:extend(pre);
2274 border-bottom: @border-thickness solid @border-default-color;
2280 border-bottom: @border-thickness solid @border-default-color;
2275 }
2281 }
2276
2282
2277 .message {
2283 .message {
2278 height: auto;
2284 height: auto;
2279 max-width: 350px;
2285 max-width: 350px;
2280 white-space: normal;
2286 white-space: normal;
2281 text-overflow: initial;
2287 text-overflow: initial;
2282 overflow: visible;
2288 overflow: visible;
2283
2289
2284 .match { background-color: #faffa6;}
2290 .match { background-color: #faffa6;}
2285 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2291 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2286 }
2292 }
2287
2293
2288 }
2294 }
2289
2295
2290 table.rctable td.td-search-results div {
2296 table.rctable td.td-search-results div {
2291 max-width: 100%;
2297 max-width: 100%;
2292 }
2298 }
2293
2299
2294 #tip-box, .tip-box{
2300 #tip-box, .tip-box{
2295 padding: @menupadding/2;
2301 padding: @menupadding/2;
2296 display: block;
2302 display: block;
2297 border: @border-thickness solid @border-highlight-color;
2303 border: @border-thickness solid @border-highlight-color;
2298 .border-radius(@border-radius);
2304 .border-radius(@border-radius);
2299 background-color: white;
2305 background-color: white;
2300 z-index: 99;
2306 z-index: 99;
2301 white-space: pre-wrap;
2307 white-space: pre-wrap;
2302 }
2308 }
2303
2309
2304 #linktt {
2310 #linktt {
2305 width: 79px;
2311 width: 79px;
2306 }
2312 }
2307
2313
2308 #help_kb .modal-content{
2314 #help_kb .modal-content{
2309 max-width: 750px;
2315 max-width: 750px;
2310 margin: 10% auto;
2316 margin: 10% auto;
2311
2317
2312 table{
2318 table{
2313 td,th{
2319 td,th{
2314 border-bottom: none;
2320 border-bottom: none;
2315 line-height: 2.5em;
2321 line-height: 2.5em;
2316 }
2322 }
2317 th{
2323 th{
2318 padding-bottom: @textmargin/2;
2324 padding-bottom: @textmargin/2;
2319 }
2325 }
2320 td.keys{
2326 td.keys{
2321 text-align: center;
2327 text-align: center;
2322 }
2328 }
2323 }
2329 }
2324
2330
2325 .block-left{
2331 .block-left{
2326 width: 45%;
2332 width: 45%;
2327 margin-right: 5%;
2333 margin-right: 5%;
2328 }
2334 }
2329 .modal-footer{
2335 .modal-footer{
2330 clear: both;
2336 clear: both;
2331 }
2337 }
2332 .key.tag{
2338 .key.tag{
2333 padding: 0.5em;
2339 padding: 0.5em;
2334 background-color: @rcblue;
2340 background-color: @rcblue;
2335 color: white;
2341 color: white;
2336 border-color: @rcblue;
2342 border-color: @rcblue;
2337 .box-shadow(none);
2343 .box-shadow(none);
2338 }
2344 }
2339 }
2345 }
2340
2346
2341
2347
2342
2348
2343 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2349 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2344
2350
2345 @import 'statistics-graph';
2351 @import 'statistics-graph';
2346 @import 'tables';
2352 @import 'tables';
2347 @import 'forms';
2353 @import 'forms';
2348 @import 'diff';
2354 @import 'diff';
2349 @import 'summary';
2355 @import 'summary';
2350 @import 'navigation';
2356 @import 'navigation';
2351
2357
2352 //--- SHOW/HIDE SECTIONS --//
2358 //--- SHOW/HIDE SECTIONS --//
2353
2359
2354 .btn-collapse {
2360 .btn-collapse {
2355 float: right;
2361 float: right;
2356 text-align: right;
2362 text-align: right;
2357 font-family: @text-light;
2363 font-family: @text-light;
2358 font-size: @basefontsize;
2364 font-size: @basefontsize;
2359 cursor: pointer;
2365 cursor: pointer;
2360 border: none;
2366 border: none;
2361 color: @rcblue;
2367 color: @rcblue;
2362 }
2368 }
2363
2369
2364 table.rctable,
2370 table.rctable,
2365 table.dataTable {
2371 table.dataTable {
2366 .btn-collapse {
2372 .btn-collapse {
2367 float: right;
2373 float: right;
2368 text-align: right;
2374 text-align: right;
2369 }
2375 }
2370 }
2376 }
2371
2377
2372
2378
2373 // TODO: johbo: Fix for IE10, this avoids that we see a border
2379 // TODO: johbo: Fix for IE10, this avoids that we see a border
2374 // and padding around checkboxes and radio boxes. Move to the right place,
2380 // and padding around checkboxes and radio boxes. Move to the right place,
2375 // or better: Remove this once we did the form refactoring.
2381 // or better: Remove this once we did the form refactoring.
2376 input[type=checkbox],
2382 input[type=checkbox],
2377 input[type=radio] {
2383 input[type=radio] {
2378 padding: 0;
2384 padding: 0;
2379 border: none;
2385 border: none;
2380 }
2386 }
2381
2387
2382 .toggle-ajax-spinner{
2388 .toggle-ajax-spinner{
2383 height: 16px;
2389 height: 16px;
2384 width: 16px;
2390 width: 16px;
2385 }
2391 }
@@ -1,542 +1,545 b''
1 //
1 //
2 // Typography
2 // Typography
3 // modified from Bootstrap
3 // modified from Bootstrap
4 // --------------------------------------------------
4 // --------------------------------------------------
5
5
6 // Base
6 // Base
7 body {
7 body {
8 font-size: @basefontsize;
8 font-size: @basefontsize;
9 font-family: @text-light;
9 font-family: @text-light;
10 letter-spacing: .02em;
10 letter-spacing: .02em;
11 color: @grey2;
11 color: @grey2;
12 }
12 }
13
13
14 #content, label{
14 #content, label{
15 font-size: @basefontsize;
15 font-size: @basefontsize;
16 }
16 }
17
17
18 label {
18 label {
19 color: @grey2;
19 color: @grey2;
20 }
20 }
21
21
22 ::selection { background: @rchighlightblue; }
22 ::selection { background: @rchighlightblue; }
23
23
24 // Headings
24 // Headings
25 // -------------------------
25 // -------------------------
26
26
27 h1, h2, h3, h4, h5, h6,
27 h1, h2, h3, h4, h5, h6,
28 .h1, .h2, .h3, .h4, .h5, .h6 {
28 .h1, .h2, .h3, .h4, .h5, .h6 {
29 margin: 0 0 @textmargin 0;
29 margin: 0 0 @textmargin 0;
30 padding: 0;
30 padding: 0;
31 line-height: 1.8em;
31 line-height: 1.8em;
32 color: @text-color;
32 color: @text-color;
33 a {
33 a {
34 color: @rcblue;
34 color: @rcblue;
35 }
35 }
36 }
36 }
37
37
38 h1, .h1 { font-size: 1.54em; font-family: @text-bold; }
38 h1, .h1 { font-size: 1.54em; font-family: @text-bold; }
39 h2, .h2 { font-size: 1.23em; font-family: @text-semibold; }
39 h2, .h2 { font-size: 1.23em; font-family: @text-semibold; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
41 h4, .h4 { font-size: 1em; font-family: @text-bold; }
41 h4, .h4 { font-size: 1em; font-family: @text-bold; }
42 h5, .h5 { font-size: 1em; font-family: @text-bold-italic; }
42 h5, .h5 { font-size: 1em; font-family: @text-bold-italic; }
43 h6, .h6 { font-size: 1em; font-family: @text-bold-italic; }
43 h6, .h6 { font-size: 1em; font-family: @text-bold-italic; }
44
44
45 // Breadcrumbs
45 // Breadcrumbs
46 .breadcrumbs {
46 .breadcrumbs {
47 &:extend(h1);
47 &:extend(h1);
48 margin: 0;
48 margin: 0;
49 }
49 }
50
50
51 .breadcrumbs_light {
51 .breadcrumbs_light {
52 float:left;
52 float:left;
53 font-size: 1.3em;
53 font-size: 1.3em;
54 line-height: 38px;
54 line-height: 38px;
55 }
55 }
56
56
57 // Body text
57 // Body text
58 // -------------------------
58 // -------------------------
59
59
60 p {
60 p {
61 margin: 0 0 @textmargin 0;
61 margin: 0 0 @textmargin 0;
62 padding: 0;
62 padding: 0;
63 line-height: 2em;
63 line-height: 2em;
64 }
64 }
65
65
66 .lead {
66 .lead {
67 margin-bottom: @textmargin;
67 margin-bottom: @textmargin;
68 font-weight: 300;
68 font-weight: 300;
69 line-height: 1.4;
69 line-height: 1.4;
70
70
71 @media (min-width: @screen-sm-min) {
71 @media (min-width: @screen-sm-min) {
72 font-size: (@basefontsize * 1.5);
72 font-size: (@basefontsize * 1.5);
73 }
73 }
74 }
74 }
75
75
76 a,
76 a,
77 .link {
77 .link {
78 color: @rcblue;
78 color: @rcblue;
79 text-decoration: none;
79 text-decoration: none;
80 outline: none;
80 outline: none;
81 cursor: pointer;
81 cursor: pointer;
82
82
83 &:focus {
83 &:focus {
84 outline: none;
84 outline: none;
85 }
85 }
86
86
87 &:hover {
87 &:hover {
88 color: @rcdarkblue;
88 color: @rcdarkblue;
89 }
89 }
90 }
90 }
91
91
92 img {
92 img {
93 border: none;
93 border: none;
94 outline: none;
94 outline: none;
95 }
95 }
96
96
97 strong {
97 strong {
98 font-family: @text-bold;
98 font-family: @text-bold;
99 }
99 }
100
100
101 em {
101 em {
102 font-family: @text-italic;
102 font-family: @text-italic;
103 }
103 }
104
104
105 strong em,
105 strong em,
106 em strong {
106 em strong {
107 font-family: @text-bold-italic;
107 font-family: @text-bold-italic;
108 }
108 }
109
109
110 //TODO: lisa: b and i are depreciated, but we are still using them in places.
110 //TODO: lisa: b and i are depreciated, but we are still using them in places.
111 // Should probably make some decision whether to keep or lose these.
111 // Should probably make some decision whether to keep or lose these.
112 b {
112 b {
113
113
114 }
114 }
115
115
116 i {
116 i {
117 font-style: normal;
117 font-style: normal;
118 }
118 }
119
119
120 label {
120 label {
121 color: @text-color;
121 color: @text-color;
122
122
123 input[type="checkbox"] {
123 input[type="checkbox"] {
124 margin-right: 1em;
124 margin-right: 1em;
125 }
125 }
126 input[type="radio"] {
126 input[type="radio"] {
127 margin-right: 1em;
127 margin-right: 1em;
128 }
128 }
129 }
129 }
130
130
131 code,
131 code,
132 .code {
132 .code {
133 font-size: .95em;
133 font-size: .95em;
134 font-family: "Lucida Console", Monaco, monospace;
134 font-family: "Lucida Console", Monaco, monospace;
135 color: @grey3;
135 color: @grey3;
136
136
137 a {
137 a {
138 color: lighten(@rcblue,10%)
138 color: lighten(@rcblue,10%)
139 }
139 }
140 }
140 }
141
141
142 pre {
142 pre {
143 margin: 0;
143 margin: 0;
144 padding: 0;
144 padding: 0;
145 border: 0;
145 border: 0;
146 outline: 0;
146 outline: 0;
147 font-size: @basefontsize*.95;
147 font-size: @basefontsize*.95;
148 line-height: 1.4em;
148 line-height: 1.4em;
149 font-family: "Lucida Console", Monaco, monospace;
149 font-family: "Lucida Console", Monaco, monospace;
150 color: @grey3;
150 color: @grey3;
151 }
151 }
152
152
153 // Emphasis & misc
153 // Emphasis & misc
154 // -------------------------
154 // -------------------------
155
155
156 small,
156 small,
157 .small {
157 .small {
158 font-size: 75%;
158 font-size: 75%;
159 font-weight: normal;
159 font-weight: normal;
160 line-height: 1em;
160 line-height: 1em;
161 }
161 }
162
162
163 mark,
163 mark,
164 .mark {
164 .mark {
165 background-color: @rclightblue;
165 background-color: @rclightblue;
166 padding: .2em;
166 padding: .2em;
167 }
167 }
168
168
169 // Alignment
169 // Alignment
170 .text-left { text-align: left; }
170 .text-left { text-align: left; }
171 .text-right { text-align: right; }
171 .text-right { text-align: right; }
172 .text-center { text-align: center; }
172 .text-center { text-align: center; }
173 .text-justify { text-align: justify; }
173 .text-justify { text-align: justify; }
174 .text-nowrap { white-space: nowrap; }
174 .text-nowrap { white-space: nowrap; }
175
175
176 // Transformation
176 // Transformation
177 .text-lowercase { text-transform: lowercase; }
177 .text-lowercase { text-transform: lowercase; }
178 .text-uppercase { text-transform: uppercase; }
178 .text-uppercase { text-transform: uppercase; }
179 .text-capitalize { text-transform: capitalize; }
179 .text-capitalize { text-transform: capitalize; }
180
180
181 // Contextual colors
181 // Contextual colors
182 .text-muted {
182 .text-muted {
183 color: @grey4;
183 color: @grey4;
184 }
184 }
185 .text-primary {
185 .text-primary {
186 color: @rcblue;
186 color: @rcblue;
187 }
187 }
188 .text-success {
188 .text-success {
189 color: @alert1;
189 color: @alert1;
190 }
190 }
191 .text-info {
191 .text-info {
192 color: @alert4;
192 color: @alert4;
193 }
193 }
194 .text-warning {
194 .text-warning {
195 color: @alert3;
195 color: @alert3;
196 }
196 }
197 .text-danger {
197 .text-danger {
198 color: @alert2;
198 color: @alert2;
199 }
199 }
200
200
201 // Contextual backgrounds
201 // Contextual backgrounds
202 .bg-primary {
202 .bg-primary {
203 background-color: white;
203 background-color: white;
204 }
204 }
205 .bg-success {
205 .bg-success {
206 background-color: @alert1;
206 background-color: @alert1;
207 }
207 }
208 .bg-info {
208 .bg-info {
209 background-color: @alert4;
209 background-color: @alert4;
210 }
210 }
211 .bg-warning {
211 .bg-warning {
212 background-color: @alert3;
212 background-color: @alert3;
213 }
213 }
214 .bg-danger {
214 .bg-danger {
215 background-color: @alert2;
215 background-color: @alert2;
216 }
216 }
217
217
218
218
219 // Page header
219 // Page header
220 // -------------------------
220 // -------------------------
221
221
222 .page-header {
222 .page-header {
223 margin: @pagepadding 0 @textmargin;
223 margin: @pagepadding 0 @textmargin;
224 border-bottom: @border-thickness solid @grey5;
224 border-bottom: @border-thickness solid @grey5;
225 }
225 }
226
226
227 .title {
227 .title {
228 clear: both;
228 clear: both;
229 float: left;
229 float: left;
230 width: 100%;
230 width: 100%;
231 margin: @pagepadding 0 @pagepadding;
231 margin: @pagepadding 0 @pagepadding;
232
232
233 .breadcrumbs{
233 .breadcrumbs{
234 float: left;
234 float: left;
235 clear: both;
235 clear: both;
236 width: 700px;
236 width: 700px;
237 margin: 0;
237 margin: 0;
238
238
239 .q_filter_box {
239 .q_filter_box {
240 margin-right: @padding;
240 margin-right: @padding;
241 }
241 }
242 }
242 }
243
243
244 h1 a {
244 h1 a {
245 color: @rcblue;
245 color: @rcblue;
246 }
246 }
247
247
248 input{
248 input{
249 margin-right: @padding;
249 margin-right: @padding;
250 }
250 }
251
251
252 h5, .h5 {
252 h5, .h5 {
253 color: @grey1;
253 color: @grey1;
254 margin-bottom: @space;
254 margin-bottom: @space;
255
255
256 span {
256 span {
257 display: inline-block;
257 display: inline-block;
258 }
258 }
259 }
259 }
260
260
261 p {
261 p {
262 margin-bottom: 0;
262 margin-bottom: 0;
263 }
263 }
264
264
265 .links {
265 .links {
266 float: right;
266 float: right;
267 display: inline;
267 display: inline;
268 margin: 0;
268 margin: 0;
269 padding-left: 0;
269 padding-left: 0;
270 list-style: none;
270 list-style: none;
271 text-align: right;
271 text-align: right;
272
272
273 li:before { content: none; }
273 li:before { content: none; }
274 li { float: right; }
274 li { float: right; }
275 a {
275 a {
276 display: inline-block;
276 display: inline-block;
277 margin-left: @textmargin/2;
277 margin-left: @textmargin/2;
278 }
278 }
279 }
279 }
280
280
281 .title-content {
281 .title-content {
282 float: left;
282 float: left;
283 margin: 0;
283 margin: 0;
284 padding: 0;
284 padding: 0;
285
285
286 & + .breadcrumbs {
286 & + .breadcrumbs {
287 margin-top: @padding;
287 margin-top: @padding;
288 }
288 }
289
289
290 & + .links {
290 & + .links {
291 margin-top: -@button-padding;
291 margin-top: -@button-padding;
292
292
293 & + .breadcrumbs {
293 & + .breadcrumbs {
294 margin-top: @padding;
294 margin-top: @padding;
295 }
295 }
296 }
296 }
297 }
297 }
298
298
299 .title-main {
299 .title-main {
300 font-size: @repo-title-fontsize;
300 font-size: @repo-title-fontsize;
301 }
301 }
302
302
303 .title-description {
303 .title-description {
304 margin-top: .5em;
304 margin-top: .5em;
305 }
305 }
306
306
307 .q_filter_box {
307 .q_filter_box {
308 width: 200px;
308 width: 200px;
309 }
309 }
310
310
311 }
311 }
312
312
313 #readme .title {
313 #readme .title {
314 text-transform: none;
314 text-transform: none;
315 }
315 }
316
316
317 // Lists
317 // Lists
318 // -------------------------
318 // -------------------------
319
319
320 // Unordered and Ordered lists
320 // Unordered and Ordered lists
321 ul,
321 ul,
322 ol {
322 ol {
323 margin-top: 0;
323 margin-top: 0;
324 margin-bottom: @textmargin;
324 margin-bottom: @textmargin;
325 ul,
325 ul,
326 ol {
326 ol {
327 margin-bottom: 0;
327 margin-bottom: 0;
328 }
328 }
329 }
329 }
330
330
331 li {
331 li {
332 line-height: 2em;
332 line-height: 2em;
333 }
333 }
334
334
335 ul li {
335 ul li {
336 position: relative;
336 position: relative;
337 display: block;
337 display: block;
338 list-style-type: none;
338 list-style-type: none;
339
339
340 &:before {
340 &:before {
341 content: "\2014\00A0";
341 content: "\2014\00A0";
342 position: absolute;
342 position: absolute;
343 top: 0;
343 top: 0;
344 left: -1.25em;
344 left: -1.25em;
345 }
345 }
346
346
347 p:first-child {
347 p:first-child {
348 display:inline;
348 display:inline;
349 }
349 }
350 }
350 }
351
351
352 // List options
352 // List options
353
353
354 // Unstyled keeps list items block level, just removes default browser padding and list-style
354 // Unstyled keeps list items block level, just removes default browser padding and list-style
355 .list-unstyled {
355 .list-unstyled {
356 padding-left: 0;
356 padding-left: 0;
357 list-style: none;
357 list-style: none;
358 li:before { content: none; }
358 li:before { content: none; }
359 }
359 }
360
360
361 // Inline turns list items into inline-block
361 // Inline turns list items into inline-block
362 .list-inline {
362 .list-inline {
363 .list-unstyled();
363 .list-unstyled();
364 margin-left: -5px;
364 margin-left: -5px;
365
365
366 > li {
366 > li {
367 display: inline-block;
367 display: inline-block;
368 padding-left: 5px;
368 padding-left: 5px;
369 padding-right: 5px;
369 padding-right: 5px;
370 }
370 }
371 }
371 }
372
372
373 // Description Lists
373 // Description Lists
374
374
375 dl {
375 dl {
376 margin-top: 0; // Remove browser default
376 margin-top: 0; // Remove browser default
377 margin-bottom: @textmargin;
377 margin-bottom: @textmargin;
378 }
378 }
379
379
380 dt,
380 dt,
381 dd {
381 dd {
382 line-height: 1.4em;
382 line-height: 1.4em;
383 }
383 }
384
384
385 dt {
385 dt {
386 margin: @textmargin 0 0 0;
386 margin: @textmargin 0 0 0;
387 font-family: @text-bold;
387 font-family: @text-bold;
388 }
388 }
389
389
390 dd {
390 dd {
391 margin-left: 0; // Undo browser default
391 margin-left: 0; // Undo browser default
392 }
392 }
393
393
394 // Horizontal description lists
394 // Horizontal description lists
395 // Defaults to being stacked without any of the below styles applied, until the
395 // Defaults to being stacked without any of the below styles applied, until the
396 // grid breakpoint is reached (default of ~768px).
396 // grid breakpoint is reached (default of ~768px).
397 // These are used in forms as well; see style guide.
397 // These are used in forms as well; see style guide.
398 // TODO: lisa: These should really not be used in forms.
398 // TODO: lisa: These should really not be used in forms.
399
399
400 .dl-horizontal {
400 .dl-horizontal {
401
401
402 overflow: hidden;
402 overflow: hidden;
403 margin-bottom: @space;
403 margin-bottom: @space;
404
404
405 dt, dd {
405 dt, dd {
406 float: left;
406 float: left;
407 margin: 5px 0 5px 0;
407 margin: 5px 0 5px 0;
408 }
408 }
409
409
410 dt {
410 dt {
411 clear: left;
411 clear: left;
412 width: @label-width - @form-vertical-margin;
412 width: @label-width - @form-vertical-margin;
413 }
413 }
414
414
415 dd {
415 dd {
416 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
416 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
417 margin-left: @form-vertical-margin;
417 margin-left: @form-vertical-margin;
418 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
418 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
419 }
419 }
420
420
421 pre {
421 pre {
422 margin: 0;
422 margin: 0;
423 }
423 }
424
424
425 &.settings {
425 &.settings {
426 dt {
426 dt {
427 text-align: left;
427 text-align: left;
428 }
428 }
429 }
429 }
430
430
431 @media (min-width: 768px) {
431 @media (min-width: 768px) {
432 dt {
432 dt {
433 float: left;
433 float: left;
434 width: 180px;
434 width: 180px;
435 clear: left;
435 clear: left;
436 text-align: right;
436 text-align: right;
437 }
437 }
438 dd {
438 dd {
439 margin-left: 20px;
439 margin-left: 20px;
440 }
440 }
441 }
441 }
442 }
442 }
443
443
444
444
445 // Misc
445 // Misc
446 // -------------------------
446 // -------------------------
447
447
448 // Abbreviations and acronyms
448 // Abbreviations and acronyms
449 abbr[title],
449 abbr[title],
450 abbr[data-original-title] {
450 abbr[data-original-title] {
451 cursor: help;
451 cursor: help;
452 border-bottom: @border-thickness dotted @grey4;
452 border-bottom: @border-thickness dotted @grey4;
453 }
453 }
454 .initialism {
454 .initialism {
455 font-size: 90%;
455 font-size: 90%;
456 text-transform: uppercase;
456 text-transform: uppercase;
457 }
457 }
458
458
459 // Blockquotes
459 // Blockquotes
460 blockquote {
460 blockquote {
461 padding: 1em 2em;
461 padding: 1em 2em;
462 margin: 0 0 2em;
462 margin: 0 0 2em;
463 font-size: @basefontsize;
463 font-size: @basefontsize;
464 border-left: 2px solid @grey6;
464 border-left: 2px solid @grey6;
465
465
466 p,
466 p,
467 ul,
467 ul,
468 ol {
468 ol {
469 &:last-child {
469 &:last-child {
470 margin-bottom: 0;
470 margin-bottom: 0;
471 }
471 }
472 }
472 }
473
473
474 footer,
474 footer,
475 small,
475 small,
476 .small {
476 .small {
477 display: block;
477 display: block;
478 font-size: 80%;
478 font-size: 80%;
479
479
480 &:before {
480 &:before {
481 content: '\2014 \00A0'; // em dash, nbsp
481 content: '\2014 \00A0'; // em dash, nbsp
482 }
482 }
483 }
483 }
484 }
484 }
485
485
486 // Opposite alignment of blockquote
486 // Opposite alignment of blockquote
487 //
487 //
488 .blockquote-reverse,
488 .blockquote-reverse,
489 blockquote.pull-right {
489 blockquote.pull-right {
490 padding-right: 15px;
490 padding-right: 15px;
491 padding-left: 0;
491 padding-left: 0;
492 border-right: 5px solid @grey6;
492 border-right: 5px solid @grey6;
493 border-left: 0;
493 border-left: 0;
494 text-align: right;
494 text-align: right;
495
495
496 // Account for citation
496 // Account for citation
497 footer,
497 footer,
498 small,
498 small,
499 .small {
499 .small {
500 &:before { content: ''; }
500 &:before { content: ''; }
501 &:after {
501 &:after {
502 content: '\00A0 \2014'; // nbsp, em dash
502 content: '\00A0 \2014'; // nbsp, em dash
503 }
503 }
504 }
504 }
505 }
505 }
506
506
507 // Addresses
507 // Addresses
508 address {
508 address {
509 margin-bottom: 2em;
509 margin-bottom: 2em;
510 font-style: normal;
510 font-style: normal;
511 line-height: 1.8em;
511 line-height: 1.8em;
512 }
512 }
513
513
514 .error-message {
514 .error-message {
515 display: block;
515 display: block;
516 margin: @padding/3 0;
516 margin: @padding/3 0;
517 color: @alert2;
517 color: @alert2;
518 }
518 }
519
519
520 .issue-tracker-link {
520 .issue-tracker-link {
521 color: @rcblue;
521 color: @rcblue;
522 }
522 }
523
523
524 .info_text{
524 .info_text{
525 font-size: @basefontsize;
525 font-size: @basefontsize;
526 color: @grey4;
526 color: @grey4;
527 font-family: @text-regular;
527 font-family: @text-regular;
528 }
528 }
529
529
530 // help block text
530 // help block text
531 .help-block {
531 .help-block {
532 display: block;
532 display: block;
533 margin: 0 0 @padding;
533 margin: 0 0 @padding;
534 color: @grey4;
534 color: @grey4;
535 font-family: @text-light;
535 font-family: @text-light;
536 &.pre-formatting {
537 white-space: pre;
538 }
536 }
539 }
537
540
538 .error-message {
541 .error-message {
539 display: block;
542 display: block;
540 margin: @padding/3 0;
543 margin: @padding/3 0;
541 color: @alert2;
544 color: @alert2;
542 }
545 }
@@ -1,158 +1,159 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
7
7
8 (_('Last login'), c.user.last_login or '-', '', ''),
8 (_('Last login'), c.user.last_login or '-', '', ''),
9 (_('Last activity'), c.user.last_activity, '', ''),
9 (_('Last activity'), c.user.last_activity, '', ''),
10
10
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
14
14
15 (_('Reviewer of pull requests'), len(c.user.reviewer_pull_requests), '', ['Pull Request #{}'.format(x.pull_request.pull_request_id) for x in c.user.reviewer_pull_requests]),
15 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
16 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
16 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
17 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
17 ]
18 ]
18 %>
19 %>
19
20
20 <div class="panel panel-default">
21 <div class="panel panel-default">
21 <div class="panel-heading">
22 <div class="panel-heading">
22 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
23 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
23 </div>
24 </div>
24 <div class="panel-body">
25 <div class="panel-body">
25 ${base.dt_info_panel(elems)}
26 ${base.dt_info_panel(elems)}
26 </div>
27 </div>
27 </div>
28 </div>
28
29
29 <div class="panel panel-default">
30 <div class="panel panel-default">
30 <div class="panel-heading">
31 <div class="panel-heading">
31 <h3 class="panel-title">${_('Force Password Reset')}</h3>
32 <h3 class="panel-title">${_('Force Password Reset')}</h3>
32 </div>
33 </div>
33 <div class="panel-body">
34 <div class="panel-body">
34 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
35 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
35 <div class="field">
36 <div class="field">
36 <button class="btn btn-default" type="submit">
37 <button class="btn btn-default" type="submit">
37 <i class="icon-lock"></i>
38 <i class="icon-lock"></i>
38 %if c.user.user_data.get('force_password_change'):
39 %if c.user.user_data.get('force_password_change'):
39 ${_('Disable forced password reset')}
40 ${_('Disable forced password reset')}
40 %else:
41 %else:
41 ${_('Enable forced password reset')}
42 ${_('Enable forced password reset')}
42 %endif
43 %endif
43 </button>
44 </button>
44 </div>
45 </div>
45 <div class="field">
46 <div class="field">
46 <span class="help-block">
47 <span class="help-block">
47 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
48 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
48 </span>
49 </span>
49 </div>
50 </div>
50 ${h.end_form()}
51 ${h.end_form()}
51 </div>
52 </div>
52 </div>
53 </div>
53
54
54 <div class="panel panel-default">
55 <div class="panel panel-default">
55 <div class="panel-heading">
56 <div class="panel-heading">
56 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
57 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
57 </div>
58 </div>
58 <div class="panel-body">
59 <div class="panel-body">
59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60
61
61 %if c.personal_repo_group:
62 %if c.personal_repo_group:
62 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
63 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
63 %else:
64 %else:
64 <div class="panel-body-title-text">
65 <div class="panel-body-title-text">
65 ${_('This user currently does not have a personal repository group')}
66 ${_('This user currently does not have a personal repository group')}
66 <br/>
67 <br/>
67 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
68 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
68 </div>
69 </div>
69 %endif
70 %endif
70 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
71 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
71 <i class="icon-folder-close"></i>
72 <i class="icon-folder-close"></i>
72 ${_('Create personal repository group')}
73 ${_('Create personal repository group')}
73 </button>
74 </button>
74 ${h.end_form()}
75 ${h.end_form()}
75 </div>
76 </div>
76 </div>
77 </div>
77
78
78
79
79 <div class="panel panel-danger">
80 <div class="panel panel-danger">
80 <div class="panel-heading">
81 <div class="panel-heading">
81 <h3 class="panel-title">${_('Delete User')}</h3>
82 <h3 class="panel-title">${_('Delete User')}</h3>
82 </div>
83 </div>
83 <div class="panel-body">
84 <div class="panel-body">
84 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
85 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
85
86
86 <table class="display">
87 <table class="display">
87 <tr>
88 <tr>
88 <td>
89 <td>
89 ${ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
90 ${ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
90 </td>
91 </td>
91 <td>
92 <td>
92 %if len(c.user.repositories) > 0:
93 %if len(c.user.repositories) > 0:
93 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
94 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
94 %endif
95 %endif
95 </td>
96 </td>
96 <td>
97 <td>
97 %if len(c.user.repositories) > 0:
98 %if len(c.user.repositories) > 0:
98 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
99 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
99 %endif
100 %endif
100 </td>
101 </td>
101 </tr>
102 </tr>
102
103
103 <tr>
104 <tr>
104 <td>
105 <td>
105 ${ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
106 ${ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
106 </td>
107 </td>
107 <td>
108 <td>
108 %if len(c.user.repository_groups) > 0:
109 %if len(c.user.repository_groups) > 0:
109 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
110 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
110 %endif
111 %endif
111 </td>
112 </td>
112 <td>
113 <td>
113 %if len(c.user.repository_groups) > 0:
114 %if len(c.user.repository_groups) > 0:
114 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
115 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
115 %endif
116 %endif
116 </td>
117 </td>
117 </tr>
118 </tr>
118
119
119 <tr>
120 <tr>
120 <td>
121 <td>
121 ${ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
122 ${ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
122 </td>
123 </td>
123 <td>
124 <td>
124 %if len(c.user.user_groups) > 0:
125 %if len(c.user.user_groups) > 0:
125 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
126 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
126 %endif
127 %endif
127 </td>
128 </td>
128 <td>
129 <td>
129 %if len(c.user.user_groups) > 0:
130 %if len(c.user.user_groups) > 0:
130 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
131 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
131 %endif
132 %endif
132 </td>
133 </td>
133 </tr>
134 </tr>
134 </table>
135 </table>
135 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136
137
137 <div class="field">
138 <div class="field">
138 <button class="btn btn-small btn-danger" type="submit"
139 <button class="btn btn-small btn-danger" type="submit"
139 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
140 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
140 ${"disabled" if not c.can_delete_user else ""}>
141 ${"disabled" if not c.can_delete_user else ""}>
141 ${_('Delete this user')}
142 ${_('Delete this user')}
142 </button>
143 </button>
143 </div>
144 </div>
144 % if c.can_delete_user_message:
145 % if c.can_delete_user_message:
145 <p class="help-block">${c.can_delete_user_message}</p>
146 <p class="help-block pre-formatting">${c.can_delete_user_message}</p>
146 % endif
147 % endif
147
148
148 <div class="field">
149 <div class="field">
149 <span class="help-block">
150 <span class="help-block">
150 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
151 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
151 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
152 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
152 %endif
153 %endif
153 </span>
154 </span>
154 </div>
155 </div>
155
156
156 ${h.end_form()}
157 ${h.end_form()}
157 </div>
158 </div>
158 </div>
159 </div>
General Comments 0
You need to be logged in to leave comments. Login now