##// END OF EJS Templates
quick-filter: make sure we always apply IN filter query. Otherwise we can...
marcink -
r2167:23aaeb72 default
parent child Browse files
Show More
@@ -1,322 +1,322 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-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 import re
21 import re
22 import logging
22 import logging
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
29 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
30 from rhodecode.lib.index import searcher_from_config
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 func, or_, in_filter_generator, Repository, RepoGroup)
34 func, or_, in_filter_generator, Repository, RepoGroup)
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import RepoGroupList, RepoList
37 from rhodecode.model.scm import RepoGroupList, RepoList
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
39 from rhodecode.model.user_group import UserGroupModel
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class HomeView(BaseAppView):
44 class HomeView(BaseAppView):
45
45
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
48 c.user = c.auth_user.get_instance()
48 c.user = c.auth_user.get_instance()
49 self._register_global_c(c)
49 self._register_global_c(c)
50 return c
50 return c
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @view_config(
53 @view_config(
54 route_name='user_autocomplete_data', request_method='GET',
54 route_name='user_autocomplete_data', request_method='GET',
55 renderer='json_ext', xhr=True)
55 renderer='json_ext', xhr=True)
56 def user_autocomplete_data(self):
56 def user_autocomplete_data(self):
57 query = self.request.GET.get('query')
57 query = self.request.GET.get('query')
58 active = str2bool(self.request.GET.get('active') or True)
58 active = str2bool(self.request.GET.get('active') or True)
59 include_groups = str2bool(self.request.GET.get('user_groups'))
59 include_groups = str2bool(self.request.GET.get('user_groups'))
60 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
60 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
61 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
61 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
62
62
63 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
63 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
64 query, active, include_groups)
64 query, active, include_groups)
65
65
66 _users = UserModel().get_users(
66 _users = UserModel().get_users(
67 name_contains=query, only_active=active)
67 name_contains=query, only_active=active)
68
68
69 def maybe_skip_default_user(usr):
69 def maybe_skip_default_user(usr):
70 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
70 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
71 return False
71 return False
72 return True
72 return True
73 _users = filter(maybe_skip_default_user, _users)
73 _users = filter(maybe_skip_default_user, _users)
74
74
75 if include_groups:
75 if include_groups:
76 # extend with user groups
76 # extend with user groups
77 _user_groups = UserGroupModel().get_user_groups(
77 _user_groups = UserGroupModel().get_user_groups(
78 name_contains=query, only_active=active,
78 name_contains=query, only_active=active,
79 expand_groups=expand_groups)
79 expand_groups=expand_groups)
80 _users = _users + _user_groups
80 _users = _users + _user_groups
81
81
82 return {'suggestions': _users}
82 return {'suggestions': _users}
83
83
84 @LoginRequired()
84 @LoginRequired()
85 @NotAnonymous()
85 @NotAnonymous()
86 @view_config(
86 @view_config(
87 route_name='user_group_autocomplete_data', request_method='GET',
87 route_name='user_group_autocomplete_data', request_method='GET',
88 renderer='json_ext', xhr=True)
88 renderer='json_ext', xhr=True)
89 def user_group_autocomplete_data(self):
89 def user_group_autocomplete_data(self):
90 query = self.request.GET.get('query')
90 query = self.request.GET.get('query')
91 active = str2bool(self.request.GET.get('active') or True)
91 active = str2bool(self.request.GET.get('active') or True)
92 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
92 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
93
93
94 log.debug('generating user group list, query:%s, active:%s',
94 log.debug('generating user group list, query:%s, active:%s',
95 query, active)
95 query, active)
96
96
97 _user_groups = UserGroupModel().get_user_groups(
97 _user_groups = UserGroupModel().get_user_groups(
98 name_contains=query, only_active=active,
98 name_contains=query, only_active=active,
99 expand_groups=expand_groups)
99 expand_groups=expand_groups)
100 _user_groups = _user_groups
100 _user_groups = _user_groups
101
101
102 return {'suggestions': _user_groups}
102 return {'suggestions': _user_groups}
103
103
104 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
104 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
105 allowed_ids = self._rhodecode_user.repo_acl_ids(
105 allowed_ids = self._rhodecode_user.repo_acl_ids(
106 ['repository.read', 'repository.write', 'repository.admin'],
106 ['repository.read', 'repository.write', 'repository.admin'],
107 cache=False, name_filter=name_contains)
107 cache=False, name_filter=name_contains) or [-1]
108
108
109 query = Repository.query()\
109 query = Repository.query()\
110 .order_by(func.length(Repository.repo_name))\
110 .order_by(func.length(Repository.repo_name))\
111 .order_by(Repository.repo_name)\
111 .order_by(Repository.repo_name)\
112 .filter(or_(
112 .filter(or_(
113 # generate multiple IN to fix limitation problems
113 # generate multiple IN to fix limitation problems
114 *in_filter_generator(Repository.repo_id, allowed_ids)
114 *in_filter_generator(Repository.repo_id, allowed_ids)
115 ))
115 ))
116
116
117 if repo_type:
117 if repo_type:
118 query = query.filter(Repository.repo_type == repo_type)
118 query = query.filter(Repository.repo_type == repo_type)
119
119
120 if name_contains:
120 if name_contains:
121 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
121 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
122 query = query.filter(
122 query = query.filter(
123 Repository.repo_name.ilike(ilike_expression))
123 Repository.repo_name.ilike(ilike_expression))
124 query = query.limit(limit)
124 query = query.limit(limit)
125
125
126 acl_repo_iter = query
126 acl_repo_iter = query
127
127
128 return [
128 return [
129 {
129 {
130 'id': obj.repo_name,
130 'id': obj.repo_name,
131 'text': obj.repo_name,
131 'text': obj.repo_name,
132 'type': 'repo',
132 'type': 'repo',
133 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
133 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
134 'repo_id': obj.repo_id},
134 'repo_id': obj.repo_id},
135 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
135 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
136 }
136 }
137 for obj in acl_repo_iter]
137 for obj in acl_repo_iter]
138
138
139 def _get_repo_group_list(self, name_contains=None, limit=20):
139 def _get_repo_group_list(self, name_contains=None, limit=20):
140 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
140 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
141 ['group.read', 'group.write', 'group.admin'],
141 ['group.read', 'group.write', 'group.admin'],
142 cache=False, name_filter=name_contains)
142 cache=False, name_filter=name_contains) or [-1]
143
143
144 query = RepoGroup.query()\
144 query = RepoGroup.query()\
145 .order_by(func.length(RepoGroup.group_name))\
145 .order_by(func.length(RepoGroup.group_name))\
146 .order_by(RepoGroup.group_name) \
146 .order_by(RepoGroup.group_name) \
147 .filter(or_(
147 .filter(or_(
148 # generate multiple IN to fix limitation problems
148 # generate multiple IN to fix limitation problems
149 *in_filter_generator(RepoGroup.group_id, allowed_ids)
149 *in_filter_generator(RepoGroup.group_id, allowed_ids)
150 ))
150 ))
151
151
152 if name_contains:
152 if name_contains:
153 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
153 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
154 query = query.filter(
154 query = query.filter(
155 RepoGroup.group_name.ilike(ilike_expression))
155 RepoGroup.group_name.ilike(ilike_expression))
156 query = query.limit(limit)
156 query = query.limit(limit)
157
157
158 acl_repo_iter = query
158 acl_repo_iter = query
159
159
160 return [
160 return [
161 {
161 {
162 'id': obj.group_name,
162 'id': obj.group_name,
163 'text': obj.group_name,
163 'text': obj.group_name,
164 'type': 'group',
164 'type': 'group',
165 'obj': {},
165 'obj': {},
166 'url': h.route_path(
166 'url': h.route_path(
167 'repo_group_home', repo_group_name=obj.group_name)
167 'repo_group_home', repo_group_name=obj.group_name)
168 }
168 }
169 for obj in acl_repo_iter]
169 for obj in acl_repo_iter]
170
170
171 def _get_hash_commit_list(self, auth_user, query=None):
171 def _get_hash_commit_list(self, auth_user, query=None):
172 if not query or len(query) < 3:
172 if not query or len(query) < 3:
173 return []
173 return []
174
174
175 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
175 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
176
176
177 if len(commit_hashes) != 1:
177 if len(commit_hashes) != 1:
178 return []
178 return []
179
179
180 commit_hash_prefix = commit_hashes[0]
180 commit_hash_prefix = commit_hashes[0]
181
181
182 searcher = searcher_from_config(self.request.registry.settings)
182 searcher = searcher_from_config(self.request.registry.settings)
183 result = searcher.search(
183 result = searcher.search(
184 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
184 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
185 raise_on_exc=False)
185 raise_on_exc=False)
186
186
187 return [
187 return [
188 {
188 {
189 'id': entry['commit_id'],
189 'id': entry['commit_id'],
190 'text': entry['commit_id'],
190 'text': entry['commit_id'],
191 'type': 'commit',
191 'type': 'commit',
192 'obj': {'repo': entry['repository']},
192 'obj': {'repo': entry['repository']},
193 'url': h.route_path(
193 'url': h.route_path(
194 'repo_commit',
194 'repo_commit',
195 repo_name=entry['repository'], commit_id=entry['commit_id'])
195 repo_name=entry['repository'], commit_id=entry['commit_id'])
196 }
196 }
197 for entry in result['results']]
197 for entry in result['results']]
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @view_config(
200 @view_config(
201 route_name='repo_list_data', request_method='GET',
201 route_name='repo_list_data', request_method='GET',
202 renderer='json_ext', xhr=True)
202 renderer='json_ext', xhr=True)
203 def repo_list_data(self):
203 def repo_list_data(self):
204 _ = self.request.translate
204 _ = self.request.translate
205
205
206 query = self.request.GET.get('query')
206 query = self.request.GET.get('query')
207 repo_type = self.request.GET.get('repo_type')
207 repo_type = self.request.GET.get('repo_type')
208 log.debug('generating repo list, query:%s, repo_type:%s',
208 log.debug('generating repo list, query:%s, repo_type:%s',
209 query, repo_type)
209 query, repo_type)
210
210
211 res = []
211 res = []
212 repos = self._get_repo_list(query, repo_type=repo_type)
212 repos = self._get_repo_list(query, repo_type=repo_type)
213 if repos:
213 if repos:
214 res.append({
214 res.append({
215 'text': _('Repositories'),
215 'text': _('Repositories'),
216 'children': repos
216 'children': repos
217 })
217 })
218
218
219 data = {
219 data = {
220 'more': False,
220 'more': False,
221 'results': res
221 'results': res
222 }
222 }
223 return data
223 return data
224
224
225 @LoginRequired()
225 @LoginRequired()
226 @view_config(
226 @view_config(
227 route_name='goto_switcher_data', request_method='GET',
227 route_name='goto_switcher_data', request_method='GET',
228 renderer='json_ext', xhr=True)
228 renderer='json_ext', xhr=True)
229 def goto_switcher_data(self):
229 def goto_switcher_data(self):
230 c = self.load_default_context()
230 c = self.load_default_context()
231
231
232 _ = self.request.translate
232 _ = self.request.translate
233
233
234 query = self.request.GET.get('query')
234 query = self.request.GET.get('query')
235 log.debug('generating goto switcher list, query %s', query)
235 log.debug('generating goto switcher list, query %s', query)
236
236
237 res = []
237 res = []
238 repo_groups = self._get_repo_group_list(query)
238 repo_groups = self._get_repo_group_list(query)
239 if repo_groups:
239 if repo_groups:
240 res.append({
240 res.append({
241 'text': _('Groups'),
241 'text': _('Groups'),
242 'children': repo_groups
242 'children': repo_groups
243 })
243 })
244
244
245 repos = self._get_repo_list(query)
245 repos = self._get_repo_list(query)
246 if repos:
246 if repos:
247 res.append({
247 res.append({
248 'text': _('Repositories'),
248 'text': _('Repositories'),
249 'children': repos
249 'children': repos
250 })
250 })
251
251
252 commits = self._get_hash_commit_list(c.auth_user, query)
252 commits = self._get_hash_commit_list(c.auth_user, query)
253 if commits:
253 if commits:
254 unique_repos = {}
254 unique_repos = {}
255 for commit in commits:
255 for commit in commits:
256 unique_repos.setdefault(commit['obj']['repo'], []
256 unique_repos.setdefault(commit['obj']['repo'], []
257 ).append(commit)
257 ).append(commit)
258
258
259 for repo in unique_repos:
259 for repo in unique_repos:
260 res.append({
260 res.append({
261 'text': _('Commits in %(repo)s') % {'repo': repo},
261 'text': _('Commits in %(repo)s') % {'repo': repo},
262 'children': unique_repos[repo]
262 'children': unique_repos[repo]
263 })
263 })
264
264
265 data = {
265 data = {
266 'more': False,
266 'more': False,
267 'results': res
267 'results': res
268 }
268 }
269 return data
269 return data
270
270
271 def _get_groups_and_repos(self, repo_group_id=None):
271 def _get_groups_and_repos(self, repo_group_id=None):
272 # repo groups groups
272 # repo groups groups
273 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
273 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
274 _perms = ['group.read', 'group.write', 'group.admin']
274 _perms = ['group.read', 'group.write', 'group.admin']
275 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
275 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
276 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
276 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
277 repo_group_list=repo_group_list_acl, admin=False)
277 repo_group_list=repo_group_list_acl, admin=False)
278
278
279 # repositories
279 # repositories
280 repo_list = Repository.get_all_repos(group_id=repo_group_id)
280 repo_list = Repository.get_all_repos(group_id=repo_group_id)
281 _perms = ['repository.read', 'repository.write', 'repository.admin']
281 _perms = ['repository.read', 'repository.write', 'repository.admin']
282 repo_list_acl = RepoList(repo_list, perm_set=_perms)
282 repo_list_acl = RepoList(repo_list, perm_set=_perms)
283 repo_data = RepoModel().get_repos_as_dict(
283 repo_data = RepoModel().get_repos_as_dict(
284 repo_list=repo_list_acl, admin=False)
284 repo_list=repo_list_acl, admin=False)
285
285
286 return repo_data, repo_group_data
286 return repo_data, repo_group_data
287
287
288 @LoginRequired()
288 @LoginRequired()
289 @view_config(
289 @view_config(
290 route_name='home', request_method='GET',
290 route_name='home', request_method='GET',
291 renderer='rhodecode:templates/index.mako')
291 renderer='rhodecode:templates/index.mako')
292 def main_page(self):
292 def main_page(self):
293 c = self.load_default_context()
293 c = self.load_default_context()
294 c.repo_group = None
294 c.repo_group = None
295
295
296 repo_data, repo_group_data = self._get_groups_and_repos()
296 repo_data, repo_group_data = self._get_groups_and_repos()
297 # json used to render the grids
297 # json used to render the grids
298 c.repos_data = json.dumps(repo_data)
298 c.repos_data = json.dumps(repo_data)
299 c.repo_groups_data = json.dumps(repo_group_data)
299 c.repo_groups_data = json.dumps(repo_group_data)
300
300
301 return self._get_template_context(c)
301 return self._get_template_context(c)
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @HasRepoGroupPermissionAnyDecorator(
304 @HasRepoGroupPermissionAnyDecorator(
305 'group.read', 'group.write', 'group.admin')
305 'group.read', 'group.write', 'group.admin')
306 @view_config(
306 @view_config(
307 route_name='repo_group_home', request_method='GET',
307 route_name='repo_group_home', request_method='GET',
308 renderer='rhodecode:templates/index_repo_group.mako')
308 renderer='rhodecode:templates/index_repo_group.mako')
309 @view_config(
309 @view_config(
310 route_name='repo_group_home_slash', request_method='GET',
310 route_name='repo_group_home_slash', request_method='GET',
311 renderer='rhodecode:templates/index_repo_group.mako')
311 renderer='rhodecode:templates/index_repo_group.mako')
312 def repo_group_main_page(self):
312 def repo_group_main_page(self):
313 c = self.load_default_context()
313 c = self.load_default_context()
314 c.repo_group = self.request.db_repo_group
314 c.repo_group = self.request.db_repo_group
315 repo_data, repo_group_data = self._get_groups_and_repos(
315 repo_data, repo_group_data = self._get_groups_and_repos(
316 c.repo_group.group_id)
316 c.repo_group.group_id)
317
317
318 # json used to render the grids
318 # json used to render the grids
319 c.repos_data = json.dumps(repo_data)
319 c.repos_data = json.dumps(repo_data)
320 c.repo_groups_data = json.dumps(repo_group_data)
320 c.repo_groups_data = json.dumps(repo_group_data)
321
321
322 return self._get_template_context(c)
322 return self._get_template_context(c)
@@ -1,4227 +1,4232 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 sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.sql.functions import coalesce, count # noqa
45 from sqlalchemy.exc import IntegrityError # noqa
45 from sqlalchemy.exc import IntegrityError # noqa
46 from sqlalchemy.dialects.mysql import LONGTEXT
46 from sqlalchemy.dialects.mysql import LONGTEXT
47 from beaker.cache import cache_region
47 from beaker.cache import cache_region
48 from zope.cachedescriptors.property import Lazy as LazyProperty
48 from zope.cachedescriptors.property import Lazy as LazyProperty
49
49
50 from pyramid.threadlocal import get_current_request
50 from pyramid.threadlocal import get_current_request
51
51
52 from rhodecode.translation import _
52 from rhodecode.translation import _
53 from rhodecode.lib.vcs import get_vcs_instance
53 from rhodecode.lib.vcs import get_vcs_instance
54 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
54 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
56 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
57 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
57 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
58 glob2re, StrictAttributeDict, cleaned_uri)
58 glob2re, StrictAttributeDict, cleaned_uri)
59 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
59 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 # beaker.session.secret if first is not set.
75 # and initialized at environment.py
75 # and initialized at environment.py
76 ENCRYPTION_KEY = None
76 ENCRYPTION_KEY = None
77
77
78 # used to sort permissions by types, '#' used here is not allowed to be in
78 # used to sort permissions by types, '#' used here is not allowed to be in
79 # usernames, and it's very early in sorted string.printable table.
79 # usernames, and it's very early in sorted string.printable table.
80 PERMISSION_TYPE_SORT = {
80 PERMISSION_TYPE_SORT = {
81 'admin': '####',
81 'admin': '####',
82 'write': '###',
82 'write': '###',
83 'read': '##',
83 'read': '##',
84 'none': '#',
84 'none': '#',
85 }
85 }
86
86
87
87
88 def display_user_sort(obj):
88 def display_user_sort(obj):
89 """
89 """
90 Sort function used to sort permissions in .permissions() function of
90 Sort function used to sort permissions in .permissions() function of
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
92 of all other resources
92 of all other resources
93 """
93 """
94
94
95 if obj.username == User.DEFAULT_USER:
95 if obj.username == User.DEFAULT_USER:
96 return '#####'
96 return '#####'
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
98 return prefix + obj.username
98 return prefix + obj.username
99
99
100
100
101 def display_user_group_sort(obj):
101 def display_user_group_sort(obj):
102 """
102 """
103 Sort function used to sort permissions in .permissions() function of
103 Sort function used to sort permissions in .permissions() function of
104 Repository, RepoGroup, UserGroup. Also it put the default user in front
104 Repository, RepoGroup, UserGroup. Also it put the default user in front
105 of all other resources
105 of all other resources
106 """
106 """
107
107
108 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
108 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
109 return prefix + obj.users_group_name
109 return prefix + obj.users_group_name
110
110
111
111
112 def _hash_key(k):
112 def _hash_key(k):
113 return md5_safe(k)
113 return md5_safe(k)
114
114
115
115
116 def in_filter_generator(qry, items, limit=500):
116 def in_filter_generator(qry, items, limit=500):
117 """
117 """
118 Splits IN() into multiple with OR
118 Splits IN() into multiple with OR
119 e.g.::
119 e.g.::
120 cnt = Repository.query().filter(
120 cnt = Repository.query().filter(
121 or_(
121 or_(
122 *in_filter_generator(Repository.repo_id, range(100000))
122 *in_filter_generator(Repository.repo_id, range(100000))
123 )).count()
123 )).count()
124 """
124 """
125 if not items:
126 # empty list will cause empty query which might cause security issues
127 # this can lead to hidden unpleasant results
128 items = [-1]
129
125 parts = []
130 parts = []
126 for chunk in xrange(0, len(items), limit):
131 for chunk in xrange(0, len(items), limit):
127 parts.append(
132 parts.append(
128 qry.in_(items[chunk: chunk + limit])
133 qry.in_(items[chunk: chunk + limit])
129 )
134 )
130
135
131 return parts
136 return parts
132
137
133
138
134 class EncryptedTextValue(TypeDecorator):
139 class EncryptedTextValue(TypeDecorator):
135 """
140 """
136 Special column for encrypted long text data, use like::
141 Special column for encrypted long text data, use like::
137
142
138 value = Column("encrypted_value", EncryptedValue(), nullable=False)
143 value = Column("encrypted_value", EncryptedValue(), nullable=False)
139
144
140 This column is intelligent so if value is in unencrypted form it return
145 This column is intelligent so if value is in unencrypted form it return
141 unencrypted form, but on save it always encrypts
146 unencrypted form, but on save it always encrypts
142 """
147 """
143 impl = Text
148 impl = Text
144
149
145 def process_bind_param(self, value, dialect):
150 def process_bind_param(self, value, dialect):
146 if not value:
151 if not value:
147 return value
152 return value
148 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
153 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
149 # protect against double encrypting if someone manually starts
154 # protect against double encrypting if someone manually starts
150 # doing
155 # doing
151 raise ValueError('value needs to be in unencrypted format, ie. '
156 raise ValueError('value needs to be in unencrypted format, ie. '
152 'not starting with enc$aes')
157 'not starting with enc$aes')
153 return 'enc$aes_hmac$%s' % AESCipher(
158 return 'enc$aes_hmac$%s' % AESCipher(
154 ENCRYPTION_KEY, hmac=True).encrypt(value)
159 ENCRYPTION_KEY, hmac=True).encrypt(value)
155
160
156 def process_result_value(self, value, dialect):
161 def process_result_value(self, value, dialect):
157 import rhodecode
162 import rhodecode
158
163
159 if not value:
164 if not value:
160 return value
165 return value
161
166
162 parts = value.split('$', 3)
167 parts = value.split('$', 3)
163 if not len(parts) == 3:
168 if not len(parts) == 3:
164 # probably not encrypted values
169 # probably not encrypted values
165 return value
170 return value
166 else:
171 else:
167 if parts[0] != 'enc':
172 if parts[0] != 'enc':
168 # parts ok but without our header ?
173 # parts ok but without our header ?
169 return value
174 return value
170 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
175 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
171 'rhodecode.encrypted_values.strict') or True)
176 'rhodecode.encrypted_values.strict') or True)
172 # at that stage we know it's our encryption
177 # at that stage we know it's our encryption
173 if parts[1] == 'aes':
178 if parts[1] == 'aes':
174 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
179 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
175 elif parts[1] == 'aes_hmac':
180 elif parts[1] == 'aes_hmac':
176 decrypted_data = AESCipher(
181 decrypted_data = AESCipher(
177 ENCRYPTION_KEY, hmac=True,
182 ENCRYPTION_KEY, hmac=True,
178 strict_verification=enc_strict_mode).decrypt(parts[2])
183 strict_verification=enc_strict_mode).decrypt(parts[2])
179 else:
184 else:
180 raise ValueError(
185 raise ValueError(
181 'Encryption type part is wrong, must be `aes` '
186 'Encryption type part is wrong, must be `aes` '
182 'or `aes_hmac`, got `%s` instead' % (parts[1]))
187 'or `aes_hmac`, got `%s` instead' % (parts[1]))
183 return decrypted_data
188 return decrypted_data
184
189
185
190
186 class BaseModel(object):
191 class BaseModel(object):
187 """
192 """
188 Base Model for all classes
193 Base Model for all classes
189 """
194 """
190
195
191 @classmethod
196 @classmethod
192 def _get_keys(cls):
197 def _get_keys(cls):
193 """return column names for this model """
198 """return column names for this model """
194 return class_mapper(cls).c.keys()
199 return class_mapper(cls).c.keys()
195
200
196 def get_dict(self):
201 def get_dict(self):
197 """
202 """
198 return dict with keys and values corresponding
203 return dict with keys and values corresponding
199 to this model data """
204 to this model data """
200
205
201 d = {}
206 d = {}
202 for k in self._get_keys():
207 for k in self._get_keys():
203 d[k] = getattr(self, k)
208 d[k] = getattr(self, k)
204
209
205 # also use __json__() if present to get additional fields
210 # also use __json__() if present to get additional fields
206 _json_attr = getattr(self, '__json__', None)
211 _json_attr = getattr(self, '__json__', None)
207 if _json_attr:
212 if _json_attr:
208 # update with attributes from __json__
213 # update with attributes from __json__
209 if callable(_json_attr):
214 if callable(_json_attr):
210 _json_attr = _json_attr()
215 _json_attr = _json_attr()
211 for k, val in _json_attr.iteritems():
216 for k, val in _json_attr.iteritems():
212 d[k] = val
217 d[k] = val
213 return d
218 return d
214
219
215 def get_appstruct(self):
220 def get_appstruct(self):
216 """return list with keys and values tuples corresponding
221 """return list with keys and values tuples corresponding
217 to this model data """
222 to this model data """
218
223
219 l = []
224 l = []
220 for k in self._get_keys():
225 for k in self._get_keys():
221 l.append((k, getattr(self, k),))
226 l.append((k, getattr(self, k),))
222 return l
227 return l
223
228
224 def populate_obj(self, populate_dict):
229 def populate_obj(self, populate_dict):
225 """populate model with data from given populate_dict"""
230 """populate model with data from given populate_dict"""
226
231
227 for k in self._get_keys():
232 for k in self._get_keys():
228 if k in populate_dict:
233 if k in populate_dict:
229 setattr(self, k, populate_dict[k])
234 setattr(self, k, populate_dict[k])
230
235
231 @classmethod
236 @classmethod
232 def query(cls):
237 def query(cls):
233 return Session().query(cls)
238 return Session().query(cls)
234
239
235 @classmethod
240 @classmethod
236 def get(cls, id_):
241 def get(cls, id_):
237 if id_:
242 if id_:
238 return cls.query().get(id_)
243 return cls.query().get(id_)
239
244
240 @classmethod
245 @classmethod
241 def get_or_404(cls, id_):
246 def get_or_404(cls, id_):
242 from pyramid.httpexceptions import HTTPNotFound
247 from pyramid.httpexceptions import HTTPNotFound
243
248
244 try:
249 try:
245 id_ = int(id_)
250 id_ = int(id_)
246 except (TypeError, ValueError):
251 except (TypeError, ValueError):
247 raise HTTPNotFound()
252 raise HTTPNotFound()
248
253
249 res = cls.query().get(id_)
254 res = cls.query().get(id_)
250 if not res:
255 if not res:
251 raise HTTPNotFound()
256 raise HTTPNotFound()
252 return res
257 return res
253
258
254 @classmethod
259 @classmethod
255 def getAll(cls):
260 def getAll(cls):
256 # deprecated and left for backward compatibility
261 # deprecated and left for backward compatibility
257 return cls.get_all()
262 return cls.get_all()
258
263
259 @classmethod
264 @classmethod
260 def get_all(cls):
265 def get_all(cls):
261 return cls.query().all()
266 return cls.query().all()
262
267
263 @classmethod
268 @classmethod
264 def delete(cls, id_):
269 def delete(cls, id_):
265 obj = cls.query().get(id_)
270 obj = cls.query().get(id_)
266 Session().delete(obj)
271 Session().delete(obj)
267
272
268 @classmethod
273 @classmethod
269 def identity_cache(cls, session, attr_name, value):
274 def identity_cache(cls, session, attr_name, value):
270 exist_in_session = []
275 exist_in_session = []
271 for (item_cls, pkey), instance in session.identity_map.items():
276 for (item_cls, pkey), instance in session.identity_map.items():
272 if cls == item_cls and getattr(instance, attr_name) == value:
277 if cls == item_cls and getattr(instance, attr_name) == value:
273 exist_in_session.append(instance)
278 exist_in_session.append(instance)
274 if exist_in_session:
279 if exist_in_session:
275 if len(exist_in_session) == 1:
280 if len(exist_in_session) == 1:
276 return exist_in_session[0]
281 return exist_in_session[0]
277 log.exception(
282 log.exception(
278 'multiple objects with attr %s and '
283 'multiple objects with attr %s and '
279 'value %s found with same name: %r',
284 'value %s found with same name: %r',
280 attr_name, value, exist_in_session)
285 attr_name, value, exist_in_session)
281
286
282 def __repr__(self):
287 def __repr__(self):
283 if hasattr(self, '__unicode__'):
288 if hasattr(self, '__unicode__'):
284 # python repr needs to return str
289 # python repr needs to return str
285 try:
290 try:
286 return safe_str(self.__unicode__())
291 return safe_str(self.__unicode__())
287 except UnicodeDecodeError:
292 except UnicodeDecodeError:
288 pass
293 pass
289 return '<DB:%s>' % (self.__class__.__name__)
294 return '<DB:%s>' % (self.__class__.__name__)
290
295
291
296
292 class RhodeCodeSetting(Base, BaseModel):
297 class RhodeCodeSetting(Base, BaseModel):
293 __tablename__ = 'rhodecode_settings'
298 __tablename__ = 'rhodecode_settings'
294 __table_args__ = (
299 __table_args__ = (
295 UniqueConstraint('app_settings_name'),
300 UniqueConstraint('app_settings_name'),
296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
301 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
302 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
298 )
303 )
299
304
300 SETTINGS_TYPES = {
305 SETTINGS_TYPES = {
301 'str': safe_str,
306 'str': safe_str,
302 'int': safe_int,
307 'int': safe_int,
303 'unicode': safe_unicode,
308 'unicode': safe_unicode,
304 'bool': str2bool,
309 'bool': str2bool,
305 'list': functools.partial(aslist, sep=',')
310 'list': functools.partial(aslist, sep=',')
306 }
311 }
307 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
312 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
308 GLOBAL_CONF_KEY = 'app_settings'
313 GLOBAL_CONF_KEY = 'app_settings'
309
314
310 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
315 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
311 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
316 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
312 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
317 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
313 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
318 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
314
319
315 def __init__(self, key='', val='', type='unicode'):
320 def __init__(self, key='', val='', type='unicode'):
316 self.app_settings_name = key
321 self.app_settings_name = key
317 self.app_settings_type = type
322 self.app_settings_type = type
318 self.app_settings_value = val
323 self.app_settings_value = val
319
324
320 @validates('_app_settings_value')
325 @validates('_app_settings_value')
321 def validate_settings_value(self, key, val):
326 def validate_settings_value(self, key, val):
322 assert type(val) == unicode
327 assert type(val) == unicode
323 return val
328 return val
324
329
325 @hybrid_property
330 @hybrid_property
326 def app_settings_value(self):
331 def app_settings_value(self):
327 v = self._app_settings_value
332 v = self._app_settings_value
328 _type = self.app_settings_type
333 _type = self.app_settings_type
329 if _type:
334 if _type:
330 _type = self.app_settings_type.split('.')[0]
335 _type = self.app_settings_type.split('.')[0]
331 # decode the encrypted value
336 # decode the encrypted value
332 if 'encrypted' in self.app_settings_type:
337 if 'encrypted' in self.app_settings_type:
333 cipher = EncryptedTextValue()
338 cipher = EncryptedTextValue()
334 v = safe_unicode(cipher.process_result_value(v, None))
339 v = safe_unicode(cipher.process_result_value(v, None))
335
340
336 converter = self.SETTINGS_TYPES.get(_type) or \
341 converter = self.SETTINGS_TYPES.get(_type) or \
337 self.SETTINGS_TYPES['unicode']
342 self.SETTINGS_TYPES['unicode']
338 return converter(v)
343 return converter(v)
339
344
340 @app_settings_value.setter
345 @app_settings_value.setter
341 def app_settings_value(self, val):
346 def app_settings_value(self, val):
342 """
347 """
343 Setter that will always make sure we use unicode in app_settings_value
348 Setter that will always make sure we use unicode in app_settings_value
344
349
345 :param val:
350 :param val:
346 """
351 """
347 val = safe_unicode(val)
352 val = safe_unicode(val)
348 # encode the encrypted value
353 # encode the encrypted value
349 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
350 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
351 val = safe_unicode(cipher.process_bind_param(val, None))
356 val = safe_unicode(cipher.process_bind_param(val, None))
352 self._app_settings_value = val
357 self._app_settings_value = val
353
358
354 @hybrid_property
359 @hybrid_property
355 def app_settings_type(self):
360 def app_settings_type(self):
356 return self._app_settings_type
361 return self._app_settings_type
357
362
358 @app_settings_type.setter
363 @app_settings_type.setter
359 def app_settings_type(self, val):
364 def app_settings_type(self, val):
360 if val.split('.')[0] not in self.SETTINGS_TYPES:
365 if val.split('.')[0] not in self.SETTINGS_TYPES:
361 raise Exception('type must be one of %s got %s'
366 raise Exception('type must be one of %s got %s'
362 % (self.SETTINGS_TYPES.keys(), val))
367 % (self.SETTINGS_TYPES.keys(), val))
363 self._app_settings_type = val
368 self._app_settings_type = val
364
369
365 def __unicode__(self):
370 def __unicode__(self):
366 return u"<%s('%s:%s[%s]')>" % (
371 return u"<%s('%s:%s[%s]')>" % (
367 self.__class__.__name__,
372 self.__class__.__name__,
368 self.app_settings_name, self.app_settings_value,
373 self.app_settings_name, self.app_settings_value,
369 self.app_settings_type
374 self.app_settings_type
370 )
375 )
371
376
372
377
373 class RhodeCodeUi(Base, BaseModel):
378 class RhodeCodeUi(Base, BaseModel):
374 __tablename__ = 'rhodecode_ui'
379 __tablename__ = 'rhodecode_ui'
375 __table_args__ = (
380 __table_args__ = (
376 UniqueConstraint('ui_key'),
381 UniqueConstraint('ui_key'),
377 {'extend_existing': True, 'mysql_engine': 'InnoDB',
382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
378 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
383 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
379 )
384 )
380
385
381 HOOK_REPO_SIZE = 'changegroup.repo_size'
386 HOOK_REPO_SIZE = 'changegroup.repo_size'
382 # HG
387 # HG
383 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
388 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
384 HOOK_PULL = 'outgoing.pull_logger'
389 HOOK_PULL = 'outgoing.pull_logger'
385 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
390 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
386 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
391 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
387 HOOK_PUSH = 'changegroup.push_logger'
392 HOOK_PUSH = 'changegroup.push_logger'
388 HOOK_PUSH_KEY = 'pushkey.key_push'
393 HOOK_PUSH_KEY = 'pushkey.key_push'
389
394
390 # TODO: johbo: Unify way how hooks are configured for git and hg,
395 # TODO: johbo: Unify way how hooks are configured for git and hg,
391 # git part is currently hardcoded.
396 # git part is currently hardcoded.
392
397
393 # SVN PATTERNS
398 # SVN PATTERNS
394 SVN_BRANCH_ID = 'vcs_svn_branch'
399 SVN_BRANCH_ID = 'vcs_svn_branch'
395 SVN_TAG_ID = 'vcs_svn_tag'
400 SVN_TAG_ID = 'vcs_svn_tag'
396
401
397 ui_id = Column(
402 ui_id = Column(
398 "ui_id", Integer(), nullable=False, unique=True, default=None,
403 "ui_id", Integer(), nullable=False, unique=True, default=None,
399 primary_key=True)
404 primary_key=True)
400 ui_section = Column(
405 ui_section = Column(
401 "ui_section", String(255), nullable=True, unique=None, default=None)
406 "ui_section", String(255), nullable=True, unique=None, default=None)
402 ui_key = Column(
407 ui_key = Column(
403 "ui_key", String(255), nullable=True, unique=None, default=None)
408 "ui_key", String(255), nullable=True, unique=None, default=None)
404 ui_value = Column(
409 ui_value = Column(
405 "ui_value", String(255), nullable=True, unique=None, default=None)
410 "ui_value", String(255), nullable=True, unique=None, default=None)
406 ui_active = Column(
411 ui_active = Column(
407 "ui_active", Boolean(), nullable=True, unique=None, default=True)
412 "ui_active", Boolean(), nullable=True, unique=None, default=True)
408
413
409 def __repr__(self):
414 def __repr__(self):
410 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
415 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
411 self.ui_key, self.ui_value)
416 self.ui_key, self.ui_value)
412
417
413
418
414 class RepoRhodeCodeSetting(Base, BaseModel):
419 class RepoRhodeCodeSetting(Base, BaseModel):
415 __tablename__ = 'repo_rhodecode_settings'
420 __tablename__ = 'repo_rhodecode_settings'
416 __table_args__ = (
421 __table_args__ = (
417 UniqueConstraint(
422 UniqueConstraint(
418 'app_settings_name', 'repository_id',
423 'app_settings_name', 'repository_id',
419 name='uq_repo_rhodecode_setting_name_repo_id'),
424 name='uq_repo_rhodecode_setting_name_repo_id'),
420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
425 {'extend_existing': True, 'mysql_engine': 'InnoDB',
421 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
426 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
422 )
427 )
423
428
424 repository_id = Column(
429 repository_id = Column(
425 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
430 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
426 nullable=False)
431 nullable=False)
427 app_settings_id = Column(
432 app_settings_id = Column(
428 "app_settings_id", Integer(), nullable=False, unique=True,
433 "app_settings_id", Integer(), nullable=False, unique=True,
429 default=None, primary_key=True)
434 default=None, primary_key=True)
430 app_settings_name = Column(
435 app_settings_name = Column(
431 "app_settings_name", String(255), nullable=True, unique=None,
436 "app_settings_name", String(255), nullable=True, unique=None,
432 default=None)
437 default=None)
433 _app_settings_value = Column(
438 _app_settings_value = Column(
434 "app_settings_value", String(4096), nullable=True, unique=None,
439 "app_settings_value", String(4096), nullable=True, unique=None,
435 default=None)
440 default=None)
436 _app_settings_type = Column(
441 _app_settings_type = Column(
437 "app_settings_type", String(255), nullable=True, unique=None,
442 "app_settings_type", String(255), nullable=True, unique=None,
438 default=None)
443 default=None)
439
444
440 repository = relationship('Repository')
445 repository = relationship('Repository')
441
446
442 def __init__(self, repository_id, key='', val='', type='unicode'):
447 def __init__(self, repository_id, key='', val='', type='unicode'):
443 self.repository_id = repository_id
448 self.repository_id = repository_id
444 self.app_settings_name = key
449 self.app_settings_name = key
445 self.app_settings_type = type
450 self.app_settings_type = type
446 self.app_settings_value = val
451 self.app_settings_value = val
447
452
448 @validates('_app_settings_value')
453 @validates('_app_settings_value')
449 def validate_settings_value(self, key, val):
454 def validate_settings_value(self, key, val):
450 assert type(val) == unicode
455 assert type(val) == unicode
451 return val
456 return val
452
457
453 @hybrid_property
458 @hybrid_property
454 def app_settings_value(self):
459 def app_settings_value(self):
455 v = self._app_settings_value
460 v = self._app_settings_value
456 type_ = self.app_settings_type
461 type_ = self.app_settings_type
457 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
462 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
458 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
463 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
459 return converter(v)
464 return converter(v)
460
465
461 @app_settings_value.setter
466 @app_settings_value.setter
462 def app_settings_value(self, val):
467 def app_settings_value(self, val):
463 """
468 """
464 Setter that will always make sure we use unicode in app_settings_value
469 Setter that will always make sure we use unicode in app_settings_value
465
470
466 :param val:
471 :param val:
467 """
472 """
468 self._app_settings_value = safe_unicode(val)
473 self._app_settings_value = safe_unicode(val)
469
474
470 @hybrid_property
475 @hybrid_property
471 def app_settings_type(self):
476 def app_settings_type(self):
472 return self._app_settings_type
477 return self._app_settings_type
473
478
474 @app_settings_type.setter
479 @app_settings_type.setter
475 def app_settings_type(self, val):
480 def app_settings_type(self, val):
476 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
481 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
477 if val not in SETTINGS_TYPES:
482 if val not in SETTINGS_TYPES:
478 raise Exception('type must be one of %s got %s'
483 raise Exception('type must be one of %s got %s'
479 % (SETTINGS_TYPES.keys(), val))
484 % (SETTINGS_TYPES.keys(), val))
480 self._app_settings_type = val
485 self._app_settings_type = val
481
486
482 def __unicode__(self):
487 def __unicode__(self):
483 return u"<%s('%s:%s:%s[%s]')>" % (
488 return u"<%s('%s:%s:%s[%s]')>" % (
484 self.__class__.__name__, self.repository.repo_name,
489 self.__class__.__name__, self.repository.repo_name,
485 self.app_settings_name, self.app_settings_value,
490 self.app_settings_name, self.app_settings_value,
486 self.app_settings_type
491 self.app_settings_type
487 )
492 )
488
493
489
494
490 class RepoRhodeCodeUi(Base, BaseModel):
495 class RepoRhodeCodeUi(Base, BaseModel):
491 __tablename__ = 'repo_rhodecode_ui'
496 __tablename__ = 'repo_rhodecode_ui'
492 __table_args__ = (
497 __table_args__ = (
493 UniqueConstraint(
498 UniqueConstraint(
494 'repository_id', 'ui_section', 'ui_key',
499 'repository_id', 'ui_section', 'ui_key',
495 name='uq_repo_rhodecode_ui_repository_id_section_key'),
500 name='uq_repo_rhodecode_ui_repository_id_section_key'),
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
498 )
503 )
499
504
500 repository_id = Column(
505 repository_id = Column(
501 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
506 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
502 nullable=False)
507 nullable=False)
503 ui_id = Column(
508 ui_id = Column(
504 "ui_id", Integer(), nullable=False, unique=True, default=None,
509 "ui_id", Integer(), nullable=False, unique=True, default=None,
505 primary_key=True)
510 primary_key=True)
506 ui_section = Column(
511 ui_section = Column(
507 "ui_section", String(255), nullable=True, unique=None, default=None)
512 "ui_section", String(255), nullable=True, unique=None, default=None)
508 ui_key = Column(
513 ui_key = Column(
509 "ui_key", String(255), nullable=True, unique=None, default=None)
514 "ui_key", String(255), nullable=True, unique=None, default=None)
510 ui_value = Column(
515 ui_value = Column(
511 "ui_value", String(255), nullable=True, unique=None, default=None)
516 "ui_value", String(255), nullable=True, unique=None, default=None)
512 ui_active = Column(
517 ui_active = Column(
513 "ui_active", Boolean(), nullable=True, unique=None, default=True)
518 "ui_active", Boolean(), nullable=True, unique=None, default=True)
514
519
515 repository = relationship('Repository')
520 repository = relationship('Repository')
516
521
517 def __repr__(self):
522 def __repr__(self):
518 return '<%s[%s:%s]%s=>%s]>' % (
523 return '<%s[%s:%s]%s=>%s]>' % (
519 self.__class__.__name__, self.repository.repo_name,
524 self.__class__.__name__, self.repository.repo_name,
520 self.ui_section, self.ui_key, self.ui_value)
525 self.ui_section, self.ui_key, self.ui_value)
521
526
522
527
523 class User(Base, BaseModel):
528 class User(Base, BaseModel):
524 __tablename__ = 'users'
529 __tablename__ = 'users'
525 __table_args__ = (
530 __table_args__ = (
526 UniqueConstraint('username'), UniqueConstraint('email'),
531 UniqueConstraint('username'), UniqueConstraint('email'),
527 Index('u_username_idx', 'username'),
532 Index('u_username_idx', 'username'),
528 Index('u_email_idx', 'email'),
533 Index('u_email_idx', 'email'),
529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
531 )
536 )
532 DEFAULT_USER = 'default'
537 DEFAULT_USER = 'default'
533 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
538 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
534 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
539 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
535
540
536 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
541 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
537 username = Column("username", String(255), nullable=True, unique=None, default=None)
542 username = Column("username", String(255), nullable=True, unique=None, default=None)
538 password = Column("password", String(255), nullable=True, unique=None, default=None)
543 password = Column("password", String(255), nullable=True, unique=None, default=None)
539 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
544 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
540 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
545 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
541 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
546 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
542 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
547 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
543 _email = Column("email", String(255), nullable=True, unique=None, default=None)
548 _email = Column("email", String(255), nullable=True, unique=None, default=None)
544 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
549 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
545 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
550 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
546
551
547 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
552 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
548 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
553 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
549 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
554 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
550 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
555 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
551 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
552 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
557 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
553
558
554 user_log = relationship('UserLog')
559 user_log = relationship('UserLog')
555 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
560 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
556
561
557 repositories = relationship('Repository')
562 repositories = relationship('Repository')
558 repository_groups = relationship('RepoGroup')
563 repository_groups = relationship('RepoGroup')
559 user_groups = relationship('UserGroup')
564 user_groups = relationship('UserGroup')
560
565
561 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
566 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
562 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
567 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
563
568
564 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
569 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
565 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
570 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
566 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
571 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
567
572
568 group_member = relationship('UserGroupMember', cascade='all')
573 group_member = relationship('UserGroupMember', cascade='all')
569
574
570 notifications = relationship('UserNotification', cascade='all')
575 notifications = relationship('UserNotification', cascade='all')
571 # notifications assigned to this user
576 # notifications assigned to this user
572 user_created_notifications = relationship('Notification', cascade='all')
577 user_created_notifications = relationship('Notification', cascade='all')
573 # comments created by this user
578 # comments created by this user
574 user_comments = relationship('ChangesetComment', cascade='all')
579 user_comments = relationship('ChangesetComment', cascade='all')
575 # user profile extra info
580 # user profile extra info
576 user_emails = relationship('UserEmailMap', cascade='all')
581 user_emails = relationship('UserEmailMap', cascade='all')
577 user_ip_map = relationship('UserIpMap', cascade='all')
582 user_ip_map = relationship('UserIpMap', cascade='all')
578 user_auth_tokens = relationship('UserApiKeys', cascade='all')
583 user_auth_tokens = relationship('UserApiKeys', cascade='all')
579 user_ssh_keys = relationship('UserSshKeys', cascade='all')
584 user_ssh_keys = relationship('UserSshKeys', cascade='all')
580
585
581 # gists
586 # gists
582 user_gists = relationship('Gist', cascade='all')
587 user_gists = relationship('Gist', cascade='all')
583 # user pull requests
588 # user pull requests
584 user_pull_requests = relationship('PullRequest', cascade='all')
589 user_pull_requests = relationship('PullRequest', cascade='all')
585 # external identities
590 # external identities
586 extenal_identities = relationship(
591 extenal_identities = relationship(
587 'ExternalIdentity',
592 'ExternalIdentity',
588 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
593 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
589 cascade='all')
594 cascade='all')
590 # review rules
595 # review rules
591 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
596 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
592
597
593 def __unicode__(self):
598 def __unicode__(self):
594 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
599 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
595 self.user_id, self.username)
600 self.user_id, self.username)
596
601
597 @hybrid_property
602 @hybrid_property
598 def email(self):
603 def email(self):
599 return self._email
604 return self._email
600
605
601 @email.setter
606 @email.setter
602 def email(self, val):
607 def email(self, val):
603 self._email = val.lower() if val else None
608 self._email = val.lower() if val else None
604
609
605 @hybrid_property
610 @hybrid_property
606 def first_name(self):
611 def first_name(self):
607 from rhodecode.lib import helpers as h
612 from rhodecode.lib import helpers as h
608 if self.name:
613 if self.name:
609 return h.escape(self.name)
614 return h.escape(self.name)
610 return self.name
615 return self.name
611
616
612 @hybrid_property
617 @hybrid_property
613 def last_name(self):
618 def last_name(self):
614 from rhodecode.lib import helpers as h
619 from rhodecode.lib import helpers as h
615 if self.lastname:
620 if self.lastname:
616 return h.escape(self.lastname)
621 return h.escape(self.lastname)
617 return self.lastname
622 return self.lastname
618
623
619 @hybrid_property
624 @hybrid_property
620 def api_key(self):
625 def api_key(self):
621 """
626 """
622 Fetch if exist an auth-token with role ALL connected to this user
627 Fetch if exist an auth-token with role ALL connected to this user
623 """
628 """
624 user_auth_token = UserApiKeys.query()\
629 user_auth_token = UserApiKeys.query()\
625 .filter(UserApiKeys.user_id == self.user_id)\
630 .filter(UserApiKeys.user_id == self.user_id)\
626 .filter(or_(UserApiKeys.expires == -1,
631 .filter(or_(UserApiKeys.expires == -1,
627 UserApiKeys.expires >= time.time()))\
632 UserApiKeys.expires >= time.time()))\
628 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
629 if user_auth_token:
634 if user_auth_token:
630 user_auth_token = user_auth_token.api_key
635 user_auth_token = user_auth_token.api_key
631
636
632 return user_auth_token
637 return user_auth_token
633
638
634 @api_key.setter
639 @api_key.setter
635 def api_key(self, val):
640 def api_key(self, val):
636 # don't allow to set API key this is deprecated for now
641 # don't allow to set API key this is deprecated for now
637 self._api_key = None
642 self._api_key = None
638
643
639 @property
644 @property
640 def reviewer_pull_requests(self):
645 def reviewer_pull_requests(self):
641 return PullRequestReviewers.query() \
646 return PullRequestReviewers.query() \
642 .options(joinedload(PullRequestReviewers.pull_request)) \
647 .options(joinedload(PullRequestReviewers.pull_request)) \
643 .filter(PullRequestReviewers.user_id == self.user_id) \
648 .filter(PullRequestReviewers.user_id == self.user_id) \
644 .all()
649 .all()
645
650
646 @property
651 @property
647 def firstname(self):
652 def firstname(self):
648 # alias for future
653 # alias for future
649 return self.name
654 return self.name
650
655
651 @property
656 @property
652 def emails(self):
657 def emails(self):
653 other = UserEmailMap.query()\
658 other = UserEmailMap.query()\
654 .filter(UserEmailMap.user == self) \
659 .filter(UserEmailMap.user == self) \
655 .order_by(UserEmailMap.email_id.asc()) \
660 .order_by(UserEmailMap.email_id.asc()) \
656 .all()
661 .all()
657 return [self.email] + [x.email for x in other]
662 return [self.email] + [x.email for x in other]
658
663
659 @property
664 @property
660 def auth_tokens(self):
665 def auth_tokens(self):
661 auth_tokens = self.get_auth_tokens()
666 auth_tokens = self.get_auth_tokens()
662 return [x.api_key for x in auth_tokens]
667 return [x.api_key for x in auth_tokens]
663
668
664 def get_auth_tokens(self):
669 def get_auth_tokens(self):
665 return UserApiKeys.query()\
670 return UserApiKeys.query()\
666 .filter(UserApiKeys.user == self)\
671 .filter(UserApiKeys.user == self)\
667 .order_by(UserApiKeys.user_api_key_id.asc())\
672 .order_by(UserApiKeys.user_api_key_id.asc())\
668 .all()
673 .all()
669
674
670 @property
675 @property
671 def feed_token(self):
676 def feed_token(self):
672 return self.get_feed_token()
677 return self.get_feed_token()
673
678
674 def get_feed_token(self):
679 def get_feed_token(self):
675 feed_tokens = UserApiKeys.query()\
680 feed_tokens = UserApiKeys.query()\
676 .filter(UserApiKeys.user == self)\
681 .filter(UserApiKeys.user == self)\
677 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
682 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
678 .all()
683 .all()
679 if feed_tokens:
684 if feed_tokens:
680 return feed_tokens[0].api_key
685 return feed_tokens[0].api_key
681 return 'NO_FEED_TOKEN_AVAILABLE'
686 return 'NO_FEED_TOKEN_AVAILABLE'
682
687
683 @classmethod
688 @classmethod
684 def get(cls, user_id, cache=False):
689 def get(cls, user_id, cache=False):
685 if not user_id:
690 if not user_id:
686 return
691 return
687
692
688 user = cls.query()
693 user = cls.query()
689 if cache:
694 if cache:
690 user = user.options(
695 user = user.options(
691 FromCache("sql_cache_short", "get_users_%s" % user_id))
696 FromCache("sql_cache_short", "get_users_%s" % user_id))
692 return user.get(user_id)
697 return user.get(user_id)
693
698
694 @classmethod
699 @classmethod
695 def extra_valid_auth_tokens(cls, user, role=None):
700 def extra_valid_auth_tokens(cls, user, role=None):
696 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
701 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
697 .filter(or_(UserApiKeys.expires == -1,
702 .filter(or_(UserApiKeys.expires == -1,
698 UserApiKeys.expires >= time.time()))
703 UserApiKeys.expires >= time.time()))
699 if role:
704 if role:
700 tokens = tokens.filter(or_(UserApiKeys.role == role,
705 tokens = tokens.filter(or_(UserApiKeys.role == role,
701 UserApiKeys.role == UserApiKeys.ROLE_ALL))
706 UserApiKeys.role == UserApiKeys.ROLE_ALL))
702 return tokens.all()
707 return tokens.all()
703
708
704 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
709 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
705 from rhodecode.lib import auth
710 from rhodecode.lib import auth
706
711
707 log.debug('Trying to authenticate user: %s via auth-token, '
712 log.debug('Trying to authenticate user: %s via auth-token, '
708 'and roles: %s', self, roles)
713 'and roles: %s', self, roles)
709
714
710 if not auth_token:
715 if not auth_token:
711 return False
716 return False
712
717
713 crypto_backend = auth.crypto_backend()
718 crypto_backend = auth.crypto_backend()
714
719
715 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
720 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
716 tokens_q = UserApiKeys.query()\
721 tokens_q = UserApiKeys.query()\
717 .filter(UserApiKeys.user_id == self.user_id)\
722 .filter(UserApiKeys.user_id == self.user_id)\
718 .filter(or_(UserApiKeys.expires == -1,
723 .filter(or_(UserApiKeys.expires == -1,
719 UserApiKeys.expires >= time.time()))
724 UserApiKeys.expires >= time.time()))
720
725
721 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
726 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
722
727
723 plain_tokens = []
728 plain_tokens = []
724 hash_tokens = []
729 hash_tokens = []
725
730
726 for token in tokens_q.all():
731 for token in tokens_q.all():
727 # verify scope first
732 # verify scope first
728 if token.repo_id:
733 if token.repo_id:
729 # token has a scope, we need to verify it
734 # token has a scope, we need to verify it
730 if scope_repo_id != token.repo_id:
735 if scope_repo_id != token.repo_id:
731 log.debug(
736 log.debug(
732 'Scope mismatch: token has a set repo scope: %s, '
737 'Scope mismatch: token has a set repo scope: %s, '
733 'and calling scope is:%s, skipping further checks',
738 'and calling scope is:%s, skipping further checks',
734 token.repo, scope_repo_id)
739 token.repo, scope_repo_id)
735 # token has a scope, and it doesn't match, skip token
740 # token has a scope, and it doesn't match, skip token
736 continue
741 continue
737
742
738 if token.api_key.startswith(crypto_backend.ENC_PREF):
743 if token.api_key.startswith(crypto_backend.ENC_PREF):
739 hash_tokens.append(token.api_key)
744 hash_tokens.append(token.api_key)
740 else:
745 else:
741 plain_tokens.append(token.api_key)
746 plain_tokens.append(token.api_key)
742
747
743 is_plain_match = auth_token in plain_tokens
748 is_plain_match = auth_token in plain_tokens
744 if is_plain_match:
749 if is_plain_match:
745 return True
750 return True
746
751
747 for hashed in hash_tokens:
752 for hashed in hash_tokens:
748 # TODO(marcink): this is expensive to calculate, but most secure
753 # TODO(marcink): this is expensive to calculate, but most secure
749 match = crypto_backend.hash_check(auth_token, hashed)
754 match = crypto_backend.hash_check(auth_token, hashed)
750 if match:
755 if match:
751 return True
756 return True
752
757
753 return False
758 return False
754
759
755 @property
760 @property
756 def ip_addresses(self):
761 def ip_addresses(self):
757 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
762 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
758 return [x.ip_addr for x in ret]
763 return [x.ip_addr for x in ret]
759
764
760 @property
765 @property
761 def username_and_name(self):
766 def username_and_name(self):
762 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
767 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
763
768
764 @property
769 @property
765 def username_or_name_or_email(self):
770 def username_or_name_or_email(self):
766 full_name = self.full_name if self.full_name is not ' ' else None
771 full_name = self.full_name if self.full_name is not ' ' else None
767 return self.username or full_name or self.email
772 return self.username or full_name or self.email
768
773
769 @property
774 @property
770 def full_name(self):
775 def full_name(self):
771 return '%s %s' % (self.first_name, self.last_name)
776 return '%s %s' % (self.first_name, self.last_name)
772
777
773 @property
778 @property
774 def full_name_or_username(self):
779 def full_name_or_username(self):
775 return ('%s %s' % (self.first_name, self.last_name)
780 return ('%s %s' % (self.first_name, self.last_name)
776 if (self.first_name and self.last_name) else self.username)
781 if (self.first_name and self.last_name) else self.username)
777
782
778 @property
783 @property
779 def full_contact(self):
784 def full_contact(self):
780 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
785 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
781
786
782 @property
787 @property
783 def short_contact(self):
788 def short_contact(self):
784 return '%s %s' % (self.first_name, self.last_name)
789 return '%s %s' % (self.first_name, self.last_name)
785
790
786 @property
791 @property
787 def is_admin(self):
792 def is_admin(self):
788 return self.admin
793 return self.admin
789
794
790 def AuthUser(self, **kwargs):
795 def AuthUser(self, **kwargs):
791 """
796 """
792 Returns instance of AuthUser for this user
797 Returns instance of AuthUser for this user
793 """
798 """
794 from rhodecode.lib.auth import AuthUser
799 from rhodecode.lib.auth import AuthUser
795 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
800 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
796
801
797 @hybrid_property
802 @hybrid_property
798 def user_data(self):
803 def user_data(self):
799 if not self._user_data:
804 if not self._user_data:
800 return {}
805 return {}
801
806
802 try:
807 try:
803 return json.loads(self._user_data)
808 return json.loads(self._user_data)
804 except TypeError:
809 except TypeError:
805 return {}
810 return {}
806
811
807 @user_data.setter
812 @user_data.setter
808 def user_data(self, val):
813 def user_data(self, val):
809 if not isinstance(val, dict):
814 if not isinstance(val, dict):
810 raise Exception('user_data must be dict, got %s' % type(val))
815 raise Exception('user_data must be dict, got %s' % type(val))
811 try:
816 try:
812 self._user_data = json.dumps(val)
817 self._user_data = json.dumps(val)
813 except Exception:
818 except Exception:
814 log.error(traceback.format_exc())
819 log.error(traceback.format_exc())
815
820
816 @classmethod
821 @classmethod
817 def get_by_username(cls, username, case_insensitive=False,
822 def get_by_username(cls, username, case_insensitive=False,
818 cache=False, identity_cache=False):
823 cache=False, identity_cache=False):
819 session = Session()
824 session = Session()
820
825
821 if case_insensitive:
826 if case_insensitive:
822 q = cls.query().filter(
827 q = cls.query().filter(
823 func.lower(cls.username) == func.lower(username))
828 func.lower(cls.username) == func.lower(username))
824 else:
829 else:
825 q = cls.query().filter(cls.username == username)
830 q = cls.query().filter(cls.username == username)
826
831
827 if cache:
832 if cache:
828 if identity_cache:
833 if identity_cache:
829 val = cls.identity_cache(session, 'username', username)
834 val = cls.identity_cache(session, 'username', username)
830 if val:
835 if val:
831 return val
836 return val
832 else:
837 else:
833 cache_key = "get_user_by_name_%s" % _hash_key(username)
838 cache_key = "get_user_by_name_%s" % _hash_key(username)
834 q = q.options(
839 q = q.options(
835 FromCache("sql_cache_short", cache_key))
840 FromCache("sql_cache_short", cache_key))
836
841
837 return q.scalar()
842 return q.scalar()
838
843
839 @classmethod
844 @classmethod
840 def get_by_auth_token(cls, auth_token, cache=False):
845 def get_by_auth_token(cls, auth_token, cache=False):
841 q = UserApiKeys.query()\
846 q = UserApiKeys.query()\
842 .filter(UserApiKeys.api_key == auth_token)\
847 .filter(UserApiKeys.api_key == auth_token)\
843 .filter(or_(UserApiKeys.expires == -1,
848 .filter(or_(UserApiKeys.expires == -1,
844 UserApiKeys.expires >= time.time()))
849 UserApiKeys.expires >= time.time()))
845 if cache:
850 if cache:
846 q = q.options(
851 q = q.options(
847 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
852 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
848
853
849 match = q.first()
854 match = q.first()
850 if match:
855 if match:
851 return match.user
856 return match.user
852
857
853 @classmethod
858 @classmethod
854 def get_by_email(cls, email, case_insensitive=False, cache=False):
859 def get_by_email(cls, email, case_insensitive=False, cache=False):
855
860
856 if case_insensitive:
861 if case_insensitive:
857 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
862 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
858
863
859 else:
864 else:
860 q = cls.query().filter(cls.email == email)
865 q = cls.query().filter(cls.email == email)
861
866
862 email_key = _hash_key(email)
867 email_key = _hash_key(email)
863 if cache:
868 if cache:
864 q = q.options(
869 q = q.options(
865 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
870 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
866
871
867 ret = q.scalar()
872 ret = q.scalar()
868 if ret is None:
873 if ret is None:
869 q = UserEmailMap.query()
874 q = UserEmailMap.query()
870 # try fetching in alternate email map
875 # try fetching in alternate email map
871 if case_insensitive:
876 if case_insensitive:
872 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
877 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
873 else:
878 else:
874 q = q.filter(UserEmailMap.email == email)
879 q = q.filter(UserEmailMap.email == email)
875 q = q.options(joinedload(UserEmailMap.user))
880 q = q.options(joinedload(UserEmailMap.user))
876 if cache:
881 if cache:
877 q = q.options(
882 q = q.options(
878 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
883 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
879 ret = getattr(q.scalar(), 'user', None)
884 ret = getattr(q.scalar(), 'user', None)
880
885
881 return ret
886 return ret
882
887
883 @classmethod
888 @classmethod
884 def get_from_cs_author(cls, author):
889 def get_from_cs_author(cls, author):
885 """
890 """
886 Tries to get User objects out of commit author string
891 Tries to get User objects out of commit author string
887
892
888 :param author:
893 :param author:
889 """
894 """
890 from rhodecode.lib.helpers import email, author_name
895 from rhodecode.lib.helpers import email, author_name
891 # Valid email in the attribute passed, see if they're in the system
896 # Valid email in the attribute passed, see if they're in the system
892 _email = email(author)
897 _email = email(author)
893 if _email:
898 if _email:
894 user = cls.get_by_email(_email, case_insensitive=True)
899 user = cls.get_by_email(_email, case_insensitive=True)
895 if user:
900 if user:
896 return user
901 return user
897 # Maybe we can match by username?
902 # Maybe we can match by username?
898 _author = author_name(author)
903 _author = author_name(author)
899 user = cls.get_by_username(_author, case_insensitive=True)
904 user = cls.get_by_username(_author, case_insensitive=True)
900 if user:
905 if user:
901 return user
906 return user
902
907
903 def update_userdata(self, **kwargs):
908 def update_userdata(self, **kwargs):
904 usr = self
909 usr = self
905 old = usr.user_data
910 old = usr.user_data
906 old.update(**kwargs)
911 old.update(**kwargs)
907 usr.user_data = old
912 usr.user_data = old
908 Session().add(usr)
913 Session().add(usr)
909 log.debug('updated userdata with ', kwargs)
914 log.debug('updated userdata with ', kwargs)
910
915
911 def update_lastlogin(self):
916 def update_lastlogin(self):
912 """Update user lastlogin"""
917 """Update user lastlogin"""
913 self.last_login = datetime.datetime.now()
918 self.last_login = datetime.datetime.now()
914 Session().add(self)
919 Session().add(self)
915 log.debug('updated user %s lastlogin', self.username)
920 log.debug('updated user %s lastlogin', self.username)
916
921
917 def update_lastactivity(self):
922 def update_lastactivity(self):
918 """Update user lastactivity"""
923 """Update user lastactivity"""
919 self.last_activity = datetime.datetime.now()
924 self.last_activity = datetime.datetime.now()
920 Session().add(self)
925 Session().add(self)
921 log.debug('updated user `%s` last activity', self.username)
926 log.debug('updated user `%s` last activity', self.username)
922
927
923 def update_password(self, new_password):
928 def update_password(self, new_password):
924 from rhodecode.lib.auth import get_crypt_password
929 from rhodecode.lib.auth import get_crypt_password
925
930
926 self.password = get_crypt_password(new_password)
931 self.password = get_crypt_password(new_password)
927 Session().add(self)
932 Session().add(self)
928
933
929 @classmethod
934 @classmethod
930 def get_first_super_admin(cls):
935 def get_first_super_admin(cls):
931 user = User.query().filter(User.admin == true()).first()
936 user = User.query().filter(User.admin == true()).first()
932 if user is None:
937 if user is None:
933 raise Exception('FATAL: Missing administrative account!')
938 raise Exception('FATAL: Missing administrative account!')
934 return user
939 return user
935
940
936 @classmethod
941 @classmethod
937 def get_all_super_admins(cls):
942 def get_all_super_admins(cls):
938 """
943 """
939 Returns all admin accounts sorted by username
944 Returns all admin accounts sorted by username
940 """
945 """
941 return User.query().filter(User.admin == true())\
946 return User.query().filter(User.admin == true())\
942 .order_by(User.username.asc()).all()
947 .order_by(User.username.asc()).all()
943
948
944 @classmethod
949 @classmethod
945 def get_default_user(cls, cache=False, refresh=False):
950 def get_default_user(cls, cache=False, refresh=False):
946 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
951 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
947 if user is None:
952 if user is None:
948 raise Exception('FATAL: Missing default account!')
953 raise Exception('FATAL: Missing default account!')
949 if refresh:
954 if refresh:
950 # The default user might be based on outdated state which
955 # The default user might be based on outdated state which
951 # has been loaded from the cache.
956 # has been loaded from the cache.
952 # A call to refresh() ensures that the
957 # A call to refresh() ensures that the
953 # latest state from the database is used.
958 # latest state from the database is used.
954 Session().refresh(user)
959 Session().refresh(user)
955 return user
960 return user
956
961
957 def _get_default_perms(self, user, suffix=''):
962 def _get_default_perms(self, user, suffix=''):
958 from rhodecode.model.permission import PermissionModel
963 from rhodecode.model.permission import PermissionModel
959 return PermissionModel().get_default_perms(user.user_perms, suffix)
964 return PermissionModel().get_default_perms(user.user_perms, suffix)
960
965
961 def get_default_perms(self, suffix=''):
966 def get_default_perms(self, suffix=''):
962 return self._get_default_perms(self, suffix)
967 return self._get_default_perms(self, suffix)
963
968
964 def get_api_data(self, include_secrets=False, details='full'):
969 def get_api_data(self, include_secrets=False, details='full'):
965 """
970 """
966 Common function for generating user related data for API
971 Common function for generating user related data for API
967
972
968 :param include_secrets: By default secrets in the API data will be replaced
973 :param include_secrets: By default secrets in the API data will be replaced
969 by a placeholder value to prevent exposing this data by accident. In case
974 by a placeholder value to prevent exposing this data by accident. In case
970 this data shall be exposed, set this flag to ``True``.
975 this data shall be exposed, set this flag to ``True``.
971
976
972 :param details: details can be 'basic|full' basic gives only a subset of
977 :param details: details can be 'basic|full' basic gives only a subset of
973 the available user information that includes user_id, name and emails.
978 the available user information that includes user_id, name and emails.
974 """
979 """
975 user = self
980 user = self
976 user_data = self.user_data
981 user_data = self.user_data
977 data = {
982 data = {
978 'user_id': user.user_id,
983 'user_id': user.user_id,
979 'username': user.username,
984 'username': user.username,
980 'firstname': user.name,
985 'firstname': user.name,
981 'lastname': user.lastname,
986 'lastname': user.lastname,
982 'email': user.email,
987 'email': user.email,
983 'emails': user.emails,
988 'emails': user.emails,
984 }
989 }
985 if details == 'basic':
990 if details == 'basic':
986 return data
991 return data
987
992
988 auth_token_length = 40
993 auth_token_length = 40
989 auth_token_replacement = '*' * auth_token_length
994 auth_token_replacement = '*' * auth_token_length
990
995
991 extras = {
996 extras = {
992 'auth_tokens': [auth_token_replacement],
997 'auth_tokens': [auth_token_replacement],
993 'active': user.active,
998 'active': user.active,
994 'admin': user.admin,
999 'admin': user.admin,
995 'extern_type': user.extern_type,
1000 'extern_type': user.extern_type,
996 'extern_name': user.extern_name,
1001 'extern_name': user.extern_name,
997 'last_login': user.last_login,
1002 'last_login': user.last_login,
998 'last_activity': user.last_activity,
1003 'last_activity': user.last_activity,
999 'ip_addresses': user.ip_addresses,
1004 'ip_addresses': user.ip_addresses,
1000 'language': user_data.get('language')
1005 'language': user_data.get('language')
1001 }
1006 }
1002 data.update(extras)
1007 data.update(extras)
1003
1008
1004 if include_secrets:
1009 if include_secrets:
1005 data['auth_tokens'] = user.auth_tokens
1010 data['auth_tokens'] = user.auth_tokens
1006 return data
1011 return data
1007
1012
1008 def __json__(self):
1013 def __json__(self):
1009 data = {
1014 data = {
1010 'full_name': self.full_name,
1015 'full_name': self.full_name,
1011 'full_name_or_username': self.full_name_or_username,
1016 'full_name_or_username': self.full_name_or_username,
1012 'short_contact': self.short_contact,
1017 'short_contact': self.short_contact,
1013 'full_contact': self.full_contact,
1018 'full_contact': self.full_contact,
1014 }
1019 }
1015 data.update(self.get_api_data())
1020 data.update(self.get_api_data())
1016 return data
1021 return data
1017
1022
1018
1023
1019 class UserApiKeys(Base, BaseModel):
1024 class UserApiKeys(Base, BaseModel):
1020 __tablename__ = 'user_api_keys'
1025 __tablename__ = 'user_api_keys'
1021 __table_args__ = (
1026 __table_args__ = (
1022 Index('uak_api_key_idx', 'api_key', unique=True),
1027 Index('uak_api_key_idx', 'api_key', unique=True),
1023 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1028 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1029 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1030 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1026 )
1031 )
1027 __mapper_args__ = {}
1032 __mapper_args__ = {}
1028
1033
1029 # ApiKey role
1034 # ApiKey role
1030 ROLE_ALL = 'token_role_all'
1035 ROLE_ALL = 'token_role_all'
1031 ROLE_HTTP = 'token_role_http'
1036 ROLE_HTTP = 'token_role_http'
1032 ROLE_VCS = 'token_role_vcs'
1037 ROLE_VCS = 'token_role_vcs'
1033 ROLE_API = 'token_role_api'
1038 ROLE_API = 'token_role_api'
1034 ROLE_FEED = 'token_role_feed'
1039 ROLE_FEED = 'token_role_feed'
1035 ROLE_PASSWORD_RESET = 'token_password_reset'
1040 ROLE_PASSWORD_RESET = 'token_password_reset'
1036
1041
1037 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1042 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1038
1043
1039 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1041 api_key = Column("api_key", String(255), nullable=False, unique=True)
1046 api_key = Column("api_key", String(255), nullable=False, unique=True)
1042 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1047 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1043 expires = Column('expires', Float(53), nullable=False)
1048 expires = Column('expires', Float(53), nullable=False)
1044 role = Column('role', String(255), nullable=True)
1049 role = Column('role', String(255), nullable=True)
1045 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1050 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1046
1051
1047 # scope columns
1052 # scope columns
1048 repo_id = Column(
1053 repo_id = Column(
1049 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1054 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1050 nullable=True, unique=None, default=None)
1055 nullable=True, unique=None, default=None)
1051 repo = relationship('Repository', lazy='joined')
1056 repo = relationship('Repository', lazy='joined')
1052
1057
1053 repo_group_id = Column(
1058 repo_group_id = Column(
1054 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1059 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1055 nullable=True, unique=None, default=None)
1060 nullable=True, unique=None, default=None)
1056 repo_group = relationship('RepoGroup', lazy='joined')
1061 repo_group = relationship('RepoGroup', lazy='joined')
1057
1062
1058 user = relationship('User', lazy='joined')
1063 user = relationship('User', lazy='joined')
1059
1064
1060 def __unicode__(self):
1065 def __unicode__(self):
1061 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1066 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1062
1067
1063 def __json__(self):
1068 def __json__(self):
1064 data = {
1069 data = {
1065 'auth_token': self.api_key,
1070 'auth_token': self.api_key,
1066 'role': self.role,
1071 'role': self.role,
1067 'scope': self.scope_humanized,
1072 'scope': self.scope_humanized,
1068 'expired': self.expired
1073 'expired': self.expired
1069 }
1074 }
1070 return data
1075 return data
1071
1076
1072 def get_api_data(self, include_secrets=False):
1077 def get_api_data(self, include_secrets=False):
1073 data = self.__json__()
1078 data = self.__json__()
1074 if include_secrets:
1079 if include_secrets:
1075 return data
1080 return data
1076 else:
1081 else:
1077 data['auth_token'] = self.token_obfuscated
1082 data['auth_token'] = self.token_obfuscated
1078 return data
1083 return data
1079
1084
1080 @hybrid_property
1085 @hybrid_property
1081 def description_safe(self):
1086 def description_safe(self):
1082 from rhodecode.lib import helpers as h
1087 from rhodecode.lib import helpers as h
1083 return h.escape(self.description)
1088 return h.escape(self.description)
1084
1089
1085 @property
1090 @property
1086 def expired(self):
1091 def expired(self):
1087 if self.expires == -1:
1092 if self.expires == -1:
1088 return False
1093 return False
1089 return time.time() > self.expires
1094 return time.time() > self.expires
1090
1095
1091 @classmethod
1096 @classmethod
1092 def _get_role_name(cls, role):
1097 def _get_role_name(cls, role):
1093 return {
1098 return {
1094 cls.ROLE_ALL: _('all'),
1099 cls.ROLE_ALL: _('all'),
1095 cls.ROLE_HTTP: _('http/web interface'),
1100 cls.ROLE_HTTP: _('http/web interface'),
1096 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1101 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1097 cls.ROLE_API: _('api calls'),
1102 cls.ROLE_API: _('api calls'),
1098 cls.ROLE_FEED: _('feed access'),
1103 cls.ROLE_FEED: _('feed access'),
1099 }.get(role, role)
1104 }.get(role, role)
1100
1105
1101 @property
1106 @property
1102 def role_humanized(self):
1107 def role_humanized(self):
1103 return self._get_role_name(self.role)
1108 return self._get_role_name(self.role)
1104
1109
1105 def _get_scope(self):
1110 def _get_scope(self):
1106 if self.repo:
1111 if self.repo:
1107 return repr(self.repo)
1112 return repr(self.repo)
1108 if self.repo_group:
1113 if self.repo_group:
1109 return repr(self.repo_group) + ' (recursive)'
1114 return repr(self.repo_group) + ' (recursive)'
1110 return 'global'
1115 return 'global'
1111
1116
1112 @property
1117 @property
1113 def scope_humanized(self):
1118 def scope_humanized(self):
1114 return self._get_scope()
1119 return self._get_scope()
1115
1120
1116 @property
1121 @property
1117 def token_obfuscated(self):
1122 def token_obfuscated(self):
1118 if self.api_key:
1123 if self.api_key:
1119 return self.api_key[:4] + "****"
1124 return self.api_key[:4] + "****"
1120
1125
1121
1126
1122 class UserEmailMap(Base, BaseModel):
1127 class UserEmailMap(Base, BaseModel):
1123 __tablename__ = 'user_email_map'
1128 __tablename__ = 'user_email_map'
1124 __table_args__ = (
1129 __table_args__ = (
1125 Index('uem_email_idx', 'email'),
1130 Index('uem_email_idx', 'email'),
1126 UniqueConstraint('email'),
1131 UniqueConstraint('email'),
1127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1128 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1129 )
1134 )
1130 __mapper_args__ = {}
1135 __mapper_args__ = {}
1131
1136
1132 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1137 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1133 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1134 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1139 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1135 user = relationship('User', lazy='joined')
1140 user = relationship('User', lazy='joined')
1136
1141
1137 @validates('_email')
1142 @validates('_email')
1138 def validate_email(self, key, email):
1143 def validate_email(self, key, email):
1139 # check if this email is not main one
1144 # check if this email is not main one
1140 main_email = Session().query(User).filter(User.email == email).scalar()
1145 main_email = Session().query(User).filter(User.email == email).scalar()
1141 if main_email is not None:
1146 if main_email is not None:
1142 raise AttributeError('email %s is present is user table' % email)
1147 raise AttributeError('email %s is present is user table' % email)
1143 return email
1148 return email
1144
1149
1145 @hybrid_property
1150 @hybrid_property
1146 def email(self):
1151 def email(self):
1147 return self._email
1152 return self._email
1148
1153
1149 @email.setter
1154 @email.setter
1150 def email(self, val):
1155 def email(self, val):
1151 self._email = val.lower() if val else None
1156 self._email = val.lower() if val else None
1152
1157
1153
1158
1154 class UserIpMap(Base, BaseModel):
1159 class UserIpMap(Base, BaseModel):
1155 __tablename__ = 'user_ip_map'
1160 __tablename__ = 'user_ip_map'
1156 __table_args__ = (
1161 __table_args__ = (
1157 UniqueConstraint('user_id', 'ip_addr'),
1162 UniqueConstraint('user_id', 'ip_addr'),
1158 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1159 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1160 )
1165 )
1161 __mapper_args__ = {}
1166 __mapper_args__ = {}
1162
1167
1163 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1168 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1164 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1169 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1165 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1170 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1166 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1171 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1167 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1172 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1168 user = relationship('User', lazy='joined')
1173 user = relationship('User', lazy='joined')
1169
1174
1170 @hybrid_property
1175 @hybrid_property
1171 def description_safe(self):
1176 def description_safe(self):
1172 from rhodecode.lib import helpers as h
1177 from rhodecode.lib import helpers as h
1173 return h.escape(self.description)
1178 return h.escape(self.description)
1174
1179
1175 @classmethod
1180 @classmethod
1176 def _get_ip_range(cls, ip_addr):
1181 def _get_ip_range(cls, ip_addr):
1177 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1182 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1178 return [str(net.network_address), str(net.broadcast_address)]
1183 return [str(net.network_address), str(net.broadcast_address)]
1179
1184
1180 def __json__(self):
1185 def __json__(self):
1181 return {
1186 return {
1182 'ip_addr': self.ip_addr,
1187 'ip_addr': self.ip_addr,
1183 'ip_range': self._get_ip_range(self.ip_addr),
1188 'ip_range': self._get_ip_range(self.ip_addr),
1184 }
1189 }
1185
1190
1186 def __unicode__(self):
1191 def __unicode__(self):
1187 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1192 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1188 self.user_id, self.ip_addr)
1193 self.user_id, self.ip_addr)
1189
1194
1190
1195
1191 class UserSshKeys(Base, BaseModel):
1196 class UserSshKeys(Base, BaseModel):
1192 __tablename__ = 'user_ssh_keys'
1197 __tablename__ = 'user_ssh_keys'
1193 __table_args__ = (
1198 __table_args__ = (
1194 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1199 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1195
1200
1196 UniqueConstraint('ssh_key_fingerprint'),
1201 UniqueConstraint('ssh_key_fingerprint'),
1197
1202
1198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1199 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1200 )
1205 )
1201 __mapper_args__ = {}
1206 __mapper_args__ = {}
1202
1207
1203 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1208 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1204 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1209 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1205 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1210 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1206
1211
1207 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1212 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1208
1213
1209 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1214 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1210 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1215 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1211 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1216 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1212
1217
1213 user = relationship('User', lazy='joined')
1218 user = relationship('User', lazy='joined')
1214
1219
1215 def __json__(self):
1220 def __json__(self):
1216 data = {
1221 data = {
1217 'ssh_fingerprint': self.ssh_key_fingerprint,
1222 'ssh_fingerprint': self.ssh_key_fingerprint,
1218 'description': self.description,
1223 'description': self.description,
1219 'created_on': self.created_on
1224 'created_on': self.created_on
1220 }
1225 }
1221 return data
1226 return data
1222
1227
1223 def get_api_data(self):
1228 def get_api_data(self):
1224 data = self.__json__()
1229 data = self.__json__()
1225 return data
1230 return data
1226
1231
1227
1232
1228 class UserLog(Base, BaseModel):
1233 class UserLog(Base, BaseModel):
1229 __tablename__ = 'user_logs'
1234 __tablename__ = 'user_logs'
1230 __table_args__ = (
1235 __table_args__ = (
1231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1232 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1237 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1233 )
1238 )
1234 VERSION_1 = 'v1'
1239 VERSION_1 = 'v1'
1235 VERSION_2 = 'v2'
1240 VERSION_2 = 'v2'
1236 VERSIONS = [VERSION_1, VERSION_2]
1241 VERSIONS = [VERSION_1, VERSION_2]
1237
1242
1238 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1243 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1240 username = Column("username", String(255), nullable=True, unique=None, default=None)
1245 username = Column("username", String(255), nullable=True, unique=None, default=None)
1241 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1242 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1247 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1243 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1248 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1244 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1249 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1245 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1250 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1246
1251
1247 version = Column("version", String(255), nullable=True, default=VERSION_1)
1252 version = Column("version", String(255), nullable=True, default=VERSION_1)
1248 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1253 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1249 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1254 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1250
1255
1251 def __unicode__(self):
1256 def __unicode__(self):
1252 return u"<%s('id:%s:%s')>" % (
1257 return u"<%s('id:%s:%s')>" % (
1253 self.__class__.__name__, self.repository_name, self.action)
1258 self.__class__.__name__, self.repository_name, self.action)
1254
1259
1255 def __json__(self):
1260 def __json__(self):
1256 return {
1261 return {
1257 'user_id': self.user_id,
1262 'user_id': self.user_id,
1258 'username': self.username,
1263 'username': self.username,
1259 'repository_id': self.repository_id,
1264 'repository_id': self.repository_id,
1260 'repository_name': self.repository_name,
1265 'repository_name': self.repository_name,
1261 'user_ip': self.user_ip,
1266 'user_ip': self.user_ip,
1262 'action_date': self.action_date,
1267 'action_date': self.action_date,
1263 'action': self.action,
1268 'action': self.action,
1264 }
1269 }
1265
1270
1266 @hybrid_property
1271 @hybrid_property
1267 def entry_id(self):
1272 def entry_id(self):
1268 return self.user_log_id
1273 return self.user_log_id
1269
1274
1270 @property
1275 @property
1271 def action_as_day(self):
1276 def action_as_day(self):
1272 return datetime.date(*self.action_date.timetuple()[:3])
1277 return datetime.date(*self.action_date.timetuple()[:3])
1273
1278
1274 user = relationship('User')
1279 user = relationship('User')
1275 repository = relationship('Repository', cascade='')
1280 repository = relationship('Repository', cascade='')
1276
1281
1277
1282
1278 class UserGroup(Base, BaseModel):
1283 class UserGroup(Base, BaseModel):
1279 __tablename__ = 'users_groups'
1284 __tablename__ = 'users_groups'
1280 __table_args__ = (
1285 __table_args__ = (
1281 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1282 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1283 )
1288 )
1284
1289
1285 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1291 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1287 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1292 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1288 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1293 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1289 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1294 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1295 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1296 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1292 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1297 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1293
1298
1294 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1299 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1295 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1300 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1296 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1301 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1297 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1302 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1298 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1303 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1299 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1304 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1300
1305
1301 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1306 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1302 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1307 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1303
1308
1304 @classmethod
1309 @classmethod
1305 def _load_group_data(cls, column):
1310 def _load_group_data(cls, column):
1306 if not column:
1311 if not column:
1307 return {}
1312 return {}
1308
1313
1309 try:
1314 try:
1310 return json.loads(column) or {}
1315 return json.loads(column) or {}
1311 except TypeError:
1316 except TypeError:
1312 return {}
1317 return {}
1313
1318
1314 @hybrid_property
1319 @hybrid_property
1315 def description_safe(self):
1320 def description_safe(self):
1316 from rhodecode.lib import helpers as h
1321 from rhodecode.lib import helpers as h
1317 return h.escape(self.description)
1322 return h.escape(self.description)
1318
1323
1319 @hybrid_property
1324 @hybrid_property
1320 def group_data(self):
1325 def group_data(self):
1321 return self._load_group_data(self._group_data)
1326 return self._load_group_data(self._group_data)
1322
1327
1323 @group_data.expression
1328 @group_data.expression
1324 def group_data(self, **kwargs):
1329 def group_data(self, **kwargs):
1325 return self._group_data
1330 return self._group_data
1326
1331
1327 @group_data.setter
1332 @group_data.setter
1328 def group_data(self, val):
1333 def group_data(self, val):
1329 try:
1334 try:
1330 self._group_data = json.dumps(val)
1335 self._group_data = json.dumps(val)
1331 except Exception:
1336 except Exception:
1332 log.error(traceback.format_exc())
1337 log.error(traceback.format_exc())
1333
1338
1334 def __unicode__(self):
1339 def __unicode__(self):
1335 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1340 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1336 self.users_group_id,
1341 self.users_group_id,
1337 self.users_group_name)
1342 self.users_group_name)
1338
1343
1339 @classmethod
1344 @classmethod
1340 def get_by_group_name(cls, group_name, cache=False,
1345 def get_by_group_name(cls, group_name, cache=False,
1341 case_insensitive=False):
1346 case_insensitive=False):
1342 if case_insensitive:
1347 if case_insensitive:
1343 q = cls.query().filter(func.lower(cls.users_group_name) ==
1348 q = cls.query().filter(func.lower(cls.users_group_name) ==
1344 func.lower(group_name))
1349 func.lower(group_name))
1345
1350
1346 else:
1351 else:
1347 q = cls.query().filter(cls.users_group_name == group_name)
1352 q = cls.query().filter(cls.users_group_name == group_name)
1348 if cache:
1353 if cache:
1349 q = q.options(
1354 q = q.options(
1350 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1355 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1351 return q.scalar()
1356 return q.scalar()
1352
1357
1353 @classmethod
1358 @classmethod
1354 def get(cls, user_group_id, cache=False):
1359 def get(cls, user_group_id, cache=False):
1355 if not user_group_id:
1360 if not user_group_id:
1356 return
1361 return
1357
1362
1358 user_group = cls.query()
1363 user_group = cls.query()
1359 if cache:
1364 if cache:
1360 user_group = user_group.options(
1365 user_group = user_group.options(
1361 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1366 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1362 return user_group.get(user_group_id)
1367 return user_group.get(user_group_id)
1363
1368
1364 def permissions(self, with_admins=True, with_owner=True):
1369 def permissions(self, with_admins=True, with_owner=True):
1365 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1370 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1366 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1371 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1367 joinedload(UserUserGroupToPerm.user),
1372 joinedload(UserUserGroupToPerm.user),
1368 joinedload(UserUserGroupToPerm.permission),)
1373 joinedload(UserUserGroupToPerm.permission),)
1369
1374
1370 # get owners and admins and permissions. We do a trick of re-writing
1375 # get owners and admins and permissions. We do a trick of re-writing
1371 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1376 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1372 # has a global reference and changing one object propagates to all
1377 # has a global reference and changing one object propagates to all
1373 # others. This means if admin is also an owner admin_row that change
1378 # others. This means if admin is also an owner admin_row that change
1374 # would propagate to both objects
1379 # would propagate to both objects
1375 perm_rows = []
1380 perm_rows = []
1376 for _usr in q.all():
1381 for _usr in q.all():
1377 usr = AttributeDict(_usr.user.get_dict())
1382 usr = AttributeDict(_usr.user.get_dict())
1378 usr.permission = _usr.permission.permission_name
1383 usr.permission = _usr.permission.permission_name
1379 perm_rows.append(usr)
1384 perm_rows.append(usr)
1380
1385
1381 # filter the perm rows by 'default' first and then sort them by
1386 # filter the perm rows by 'default' first and then sort them by
1382 # admin,write,read,none permissions sorted again alphabetically in
1387 # admin,write,read,none permissions sorted again alphabetically in
1383 # each group
1388 # each group
1384 perm_rows = sorted(perm_rows, key=display_user_sort)
1389 perm_rows = sorted(perm_rows, key=display_user_sort)
1385
1390
1386 _admin_perm = 'usergroup.admin'
1391 _admin_perm = 'usergroup.admin'
1387 owner_row = []
1392 owner_row = []
1388 if with_owner:
1393 if with_owner:
1389 usr = AttributeDict(self.user.get_dict())
1394 usr = AttributeDict(self.user.get_dict())
1390 usr.owner_row = True
1395 usr.owner_row = True
1391 usr.permission = _admin_perm
1396 usr.permission = _admin_perm
1392 owner_row.append(usr)
1397 owner_row.append(usr)
1393
1398
1394 super_admin_rows = []
1399 super_admin_rows = []
1395 if with_admins:
1400 if with_admins:
1396 for usr in User.get_all_super_admins():
1401 for usr in User.get_all_super_admins():
1397 # if this admin is also owner, don't double the record
1402 # if this admin is also owner, don't double the record
1398 if usr.user_id == owner_row[0].user_id:
1403 if usr.user_id == owner_row[0].user_id:
1399 owner_row[0].admin_row = True
1404 owner_row[0].admin_row = True
1400 else:
1405 else:
1401 usr = AttributeDict(usr.get_dict())
1406 usr = AttributeDict(usr.get_dict())
1402 usr.admin_row = True
1407 usr.admin_row = True
1403 usr.permission = _admin_perm
1408 usr.permission = _admin_perm
1404 super_admin_rows.append(usr)
1409 super_admin_rows.append(usr)
1405
1410
1406 return super_admin_rows + owner_row + perm_rows
1411 return super_admin_rows + owner_row + perm_rows
1407
1412
1408 def permission_user_groups(self):
1413 def permission_user_groups(self):
1409 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1414 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1410 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1415 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1411 joinedload(UserGroupUserGroupToPerm.target_user_group),
1416 joinedload(UserGroupUserGroupToPerm.target_user_group),
1412 joinedload(UserGroupUserGroupToPerm.permission),)
1417 joinedload(UserGroupUserGroupToPerm.permission),)
1413
1418
1414 perm_rows = []
1419 perm_rows = []
1415 for _user_group in q.all():
1420 for _user_group in q.all():
1416 usr = AttributeDict(_user_group.user_group.get_dict())
1421 usr = AttributeDict(_user_group.user_group.get_dict())
1417 usr.permission = _user_group.permission.permission_name
1422 usr.permission = _user_group.permission.permission_name
1418 perm_rows.append(usr)
1423 perm_rows.append(usr)
1419
1424
1420 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1425 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1421 return perm_rows
1426 return perm_rows
1422
1427
1423 def _get_default_perms(self, user_group, suffix=''):
1428 def _get_default_perms(self, user_group, suffix=''):
1424 from rhodecode.model.permission import PermissionModel
1429 from rhodecode.model.permission import PermissionModel
1425 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1430 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1426
1431
1427 def get_default_perms(self, suffix=''):
1432 def get_default_perms(self, suffix=''):
1428 return self._get_default_perms(self, suffix)
1433 return self._get_default_perms(self, suffix)
1429
1434
1430 def get_api_data(self, with_group_members=True, include_secrets=False):
1435 def get_api_data(self, with_group_members=True, include_secrets=False):
1431 """
1436 """
1432 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1437 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1433 basically forwarded.
1438 basically forwarded.
1434
1439
1435 """
1440 """
1436 user_group = self
1441 user_group = self
1437 data = {
1442 data = {
1438 'users_group_id': user_group.users_group_id,
1443 'users_group_id': user_group.users_group_id,
1439 'group_name': user_group.users_group_name,
1444 'group_name': user_group.users_group_name,
1440 'group_description': user_group.user_group_description,
1445 'group_description': user_group.user_group_description,
1441 'active': user_group.users_group_active,
1446 'active': user_group.users_group_active,
1442 'owner': user_group.user.username,
1447 'owner': user_group.user.username,
1443 'owner_email': user_group.user.email,
1448 'owner_email': user_group.user.email,
1444 }
1449 }
1445
1450
1446 if with_group_members:
1451 if with_group_members:
1447 users = []
1452 users = []
1448 for user in user_group.members:
1453 for user in user_group.members:
1449 user = user.user
1454 user = user.user
1450 users.append(user.get_api_data(include_secrets=include_secrets))
1455 users.append(user.get_api_data(include_secrets=include_secrets))
1451 data['users'] = users
1456 data['users'] = users
1452
1457
1453 return data
1458 return data
1454
1459
1455
1460
1456 class UserGroupMember(Base, BaseModel):
1461 class UserGroupMember(Base, BaseModel):
1457 __tablename__ = 'users_groups_members'
1462 __tablename__ = 'users_groups_members'
1458 __table_args__ = (
1463 __table_args__ = (
1459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1460 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1461 )
1466 )
1462
1467
1463 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1464 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1465 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1466
1471
1467 user = relationship('User', lazy='joined')
1472 user = relationship('User', lazy='joined')
1468 users_group = relationship('UserGroup')
1473 users_group = relationship('UserGroup')
1469
1474
1470 def __init__(self, gr_id='', u_id=''):
1475 def __init__(self, gr_id='', u_id=''):
1471 self.users_group_id = gr_id
1476 self.users_group_id = gr_id
1472 self.user_id = u_id
1477 self.user_id = u_id
1473
1478
1474
1479
1475 class RepositoryField(Base, BaseModel):
1480 class RepositoryField(Base, BaseModel):
1476 __tablename__ = 'repositories_fields'
1481 __tablename__ = 'repositories_fields'
1477 __table_args__ = (
1482 __table_args__ = (
1478 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1483 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1484 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1485 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1481 )
1486 )
1482 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1487 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1483
1488
1484 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1489 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1485 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1490 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1486 field_key = Column("field_key", String(250))
1491 field_key = Column("field_key", String(250))
1487 field_label = Column("field_label", String(1024), nullable=False)
1492 field_label = Column("field_label", String(1024), nullable=False)
1488 field_value = Column("field_value", String(10000), nullable=False)
1493 field_value = Column("field_value", String(10000), nullable=False)
1489 field_desc = Column("field_desc", String(1024), nullable=False)
1494 field_desc = Column("field_desc", String(1024), nullable=False)
1490 field_type = Column("field_type", String(255), nullable=False, unique=None)
1495 field_type = Column("field_type", String(255), nullable=False, unique=None)
1491 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1496 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1492
1497
1493 repository = relationship('Repository')
1498 repository = relationship('Repository')
1494
1499
1495 @property
1500 @property
1496 def field_key_prefixed(self):
1501 def field_key_prefixed(self):
1497 return 'ex_%s' % self.field_key
1502 return 'ex_%s' % self.field_key
1498
1503
1499 @classmethod
1504 @classmethod
1500 def un_prefix_key(cls, key):
1505 def un_prefix_key(cls, key):
1501 if key.startswith(cls.PREFIX):
1506 if key.startswith(cls.PREFIX):
1502 return key[len(cls.PREFIX):]
1507 return key[len(cls.PREFIX):]
1503 return key
1508 return key
1504
1509
1505 @classmethod
1510 @classmethod
1506 def get_by_key_name(cls, key, repo):
1511 def get_by_key_name(cls, key, repo):
1507 row = cls.query()\
1512 row = cls.query()\
1508 .filter(cls.repository == repo)\
1513 .filter(cls.repository == repo)\
1509 .filter(cls.field_key == key).scalar()
1514 .filter(cls.field_key == key).scalar()
1510 return row
1515 return row
1511
1516
1512
1517
1513 class Repository(Base, BaseModel):
1518 class Repository(Base, BaseModel):
1514 __tablename__ = 'repositories'
1519 __tablename__ = 'repositories'
1515 __table_args__ = (
1520 __table_args__ = (
1516 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1521 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1522 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1523 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1519 )
1524 )
1520 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1525 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1521 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1526 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1522
1527
1523 STATE_CREATED = 'repo_state_created'
1528 STATE_CREATED = 'repo_state_created'
1524 STATE_PENDING = 'repo_state_pending'
1529 STATE_PENDING = 'repo_state_pending'
1525 STATE_ERROR = 'repo_state_error'
1530 STATE_ERROR = 'repo_state_error'
1526
1531
1527 LOCK_AUTOMATIC = 'lock_auto'
1532 LOCK_AUTOMATIC = 'lock_auto'
1528 LOCK_API = 'lock_api'
1533 LOCK_API = 'lock_api'
1529 LOCK_WEB = 'lock_web'
1534 LOCK_WEB = 'lock_web'
1530 LOCK_PULL = 'lock_pull'
1535 LOCK_PULL = 'lock_pull'
1531
1536
1532 NAME_SEP = URL_SEP
1537 NAME_SEP = URL_SEP
1533
1538
1534 repo_id = Column(
1539 repo_id = Column(
1535 "repo_id", Integer(), nullable=False, unique=True, default=None,
1540 "repo_id", Integer(), nullable=False, unique=True, default=None,
1536 primary_key=True)
1541 primary_key=True)
1537 _repo_name = Column(
1542 _repo_name = Column(
1538 "repo_name", Text(), nullable=False, default=None)
1543 "repo_name", Text(), nullable=False, default=None)
1539 _repo_name_hash = Column(
1544 _repo_name_hash = Column(
1540 "repo_name_hash", String(255), nullable=False, unique=True)
1545 "repo_name_hash", String(255), nullable=False, unique=True)
1541 repo_state = Column("repo_state", String(255), nullable=True)
1546 repo_state = Column("repo_state", String(255), nullable=True)
1542
1547
1543 clone_uri = Column(
1548 clone_uri = Column(
1544 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1549 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1545 default=None)
1550 default=None)
1546 repo_type = Column(
1551 repo_type = Column(
1547 "repo_type", String(255), nullable=False, unique=False, default=None)
1552 "repo_type", String(255), nullable=False, unique=False, default=None)
1548 user_id = Column(
1553 user_id = Column(
1549 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1554 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1550 unique=False, default=None)
1555 unique=False, default=None)
1551 private = Column(
1556 private = Column(
1552 "private", Boolean(), nullable=True, unique=None, default=None)
1557 "private", Boolean(), nullable=True, unique=None, default=None)
1553 enable_statistics = Column(
1558 enable_statistics = Column(
1554 "statistics", Boolean(), nullable=True, unique=None, default=True)
1559 "statistics", Boolean(), nullable=True, unique=None, default=True)
1555 enable_downloads = Column(
1560 enable_downloads = Column(
1556 "downloads", Boolean(), nullable=True, unique=None, default=True)
1561 "downloads", Boolean(), nullable=True, unique=None, default=True)
1557 description = Column(
1562 description = Column(
1558 "description", String(10000), nullable=True, unique=None, default=None)
1563 "description", String(10000), nullable=True, unique=None, default=None)
1559 created_on = Column(
1564 created_on = Column(
1560 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1565 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1561 default=datetime.datetime.now)
1566 default=datetime.datetime.now)
1562 updated_on = Column(
1567 updated_on = Column(
1563 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1568 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1564 default=datetime.datetime.now)
1569 default=datetime.datetime.now)
1565 _landing_revision = Column(
1570 _landing_revision = Column(
1566 "landing_revision", String(255), nullable=False, unique=False,
1571 "landing_revision", String(255), nullable=False, unique=False,
1567 default=None)
1572 default=None)
1568 enable_locking = Column(
1573 enable_locking = Column(
1569 "enable_locking", Boolean(), nullable=False, unique=None,
1574 "enable_locking", Boolean(), nullable=False, unique=None,
1570 default=False)
1575 default=False)
1571 _locked = Column(
1576 _locked = Column(
1572 "locked", String(255), nullable=True, unique=False, default=None)
1577 "locked", String(255), nullable=True, unique=False, default=None)
1573 _changeset_cache = Column(
1578 _changeset_cache = Column(
1574 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1579 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1575
1580
1576 fork_id = Column(
1581 fork_id = Column(
1577 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1582 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1578 nullable=True, unique=False, default=None)
1583 nullable=True, unique=False, default=None)
1579 group_id = Column(
1584 group_id = Column(
1580 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1585 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1581 unique=False, default=None)
1586 unique=False, default=None)
1582
1587
1583 user = relationship('User', lazy='joined')
1588 user = relationship('User', lazy='joined')
1584 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1589 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1585 group = relationship('RepoGroup', lazy='joined')
1590 group = relationship('RepoGroup', lazy='joined')
1586 repo_to_perm = relationship(
1591 repo_to_perm = relationship(
1587 'UserRepoToPerm', cascade='all',
1592 'UserRepoToPerm', cascade='all',
1588 order_by='UserRepoToPerm.repo_to_perm_id')
1593 order_by='UserRepoToPerm.repo_to_perm_id')
1589 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1594 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1590 stats = relationship('Statistics', cascade='all', uselist=False)
1595 stats = relationship('Statistics', cascade='all', uselist=False)
1591
1596
1592 followers = relationship(
1597 followers = relationship(
1593 'UserFollowing',
1598 'UserFollowing',
1594 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1599 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1595 cascade='all')
1600 cascade='all')
1596 extra_fields = relationship(
1601 extra_fields = relationship(
1597 'RepositoryField', cascade="all, delete, delete-orphan")
1602 'RepositoryField', cascade="all, delete, delete-orphan")
1598 logs = relationship('UserLog')
1603 logs = relationship('UserLog')
1599 comments = relationship(
1604 comments = relationship(
1600 'ChangesetComment', cascade="all, delete, delete-orphan")
1605 'ChangesetComment', cascade="all, delete, delete-orphan")
1601 pull_requests_source = relationship(
1606 pull_requests_source = relationship(
1602 'PullRequest',
1607 'PullRequest',
1603 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1608 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1604 cascade="all, delete, delete-orphan")
1609 cascade="all, delete, delete-orphan")
1605 pull_requests_target = relationship(
1610 pull_requests_target = relationship(
1606 'PullRequest',
1611 'PullRequest',
1607 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1612 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1608 cascade="all, delete, delete-orphan")
1613 cascade="all, delete, delete-orphan")
1609 ui = relationship('RepoRhodeCodeUi', cascade="all")
1614 ui = relationship('RepoRhodeCodeUi', cascade="all")
1610 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1615 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1611 integrations = relationship('Integration',
1616 integrations = relationship('Integration',
1612 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1613
1618
1614 def __unicode__(self):
1619 def __unicode__(self):
1615 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1620 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1616 safe_unicode(self.repo_name))
1621 safe_unicode(self.repo_name))
1617
1622
1618 @hybrid_property
1623 @hybrid_property
1619 def description_safe(self):
1624 def description_safe(self):
1620 from rhodecode.lib import helpers as h
1625 from rhodecode.lib import helpers as h
1621 return h.escape(self.description)
1626 return h.escape(self.description)
1622
1627
1623 @hybrid_property
1628 @hybrid_property
1624 def landing_rev(self):
1629 def landing_rev(self):
1625 # always should return [rev_type, rev]
1630 # always should return [rev_type, rev]
1626 if self._landing_revision:
1631 if self._landing_revision:
1627 _rev_info = self._landing_revision.split(':')
1632 _rev_info = self._landing_revision.split(':')
1628 if len(_rev_info) < 2:
1633 if len(_rev_info) < 2:
1629 _rev_info.insert(0, 'rev')
1634 _rev_info.insert(0, 'rev')
1630 return [_rev_info[0], _rev_info[1]]
1635 return [_rev_info[0], _rev_info[1]]
1631 return [None, None]
1636 return [None, None]
1632
1637
1633 @landing_rev.setter
1638 @landing_rev.setter
1634 def landing_rev(self, val):
1639 def landing_rev(self, val):
1635 if ':' not in val:
1640 if ':' not in val:
1636 raise ValueError('value must be delimited with `:` and consist '
1641 raise ValueError('value must be delimited with `:` and consist '
1637 'of <rev_type>:<rev>, got %s instead' % val)
1642 'of <rev_type>:<rev>, got %s instead' % val)
1638 self._landing_revision = val
1643 self._landing_revision = val
1639
1644
1640 @hybrid_property
1645 @hybrid_property
1641 def locked(self):
1646 def locked(self):
1642 if self._locked:
1647 if self._locked:
1643 user_id, timelocked, reason = self._locked.split(':')
1648 user_id, timelocked, reason = self._locked.split(':')
1644 lock_values = int(user_id), timelocked, reason
1649 lock_values = int(user_id), timelocked, reason
1645 else:
1650 else:
1646 lock_values = [None, None, None]
1651 lock_values = [None, None, None]
1647 return lock_values
1652 return lock_values
1648
1653
1649 @locked.setter
1654 @locked.setter
1650 def locked(self, val):
1655 def locked(self, val):
1651 if val and isinstance(val, (list, tuple)):
1656 if val and isinstance(val, (list, tuple)):
1652 self._locked = ':'.join(map(str, val))
1657 self._locked = ':'.join(map(str, val))
1653 else:
1658 else:
1654 self._locked = None
1659 self._locked = None
1655
1660
1656 @hybrid_property
1661 @hybrid_property
1657 def changeset_cache(self):
1662 def changeset_cache(self):
1658 from rhodecode.lib.vcs.backends.base import EmptyCommit
1663 from rhodecode.lib.vcs.backends.base import EmptyCommit
1659 dummy = EmptyCommit().__json__()
1664 dummy = EmptyCommit().__json__()
1660 if not self._changeset_cache:
1665 if not self._changeset_cache:
1661 return dummy
1666 return dummy
1662 try:
1667 try:
1663 return json.loads(self._changeset_cache)
1668 return json.loads(self._changeset_cache)
1664 except TypeError:
1669 except TypeError:
1665 return dummy
1670 return dummy
1666 except Exception:
1671 except Exception:
1667 log.error(traceback.format_exc())
1672 log.error(traceback.format_exc())
1668 return dummy
1673 return dummy
1669
1674
1670 @changeset_cache.setter
1675 @changeset_cache.setter
1671 def changeset_cache(self, val):
1676 def changeset_cache(self, val):
1672 try:
1677 try:
1673 self._changeset_cache = json.dumps(val)
1678 self._changeset_cache = json.dumps(val)
1674 except Exception:
1679 except Exception:
1675 log.error(traceback.format_exc())
1680 log.error(traceback.format_exc())
1676
1681
1677 @hybrid_property
1682 @hybrid_property
1678 def repo_name(self):
1683 def repo_name(self):
1679 return self._repo_name
1684 return self._repo_name
1680
1685
1681 @repo_name.setter
1686 @repo_name.setter
1682 def repo_name(self, value):
1687 def repo_name(self, value):
1683 self._repo_name = value
1688 self._repo_name = value
1684 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1689 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1685
1690
1686 @classmethod
1691 @classmethod
1687 def normalize_repo_name(cls, repo_name):
1692 def normalize_repo_name(cls, repo_name):
1688 """
1693 """
1689 Normalizes os specific repo_name to the format internally stored inside
1694 Normalizes os specific repo_name to the format internally stored inside
1690 database using URL_SEP
1695 database using URL_SEP
1691
1696
1692 :param cls:
1697 :param cls:
1693 :param repo_name:
1698 :param repo_name:
1694 """
1699 """
1695 return cls.NAME_SEP.join(repo_name.split(os.sep))
1700 return cls.NAME_SEP.join(repo_name.split(os.sep))
1696
1701
1697 @classmethod
1702 @classmethod
1698 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1703 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1699 session = Session()
1704 session = Session()
1700 q = session.query(cls).filter(cls.repo_name == repo_name)
1705 q = session.query(cls).filter(cls.repo_name == repo_name)
1701
1706
1702 if cache:
1707 if cache:
1703 if identity_cache:
1708 if identity_cache:
1704 val = cls.identity_cache(session, 'repo_name', repo_name)
1709 val = cls.identity_cache(session, 'repo_name', repo_name)
1705 if val:
1710 if val:
1706 return val
1711 return val
1707 else:
1712 else:
1708 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1713 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1709 q = q.options(
1714 q = q.options(
1710 FromCache("sql_cache_short", cache_key))
1715 FromCache("sql_cache_short", cache_key))
1711
1716
1712 return q.scalar()
1717 return q.scalar()
1713
1718
1714 @classmethod
1719 @classmethod
1715 def get_by_full_path(cls, repo_full_path):
1720 def get_by_full_path(cls, repo_full_path):
1716 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1721 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1717 repo_name = cls.normalize_repo_name(repo_name)
1722 repo_name = cls.normalize_repo_name(repo_name)
1718 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1723 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1719
1724
1720 @classmethod
1725 @classmethod
1721 def get_repo_forks(cls, repo_id):
1726 def get_repo_forks(cls, repo_id):
1722 return cls.query().filter(Repository.fork_id == repo_id)
1727 return cls.query().filter(Repository.fork_id == repo_id)
1723
1728
1724 @classmethod
1729 @classmethod
1725 def base_path(cls):
1730 def base_path(cls):
1726 """
1731 """
1727 Returns base path when all repos are stored
1732 Returns base path when all repos are stored
1728
1733
1729 :param cls:
1734 :param cls:
1730 """
1735 """
1731 q = Session().query(RhodeCodeUi)\
1736 q = Session().query(RhodeCodeUi)\
1732 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1737 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1733 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1738 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1734 return q.one().ui_value
1739 return q.one().ui_value
1735
1740
1736 @classmethod
1741 @classmethod
1737 def is_valid(cls, repo_name):
1742 def is_valid(cls, repo_name):
1738 """
1743 """
1739 returns True if given repo name is a valid filesystem repository
1744 returns True if given repo name is a valid filesystem repository
1740
1745
1741 :param cls:
1746 :param cls:
1742 :param repo_name:
1747 :param repo_name:
1743 """
1748 """
1744 from rhodecode.lib.utils import is_valid_repo
1749 from rhodecode.lib.utils import is_valid_repo
1745
1750
1746 return is_valid_repo(repo_name, cls.base_path())
1751 return is_valid_repo(repo_name, cls.base_path())
1747
1752
1748 @classmethod
1753 @classmethod
1749 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1754 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1750 case_insensitive=True):
1755 case_insensitive=True):
1751 q = Repository.query()
1756 q = Repository.query()
1752
1757
1753 if not isinstance(user_id, Optional):
1758 if not isinstance(user_id, Optional):
1754 q = q.filter(Repository.user_id == user_id)
1759 q = q.filter(Repository.user_id == user_id)
1755
1760
1756 if not isinstance(group_id, Optional):
1761 if not isinstance(group_id, Optional):
1757 q = q.filter(Repository.group_id == group_id)
1762 q = q.filter(Repository.group_id == group_id)
1758
1763
1759 if case_insensitive:
1764 if case_insensitive:
1760 q = q.order_by(func.lower(Repository.repo_name))
1765 q = q.order_by(func.lower(Repository.repo_name))
1761 else:
1766 else:
1762 q = q.order_by(Repository.repo_name)
1767 q = q.order_by(Repository.repo_name)
1763 return q.all()
1768 return q.all()
1764
1769
1765 @property
1770 @property
1766 def forks(self):
1771 def forks(self):
1767 """
1772 """
1768 Return forks of this repo
1773 Return forks of this repo
1769 """
1774 """
1770 return Repository.get_repo_forks(self.repo_id)
1775 return Repository.get_repo_forks(self.repo_id)
1771
1776
1772 @property
1777 @property
1773 def parent(self):
1778 def parent(self):
1774 """
1779 """
1775 Returns fork parent
1780 Returns fork parent
1776 """
1781 """
1777 return self.fork
1782 return self.fork
1778
1783
1779 @property
1784 @property
1780 def just_name(self):
1785 def just_name(self):
1781 return self.repo_name.split(self.NAME_SEP)[-1]
1786 return self.repo_name.split(self.NAME_SEP)[-1]
1782
1787
1783 @property
1788 @property
1784 def groups_with_parents(self):
1789 def groups_with_parents(self):
1785 groups = []
1790 groups = []
1786 if self.group is None:
1791 if self.group is None:
1787 return groups
1792 return groups
1788
1793
1789 cur_gr = self.group
1794 cur_gr = self.group
1790 groups.insert(0, cur_gr)
1795 groups.insert(0, cur_gr)
1791 while 1:
1796 while 1:
1792 gr = getattr(cur_gr, 'parent_group', None)
1797 gr = getattr(cur_gr, 'parent_group', None)
1793 cur_gr = cur_gr.parent_group
1798 cur_gr = cur_gr.parent_group
1794 if gr is None:
1799 if gr is None:
1795 break
1800 break
1796 groups.insert(0, gr)
1801 groups.insert(0, gr)
1797
1802
1798 return groups
1803 return groups
1799
1804
1800 @property
1805 @property
1801 def groups_and_repo(self):
1806 def groups_and_repo(self):
1802 return self.groups_with_parents, self
1807 return self.groups_with_parents, self
1803
1808
1804 @LazyProperty
1809 @LazyProperty
1805 def repo_path(self):
1810 def repo_path(self):
1806 """
1811 """
1807 Returns base full path for that repository means where it actually
1812 Returns base full path for that repository means where it actually
1808 exists on a filesystem
1813 exists on a filesystem
1809 """
1814 """
1810 q = Session().query(RhodeCodeUi).filter(
1815 q = Session().query(RhodeCodeUi).filter(
1811 RhodeCodeUi.ui_key == self.NAME_SEP)
1816 RhodeCodeUi.ui_key == self.NAME_SEP)
1812 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1817 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1813 return q.one().ui_value
1818 return q.one().ui_value
1814
1819
1815 @property
1820 @property
1816 def repo_full_path(self):
1821 def repo_full_path(self):
1817 p = [self.repo_path]
1822 p = [self.repo_path]
1818 # we need to split the name by / since this is how we store the
1823 # we need to split the name by / since this is how we store the
1819 # names in the database, but that eventually needs to be converted
1824 # names in the database, but that eventually needs to be converted
1820 # into a valid system path
1825 # into a valid system path
1821 p += self.repo_name.split(self.NAME_SEP)
1826 p += self.repo_name.split(self.NAME_SEP)
1822 return os.path.join(*map(safe_unicode, p))
1827 return os.path.join(*map(safe_unicode, p))
1823
1828
1824 @property
1829 @property
1825 def cache_keys(self):
1830 def cache_keys(self):
1826 """
1831 """
1827 Returns associated cache keys for that repo
1832 Returns associated cache keys for that repo
1828 """
1833 """
1829 return CacheKey.query()\
1834 return CacheKey.query()\
1830 .filter(CacheKey.cache_args == self.repo_name)\
1835 .filter(CacheKey.cache_args == self.repo_name)\
1831 .order_by(CacheKey.cache_key)\
1836 .order_by(CacheKey.cache_key)\
1832 .all()
1837 .all()
1833
1838
1834 def get_new_name(self, repo_name):
1839 def get_new_name(self, repo_name):
1835 """
1840 """
1836 returns new full repository name based on assigned group and new new
1841 returns new full repository name based on assigned group and new new
1837
1842
1838 :param group_name:
1843 :param group_name:
1839 """
1844 """
1840 path_prefix = self.group.full_path_splitted if self.group else []
1845 path_prefix = self.group.full_path_splitted if self.group else []
1841 return self.NAME_SEP.join(path_prefix + [repo_name])
1846 return self.NAME_SEP.join(path_prefix + [repo_name])
1842
1847
1843 @property
1848 @property
1844 def _config(self):
1849 def _config(self):
1845 """
1850 """
1846 Returns db based config object.
1851 Returns db based config object.
1847 """
1852 """
1848 from rhodecode.lib.utils import make_db_config
1853 from rhodecode.lib.utils import make_db_config
1849 return make_db_config(clear_session=False, repo=self)
1854 return make_db_config(clear_session=False, repo=self)
1850
1855
1851 def permissions(self, with_admins=True, with_owner=True):
1856 def permissions(self, with_admins=True, with_owner=True):
1852 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1857 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1853 q = q.options(joinedload(UserRepoToPerm.repository),
1858 q = q.options(joinedload(UserRepoToPerm.repository),
1854 joinedload(UserRepoToPerm.user),
1859 joinedload(UserRepoToPerm.user),
1855 joinedload(UserRepoToPerm.permission),)
1860 joinedload(UserRepoToPerm.permission),)
1856
1861
1857 # get owners and admins and permissions. We do a trick of re-writing
1862 # get owners and admins and permissions. We do a trick of re-writing
1858 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1863 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1859 # has a global reference and changing one object propagates to all
1864 # has a global reference and changing one object propagates to all
1860 # others. This means if admin is also an owner admin_row that change
1865 # others. This means if admin is also an owner admin_row that change
1861 # would propagate to both objects
1866 # would propagate to both objects
1862 perm_rows = []
1867 perm_rows = []
1863 for _usr in q.all():
1868 for _usr in q.all():
1864 usr = AttributeDict(_usr.user.get_dict())
1869 usr = AttributeDict(_usr.user.get_dict())
1865 usr.permission = _usr.permission.permission_name
1870 usr.permission = _usr.permission.permission_name
1866 perm_rows.append(usr)
1871 perm_rows.append(usr)
1867
1872
1868 # filter the perm rows by 'default' first and then sort them by
1873 # filter the perm rows by 'default' first and then sort them by
1869 # admin,write,read,none permissions sorted again alphabetically in
1874 # admin,write,read,none permissions sorted again alphabetically in
1870 # each group
1875 # each group
1871 perm_rows = sorted(perm_rows, key=display_user_sort)
1876 perm_rows = sorted(perm_rows, key=display_user_sort)
1872
1877
1873 _admin_perm = 'repository.admin'
1878 _admin_perm = 'repository.admin'
1874 owner_row = []
1879 owner_row = []
1875 if with_owner:
1880 if with_owner:
1876 usr = AttributeDict(self.user.get_dict())
1881 usr = AttributeDict(self.user.get_dict())
1877 usr.owner_row = True
1882 usr.owner_row = True
1878 usr.permission = _admin_perm
1883 usr.permission = _admin_perm
1879 owner_row.append(usr)
1884 owner_row.append(usr)
1880
1885
1881 super_admin_rows = []
1886 super_admin_rows = []
1882 if with_admins:
1887 if with_admins:
1883 for usr in User.get_all_super_admins():
1888 for usr in User.get_all_super_admins():
1884 # if this admin is also owner, don't double the record
1889 # if this admin is also owner, don't double the record
1885 if usr.user_id == owner_row[0].user_id:
1890 if usr.user_id == owner_row[0].user_id:
1886 owner_row[0].admin_row = True
1891 owner_row[0].admin_row = True
1887 else:
1892 else:
1888 usr = AttributeDict(usr.get_dict())
1893 usr = AttributeDict(usr.get_dict())
1889 usr.admin_row = True
1894 usr.admin_row = True
1890 usr.permission = _admin_perm
1895 usr.permission = _admin_perm
1891 super_admin_rows.append(usr)
1896 super_admin_rows.append(usr)
1892
1897
1893 return super_admin_rows + owner_row + perm_rows
1898 return super_admin_rows + owner_row + perm_rows
1894
1899
1895 def permission_user_groups(self):
1900 def permission_user_groups(self):
1896 q = UserGroupRepoToPerm.query().filter(
1901 q = UserGroupRepoToPerm.query().filter(
1897 UserGroupRepoToPerm.repository == self)
1902 UserGroupRepoToPerm.repository == self)
1898 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1903 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1899 joinedload(UserGroupRepoToPerm.users_group),
1904 joinedload(UserGroupRepoToPerm.users_group),
1900 joinedload(UserGroupRepoToPerm.permission),)
1905 joinedload(UserGroupRepoToPerm.permission),)
1901
1906
1902 perm_rows = []
1907 perm_rows = []
1903 for _user_group in q.all():
1908 for _user_group in q.all():
1904 usr = AttributeDict(_user_group.users_group.get_dict())
1909 usr = AttributeDict(_user_group.users_group.get_dict())
1905 usr.permission = _user_group.permission.permission_name
1910 usr.permission = _user_group.permission.permission_name
1906 perm_rows.append(usr)
1911 perm_rows.append(usr)
1907
1912
1908 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1913 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1909 return perm_rows
1914 return perm_rows
1910
1915
1911 def get_api_data(self, include_secrets=False):
1916 def get_api_data(self, include_secrets=False):
1912 """
1917 """
1913 Common function for generating repo api data
1918 Common function for generating repo api data
1914
1919
1915 :param include_secrets: See :meth:`User.get_api_data`.
1920 :param include_secrets: See :meth:`User.get_api_data`.
1916
1921
1917 """
1922 """
1918 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1923 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1919 # move this methods on models level.
1924 # move this methods on models level.
1920 from rhodecode.model.settings import SettingsModel
1925 from rhodecode.model.settings import SettingsModel
1921 from rhodecode.model.repo import RepoModel
1926 from rhodecode.model.repo import RepoModel
1922
1927
1923 repo = self
1928 repo = self
1924 _user_id, _time, _reason = self.locked
1929 _user_id, _time, _reason = self.locked
1925
1930
1926 data = {
1931 data = {
1927 'repo_id': repo.repo_id,
1932 'repo_id': repo.repo_id,
1928 'repo_name': repo.repo_name,
1933 'repo_name': repo.repo_name,
1929 'repo_type': repo.repo_type,
1934 'repo_type': repo.repo_type,
1930 'clone_uri': repo.clone_uri or '',
1935 'clone_uri': repo.clone_uri or '',
1931 'url': RepoModel().get_url(self),
1936 'url': RepoModel().get_url(self),
1932 'private': repo.private,
1937 'private': repo.private,
1933 'created_on': repo.created_on,
1938 'created_on': repo.created_on,
1934 'description': repo.description_safe,
1939 'description': repo.description_safe,
1935 'landing_rev': repo.landing_rev,
1940 'landing_rev': repo.landing_rev,
1936 'owner': repo.user.username,
1941 'owner': repo.user.username,
1937 'fork_of': repo.fork.repo_name if repo.fork else None,
1942 'fork_of': repo.fork.repo_name if repo.fork else None,
1938 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1943 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1939 'enable_statistics': repo.enable_statistics,
1944 'enable_statistics': repo.enable_statistics,
1940 'enable_locking': repo.enable_locking,
1945 'enable_locking': repo.enable_locking,
1941 'enable_downloads': repo.enable_downloads,
1946 'enable_downloads': repo.enable_downloads,
1942 'last_changeset': repo.changeset_cache,
1947 'last_changeset': repo.changeset_cache,
1943 'locked_by': User.get(_user_id).get_api_data(
1948 'locked_by': User.get(_user_id).get_api_data(
1944 include_secrets=include_secrets) if _user_id else None,
1949 include_secrets=include_secrets) if _user_id else None,
1945 'locked_date': time_to_datetime(_time) if _time else None,
1950 'locked_date': time_to_datetime(_time) if _time else None,
1946 'lock_reason': _reason if _reason else None,
1951 'lock_reason': _reason if _reason else None,
1947 }
1952 }
1948
1953
1949 # TODO: mikhail: should be per-repo settings here
1954 # TODO: mikhail: should be per-repo settings here
1950 rc_config = SettingsModel().get_all_settings()
1955 rc_config = SettingsModel().get_all_settings()
1951 repository_fields = str2bool(
1956 repository_fields = str2bool(
1952 rc_config.get('rhodecode_repository_fields'))
1957 rc_config.get('rhodecode_repository_fields'))
1953 if repository_fields:
1958 if repository_fields:
1954 for f in self.extra_fields:
1959 for f in self.extra_fields:
1955 data[f.field_key_prefixed] = f.field_value
1960 data[f.field_key_prefixed] = f.field_value
1956
1961
1957 return data
1962 return data
1958
1963
1959 @classmethod
1964 @classmethod
1960 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1965 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1961 if not lock_time:
1966 if not lock_time:
1962 lock_time = time.time()
1967 lock_time = time.time()
1963 if not lock_reason:
1968 if not lock_reason:
1964 lock_reason = cls.LOCK_AUTOMATIC
1969 lock_reason = cls.LOCK_AUTOMATIC
1965 repo.locked = [user_id, lock_time, lock_reason]
1970 repo.locked = [user_id, lock_time, lock_reason]
1966 Session().add(repo)
1971 Session().add(repo)
1967 Session().commit()
1972 Session().commit()
1968
1973
1969 @classmethod
1974 @classmethod
1970 def unlock(cls, repo):
1975 def unlock(cls, repo):
1971 repo.locked = None
1976 repo.locked = None
1972 Session().add(repo)
1977 Session().add(repo)
1973 Session().commit()
1978 Session().commit()
1974
1979
1975 @classmethod
1980 @classmethod
1976 def getlock(cls, repo):
1981 def getlock(cls, repo):
1977 return repo.locked
1982 return repo.locked
1978
1983
1979 def is_user_lock(self, user_id):
1984 def is_user_lock(self, user_id):
1980 if self.lock[0]:
1985 if self.lock[0]:
1981 lock_user_id = safe_int(self.lock[0])
1986 lock_user_id = safe_int(self.lock[0])
1982 user_id = safe_int(user_id)
1987 user_id = safe_int(user_id)
1983 # both are ints, and they are equal
1988 # both are ints, and they are equal
1984 return all([lock_user_id, user_id]) and lock_user_id == user_id
1989 return all([lock_user_id, user_id]) and lock_user_id == user_id
1985
1990
1986 return False
1991 return False
1987
1992
1988 def get_locking_state(self, action, user_id, only_when_enabled=True):
1993 def get_locking_state(self, action, user_id, only_when_enabled=True):
1989 """
1994 """
1990 Checks locking on this repository, if locking is enabled and lock is
1995 Checks locking on this repository, if locking is enabled and lock is
1991 present returns a tuple of make_lock, locked, locked_by.
1996 present returns a tuple of make_lock, locked, locked_by.
1992 make_lock can have 3 states None (do nothing) True, make lock
1997 make_lock can have 3 states None (do nothing) True, make lock
1993 False release lock, This value is later propagated to hooks, which
1998 False release lock, This value is later propagated to hooks, which
1994 do the locking. Think about this as signals passed to hooks what to do.
1999 do the locking. Think about this as signals passed to hooks what to do.
1995
2000
1996 """
2001 """
1997 # TODO: johbo: This is part of the business logic and should be moved
2002 # TODO: johbo: This is part of the business logic and should be moved
1998 # into the RepositoryModel.
2003 # into the RepositoryModel.
1999
2004
2000 if action not in ('push', 'pull'):
2005 if action not in ('push', 'pull'):
2001 raise ValueError("Invalid action value: %s" % repr(action))
2006 raise ValueError("Invalid action value: %s" % repr(action))
2002
2007
2003 # defines if locked error should be thrown to user
2008 # defines if locked error should be thrown to user
2004 currently_locked = False
2009 currently_locked = False
2005 # defines if new lock should be made, tri-state
2010 # defines if new lock should be made, tri-state
2006 make_lock = None
2011 make_lock = None
2007 repo = self
2012 repo = self
2008 user = User.get(user_id)
2013 user = User.get(user_id)
2009
2014
2010 lock_info = repo.locked
2015 lock_info = repo.locked
2011
2016
2012 if repo and (repo.enable_locking or not only_when_enabled):
2017 if repo and (repo.enable_locking or not only_when_enabled):
2013 if action == 'push':
2018 if action == 'push':
2014 # check if it's already locked !, if it is compare users
2019 # check if it's already locked !, if it is compare users
2015 locked_by_user_id = lock_info[0]
2020 locked_by_user_id = lock_info[0]
2016 if user.user_id == locked_by_user_id:
2021 if user.user_id == locked_by_user_id:
2017 log.debug(
2022 log.debug(
2018 'Got `push` action from user %s, now unlocking', user)
2023 'Got `push` action from user %s, now unlocking', user)
2019 # unlock if we have push from user who locked
2024 # unlock if we have push from user who locked
2020 make_lock = False
2025 make_lock = False
2021 else:
2026 else:
2022 # we're not the same user who locked, ban with
2027 # we're not the same user who locked, ban with
2023 # code defined in settings (default is 423 HTTP Locked) !
2028 # code defined in settings (default is 423 HTTP Locked) !
2024 log.debug('Repo %s is currently locked by %s', repo, user)
2029 log.debug('Repo %s is currently locked by %s', repo, user)
2025 currently_locked = True
2030 currently_locked = True
2026 elif action == 'pull':
2031 elif action == 'pull':
2027 # [0] user [1] date
2032 # [0] user [1] date
2028 if lock_info[0] and lock_info[1]:
2033 if lock_info[0] and lock_info[1]:
2029 log.debug('Repo %s is currently locked by %s', repo, user)
2034 log.debug('Repo %s is currently locked by %s', repo, user)
2030 currently_locked = True
2035 currently_locked = True
2031 else:
2036 else:
2032 log.debug('Setting lock on repo %s by %s', repo, user)
2037 log.debug('Setting lock on repo %s by %s', repo, user)
2033 make_lock = True
2038 make_lock = True
2034
2039
2035 else:
2040 else:
2036 log.debug('Repository %s do not have locking enabled', repo)
2041 log.debug('Repository %s do not have locking enabled', repo)
2037
2042
2038 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2043 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2039 make_lock, currently_locked, lock_info)
2044 make_lock, currently_locked, lock_info)
2040
2045
2041 from rhodecode.lib.auth import HasRepoPermissionAny
2046 from rhodecode.lib.auth import HasRepoPermissionAny
2042 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2047 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2043 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2048 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2044 # if we don't have at least write permission we cannot make a lock
2049 # if we don't have at least write permission we cannot make a lock
2045 log.debug('lock state reset back to FALSE due to lack '
2050 log.debug('lock state reset back to FALSE due to lack '
2046 'of at least read permission')
2051 'of at least read permission')
2047 make_lock = False
2052 make_lock = False
2048
2053
2049 return make_lock, currently_locked, lock_info
2054 return make_lock, currently_locked, lock_info
2050
2055
2051 @property
2056 @property
2052 def last_db_change(self):
2057 def last_db_change(self):
2053 return self.updated_on
2058 return self.updated_on
2054
2059
2055 @property
2060 @property
2056 def clone_uri_hidden(self):
2061 def clone_uri_hidden(self):
2057 clone_uri = self.clone_uri
2062 clone_uri = self.clone_uri
2058 if clone_uri:
2063 if clone_uri:
2059 import urlobject
2064 import urlobject
2060 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2065 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2061 if url_obj.password:
2066 if url_obj.password:
2062 clone_uri = url_obj.with_password('*****')
2067 clone_uri = url_obj.with_password('*****')
2063 return clone_uri
2068 return clone_uri
2064
2069
2065 def clone_url(self, **override):
2070 def clone_url(self, **override):
2066 from rhodecode.model.settings import SettingsModel
2071 from rhodecode.model.settings import SettingsModel
2067
2072
2068 uri_tmpl = None
2073 uri_tmpl = None
2069 if 'with_id' in override:
2074 if 'with_id' in override:
2070 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2075 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2071 del override['with_id']
2076 del override['with_id']
2072
2077
2073 if 'uri_tmpl' in override:
2078 if 'uri_tmpl' in override:
2074 uri_tmpl = override['uri_tmpl']
2079 uri_tmpl = override['uri_tmpl']
2075 del override['uri_tmpl']
2080 del override['uri_tmpl']
2076
2081
2077 # we didn't override our tmpl from **overrides
2082 # we didn't override our tmpl from **overrides
2078 if not uri_tmpl:
2083 if not uri_tmpl:
2079 rc_config = SettingsModel().get_all_settings(cache=True)
2084 rc_config = SettingsModel().get_all_settings(cache=True)
2080 uri_tmpl = rc_config.get(
2085 uri_tmpl = rc_config.get(
2081 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2086 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2082
2087
2083 request = get_current_request()
2088 request = get_current_request()
2084 return get_clone_url(request=request,
2089 return get_clone_url(request=request,
2085 uri_tmpl=uri_tmpl,
2090 uri_tmpl=uri_tmpl,
2086 repo_name=self.repo_name,
2091 repo_name=self.repo_name,
2087 repo_id=self.repo_id, **override)
2092 repo_id=self.repo_id, **override)
2088
2093
2089 def set_state(self, state):
2094 def set_state(self, state):
2090 self.repo_state = state
2095 self.repo_state = state
2091 Session().add(self)
2096 Session().add(self)
2092 #==========================================================================
2097 #==========================================================================
2093 # SCM PROPERTIES
2098 # SCM PROPERTIES
2094 #==========================================================================
2099 #==========================================================================
2095
2100
2096 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2101 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2097 return get_commit_safe(
2102 return get_commit_safe(
2098 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2103 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2099
2104
2100 def get_changeset(self, rev=None, pre_load=None):
2105 def get_changeset(self, rev=None, pre_load=None):
2101 warnings.warn("Use get_commit", DeprecationWarning)
2106 warnings.warn("Use get_commit", DeprecationWarning)
2102 commit_id = None
2107 commit_id = None
2103 commit_idx = None
2108 commit_idx = None
2104 if isinstance(rev, basestring):
2109 if isinstance(rev, basestring):
2105 commit_id = rev
2110 commit_id = rev
2106 else:
2111 else:
2107 commit_idx = rev
2112 commit_idx = rev
2108 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2113 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2109 pre_load=pre_load)
2114 pre_load=pre_load)
2110
2115
2111 def get_landing_commit(self):
2116 def get_landing_commit(self):
2112 """
2117 """
2113 Returns landing commit, or if that doesn't exist returns the tip
2118 Returns landing commit, or if that doesn't exist returns the tip
2114 """
2119 """
2115 _rev_type, _rev = self.landing_rev
2120 _rev_type, _rev = self.landing_rev
2116 commit = self.get_commit(_rev)
2121 commit = self.get_commit(_rev)
2117 if isinstance(commit, EmptyCommit):
2122 if isinstance(commit, EmptyCommit):
2118 return self.get_commit()
2123 return self.get_commit()
2119 return commit
2124 return commit
2120
2125
2121 def update_commit_cache(self, cs_cache=None, config=None):
2126 def update_commit_cache(self, cs_cache=None, config=None):
2122 """
2127 """
2123 Update cache of last changeset for repository, keys should be::
2128 Update cache of last changeset for repository, keys should be::
2124
2129
2125 short_id
2130 short_id
2126 raw_id
2131 raw_id
2127 revision
2132 revision
2128 parents
2133 parents
2129 message
2134 message
2130 date
2135 date
2131 author
2136 author
2132
2137
2133 :param cs_cache:
2138 :param cs_cache:
2134 """
2139 """
2135 from rhodecode.lib.vcs.backends.base import BaseChangeset
2140 from rhodecode.lib.vcs.backends.base import BaseChangeset
2136 if cs_cache is None:
2141 if cs_cache is None:
2137 # use no-cache version here
2142 # use no-cache version here
2138 scm_repo = self.scm_instance(cache=False, config=config)
2143 scm_repo = self.scm_instance(cache=False, config=config)
2139 if scm_repo:
2144 if scm_repo:
2140 cs_cache = scm_repo.get_commit(
2145 cs_cache = scm_repo.get_commit(
2141 pre_load=["author", "date", "message", "parents"])
2146 pre_load=["author", "date", "message", "parents"])
2142 else:
2147 else:
2143 cs_cache = EmptyCommit()
2148 cs_cache = EmptyCommit()
2144
2149
2145 if isinstance(cs_cache, BaseChangeset):
2150 if isinstance(cs_cache, BaseChangeset):
2146 cs_cache = cs_cache.__json__()
2151 cs_cache = cs_cache.__json__()
2147
2152
2148 def is_outdated(new_cs_cache):
2153 def is_outdated(new_cs_cache):
2149 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2154 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2150 new_cs_cache['revision'] != self.changeset_cache['revision']):
2155 new_cs_cache['revision'] != self.changeset_cache['revision']):
2151 return True
2156 return True
2152 return False
2157 return False
2153
2158
2154 # check if we have maybe already latest cached revision
2159 # check if we have maybe already latest cached revision
2155 if is_outdated(cs_cache) or not self.changeset_cache:
2160 if is_outdated(cs_cache) or not self.changeset_cache:
2156 _default = datetime.datetime.fromtimestamp(0)
2161 _default = datetime.datetime.fromtimestamp(0)
2157 last_change = cs_cache.get('date') or _default
2162 last_change = cs_cache.get('date') or _default
2158 log.debug('updated repo %s with new cs cache %s',
2163 log.debug('updated repo %s with new cs cache %s',
2159 self.repo_name, cs_cache)
2164 self.repo_name, cs_cache)
2160 self.updated_on = last_change
2165 self.updated_on = last_change
2161 self.changeset_cache = cs_cache
2166 self.changeset_cache = cs_cache
2162 Session().add(self)
2167 Session().add(self)
2163 Session().commit()
2168 Session().commit()
2164 else:
2169 else:
2165 log.debug('Skipping update_commit_cache for repo:`%s` '
2170 log.debug('Skipping update_commit_cache for repo:`%s` '
2166 'commit already with latest changes', self.repo_name)
2171 'commit already with latest changes', self.repo_name)
2167
2172
2168 @property
2173 @property
2169 def tip(self):
2174 def tip(self):
2170 return self.get_commit('tip')
2175 return self.get_commit('tip')
2171
2176
2172 @property
2177 @property
2173 def author(self):
2178 def author(self):
2174 return self.tip.author
2179 return self.tip.author
2175
2180
2176 @property
2181 @property
2177 def last_change(self):
2182 def last_change(self):
2178 return self.scm_instance().last_change
2183 return self.scm_instance().last_change
2179
2184
2180 def get_comments(self, revisions=None):
2185 def get_comments(self, revisions=None):
2181 """
2186 """
2182 Returns comments for this repository grouped by revisions
2187 Returns comments for this repository grouped by revisions
2183
2188
2184 :param revisions: filter query by revisions only
2189 :param revisions: filter query by revisions only
2185 """
2190 """
2186 cmts = ChangesetComment.query()\
2191 cmts = ChangesetComment.query()\
2187 .filter(ChangesetComment.repo == self)
2192 .filter(ChangesetComment.repo == self)
2188 if revisions:
2193 if revisions:
2189 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2194 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2190 grouped = collections.defaultdict(list)
2195 grouped = collections.defaultdict(list)
2191 for cmt in cmts.all():
2196 for cmt in cmts.all():
2192 grouped[cmt.revision].append(cmt)
2197 grouped[cmt.revision].append(cmt)
2193 return grouped
2198 return grouped
2194
2199
2195 def statuses(self, revisions=None):
2200 def statuses(self, revisions=None):
2196 """
2201 """
2197 Returns statuses for this repository
2202 Returns statuses for this repository
2198
2203
2199 :param revisions: list of revisions to get statuses for
2204 :param revisions: list of revisions to get statuses for
2200 """
2205 """
2201 statuses = ChangesetStatus.query()\
2206 statuses = ChangesetStatus.query()\
2202 .filter(ChangesetStatus.repo == self)\
2207 .filter(ChangesetStatus.repo == self)\
2203 .filter(ChangesetStatus.version == 0)
2208 .filter(ChangesetStatus.version == 0)
2204
2209
2205 if revisions:
2210 if revisions:
2206 # Try doing the filtering in chunks to avoid hitting limits
2211 # Try doing the filtering in chunks to avoid hitting limits
2207 size = 500
2212 size = 500
2208 status_results = []
2213 status_results = []
2209 for chunk in xrange(0, len(revisions), size):
2214 for chunk in xrange(0, len(revisions), size):
2210 status_results += statuses.filter(
2215 status_results += statuses.filter(
2211 ChangesetStatus.revision.in_(
2216 ChangesetStatus.revision.in_(
2212 revisions[chunk: chunk+size])
2217 revisions[chunk: chunk+size])
2213 ).all()
2218 ).all()
2214 else:
2219 else:
2215 status_results = statuses.all()
2220 status_results = statuses.all()
2216
2221
2217 grouped = {}
2222 grouped = {}
2218
2223
2219 # maybe we have open new pullrequest without a status?
2224 # maybe we have open new pullrequest without a status?
2220 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2225 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2221 status_lbl = ChangesetStatus.get_status_lbl(stat)
2226 status_lbl = ChangesetStatus.get_status_lbl(stat)
2222 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2227 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2223 for rev in pr.revisions:
2228 for rev in pr.revisions:
2224 pr_id = pr.pull_request_id
2229 pr_id = pr.pull_request_id
2225 pr_repo = pr.target_repo.repo_name
2230 pr_repo = pr.target_repo.repo_name
2226 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2231 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2227
2232
2228 for stat in status_results:
2233 for stat in status_results:
2229 pr_id = pr_repo = None
2234 pr_id = pr_repo = None
2230 if stat.pull_request:
2235 if stat.pull_request:
2231 pr_id = stat.pull_request.pull_request_id
2236 pr_id = stat.pull_request.pull_request_id
2232 pr_repo = stat.pull_request.target_repo.repo_name
2237 pr_repo = stat.pull_request.target_repo.repo_name
2233 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2238 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2234 pr_id, pr_repo]
2239 pr_id, pr_repo]
2235 return grouped
2240 return grouped
2236
2241
2237 # ==========================================================================
2242 # ==========================================================================
2238 # SCM CACHE INSTANCE
2243 # SCM CACHE INSTANCE
2239 # ==========================================================================
2244 # ==========================================================================
2240
2245
2241 def scm_instance(self, **kwargs):
2246 def scm_instance(self, **kwargs):
2242 import rhodecode
2247 import rhodecode
2243
2248
2244 # Passing a config will not hit the cache currently only used
2249 # Passing a config will not hit the cache currently only used
2245 # for repo2dbmapper
2250 # for repo2dbmapper
2246 config = kwargs.pop('config', None)
2251 config = kwargs.pop('config', None)
2247 cache = kwargs.pop('cache', None)
2252 cache = kwargs.pop('cache', None)
2248 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2253 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2249 # if cache is NOT defined use default global, else we have a full
2254 # if cache is NOT defined use default global, else we have a full
2250 # control over cache behaviour
2255 # control over cache behaviour
2251 if cache is None and full_cache and not config:
2256 if cache is None and full_cache and not config:
2252 return self._get_instance_cached()
2257 return self._get_instance_cached()
2253 return self._get_instance(cache=bool(cache), config=config)
2258 return self._get_instance(cache=bool(cache), config=config)
2254
2259
2255 def _get_instance_cached(self):
2260 def _get_instance_cached(self):
2256 @cache_region('long_term')
2261 @cache_region('long_term')
2257 def _get_repo(cache_key):
2262 def _get_repo(cache_key):
2258 return self._get_instance()
2263 return self._get_instance()
2259
2264
2260 invalidator_context = CacheKey.repo_context_cache(
2265 invalidator_context = CacheKey.repo_context_cache(
2261 _get_repo, self.repo_name, None, thread_scoped=True)
2266 _get_repo, self.repo_name, None, thread_scoped=True)
2262
2267
2263 with invalidator_context as context:
2268 with invalidator_context as context:
2264 context.invalidate()
2269 context.invalidate()
2265 repo = context.compute()
2270 repo = context.compute()
2266
2271
2267 return repo
2272 return repo
2268
2273
2269 def _get_instance(self, cache=True, config=None):
2274 def _get_instance(self, cache=True, config=None):
2270 config = config or self._config
2275 config = config or self._config
2271 custom_wire = {
2276 custom_wire = {
2272 'cache': cache # controls the vcs.remote cache
2277 'cache': cache # controls the vcs.remote cache
2273 }
2278 }
2274 repo = get_vcs_instance(
2279 repo = get_vcs_instance(
2275 repo_path=safe_str(self.repo_full_path),
2280 repo_path=safe_str(self.repo_full_path),
2276 config=config,
2281 config=config,
2277 with_wire=custom_wire,
2282 with_wire=custom_wire,
2278 create=False,
2283 create=False,
2279 _vcs_alias=self.repo_type)
2284 _vcs_alias=self.repo_type)
2280
2285
2281 return repo
2286 return repo
2282
2287
2283 def __json__(self):
2288 def __json__(self):
2284 return {'landing_rev': self.landing_rev}
2289 return {'landing_rev': self.landing_rev}
2285
2290
2286 def get_dict(self):
2291 def get_dict(self):
2287
2292
2288 # Since we transformed `repo_name` to a hybrid property, we need to
2293 # Since we transformed `repo_name` to a hybrid property, we need to
2289 # keep compatibility with the code which uses `repo_name` field.
2294 # keep compatibility with the code which uses `repo_name` field.
2290
2295
2291 result = super(Repository, self).get_dict()
2296 result = super(Repository, self).get_dict()
2292 result['repo_name'] = result.pop('_repo_name', None)
2297 result['repo_name'] = result.pop('_repo_name', None)
2293 return result
2298 return result
2294
2299
2295
2300
2296 class RepoGroup(Base, BaseModel):
2301 class RepoGroup(Base, BaseModel):
2297 __tablename__ = 'groups'
2302 __tablename__ = 'groups'
2298 __table_args__ = (
2303 __table_args__ = (
2299 UniqueConstraint('group_name', 'group_parent_id'),
2304 UniqueConstraint('group_name', 'group_parent_id'),
2300 CheckConstraint('group_id != group_parent_id'),
2305 CheckConstraint('group_id != group_parent_id'),
2301 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2302 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2307 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2303 )
2308 )
2304 __mapper_args__ = {'order_by': 'group_name'}
2309 __mapper_args__ = {'order_by': 'group_name'}
2305
2310
2306 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2311 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2307
2312
2308 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2313 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2309 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2314 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2310 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2315 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2311 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2316 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2312 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2317 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2313 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2314 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2319 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2315 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2320 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2316 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2321 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2317
2322
2318 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2323 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2319 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2324 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2320 parent_group = relationship('RepoGroup', remote_side=group_id)
2325 parent_group = relationship('RepoGroup', remote_side=group_id)
2321 user = relationship('User')
2326 user = relationship('User')
2322 integrations = relationship('Integration',
2327 integrations = relationship('Integration',
2323 cascade="all, delete, delete-orphan")
2328 cascade="all, delete, delete-orphan")
2324
2329
2325 def __init__(self, group_name='', parent_group=None):
2330 def __init__(self, group_name='', parent_group=None):
2326 self.group_name = group_name
2331 self.group_name = group_name
2327 self.parent_group = parent_group
2332 self.parent_group = parent_group
2328
2333
2329 def __unicode__(self):
2334 def __unicode__(self):
2330 return u"<%s('id:%s:%s')>" % (
2335 return u"<%s('id:%s:%s')>" % (
2331 self.__class__.__name__, self.group_id, self.group_name)
2336 self.__class__.__name__, self.group_id, self.group_name)
2332
2337
2333 @hybrid_property
2338 @hybrid_property
2334 def description_safe(self):
2339 def description_safe(self):
2335 from rhodecode.lib import helpers as h
2340 from rhodecode.lib import helpers as h
2336 return h.escape(self.group_description)
2341 return h.escape(self.group_description)
2337
2342
2338 @classmethod
2343 @classmethod
2339 def _generate_choice(cls, repo_group):
2344 def _generate_choice(cls, repo_group):
2340 from webhelpers.html import literal as _literal
2345 from webhelpers.html import literal as _literal
2341 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2346 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2342 return repo_group.group_id, _name(repo_group.full_path_splitted)
2347 return repo_group.group_id, _name(repo_group.full_path_splitted)
2343
2348
2344 @classmethod
2349 @classmethod
2345 def groups_choices(cls, groups=None, show_empty_group=True):
2350 def groups_choices(cls, groups=None, show_empty_group=True):
2346 if not groups:
2351 if not groups:
2347 groups = cls.query().all()
2352 groups = cls.query().all()
2348
2353
2349 repo_groups = []
2354 repo_groups = []
2350 if show_empty_group:
2355 if show_empty_group:
2351 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2356 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2352
2357
2353 repo_groups.extend([cls._generate_choice(x) for x in groups])
2358 repo_groups.extend([cls._generate_choice(x) for x in groups])
2354
2359
2355 repo_groups = sorted(
2360 repo_groups = sorted(
2356 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2361 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2357 return repo_groups
2362 return repo_groups
2358
2363
2359 @classmethod
2364 @classmethod
2360 def url_sep(cls):
2365 def url_sep(cls):
2361 return URL_SEP
2366 return URL_SEP
2362
2367
2363 @classmethod
2368 @classmethod
2364 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2365 if case_insensitive:
2370 if case_insensitive:
2366 gr = cls.query().filter(func.lower(cls.group_name)
2371 gr = cls.query().filter(func.lower(cls.group_name)
2367 == func.lower(group_name))
2372 == func.lower(group_name))
2368 else:
2373 else:
2369 gr = cls.query().filter(cls.group_name == group_name)
2374 gr = cls.query().filter(cls.group_name == group_name)
2370 if cache:
2375 if cache:
2371 name_key = _hash_key(group_name)
2376 name_key = _hash_key(group_name)
2372 gr = gr.options(
2377 gr = gr.options(
2373 FromCache("sql_cache_short", "get_group_%s" % name_key))
2378 FromCache("sql_cache_short", "get_group_%s" % name_key))
2374 return gr.scalar()
2379 return gr.scalar()
2375
2380
2376 @classmethod
2381 @classmethod
2377 def get_user_personal_repo_group(cls, user_id):
2382 def get_user_personal_repo_group(cls, user_id):
2378 user = User.get(user_id)
2383 user = User.get(user_id)
2379 if user.username == User.DEFAULT_USER:
2384 if user.username == User.DEFAULT_USER:
2380 return None
2385 return None
2381
2386
2382 return cls.query()\
2387 return cls.query()\
2383 .filter(cls.personal == true()) \
2388 .filter(cls.personal == true()) \
2384 .filter(cls.user == user).scalar()
2389 .filter(cls.user == user).scalar()
2385
2390
2386 @classmethod
2391 @classmethod
2387 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2392 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2388 case_insensitive=True):
2393 case_insensitive=True):
2389 q = RepoGroup.query()
2394 q = RepoGroup.query()
2390
2395
2391 if not isinstance(user_id, Optional):
2396 if not isinstance(user_id, Optional):
2392 q = q.filter(RepoGroup.user_id == user_id)
2397 q = q.filter(RepoGroup.user_id == user_id)
2393
2398
2394 if not isinstance(group_id, Optional):
2399 if not isinstance(group_id, Optional):
2395 q = q.filter(RepoGroup.group_parent_id == group_id)
2400 q = q.filter(RepoGroup.group_parent_id == group_id)
2396
2401
2397 if case_insensitive:
2402 if case_insensitive:
2398 q = q.order_by(func.lower(RepoGroup.group_name))
2403 q = q.order_by(func.lower(RepoGroup.group_name))
2399 else:
2404 else:
2400 q = q.order_by(RepoGroup.group_name)
2405 q = q.order_by(RepoGroup.group_name)
2401 return q.all()
2406 return q.all()
2402
2407
2403 @property
2408 @property
2404 def parents(self):
2409 def parents(self):
2405 parents_recursion_limit = 10
2410 parents_recursion_limit = 10
2406 groups = []
2411 groups = []
2407 if self.parent_group is None:
2412 if self.parent_group is None:
2408 return groups
2413 return groups
2409 cur_gr = self.parent_group
2414 cur_gr = self.parent_group
2410 groups.insert(0, cur_gr)
2415 groups.insert(0, cur_gr)
2411 cnt = 0
2416 cnt = 0
2412 while 1:
2417 while 1:
2413 cnt += 1
2418 cnt += 1
2414 gr = getattr(cur_gr, 'parent_group', None)
2419 gr = getattr(cur_gr, 'parent_group', None)
2415 cur_gr = cur_gr.parent_group
2420 cur_gr = cur_gr.parent_group
2416 if gr is None:
2421 if gr is None:
2417 break
2422 break
2418 if cnt == parents_recursion_limit:
2423 if cnt == parents_recursion_limit:
2419 # this will prevent accidental infinit loops
2424 # this will prevent accidental infinit loops
2420 log.error(('more than %s parents found for group %s, stopping '
2425 log.error(('more than %s parents found for group %s, stopping '
2421 'recursive parent fetching' % (parents_recursion_limit, self)))
2426 'recursive parent fetching' % (parents_recursion_limit, self)))
2422 break
2427 break
2423
2428
2424 groups.insert(0, gr)
2429 groups.insert(0, gr)
2425 return groups
2430 return groups
2426
2431
2427 @property
2432 @property
2428 def last_db_change(self):
2433 def last_db_change(self):
2429 return self.updated_on
2434 return self.updated_on
2430
2435
2431 @property
2436 @property
2432 def children(self):
2437 def children(self):
2433 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2438 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2434
2439
2435 @property
2440 @property
2436 def name(self):
2441 def name(self):
2437 return self.group_name.split(RepoGroup.url_sep())[-1]
2442 return self.group_name.split(RepoGroup.url_sep())[-1]
2438
2443
2439 @property
2444 @property
2440 def full_path(self):
2445 def full_path(self):
2441 return self.group_name
2446 return self.group_name
2442
2447
2443 @property
2448 @property
2444 def full_path_splitted(self):
2449 def full_path_splitted(self):
2445 return self.group_name.split(RepoGroup.url_sep())
2450 return self.group_name.split(RepoGroup.url_sep())
2446
2451
2447 @property
2452 @property
2448 def repositories(self):
2453 def repositories(self):
2449 return Repository.query()\
2454 return Repository.query()\
2450 .filter(Repository.group == self)\
2455 .filter(Repository.group == self)\
2451 .order_by(Repository.repo_name)
2456 .order_by(Repository.repo_name)
2452
2457
2453 @property
2458 @property
2454 def repositories_recursive_count(self):
2459 def repositories_recursive_count(self):
2455 cnt = self.repositories.count()
2460 cnt = self.repositories.count()
2456
2461
2457 def children_count(group):
2462 def children_count(group):
2458 cnt = 0
2463 cnt = 0
2459 for child in group.children:
2464 for child in group.children:
2460 cnt += child.repositories.count()
2465 cnt += child.repositories.count()
2461 cnt += children_count(child)
2466 cnt += children_count(child)
2462 return cnt
2467 return cnt
2463
2468
2464 return cnt + children_count(self)
2469 return cnt + children_count(self)
2465
2470
2466 def _recursive_objects(self, include_repos=True):
2471 def _recursive_objects(self, include_repos=True):
2467 all_ = []
2472 all_ = []
2468
2473
2469 def _get_members(root_gr):
2474 def _get_members(root_gr):
2470 if include_repos:
2475 if include_repos:
2471 for r in root_gr.repositories:
2476 for r in root_gr.repositories:
2472 all_.append(r)
2477 all_.append(r)
2473 childs = root_gr.children.all()
2478 childs = root_gr.children.all()
2474 if childs:
2479 if childs:
2475 for gr in childs:
2480 for gr in childs:
2476 all_.append(gr)
2481 all_.append(gr)
2477 _get_members(gr)
2482 _get_members(gr)
2478
2483
2479 _get_members(self)
2484 _get_members(self)
2480 return [self] + all_
2485 return [self] + all_
2481
2486
2482 def recursive_groups_and_repos(self):
2487 def recursive_groups_and_repos(self):
2483 """
2488 """
2484 Recursive return all groups, with repositories in those groups
2489 Recursive return all groups, with repositories in those groups
2485 """
2490 """
2486 return self._recursive_objects()
2491 return self._recursive_objects()
2487
2492
2488 def recursive_groups(self):
2493 def recursive_groups(self):
2489 """
2494 """
2490 Returns all children groups for this group including children of children
2495 Returns all children groups for this group including children of children
2491 """
2496 """
2492 return self._recursive_objects(include_repos=False)
2497 return self._recursive_objects(include_repos=False)
2493
2498
2494 def get_new_name(self, group_name):
2499 def get_new_name(self, group_name):
2495 """
2500 """
2496 returns new full group name based on parent and new name
2501 returns new full group name based on parent and new name
2497
2502
2498 :param group_name:
2503 :param group_name:
2499 """
2504 """
2500 path_prefix = (self.parent_group.full_path_splitted if
2505 path_prefix = (self.parent_group.full_path_splitted if
2501 self.parent_group else [])
2506 self.parent_group else [])
2502 return RepoGroup.url_sep().join(path_prefix + [group_name])
2507 return RepoGroup.url_sep().join(path_prefix + [group_name])
2503
2508
2504 def permissions(self, with_admins=True, with_owner=True):
2509 def permissions(self, with_admins=True, with_owner=True):
2505 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2510 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2506 q = q.options(joinedload(UserRepoGroupToPerm.group),
2511 q = q.options(joinedload(UserRepoGroupToPerm.group),
2507 joinedload(UserRepoGroupToPerm.user),
2512 joinedload(UserRepoGroupToPerm.user),
2508 joinedload(UserRepoGroupToPerm.permission),)
2513 joinedload(UserRepoGroupToPerm.permission),)
2509
2514
2510 # get owners and admins and permissions. We do a trick of re-writing
2515 # get owners and admins and permissions. We do a trick of re-writing
2511 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2516 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2512 # has a global reference and changing one object propagates to all
2517 # has a global reference and changing one object propagates to all
2513 # others. This means if admin is also an owner admin_row that change
2518 # others. This means if admin is also an owner admin_row that change
2514 # would propagate to both objects
2519 # would propagate to both objects
2515 perm_rows = []
2520 perm_rows = []
2516 for _usr in q.all():
2521 for _usr in q.all():
2517 usr = AttributeDict(_usr.user.get_dict())
2522 usr = AttributeDict(_usr.user.get_dict())
2518 usr.permission = _usr.permission.permission_name
2523 usr.permission = _usr.permission.permission_name
2519 perm_rows.append(usr)
2524 perm_rows.append(usr)
2520
2525
2521 # filter the perm rows by 'default' first and then sort them by
2526 # filter the perm rows by 'default' first and then sort them by
2522 # admin,write,read,none permissions sorted again alphabetically in
2527 # admin,write,read,none permissions sorted again alphabetically in
2523 # each group
2528 # each group
2524 perm_rows = sorted(perm_rows, key=display_user_sort)
2529 perm_rows = sorted(perm_rows, key=display_user_sort)
2525
2530
2526 _admin_perm = 'group.admin'
2531 _admin_perm = 'group.admin'
2527 owner_row = []
2532 owner_row = []
2528 if with_owner:
2533 if with_owner:
2529 usr = AttributeDict(self.user.get_dict())
2534 usr = AttributeDict(self.user.get_dict())
2530 usr.owner_row = True
2535 usr.owner_row = True
2531 usr.permission = _admin_perm
2536 usr.permission = _admin_perm
2532 owner_row.append(usr)
2537 owner_row.append(usr)
2533
2538
2534 super_admin_rows = []
2539 super_admin_rows = []
2535 if with_admins:
2540 if with_admins:
2536 for usr in User.get_all_super_admins():
2541 for usr in User.get_all_super_admins():
2537 # if this admin is also owner, don't double the record
2542 # if this admin is also owner, don't double the record
2538 if usr.user_id == owner_row[0].user_id:
2543 if usr.user_id == owner_row[0].user_id:
2539 owner_row[0].admin_row = True
2544 owner_row[0].admin_row = True
2540 else:
2545 else:
2541 usr = AttributeDict(usr.get_dict())
2546 usr = AttributeDict(usr.get_dict())
2542 usr.admin_row = True
2547 usr.admin_row = True
2543 usr.permission = _admin_perm
2548 usr.permission = _admin_perm
2544 super_admin_rows.append(usr)
2549 super_admin_rows.append(usr)
2545
2550
2546 return super_admin_rows + owner_row + perm_rows
2551 return super_admin_rows + owner_row + perm_rows
2547
2552
2548 def permission_user_groups(self):
2553 def permission_user_groups(self):
2549 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2554 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2550 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2555 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2551 joinedload(UserGroupRepoGroupToPerm.users_group),
2556 joinedload(UserGroupRepoGroupToPerm.users_group),
2552 joinedload(UserGroupRepoGroupToPerm.permission),)
2557 joinedload(UserGroupRepoGroupToPerm.permission),)
2553
2558
2554 perm_rows = []
2559 perm_rows = []
2555 for _user_group in q.all():
2560 for _user_group in q.all():
2556 usr = AttributeDict(_user_group.users_group.get_dict())
2561 usr = AttributeDict(_user_group.users_group.get_dict())
2557 usr.permission = _user_group.permission.permission_name
2562 usr.permission = _user_group.permission.permission_name
2558 perm_rows.append(usr)
2563 perm_rows.append(usr)
2559
2564
2560 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2565 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2561 return perm_rows
2566 return perm_rows
2562
2567
2563 def get_api_data(self):
2568 def get_api_data(self):
2564 """
2569 """
2565 Common function for generating api data
2570 Common function for generating api data
2566
2571
2567 """
2572 """
2568 group = self
2573 group = self
2569 data = {
2574 data = {
2570 'group_id': group.group_id,
2575 'group_id': group.group_id,
2571 'group_name': group.group_name,
2576 'group_name': group.group_name,
2572 'group_description': group.description_safe,
2577 'group_description': group.description_safe,
2573 'parent_group': group.parent_group.group_name if group.parent_group else None,
2578 'parent_group': group.parent_group.group_name if group.parent_group else None,
2574 'repositories': [x.repo_name for x in group.repositories],
2579 'repositories': [x.repo_name for x in group.repositories],
2575 'owner': group.user.username,
2580 'owner': group.user.username,
2576 }
2581 }
2577 return data
2582 return data
2578
2583
2579
2584
2580 class Permission(Base, BaseModel):
2585 class Permission(Base, BaseModel):
2581 __tablename__ = 'permissions'
2586 __tablename__ = 'permissions'
2582 __table_args__ = (
2587 __table_args__ = (
2583 Index('p_perm_name_idx', 'permission_name'),
2588 Index('p_perm_name_idx', 'permission_name'),
2584 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2589 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2585 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2590 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2586 )
2591 )
2587 PERMS = [
2592 PERMS = [
2588 ('hg.admin', _('RhodeCode Super Administrator')),
2593 ('hg.admin', _('RhodeCode Super Administrator')),
2589
2594
2590 ('repository.none', _('Repository no access')),
2595 ('repository.none', _('Repository no access')),
2591 ('repository.read', _('Repository read access')),
2596 ('repository.read', _('Repository read access')),
2592 ('repository.write', _('Repository write access')),
2597 ('repository.write', _('Repository write access')),
2593 ('repository.admin', _('Repository admin access')),
2598 ('repository.admin', _('Repository admin access')),
2594
2599
2595 ('group.none', _('Repository group no access')),
2600 ('group.none', _('Repository group no access')),
2596 ('group.read', _('Repository group read access')),
2601 ('group.read', _('Repository group read access')),
2597 ('group.write', _('Repository group write access')),
2602 ('group.write', _('Repository group write access')),
2598 ('group.admin', _('Repository group admin access')),
2603 ('group.admin', _('Repository group admin access')),
2599
2604
2600 ('usergroup.none', _('User group no access')),
2605 ('usergroup.none', _('User group no access')),
2601 ('usergroup.read', _('User group read access')),
2606 ('usergroup.read', _('User group read access')),
2602 ('usergroup.write', _('User group write access')),
2607 ('usergroup.write', _('User group write access')),
2603 ('usergroup.admin', _('User group admin access')),
2608 ('usergroup.admin', _('User group admin access')),
2604
2609
2605 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2610 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2606 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2611 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2607
2612
2608 ('hg.usergroup.create.false', _('User Group creation disabled')),
2613 ('hg.usergroup.create.false', _('User Group creation disabled')),
2609 ('hg.usergroup.create.true', _('User Group creation enabled')),
2614 ('hg.usergroup.create.true', _('User Group creation enabled')),
2610
2615
2611 ('hg.create.none', _('Repository creation disabled')),
2616 ('hg.create.none', _('Repository creation disabled')),
2612 ('hg.create.repository', _('Repository creation enabled')),
2617 ('hg.create.repository', _('Repository creation enabled')),
2613 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2618 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2614 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2619 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2615
2620
2616 ('hg.fork.none', _('Repository forking disabled')),
2621 ('hg.fork.none', _('Repository forking disabled')),
2617 ('hg.fork.repository', _('Repository forking enabled')),
2622 ('hg.fork.repository', _('Repository forking enabled')),
2618
2623
2619 ('hg.register.none', _('Registration disabled')),
2624 ('hg.register.none', _('Registration disabled')),
2620 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2625 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2621 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2626 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2622
2627
2623 ('hg.password_reset.enabled', _('Password reset enabled')),
2628 ('hg.password_reset.enabled', _('Password reset enabled')),
2624 ('hg.password_reset.hidden', _('Password reset hidden')),
2629 ('hg.password_reset.hidden', _('Password reset hidden')),
2625 ('hg.password_reset.disabled', _('Password reset disabled')),
2630 ('hg.password_reset.disabled', _('Password reset disabled')),
2626
2631
2627 ('hg.extern_activate.manual', _('Manual activation of external account')),
2632 ('hg.extern_activate.manual', _('Manual activation of external account')),
2628 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2633 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2629
2634
2630 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2635 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2631 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2636 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2632 ]
2637 ]
2633
2638
2634 # definition of system default permissions for DEFAULT user
2639 # definition of system default permissions for DEFAULT user
2635 DEFAULT_USER_PERMISSIONS = [
2640 DEFAULT_USER_PERMISSIONS = [
2636 'repository.read',
2641 'repository.read',
2637 'group.read',
2642 'group.read',
2638 'usergroup.read',
2643 'usergroup.read',
2639 'hg.create.repository',
2644 'hg.create.repository',
2640 'hg.repogroup.create.false',
2645 'hg.repogroup.create.false',
2641 'hg.usergroup.create.false',
2646 'hg.usergroup.create.false',
2642 'hg.create.write_on_repogroup.true',
2647 'hg.create.write_on_repogroup.true',
2643 'hg.fork.repository',
2648 'hg.fork.repository',
2644 'hg.register.manual_activate',
2649 'hg.register.manual_activate',
2645 'hg.password_reset.enabled',
2650 'hg.password_reset.enabled',
2646 'hg.extern_activate.auto',
2651 'hg.extern_activate.auto',
2647 'hg.inherit_default_perms.true',
2652 'hg.inherit_default_perms.true',
2648 ]
2653 ]
2649
2654
2650 # defines which permissions are more important higher the more important
2655 # defines which permissions are more important higher the more important
2651 # Weight defines which permissions are more important.
2656 # Weight defines which permissions are more important.
2652 # The higher number the more important.
2657 # The higher number the more important.
2653 PERM_WEIGHTS = {
2658 PERM_WEIGHTS = {
2654 'repository.none': 0,
2659 'repository.none': 0,
2655 'repository.read': 1,
2660 'repository.read': 1,
2656 'repository.write': 3,
2661 'repository.write': 3,
2657 'repository.admin': 4,
2662 'repository.admin': 4,
2658
2663
2659 'group.none': 0,
2664 'group.none': 0,
2660 'group.read': 1,
2665 'group.read': 1,
2661 'group.write': 3,
2666 'group.write': 3,
2662 'group.admin': 4,
2667 'group.admin': 4,
2663
2668
2664 'usergroup.none': 0,
2669 'usergroup.none': 0,
2665 'usergroup.read': 1,
2670 'usergroup.read': 1,
2666 'usergroup.write': 3,
2671 'usergroup.write': 3,
2667 'usergroup.admin': 4,
2672 'usergroup.admin': 4,
2668
2673
2669 'hg.repogroup.create.false': 0,
2674 'hg.repogroup.create.false': 0,
2670 'hg.repogroup.create.true': 1,
2675 'hg.repogroup.create.true': 1,
2671
2676
2672 'hg.usergroup.create.false': 0,
2677 'hg.usergroup.create.false': 0,
2673 'hg.usergroup.create.true': 1,
2678 'hg.usergroup.create.true': 1,
2674
2679
2675 'hg.fork.none': 0,
2680 'hg.fork.none': 0,
2676 'hg.fork.repository': 1,
2681 'hg.fork.repository': 1,
2677 'hg.create.none': 0,
2682 'hg.create.none': 0,
2678 'hg.create.repository': 1
2683 'hg.create.repository': 1
2679 }
2684 }
2680
2685
2681 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2686 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2682 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2687 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2683 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2688 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2684
2689
2685 def __unicode__(self):
2690 def __unicode__(self):
2686 return u"<%s('%s:%s')>" % (
2691 return u"<%s('%s:%s')>" % (
2687 self.__class__.__name__, self.permission_id, self.permission_name
2692 self.__class__.__name__, self.permission_id, self.permission_name
2688 )
2693 )
2689
2694
2690 @classmethod
2695 @classmethod
2691 def get_by_key(cls, key):
2696 def get_by_key(cls, key):
2692 return cls.query().filter(cls.permission_name == key).scalar()
2697 return cls.query().filter(cls.permission_name == key).scalar()
2693
2698
2694 @classmethod
2699 @classmethod
2695 def get_default_repo_perms(cls, user_id, repo_id=None):
2700 def get_default_repo_perms(cls, user_id, repo_id=None):
2696 q = Session().query(UserRepoToPerm, Repository, Permission)\
2701 q = Session().query(UserRepoToPerm, Repository, Permission)\
2697 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2702 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2698 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2703 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2699 .filter(UserRepoToPerm.user_id == user_id)
2704 .filter(UserRepoToPerm.user_id == user_id)
2700 if repo_id:
2705 if repo_id:
2701 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2706 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2702 return q.all()
2707 return q.all()
2703
2708
2704 @classmethod
2709 @classmethod
2705 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2710 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2706 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2711 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2707 .join(
2712 .join(
2708 Permission,
2713 Permission,
2709 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2714 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2710 .join(
2715 .join(
2711 Repository,
2716 Repository,
2712 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2717 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2713 .join(
2718 .join(
2714 UserGroup,
2719 UserGroup,
2715 UserGroupRepoToPerm.users_group_id ==
2720 UserGroupRepoToPerm.users_group_id ==
2716 UserGroup.users_group_id)\
2721 UserGroup.users_group_id)\
2717 .join(
2722 .join(
2718 UserGroupMember,
2723 UserGroupMember,
2719 UserGroupRepoToPerm.users_group_id ==
2724 UserGroupRepoToPerm.users_group_id ==
2720 UserGroupMember.users_group_id)\
2725 UserGroupMember.users_group_id)\
2721 .filter(
2726 .filter(
2722 UserGroupMember.user_id == user_id,
2727 UserGroupMember.user_id == user_id,
2723 UserGroup.users_group_active == true())
2728 UserGroup.users_group_active == true())
2724 if repo_id:
2729 if repo_id:
2725 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2730 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2726 return q.all()
2731 return q.all()
2727
2732
2728 @classmethod
2733 @classmethod
2729 def get_default_group_perms(cls, user_id, repo_group_id=None):
2734 def get_default_group_perms(cls, user_id, repo_group_id=None):
2730 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2735 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2731 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2736 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2732 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2737 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2733 .filter(UserRepoGroupToPerm.user_id == user_id)
2738 .filter(UserRepoGroupToPerm.user_id == user_id)
2734 if repo_group_id:
2739 if repo_group_id:
2735 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2740 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2736 return q.all()
2741 return q.all()
2737
2742
2738 @classmethod
2743 @classmethod
2739 def get_default_group_perms_from_user_group(
2744 def get_default_group_perms_from_user_group(
2740 cls, user_id, repo_group_id=None):
2745 cls, user_id, repo_group_id=None):
2741 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2746 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2742 .join(
2747 .join(
2743 Permission,
2748 Permission,
2744 UserGroupRepoGroupToPerm.permission_id ==
2749 UserGroupRepoGroupToPerm.permission_id ==
2745 Permission.permission_id)\
2750 Permission.permission_id)\
2746 .join(
2751 .join(
2747 RepoGroup,
2752 RepoGroup,
2748 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2753 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2749 .join(
2754 .join(
2750 UserGroup,
2755 UserGroup,
2751 UserGroupRepoGroupToPerm.users_group_id ==
2756 UserGroupRepoGroupToPerm.users_group_id ==
2752 UserGroup.users_group_id)\
2757 UserGroup.users_group_id)\
2753 .join(
2758 .join(
2754 UserGroupMember,
2759 UserGroupMember,
2755 UserGroupRepoGroupToPerm.users_group_id ==
2760 UserGroupRepoGroupToPerm.users_group_id ==
2756 UserGroupMember.users_group_id)\
2761 UserGroupMember.users_group_id)\
2757 .filter(
2762 .filter(
2758 UserGroupMember.user_id == user_id,
2763 UserGroupMember.user_id == user_id,
2759 UserGroup.users_group_active == true())
2764 UserGroup.users_group_active == true())
2760 if repo_group_id:
2765 if repo_group_id:
2761 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2766 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2762 return q.all()
2767 return q.all()
2763
2768
2764 @classmethod
2769 @classmethod
2765 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2770 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2766 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2771 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2767 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2772 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2768 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2773 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2769 .filter(UserUserGroupToPerm.user_id == user_id)
2774 .filter(UserUserGroupToPerm.user_id == user_id)
2770 if user_group_id:
2775 if user_group_id:
2771 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2776 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2772 return q.all()
2777 return q.all()
2773
2778
2774 @classmethod
2779 @classmethod
2775 def get_default_user_group_perms_from_user_group(
2780 def get_default_user_group_perms_from_user_group(
2776 cls, user_id, user_group_id=None):
2781 cls, user_id, user_group_id=None):
2777 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2782 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2778 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2783 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2779 .join(
2784 .join(
2780 Permission,
2785 Permission,
2781 UserGroupUserGroupToPerm.permission_id ==
2786 UserGroupUserGroupToPerm.permission_id ==
2782 Permission.permission_id)\
2787 Permission.permission_id)\
2783 .join(
2788 .join(
2784 TargetUserGroup,
2789 TargetUserGroup,
2785 UserGroupUserGroupToPerm.target_user_group_id ==
2790 UserGroupUserGroupToPerm.target_user_group_id ==
2786 TargetUserGroup.users_group_id)\
2791 TargetUserGroup.users_group_id)\
2787 .join(
2792 .join(
2788 UserGroup,
2793 UserGroup,
2789 UserGroupUserGroupToPerm.user_group_id ==
2794 UserGroupUserGroupToPerm.user_group_id ==
2790 UserGroup.users_group_id)\
2795 UserGroup.users_group_id)\
2791 .join(
2796 .join(
2792 UserGroupMember,
2797 UserGroupMember,
2793 UserGroupUserGroupToPerm.user_group_id ==
2798 UserGroupUserGroupToPerm.user_group_id ==
2794 UserGroupMember.users_group_id)\
2799 UserGroupMember.users_group_id)\
2795 .filter(
2800 .filter(
2796 UserGroupMember.user_id == user_id,
2801 UserGroupMember.user_id == user_id,
2797 UserGroup.users_group_active == true())
2802 UserGroup.users_group_active == true())
2798 if user_group_id:
2803 if user_group_id:
2799 q = q.filter(
2804 q = q.filter(
2800 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2805 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2801
2806
2802 return q.all()
2807 return q.all()
2803
2808
2804
2809
2805 class UserRepoToPerm(Base, BaseModel):
2810 class UserRepoToPerm(Base, BaseModel):
2806 __tablename__ = 'repo_to_perm'
2811 __tablename__ = 'repo_to_perm'
2807 __table_args__ = (
2812 __table_args__ = (
2808 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2813 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2810 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2811 )
2816 )
2812 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2817 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2813 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2814 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2819 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2815 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2820 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2816
2821
2817 user = relationship('User')
2822 user = relationship('User')
2818 repository = relationship('Repository')
2823 repository = relationship('Repository')
2819 permission = relationship('Permission')
2824 permission = relationship('Permission')
2820
2825
2821 @classmethod
2826 @classmethod
2822 def create(cls, user, repository, permission):
2827 def create(cls, user, repository, permission):
2823 n = cls()
2828 n = cls()
2824 n.user = user
2829 n.user = user
2825 n.repository = repository
2830 n.repository = repository
2826 n.permission = permission
2831 n.permission = permission
2827 Session().add(n)
2832 Session().add(n)
2828 return n
2833 return n
2829
2834
2830 def __unicode__(self):
2835 def __unicode__(self):
2831 return u'<%s => %s >' % (self.user, self.repository)
2836 return u'<%s => %s >' % (self.user, self.repository)
2832
2837
2833
2838
2834 class UserUserGroupToPerm(Base, BaseModel):
2839 class UserUserGroupToPerm(Base, BaseModel):
2835 __tablename__ = 'user_user_group_to_perm'
2840 __tablename__ = 'user_user_group_to_perm'
2836 __table_args__ = (
2841 __table_args__ = (
2837 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2842 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2838 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2843 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2839 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2844 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2840 )
2845 )
2841 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2846 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2842 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2843 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2848 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2844 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2849 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2845
2850
2846 user = relationship('User')
2851 user = relationship('User')
2847 user_group = relationship('UserGroup')
2852 user_group = relationship('UserGroup')
2848 permission = relationship('Permission')
2853 permission = relationship('Permission')
2849
2854
2850 @classmethod
2855 @classmethod
2851 def create(cls, user, user_group, permission):
2856 def create(cls, user, user_group, permission):
2852 n = cls()
2857 n = cls()
2853 n.user = user
2858 n.user = user
2854 n.user_group = user_group
2859 n.user_group = user_group
2855 n.permission = permission
2860 n.permission = permission
2856 Session().add(n)
2861 Session().add(n)
2857 return n
2862 return n
2858
2863
2859 def __unicode__(self):
2864 def __unicode__(self):
2860 return u'<%s => %s >' % (self.user, self.user_group)
2865 return u'<%s => %s >' % (self.user, self.user_group)
2861
2866
2862
2867
2863 class UserToPerm(Base, BaseModel):
2868 class UserToPerm(Base, BaseModel):
2864 __tablename__ = 'user_to_perm'
2869 __tablename__ = 'user_to_perm'
2865 __table_args__ = (
2870 __table_args__ = (
2866 UniqueConstraint('user_id', 'permission_id'),
2871 UniqueConstraint('user_id', 'permission_id'),
2867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2868 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2869 )
2874 )
2870 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2875 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2871 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2876 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2872 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2877 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2873
2878
2874 user = relationship('User')
2879 user = relationship('User')
2875 permission = relationship('Permission', lazy='joined')
2880 permission = relationship('Permission', lazy='joined')
2876
2881
2877 def __unicode__(self):
2882 def __unicode__(self):
2878 return u'<%s => %s >' % (self.user, self.permission)
2883 return u'<%s => %s >' % (self.user, self.permission)
2879
2884
2880
2885
2881 class UserGroupRepoToPerm(Base, BaseModel):
2886 class UserGroupRepoToPerm(Base, BaseModel):
2882 __tablename__ = 'users_group_repo_to_perm'
2887 __tablename__ = 'users_group_repo_to_perm'
2883 __table_args__ = (
2888 __table_args__ = (
2884 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2889 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2885 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2886 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2887 )
2892 )
2888 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2893 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2889 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2894 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2890 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2895 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2891 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2896 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2892
2897
2893 users_group = relationship('UserGroup')
2898 users_group = relationship('UserGroup')
2894 permission = relationship('Permission')
2899 permission = relationship('Permission')
2895 repository = relationship('Repository')
2900 repository = relationship('Repository')
2896
2901
2897 @classmethod
2902 @classmethod
2898 def create(cls, users_group, repository, permission):
2903 def create(cls, users_group, repository, permission):
2899 n = cls()
2904 n = cls()
2900 n.users_group = users_group
2905 n.users_group = users_group
2901 n.repository = repository
2906 n.repository = repository
2902 n.permission = permission
2907 n.permission = permission
2903 Session().add(n)
2908 Session().add(n)
2904 return n
2909 return n
2905
2910
2906 def __unicode__(self):
2911 def __unicode__(self):
2907 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2912 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2908
2913
2909
2914
2910 class UserGroupUserGroupToPerm(Base, BaseModel):
2915 class UserGroupUserGroupToPerm(Base, BaseModel):
2911 __tablename__ = 'user_group_user_group_to_perm'
2916 __tablename__ = 'user_group_user_group_to_perm'
2912 __table_args__ = (
2917 __table_args__ = (
2913 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2918 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2914 CheckConstraint('target_user_group_id != user_group_id'),
2919 CheckConstraint('target_user_group_id != user_group_id'),
2915 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2916 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2917 )
2922 )
2918 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)
2923 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)
2919 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2924 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2920 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2925 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2921 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2926 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2922
2927
2923 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2928 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2924 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2929 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2925 permission = relationship('Permission')
2930 permission = relationship('Permission')
2926
2931
2927 @classmethod
2932 @classmethod
2928 def create(cls, target_user_group, user_group, permission):
2933 def create(cls, target_user_group, user_group, permission):
2929 n = cls()
2934 n = cls()
2930 n.target_user_group = target_user_group
2935 n.target_user_group = target_user_group
2931 n.user_group = user_group
2936 n.user_group = user_group
2932 n.permission = permission
2937 n.permission = permission
2933 Session().add(n)
2938 Session().add(n)
2934 return n
2939 return n
2935
2940
2936 def __unicode__(self):
2941 def __unicode__(self):
2937 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2942 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2938
2943
2939
2944
2940 class UserGroupToPerm(Base, BaseModel):
2945 class UserGroupToPerm(Base, BaseModel):
2941 __tablename__ = 'users_group_to_perm'
2946 __tablename__ = 'users_group_to_perm'
2942 __table_args__ = (
2947 __table_args__ = (
2943 UniqueConstraint('users_group_id', 'permission_id',),
2948 UniqueConstraint('users_group_id', 'permission_id',),
2944 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2945 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2946 )
2951 )
2947 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2952 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2948 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2953 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2949 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2954 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2950
2955
2951 users_group = relationship('UserGroup')
2956 users_group = relationship('UserGroup')
2952 permission = relationship('Permission')
2957 permission = relationship('Permission')
2953
2958
2954
2959
2955 class UserRepoGroupToPerm(Base, BaseModel):
2960 class UserRepoGroupToPerm(Base, BaseModel):
2956 __tablename__ = 'user_repo_group_to_perm'
2961 __tablename__ = 'user_repo_group_to_perm'
2957 __table_args__ = (
2962 __table_args__ = (
2958 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2963 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2961 )
2966 )
2962
2967
2963 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2968 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2965 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2970 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2966 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2967
2972
2968 user = relationship('User')
2973 user = relationship('User')
2969 group = relationship('RepoGroup')
2974 group = relationship('RepoGroup')
2970 permission = relationship('Permission')
2975 permission = relationship('Permission')
2971
2976
2972 @classmethod
2977 @classmethod
2973 def create(cls, user, repository_group, permission):
2978 def create(cls, user, repository_group, permission):
2974 n = cls()
2979 n = cls()
2975 n.user = user
2980 n.user = user
2976 n.group = repository_group
2981 n.group = repository_group
2977 n.permission = permission
2982 n.permission = permission
2978 Session().add(n)
2983 Session().add(n)
2979 return n
2984 return n
2980
2985
2981
2986
2982 class UserGroupRepoGroupToPerm(Base, BaseModel):
2987 class UserGroupRepoGroupToPerm(Base, BaseModel):
2983 __tablename__ = 'users_group_repo_group_to_perm'
2988 __tablename__ = 'users_group_repo_group_to_perm'
2984 __table_args__ = (
2989 __table_args__ = (
2985 UniqueConstraint('users_group_id', 'group_id'),
2990 UniqueConstraint('users_group_id', 'group_id'),
2986 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2987 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2988 )
2993 )
2989
2994
2990 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)
2995 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)
2991 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2996 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2992 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2997 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2993 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2998 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2994
2999
2995 users_group = relationship('UserGroup')
3000 users_group = relationship('UserGroup')
2996 permission = relationship('Permission')
3001 permission = relationship('Permission')
2997 group = relationship('RepoGroup')
3002 group = relationship('RepoGroup')
2998
3003
2999 @classmethod
3004 @classmethod
3000 def create(cls, user_group, repository_group, permission):
3005 def create(cls, user_group, repository_group, permission):
3001 n = cls()
3006 n = cls()
3002 n.users_group = user_group
3007 n.users_group = user_group
3003 n.group = repository_group
3008 n.group = repository_group
3004 n.permission = permission
3009 n.permission = permission
3005 Session().add(n)
3010 Session().add(n)
3006 return n
3011 return n
3007
3012
3008 def __unicode__(self):
3013 def __unicode__(self):
3009 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3014 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3010
3015
3011
3016
3012 class Statistics(Base, BaseModel):
3017 class Statistics(Base, BaseModel):
3013 __tablename__ = 'statistics'
3018 __tablename__ = 'statistics'
3014 __table_args__ = (
3019 __table_args__ = (
3015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3016 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3021 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3017 )
3022 )
3018 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3023 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3019 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3024 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3020 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3025 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3021 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3026 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3022 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3027 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3023 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3028 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3024
3029
3025 repository = relationship('Repository', single_parent=True)
3030 repository = relationship('Repository', single_parent=True)
3026
3031
3027
3032
3028 class UserFollowing(Base, BaseModel):
3033 class UserFollowing(Base, BaseModel):
3029 __tablename__ = 'user_followings'
3034 __tablename__ = 'user_followings'
3030 __table_args__ = (
3035 __table_args__ = (
3031 UniqueConstraint('user_id', 'follows_repository_id'),
3036 UniqueConstraint('user_id', 'follows_repository_id'),
3032 UniqueConstraint('user_id', 'follows_user_id'),
3037 UniqueConstraint('user_id', 'follows_user_id'),
3033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3035 )
3040 )
3036
3041
3037 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3042 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3038 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3043 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3039 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3044 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3040 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3045 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3041 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3046 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3042
3047
3043 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3048 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3044
3049
3045 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3050 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3046 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3051 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3047
3052
3048 @classmethod
3053 @classmethod
3049 def get_repo_followers(cls, repo_id):
3054 def get_repo_followers(cls, repo_id):
3050 return cls.query().filter(cls.follows_repo_id == repo_id)
3055 return cls.query().filter(cls.follows_repo_id == repo_id)
3051
3056
3052
3057
3053 class CacheKey(Base, BaseModel):
3058 class CacheKey(Base, BaseModel):
3054 __tablename__ = 'cache_invalidation'
3059 __tablename__ = 'cache_invalidation'
3055 __table_args__ = (
3060 __table_args__ = (
3056 UniqueConstraint('cache_key'),
3061 UniqueConstraint('cache_key'),
3057 Index('key_idx', 'cache_key'),
3062 Index('key_idx', 'cache_key'),
3058 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3059 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3064 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3060 )
3065 )
3061 CACHE_TYPE_ATOM = 'ATOM'
3066 CACHE_TYPE_ATOM = 'ATOM'
3062 CACHE_TYPE_RSS = 'RSS'
3067 CACHE_TYPE_RSS = 'RSS'
3063 CACHE_TYPE_README = 'README'
3068 CACHE_TYPE_README = 'README'
3064
3069
3065 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3070 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3066 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3071 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3067 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3072 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3068 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3073 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3069
3074
3070 def __init__(self, cache_key, cache_args=''):
3075 def __init__(self, cache_key, cache_args=''):
3071 self.cache_key = cache_key
3076 self.cache_key = cache_key
3072 self.cache_args = cache_args
3077 self.cache_args = cache_args
3073 self.cache_active = False
3078 self.cache_active = False
3074
3079
3075 def __unicode__(self):
3080 def __unicode__(self):
3076 return u"<%s('%s:%s[%s]')>" % (
3081 return u"<%s('%s:%s[%s]')>" % (
3077 self.__class__.__name__,
3082 self.__class__.__name__,
3078 self.cache_id, self.cache_key, self.cache_active)
3083 self.cache_id, self.cache_key, self.cache_active)
3079
3084
3080 def _cache_key_partition(self):
3085 def _cache_key_partition(self):
3081 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3086 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3082 return prefix, repo_name, suffix
3087 return prefix, repo_name, suffix
3083
3088
3084 def get_prefix(self):
3089 def get_prefix(self):
3085 """
3090 """
3086 Try to extract prefix from existing cache key. The key could consist
3091 Try to extract prefix from existing cache key. The key could consist
3087 of prefix, repo_name, suffix
3092 of prefix, repo_name, suffix
3088 """
3093 """
3089 # this returns prefix, repo_name, suffix
3094 # this returns prefix, repo_name, suffix
3090 return self._cache_key_partition()[0]
3095 return self._cache_key_partition()[0]
3091
3096
3092 def get_suffix(self):
3097 def get_suffix(self):
3093 """
3098 """
3094 get suffix that might have been used in _get_cache_key to
3099 get suffix that might have been used in _get_cache_key to
3095 generate self.cache_key. Only used for informational purposes
3100 generate self.cache_key. Only used for informational purposes
3096 in repo_edit.mako.
3101 in repo_edit.mako.
3097 """
3102 """
3098 # prefix, repo_name, suffix
3103 # prefix, repo_name, suffix
3099 return self._cache_key_partition()[2]
3104 return self._cache_key_partition()[2]
3100
3105
3101 @classmethod
3106 @classmethod
3102 def delete_all_cache(cls):
3107 def delete_all_cache(cls):
3103 """
3108 """
3104 Delete all cache keys from database.
3109 Delete all cache keys from database.
3105 Should only be run when all instances are down and all entries
3110 Should only be run when all instances are down and all entries
3106 thus stale.
3111 thus stale.
3107 """
3112 """
3108 cls.query().delete()
3113 cls.query().delete()
3109 Session().commit()
3114 Session().commit()
3110
3115
3111 @classmethod
3116 @classmethod
3112 def get_cache_key(cls, repo_name, cache_type):
3117 def get_cache_key(cls, repo_name, cache_type):
3113 """
3118 """
3114
3119
3115 Generate a cache key for this process of RhodeCode instance.
3120 Generate a cache key for this process of RhodeCode instance.
3116 Prefix most likely will be process id or maybe explicitly set
3121 Prefix most likely will be process id or maybe explicitly set
3117 instance_id from .ini file.
3122 instance_id from .ini file.
3118 """
3123 """
3119 import rhodecode
3124 import rhodecode
3120 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3125 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3121
3126
3122 repo_as_unicode = safe_unicode(repo_name)
3127 repo_as_unicode = safe_unicode(repo_name)
3123 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3128 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3124 if cache_type else repo_as_unicode
3129 if cache_type else repo_as_unicode
3125
3130
3126 return u'{}{}'.format(prefix, key)
3131 return u'{}{}'.format(prefix, key)
3127
3132
3128 @classmethod
3133 @classmethod
3129 def set_invalidate(cls, repo_name, delete=False):
3134 def set_invalidate(cls, repo_name, delete=False):
3130 """
3135 """
3131 Mark all caches of a repo as invalid in the database.
3136 Mark all caches of a repo as invalid in the database.
3132 """
3137 """
3133
3138
3134 try:
3139 try:
3135 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3140 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3136 if delete:
3141 if delete:
3137 log.debug('cache objects deleted for repo %s',
3142 log.debug('cache objects deleted for repo %s',
3138 safe_str(repo_name))
3143 safe_str(repo_name))
3139 qry.delete()
3144 qry.delete()
3140 else:
3145 else:
3141 log.debug('cache objects marked as invalid for repo %s',
3146 log.debug('cache objects marked as invalid for repo %s',
3142 safe_str(repo_name))
3147 safe_str(repo_name))
3143 qry.update({"cache_active": False})
3148 qry.update({"cache_active": False})
3144
3149
3145 Session().commit()
3150 Session().commit()
3146 except Exception:
3151 except Exception:
3147 log.exception(
3152 log.exception(
3148 'Cache key invalidation failed for repository %s',
3153 'Cache key invalidation failed for repository %s',
3149 safe_str(repo_name))
3154 safe_str(repo_name))
3150 Session().rollback()
3155 Session().rollback()
3151
3156
3152 @classmethod
3157 @classmethod
3153 def get_active_cache(cls, cache_key):
3158 def get_active_cache(cls, cache_key):
3154 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3159 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3155 if inv_obj:
3160 if inv_obj:
3156 return inv_obj
3161 return inv_obj
3157 return None
3162 return None
3158
3163
3159 @classmethod
3164 @classmethod
3160 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3165 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3161 thread_scoped=False):
3166 thread_scoped=False):
3162 """
3167 """
3163 @cache_region('long_term')
3168 @cache_region('long_term')
3164 def _heavy_calculation(cache_key):
3169 def _heavy_calculation(cache_key):
3165 return 'result'
3170 return 'result'
3166
3171
3167 cache_context = CacheKey.repo_context_cache(
3172 cache_context = CacheKey.repo_context_cache(
3168 _heavy_calculation, repo_name, cache_type)
3173 _heavy_calculation, repo_name, cache_type)
3169
3174
3170 with cache_context as context:
3175 with cache_context as context:
3171 context.invalidate()
3176 context.invalidate()
3172 computed = context.compute()
3177 computed = context.compute()
3173
3178
3174 assert computed == 'result'
3179 assert computed == 'result'
3175 """
3180 """
3176 from rhodecode.lib import caches
3181 from rhodecode.lib import caches
3177 return caches.InvalidationContext(
3182 return caches.InvalidationContext(
3178 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3183 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3179
3184
3180
3185
3181 class ChangesetComment(Base, BaseModel):
3186 class ChangesetComment(Base, BaseModel):
3182 __tablename__ = 'changeset_comments'
3187 __tablename__ = 'changeset_comments'
3183 __table_args__ = (
3188 __table_args__ = (
3184 Index('cc_revision_idx', 'revision'),
3189 Index('cc_revision_idx', 'revision'),
3185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3187 )
3192 )
3188
3193
3189 COMMENT_OUTDATED = u'comment_outdated'
3194 COMMENT_OUTDATED = u'comment_outdated'
3190 COMMENT_TYPE_NOTE = u'note'
3195 COMMENT_TYPE_NOTE = u'note'
3191 COMMENT_TYPE_TODO = u'todo'
3196 COMMENT_TYPE_TODO = u'todo'
3192 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3197 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3193
3198
3194 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3199 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3195 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3200 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3196 revision = Column('revision', String(40), nullable=True)
3201 revision = Column('revision', String(40), nullable=True)
3197 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3202 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3198 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3203 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3199 line_no = Column('line_no', Unicode(10), nullable=True)
3204 line_no = Column('line_no', Unicode(10), nullable=True)
3200 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3205 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3201 f_path = Column('f_path', Unicode(1000), nullable=True)
3206 f_path = Column('f_path', Unicode(1000), nullable=True)
3202 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3207 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3203 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3208 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3204 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3209 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3205 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3210 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3206 renderer = Column('renderer', Unicode(64), nullable=True)
3211 renderer = Column('renderer', Unicode(64), nullable=True)
3207 display_state = Column('display_state', Unicode(128), nullable=True)
3212 display_state = Column('display_state', Unicode(128), nullable=True)
3208
3213
3209 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3214 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3210 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3215 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3211 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3216 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3212 author = relationship('User', lazy='joined')
3217 author = relationship('User', lazy='joined')
3213 repo = relationship('Repository')
3218 repo = relationship('Repository')
3214 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3219 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3215 pull_request = relationship('PullRequest', lazy='joined')
3220 pull_request = relationship('PullRequest', lazy='joined')
3216 pull_request_version = relationship('PullRequestVersion')
3221 pull_request_version = relationship('PullRequestVersion')
3217
3222
3218 @classmethod
3223 @classmethod
3219 def get_users(cls, revision=None, pull_request_id=None):
3224 def get_users(cls, revision=None, pull_request_id=None):
3220 """
3225 """
3221 Returns user associated with this ChangesetComment. ie those
3226 Returns user associated with this ChangesetComment. ie those
3222 who actually commented
3227 who actually commented
3223
3228
3224 :param cls:
3229 :param cls:
3225 :param revision:
3230 :param revision:
3226 """
3231 """
3227 q = Session().query(User)\
3232 q = Session().query(User)\
3228 .join(ChangesetComment.author)
3233 .join(ChangesetComment.author)
3229 if revision:
3234 if revision:
3230 q = q.filter(cls.revision == revision)
3235 q = q.filter(cls.revision == revision)
3231 elif pull_request_id:
3236 elif pull_request_id:
3232 q = q.filter(cls.pull_request_id == pull_request_id)
3237 q = q.filter(cls.pull_request_id == pull_request_id)
3233 return q.all()
3238 return q.all()
3234
3239
3235 @classmethod
3240 @classmethod
3236 def get_index_from_version(cls, pr_version, versions):
3241 def get_index_from_version(cls, pr_version, versions):
3237 num_versions = [x.pull_request_version_id for x in versions]
3242 num_versions = [x.pull_request_version_id for x in versions]
3238 try:
3243 try:
3239 return num_versions.index(pr_version) +1
3244 return num_versions.index(pr_version) +1
3240 except (IndexError, ValueError):
3245 except (IndexError, ValueError):
3241 return
3246 return
3242
3247
3243 @property
3248 @property
3244 def outdated(self):
3249 def outdated(self):
3245 return self.display_state == self.COMMENT_OUTDATED
3250 return self.display_state == self.COMMENT_OUTDATED
3246
3251
3247 def outdated_at_version(self, version):
3252 def outdated_at_version(self, version):
3248 """
3253 """
3249 Checks if comment is outdated for given pull request version
3254 Checks if comment is outdated for given pull request version
3250 """
3255 """
3251 return self.outdated and self.pull_request_version_id != version
3256 return self.outdated and self.pull_request_version_id != version
3252
3257
3253 def older_than_version(self, version):
3258 def older_than_version(self, version):
3254 """
3259 """
3255 Checks if comment is made from previous version than given
3260 Checks if comment is made from previous version than given
3256 """
3261 """
3257 if version is None:
3262 if version is None:
3258 return self.pull_request_version_id is not None
3263 return self.pull_request_version_id is not None
3259
3264
3260 return self.pull_request_version_id < version
3265 return self.pull_request_version_id < version
3261
3266
3262 @property
3267 @property
3263 def resolved(self):
3268 def resolved(self):
3264 return self.resolved_by[0] if self.resolved_by else None
3269 return self.resolved_by[0] if self.resolved_by else None
3265
3270
3266 @property
3271 @property
3267 def is_todo(self):
3272 def is_todo(self):
3268 return self.comment_type == self.COMMENT_TYPE_TODO
3273 return self.comment_type == self.COMMENT_TYPE_TODO
3269
3274
3270 @property
3275 @property
3271 def is_inline(self):
3276 def is_inline(self):
3272 return self.line_no and self.f_path
3277 return self.line_no and self.f_path
3273
3278
3274 def get_index_version(self, versions):
3279 def get_index_version(self, versions):
3275 return self.get_index_from_version(
3280 return self.get_index_from_version(
3276 self.pull_request_version_id, versions)
3281 self.pull_request_version_id, versions)
3277
3282
3278 def __repr__(self):
3283 def __repr__(self):
3279 if self.comment_id:
3284 if self.comment_id:
3280 return '<DB:Comment #%s>' % self.comment_id
3285 return '<DB:Comment #%s>' % self.comment_id
3281 else:
3286 else:
3282 return '<DB:Comment at %#x>' % id(self)
3287 return '<DB:Comment at %#x>' % id(self)
3283
3288
3284 def get_api_data(self):
3289 def get_api_data(self):
3285 comment = self
3290 comment = self
3286 data = {
3291 data = {
3287 'comment_id': comment.comment_id,
3292 'comment_id': comment.comment_id,
3288 'comment_type': comment.comment_type,
3293 'comment_type': comment.comment_type,
3289 'comment_text': comment.text,
3294 'comment_text': comment.text,
3290 'comment_status': comment.status_change,
3295 'comment_status': comment.status_change,
3291 'comment_f_path': comment.f_path,
3296 'comment_f_path': comment.f_path,
3292 'comment_lineno': comment.line_no,
3297 'comment_lineno': comment.line_no,
3293 'comment_author': comment.author,
3298 'comment_author': comment.author,
3294 'comment_created_on': comment.created_on
3299 'comment_created_on': comment.created_on
3295 }
3300 }
3296 return data
3301 return data
3297
3302
3298 def __json__(self):
3303 def __json__(self):
3299 data = dict()
3304 data = dict()
3300 data.update(self.get_api_data())
3305 data.update(self.get_api_data())
3301 return data
3306 return data
3302
3307
3303
3308
3304 class ChangesetStatus(Base, BaseModel):
3309 class ChangesetStatus(Base, BaseModel):
3305 __tablename__ = 'changeset_statuses'
3310 __tablename__ = 'changeset_statuses'
3306 __table_args__ = (
3311 __table_args__ = (
3307 Index('cs_revision_idx', 'revision'),
3312 Index('cs_revision_idx', 'revision'),
3308 Index('cs_version_idx', 'version'),
3313 Index('cs_version_idx', 'version'),
3309 UniqueConstraint('repo_id', 'revision', 'version'),
3314 UniqueConstraint('repo_id', 'revision', 'version'),
3310 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3311 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3312 )
3317 )
3313 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3318 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3314 STATUS_APPROVED = 'approved'
3319 STATUS_APPROVED = 'approved'
3315 STATUS_REJECTED = 'rejected'
3320 STATUS_REJECTED = 'rejected'
3316 STATUS_UNDER_REVIEW = 'under_review'
3321 STATUS_UNDER_REVIEW = 'under_review'
3317
3322
3318 STATUSES = [
3323 STATUSES = [
3319 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3324 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3320 (STATUS_APPROVED, _("Approved")),
3325 (STATUS_APPROVED, _("Approved")),
3321 (STATUS_REJECTED, _("Rejected")),
3326 (STATUS_REJECTED, _("Rejected")),
3322 (STATUS_UNDER_REVIEW, _("Under Review")),
3327 (STATUS_UNDER_REVIEW, _("Under Review")),
3323 ]
3328 ]
3324
3329
3325 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3330 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3326 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3331 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3328 revision = Column('revision', String(40), nullable=False)
3333 revision = Column('revision', String(40), nullable=False)
3329 status = Column('status', String(128), nullable=False, default=DEFAULT)
3334 status = Column('status', String(128), nullable=False, default=DEFAULT)
3330 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3335 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3331 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3336 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3332 version = Column('version', Integer(), nullable=False, default=0)
3337 version = Column('version', Integer(), nullable=False, default=0)
3333 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3338 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3334
3339
3335 author = relationship('User', lazy='joined')
3340 author = relationship('User', lazy='joined')
3336 repo = relationship('Repository')
3341 repo = relationship('Repository')
3337 comment = relationship('ChangesetComment', lazy='joined')
3342 comment = relationship('ChangesetComment', lazy='joined')
3338 pull_request = relationship('PullRequest', lazy='joined')
3343 pull_request = relationship('PullRequest', lazy='joined')
3339
3344
3340 def __unicode__(self):
3345 def __unicode__(self):
3341 return u"<%s('%s[v%s]:%s')>" % (
3346 return u"<%s('%s[v%s]:%s')>" % (
3342 self.__class__.__name__,
3347 self.__class__.__name__,
3343 self.status, self.version, self.author
3348 self.status, self.version, self.author
3344 )
3349 )
3345
3350
3346 @classmethod
3351 @classmethod
3347 def get_status_lbl(cls, value):
3352 def get_status_lbl(cls, value):
3348 return dict(cls.STATUSES).get(value)
3353 return dict(cls.STATUSES).get(value)
3349
3354
3350 @property
3355 @property
3351 def status_lbl(self):
3356 def status_lbl(self):
3352 return ChangesetStatus.get_status_lbl(self.status)
3357 return ChangesetStatus.get_status_lbl(self.status)
3353
3358
3354 def get_api_data(self):
3359 def get_api_data(self):
3355 status = self
3360 status = self
3356 data = {
3361 data = {
3357 'status_id': status.changeset_status_id,
3362 'status_id': status.changeset_status_id,
3358 'status': status.status,
3363 'status': status.status,
3359 }
3364 }
3360 return data
3365 return data
3361
3366
3362 def __json__(self):
3367 def __json__(self):
3363 data = dict()
3368 data = dict()
3364 data.update(self.get_api_data())
3369 data.update(self.get_api_data())
3365 return data
3370 return data
3366
3371
3367
3372
3368 class _PullRequestBase(BaseModel):
3373 class _PullRequestBase(BaseModel):
3369 """
3374 """
3370 Common attributes of pull request and version entries.
3375 Common attributes of pull request and version entries.
3371 """
3376 """
3372
3377
3373 # .status values
3378 # .status values
3374 STATUS_NEW = u'new'
3379 STATUS_NEW = u'new'
3375 STATUS_OPEN = u'open'
3380 STATUS_OPEN = u'open'
3376 STATUS_CLOSED = u'closed'
3381 STATUS_CLOSED = u'closed'
3377
3382
3378 title = Column('title', Unicode(255), nullable=True)
3383 title = Column('title', Unicode(255), nullable=True)
3379 description = Column(
3384 description = Column(
3380 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3385 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3381 nullable=True)
3386 nullable=True)
3382 # new/open/closed status of pull request (not approve/reject/etc)
3387 # new/open/closed status of pull request (not approve/reject/etc)
3383 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3388 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3384 created_on = Column(
3389 created_on = Column(
3385 'created_on', DateTime(timezone=False), nullable=False,
3390 'created_on', DateTime(timezone=False), nullable=False,
3386 default=datetime.datetime.now)
3391 default=datetime.datetime.now)
3387 updated_on = Column(
3392 updated_on = Column(
3388 'updated_on', DateTime(timezone=False), nullable=False,
3393 'updated_on', DateTime(timezone=False), nullable=False,
3389 default=datetime.datetime.now)
3394 default=datetime.datetime.now)
3390
3395
3391 @declared_attr
3396 @declared_attr
3392 def user_id(cls):
3397 def user_id(cls):
3393 return Column(
3398 return Column(
3394 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3399 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3395 unique=None)
3400 unique=None)
3396
3401
3397 # 500 revisions max
3402 # 500 revisions max
3398 _revisions = Column(
3403 _revisions = Column(
3399 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3404 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3400
3405
3401 @declared_attr
3406 @declared_attr
3402 def source_repo_id(cls):
3407 def source_repo_id(cls):
3403 # TODO: dan: rename column to source_repo_id
3408 # TODO: dan: rename column to source_repo_id
3404 return Column(
3409 return Column(
3405 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3410 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3406 nullable=False)
3411 nullable=False)
3407
3412
3408 source_ref = Column('org_ref', Unicode(255), nullable=False)
3413 source_ref = Column('org_ref', Unicode(255), nullable=False)
3409
3414
3410 @declared_attr
3415 @declared_attr
3411 def target_repo_id(cls):
3416 def target_repo_id(cls):
3412 # TODO: dan: rename column to target_repo_id
3417 # TODO: dan: rename column to target_repo_id
3413 return Column(
3418 return Column(
3414 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3419 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3415 nullable=False)
3420 nullable=False)
3416
3421
3417 target_ref = Column('other_ref', Unicode(255), nullable=False)
3422 target_ref = Column('other_ref', Unicode(255), nullable=False)
3418 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3423 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3419
3424
3420 # TODO: dan: rename column to last_merge_source_rev
3425 # TODO: dan: rename column to last_merge_source_rev
3421 _last_merge_source_rev = Column(
3426 _last_merge_source_rev = Column(
3422 'last_merge_org_rev', String(40), nullable=True)
3427 'last_merge_org_rev', String(40), nullable=True)
3423 # TODO: dan: rename column to last_merge_target_rev
3428 # TODO: dan: rename column to last_merge_target_rev
3424 _last_merge_target_rev = Column(
3429 _last_merge_target_rev = Column(
3425 'last_merge_other_rev', String(40), nullable=True)
3430 'last_merge_other_rev', String(40), nullable=True)
3426 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3431 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3427 merge_rev = Column('merge_rev', String(40), nullable=True)
3432 merge_rev = Column('merge_rev', String(40), nullable=True)
3428
3433
3429 reviewer_data = Column(
3434 reviewer_data = Column(
3430 'reviewer_data_json', MutationObj.as_mutable(
3435 'reviewer_data_json', MutationObj.as_mutable(
3431 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3436 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3432
3437
3433 @property
3438 @property
3434 def reviewer_data_json(self):
3439 def reviewer_data_json(self):
3435 return json.dumps(self.reviewer_data)
3440 return json.dumps(self.reviewer_data)
3436
3441
3437 @hybrid_property
3442 @hybrid_property
3438 def description_safe(self):
3443 def description_safe(self):
3439 from rhodecode.lib import helpers as h
3444 from rhodecode.lib import helpers as h
3440 return h.escape(self.description)
3445 return h.escape(self.description)
3441
3446
3442 @hybrid_property
3447 @hybrid_property
3443 def revisions(self):
3448 def revisions(self):
3444 return self._revisions.split(':') if self._revisions else []
3449 return self._revisions.split(':') if self._revisions else []
3445
3450
3446 @revisions.setter
3451 @revisions.setter
3447 def revisions(self, val):
3452 def revisions(self, val):
3448 self._revisions = ':'.join(val)
3453 self._revisions = ':'.join(val)
3449
3454
3450 @hybrid_property
3455 @hybrid_property
3451 def last_merge_status(self):
3456 def last_merge_status(self):
3452 return safe_int(self._last_merge_status)
3457 return safe_int(self._last_merge_status)
3453
3458
3454 @last_merge_status.setter
3459 @last_merge_status.setter
3455 def last_merge_status(self, val):
3460 def last_merge_status(self, val):
3456 self._last_merge_status = val
3461 self._last_merge_status = val
3457
3462
3458 @declared_attr
3463 @declared_attr
3459 def author(cls):
3464 def author(cls):
3460 return relationship('User', lazy='joined')
3465 return relationship('User', lazy='joined')
3461
3466
3462 @declared_attr
3467 @declared_attr
3463 def source_repo(cls):
3468 def source_repo(cls):
3464 return relationship(
3469 return relationship(
3465 'Repository',
3470 'Repository',
3466 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3471 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3467
3472
3468 @property
3473 @property
3469 def source_ref_parts(self):
3474 def source_ref_parts(self):
3470 return self.unicode_to_reference(self.source_ref)
3475 return self.unicode_to_reference(self.source_ref)
3471
3476
3472 @declared_attr
3477 @declared_attr
3473 def target_repo(cls):
3478 def target_repo(cls):
3474 return relationship(
3479 return relationship(
3475 'Repository',
3480 'Repository',
3476 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3481 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3477
3482
3478 @property
3483 @property
3479 def target_ref_parts(self):
3484 def target_ref_parts(self):
3480 return self.unicode_to_reference(self.target_ref)
3485 return self.unicode_to_reference(self.target_ref)
3481
3486
3482 @property
3487 @property
3483 def shadow_merge_ref(self):
3488 def shadow_merge_ref(self):
3484 return self.unicode_to_reference(self._shadow_merge_ref)
3489 return self.unicode_to_reference(self._shadow_merge_ref)
3485
3490
3486 @shadow_merge_ref.setter
3491 @shadow_merge_ref.setter
3487 def shadow_merge_ref(self, ref):
3492 def shadow_merge_ref(self, ref):
3488 self._shadow_merge_ref = self.reference_to_unicode(ref)
3493 self._shadow_merge_ref = self.reference_to_unicode(ref)
3489
3494
3490 def unicode_to_reference(self, raw):
3495 def unicode_to_reference(self, raw):
3491 """
3496 """
3492 Convert a unicode (or string) to a reference object.
3497 Convert a unicode (or string) to a reference object.
3493 If unicode evaluates to False it returns None.
3498 If unicode evaluates to False it returns None.
3494 """
3499 """
3495 if raw:
3500 if raw:
3496 refs = raw.split(':')
3501 refs = raw.split(':')
3497 return Reference(*refs)
3502 return Reference(*refs)
3498 else:
3503 else:
3499 return None
3504 return None
3500
3505
3501 def reference_to_unicode(self, ref):
3506 def reference_to_unicode(self, ref):
3502 """
3507 """
3503 Convert a reference object to unicode.
3508 Convert a reference object to unicode.
3504 If reference is None it returns None.
3509 If reference is None it returns None.
3505 """
3510 """
3506 if ref:
3511 if ref:
3507 return u':'.join(ref)
3512 return u':'.join(ref)
3508 else:
3513 else:
3509 return None
3514 return None
3510
3515
3511 def get_api_data(self, with_merge_state=True):
3516 def get_api_data(self, with_merge_state=True):
3512 from rhodecode.model.pull_request import PullRequestModel
3517 from rhodecode.model.pull_request import PullRequestModel
3513
3518
3514 pull_request = self
3519 pull_request = self
3515 if with_merge_state:
3520 if with_merge_state:
3516 merge_status = PullRequestModel().merge_status(pull_request)
3521 merge_status = PullRequestModel().merge_status(pull_request)
3517 merge_state = {
3522 merge_state = {
3518 'status': merge_status[0],
3523 'status': merge_status[0],
3519 'message': safe_unicode(merge_status[1]),
3524 'message': safe_unicode(merge_status[1]),
3520 }
3525 }
3521 else:
3526 else:
3522 merge_state = {'status': 'not_available',
3527 merge_state = {'status': 'not_available',
3523 'message': 'not_available'}
3528 'message': 'not_available'}
3524
3529
3525 merge_data = {
3530 merge_data = {
3526 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3531 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3527 'reference': (
3532 'reference': (
3528 pull_request.shadow_merge_ref._asdict()
3533 pull_request.shadow_merge_ref._asdict()
3529 if pull_request.shadow_merge_ref else None),
3534 if pull_request.shadow_merge_ref else None),
3530 }
3535 }
3531
3536
3532 data = {
3537 data = {
3533 'pull_request_id': pull_request.pull_request_id,
3538 'pull_request_id': pull_request.pull_request_id,
3534 'url': PullRequestModel().get_url(pull_request),
3539 'url': PullRequestModel().get_url(pull_request),
3535 'title': pull_request.title,
3540 'title': pull_request.title,
3536 'description': pull_request.description,
3541 'description': pull_request.description,
3537 'status': pull_request.status,
3542 'status': pull_request.status,
3538 'created_on': pull_request.created_on,
3543 'created_on': pull_request.created_on,
3539 'updated_on': pull_request.updated_on,
3544 'updated_on': pull_request.updated_on,
3540 'commit_ids': pull_request.revisions,
3545 'commit_ids': pull_request.revisions,
3541 'review_status': pull_request.calculated_review_status(),
3546 'review_status': pull_request.calculated_review_status(),
3542 'mergeable': merge_state,
3547 'mergeable': merge_state,
3543 'source': {
3548 'source': {
3544 'clone_url': pull_request.source_repo.clone_url(),
3549 'clone_url': pull_request.source_repo.clone_url(),
3545 'repository': pull_request.source_repo.repo_name,
3550 'repository': pull_request.source_repo.repo_name,
3546 'reference': {
3551 'reference': {
3547 'name': pull_request.source_ref_parts.name,
3552 'name': pull_request.source_ref_parts.name,
3548 'type': pull_request.source_ref_parts.type,
3553 'type': pull_request.source_ref_parts.type,
3549 'commit_id': pull_request.source_ref_parts.commit_id,
3554 'commit_id': pull_request.source_ref_parts.commit_id,
3550 },
3555 },
3551 },
3556 },
3552 'target': {
3557 'target': {
3553 'clone_url': pull_request.target_repo.clone_url(),
3558 'clone_url': pull_request.target_repo.clone_url(),
3554 'repository': pull_request.target_repo.repo_name,
3559 'repository': pull_request.target_repo.repo_name,
3555 'reference': {
3560 'reference': {
3556 'name': pull_request.target_ref_parts.name,
3561 'name': pull_request.target_ref_parts.name,
3557 'type': pull_request.target_ref_parts.type,
3562 'type': pull_request.target_ref_parts.type,
3558 'commit_id': pull_request.target_ref_parts.commit_id,
3563 'commit_id': pull_request.target_ref_parts.commit_id,
3559 },
3564 },
3560 },
3565 },
3561 'merge': merge_data,
3566 'merge': merge_data,
3562 'author': pull_request.author.get_api_data(include_secrets=False,
3567 'author': pull_request.author.get_api_data(include_secrets=False,
3563 details='basic'),
3568 details='basic'),
3564 'reviewers': [
3569 'reviewers': [
3565 {
3570 {
3566 'user': reviewer.get_api_data(include_secrets=False,
3571 'user': reviewer.get_api_data(include_secrets=False,
3567 details='basic'),
3572 details='basic'),
3568 'reasons': reasons,
3573 'reasons': reasons,
3569 'review_status': st[0][1].status if st else 'not_reviewed',
3574 'review_status': st[0][1].status if st else 'not_reviewed',
3570 }
3575 }
3571 for reviewer, reasons, mandatory, st in
3576 for reviewer, reasons, mandatory, st in
3572 pull_request.reviewers_statuses()
3577 pull_request.reviewers_statuses()
3573 ]
3578 ]
3574 }
3579 }
3575
3580
3576 return data
3581 return data
3577
3582
3578
3583
3579 class PullRequest(Base, _PullRequestBase):
3584 class PullRequest(Base, _PullRequestBase):
3580 __tablename__ = 'pull_requests'
3585 __tablename__ = 'pull_requests'
3581 __table_args__ = (
3586 __table_args__ = (
3582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3588 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3584 )
3589 )
3585
3590
3586 pull_request_id = Column(
3591 pull_request_id = Column(
3587 'pull_request_id', Integer(), nullable=False, primary_key=True)
3592 'pull_request_id', Integer(), nullable=False, primary_key=True)
3588
3593
3589 def __repr__(self):
3594 def __repr__(self):
3590 if self.pull_request_id:
3595 if self.pull_request_id:
3591 return '<DB:PullRequest #%s>' % self.pull_request_id
3596 return '<DB:PullRequest #%s>' % self.pull_request_id
3592 else:
3597 else:
3593 return '<DB:PullRequest at %#x>' % id(self)
3598 return '<DB:PullRequest at %#x>' % id(self)
3594
3599
3595 reviewers = relationship('PullRequestReviewers',
3600 reviewers = relationship('PullRequestReviewers',
3596 cascade="all, delete, delete-orphan")
3601 cascade="all, delete, delete-orphan")
3597 statuses = relationship('ChangesetStatus',
3602 statuses = relationship('ChangesetStatus',
3598 cascade="all, delete, delete-orphan")
3603 cascade="all, delete, delete-orphan")
3599 comments = relationship('ChangesetComment',
3604 comments = relationship('ChangesetComment',
3600 cascade="all, delete, delete-orphan")
3605 cascade="all, delete, delete-orphan")
3601 versions = relationship('PullRequestVersion',
3606 versions = relationship('PullRequestVersion',
3602 cascade="all, delete, delete-orphan",
3607 cascade="all, delete, delete-orphan",
3603 lazy='dynamic')
3608 lazy='dynamic')
3604
3609
3605 @classmethod
3610 @classmethod
3606 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3611 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3607 internal_methods=None):
3612 internal_methods=None):
3608
3613
3609 class PullRequestDisplay(object):
3614 class PullRequestDisplay(object):
3610 """
3615 """
3611 Special object wrapper for showing PullRequest data via Versions
3616 Special object wrapper for showing PullRequest data via Versions
3612 It mimics PR object as close as possible. This is read only object
3617 It mimics PR object as close as possible. This is read only object
3613 just for display
3618 just for display
3614 """
3619 """
3615
3620
3616 def __init__(self, attrs, internal=None):
3621 def __init__(self, attrs, internal=None):
3617 self.attrs = attrs
3622 self.attrs = attrs
3618 # internal have priority over the given ones via attrs
3623 # internal have priority over the given ones via attrs
3619 self.internal = internal or ['versions']
3624 self.internal = internal or ['versions']
3620
3625
3621 def __getattr__(self, item):
3626 def __getattr__(self, item):
3622 if item in self.internal:
3627 if item in self.internal:
3623 return getattr(self, item)
3628 return getattr(self, item)
3624 try:
3629 try:
3625 return self.attrs[item]
3630 return self.attrs[item]
3626 except KeyError:
3631 except KeyError:
3627 raise AttributeError(
3632 raise AttributeError(
3628 '%s object has no attribute %s' % (self, item))
3633 '%s object has no attribute %s' % (self, item))
3629
3634
3630 def __repr__(self):
3635 def __repr__(self):
3631 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3636 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3632
3637
3633 def versions(self):
3638 def versions(self):
3634 return pull_request_obj.versions.order_by(
3639 return pull_request_obj.versions.order_by(
3635 PullRequestVersion.pull_request_version_id).all()
3640 PullRequestVersion.pull_request_version_id).all()
3636
3641
3637 def is_closed(self):
3642 def is_closed(self):
3638 return pull_request_obj.is_closed()
3643 return pull_request_obj.is_closed()
3639
3644
3640 @property
3645 @property
3641 def pull_request_version_id(self):
3646 def pull_request_version_id(self):
3642 return getattr(pull_request_obj, 'pull_request_version_id', None)
3647 return getattr(pull_request_obj, 'pull_request_version_id', None)
3643
3648
3644 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3649 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3645
3650
3646 attrs.author = StrictAttributeDict(
3651 attrs.author = StrictAttributeDict(
3647 pull_request_obj.author.get_api_data())
3652 pull_request_obj.author.get_api_data())
3648 if pull_request_obj.target_repo:
3653 if pull_request_obj.target_repo:
3649 attrs.target_repo = StrictAttributeDict(
3654 attrs.target_repo = StrictAttributeDict(
3650 pull_request_obj.target_repo.get_api_data())
3655 pull_request_obj.target_repo.get_api_data())
3651 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3656 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3652
3657
3653 if pull_request_obj.source_repo:
3658 if pull_request_obj.source_repo:
3654 attrs.source_repo = StrictAttributeDict(
3659 attrs.source_repo = StrictAttributeDict(
3655 pull_request_obj.source_repo.get_api_data())
3660 pull_request_obj.source_repo.get_api_data())
3656 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3661 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3657
3662
3658 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3663 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3659 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3664 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3660 attrs.revisions = pull_request_obj.revisions
3665 attrs.revisions = pull_request_obj.revisions
3661
3666
3662 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3667 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3663 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3668 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3664 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3669 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3665
3670
3666 return PullRequestDisplay(attrs, internal=internal_methods)
3671 return PullRequestDisplay(attrs, internal=internal_methods)
3667
3672
3668 def is_closed(self):
3673 def is_closed(self):
3669 return self.status == self.STATUS_CLOSED
3674 return self.status == self.STATUS_CLOSED
3670
3675
3671 def __json__(self):
3676 def __json__(self):
3672 return {
3677 return {
3673 'revisions': self.revisions,
3678 'revisions': self.revisions,
3674 }
3679 }
3675
3680
3676 def calculated_review_status(self):
3681 def calculated_review_status(self):
3677 from rhodecode.model.changeset_status import ChangesetStatusModel
3682 from rhodecode.model.changeset_status import ChangesetStatusModel
3678 return ChangesetStatusModel().calculated_review_status(self)
3683 return ChangesetStatusModel().calculated_review_status(self)
3679
3684
3680 def reviewers_statuses(self):
3685 def reviewers_statuses(self):
3681 from rhodecode.model.changeset_status import ChangesetStatusModel
3686 from rhodecode.model.changeset_status import ChangesetStatusModel
3682 return ChangesetStatusModel().reviewers_statuses(self)
3687 return ChangesetStatusModel().reviewers_statuses(self)
3683
3688
3684 @property
3689 @property
3685 def workspace_id(self):
3690 def workspace_id(self):
3686 from rhodecode.model.pull_request import PullRequestModel
3691 from rhodecode.model.pull_request import PullRequestModel
3687 return PullRequestModel()._workspace_id(self)
3692 return PullRequestModel()._workspace_id(self)
3688
3693
3689 def get_shadow_repo(self):
3694 def get_shadow_repo(self):
3690 workspace_id = self.workspace_id
3695 workspace_id = self.workspace_id
3691 vcs_obj = self.target_repo.scm_instance()
3696 vcs_obj = self.target_repo.scm_instance()
3692 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3697 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3693 workspace_id)
3698 workspace_id)
3694 return vcs_obj._get_shadow_instance(shadow_repository_path)
3699 return vcs_obj._get_shadow_instance(shadow_repository_path)
3695
3700
3696
3701
3697 class PullRequestVersion(Base, _PullRequestBase):
3702 class PullRequestVersion(Base, _PullRequestBase):
3698 __tablename__ = 'pull_request_versions'
3703 __tablename__ = 'pull_request_versions'
3699 __table_args__ = (
3704 __table_args__ = (
3700 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3701 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3702 )
3707 )
3703
3708
3704 pull_request_version_id = Column(
3709 pull_request_version_id = Column(
3705 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3710 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3706 pull_request_id = Column(
3711 pull_request_id = Column(
3707 'pull_request_id', Integer(),
3712 'pull_request_id', Integer(),
3708 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3713 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3709 pull_request = relationship('PullRequest')
3714 pull_request = relationship('PullRequest')
3710
3715
3711 def __repr__(self):
3716 def __repr__(self):
3712 if self.pull_request_version_id:
3717 if self.pull_request_version_id:
3713 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3718 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3714 else:
3719 else:
3715 return '<DB:PullRequestVersion at %#x>' % id(self)
3720 return '<DB:PullRequestVersion at %#x>' % id(self)
3716
3721
3717 @property
3722 @property
3718 def reviewers(self):
3723 def reviewers(self):
3719 return self.pull_request.reviewers
3724 return self.pull_request.reviewers
3720
3725
3721 @property
3726 @property
3722 def versions(self):
3727 def versions(self):
3723 return self.pull_request.versions
3728 return self.pull_request.versions
3724
3729
3725 def is_closed(self):
3730 def is_closed(self):
3726 # calculate from original
3731 # calculate from original
3727 return self.pull_request.status == self.STATUS_CLOSED
3732 return self.pull_request.status == self.STATUS_CLOSED
3728
3733
3729 def calculated_review_status(self):
3734 def calculated_review_status(self):
3730 return self.pull_request.calculated_review_status()
3735 return self.pull_request.calculated_review_status()
3731
3736
3732 def reviewers_statuses(self):
3737 def reviewers_statuses(self):
3733 return self.pull_request.reviewers_statuses()
3738 return self.pull_request.reviewers_statuses()
3734
3739
3735
3740
3736 class PullRequestReviewers(Base, BaseModel):
3741 class PullRequestReviewers(Base, BaseModel):
3737 __tablename__ = 'pull_request_reviewers'
3742 __tablename__ = 'pull_request_reviewers'
3738 __table_args__ = (
3743 __table_args__ = (
3739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3744 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3740 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3745 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3741 )
3746 )
3742
3747
3743 @hybrid_property
3748 @hybrid_property
3744 def reasons(self):
3749 def reasons(self):
3745 if not self._reasons:
3750 if not self._reasons:
3746 return []
3751 return []
3747 return self._reasons
3752 return self._reasons
3748
3753
3749 @reasons.setter
3754 @reasons.setter
3750 def reasons(self, val):
3755 def reasons(self, val):
3751 val = val or []
3756 val = val or []
3752 if any(not isinstance(x, basestring) for x in val):
3757 if any(not isinstance(x, basestring) for x in val):
3753 raise Exception('invalid reasons type, must be list of strings')
3758 raise Exception('invalid reasons type, must be list of strings')
3754 self._reasons = val
3759 self._reasons = val
3755
3760
3756 pull_requests_reviewers_id = Column(
3761 pull_requests_reviewers_id = Column(
3757 'pull_requests_reviewers_id', Integer(), nullable=False,
3762 'pull_requests_reviewers_id', Integer(), nullable=False,
3758 primary_key=True)
3763 primary_key=True)
3759 pull_request_id = Column(
3764 pull_request_id = Column(
3760 "pull_request_id", Integer(),
3765 "pull_request_id", Integer(),
3761 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3766 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3762 user_id = Column(
3767 user_id = Column(
3763 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3768 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3764 _reasons = Column(
3769 _reasons = Column(
3765 'reason', MutationList.as_mutable(
3770 'reason', MutationList.as_mutable(
3766 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3771 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3767 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3772 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3768 user = relationship('User')
3773 user = relationship('User')
3769 pull_request = relationship('PullRequest')
3774 pull_request = relationship('PullRequest')
3770
3775
3771
3776
3772 class Notification(Base, BaseModel):
3777 class Notification(Base, BaseModel):
3773 __tablename__ = 'notifications'
3778 __tablename__ = 'notifications'
3774 __table_args__ = (
3779 __table_args__ = (
3775 Index('notification_type_idx', 'type'),
3780 Index('notification_type_idx', 'type'),
3776 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3777 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3782 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3778 )
3783 )
3779
3784
3780 TYPE_CHANGESET_COMMENT = u'cs_comment'
3785 TYPE_CHANGESET_COMMENT = u'cs_comment'
3781 TYPE_MESSAGE = u'message'
3786 TYPE_MESSAGE = u'message'
3782 TYPE_MENTION = u'mention'
3787 TYPE_MENTION = u'mention'
3783 TYPE_REGISTRATION = u'registration'
3788 TYPE_REGISTRATION = u'registration'
3784 TYPE_PULL_REQUEST = u'pull_request'
3789 TYPE_PULL_REQUEST = u'pull_request'
3785 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3790 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3786
3791
3787 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3792 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3788 subject = Column('subject', Unicode(512), nullable=True)
3793 subject = Column('subject', Unicode(512), nullable=True)
3789 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3794 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3790 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3795 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3791 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3796 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3792 type_ = Column('type', Unicode(255))
3797 type_ = Column('type', Unicode(255))
3793
3798
3794 created_by_user = relationship('User')
3799 created_by_user = relationship('User')
3795 notifications_to_users = relationship('UserNotification', lazy='joined',
3800 notifications_to_users = relationship('UserNotification', lazy='joined',
3796 cascade="all, delete, delete-orphan")
3801 cascade="all, delete, delete-orphan")
3797
3802
3798 @property
3803 @property
3799 def recipients(self):
3804 def recipients(self):
3800 return [x.user for x in UserNotification.query()\
3805 return [x.user for x in UserNotification.query()\
3801 .filter(UserNotification.notification == self)\
3806 .filter(UserNotification.notification == self)\
3802 .order_by(UserNotification.user_id.asc()).all()]
3807 .order_by(UserNotification.user_id.asc()).all()]
3803
3808
3804 @classmethod
3809 @classmethod
3805 def create(cls, created_by, subject, body, recipients, type_=None):
3810 def create(cls, created_by, subject, body, recipients, type_=None):
3806 if type_ is None:
3811 if type_ is None:
3807 type_ = Notification.TYPE_MESSAGE
3812 type_ = Notification.TYPE_MESSAGE
3808
3813
3809 notification = cls()
3814 notification = cls()
3810 notification.created_by_user = created_by
3815 notification.created_by_user = created_by
3811 notification.subject = subject
3816 notification.subject = subject
3812 notification.body = body
3817 notification.body = body
3813 notification.type_ = type_
3818 notification.type_ = type_
3814 notification.created_on = datetime.datetime.now()
3819 notification.created_on = datetime.datetime.now()
3815
3820
3816 for u in recipients:
3821 for u in recipients:
3817 assoc = UserNotification()
3822 assoc = UserNotification()
3818 assoc.notification = notification
3823 assoc.notification = notification
3819
3824
3820 # if created_by is inside recipients mark his notification
3825 # if created_by is inside recipients mark his notification
3821 # as read
3826 # as read
3822 if u.user_id == created_by.user_id:
3827 if u.user_id == created_by.user_id:
3823 assoc.read = True
3828 assoc.read = True
3824
3829
3825 u.notifications.append(assoc)
3830 u.notifications.append(assoc)
3826 Session().add(notification)
3831 Session().add(notification)
3827
3832
3828 return notification
3833 return notification
3829
3834
3830
3835
3831 class UserNotification(Base, BaseModel):
3836 class UserNotification(Base, BaseModel):
3832 __tablename__ = 'user_to_notification'
3837 __tablename__ = 'user_to_notification'
3833 __table_args__ = (
3838 __table_args__ = (
3834 UniqueConstraint('user_id', 'notification_id'),
3839 UniqueConstraint('user_id', 'notification_id'),
3835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3836 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3837 )
3842 )
3838 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3843 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3839 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3844 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3840 read = Column('read', Boolean, default=False)
3845 read = Column('read', Boolean, default=False)
3841 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3846 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3842
3847
3843 user = relationship('User', lazy="joined")
3848 user = relationship('User', lazy="joined")
3844 notification = relationship('Notification', lazy="joined",
3849 notification = relationship('Notification', lazy="joined",
3845 order_by=lambda: Notification.created_on.desc(),)
3850 order_by=lambda: Notification.created_on.desc(),)
3846
3851
3847 def mark_as_read(self):
3852 def mark_as_read(self):
3848 self.read = True
3853 self.read = True
3849 Session().add(self)
3854 Session().add(self)
3850
3855
3851
3856
3852 class Gist(Base, BaseModel):
3857 class Gist(Base, BaseModel):
3853 __tablename__ = 'gists'
3858 __tablename__ = 'gists'
3854 __table_args__ = (
3859 __table_args__ = (
3855 Index('g_gist_access_id_idx', 'gist_access_id'),
3860 Index('g_gist_access_id_idx', 'gist_access_id'),
3856 Index('g_created_on_idx', 'created_on'),
3861 Index('g_created_on_idx', 'created_on'),
3857 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3858 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3859 )
3864 )
3860 GIST_PUBLIC = u'public'
3865 GIST_PUBLIC = u'public'
3861 GIST_PRIVATE = u'private'
3866 GIST_PRIVATE = u'private'
3862 DEFAULT_FILENAME = u'gistfile1.txt'
3867 DEFAULT_FILENAME = u'gistfile1.txt'
3863
3868
3864 ACL_LEVEL_PUBLIC = u'acl_public'
3869 ACL_LEVEL_PUBLIC = u'acl_public'
3865 ACL_LEVEL_PRIVATE = u'acl_private'
3870 ACL_LEVEL_PRIVATE = u'acl_private'
3866
3871
3867 gist_id = Column('gist_id', Integer(), primary_key=True)
3872 gist_id = Column('gist_id', Integer(), primary_key=True)
3868 gist_access_id = Column('gist_access_id', Unicode(250))
3873 gist_access_id = Column('gist_access_id', Unicode(250))
3869 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3874 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3870 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3875 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3871 gist_expires = Column('gist_expires', Float(53), nullable=False)
3876 gist_expires = Column('gist_expires', Float(53), nullable=False)
3872 gist_type = Column('gist_type', Unicode(128), nullable=False)
3877 gist_type = Column('gist_type', Unicode(128), nullable=False)
3873 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3874 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3879 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3875 acl_level = Column('acl_level', Unicode(128), nullable=True)
3880 acl_level = Column('acl_level', Unicode(128), nullable=True)
3876
3881
3877 owner = relationship('User')
3882 owner = relationship('User')
3878
3883
3879 def __repr__(self):
3884 def __repr__(self):
3880 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3885 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3881
3886
3882 @hybrid_property
3887 @hybrid_property
3883 def description_safe(self):
3888 def description_safe(self):
3884 from rhodecode.lib import helpers as h
3889 from rhodecode.lib import helpers as h
3885 return h.escape(self.gist_description)
3890 return h.escape(self.gist_description)
3886
3891
3887 @classmethod
3892 @classmethod
3888 def get_or_404(cls, id_):
3893 def get_or_404(cls, id_):
3889 from pyramid.httpexceptions import HTTPNotFound
3894 from pyramid.httpexceptions import HTTPNotFound
3890
3895
3891 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3896 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3892 if not res:
3897 if not res:
3893 raise HTTPNotFound()
3898 raise HTTPNotFound()
3894 return res
3899 return res
3895
3900
3896 @classmethod
3901 @classmethod
3897 def get_by_access_id(cls, gist_access_id):
3902 def get_by_access_id(cls, gist_access_id):
3898 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3903 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3899
3904
3900 def gist_url(self):
3905 def gist_url(self):
3901 from rhodecode.model.gist import GistModel
3906 from rhodecode.model.gist import GistModel
3902 return GistModel().get_url(self)
3907 return GistModel().get_url(self)
3903
3908
3904 @classmethod
3909 @classmethod
3905 def base_path(cls):
3910 def base_path(cls):
3906 """
3911 """
3907 Returns base path when all gists are stored
3912 Returns base path when all gists are stored
3908
3913
3909 :param cls:
3914 :param cls:
3910 """
3915 """
3911 from rhodecode.model.gist import GIST_STORE_LOC
3916 from rhodecode.model.gist import GIST_STORE_LOC
3912 q = Session().query(RhodeCodeUi)\
3917 q = Session().query(RhodeCodeUi)\
3913 .filter(RhodeCodeUi.ui_key == URL_SEP)
3918 .filter(RhodeCodeUi.ui_key == URL_SEP)
3914 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3919 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3915 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3920 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3916
3921
3917 def get_api_data(self):
3922 def get_api_data(self):
3918 """
3923 """
3919 Common function for generating gist related data for API
3924 Common function for generating gist related data for API
3920 """
3925 """
3921 gist = self
3926 gist = self
3922 data = {
3927 data = {
3923 'gist_id': gist.gist_id,
3928 'gist_id': gist.gist_id,
3924 'type': gist.gist_type,
3929 'type': gist.gist_type,
3925 'access_id': gist.gist_access_id,
3930 'access_id': gist.gist_access_id,
3926 'description': gist.gist_description,
3931 'description': gist.gist_description,
3927 'url': gist.gist_url(),
3932 'url': gist.gist_url(),
3928 'expires': gist.gist_expires,
3933 'expires': gist.gist_expires,
3929 'created_on': gist.created_on,
3934 'created_on': gist.created_on,
3930 'modified_at': gist.modified_at,
3935 'modified_at': gist.modified_at,
3931 'content': None,
3936 'content': None,
3932 'acl_level': gist.acl_level,
3937 'acl_level': gist.acl_level,
3933 }
3938 }
3934 return data
3939 return data
3935
3940
3936 def __json__(self):
3941 def __json__(self):
3937 data = dict(
3942 data = dict(
3938 )
3943 )
3939 data.update(self.get_api_data())
3944 data.update(self.get_api_data())
3940 return data
3945 return data
3941 # SCM functions
3946 # SCM functions
3942
3947
3943 def scm_instance(self, **kwargs):
3948 def scm_instance(self, **kwargs):
3944 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3949 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3945 return get_vcs_instance(
3950 return get_vcs_instance(
3946 repo_path=safe_str(full_repo_path), create=False)
3951 repo_path=safe_str(full_repo_path), create=False)
3947
3952
3948
3953
3949 class ExternalIdentity(Base, BaseModel):
3954 class ExternalIdentity(Base, BaseModel):
3950 __tablename__ = 'external_identities'
3955 __tablename__ = 'external_identities'
3951 __table_args__ = (
3956 __table_args__ = (
3952 Index('local_user_id_idx', 'local_user_id'),
3957 Index('local_user_id_idx', 'local_user_id'),
3953 Index('external_id_idx', 'external_id'),
3958 Index('external_id_idx', 'external_id'),
3954 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3955 'mysql_charset': 'utf8'})
3960 'mysql_charset': 'utf8'})
3956
3961
3957 external_id = Column('external_id', Unicode(255), default=u'',
3962 external_id = Column('external_id', Unicode(255), default=u'',
3958 primary_key=True)
3963 primary_key=True)
3959 external_username = Column('external_username', Unicode(1024), default=u'')
3964 external_username = Column('external_username', Unicode(1024), default=u'')
3960 local_user_id = Column('local_user_id', Integer(),
3965 local_user_id = Column('local_user_id', Integer(),
3961 ForeignKey('users.user_id'), primary_key=True)
3966 ForeignKey('users.user_id'), primary_key=True)
3962 provider_name = Column('provider_name', Unicode(255), default=u'',
3967 provider_name = Column('provider_name', Unicode(255), default=u'',
3963 primary_key=True)
3968 primary_key=True)
3964 access_token = Column('access_token', String(1024), default=u'')
3969 access_token = Column('access_token', String(1024), default=u'')
3965 alt_token = Column('alt_token', String(1024), default=u'')
3970 alt_token = Column('alt_token', String(1024), default=u'')
3966 token_secret = Column('token_secret', String(1024), default=u'')
3971 token_secret = Column('token_secret', String(1024), default=u'')
3967
3972
3968 @classmethod
3973 @classmethod
3969 def by_external_id_and_provider(cls, external_id, provider_name,
3974 def by_external_id_and_provider(cls, external_id, provider_name,
3970 local_user_id=None):
3975 local_user_id=None):
3971 """
3976 """
3972 Returns ExternalIdentity instance based on search params
3977 Returns ExternalIdentity instance based on search params
3973
3978
3974 :param external_id:
3979 :param external_id:
3975 :param provider_name:
3980 :param provider_name:
3976 :return: ExternalIdentity
3981 :return: ExternalIdentity
3977 """
3982 """
3978 query = cls.query()
3983 query = cls.query()
3979 query = query.filter(cls.external_id == external_id)
3984 query = query.filter(cls.external_id == external_id)
3980 query = query.filter(cls.provider_name == provider_name)
3985 query = query.filter(cls.provider_name == provider_name)
3981 if local_user_id:
3986 if local_user_id:
3982 query = query.filter(cls.local_user_id == local_user_id)
3987 query = query.filter(cls.local_user_id == local_user_id)
3983 return query.first()
3988 return query.first()
3984
3989
3985 @classmethod
3990 @classmethod
3986 def user_by_external_id_and_provider(cls, external_id, provider_name):
3991 def user_by_external_id_and_provider(cls, external_id, provider_name):
3987 """
3992 """
3988 Returns User instance based on search params
3993 Returns User instance based on search params
3989
3994
3990 :param external_id:
3995 :param external_id:
3991 :param provider_name:
3996 :param provider_name:
3992 :return: User
3997 :return: User
3993 """
3998 """
3994 query = User.query()
3999 query = User.query()
3995 query = query.filter(cls.external_id == external_id)
4000 query = query.filter(cls.external_id == external_id)
3996 query = query.filter(cls.provider_name == provider_name)
4001 query = query.filter(cls.provider_name == provider_name)
3997 query = query.filter(User.user_id == cls.local_user_id)
4002 query = query.filter(User.user_id == cls.local_user_id)
3998 return query.first()
4003 return query.first()
3999
4004
4000 @classmethod
4005 @classmethod
4001 def by_local_user_id(cls, local_user_id):
4006 def by_local_user_id(cls, local_user_id):
4002 """
4007 """
4003 Returns all tokens for user
4008 Returns all tokens for user
4004
4009
4005 :param local_user_id:
4010 :param local_user_id:
4006 :return: ExternalIdentity
4011 :return: ExternalIdentity
4007 """
4012 """
4008 query = cls.query()
4013 query = cls.query()
4009 query = query.filter(cls.local_user_id == local_user_id)
4014 query = query.filter(cls.local_user_id == local_user_id)
4010 return query
4015 return query
4011
4016
4012
4017
4013 class Integration(Base, BaseModel):
4018 class Integration(Base, BaseModel):
4014 __tablename__ = 'integrations'
4019 __tablename__ = 'integrations'
4015 __table_args__ = (
4020 __table_args__ = (
4016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4017 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4018 )
4023 )
4019
4024
4020 integration_id = Column('integration_id', Integer(), primary_key=True)
4025 integration_id = Column('integration_id', Integer(), primary_key=True)
4021 integration_type = Column('integration_type', String(255))
4026 integration_type = Column('integration_type', String(255))
4022 enabled = Column('enabled', Boolean(), nullable=False)
4027 enabled = Column('enabled', Boolean(), nullable=False)
4023 name = Column('name', String(255), nullable=False)
4028 name = Column('name', String(255), nullable=False)
4024 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4029 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4025 default=False)
4030 default=False)
4026
4031
4027 settings = Column(
4032 settings = Column(
4028 'settings_json', MutationObj.as_mutable(
4033 'settings_json', MutationObj.as_mutable(
4029 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4034 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4030 repo_id = Column(
4035 repo_id = Column(
4031 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4036 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4032 nullable=True, unique=None, default=None)
4037 nullable=True, unique=None, default=None)
4033 repo = relationship('Repository', lazy='joined')
4038 repo = relationship('Repository', lazy='joined')
4034
4039
4035 repo_group_id = Column(
4040 repo_group_id = Column(
4036 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4041 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4037 nullable=True, unique=None, default=None)
4042 nullable=True, unique=None, default=None)
4038 repo_group = relationship('RepoGroup', lazy='joined')
4043 repo_group = relationship('RepoGroup', lazy='joined')
4039
4044
4040 @property
4045 @property
4041 def scope(self):
4046 def scope(self):
4042 if self.repo:
4047 if self.repo:
4043 return repr(self.repo)
4048 return repr(self.repo)
4044 if self.repo_group:
4049 if self.repo_group:
4045 if self.child_repos_only:
4050 if self.child_repos_only:
4046 return repr(self.repo_group) + ' (child repos only)'
4051 return repr(self.repo_group) + ' (child repos only)'
4047 else:
4052 else:
4048 return repr(self.repo_group) + ' (recursive)'
4053 return repr(self.repo_group) + ' (recursive)'
4049 if self.child_repos_only:
4054 if self.child_repos_only:
4050 return 'root_repos'
4055 return 'root_repos'
4051 return 'global'
4056 return 'global'
4052
4057
4053 def __repr__(self):
4058 def __repr__(self):
4054 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4059 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4055
4060
4056
4061
4057 class RepoReviewRuleUser(Base, BaseModel):
4062 class RepoReviewRuleUser(Base, BaseModel):
4058 __tablename__ = 'repo_review_rules_users'
4063 __tablename__ = 'repo_review_rules_users'
4059 __table_args__ = (
4064 __table_args__ = (
4060 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4065 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4061 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4066 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4062 )
4067 )
4063 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4068 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4064 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4069 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4070 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4066 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4071 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4067 user = relationship('User')
4072 user = relationship('User')
4068
4073
4069 def rule_data(self):
4074 def rule_data(self):
4070 return {
4075 return {
4071 'mandatory': self.mandatory
4076 'mandatory': self.mandatory
4072 }
4077 }
4073
4078
4074
4079
4075 class RepoReviewRuleUserGroup(Base, BaseModel):
4080 class RepoReviewRuleUserGroup(Base, BaseModel):
4076 __tablename__ = 'repo_review_rules_users_groups'
4081 __tablename__ = 'repo_review_rules_users_groups'
4077 __table_args__ = (
4082 __table_args__ = (
4078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4079 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4080 )
4085 )
4081 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4086 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4082 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4087 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4083 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4088 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4084 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4089 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4085 users_group = relationship('UserGroup')
4090 users_group = relationship('UserGroup')
4086
4091
4087 def rule_data(self):
4092 def rule_data(self):
4088 return {
4093 return {
4089 'mandatory': self.mandatory
4094 'mandatory': self.mandatory
4090 }
4095 }
4091
4096
4092
4097
4093 class RepoReviewRule(Base, BaseModel):
4098 class RepoReviewRule(Base, BaseModel):
4094 __tablename__ = 'repo_review_rules'
4099 __tablename__ = 'repo_review_rules'
4095 __table_args__ = (
4100 __table_args__ = (
4096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4098 )
4103 )
4099
4104
4100 repo_review_rule_id = Column(
4105 repo_review_rule_id = Column(
4101 'repo_review_rule_id', Integer(), primary_key=True)
4106 'repo_review_rule_id', Integer(), primary_key=True)
4102 repo_id = Column(
4107 repo_id = Column(
4103 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4108 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4104 repo = relationship('Repository', backref='review_rules')
4109 repo = relationship('Repository', backref='review_rules')
4105
4110
4106 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4111 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4107 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4112 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4108
4113
4109 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4114 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4110 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4115 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4111 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4116 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4112 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4117 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4113
4118
4114 rule_users = relationship('RepoReviewRuleUser')
4119 rule_users = relationship('RepoReviewRuleUser')
4115 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4120 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4116
4121
4117 @hybrid_property
4122 @hybrid_property
4118 def branch_pattern(self):
4123 def branch_pattern(self):
4119 return self._branch_pattern or '*'
4124 return self._branch_pattern or '*'
4120
4125
4121 def _validate_glob(self, value):
4126 def _validate_glob(self, value):
4122 re.compile('^' + glob2re(value) + '$')
4127 re.compile('^' + glob2re(value) + '$')
4123
4128
4124 @branch_pattern.setter
4129 @branch_pattern.setter
4125 def branch_pattern(self, value):
4130 def branch_pattern(self, value):
4126 self._validate_glob(value)
4131 self._validate_glob(value)
4127 self._branch_pattern = value or '*'
4132 self._branch_pattern = value or '*'
4128
4133
4129 @hybrid_property
4134 @hybrid_property
4130 def file_pattern(self):
4135 def file_pattern(self):
4131 return self._file_pattern or '*'
4136 return self._file_pattern or '*'
4132
4137
4133 @file_pattern.setter
4138 @file_pattern.setter
4134 def file_pattern(self, value):
4139 def file_pattern(self, value):
4135 self._validate_glob(value)
4140 self._validate_glob(value)
4136 self._file_pattern = value or '*'
4141 self._file_pattern = value or '*'
4137
4142
4138 def matches(self, branch, files_changed):
4143 def matches(self, branch, files_changed):
4139 """
4144 """
4140 Check if this review rule matches a branch/files in a pull request
4145 Check if this review rule matches a branch/files in a pull request
4141
4146
4142 :param branch: branch name for the commit
4147 :param branch: branch name for the commit
4143 :param files_changed: list of file paths changed in the pull request
4148 :param files_changed: list of file paths changed in the pull request
4144 """
4149 """
4145
4150
4146 branch = branch or ''
4151 branch = branch or ''
4147 files_changed = files_changed or []
4152 files_changed = files_changed or []
4148
4153
4149 branch_matches = True
4154 branch_matches = True
4150 if branch:
4155 if branch:
4151 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4156 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4152 branch_matches = bool(branch_regex.search(branch))
4157 branch_matches = bool(branch_regex.search(branch))
4153
4158
4154 files_matches = True
4159 files_matches = True
4155 if self.file_pattern != '*':
4160 if self.file_pattern != '*':
4156 files_matches = False
4161 files_matches = False
4157 file_regex = re.compile(glob2re(self.file_pattern))
4162 file_regex = re.compile(glob2re(self.file_pattern))
4158 for filename in files_changed:
4163 for filename in files_changed:
4159 if file_regex.search(filename):
4164 if file_regex.search(filename):
4160 files_matches = True
4165 files_matches = True
4161 break
4166 break
4162
4167
4163 return branch_matches and files_matches
4168 return branch_matches and files_matches
4164
4169
4165 @property
4170 @property
4166 def review_users(self):
4171 def review_users(self):
4167 """ Returns the users which this rule applies to """
4172 """ Returns the users which this rule applies to """
4168
4173
4169 users = collections.OrderedDict()
4174 users = collections.OrderedDict()
4170
4175
4171 for rule_user in self.rule_users:
4176 for rule_user in self.rule_users:
4172 if rule_user.user.active:
4177 if rule_user.user.active:
4173 if rule_user.user not in users:
4178 if rule_user.user not in users:
4174 users[rule_user.user.username] = {
4179 users[rule_user.user.username] = {
4175 'user': rule_user.user,
4180 'user': rule_user.user,
4176 'source': 'user',
4181 'source': 'user',
4177 'source_data': {},
4182 'source_data': {},
4178 'data': rule_user.rule_data()
4183 'data': rule_user.rule_data()
4179 }
4184 }
4180
4185
4181 for rule_user_group in self.rule_user_groups:
4186 for rule_user_group in self.rule_user_groups:
4182 source_data = {
4187 source_data = {
4183 'name': rule_user_group.users_group.users_group_name,
4188 'name': rule_user_group.users_group.users_group_name,
4184 'members': len(rule_user_group.users_group.members)
4189 'members': len(rule_user_group.users_group.members)
4185 }
4190 }
4186 for member in rule_user_group.users_group.members:
4191 for member in rule_user_group.users_group.members:
4187 if member.user.active:
4192 if member.user.active:
4188 users[member.user.username] = {
4193 users[member.user.username] = {
4189 'user': member.user,
4194 'user': member.user,
4190 'source': 'user_group',
4195 'source': 'user_group',
4191 'source_data': source_data,
4196 'source_data': source_data,
4192 'data': rule_user_group.rule_data()
4197 'data': rule_user_group.rule_data()
4193 }
4198 }
4194
4199
4195 return users
4200 return users
4196
4201
4197 def __repr__(self):
4202 def __repr__(self):
4198 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4203 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4199 self.repo_review_rule_id, self.repo)
4204 self.repo_review_rule_id, self.repo)
4200
4205
4201
4206
4202 class DbMigrateVersion(Base, BaseModel):
4207 class DbMigrateVersion(Base, BaseModel):
4203 __tablename__ = 'db_migrate_version'
4208 __tablename__ = 'db_migrate_version'
4204 __table_args__ = (
4209 __table_args__ = (
4205 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4210 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4206 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4211 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4207 )
4212 )
4208 repository_id = Column('repository_id', String(250), primary_key=True)
4213 repository_id = Column('repository_id', String(250), primary_key=True)
4209 repository_path = Column('repository_path', Text)
4214 repository_path = Column('repository_path', Text)
4210 version = Column('version', Integer)
4215 version = Column('version', Integer)
4211
4216
4212
4217
4213 class DbSession(Base, BaseModel):
4218 class DbSession(Base, BaseModel):
4214 __tablename__ = 'db_session'
4219 __tablename__ = 'db_session'
4215 __table_args__ = (
4220 __table_args__ = (
4216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4217 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4222 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4218 )
4223 )
4219
4224
4220 def __repr__(self):
4225 def __repr__(self):
4221 return '<DB:DbSession({})>'.format(self.id)
4226 return '<DB:DbSession({})>'.format(self.id)
4222
4227
4223 id = Column('id', Integer())
4228 id = Column('id', Integer())
4224 namespace = Column('namespace', String(255), primary_key=True)
4229 namespace = Column('namespace', String(255), primary_key=True)
4225 accessed = Column('accessed', DateTime, nullable=False)
4230 accessed = Column('accessed', DateTime, nullable=False)
4226 created = Column('created', DateTime, nullable=False)
4231 created = Column('created', DateTime, nullable=False)
4227 data = Column('data', PickleType, nullable=False)
4232 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now