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