##// END OF EJS Templates
commit-cache: update repo group/repo every 5 min
marcink -
r3705:4bcb793e new-ui
parent child Browse files
Show More
@@ -1,747 +1,749 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 CSRFRequired)
31 CSRFRequired)
32 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.db import (
35 from rhodecode.model.db import (
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.user_group import UserGroupModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeView(BaseAppView):
46 class HomeView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
50 c.user = c.auth_user.get_instance()
51
51
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
56 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
57 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
58 def user_autocomplete_data(self):
59 self.load_default_context()
59 self.load_default_context()
60 query = self.request.GET.get('query')
60 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
61 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
65
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
67 query, active, include_groups)
68
68
69 _users = UserModel().get_users(
69 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
70 name_contains=query, only_active=active)
71
71
72 def maybe_skip_default_user(usr):
72 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
74 return False
75 return True
75 return True
76 _users = filter(maybe_skip_default_user, _users)
76 _users = filter(maybe_skip_default_user, _users)
77
77
78 if include_groups:
78 if include_groups:
79 # extend with user groups
79 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
80 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
81 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
82 expand_groups=expand_groups)
83 _users = _users + _user_groups
83 _users = _users + _user_groups
84
84
85 return {'suggestions': _users}
85 return {'suggestions': _users}
86
86
87 @LoginRequired()
87 @LoginRequired()
88 @NotAnonymous()
88 @NotAnonymous()
89 @view_config(
89 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
90 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
91 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
92 def user_group_autocomplete_data(self):
93 self.load_default_context()
93 self.load_default_context()
94 query = self.request.GET.get('query')
94 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
95 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
97
98 log.debug('generating user group list, query:%s, active:%s',
98 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
99 query, active)
100
100
101 _user_groups = UserGroupModel().get_user_groups(
101 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
102 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
103 expand_groups=expand_groups)
104 _user_groups = _user_groups
104 _user_groups = _user_groups
105
105
106 return {'suggestions': _user_groups}
106 return {'suggestions': _user_groups}
107
107
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
109 org_query = name_contains
109 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
111 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
112 cache=False, name_filter=name_contains) or [-1]
113
113
114 query = Repository.query()\
114 query = Repository.query()\
115 .filter(Repository.archived.isnot(true()))\
115 .filter(Repository.archived.isnot(true()))\
116 .filter(or_(
116 .filter(or_(
117 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
118 *in_filter_generator(Repository.repo_id, allowed_ids)
118 *in_filter_generator(Repository.repo_id, allowed_ids)
119 ))
119 ))
120
120
121 query = query.order_by(case(
121 query = query.order_by(case(
122 [
122 [
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 ],
124 ],
125 ))
125 ))
126 query = query.order_by(func.length(Repository.repo_name))
126 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(Repository.repo_name)
127 query = query.order_by(Repository.repo_name)
128
128
129 if repo_type:
129 if repo_type:
130 query = query.filter(Repository.repo_type == repo_type)
130 query = query.filter(Repository.repo_type == repo_type)
131
131
132 if name_contains:
132 if name_contains:
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
134 query = query.filter(
134 query = query.filter(
135 Repository.repo_name.ilike(ilike_expression))
135 Repository.repo_name.ilike(ilike_expression))
136 query = query.limit(limit)
136 query = query.limit(limit)
137
137
138 acl_iter = query
138 acl_iter = query
139
139
140 return [
140 return [
141 {
141 {
142 'id': obj.repo_name,
142 'id': obj.repo_name,
143 'value': org_query,
143 'value': org_query,
144 'value_display': obj.repo_name,
144 'value_display': obj.repo_name,
145 'text': obj.repo_name,
145 'text': obj.repo_name,
146 'type': 'repo',
146 'type': 'repo',
147 'repo_id': obj.repo_id,
147 'repo_id': obj.repo_id,
148 'repo_type': obj.repo_type,
148 'repo_type': obj.repo_type,
149 'private': obj.private,
149 'private': obj.private,
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
151 }
151 }
152 for obj in acl_iter]
152 for obj in acl_iter]
153
153
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
155 org_query = name_contains
155 org_query = name_contains
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
157 ['group.read', 'group.write', 'group.admin'],
157 ['group.read', 'group.write', 'group.admin'],
158 cache=False, name_filter=name_contains) or [-1]
158 cache=False, name_filter=name_contains) or [-1]
159
159
160 query = RepoGroup.query()\
160 query = RepoGroup.query()\
161 .filter(or_(
161 .filter(or_(
162 # generate multiple IN to fix limitation problems
162 # generate multiple IN to fix limitation problems
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 ))
164 ))
165
165
166 query = query.order_by(case(
166 query = query.order_by(case(
167 [
167 [
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 ],
169 ],
170 ))
170 ))
171 query = query.order_by(func.length(RepoGroup.group_name))
171 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(RepoGroup.group_name)
172 query = query.order_by(RepoGroup.group_name)
173
173
174 if name_contains:
174 if name_contains:
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
176 query = query.filter(
176 query = query.filter(
177 RepoGroup.group_name.ilike(ilike_expression))
177 RepoGroup.group_name.ilike(ilike_expression))
178 query = query.limit(limit)
178 query = query.limit(limit)
179
179
180 acl_iter = query
180 acl_iter = query
181
181
182 return [
182 return [
183 {
183 {
184 'id': obj.group_name,
184 'id': obj.group_name,
185 'value': org_query,
185 'value': org_query,
186 'value_display': obj.group_name,
186 'value_display': obj.group_name,
187 'text': obj.group_name,
187 'text': obj.group_name,
188 'type': 'repo_group',
188 'type': 'repo_group',
189 'repo_group_id': obj.group_id,
189 'repo_group_id': obj.group_id,
190 'url': h.route_path(
190 'url': h.route_path(
191 'repo_group_home', repo_group_name=obj.group_name)
191 'repo_group_home', repo_group_name=obj.group_name)
192 }
192 }
193 for obj in acl_iter]
193 for obj in acl_iter]
194
194
195 def _get_user_list(self, name_contains=None, limit=20):
195 def _get_user_list(self, name_contains=None, limit=20):
196 org_query = name_contains
196 org_query = name_contains
197 if not name_contains:
197 if not name_contains:
198 return [], False
198 return [], False
199
199
200 # TODO(marcink): should all logged in users be allowed to search others?
200 # TODO(marcink): should all logged in users be allowed to search others?
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 if not allowed_user_search:
202 if not allowed_user_search:
203 return [], False
203 return [], False
204
204
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
206 if len(name_contains) != 1:
206 if len(name_contains) != 1:
207 return [], False
207 return [], False
208
208
209 name_contains = name_contains[0]
209 name_contains = name_contains[0]
210
210
211 query = User.query()\
211 query = User.query()\
212 .order_by(func.length(User.username))\
212 .order_by(func.length(User.username))\
213 .order_by(User.username) \
213 .order_by(User.username) \
214 .filter(User.username != User.DEFAULT_USER)
214 .filter(User.username != User.DEFAULT_USER)
215
215
216 if name_contains:
216 if name_contains:
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
218 query = query.filter(
218 query = query.filter(
219 User.username.ilike(ilike_expression))
219 User.username.ilike(ilike_expression))
220 query = query.limit(limit)
220 query = query.limit(limit)
221
221
222 acl_iter = query
222 acl_iter = query
223
223
224 return [
224 return [
225 {
225 {
226 'id': obj.user_id,
226 'id': obj.user_id,
227 'value': org_query,
227 'value': org_query,
228 'value_display': 'user: `{}`'.format(obj.username),
228 'value_display': 'user: `{}`'.format(obj.username),
229 'type': 'user',
229 'type': 'user',
230 'icon_link': h.gravatar_url(obj.email, 30),
230 'icon_link': h.gravatar_url(obj.email, 30),
231 'url': h.route_path(
231 'url': h.route_path(
232 'user_profile', username=obj.username)
232 'user_profile', username=obj.username)
233 }
233 }
234 for obj in acl_iter], True
234 for obj in acl_iter], True
235
235
236 def _get_user_groups_list(self, name_contains=None, limit=20):
236 def _get_user_groups_list(self, name_contains=None, limit=20):
237 org_query = name_contains
237 org_query = name_contains
238 if not name_contains:
238 if not name_contains:
239 return [], False
239 return [], False
240
240
241 # TODO(marcink): should all logged in users be allowed to search others?
241 # TODO(marcink): should all logged in users be allowed to search others?
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 if not allowed_user_search:
243 if not allowed_user_search:
244 return [], False
244 return [], False
245
245
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
247 if len(name_contains) != 1:
247 if len(name_contains) != 1:
248 return [], False
248 return [], False
249
249
250 name_contains = name_contains[0]
250 name_contains = name_contains[0]
251
251
252 query = UserGroup.query()\
252 query = UserGroup.query()\
253 .order_by(func.length(UserGroup.users_group_name))\
253 .order_by(func.length(UserGroup.users_group_name))\
254 .order_by(UserGroup.users_group_name)
254 .order_by(UserGroup.users_group_name)
255
255
256 if name_contains:
256 if name_contains:
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
258 query = query.filter(
258 query = query.filter(
259 UserGroup.users_group_name.ilike(ilike_expression))
259 UserGroup.users_group_name.ilike(ilike_expression))
260 query = query.limit(limit)
260 query = query.limit(limit)
261
261
262 acl_iter = query
262 acl_iter = query
263
263
264 return [
264 return [
265 {
265 {
266 'id': obj.users_group_id,
266 'id': obj.users_group_id,
267 'value': org_query,
267 'value': org_query,
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
269 'type': 'user_group',
269 'type': 'user_group',
270 'url': h.route_path(
270 'url': h.route_path(
271 'user_group_profile', user_group_name=obj.users_group_name)
271 'user_group_profile', user_group_name=obj.users_group_name)
272 }
272 }
273 for obj in acl_iter], True
273 for obj in acl_iter], True
274
274
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 repo_name = repo_group_name = None
276 repo_name = repo_group_name = None
277 if repo:
277 if repo:
278 repo_name = repo.repo_name
278 repo_name = repo.repo_name
279 if repo_group:
279 if repo_group:
280 repo_group_name = repo_group.group_name
280 repo_group_name = repo_group.group_name
281
281
282 org_query = query
282 org_query = query
283 if not query or len(query) < 3 or not searcher:
283 if not query or len(query) < 3 or not searcher:
284 return [], False
284 return [], False
285
285
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
287
287
288 if len(commit_hashes) != 1:
288 if len(commit_hashes) != 1:
289 return [], False
289 return [], False
290
290
291 commit_hash = commit_hashes[0]
291 commit_hash = commit_hashes[0]
292
292
293 result = searcher.search(
293 result = searcher.search(
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
295 repo_name, repo_group_name, raise_on_exc=False)
295 repo_name, repo_group_name, raise_on_exc=False)
296
296
297 commits = []
297 commits = []
298 for entry in result['results']:
298 for entry in result['results']:
299 repo_data = {
299 repo_data = {
300 'repository_id': entry.get('repository_id'),
300 'repository_id': entry.get('repository_id'),
301 'repository_type': entry.get('repo_type'),
301 'repository_type': entry.get('repo_type'),
302 'repository_name': entry.get('repository'),
302 'repository_name': entry.get('repository'),
303 }
303 }
304
304
305 commit_entry = {
305 commit_entry = {
306 'id': entry['commit_id'],
306 'id': entry['commit_id'],
307 'value': org_query,
307 'value': org_query,
308 'value_display': '`{}` commit: {}'.format(
308 'value_display': '`{}` commit: {}'.format(
309 entry['repository'], entry['commit_id']),
309 entry['repository'], entry['commit_id']),
310 'type': 'commit',
310 'type': 'commit',
311 'repo': entry['repository'],
311 'repo': entry['repository'],
312 'repo_data': repo_data,
312 'repo_data': repo_data,
313
313
314 'url': h.route_path(
314 'url': h.route_path(
315 'repo_commit',
315 'repo_commit',
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
317 }
317 }
318
318
319 commits.append(commit_entry)
319 commits.append(commit_entry)
320 return commits, True
320 return commits, True
321
321
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 repo_name = repo_group_name = None
323 repo_name = repo_group_name = None
324 if repo:
324 if repo:
325 repo_name = repo.repo_name
325 repo_name = repo.repo_name
326 if repo_group:
326 if repo_group:
327 repo_group_name = repo_group.group_name
327 repo_group_name = repo_group.group_name
328
328
329 org_query = query
329 org_query = query
330 if not query or len(query) < 3 or not searcher:
330 if not query or len(query) < 3 or not searcher:
331 return [], False
331 return [], False
332
332
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
334 if len(paths_re) != 1:
334 if len(paths_re) != 1:
335 return [], False
335 return [], False
336
336
337 file_path = paths_re[0]
337 file_path = paths_re[0]
338
338
339 search_path = searcher.escape_specials(file_path)
339 search_path = searcher.escape_specials(file_path)
340 result = searcher.search(
340 result = searcher.search(
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
342 repo_name, repo_group_name, raise_on_exc=False)
342 repo_name, repo_group_name, raise_on_exc=False)
343
343
344 files = []
344 files = []
345 for entry in result['results']:
345 for entry in result['results']:
346 repo_data = {
346 repo_data = {
347 'repository_id': entry.get('repository_id'),
347 'repository_id': entry.get('repository_id'),
348 'repository_type': entry.get('repo_type'),
348 'repository_type': entry.get('repo_type'),
349 'repository_name': entry.get('repository'),
349 'repository_name': entry.get('repository'),
350 }
350 }
351
351
352 file_entry = {
352 file_entry = {
353 'id': entry['commit_id'],
353 'id': entry['commit_id'],
354 'value': org_query,
354 'value': org_query,
355 'value_display': '`{}` file: {}'.format(
355 'value_display': '`{}` file: {}'.format(
356 entry['repository'], entry['file']),
356 entry['repository'], entry['file']),
357 'type': 'file',
357 'type': 'file',
358 'repo': entry['repository'],
358 'repo': entry['repository'],
359 'repo_data': repo_data,
359 'repo_data': repo_data,
360
360
361 'url': h.route_path(
361 'url': h.route_path(
362 'repo_files',
362 'repo_files',
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
364 f_path=entry['file'])
364 f_path=entry['file'])
365 }
365 }
366
366
367 files.append(file_entry)
367 files.append(file_entry)
368 return files, True
368 return files, True
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @view_config(
371 @view_config(
372 route_name='repo_list_data', request_method='GET',
372 route_name='repo_list_data', request_method='GET',
373 renderer='json_ext', xhr=True)
373 renderer='json_ext', xhr=True)
374 def repo_list_data(self):
374 def repo_list_data(self):
375 _ = self.request.translate
375 _ = self.request.translate
376 self.load_default_context()
376 self.load_default_context()
377
377
378 query = self.request.GET.get('query')
378 query = self.request.GET.get('query')
379 repo_type = self.request.GET.get('repo_type')
379 repo_type = self.request.GET.get('repo_type')
380 log.debug('generating repo list, query:%s, repo_type:%s',
380 log.debug('generating repo list, query:%s, repo_type:%s',
381 query, repo_type)
381 query, repo_type)
382
382
383 res = []
383 res = []
384 repos = self._get_repo_list(query, repo_type=repo_type)
384 repos = self._get_repo_list(query, repo_type=repo_type)
385 if repos:
385 if repos:
386 res.append({
386 res.append({
387 'text': _('Repositories'),
387 'text': _('Repositories'),
388 'children': repos
388 'children': repos
389 })
389 })
390
390
391 data = {
391 data = {
392 'more': False,
392 'more': False,
393 'results': res
393 'results': res
394 }
394 }
395 return data
395 return data
396
396
397 @LoginRequired()
397 @LoginRequired()
398 @view_config(
398 @view_config(
399 route_name='repo_group_list_data', request_method='GET',
399 route_name='repo_group_list_data', request_method='GET',
400 renderer='json_ext', xhr=True)
400 renderer='json_ext', xhr=True)
401 def repo_group_list_data(self):
401 def repo_group_list_data(self):
402 _ = self.request.translate
402 _ = self.request.translate
403 self.load_default_context()
403 self.load_default_context()
404
404
405 query = self.request.GET.get('query')
405 query = self.request.GET.get('query')
406
406
407 log.debug('generating repo group list, query:%s',
407 log.debug('generating repo group list, query:%s',
408 query)
408 query)
409
409
410 res = []
410 res = []
411 repo_groups = self._get_repo_group_list(query)
411 repo_groups = self._get_repo_group_list(query)
412 if repo_groups:
412 if repo_groups:
413 res.append({
413 res.append({
414 'text': _('Repository Groups'),
414 'text': _('Repository Groups'),
415 'children': repo_groups
415 'children': repo_groups
416 })
416 })
417
417
418 data = {
418 data = {
419 'more': False,
419 'more': False,
420 'results': res
420 'results': res
421 }
421 }
422 return data
422 return data
423
423
424 def _get_default_search_queries(self, search_context, searcher, query):
424 def _get_default_search_queries(self, search_context, searcher, query):
425 if not searcher:
425 if not searcher:
426 return []
426 return []
427
427
428 is_es_6 = searcher.is_es_6
428 is_es_6 = searcher.is_es_6
429
429
430 queries = []
430 queries = []
431 repo_group_name, repo_name, repo_context = None, None, None
431 repo_group_name, repo_name, repo_context = None, None, None
432
432
433 # repo group context
433 # repo group context
434 if search_context.get('search_context[repo_group_name]'):
434 if search_context.get('search_context[repo_group_name]'):
435 repo_group_name = search_context.get('search_context[repo_group_name]')
435 repo_group_name = search_context.get('search_context[repo_group_name]')
436 if search_context.get('search_context[repo_name]'):
436 if search_context.get('search_context[repo_name]'):
437 repo_name = search_context.get('search_context[repo_name]')
437 repo_name = search_context.get('search_context[repo_name]')
438 repo_context = search_context.get('search_context[repo_view_type]')
438 repo_context = search_context.get('search_context[repo_view_type]')
439
439
440 if is_es_6 and repo_name:
440 if is_es_6 and repo_name:
441 # files
441 # files
442 def query_modifier():
442 def query_modifier():
443 qry = query
443 qry = query
444 return {'q': qry, 'type': 'content'}
444 return {'q': qry, 'type': 'content'}
445 label = u'File search for `{}` in this repository.'.format(query)
445 label = u'File search for `{}` in this repository.'.format(query)
446 file_qry = {
446 file_qry = {
447 'id': -10,
447 'id': -10,
448 'value': query,
448 'value': query,
449 'value_display': label,
449 'value_display': label,
450 'type': 'search',
450 'type': 'search',
451 'url': h.route_path('search_repo',
451 'url': h.route_path('search_repo',
452 repo_name=repo_name,
452 repo_name=repo_name,
453 _query=query_modifier())
453 _query=query_modifier())
454 }
454 }
455
455
456 # commits
456 # commits
457 def query_modifier():
457 def query_modifier():
458 qry = query
458 qry = query
459 return {'q': qry, 'type': 'commit'}
459 return {'q': qry, 'type': 'commit'}
460
460
461 label = u'Commit search for `{}` in this repository.'.format(query)
461 label = u'Commit search for `{}` in this repository.'.format(query)
462 commit_qry = {
462 commit_qry = {
463 'id': -20,
463 'id': -20,
464 'value': query,
464 'value': query,
465 'value_display': label,
465 'value_display': label,
466 'type': 'search',
466 'type': 'search',
467 'url': h.route_path('search_repo',
467 'url': h.route_path('search_repo',
468 repo_name=repo_name,
468 repo_name=repo_name,
469 _query=query_modifier())
469 _query=query_modifier())
470 }
470 }
471
471
472 if repo_context in ['commit', 'changelog']:
472 if repo_context in ['commit', 'changelog']:
473 queries.extend([commit_qry, file_qry])
473 queries.extend([commit_qry, file_qry])
474 elif repo_context in ['files', 'summary']:
474 elif repo_context in ['files', 'summary']:
475 queries.extend([file_qry, commit_qry])
475 queries.extend([file_qry, commit_qry])
476 else:
476 else:
477 queries.extend([commit_qry, file_qry])
477 queries.extend([commit_qry, file_qry])
478
478
479 elif is_es_6 and repo_group_name:
479 elif is_es_6 and repo_group_name:
480 # files
480 # files
481 def query_modifier():
481 def query_modifier():
482 qry = query
482 qry = query
483 return {'q': qry, 'type': 'content'}
483 return {'q': qry, 'type': 'content'}
484
484
485 label = u'File search for `{}` in this repository group'.format(query)
485 label = u'File search for `{}` in this repository group'.format(query)
486 file_qry = {
486 file_qry = {
487 'id': -30,
487 'id': -30,
488 'value': query,
488 'value': query,
489 'value_display': label,
489 'value_display': label,
490 'type': 'search',
490 'type': 'search',
491 'url': h.route_path('search_repo_group',
491 'url': h.route_path('search_repo_group',
492 repo_group_name=repo_group_name,
492 repo_group_name=repo_group_name,
493 _query=query_modifier())
493 _query=query_modifier())
494 }
494 }
495
495
496 # commits
496 # commits
497 def query_modifier():
497 def query_modifier():
498 qry = query
498 qry = query
499 return {'q': qry, 'type': 'commit'}
499 return {'q': qry, 'type': 'commit'}
500
500
501 label = u'Commit search for `{}` in this repository group'.format(query)
501 label = u'Commit search for `{}` in this repository group'.format(query)
502 commit_qry = {
502 commit_qry = {
503 'id': -40,
503 'id': -40,
504 'value': query,
504 'value': query,
505 'value_display': label,
505 'value_display': label,
506 'type': 'search',
506 'type': 'search',
507 'url': h.route_path('search_repo_group',
507 'url': h.route_path('search_repo_group',
508 repo_group_name=repo_group_name,
508 repo_group_name=repo_group_name,
509 _query=query_modifier())
509 _query=query_modifier())
510 }
510 }
511
511
512 if repo_context in ['commit', 'changelog']:
512 if repo_context in ['commit', 'changelog']:
513 queries.extend([commit_qry, file_qry])
513 queries.extend([commit_qry, file_qry])
514 elif repo_context in ['files', 'summary']:
514 elif repo_context in ['files', 'summary']:
515 queries.extend([file_qry, commit_qry])
515 queries.extend([file_qry, commit_qry])
516 else:
516 else:
517 queries.extend([commit_qry, file_qry])
517 queries.extend([commit_qry, file_qry])
518
518
519 # Global, not scoped
519 # Global, not scoped
520 if not queries:
520 if not queries:
521 queries.append(
521 queries.append(
522 {
522 {
523 'id': -1,
523 'id': -1,
524 'value': query,
524 'value': query,
525 'value_display': u'File search for: `{}`'.format(query),
525 'value_display': u'File search for: `{}`'.format(query),
526 'type': 'search',
526 'type': 'search',
527 'url': h.route_path('search',
527 'url': h.route_path('search',
528 _query={'q': query, 'type': 'content'})
528 _query={'q': query, 'type': 'content'})
529 })
529 })
530 queries.append(
530 queries.append(
531 {
531 {
532 'id': -2,
532 'id': -2,
533 'value': query,
533 'value': query,
534 'value_display': u'Commit search for: `{}`'.format(query),
534 'value_display': u'Commit search for: `{}`'.format(query),
535 'type': 'search',
535 'type': 'search',
536 'url': h.route_path('search',
536 'url': h.route_path('search',
537 _query={'q': query, 'type': 'commit'})
537 _query={'q': query, 'type': 'commit'})
538 })
538 })
539
539
540 return queries
540 return queries
541
541
542 @LoginRequired()
542 @LoginRequired()
543 @view_config(
543 @view_config(
544 route_name='goto_switcher_data', request_method='GET',
544 route_name='goto_switcher_data', request_method='GET',
545 renderer='json_ext', xhr=True)
545 renderer='json_ext', xhr=True)
546 def goto_switcher_data(self):
546 def goto_switcher_data(self):
547 c = self.load_default_context()
547 c = self.load_default_context()
548
548
549 _ = self.request.translate
549 _ = self.request.translate
550
550
551 query = self.request.GET.get('query')
551 query = self.request.GET.get('query')
552 log.debug('generating main filter data, query %s', query)
552 log.debug('generating main filter data, query %s', query)
553
553
554 res = []
554 res = []
555 if not query:
555 if not query:
556 return {'suggestions': res}
556 return {'suggestions': res}
557
557
558 def no_match(name):
558 def no_match(name):
559 return {
559 return {
560 'id': -1,
560 'id': -1,
561 'value': "",
561 'value': "",
562 'value_display': name,
562 'value_display': name,
563 'type': 'text',
563 'type': 'text',
564 'url': ""
564 'url': ""
565 }
565 }
566 searcher = searcher_from_config(self.request.registry.settings)
566 searcher = searcher_from_config(self.request.registry.settings)
567 has_specialized_search = False
567 has_specialized_search = False
568
568
569 # set repo context
569 # set repo context
570 repo = None
570 repo = None
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
572 if repo_id:
572 if repo_id:
573 repo = Repository.get(repo_id)
573 repo = Repository.get(repo_id)
574
574
575 # set group context
575 # set group context
576 repo_group = None
576 repo_group = None
577 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
577 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
578 if repo_group_id:
578 if repo_group_id:
579 repo_group = RepoGroup.get(repo_group_id)
579 repo_group = RepoGroup.get(repo_group_id)
580 prefix_match = False
580 prefix_match = False
581
581
582 # user: type search
582 # user: type search
583 if not prefix_match:
583 if not prefix_match:
584 users, prefix_match = self._get_user_list(query)
584 users, prefix_match = self._get_user_list(query)
585 if users:
585 if users:
586 has_specialized_search = True
586 has_specialized_search = True
587 for serialized_user in users:
587 for serialized_user in users:
588 res.append(serialized_user)
588 res.append(serialized_user)
589 elif prefix_match:
589 elif prefix_match:
590 has_specialized_search = True
590 has_specialized_search = True
591 res.append(no_match('No matching users found'))
591 res.append(no_match('No matching users found'))
592
592
593 # user_group: type search
593 # user_group: type search
594 if not prefix_match:
594 if not prefix_match:
595 user_groups, prefix_match = self._get_user_groups_list(query)
595 user_groups, prefix_match = self._get_user_groups_list(query)
596 if user_groups:
596 if user_groups:
597 has_specialized_search = True
597 has_specialized_search = True
598 for serialized_user_group in user_groups:
598 for serialized_user_group in user_groups:
599 res.append(serialized_user_group)
599 res.append(serialized_user_group)
600 elif prefix_match:
600 elif prefix_match:
601 has_specialized_search = True
601 has_specialized_search = True
602 res.append(no_match('No matching user groups found'))
602 res.append(no_match('No matching user groups found'))
603
603
604 # FTS commit: type search
604 # FTS commit: type search
605 if not prefix_match:
605 if not prefix_match:
606 commits, prefix_match = self._get_hash_commit_list(
606 commits, prefix_match = self._get_hash_commit_list(
607 c.auth_user, searcher, query, repo, repo_group)
607 c.auth_user, searcher, query, repo, repo_group)
608 if commits:
608 if commits:
609 has_specialized_search = True
609 has_specialized_search = True
610 unique_repos = collections.OrderedDict()
610 unique_repos = collections.OrderedDict()
611 for commit in commits:
611 for commit in commits:
612 repo_name = commit['repo']
612 repo_name = commit['repo']
613 unique_repos.setdefault(repo_name, []).append(commit)
613 unique_repos.setdefault(repo_name, []).append(commit)
614
614
615 for _repo, commits in unique_repos.items():
615 for _repo, commits in unique_repos.items():
616 for commit in commits:
616 for commit in commits:
617 res.append(commit)
617 res.append(commit)
618 elif prefix_match:
618 elif prefix_match:
619 has_specialized_search = True
619 has_specialized_search = True
620 res.append(no_match('No matching commits found'))
620 res.append(no_match('No matching commits found'))
621
621
622 # FTS file: type search
622 # FTS file: type search
623 if not prefix_match:
623 if not prefix_match:
624 paths, prefix_match = self._get_path_list(
624 paths, prefix_match = self._get_path_list(
625 c.auth_user, searcher, query, repo, repo_group)
625 c.auth_user, searcher, query, repo, repo_group)
626 if paths:
626 if paths:
627 has_specialized_search = True
627 has_specialized_search = True
628 unique_repos = collections.OrderedDict()
628 unique_repos = collections.OrderedDict()
629 for path in paths:
629 for path in paths:
630 repo_name = path['repo']
630 repo_name = path['repo']
631 unique_repos.setdefault(repo_name, []).append(path)
631 unique_repos.setdefault(repo_name, []).append(path)
632
632
633 for repo, paths in unique_repos.items():
633 for repo, paths in unique_repos.items():
634 for path in paths:
634 for path in paths:
635 res.append(path)
635 res.append(path)
636 elif prefix_match:
636 elif prefix_match:
637 has_specialized_search = True
637 has_specialized_search = True
638 res.append(no_match('No matching files found'))
638 res.append(no_match('No matching files found'))
639
639
640 # main suggestions
640 # main suggestions
641 if not has_specialized_search:
641 if not has_specialized_search:
642 repo_group_name = ''
642 repo_group_name = ''
643 if repo_group:
643 if repo_group:
644 repo_group_name = repo_group.group_name
644 repo_group_name = repo_group.group_name
645
645
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
647 res.append(_q)
647 res.append(_q)
648
648
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
650 for serialized_repo_group in repo_groups:
650 for serialized_repo_group in repo_groups:
651 res.append(serialized_repo_group)
651 res.append(serialized_repo_group)
652
652
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
654 for serialized_repo in repos:
654 for serialized_repo in repos:
655 res.append(serialized_repo)
655 res.append(serialized_repo)
656
656
657 if not repos and not repo_groups:
657 if not repos and not repo_groups:
658 res.append(no_match('No matches found'))
658 res.append(no_match('No matches found'))
659
659
660 return {'suggestions': res}
660 return {'suggestions': res}
661
661
662 def _get_groups_and_repos(self, repo_group_id=None):
662 def _get_groups_and_repos(self, repo_group_id=None):
663 # repo groups groups
663 # repo groups groups
664 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
664 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
665 _perms = ['group.read', 'group.write', 'group.admin']
665 _perms = ['group.read', 'group.write', 'group.admin']
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
668 repo_group_list=repo_group_list_acl, admin=False)
668 repo_group_list=repo_group_list_acl, admin=False)
669
669
670 # repositories
670 # repositories
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
674 repo_data = RepoModel().get_repos_as_dict(
674 repo_data = RepoModel().get_repos_as_dict(
675 repo_list=repo_list_acl, admin=False)
675 repo_list=repo_list_acl, admin=False)
676
676
677 return repo_data, repo_group_data
677 return repo_data, repo_group_data
678
678
679 @LoginRequired()
679 @LoginRequired()
680 @view_config(
680 @view_config(
681 route_name='home', request_method='GET',
681 route_name='home', request_method='GET',
682 renderer='rhodecode:templates/index.mako')
682 renderer='rhodecode:templates/index.mako')
683 def main_page(self):
683 def main_page(self):
684 c = self.load_default_context()
684 c = self.load_default_context()
685 c.repo_group = None
685 c.repo_group = None
686
686
687 repo_data, repo_group_data = self._get_groups_and_repos()
687 repo_data, repo_group_data = self._get_groups_and_repos()
688 # json used to render the grids
688 # json used to render the grids
689 c.repos_data = json.dumps(repo_data)
689 c.repos_data = json.dumps(repo_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
691
691
692 return self._get_template_context(c)
692 return self._get_template_context(c)
693
693
694 @LoginRequired()
694 @LoginRequired()
695 @HasRepoGroupPermissionAnyDecorator(
695 @HasRepoGroupPermissionAnyDecorator(
696 'group.read', 'group.write', 'group.admin')
696 'group.read', 'group.write', 'group.admin')
697 @view_config(
697 @view_config(
698 route_name='repo_group_home', request_method='GET',
698 route_name='repo_group_home', request_method='GET',
699 renderer='rhodecode:templates/index_repo_group.mako')
699 renderer='rhodecode:templates/index_repo_group.mako')
700 @view_config(
700 @view_config(
701 route_name='repo_group_home_slash', request_method='GET',
701 route_name='repo_group_home_slash', request_method='GET',
702 renderer='rhodecode:templates/index_repo_group.mako')
702 renderer='rhodecode:templates/index_repo_group.mako')
703 def repo_group_main_page(self):
703 def repo_group_main_page(self):
704 c = self.load_default_context()
704 c = self.load_default_context()
705 c.repo_group = self.request.db_repo_group
705 c.repo_group = self.request.db_repo_group
706 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
706 repo_data, repo_group_data = self._get_groups_and_repos(c.repo_group.group_id)
707
707
708 c.repo_group.update_commit_cache()
708 # update every 5 min
709 if self.request.db_repo_group.last_commit_cache_update_diff > 60 * 5:
710 self.request.db_repo_group.update_commit_cache()
709
711
710 # json used to render the grids
712 # json used to render the grids
711 c.repos_data = json.dumps(repo_data)
713 c.repos_data = json.dumps(repo_data)
712 c.repo_groups_data = json.dumps(repo_group_data)
714 c.repo_groups_data = json.dumps(repo_group_data)
713
715
714 return self._get_template_context(c)
716 return self._get_template_context(c)
715
717
716 @LoginRequired()
718 @LoginRequired()
717 @CSRFRequired()
719 @CSRFRequired()
718 @view_config(
720 @view_config(
719 route_name='markup_preview', request_method='POST',
721 route_name='markup_preview', request_method='POST',
720 renderer='string', xhr=True)
722 renderer='string', xhr=True)
721 def markup_preview(self):
723 def markup_preview(self):
722 # Technically a CSRF token is not needed as no state changes with this
724 # Technically a CSRF token is not needed as no state changes with this
723 # call. However, as this is a POST is better to have it, so automated
725 # call. However, as this is a POST is better to have it, so automated
724 # tools don't flag it as potential CSRF.
726 # tools don't flag it as potential CSRF.
725 # Post is required because the payload could be bigger than the maximum
727 # Post is required because the payload could be bigger than the maximum
726 # allowed by GET.
728 # allowed by GET.
727
729
728 text = self.request.POST.get('text')
730 text = self.request.POST.get('text')
729 renderer = self.request.POST.get('renderer') or 'rst'
731 renderer = self.request.POST.get('renderer') or 'rst'
730 if text:
732 if text:
731 return h.render(text, renderer=renderer, mentions=True)
733 return h.render(text, renderer=renderer, mentions=True)
732 return ''
734 return ''
733
735
734 @LoginRequired()
736 @LoginRequired()
735 @CSRFRequired()
737 @CSRFRequired()
736 @view_config(
738 @view_config(
737 route_name='store_user_session_value', request_method='POST',
739 route_name='store_user_session_value', request_method='POST',
738 renderer='string', xhr=True)
740 renderer='string', xhr=True)
739 def store_user_session_attr(self):
741 def store_user_session_attr(self):
740 key = self.request.POST.get('key')
742 key = self.request.POST.get('key')
741 val = self.request.POST.get('val')
743 val = self.request.POST.get('val')
742
744
743 existing_value = self.request.session.get(key)
745 existing_value = self.request.session.get(key)
744 if existing_value != val:
746 if existing_value != val:
745 self.request.session[key] = val
747 self.request.session[key] = val
746
748
747 return 'stored:{}:{}'.format(key, val)
749 return 'stored:{}:{}'.format(key, val)
@@ -1,390 +1,394 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23 import rhodecode
23 import rhodecode
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.lib.view_utils import get_format_ref_id
27 from rhodecode.lib.view_utils import get_format_ref_id
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import helpers as h, rc_cache
30 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib.utils2 import safe_str, safe_int
31 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import (
36 from rhodecode.lib.vcs.exceptions import (
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 from rhodecode.model.db import Statistics, CacheKey, User
38 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
50 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
53 return c
54
54
55 def _get_readme_data(self, db_repo, renderer_type):
55 def _get_readme_data(self, db_repo, renderer_type):
56
56
57 log.debug('Looking for README file')
57 log.debug('Looking for README file')
58
58
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
62 repo_id=self.db_repo.repo_id)
62 repo_id=self.db_repo.repo_id)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
64
64
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
67 readme_data = None
67 readme_data = None
68 readme_node = None
68 readme_node = None
69 readme_filename = None
69 readme_filename = None
70 commit = self._get_landing_commit_or_none(db_repo)
70 commit = self._get_landing_commit_or_none(db_repo)
71 if commit:
71 if commit:
72 log.debug("Searching for a README file.")
72 log.debug("Searching for a README file.")
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
74 if readme_node:
74 if readme_node:
75 relative_urls = {
75 relative_urls = {
76 'raw': h.route_path(
76 'raw': h.route_path(
77 'repo_file_raw', repo_name=_repo_name,
77 'repo_file_raw', repo_name=_repo_name,
78 commit_id=commit.raw_id, f_path=readme_node.path),
78 commit_id=commit.raw_id, f_path=readme_node.path),
79 'standard': h.route_path(
79 'standard': h.route_path(
80 'repo_files', repo_name=_repo_name,
80 'repo_files', repo_name=_repo_name,
81 commit_id=commit.raw_id, f_path=readme_node.path),
81 commit_id=commit.raw_id, f_path=readme_node.path),
82 }
82 }
83 readme_data = self._render_readme_or_none(
83 readme_data = self._render_readme_or_none(
84 commit, readme_node, relative_urls)
84 commit, readme_node, relative_urls)
85 readme_filename = readme_node.path
85 readme_filename = readme_node.path
86 return readme_data, readme_filename
86 return readme_data, readme_filename
87
87
88 inv_context_manager = rc_cache.InvalidationContext(
88 inv_context_manager = rc_cache.InvalidationContext(
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
90 with inv_context_manager as invalidation_context:
90 with inv_context_manager as invalidation_context:
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
92 # re-compute and store cache if we get invalidate signal
92 # re-compute and store cache if we get invalidate signal
93 if invalidation_context.should_invalidate():
93 if invalidation_context.should_invalidate():
94 instance = generate_repo_readme.refresh(*args)
94 instance = generate_repo_readme.refresh(*args)
95 else:
95 else:
96 instance = generate_repo_readme(*args)
96 instance = generate_repo_readme(*args)
97
97
98 log.debug(
98 log.debug(
99 'Repo readme generated and computed in %.3fs',
99 'Repo readme generated and computed in %.3fs',
100 inv_context_manager.compute_time)
100 inv_context_manager.compute_time)
101 return instance
101 return instance
102
102
103 def _get_landing_commit_or_none(self, db_repo):
103 def _get_landing_commit_or_none(self, db_repo):
104 log.debug("Getting the landing commit.")
104 log.debug("Getting the landing commit.")
105 try:
105 try:
106 commit = db_repo.get_landing_commit()
106 commit = db_repo.get_landing_commit()
107 if not isinstance(commit, EmptyCommit):
107 if not isinstance(commit, EmptyCommit):
108 return commit
108 return commit
109 else:
109 else:
110 log.debug("Repository is empty, no README to render.")
110 log.debug("Repository is empty, no README to render.")
111 except CommitError:
111 except CommitError:
112 log.exception(
112 log.exception(
113 "Problem getting commit when trying to render the README.")
113 "Problem getting commit when trying to render the README.")
114
114
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
116 log.debug(
116 log.debug(
117 'Found README file `%s` rendering...', readme_node.path)
117 'Found README file `%s` rendering...', readme_node.path)
118 renderer = MarkupRenderer()
118 renderer = MarkupRenderer()
119 try:
119 try:
120 html_source = renderer.render(
120 html_source = renderer.render(
121 readme_node.content, filename=readme_node.path)
121 readme_node.content, filename=readme_node.path)
122 if relative_urls:
122 if relative_urls:
123 return relative_links(html_source, relative_urls)
123 return relative_links(html_source, relative_urls)
124 return html_source
124 return html_source
125 except Exception:
125 except Exception:
126 log.exception(
126 log.exception(
127 "Exception while trying to render the README")
127 "Exception while trying to render the README")
128
128
129 def _load_commits_context(self, c):
129 def _load_commits_context(self, c):
130 p = safe_int(self.request.GET.get('page'), 1)
130 p = safe_int(self.request.GET.get('page'), 1)
131 size = safe_int(self.request.GET.get('size'), 10)
131 size = safe_int(self.request.GET.get('size'), 10)
132
132
133 def url_generator(**kw):
133 def url_generator(**kw):
134 query_params = {
134 query_params = {
135 'size': size
135 'size': size
136 }
136 }
137 query_params.update(kw)
137 query_params.update(kw)
138 return h.route_path(
138 return h.route_path(
139 'repo_summary_commits',
139 'repo_summary_commits',
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
141
141
142 pre_load = ['author', 'branch', 'date', 'message']
142 pre_load = ['author', 'branch', 'date', 'message']
143 try:
143 try:
144 collection = self.rhodecode_vcs_repo.get_commits(
144 collection = self.rhodecode_vcs_repo.get_commits(
145 pre_load=pre_load, translate_tags=False)
145 pre_load=pre_load, translate_tags=False)
146 except EmptyRepositoryError:
146 except EmptyRepositoryError:
147 collection = self.rhodecode_vcs_repo
147 collection = self.rhodecode_vcs_repo
148
148
149 c.repo_commits = h.RepoPage(
149 c.repo_commits = h.RepoPage(
150 collection, page=p, items_per_page=size, url=url_generator)
150 collection, page=p, items_per_page=size, url=url_generator)
151 page_ids = [x.raw_id for x in c.repo_commits]
151 page_ids = [x.raw_id for x in c.repo_commits]
152 c.comments = self.db_repo.get_comments(page_ids)
152 c.comments = self.db_repo.get_comments(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
154
154
155 def _prepare_and_set_clone_url(self, c):
155 def _prepare_and_set_clone_url(self, c):
156 username = ''
156 username = ''
157 if self._rhodecode_user.username != User.DEFAULT_USER:
157 if self._rhodecode_user.username != User.DEFAULT_USER:
158 username = safe_str(self._rhodecode_user.username)
158 username = safe_str(self._rhodecode_user.username)
159
159
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
162
162
163 if '{repo}' in _def_clone_uri:
163 if '{repo}' in _def_clone_uri:
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
165 elif '{repoid}' in _def_clone_uri:
165 elif '{repoid}' in _def_clone_uri:
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
167
167
168 c.clone_repo_url = self.db_repo.clone_url(
168 c.clone_repo_url = self.db_repo.clone_url(
169 user=username, uri_tmpl=_def_clone_uri)
169 user=username, uri_tmpl=_def_clone_uri)
170 c.clone_repo_url_id = self.db_repo.clone_url(
170 c.clone_repo_url_id = self.db_repo.clone_url(
171 user=username, uri_tmpl=_def_clone_uri_id)
171 user=username, uri_tmpl=_def_clone_uri_id)
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
174
174
175 @LoginRequired()
175 @LoginRequired()
176 @HasRepoPermissionAnyDecorator(
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
177 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
178 @view_config(
179 route_name='repo_summary_commits', request_method='GET',
179 route_name='repo_summary_commits', request_method='GET',
180 renderer='rhodecode:templates/summary/summary_commits.mako')
180 renderer='rhodecode:templates/summary/summary_commits.mako')
181 def summary_commits(self):
181 def summary_commits(self):
182 c = self.load_default_context()
182 c = self.load_default_context()
183 self._prepare_and_set_clone_url(c)
183 self._prepare_and_set_clone_url(c)
184 self._load_commits_context(c)
184 self._load_commits_context(c)
185 return self._get_template_context(c)
185 return self._get_template_context(c)
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasRepoPermissionAnyDecorator(
188 @HasRepoPermissionAnyDecorator(
189 'repository.read', 'repository.write', 'repository.admin')
189 'repository.read', 'repository.write', 'repository.admin')
190 @view_config(
190 @view_config(
191 route_name='repo_summary', request_method='GET',
191 route_name='repo_summary', request_method='GET',
192 renderer='rhodecode:templates/summary/summary.mako')
192 renderer='rhodecode:templates/summary/summary.mako')
193 @view_config(
193 @view_config(
194 route_name='repo_summary_slash', request_method='GET',
194 route_name='repo_summary_slash', request_method='GET',
195 renderer='rhodecode:templates/summary/summary.mako')
195 renderer='rhodecode:templates/summary/summary.mako')
196 @view_config(
196 @view_config(
197 route_name='repo_summary_explicit', request_method='GET',
197 route_name='repo_summary_explicit', request_method='GET',
198 renderer='rhodecode:templates/summary/summary.mako')
198 renderer='rhodecode:templates/summary/summary.mako')
199 def summary(self):
199 def summary(self):
200 c = self.load_default_context()
200 c = self.load_default_context()
201
201
202 # Prepare the clone URL
202 # Prepare the clone URL
203 self._prepare_and_set_clone_url(c)
203 self._prepare_and_set_clone_url(c)
204
204
205 # update every 5 min
206 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
207 self.db_repo.update_commit_cache()
208
205 # If enabled, get statistics data
209 # If enabled, get statistics data
206
210
207 c.show_stats = bool(self.db_repo.enable_statistics)
211 c.show_stats = bool(self.db_repo.enable_statistics)
208
212
209 stats = Session().query(Statistics) \
213 stats = Session().query(Statistics) \
210 .filter(Statistics.repository == self.db_repo) \
214 .filter(Statistics.repository == self.db_repo) \
211 .scalar()
215 .scalar()
212
216
213 c.stats_percentage = 0
217 c.stats_percentage = 0
214
218
215 if stats and stats.languages:
219 if stats and stats.languages:
216 c.no_data = False is self.db_repo.enable_statistics
220 c.no_data = False is self.db_repo.enable_statistics
217 lang_stats_d = json.loads(stats.languages)
221 lang_stats_d = json.loads(stats.languages)
218
222
219 # Sort first by decreasing count and second by the file extension,
223 # Sort first by decreasing count and second by the file extension,
220 # so we have a consistent output.
224 # so we have a consistent output.
221 lang_stats_items = sorted(lang_stats_d.iteritems(),
225 lang_stats_items = sorted(lang_stats_d.iteritems(),
222 key=lambda k: (-k[1], k[0]))[:10]
226 key=lambda k: (-k[1], k[0]))[:10]
223 lang_stats = [(x, {"count": y,
227 lang_stats = [(x, {"count": y,
224 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
228 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
225 for x, y in lang_stats_items]
229 for x, y in lang_stats_items]
226
230
227 c.trending_languages = json.dumps(lang_stats)
231 c.trending_languages = json.dumps(lang_stats)
228 else:
232 else:
229 c.no_data = True
233 c.no_data = True
230 c.trending_languages = json.dumps({})
234 c.trending_languages = json.dumps({})
231
235
232 scm_model = ScmModel()
236 scm_model = ScmModel()
233 c.enable_downloads = self.db_repo.enable_downloads
237 c.enable_downloads = self.db_repo.enable_downloads
234 c.repository_followers = scm_model.get_followers(self.db_repo)
238 c.repository_followers = scm_model.get_followers(self.db_repo)
235 c.repository_forks = scm_model.get_forks(self.db_repo)
239 c.repository_forks = scm_model.get_forks(self.db_repo)
236
240
237 # first interaction with the VCS instance after here...
241 # first interaction with the VCS instance after here...
238 if c.repository_requirements_missing:
242 if c.repository_requirements_missing:
239 self.request.override_renderer = \
243 self.request.override_renderer = \
240 'rhodecode:templates/summary/missing_requirements.mako'
244 'rhodecode:templates/summary/missing_requirements.mako'
241 return self._get_template_context(c)
245 return self._get_template_context(c)
242
246
243 c.readme_data, c.readme_file = \
247 c.readme_data, c.readme_file = \
244 self._get_readme_data(self.db_repo, c.visual.default_renderer)
248 self._get_readme_data(self.db_repo, c.visual.default_renderer)
245
249
246 # loads the summary commits template context
250 # loads the summary commits template context
247 self._load_commits_context(c)
251 self._load_commits_context(c)
248
252
249 return self._get_template_context(c)
253 return self._get_template_context(c)
250
254
251 def get_request_commit_id(self):
255 def get_request_commit_id(self):
252 return self.request.matchdict['commit_id']
256 return self.request.matchdict['commit_id']
253
257
254 @LoginRequired()
258 @LoginRequired()
255 @HasRepoPermissionAnyDecorator(
259 @HasRepoPermissionAnyDecorator(
256 'repository.read', 'repository.write', 'repository.admin')
260 'repository.read', 'repository.write', 'repository.admin')
257 @view_config(
261 @view_config(
258 route_name='repo_stats', request_method='GET',
262 route_name='repo_stats', request_method='GET',
259 renderer='json_ext')
263 renderer='json_ext')
260 def repo_stats(self):
264 def repo_stats(self):
261 commit_id = self.get_request_commit_id()
265 commit_id = self.get_request_commit_id()
262 show_stats = bool(self.db_repo.enable_statistics)
266 show_stats = bool(self.db_repo.enable_statistics)
263 repo_id = self.db_repo.repo_id
267 repo_id = self.db_repo.repo_id
264
268
265 cache_seconds = safe_int(
269 cache_seconds = safe_int(
266 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
270 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
267 cache_on = cache_seconds > 0
271 cache_on = cache_seconds > 0
268 log.debug(
272 log.debug(
269 'Computing REPO TREE for repo_id %s commit_id `%s` '
273 'Computing REPO TREE for repo_id %s commit_id `%s` '
270 'with caching: %s[TTL: %ss]' % (
274 'with caching: %s[TTL: %ss]' % (
271 repo_id, commit_id, cache_on, cache_seconds or 0))
275 repo_id, commit_id, cache_on, cache_seconds or 0))
272
276
273 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
277 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
274 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
278 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
275
279
276 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
280 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
277 condition=cache_on)
281 condition=cache_on)
278 def compute_stats(repo_id, commit_id, show_stats):
282 def compute_stats(repo_id, commit_id, show_stats):
279 code_stats = {}
283 code_stats = {}
280 size = 0
284 size = 0
281 try:
285 try:
282 scm_instance = self.db_repo.scm_instance()
286 scm_instance = self.db_repo.scm_instance()
283 commit = scm_instance.get_commit(commit_id)
287 commit = scm_instance.get_commit(commit_id)
284
288
285 for node in commit.get_filenodes_generator():
289 for node in commit.get_filenodes_generator():
286 size += node.size
290 size += node.size
287 if not show_stats:
291 if not show_stats:
288 continue
292 continue
289 ext = string.lower(node.extension)
293 ext = string.lower(node.extension)
290 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
294 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
291 if ext_info:
295 if ext_info:
292 if ext in code_stats:
296 if ext in code_stats:
293 code_stats[ext]['count'] += 1
297 code_stats[ext]['count'] += 1
294 else:
298 else:
295 code_stats[ext] = {"count": 1, "desc": ext_info}
299 code_stats[ext] = {"count": 1, "desc": ext_info}
296 except (EmptyRepositoryError, CommitDoesNotExistError):
300 except (EmptyRepositoryError, CommitDoesNotExistError):
297 pass
301 pass
298 return {'size': h.format_byte_size_binary(size),
302 return {'size': h.format_byte_size_binary(size),
299 'code_stats': code_stats}
303 'code_stats': code_stats}
300
304
301 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
305 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
302 return stats
306 return stats
303
307
304 @LoginRequired()
308 @LoginRequired()
305 @HasRepoPermissionAnyDecorator(
309 @HasRepoPermissionAnyDecorator(
306 'repository.read', 'repository.write', 'repository.admin')
310 'repository.read', 'repository.write', 'repository.admin')
307 @view_config(
311 @view_config(
308 route_name='repo_refs_data', request_method='GET',
312 route_name='repo_refs_data', request_method='GET',
309 renderer='json_ext')
313 renderer='json_ext')
310 def repo_refs_data(self):
314 def repo_refs_data(self):
311 _ = self.request.translate
315 _ = self.request.translate
312 self.load_default_context()
316 self.load_default_context()
313
317
314 repo = self.rhodecode_vcs_repo
318 repo = self.rhodecode_vcs_repo
315 refs_to_create = [
319 refs_to_create = [
316 (_("Branch"), repo.branches, 'branch'),
320 (_("Branch"), repo.branches, 'branch'),
317 (_("Tag"), repo.tags, 'tag'),
321 (_("Tag"), repo.tags, 'tag'),
318 (_("Bookmark"), repo.bookmarks, 'book'),
322 (_("Bookmark"), repo.bookmarks, 'book'),
319 ]
323 ]
320 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
324 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
321 data = {
325 data = {
322 'more': False,
326 'more': False,
323 'results': res
327 'results': res
324 }
328 }
325 return data
329 return data
326
330
327 @LoginRequired()
331 @LoginRequired()
328 @HasRepoPermissionAnyDecorator(
332 @HasRepoPermissionAnyDecorator(
329 'repository.read', 'repository.write', 'repository.admin')
333 'repository.read', 'repository.write', 'repository.admin')
330 @view_config(
334 @view_config(
331 route_name='repo_refs_changelog_data', request_method='GET',
335 route_name='repo_refs_changelog_data', request_method='GET',
332 renderer='json_ext')
336 renderer='json_ext')
333 def repo_refs_changelog_data(self):
337 def repo_refs_changelog_data(self):
334 _ = self.request.translate
338 _ = self.request.translate
335 self.load_default_context()
339 self.load_default_context()
336
340
337 repo = self.rhodecode_vcs_repo
341 repo = self.rhodecode_vcs_repo
338
342
339 refs_to_create = [
343 refs_to_create = [
340 (_("Branches"), repo.branches, 'branch'),
344 (_("Branches"), repo.branches, 'branch'),
341 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
345 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
342 # TODO: enable when vcs can handle bookmarks filters
346 # TODO: enable when vcs can handle bookmarks filters
343 # (_("Bookmarks"), repo.bookmarks, "book"),
347 # (_("Bookmarks"), repo.bookmarks, "book"),
344 ]
348 ]
345 res = self._create_reference_data(
349 res = self._create_reference_data(
346 repo, self.db_repo_name, refs_to_create)
350 repo, self.db_repo_name, refs_to_create)
347 data = {
351 data = {
348 'more': False,
352 'more': False,
349 'results': res
353 'results': res
350 }
354 }
351 return data
355 return data
352
356
353 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
357 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
354 format_ref_id = get_format_ref_id(repo)
358 format_ref_id = get_format_ref_id(repo)
355
359
356 result = []
360 result = []
357 for title, refs, ref_type in refs_to_create:
361 for title, refs, ref_type in refs_to_create:
358 if refs:
362 if refs:
359 result.append({
363 result.append({
360 'text': title,
364 'text': title,
361 'children': self._create_reference_items(
365 'children': self._create_reference_items(
362 repo, full_repo_name, refs, ref_type,
366 repo, full_repo_name, refs, ref_type,
363 format_ref_id),
367 format_ref_id),
364 })
368 })
365 return result
369 return result
366
370
367 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
371 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
368 result = []
372 result = []
369 is_svn = h.is_svn(repo)
373 is_svn = h.is_svn(repo)
370 for ref_name, raw_id in refs.iteritems():
374 for ref_name, raw_id in refs.iteritems():
371 files_url = self._create_files_url(
375 files_url = self._create_files_url(
372 repo, full_repo_name, ref_name, raw_id, is_svn)
376 repo, full_repo_name, ref_name, raw_id, is_svn)
373 result.append({
377 result.append({
374 'text': ref_name,
378 'text': ref_name,
375 'id': format_ref_id(ref_name, raw_id),
379 'id': format_ref_id(ref_name, raw_id),
376 'raw_id': raw_id,
380 'raw_id': raw_id,
377 'type': ref_type,
381 'type': ref_type,
378 'files_url': files_url,
382 'files_url': files_url,
379 'idx': 0,
383 'idx': 0,
380 })
384 })
381 return result
385 return result
382
386
383 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
387 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
384 use_commit_id = '/' in ref_name or is_svn
388 use_commit_id = '/' in ref_name or is_svn
385 return h.route_path(
389 return h.route_path(
386 'repo_files',
390 'repo_files',
387 repo_name=full_repo_name,
391 repo_name=full_repo_name,
388 f_path=ref_name if is_svn else '',
392 f_path=ref_name if is_svn else '',
389 commit_id=raw_id if use_commit_id else ref_name,
393 commit_id=raw_id if use_commit_id else ref_name,
390 _query=dict(at=ref_name))
394 _query=dict(at=ref_name))
@@ -1,2049 +1,2050 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202 get_error = _GetError()
202 get_error = _GetError()
203
203
204
204
205 class _ToolTip(object):
205 class _ToolTip(object):
206
206
207 def __call__(self, tooltip_title, trim_at=50):
207 def __call__(self, tooltip_title, trim_at=50):
208 """
208 """
209 Special function just to wrap our text into nice formatted
209 Special function just to wrap our text into nice formatted
210 autowrapped text
210 autowrapped text
211
211
212 :param tooltip_title:
212 :param tooltip_title:
213 """
213 """
214 tooltip_title = escape(tooltip_title)
214 tooltip_title = escape(tooltip_title)
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 return tooltip_title
216 return tooltip_title
217
217 tooltip = _ToolTip()
218 tooltip = _ToolTip()
218
219
219
220
220 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None):
221 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None):
221 if isinstance(file_path, str):
222 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
223 file_path = safe_unicode(file_path)
223 route_qry = {'at': at_ref} if at_ref else None
224 route_qry = {'at': at_ref} if at_ref else None
224
225
225 # TODO: johbo: Is this always a url like path, or is this operating
226 # TODO: johbo: Is this always a url like path, or is this operating
226 # system dependent?
227 # system dependent?
227 path_segments = file_path.split('/')
228 path_segments = file_path.split('/')
228
229
229 repo_name_html = escape(repo_name)
230 repo_name_html = escape(repo_name)
230 if len(path_segments) == 1 and path_segments[0] == '':
231 if len(path_segments) == 1 and path_segments[0] == '':
231 url_segments = [repo_name_html]
232 url_segments = [repo_name_html]
232 else:
233 else:
233 url_segments = [
234 url_segments = [
234 link_to(
235 link_to(
235 repo_name_html,
236 repo_name_html,
236 route_path(
237 route_path(
237 'repo_files',
238 'repo_files',
238 repo_name=repo_name,
239 repo_name=repo_name,
239 commit_id=commit_id,
240 commit_id=commit_id,
240 f_path='',
241 f_path='',
241 _query=route_qry),
242 _query=route_qry),
242 )]
243 )]
243
244
244 last_cnt = len(path_segments) - 1
245 last_cnt = len(path_segments) - 1
245 for cnt, segment in enumerate(path_segments):
246 for cnt, segment in enumerate(path_segments):
246 if not segment:
247 if not segment:
247 continue
248 continue
248 segment_html = escape(segment)
249 segment_html = escape(segment)
249
250
250 if cnt != last_cnt:
251 if cnt != last_cnt:
251 url_segments.append(
252 url_segments.append(
252 link_to(
253 link_to(
253 segment_html,
254 segment_html,
254 route_path(
255 route_path(
255 'repo_files',
256 'repo_files',
256 repo_name=repo_name,
257 repo_name=repo_name,
257 commit_id=commit_id,
258 commit_id=commit_id,
258 f_path='/'.join(path_segments[:cnt + 1]),
259 f_path='/'.join(path_segments[:cnt + 1]),
259 _query=route_qry),
260 _query=route_qry),
260 ))
261 ))
261 else:
262 else:
262 url_segments.append(segment_html)
263 url_segments.append(segment_html)
263
264
264 return literal('/'.join(url_segments))
265 return literal('/'.join(url_segments))
265
266
266
267
267 def code_highlight(code, lexer, formatter, use_hl_filter=False):
268 def code_highlight(code, lexer, formatter, use_hl_filter=False):
268 """
269 """
269 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
270 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
270
271
271 If ``outfile`` is given and a valid file object (an object
272 If ``outfile`` is given and a valid file object (an object
272 with a ``write`` method), the result will be written to it, otherwise
273 with a ``write`` method), the result will be written to it, otherwise
273 it is returned as a string.
274 it is returned as a string.
274 """
275 """
275 if use_hl_filter:
276 if use_hl_filter:
276 # add HL filter
277 # add HL filter
277 from rhodecode.lib.index import search_utils
278 from rhodecode.lib.index import search_utils
278 lexer.add_filter(search_utils.ElasticSearchHLFilter())
279 lexer.add_filter(search_utils.ElasticSearchHLFilter())
279 return pygments.format(pygments.lex(code, lexer), formatter)
280 return pygments.format(pygments.lex(code, lexer), formatter)
280
281
281
282
282 class CodeHtmlFormatter(HtmlFormatter):
283 class CodeHtmlFormatter(HtmlFormatter):
283 """
284 """
284 My code Html Formatter for source codes
285 My code Html Formatter for source codes
285 """
286 """
286
287
287 def wrap(self, source, outfile):
288 def wrap(self, source, outfile):
288 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
289 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
289
290
290 def _wrap_code(self, source):
291 def _wrap_code(self, source):
291 for cnt, it in enumerate(source):
292 for cnt, it in enumerate(source):
292 i, t = it
293 i, t = it
293 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
294 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
294 yield i, t
295 yield i, t
295
296
296 def _wrap_tablelinenos(self, inner):
297 def _wrap_tablelinenos(self, inner):
297 dummyoutfile = StringIO.StringIO()
298 dummyoutfile = StringIO.StringIO()
298 lncount = 0
299 lncount = 0
299 for t, line in inner:
300 for t, line in inner:
300 if t:
301 if t:
301 lncount += 1
302 lncount += 1
302 dummyoutfile.write(line)
303 dummyoutfile.write(line)
303
304
304 fl = self.linenostart
305 fl = self.linenostart
305 mw = len(str(lncount + fl - 1))
306 mw = len(str(lncount + fl - 1))
306 sp = self.linenospecial
307 sp = self.linenospecial
307 st = self.linenostep
308 st = self.linenostep
308 la = self.lineanchors
309 la = self.lineanchors
309 aln = self.anchorlinenos
310 aln = self.anchorlinenos
310 nocls = self.noclasses
311 nocls = self.noclasses
311 if sp:
312 if sp:
312 lines = []
313 lines = []
313
314
314 for i in range(fl, fl + lncount):
315 for i in range(fl, fl + lncount):
315 if i % st == 0:
316 if i % st == 0:
316 if i % sp == 0:
317 if i % sp == 0:
317 if aln:
318 if aln:
318 lines.append('<a href="#%s%d" class="special">%*d</a>' %
319 lines.append('<a href="#%s%d" class="special">%*d</a>' %
319 (la, i, mw, i))
320 (la, i, mw, i))
320 else:
321 else:
321 lines.append('<span class="special">%*d</span>' % (mw, i))
322 lines.append('<span class="special">%*d</span>' % (mw, i))
322 else:
323 else:
323 if aln:
324 if aln:
324 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
325 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
325 else:
326 else:
326 lines.append('%*d' % (mw, i))
327 lines.append('%*d' % (mw, i))
327 else:
328 else:
328 lines.append('')
329 lines.append('')
329 ls = '\n'.join(lines)
330 ls = '\n'.join(lines)
330 else:
331 else:
331 lines = []
332 lines = []
332 for i in range(fl, fl + lncount):
333 for i in range(fl, fl + lncount):
333 if i % st == 0:
334 if i % st == 0:
334 if aln:
335 if aln:
335 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
336 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
336 else:
337 else:
337 lines.append('%*d' % (mw, i))
338 lines.append('%*d' % (mw, i))
338 else:
339 else:
339 lines.append('')
340 lines.append('')
340 ls = '\n'.join(lines)
341 ls = '\n'.join(lines)
341
342
342 # in case you wonder about the seemingly redundant <div> here: since the
343 # in case you wonder about the seemingly redundant <div> here: since the
343 # content in the other cell also is wrapped in a div, some browsers in
344 # content in the other cell also is wrapped in a div, some browsers in
344 # some configurations seem to mess up the formatting...
345 # some configurations seem to mess up the formatting...
345 if nocls:
346 if nocls:
346 yield 0, ('<table class="%stable">' % self.cssclass +
347 yield 0, ('<table class="%stable">' % self.cssclass +
347 '<tr><td><div class="linenodiv" '
348 '<tr><td><div class="linenodiv" '
348 'style="background-color: #f0f0f0; padding-right: 10px">'
349 'style="background-color: #f0f0f0; padding-right: 10px">'
349 '<pre style="line-height: 125%">' +
350 '<pre style="line-height: 125%">' +
350 ls + '</pre></div></td><td id="hlcode" class="code">')
351 ls + '</pre></div></td><td id="hlcode" class="code">')
351 else:
352 else:
352 yield 0, ('<table class="%stable">' % self.cssclass +
353 yield 0, ('<table class="%stable">' % self.cssclass +
353 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
354 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
354 ls + '</pre></div></td><td id="hlcode" class="code">')
355 ls + '</pre></div></td><td id="hlcode" class="code">')
355 yield 0, dummyoutfile.getvalue()
356 yield 0, dummyoutfile.getvalue()
356 yield 0, '</td></tr></table>'
357 yield 0, '</td></tr></table>'
357
358
358
359
359 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
360 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
360 def __init__(self, **kw):
361 def __init__(self, **kw):
361 # only show these line numbers if set
362 # only show these line numbers if set
362 self.only_lines = kw.pop('only_line_numbers', [])
363 self.only_lines = kw.pop('only_line_numbers', [])
363 self.query_terms = kw.pop('query_terms', [])
364 self.query_terms = kw.pop('query_terms', [])
364 self.max_lines = kw.pop('max_lines', 5)
365 self.max_lines = kw.pop('max_lines', 5)
365 self.line_context = kw.pop('line_context', 3)
366 self.line_context = kw.pop('line_context', 3)
366 self.url = kw.pop('url', None)
367 self.url = kw.pop('url', None)
367
368
368 super(CodeHtmlFormatter, self).__init__(**kw)
369 super(CodeHtmlFormatter, self).__init__(**kw)
369
370
370 def _wrap_code(self, source):
371 def _wrap_code(self, source):
371 for cnt, it in enumerate(source):
372 for cnt, it in enumerate(source):
372 i, t = it
373 i, t = it
373 t = '<pre>%s</pre>' % t
374 t = '<pre>%s</pre>' % t
374 yield i, t
375 yield i, t
375
376
376 def _wrap_tablelinenos(self, inner):
377 def _wrap_tablelinenos(self, inner):
377 yield 0, '<table class="code-highlight %stable">' % self.cssclass
378 yield 0, '<table class="code-highlight %stable">' % self.cssclass
378
379
379 last_shown_line_number = 0
380 last_shown_line_number = 0
380 current_line_number = 1
381 current_line_number = 1
381
382
382 for t, line in inner:
383 for t, line in inner:
383 if not t:
384 if not t:
384 yield t, line
385 yield t, line
385 continue
386 continue
386
387
387 if current_line_number in self.only_lines:
388 if current_line_number in self.only_lines:
388 if last_shown_line_number + 1 != current_line_number:
389 if last_shown_line_number + 1 != current_line_number:
389 yield 0, '<tr>'
390 yield 0, '<tr>'
390 yield 0, '<td class="line">...</td>'
391 yield 0, '<td class="line">...</td>'
391 yield 0, '<td id="hlcode" class="code"></td>'
392 yield 0, '<td id="hlcode" class="code"></td>'
392 yield 0, '</tr>'
393 yield 0, '</tr>'
393
394
394 yield 0, '<tr>'
395 yield 0, '<tr>'
395 if self.url:
396 if self.url:
396 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
397 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
397 self.url, current_line_number, current_line_number)
398 self.url, current_line_number, current_line_number)
398 else:
399 else:
399 yield 0, '<td class="line"><a href="">%i</a></td>' % (
400 yield 0, '<td class="line"><a href="">%i</a></td>' % (
400 current_line_number)
401 current_line_number)
401 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
402 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
402 yield 0, '</tr>'
403 yield 0, '</tr>'
403
404
404 last_shown_line_number = current_line_number
405 last_shown_line_number = current_line_number
405
406
406 current_line_number += 1
407 current_line_number += 1
407
408
408 yield 0, '</table>'
409 yield 0, '</table>'
409
410
410
411
411 def hsv_to_rgb(h, s, v):
412 def hsv_to_rgb(h, s, v):
412 """ Convert hsv color values to rgb """
413 """ Convert hsv color values to rgb """
413
414
414 if s == 0.0:
415 if s == 0.0:
415 return v, v, v
416 return v, v, v
416 i = int(h * 6.0) # XXX assume int() truncates!
417 i = int(h * 6.0) # XXX assume int() truncates!
417 f = (h * 6.0) - i
418 f = (h * 6.0) - i
418 p = v * (1.0 - s)
419 p = v * (1.0 - s)
419 q = v * (1.0 - s * f)
420 q = v * (1.0 - s * f)
420 t = v * (1.0 - s * (1.0 - f))
421 t = v * (1.0 - s * (1.0 - f))
421 i = i % 6
422 i = i % 6
422 if i == 0:
423 if i == 0:
423 return v, t, p
424 return v, t, p
424 if i == 1:
425 if i == 1:
425 return q, v, p
426 return q, v, p
426 if i == 2:
427 if i == 2:
427 return p, v, t
428 return p, v, t
428 if i == 3:
429 if i == 3:
429 return p, q, v
430 return p, q, v
430 if i == 4:
431 if i == 4:
431 return t, p, v
432 return t, p, v
432 if i == 5:
433 if i == 5:
433 return v, p, q
434 return v, p, q
434
435
435
436
436 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
437 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
437 """
438 """
438 Generator for getting n of evenly distributed colors using
439 Generator for getting n of evenly distributed colors using
439 hsv color and golden ratio. It always return same order of colors
440 hsv color and golden ratio. It always return same order of colors
440
441
441 :param n: number of colors to generate
442 :param n: number of colors to generate
442 :param saturation: saturation of returned colors
443 :param saturation: saturation of returned colors
443 :param lightness: lightness of returned colors
444 :param lightness: lightness of returned colors
444 :returns: RGB tuple
445 :returns: RGB tuple
445 """
446 """
446
447
447 golden_ratio = 0.618033988749895
448 golden_ratio = 0.618033988749895
448 h = 0.22717784590367374
449 h = 0.22717784590367374
449
450
450 for _ in xrange(n):
451 for _ in xrange(n):
451 h += golden_ratio
452 h += golden_ratio
452 h %= 1
453 h %= 1
453 HSV_tuple = [h, saturation, lightness]
454 HSV_tuple = [h, saturation, lightness]
454 RGB_tuple = hsv_to_rgb(*HSV_tuple)
455 RGB_tuple = hsv_to_rgb(*HSV_tuple)
455 yield map(lambda x: str(int(x * 256)), RGB_tuple)
456 yield map(lambda x: str(int(x * 256)), RGB_tuple)
456
457
457
458
458 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
459 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
459 """
460 """
460 Returns a function which when called with an argument returns a unique
461 Returns a function which when called with an argument returns a unique
461 color for that argument, eg.
462 color for that argument, eg.
462
463
463 :param n: number of colors to generate
464 :param n: number of colors to generate
464 :param saturation: saturation of returned colors
465 :param saturation: saturation of returned colors
465 :param lightness: lightness of returned colors
466 :param lightness: lightness of returned colors
466 :returns: css RGB string
467 :returns: css RGB string
467
468
468 >>> color_hash = color_hasher()
469 >>> color_hash = color_hasher()
469 >>> color_hash('hello')
470 >>> color_hash('hello')
470 'rgb(34, 12, 59)'
471 'rgb(34, 12, 59)'
471 >>> color_hash('hello')
472 >>> color_hash('hello')
472 'rgb(34, 12, 59)'
473 'rgb(34, 12, 59)'
473 >>> color_hash('other')
474 >>> color_hash('other')
474 'rgb(90, 224, 159)'
475 'rgb(90, 224, 159)'
475 """
476 """
476
477
477 color_dict = {}
478 color_dict = {}
478 cgenerator = unique_color_generator(
479 cgenerator = unique_color_generator(
479 saturation=saturation, lightness=lightness)
480 saturation=saturation, lightness=lightness)
480
481
481 def get_color_string(thing):
482 def get_color_string(thing):
482 if thing in color_dict:
483 if thing in color_dict:
483 col = color_dict[thing]
484 col = color_dict[thing]
484 else:
485 else:
485 col = color_dict[thing] = cgenerator.next()
486 col = color_dict[thing] = cgenerator.next()
486 return "rgb(%s)" % (', '.join(col))
487 return "rgb(%s)" % (', '.join(col))
487
488
488 return get_color_string
489 return get_color_string
489
490
490
491
491 def get_lexer_safe(mimetype=None, filepath=None):
492 def get_lexer_safe(mimetype=None, filepath=None):
492 """
493 """
493 Tries to return a relevant pygments lexer using mimetype/filepath name,
494 Tries to return a relevant pygments lexer using mimetype/filepath name,
494 defaulting to plain text if none could be found
495 defaulting to plain text if none could be found
495 """
496 """
496 lexer = None
497 lexer = None
497 try:
498 try:
498 if mimetype:
499 if mimetype:
499 lexer = get_lexer_for_mimetype(mimetype)
500 lexer = get_lexer_for_mimetype(mimetype)
500 if not lexer:
501 if not lexer:
501 lexer = get_lexer_for_filename(filepath)
502 lexer = get_lexer_for_filename(filepath)
502 except pygments.util.ClassNotFound:
503 except pygments.util.ClassNotFound:
503 pass
504 pass
504
505
505 if not lexer:
506 if not lexer:
506 lexer = get_lexer_by_name('text')
507 lexer = get_lexer_by_name('text')
507
508
508 return lexer
509 return lexer
509
510
510
511
511 def get_lexer_for_filenode(filenode):
512 def get_lexer_for_filenode(filenode):
512 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
513 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
513 return lexer
514 return lexer
514
515
515
516
516 def pygmentize(filenode, **kwargs):
517 def pygmentize(filenode, **kwargs):
517 """
518 """
518 pygmentize function using pygments
519 pygmentize function using pygments
519
520
520 :param filenode:
521 :param filenode:
521 """
522 """
522 lexer = get_lexer_for_filenode(filenode)
523 lexer = get_lexer_for_filenode(filenode)
523 return literal(code_highlight(filenode.content, lexer,
524 return literal(code_highlight(filenode.content, lexer,
524 CodeHtmlFormatter(**kwargs)))
525 CodeHtmlFormatter(**kwargs)))
525
526
526
527
527 def is_following_repo(repo_name, user_id):
528 def is_following_repo(repo_name, user_id):
528 from rhodecode.model.scm import ScmModel
529 from rhodecode.model.scm import ScmModel
529 return ScmModel().is_following_repo(repo_name, user_id)
530 return ScmModel().is_following_repo(repo_name, user_id)
530
531
531
532
532 class _Message(object):
533 class _Message(object):
533 """A message returned by ``Flash.pop_messages()``.
534 """A message returned by ``Flash.pop_messages()``.
534
535
535 Converting the message to a string returns the message text. Instances
536 Converting the message to a string returns the message text. Instances
536 also have the following attributes:
537 also have the following attributes:
537
538
538 * ``message``: the message text.
539 * ``message``: the message text.
539 * ``category``: the category specified when the message was created.
540 * ``category``: the category specified when the message was created.
540 """
541 """
541
542
542 def __init__(self, category, message):
543 def __init__(self, category, message):
543 self.category = category
544 self.category = category
544 self.message = message
545 self.message = message
545
546
546 def __str__(self):
547 def __str__(self):
547 return self.message
548 return self.message
548
549
549 __unicode__ = __str__
550 __unicode__ = __str__
550
551
551 def __html__(self):
552 def __html__(self):
552 return escape(safe_unicode(self.message))
553 return escape(safe_unicode(self.message))
553
554
554
555
555 class Flash(object):
556 class Flash(object):
556 # List of allowed categories. If None, allow any category.
557 # List of allowed categories. If None, allow any category.
557 categories = ["warning", "notice", "error", "success"]
558 categories = ["warning", "notice", "error", "success"]
558
559
559 # Default category if none is specified.
560 # Default category if none is specified.
560 default_category = "notice"
561 default_category = "notice"
561
562
562 def __init__(self, session_key="flash", categories=None,
563 def __init__(self, session_key="flash", categories=None,
563 default_category=None):
564 default_category=None):
564 """
565 """
565 Instantiate a ``Flash`` object.
566 Instantiate a ``Flash`` object.
566
567
567 ``session_key`` is the key to save the messages under in the user's
568 ``session_key`` is the key to save the messages under in the user's
568 session.
569 session.
569
570
570 ``categories`` is an optional list which overrides the default list
571 ``categories`` is an optional list which overrides the default list
571 of categories.
572 of categories.
572
573
573 ``default_category`` overrides the default category used for messages
574 ``default_category`` overrides the default category used for messages
574 when none is specified.
575 when none is specified.
575 """
576 """
576 self.session_key = session_key
577 self.session_key = session_key
577 if categories is not None:
578 if categories is not None:
578 self.categories = categories
579 self.categories = categories
579 if default_category is not None:
580 if default_category is not None:
580 self.default_category = default_category
581 self.default_category = default_category
581 if self.categories and self.default_category not in self.categories:
582 if self.categories and self.default_category not in self.categories:
582 raise ValueError(
583 raise ValueError(
583 "unrecognized default category %r" % (self.default_category,))
584 "unrecognized default category %r" % (self.default_category,))
584
585
585 def pop_messages(self, session=None, request=None):
586 def pop_messages(self, session=None, request=None):
586 """
587 """
587 Return all accumulated messages and delete them from the session.
588 Return all accumulated messages and delete them from the session.
588
589
589 The return value is a list of ``Message`` objects.
590 The return value is a list of ``Message`` objects.
590 """
591 """
591 messages = []
592 messages = []
592
593
593 if not session:
594 if not session:
594 if not request:
595 if not request:
595 request = get_current_request()
596 request = get_current_request()
596 session = request.session
597 session = request.session
597
598
598 # Pop the 'old' pylons flash messages. They are tuples of the form
599 # Pop the 'old' pylons flash messages. They are tuples of the form
599 # (category, message)
600 # (category, message)
600 for cat, msg in session.pop(self.session_key, []):
601 for cat, msg in session.pop(self.session_key, []):
601 messages.append(_Message(cat, msg))
602 messages.append(_Message(cat, msg))
602
603
603 # Pop the 'new' pyramid flash messages for each category as list
604 # Pop the 'new' pyramid flash messages for each category as list
604 # of strings.
605 # of strings.
605 for cat in self.categories:
606 for cat in self.categories:
606 for msg in session.pop_flash(queue=cat):
607 for msg in session.pop_flash(queue=cat):
607 messages.append(_Message(cat, msg))
608 messages.append(_Message(cat, msg))
608 # Map messages from the default queue to the 'notice' category.
609 # Map messages from the default queue to the 'notice' category.
609 for msg in session.pop_flash():
610 for msg in session.pop_flash():
610 messages.append(_Message('notice', msg))
611 messages.append(_Message('notice', msg))
611
612
612 session.save()
613 session.save()
613 return messages
614 return messages
614
615
615 def json_alerts(self, session=None, request=None):
616 def json_alerts(self, session=None, request=None):
616 payloads = []
617 payloads = []
617 messages = flash.pop_messages(session=session, request=request)
618 messages = flash.pop_messages(session=session, request=request)
618 if messages:
619 if messages:
619 for message in messages:
620 for message in messages:
620 subdata = {}
621 subdata = {}
621 if hasattr(message.message, 'rsplit'):
622 if hasattr(message.message, 'rsplit'):
622 flash_data = message.message.rsplit('|DELIM|', 1)
623 flash_data = message.message.rsplit('|DELIM|', 1)
623 org_message = flash_data[0]
624 org_message = flash_data[0]
624 if len(flash_data) > 1:
625 if len(flash_data) > 1:
625 subdata = json.loads(flash_data[1])
626 subdata = json.loads(flash_data[1])
626 else:
627 else:
627 org_message = message.message
628 org_message = message.message
628 payloads.append({
629 payloads.append({
629 'message': {
630 'message': {
630 'message': u'{}'.format(org_message),
631 'message': u'{}'.format(org_message),
631 'level': message.category,
632 'level': message.category,
632 'force': True,
633 'force': True,
633 'subdata': subdata
634 'subdata': subdata
634 }
635 }
635 })
636 })
636 return json.dumps(payloads)
637 return json.dumps(payloads)
637
638
638 def __call__(self, message, category=None, ignore_duplicate=False,
639 def __call__(self, message, category=None, ignore_duplicate=False,
639 session=None, request=None):
640 session=None, request=None):
640
641
641 if not session:
642 if not session:
642 if not request:
643 if not request:
643 request = get_current_request()
644 request = get_current_request()
644 session = request.session
645 session = request.session
645
646
646 session.flash(
647 session.flash(
647 message, queue=category, allow_duplicate=not ignore_duplicate)
648 message, queue=category, allow_duplicate=not ignore_duplicate)
648
649
649
650
650 flash = Flash()
651 flash = Flash()
651
652
652 #==============================================================================
653 #==============================================================================
653 # SCM FILTERS available via h.
654 # SCM FILTERS available via h.
654 #==============================================================================
655 #==============================================================================
655 from rhodecode.lib.vcs.utils import author_name, author_email
656 from rhodecode.lib.vcs.utils import author_name, author_email
656 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
657 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
657 from rhodecode.model.db import User, ChangesetStatus
658 from rhodecode.model.db import User, ChangesetStatus
658
659
659 capitalize = lambda x: x.capitalize()
660 capitalize = lambda x: x.capitalize()
660 email = author_email
661 email = author_email
661 short_id = lambda x: x[:12]
662 short_id = lambda x: x[:12]
662 hide_credentials = lambda x: ''.join(credentials_filter(x))
663 hide_credentials = lambda x: ''.join(credentials_filter(x))
663
664
664
665
665 import pytz
666 import pytz
666 import tzlocal
667 import tzlocal
667 local_timezone = tzlocal.get_localzone()
668 local_timezone = tzlocal.get_localzone()
668
669
669
670
670 def age_component(datetime_iso, value=None, time_is_local=False):
671 def age_component(datetime_iso, value=None, time_is_local=False):
671 title = value or format_date(datetime_iso)
672 title = value or format_date(datetime_iso)
672 tzinfo = '+00:00'
673 tzinfo = '+00:00'
673
674
674 # detect if we have a timezone info, otherwise, add it
675 # detect if we have a timezone info, otherwise, add it
675 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
676 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
676 force_timezone = os.environ.get('RC_TIMEZONE', '')
677 force_timezone = os.environ.get('RC_TIMEZONE', '')
677 if force_timezone:
678 if force_timezone:
678 force_timezone = pytz.timezone(force_timezone)
679 force_timezone = pytz.timezone(force_timezone)
679 timezone = force_timezone or local_timezone
680 timezone = force_timezone or local_timezone
680 offset = timezone.localize(datetime_iso).strftime('%z')
681 offset = timezone.localize(datetime_iso).strftime('%z')
681 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
682 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
682
683
683 return literal(
684 return literal(
684 '<time class="timeago tooltip" '
685 '<time class="timeago tooltip" '
685 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
686 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
686 datetime_iso, title, tzinfo))
687 datetime_iso, title, tzinfo))
687
688
688
689
689 def _shorten_commit_id(commit_id, commit_len=None):
690 def _shorten_commit_id(commit_id, commit_len=None):
690 if commit_len is None:
691 if commit_len is None:
691 request = get_current_request()
692 request = get_current_request()
692 commit_len = request.call_context.visual.show_sha_length
693 commit_len = request.call_context.visual.show_sha_length
693 return commit_id[:commit_len]
694 return commit_id[:commit_len]
694
695
695
696
696 def show_id(commit, show_idx=None, commit_len=None):
697 def show_id(commit, show_idx=None, commit_len=None):
697 """
698 """
698 Configurable function that shows ID
699 Configurable function that shows ID
699 by default it's r123:fffeeefffeee
700 by default it's r123:fffeeefffeee
700
701
701 :param commit: commit instance
702 :param commit: commit instance
702 """
703 """
703 if show_idx is None:
704 if show_idx is None:
704 request = get_current_request()
705 request = get_current_request()
705 show_idx = request.call_context.visual.show_revision_number
706 show_idx = request.call_context.visual.show_revision_number
706
707
707 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
708 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
708 if show_idx:
709 if show_idx:
709 return 'r%s:%s' % (commit.idx, raw_id)
710 return 'r%s:%s' % (commit.idx, raw_id)
710 else:
711 else:
711 return '%s' % (raw_id, )
712 return '%s' % (raw_id, )
712
713
713
714
714 def format_date(date):
715 def format_date(date):
715 """
716 """
716 use a standardized formatting for dates used in RhodeCode
717 use a standardized formatting for dates used in RhodeCode
717
718
718 :param date: date/datetime object
719 :param date: date/datetime object
719 :return: formatted date
720 :return: formatted date
720 """
721 """
721
722
722 if date:
723 if date:
723 _fmt = "%a, %d %b %Y %H:%M:%S"
724 _fmt = "%a, %d %b %Y %H:%M:%S"
724 return safe_unicode(date.strftime(_fmt))
725 return safe_unicode(date.strftime(_fmt))
725
726
726 return u""
727 return u""
727
728
728
729
729 class _RepoChecker(object):
730 class _RepoChecker(object):
730
731
731 def __init__(self, backend_alias):
732 def __init__(self, backend_alias):
732 self._backend_alias = backend_alias
733 self._backend_alias = backend_alias
733
734
734 def __call__(self, repository):
735 def __call__(self, repository):
735 if hasattr(repository, 'alias'):
736 if hasattr(repository, 'alias'):
736 _type = repository.alias
737 _type = repository.alias
737 elif hasattr(repository, 'repo_type'):
738 elif hasattr(repository, 'repo_type'):
738 _type = repository.repo_type
739 _type = repository.repo_type
739 else:
740 else:
740 _type = repository
741 _type = repository
741 return _type == self._backend_alias
742 return _type == self._backend_alias
742
743
743
744
744 is_git = _RepoChecker('git')
745 is_git = _RepoChecker('git')
745 is_hg = _RepoChecker('hg')
746 is_hg = _RepoChecker('hg')
746 is_svn = _RepoChecker('svn')
747 is_svn = _RepoChecker('svn')
747
748
748
749
749 def get_repo_type_by_name(repo_name):
750 def get_repo_type_by_name(repo_name):
750 repo = Repository.get_by_repo_name(repo_name)
751 repo = Repository.get_by_repo_name(repo_name)
751 if repo:
752 if repo:
752 return repo.repo_type
753 return repo.repo_type
753
754
754
755
755 def is_svn_without_proxy(repository):
756 def is_svn_without_proxy(repository):
756 if is_svn(repository):
757 if is_svn(repository):
757 from rhodecode.model.settings import VcsSettingsModel
758 from rhodecode.model.settings import VcsSettingsModel
758 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
759 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
759 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
760 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
760 return False
761 return False
761
762
762
763
763 def discover_user(author):
764 def discover_user(author):
764 """
765 """
765 Tries to discover RhodeCode User based on the autho string. Author string
766 Tries to discover RhodeCode User based on the autho string. Author string
766 is typically `FirstName LastName <email@address.com>`
767 is typically `FirstName LastName <email@address.com>`
767 """
768 """
768
769
769 # if author is already an instance use it for extraction
770 # if author is already an instance use it for extraction
770 if isinstance(author, User):
771 if isinstance(author, User):
771 return author
772 return author
772
773
773 # Valid email in the attribute passed, see if they're in the system
774 # Valid email in the attribute passed, see if they're in the system
774 _email = author_email(author)
775 _email = author_email(author)
775 if _email != '':
776 if _email != '':
776 user = User.get_by_email(_email, case_insensitive=True, cache=True)
777 user = User.get_by_email(_email, case_insensitive=True, cache=True)
777 if user is not None:
778 if user is not None:
778 return user
779 return user
779
780
780 # Maybe it's a username, we try to extract it and fetch by username ?
781 # Maybe it's a username, we try to extract it and fetch by username ?
781 _author = author_name(author)
782 _author = author_name(author)
782 user = User.get_by_username(_author, case_insensitive=True, cache=True)
783 user = User.get_by_username(_author, case_insensitive=True, cache=True)
783 if user is not None:
784 if user is not None:
784 return user
785 return user
785
786
786 return None
787 return None
787
788
788
789
789 def email_or_none(author):
790 def email_or_none(author):
790 # extract email from the commit string
791 # extract email from the commit string
791 _email = author_email(author)
792 _email = author_email(author)
792
793
793 # If we have an email, use it, otherwise
794 # If we have an email, use it, otherwise
794 # see if it contains a username we can get an email from
795 # see if it contains a username we can get an email from
795 if _email != '':
796 if _email != '':
796 return _email
797 return _email
797 else:
798 else:
798 user = User.get_by_username(
799 user = User.get_by_username(
799 author_name(author), case_insensitive=True, cache=True)
800 author_name(author), case_insensitive=True, cache=True)
800
801
801 if user is not None:
802 if user is not None:
802 return user.email
803 return user.email
803
804
804 # No valid email, not a valid user in the system, none!
805 # No valid email, not a valid user in the system, none!
805 return None
806 return None
806
807
807
808
808 def link_to_user(author, length=0, **kwargs):
809 def link_to_user(author, length=0, **kwargs):
809 user = discover_user(author)
810 user = discover_user(author)
810 # user can be None, but if we have it already it means we can re-use it
811 # user can be None, but if we have it already it means we can re-use it
811 # in the person() function, so we save 1 intensive-query
812 # in the person() function, so we save 1 intensive-query
812 if user:
813 if user:
813 author = user
814 author = user
814
815
815 display_person = person(author, 'username_or_name_or_email')
816 display_person = person(author, 'username_or_name_or_email')
816 if length:
817 if length:
817 display_person = shorter(display_person, length)
818 display_person = shorter(display_person, length)
818
819
819 if user:
820 if user:
820 return link_to(
821 return link_to(
821 escape(display_person),
822 escape(display_person),
822 route_path('user_profile', username=user.username),
823 route_path('user_profile', username=user.username),
823 **kwargs)
824 **kwargs)
824 else:
825 else:
825 return escape(display_person)
826 return escape(display_person)
826
827
827
828
828 def link_to_group(users_group_name, **kwargs):
829 def link_to_group(users_group_name, **kwargs):
829 return link_to(
830 return link_to(
830 escape(users_group_name),
831 escape(users_group_name),
831 route_path('user_group_profile', user_group_name=users_group_name),
832 route_path('user_group_profile', user_group_name=users_group_name),
832 **kwargs)
833 **kwargs)
833
834
834
835
835 def person(author, show_attr="username_and_name"):
836 def person(author, show_attr="username_and_name"):
836 user = discover_user(author)
837 user = discover_user(author)
837 if user:
838 if user:
838 return getattr(user, show_attr)
839 return getattr(user, show_attr)
839 else:
840 else:
840 _author = author_name(author)
841 _author = author_name(author)
841 _email = email(author)
842 _email = email(author)
842 return _author or _email
843 return _author or _email
843
844
844
845
845 def author_string(email):
846 def author_string(email):
846 if email:
847 if email:
847 user = User.get_by_email(email, case_insensitive=True, cache=True)
848 user = User.get_by_email(email, case_insensitive=True, cache=True)
848 if user:
849 if user:
849 if user.first_name or user.last_name:
850 if user.first_name or user.last_name:
850 return '%s %s &lt;%s&gt;' % (
851 return '%s %s &lt;%s&gt;' % (
851 user.first_name, user.last_name, email)
852 user.first_name, user.last_name, email)
852 else:
853 else:
853 return email
854 return email
854 else:
855 else:
855 return email
856 return email
856 else:
857 else:
857 return None
858 return None
858
859
859
860
860 def person_by_id(id_, show_attr="username_and_name"):
861 def person_by_id(id_, show_attr="username_and_name"):
861 # attr to return from fetched user
862 # attr to return from fetched user
862 person_getter = lambda usr: getattr(usr, show_attr)
863 person_getter = lambda usr: getattr(usr, show_attr)
863
864
864 #maybe it's an ID ?
865 #maybe it's an ID ?
865 if str(id_).isdigit() or isinstance(id_, int):
866 if str(id_).isdigit() or isinstance(id_, int):
866 id_ = int(id_)
867 id_ = int(id_)
867 user = User.get(id_)
868 user = User.get(id_)
868 if user is not None:
869 if user is not None:
869 return person_getter(user)
870 return person_getter(user)
870 return id_
871 return id_
871
872
872
873
873 def gravatar_with_user(request, author, show_disabled=False):
874 def gravatar_with_user(request, author, show_disabled=False):
874 _render = request.get_partial_renderer(
875 _render = request.get_partial_renderer(
875 'rhodecode:templates/base/base.mako')
876 'rhodecode:templates/base/base.mako')
876 return _render('gravatar_with_user', author, show_disabled=show_disabled)
877 return _render('gravatar_with_user', author, show_disabled=show_disabled)
877
878
878
879
879 tags_paterns = OrderedDict((
880 tags_paterns = OrderedDict((
880 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
881 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
881 '<div class="metatag" tag="lang">\\2</div>')),
882 '<div class="metatag" tag="lang">\\2</div>')),
882
883
883 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
884 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
884 '<div class="metatag" tag="see">see: \\1 </div>')),
885 '<div class="metatag" tag="see">see: \\1 </div>')),
885
886
886 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
887 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
887 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
888 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
888
889
889 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
890 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
890 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
891 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
891
892
892 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
893 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
893 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
894 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
894
895
895 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
896 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
896 '<div class="metatag" tag="state \\1">\\1</div>')),
897 '<div class="metatag" tag="state \\1">\\1</div>')),
897
898
898 # label in grey
899 # label in grey
899 ('label', (re.compile(r'\[([a-z]+)\]'),
900 ('label', (re.compile(r'\[([a-z]+)\]'),
900 '<div class="metatag" tag="label">\\1</div>')),
901 '<div class="metatag" tag="label">\\1</div>')),
901
902
902 # generic catch all in grey
903 # generic catch all in grey
903 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
904 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
904 '<div class="metatag" tag="generic">\\1</div>')),
905 '<div class="metatag" tag="generic">\\1</div>')),
905 ))
906 ))
906
907
907
908
908 def extract_metatags(value):
909 def extract_metatags(value):
909 """
910 """
910 Extract supported meta-tags from given text value
911 Extract supported meta-tags from given text value
911 """
912 """
912 tags = []
913 tags = []
913 if not value:
914 if not value:
914 return tags, ''
915 return tags, ''
915
916
916 for key, val in tags_paterns.items():
917 for key, val in tags_paterns.items():
917 pat, replace_html = val
918 pat, replace_html = val
918 tags.extend([(key, x.group()) for x in pat.finditer(value)])
919 tags.extend([(key, x.group()) for x in pat.finditer(value)])
919 value = pat.sub('', value)
920 value = pat.sub('', value)
920
921
921 return tags, value
922 return tags, value
922
923
923
924
924 def style_metatag(tag_type, value):
925 def style_metatag(tag_type, value):
925 """
926 """
926 converts tags from value into html equivalent
927 converts tags from value into html equivalent
927 """
928 """
928 if not value:
929 if not value:
929 return ''
930 return ''
930
931
931 html_value = value
932 html_value = value
932 tag_data = tags_paterns.get(tag_type)
933 tag_data = tags_paterns.get(tag_type)
933 if tag_data:
934 if tag_data:
934 pat, replace_html = tag_data
935 pat, replace_html = tag_data
935 # convert to plain `unicode` instead of a markup tag to be used in
936 # convert to plain `unicode` instead of a markup tag to be used in
936 # regex expressions. safe_unicode doesn't work here
937 # regex expressions. safe_unicode doesn't work here
937 html_value = pat.sub(replace_html, unicode(value))
938 html_value = pat.sub(replace_html, unicode(value))
938
939
939 return html_value
940 return html_value
940
941
941
942
942 def bool2icon(value, show_at_false=True):
943 def bool2icon(value, show_at_false=True):
943 """
944 """
944 Returns boolean value of a given value, represented as html element with
945 Returns boolean value of a given value, represented as html element with
945 classes that will represent icons
946 classes that will represent icons
946
947
947 :param value: given value to convert to html node
948 :param value: given value to convert to html node
948 """
949 """
949
950
950 if value: # does bool conversion
951 if value: # does bool conversion
951 return HTML.tag('i', class_="icon-true")
952 return HTML.tag('i', class_="icon-true")
952 else: # not true as bool
953 else: # not true as bool
953 if show_at_false:
954 if show_at_false:
954 return HTML.tag('i', class_="icon-false")
955 return HTML.tag('i', class_="icon-false")
955 return HTML.tag('i')
956 return HTML.tag('i')
956
957
957 #==============================================================================
958 #==============================================================================
958 # PERMS
959 # PERMS
959 #==============================================================================
960 #==============================================================================
960 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
961 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
961 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
962 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
962 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
963 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
963 csrf_token_key
964 csrf_token_key
964
965
965
966
966 #==============================================================================
967 #==============================================================================
967 # GRAVATAR URL
968 # GRAVATAR URL
968 #==============================================================================
969 #==============================================================================
969 class InitialsGravatar(object):
970 class InitialsGravatar(object):
970 def __init__(self, email_address, first_name, last_name, size=30,
971 def __init__(self, email_address, first_name, last_name, size=30,
971 background=None, text_color='#fff'):
972 background=None, text_color='#fff'):
972 self.size = size
973 self.size = size
973 self.first_name = first_name
974 self.first_name = first_name
974 self.last_name = last_name
975 self.last_name = last_name
975 self.email_address = email_address
976 self.email_address = email_address
976 self.background = background or self.str2color(email_address)
977 self.background = background or self.str2color(email_address)
977 self.text_color = text_color
978 self.text_color = text_color
978
979
979 def get_color_bank(self):
980 def get_color_bank(self):
980 """
981 """
981 returns a predefined list of colors that gravatars can use.
982 returns a predefined list of colors that gravatars can use.
982 Those are randomized distinct colors that guarantee readability and
983 Those are randomized distinct colors that guarantee readability and
983 uniqueness.
984 uniqueness.
984
985
985 generated with: http://phrogz.net/css/distinct-colors.html
986 generated with: http://phrogz.net/css/distinct-colors.html
986 """
987 """
987 return [
988 return [
988 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
989 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
989 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
990 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
990 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
991 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
991 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
992 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
992 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
993 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
993 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
994 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
994 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
995 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
995 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
996 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
996 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
997 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
997 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
998 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
998 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
999 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
999 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1000 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1000 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1001 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1001 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1002 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1002 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1003 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1003 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1004 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1004 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1005 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1005 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1006 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1006 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1007 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1007 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1008 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1008 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1009 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1009 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1010 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1010 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1011 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1011 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1012 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1012 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1013 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1013 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1014 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1014 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1015 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1015 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1016 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1016 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1017 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1017 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1018 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1018 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1019 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1019 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1020 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1020 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1021 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1021 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1022 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1022 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1023 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1023 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1024 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1024 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1025 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1025 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1026 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1026 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1027 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1027 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1028 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1028 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1029 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1029 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1030 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1030 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1031 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1031 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1032 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1032 '#4f8c46', '#368dd9', '#5c0073'
1033 '#4f8c46', '#368dd9', '#5c0073'
1033 ]
1034 ]
1034
1035
1035 def rgb_to_hex_color(self, rgb_tuple):
1036 def rgb_to_hex_color(self, rgb_tuple):
1036 """
1037 """
1037 Converts an rgb_tuple passed to an hex color.
1038 Converts an rgb_tuple passed to an hex color.
1038
1039
1039 :param rgb_tuple: tuple with 3 ints represents rgb color space
1040 :param rgb_tuple: tuple with 3 ints represents rgb color space
1040 """
1041 """
1041 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1042 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1042
1043
1043 def email_to_int_list(self, email_str):
1044 def email_to_int_list(self, email_str):
1044 """
1045 """
1045 Get every byte of the hex digest value of email and turn it to integer.
1046 Get every byte of the hex digest value of email and turn it to integer.
1046 It's going to be always between 0-255
1047 It's going to be always between 0-255
1047 """
1048 """
1048 digest = md5_safe(email_str.lower())
1049 digest = md5_safe(email_str.lower())
1049 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1050 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1050
1051
1051 def pick_color_bank_index(self, email_str, color_bank):
1052 def pick_color_bank_index(self, email_str, color_bank):
1052 return self.email_to_int_list(email_str)[0] % len(color_bank)
1053 return self.email_to_int_list(email_str)[0] % len(color_bank)
1053
1054
1054 def str2color(self, email_str):
1055 def str2color(self, email_str):
1055 """
1056 """
1056 Tries to map in a stable algorithm an email to color
1057 Tries to map in a stable algorithm an email to color
1057
1058
1058 :param email_str:
1059 :param email_str:
1059 """
1060 """
1060 color_bank = self.get_color_bank()
1061 color_bank = self.get_color_bank()
1061 # pick position (module it's length so we always find it in the
1062 # pick position (module it's length so we always find it in the
1062 # bank even if it's smaller than 256 values
1063 # bank even if it's smaller than 256 values
1063 pos = self.pick_color_bank_index(email_str, color_bank)
1064 pos = self.pick_color_bank_index(email_str, color_bank)
1064 return color_bank[pos]
1065 return color_bank[pos]
1065
1066
1066 def normalize_email(self, email_address):
1067 def normalize_email(self, email_address):
1067 import unicodedata
1068 import unicodedata
1068 # default host used to fill in the fake/missing email
1069 # default host used to fill in the fake/missing email
1069 default_host = u'localhost'
1070 default_host = u'localhost'
1070
1071
1071 if not email_address:
1072 if not email_address:
1072 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1073 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1073
1074
1074 email_address = safe_unicode(email_address)
1075 email_address = safe_unicode(email_address)
1075
1076
1076 if u'@' not in email_address:
1077 if u'@' not in email_address:
1077 email_address = u'%s@%s' % (email_address, default_host)
1078 email_address = u'%s@%s' % (email_address, default_host)
1078
1079
1079 if email_address.endswith(u'@'):
1080 if email_address.endswith(u'@'):
1080 email_address = u'%s%s' % (email_address, default_host)
1081 email_address = u'%s%s' % (email_address, default_host)
1081
1082
1082 email_address = unicodedata.normalize('NFKD', email_address)\
1083 email_address = unicodedata.normalize('NFKD', email_address)\
1083 .encode('ascii', 'ignore')
1084 .encode('ascii', 'ignore')
1084 return email_address
1085 return email_address
1085
1086
1086 def get_initials(self):
1087 def get_initials(self):
1087 """
1088 """
1088 Returns 2 letter initials calculated based on the input.
1089 Returns 2 letter initials calculated based on the input.
1089 The algorithm picks first given email address, and takes first letter
1090 The algorithm picks first given email address, and takes first letter
1090 of part before @, and then the first letter of server name. In case
1091 of part before @, and then the first letter of server name. In case
1091 the part before @ is in a format of `somestring.somestring2` it replaces
1092 the part before @ is in a format of `somestring.somestring2` it replaces
1092 the server letter with first letter of somestring2
1093 the server letter with first letter of somestring2
1093
1094
1094 In case function was initialized with both first and lastname, this
1095 In case function was initialized with both first and lastname, this
1095 overrides the extraction from email by first letter of the first and
1096 overrides the extraction from email by first letter of the first and
1096 last name. We add special logic to that functionality, In case Full name
1097 last name. We add special logic to that functionality, In case Full name
1097 is compound, like Guido Von Rossum, we use last part of the last name
1098 is compound, like Guido Von Rossum, we use last part of the last name
1098 (Von Rossum) picking `R`.
1099 (Von Rossum) picking `R`.
1099
1100
1100 Function also normalizes the non-ascii characters to they ascii
1101 Function also normalizes the non-ascii characters to they ascii
1101 representation, eg Δ„ => A
1102 representation, eg Δ„ => A
1102 """
1103 """
1103 import unicodedata
1104 import unicodedata
1104 # replace non-ascii to ascii
1105 # replace non-ascii to ascii
1105 first_name = unicodedata.normalize(
1106 first_name = unicodedata.normalize(
1106 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1107 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1107 last_name = unicodedata.normalize(
1108 last_name = unicodedata.normalize(
1108 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1109 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1109
1110
1110 # do NFKD encoding, and also make sure email has proper format
1111 # do NFKD encoding, and also make sure email has proper format
1111 email_address = self.normalize_email(self.email_address)
1112 email_address = self.normalize_email(self.email_address)
1112
1113
1113 # first push the email initials
1114 # first push the email initials
1114 prefix, server = email_address.split('@', 1)
1115 prefix, server = email_address.split('@', 1)
1115
1116
1116 # check if prefix is maybe a 'first_name.last_name' syntax
1117 # check if prefix is maybe a 'first_name.last_name' syntax
1117 _dot_split = prefix.rsplit('.', 1)
1118 _dot_split = prefix.rsplit('.', 1)
1118 if len(_dot_split) == 2 and _dot_split[1]:
1119 if len(_dot_split) == 2 and _dot_split[1]:
1119 initials = [_dot_split[0][0], _dot_split[1][0]]
1120 initials = [_dot_split[0][0], _dot_split[1][0]]
1120 else:
1121 else:
1121 initials = [prefix[0], server[0]]
1122 initials = [prefix[0], server[0]]
1122
1123
1123 # then try to replace either first_name or last_name
1124 # then try to replace either first_name or last_name
1124 fn_letter = (first_name or " ")[0].strip()
1125 fn_letter = (first_name or " ")[0].strip()
1125 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1126 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1126
1127
1127 if fn_letter:
1128 if fn_letter:
1128 initials[0] = fn_letter
1129 initials[0] = fn_letter
1129
1130
1130 if ln_letter:
1131 if ln_letter:
1131 initials[1] = ln_letter
1132 initials[1] = ln_letter
1132
1133
1133 return ''.join(initials).upper()
1134 return ''.join(initials).upper()
1134
1135
1135 def get_img_data_by_type(self, font_family, img_type):
1136 def get_img_data_by_type(self, font_family, img_type):
1136 default_user = """
1137 default_user = """
1137 <svg xmlns="http://www.w3.org/2000/svg"
1138 <svg xmlns="http://www.w3.org/2000/svg"
1138 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1139 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1139 viewBox="-15 -10 439.165 429.164"
1140 viewBox="-15 -10 439.165 429.164"
1140
1141
1141 xml:space="preserve"
1142 xml:space="preserve"
1142 style="background:{background};" >
1143 style="background:{background};" >
1143
1144
1144 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1145 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1145 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1146 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1146 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1147 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1147 168.596,153.916,216.671,
1148 168.596,153.916,216.671,
1148 204.583,216.671z" fill="{text_color}"/>
1149 204.583,216.671z" fill="{text_color}"/>
1149 <path d="M407.164,374.717L360.88,
1150 <path d="M407.164,374.717L360.88,
1150 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1151 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1151 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1152 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1152 15.366-44.203,23.488-69.076,23.488c-24.877,
1153 15.366-44.203,23.488-69.076,23.488c-24.877,
1153 0-48.762-8.122-69.078-23.488
1154 0-48.762-8.122-69.078-23.488
1154 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1155 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1155 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1156 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1156 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1157 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1157 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1158 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1158 19.402-10.527 C409.699,390.129,
1159 19.402-10.527 C409.699,390.129,
1159 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1160 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1160 </svg>""".format(
1161 </svg>""".format(
1161 size=self.size,
1162 size=self.size,
1162 background='#979797', # @grey4
1163 background='#979797', # @grey4
1163 text_color=self.text_color,
1164 text_color=self.text_color,
1164 font_family=font_family)
1165 font_family=font_family)
1165
1166
1166 return {
1167 return {
1167 "default_user": default_user
1168 "default_user": default_user
1168 }[img_type]
1169 }[img_type]
1169
1170
1170 def get_img_data(self, svg_type=None):
1171 def get_img_data(self, svg_type=None):
1171 """
1172 """
1172 generates the svg metadata for image
1173 generates the svg metadata for image
1173 """
1174 """
1174 fonts = [
1175 fonts = [
1175 '-apple-system',
1176 '-apple-system',
1176 'BlinkMacSystemFont',
1177 'BlinkMacSystemFont',
1177 'Segoe UI',
1178 'Segoe UI',
1178 'Roboto',
1179 'Roboto',
1179 'Oxygen-Sans',
1180 'Oxygen-Sans',
1180 'Ubuntu',
1181 'Ubuntu',
1181 'Cantarell',
1182 'Cantarell',
1182 'Helvetica Neue',
1183 'Helvetica Neue',
1183 'sans-serif'
1184 'sans-serif'
1184 ]
1185 ]
1185 font_family = ','.join(fonts)
1186 font_family = ','.join(fonts)
1186 if svg_type:
1187 if svg_type:
1187 return self.get_img_data_by_type(font_family, svg_type)
1188 return self.get_img_data_by_type(font_family, svg_type)
1188
1189
1189 initials = self.get_initials()
1190 initials = self.get_initials()
1190 img_data = """
1191 img_data = """
1191 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1192 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1192 width="{size}" height="{size}"
1193 width="{size}" height="{size}"
1193 style="width: 100%; height: 100%; background-color: {background}"
1194 style="width: 100%; height: 100%; background-color: {background}"
1194 viewBox="0 0 {size} {size}">
1195 viewBox="0 0 {size} {size}">
1195 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1196 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1196 pointer-events="auto" fill="{text_color}"
1197 pointer-events="auto" fill="{text_color}"
1197 font-family="{font_family}"
1198 font-family="{font_family}"
1198 style="font-weight: 400; font-size: {f_size}px;">{text}
1199 style="font-weight: 400; font-size: {f_size}px;">{text}
1199 </text>
1200 </text>
1200 </svg>""".format(
1201 </svg>""".format(
1201 size=self.size,
1202 size=self.size,
1202 f_size=self.size/2.05, # scale the text inside the box nicely
1203 f_size=self.size/2.05, # scale the text inside the box nicely
1203 background=self.background,
1204 background=self.background,
1204 text_color=self.text_color,
1205 text_color=self.text_color,
1205 text=initials.upper(),
1206 text=initials.upper(),
1206 font_family=font_family)
1207 font_family=font_family)
1207
1208
1208 return img_data
1209 return img_data
1209
1210
1210 def generate_svg(self, svg_type=None):
1211 def generate_svg(self, svg_type=None):
1211 img_data = self.get_img_data(svg_type)
1212 img_data = self.get_img_data(svg_type)
1212 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1213 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1213
1214
1214
1215
1215 def initials_gravatar(email_address, first_name, last_name, size=30):
1216 def initials_gravatar(email_address, first_name, last_name, size=30):
1216 svg_type = None
1217 svg_type = None
1217 if email_address == User.DEFAULT_USER_EMAIL:
1218 if email_address == User.DEFAULT_USER_EMAIL:
1218 svg_type = 'default_user'
1219 svg_type = 'default_user'
1219 klass = InitialsGravatar(email_address, first_name, last_name, size)
1220 klass = InitialsGravatar(email_address, first_name, last_name, size)
1220 return klass.generate_svg(svg_type=svg_type)
1221 return klass.generate_svg(svg_type=svg_type)
1221
1222
1222
1223
1223 def gravatar_url(email_address, size=30, request=None):
1224 def gravatar_url(email_address, size=30, request=None):
1224 request = get_current_request()
1225 request = get_current_request()
1225 _use_gravatar = request.call_context.visual.use_gravatar
1226 _use_gravatar = request.call_context.visual.use_gravatar
1226 _gravatar_url = request.call_context.visual.gravatar_url
1227 _gravatar_url = request.call_context.visual.gravatar_url
1227
1228
1228 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1229 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1229
1230
1230 email_address = email_address or User.DEFAULT_USER_EMAIL
1231 email_address = email_address or User.DEFAULT_USER_EMAIL
1231 if isinstance(email_address, unicode):
1232 if isinstance(email_address, unicode):
1232 # hashlib crashes on unicode items
1233 # hashlib crashes on unicode items
1233 email_address = safe_str(email_address)
1234 email_address = safe_str(email_address)
1234
1235
1235 # empty email or default user
1236 # empty email or default user
1236 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1237 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1237 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1238 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1238
1239
1239 if _use_gravatar:
1240 if _use_gravatar:
1240 # TODO: Disuse pyramid thread locals. Think about another solution to
1241 # TODO: Disuse pyramid thread locals. Think about another solution to
1241 # get the host and schema here.
1242 # get the host and schema here.
1242 request = get_current_request()
1243 request = get_current_request()
1243 tmpl = safe_str(_gravatar_url)
1244 tmpl = safe_str(_gravatar_url)
1244 tmpl = tmpl.replace('{email}', email_address)\
1245 tmpl = tmpl.replace('{email}', email_address)\
1245 .replace('{md5email}', md5_safe(email_address.lower())) \
1246 .replace('{md5email}', md5_safe(email_address.lower())) \
1246 .replace('{netloc}', request.host)\
1247 .replace('{netloc}', request.host)\
1247 .replace('{scheme}', request.scheme)\
1248 .replace('{scheme}', request.scheme)\
1248 .replace('{size}', safe_str(size))
1249 .replace('{size}', safe_str(size))
1249 return tmpl
1250 return tmpl
1250 else:
1251 else:
1251 return initials_gravatar(email_address, '', '', size=size)
1252 return initials_gravatar(email_address, '', '', size=size)
1252
1253
1253
1254
1254 class Page(_Page):
1255 class Page(_Page):
1255 """
1256 """
1256 Custom pager to match rendering style with paginator
1257 Custom pager to match rendering style with paginator
1257 """
1258 """
1258
1259
1259 def _get_pos(self, cur_page, max_page, items):
1260 def _get_pos(self, cur_page, max_page, items):
1260 edge = (items / 2) + 1
1261 edge = (items / 2) + 1
1261 if (cur_page <= edge):
1262 if (cur_page <= edge):
1262 radius = max(items / 2, items - cur_page)
1263 radius = max(items / 2, items - cur_page)
1263 elif (max_page - cur_page) < edge:
1264 elif (max_page - cur_page) < edge:
1264 radius = (items - 1) - (max_page - cur_page)
1265 radius = (items - 1) - (max_page - cur_page)
1265 else:
1266 else:
1266 radius = items / 2
1267 radius = items / 2
1267
1268
1268 left = max(1, (cur_page - (radius)))
1269 left = max(1, (cur_page - (radius)))
1269 right = min(max_page, cur_page + (radius))
1270 right = min(max_page, cur_page + (radius))
1270 return left, cur_page, right
1271 return left, cur_page, right
1271
1272
1272 def _range(self, regexp_match):
1273 def _range(self, regexp_match):
1273 """
1274 """
1274 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1275 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1275
1276
1276 Arguments:
1277 Arguments:
1277
1278
1278 regexp_match
1279 regexp_match
1279 A "re" (regular expressions) match object containing the
1280 A "re" (regular expressions) match object containing the
1280 radius of linked pages around the current page in
1281 radius of linked pages around the current page in
1281 regexp_match.group(1) as a string
1282 regexp_match.group(1) as a string
1282
1283
1283 This function is supposed to be called as a callable in
1284 This function is supposed to be called as a callable in
1284 re.sub.
1285 re.sub.
1285
1286
1286 """
1287 """
1287 radius = int(regexp_match.group(1))
1288 radius = int(regexp_match.group(1))
1288
1289
1289 # Compute the first and last page number within the radius
1290 # Compute the first and last page number within the radius
1290 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1291 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1291 # -> leftmost_page = 5
1292 # -> leftmost_page = 5
1292 # -> rightmost_page = 9
1293 # -> rightmost_page = 9
1293 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1294 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1294 self.last_page,
1295 self.last_page,
1295 (radius * 2) + 1)
1296 (radius * 2) + 1)
1296 nav_items = []
1297 nav_items = []
1297
1298
1298 # Create a link to the first page (unless we are on the first page
1299 # Create a link to the first page (unless we are on the first page
1299 # or there would be no need to insert '..' spacers)
1300 # or there would be no need to insert '..' spacers)
1300 if self.page != self.first_page and self.first_page < leftmost_page:
1301 if self.page != self.first_page and self.first_page < leftmost_page:
1301 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1302 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1302
1303
1303 # Insert dots if there are pages between the first page
1304 # Insert dots if there are pages between the first page
1304 # and the currently displayed page range
1305 # and the currently displayed page range
1305 if leftmost_page - self.first_page > 1:
1306 if leftmost_page - self.first_page > 1:
1306 # Wrap in a SPAN tag if nolink_attr is set
1307 # Wrap in a SPAN tag if nolink_attr is set
1307 text = '..'
1308 text = '..'
1308 if self.dotdot_attr:
1309 if self.dotdot_attr:
1309 text = HTML.span(c=text, **self.dotdot_attr)
1310 text = HTML.span(c=text, **self.dotdot_attr)
1310 nav_items.append(text)
1311 nav_items.append(text)
1311
1312
1312 for thispage in xrange(leftmost_page, rightmost_page + 1):
1313 for thispage in xrange(leftmost_page, rightmost_page + 1):
1313 # Hilight the current page number and do not use a link
1314 # Hilight the current page number and do not use a link
1314 if thispage == self.page:
1315 if thispage == self.page:
1315 text = '%s' % (thispage,)
1316 text = '%s' % (thispage,)
1316 # Wrap in a SPAN tag if nolink_attr is set
1317 # Wrap in a SPAN tag if nolink_attr is set
1317 if self.curpage_attr:
1318 if self.curpage_attr:
1318 text = HTML.span(c=text, **self.curpage_attr)
1319 text = HTML.span(c=text, **self.curpage_attr)
1319 nav_items.append(text)
1320 nav_items.append(text)
1320 # Otherwise create just a link to that page
1321 # Otherwise create just a link to that page
1321 else:
1322 else:
1322 text = '%s' % (thispage,)
1323 text = '%s' % (thispage,)
1323 nav_items.append(self._pagerlink(thispage, text))
1324 nav_items.append(self._pagerlink(thispage, text))
1324
1325
1325 # Insert dots if there are pages between the displayed
1326 # Insert dots if there are pages between the displayed
1326 # page numbers and the end of the page range
1327 # page numbers and the end of the page range
1327 if self.last_page - rightmost_page > 1:
1328 if self.last_page - rightmost_page > 1:
1328 text = '..'
1329 text = '..'
1329 # Wrap in a SPAN tag if nolink_attr is set
1330 # Wrap in a SPAN tag if nolink_attr is set
1330 if self.dotdot_attr:
1331 if self.dotdot_attr:
1331 text = HTML.span(c=text, **self.dotdot_attr)
1332 text = HTML.span(c=text, **self.dotdot_attr)
1332 nav_items.append(text)
1333 nav_items.append(text)
1333
1334
1334 # Create a link to the very last page (unless we are on the last
1335 # Create a link to the very last page (unless we are on the last
1335 # page or there would be no need to insert '..' spacers)
1336 # page or there would be no need to insert '..' spacers)
1336 if self.page != self.last_page and rightmost_page < self.last_page:
1337 if self.page != self.last_page and rightmost_page < self.last_page:
1337 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1338 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1338
1339
1339 ## prerender links
1340 ## prerender links
1340 #_page_link = url.current()
1341 #_page_link = url.current()
1341 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1342 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1342 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1343 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1343 return self.separator.join(nav_items)
1344 return self.separator.join(nav_items)
1344
1345
1345 def pager(self, format='~2~', page_param='page', partial_param='partial',
1346 def pager(self, format='~2~', page_param='page', partial_param='partial',
1346 show_if_single_page=False, separator=' ', onclick=None,
1347 show_if_single_page=False, separator=' ', onclick=None,
1347 symbol_first='<<', symbol_last='>>',
1348 symbol_first='<<', symbol_last='>>',
1348 symbol_previous='<', symbol_next='>',
1349 symbol_previous='<', symbol_next='>',
1349 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1350 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1350 curpage_attr={'class': 'pager_curpage'},
1351 curpage_attr={'class': 'pager_curpage'},
1351 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1352 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1352
1353
1353 self.curpage_attr = curpage_attr
1354 self.curpage_attr = curpage_attr
1354 self.separator = separator
1355 self.separator = separator
1355 self.pager_kwargs = kwargs
1356 self.pager_kwargs = kwargs
1356 self.page_param = page_param
1357 self.page_param = page_param
1357 self.partial_param = partial_param
1358 self.partial_param = partial_param
1358 self.onclick = onclick
1359 self.onclick = onclick
1359 self.link_attr = link_attr
1360 self.link_attr = link_attr
1360 self.dotdot_attr = dotdot_attr
1361 self.dotdot_attr = dotdot_attr
1361
1362
1362 # Don't show navigator if there is no more than one page
1363 # Don't show navigator if there is no more than one page
1363 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1364 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1364 return ''
1365 return ''
1365
1366
1366 from string import Template
1367 from string import Template
1367 # Replace ~...~ in token format by range of pages
1368 # Replace ~...~ in token format by range of pages
1368 result = re.sub(r'~(\d+)~', self._range, format)
1369 result = re.sub(r'~(\d+)~', self._range, format)
1369
1370
1370 # Interpolate '%' variables
1371 # Interpolate '%' variables
1371 result = Template(result).safe_substitute({
1372 result = Template(result).safe_substitute({
1372 'first_page': self.first_page,
1373 'first_page': self.first_page,
1373 'last_page': self.last_page,
1374 'last_page': self.last_page,
1374 'page': self.page,
1375 'page': self.page,
1375 'page_count': self.page_count,
1376 'page_count': self.page_count,
1376 'items_per_page': self.items_per_page,
1377 'items_per_page': self.items_per_page,
1377 'first_item': self.first_item,
1378 'first_item': self.first_item,
1378 'last_item': self.last_item,
1379 'last_item': self.last_item,
1379 'item_count': self.item_count,
1380 'item_count': self.item_count,
1380 'link_first': self.page > self.first_page and \
1381 'link_first': self.page > self.first_page and \
1381 self._pagerlink(self.first_page, symbol_first) or '',
1382 self._pagerlink(self.first_page, symbol_first) or '',
1382 'link_last': self.page < self.last_page and \
1383 'link_last': self.page < self.last_page and \
1383 self._pagerlink(self.last_page, symbol_last) or '',
1384 self._pagerlink(self.last_page, symbol_last) or '',
1384 'link_previous': self.previous_page and \
1385 'link_previous': self.previous_page and \
1385 self._pagerlink(self.previous_page, symbol_previous) \
1386 self._pagerlink(self.previous_page, symbol_previous) \
1386 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1387 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1387 'link_next': self.next_page and \
1388 'link_next': self.next_page and \
1388 self._pagerlink(self.next_page, symbol_next) \
1389 self._pagerlink(self.next_page, symbol_next) \
1389 or HTML.span(symbol_next, class_="pg-next disabled")
1390 or HTML.span(symbol_next, class_="pg-next disabled")
1390 })
1391 })
1391
1392
1392 return literal(result)
1393 return literal(result)
1393
1394
1394
1395
1395 #==============================================================================
1396 #==============================================================================
1396 # REPO PAGER, PAGER FOR REPOSITORY
1397 # REPO PAGER, PAGER FOR REPOSITORY
1397 #==============================================================================
1398 #==============================================================================
1398 class RepoPage(Page):
1399 class RepoPage(Page):
1399
1400
1400 def __init__(self, collection, page=1, items_per_page=20,
1401 def __init__(self, collection, page=1, items_per_page=20,
1401 item_count=None, url=None, **kwargs):
1402 item_count=None, url=None, **kwargs):
1402
1403
1403 """Create a "RepoPage" instance. special pager for paging
1404 """Create a "RepoPage" instance. special pager for paging
1404 repository
1405 repository
1405 """
1406 """
1406 self._url_generator = url
1407 self._url_generator = url
1407
1408
1408 # Safe the kwargs class-wide so they can be used in the pager() method
1409 # Safe the kwargs class-wide so they can be used in the pager() method
1409 self.kwargs = kwargs
1410 self.kwargs = kwargs
1410
1411
1411 # Save a reference to the collection
1412 # Save a reference to the collection
1412 self.original_collection = collection
1413 self.original_collection = collection
1413
1414
1414 self.collection = collection
1415 self.collection = collection
1415
1416
1416 # The self.page is the number of the current page.
1417 # The self.page is the number of the current page.
1417 # The first page has the number 1!
1418 # The first page has the number 1!
1418 try:
1419 try:
1419 self.page = int(page) # make it int() if we get it as a string
1420 self.page = int(page) # make it int() if we get it as a string
1420 except (ValueError, TypeError):
1421 except (ValueError, TypeError):
1421 self.page = 1
1422 self.page = 1
1422
1423
1423 self.items_per_page = items_per_page
1424 self.items_per_page = items_per_page
1424
1425
1425 # Unless the user tells us how many items the collections has
1426 # Unless the user tells us how many items the collections has
1426 # we calculate that ourselves.
1427 # we calculate that ourselves.
1427 if item_count is not None:
1428 if item_count is not None:
1428 self.item_count = item_count
1429 self.item_count = item_count
1429 else:
1430 else:
1430 self.item_count = len(self.collection)
1431 self.item_count = len(self.collection)
1431
1432
1432 # Compute the number of the first and last available page
1433 # Compute the number of the first and last available page
1433 if self.item_count > 0:
1434 if self.item_count > 0:
1434 self.first_page = 1
1435 self.first_page = 1
1435 self.page_count = int(math.ceil(float(self.item_count) /
1436 self.page_count = int(math.ceil(float(self.item_count) /
1436 self.items_per_page))
1437 self.items_per_page))
1437 self.last_page = self.first_page + self.page_count - 1
1438 self.last_page = self.first_page + self.page_count - 1
1438
1439
1439 # Make sure that the requested page number is the range of
1440 # Make sure that the requested page number is the range of
1440 # valid pages
1441 # valid pages
1441 if self.page > self.last_page:
1442 if self.page > self.last_page:
1442 self.page = self.last_page
1443 self.page = self.last_page
1443 elif self.page < self.first_page:
1444 elif self.page < self.first_page:
1444 self.page = self.first_page
1445 self.page = self.first_page
1445
1446
1446 # Note: the number of items on this page can be less than
1447 # Note: the number of items on this page can be less than
1447 # items_per_page if the last page is not full
1448 # items_per_page if the last page is not full
1448 self.first_item = max(0, (self.item_count) - (self.page *
1449 self.first_item = max(0, (self.item_count) - (self.page *
1449 items_per_page))
1450 items_per_page))
1450 self.last_item = ((self.item_count - 1) - items_per_page *
1451 self.last_item = ((self.item_count - 1) - items_per_page *
1451 (self.page - 1))
1452 (self.page - 1))
1452
1453
1453 self.items = list(self.collection[self.first_item:self.last_item + 1])
1454 self.items = list(self.collection[self.first_item:self.last_item + 1])
1454
1455
1455 # Links to previous and next page
1456 # Links to previous and next page
1456 if self.page > self.first_page:
1457 if self.page > self.first_page:
1457 self.previous_page = self.page - 1
1458 self.previous_page = self.page - 1
1458 else:
1459 else:
1459 self.previous_page = None
1460 self.previous_page = None
1460
1461
1461 if self.page < self.last_page:
1462 if self.page < self.last_page:
1462 self.next_page = self.page + 1
1463 self.next_page = self.page + 1
1463 else:
1464 else:
1464 self.next_page = None
1465 self.next_page = None
1465
1466
1466 # No items available
1467 # No items available
1467 else:
1468 else:
1468 self.first_page = None
1469 self.first_page = None
1469 self.page_count = 0
1470 self.page_count = 0
1470 self.last_page = None
1471 self.last_page = None
1471 self.first_item = None
1472 self.first_item = None
1472 self.last_item = None
1473 self.last_item = None
1473 self.previous_page = None
1474 self.previous_page = None
1474 self.next_page = None
1475 self.next_page = None
1475 self.items = []
1476 self.items = []
1476
1477
1477 # This is a subclass of the 'list' type. Initialise the list now.
1478 # This is a subclass of the 'list' type. Initialise the list now.
1478 list.__init__(self, reversed(self.items))
1479 list.__init__(self, reversed(self.items))
1479
1480
1480
1481
1481 def breadcrumb_repo_link(repo):
1482 def breadcrumb_repo_link(repo):
1482 """
1483 """
1483 Makes a breadcrumbs path link to repo
1484 Makes a breadcrumbs path link to repo
1484
1485
1485 ex::
1486 ex::
1486 group >> subgroup >> repo
1487 group >> subgroup >> repo
1487
1488
1488 :param repo: a Repository instance
1489 :param repo: a Repository instance
1489 """
1490 """
1490
1491
1491 path = [
1492 path = [
1492 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1493 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1493 title='last change:{}'.format(format_date(group.last_commit_change)))
1494 title='last change:{}'.format(format_date(group.last_commit_change)))
1494 for group in repo.groups_with_parents
1495 for group in repo.groups_with_parents
1495 ] + [
1496 ] + [
1496 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1497 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1497 title='last change:{}'.format(format_date(repo.last_commit_change)))
1498 title='last change:{}'.format(format_date(repo.last_commit_change)))
1498 ]
1499 ]
1499
1500
1500 return literal(' &raquo; '.join(path))
1501 return literal(' &raquo; '.join(path))
1501
1502
1502
1503
1503 def breadcrumb_repo_group_link(repo_group):
1504 def breadcrumb_repo_group_link(repo_group):
1504 """
1505 """
1505 Makes a breadcrumbs path link to repo
1506 Makes a breadcrumbs path link to repo
1506
1507
1507 ex::
1508 ex::
1508 group >> subgroup
1509 group >> subgroup
1509
1510
1510 :param repo_group: a Repository Group instance
1511 :param repo_group: a Repository Group instance
1511 """
1512 """
1512
1513
1513 path = [
1514 path = [
1514 link_to(group.name,
1515 link_to(group.name,
1515 route_path('repo_group_home', repo_group_name=group.group_name),
1516 route_path('repo_group_home', repo_group_name=group.group_name),
1516 title='last change:{}'.format(format_date(group.last_commit_change)))
1517 title='last change:{}'.format(format_date(group.last_commit_change)))
1517 for group in repo_group.parents
1518 for group in repo_group.parents
1518 ] + [
1519 ] + [
1519 link_to(repo_group.name,
1520 link_to(repo_group.name,
1520 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1521 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1521 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1522 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1522 ]
1523 ]
1523
1524
1524 return literal(' &raquo; '.join(path))
1525 return literal(' &raquo; '.join(path))
1525
1526
1526
1527
1527 def format_byte_size_binary(file_size):
1528 def format_byte_size_binary(file_size):
1528 """
1529 """
1529 Formats file/folder sizes to standard.
1530 Formats file/folder sizes to standard.
1530 """
1531 """
1531 if file_size is None:
1532 if file_size is None:
1532 file_size = 0
1533 file_size = 0
1533
1534
1534 formatted_size = format_byte_size(file_size, binary=True)
1535 formatted_size = format_byte_size(file_size, binary=True)
1535 return formatted_size
1536 return formatted_size
1536
1537
1537
1538
1538 def urlify_text(text_, safe=True):
1539 def urlify_text(text_, safe=True):
1539 """
1540 """
1540 Extrac urls from text and make html links out of them
1541 Extrac urls from text and make html links out of them
1541
1542
1542 :param text_:
1543 :param text_:
1543 """
1544 """
1544
1545
1545 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1546 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1546 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1547 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1547
1548
1548 def url_func(match_obj):
1549 def url_func(match_obj):
1549 url_full = match_obj.groups()[0]
1550 url_full = match_obj.groups()[0]
1550 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1551 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1551 _newtext = url_pat.sub(url_func, text_)
1552 _newtext = url_pat.sub(url_func, text_)
1552 if safe:
1553 if safe:
1553 return literal(_newtext)
1554 return literal(_newtext)
1554 return _newtext
1555 return _newtext
1555
1556
1556
1557
1557 def urlify_commits(text_, repository):
1558 def urlify_commits(text_, repository):
1558 """
1559 """
1559 Extract commit ids from text and make link from them
1560 Extract commit ids from text and make link from them
1560
1561
1561 :param text_:
1562 :param text_:
1562 :param repository: repo name to build the URL with
1563 :param repository: repo name to build the URL with
1563 """
1564 """
1564
1565
1565 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1566 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1566
1567
1567 def url_func(match_obj):
1568 def url_func(match_obj):
1568 commit_id = match_obj.groups()[1]
1569 commit_id = match_obj.groups()[1]
1569 pref = match_obj.groups()[0]
1570 pref = match_obj.groups()[0]
1570 suf = match_obj.groups()[2]
1571 suf = match_obj.groups()[2]
1571
1572
1572 tmpl = (
1573 tmpl = (
1573 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1574 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1574 '%(commit_id)s</a>%(suf)s'
1575 '%(commit_id)s</a>%(suf)s'
1575 )
1576 )
1576 return tmpl % {
1577 return tmpl % {
1577 'pref': pref,
1578 'pref': pref,
1578 'cls': 'revision-link',
1579 'cls': 'revision-link',
1579 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1580 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1580 'commit_id': commit_id,
1581 'commit_id': commit_id,
1581 'suf': suf
1582 'suf': suf
1582 }
1583 }
1583
1584
1584 newtext = URL_PAT.sub(url_func, text_)
1585 newtext = URL_PAT.sub(url_func, text_)
1585
1586
1586 return newtext
1587 return newtext
1587
1588
1588
1589
1589 def _process_url_func(match_obj, repo_name, uid, entry,
1590 def _process_url_func(match_obj, repo_name, uid, entry,
1590 return_raw_data=False, link_format='html'):
1591 return_raw_data=False, link_format='html'):
1591 pref = ''
1592 pref = ''
1592 if match_obj.group().startswith(' '):
1593 if match_obj.group().startswith(' '):
1593 pref = ' '
1594 pref = ' '
1594
1595
1595 issue_id = ''.join(match_obj.groups())
1596 issue_id = ''.join(match_obj.groups())
1596
1597
1597 if link_format == 'html':
1598 if link_format == 'html':
1598 tmpl = (
1599 tmpl = (
1599 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1600 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1600 '%(issue-prefix)s%(id-repr)s'
1601 '%(issue-prefix)s%(id-repr)s'
1601 '</a>')
1602 '</a>')
1602 elif link_format == 'rst':
1603 elif link_format == 'rst':
1603 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1604 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1604 elif link_format == 'markdown':
1605 elif link_format == 'markdown':
1605 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1606 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1606 else:
1607 else:
1607 raise ValueError('Bad link_format:{}'.format(link_format))
1608 raise ValueError('Bad link_format:{}'.format(link_format))
1608
1609
1609 (repo_name_cleaned,
1610 (repo_name_cleaned,
1610 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1611 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1611
1612
1612 # variables replacement
1613 # variables replacement
1613 named_vars = {
1614 named_vars = {
1614 'id': issue_id,
1615 'id': issue_id,
1615 'repo': repo_name,
1616 'repo': repo_name,
1616 'repo_name': repo_name_cleaned,
1617 'repo_name': repo_name_cleaned,
1617 'group_name': parent_group_name
1618 'group_name': parent_group_name
1618 }
1619 }
1619 # named regex variables
1620 # named regex variables
1620 named_vars.update(match_obj.groupdict())
1621 named_vars.update(match_obj.groupdict())
1621 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1622 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1622
1623
1623 def quote_cleaner(input_str):
1624 def quote_cleaner(input_str):
1624 """Remove quotes as it's HTML"""
1625 """Remove quotes as it's HTML"""
1625 return input_str.replace('"', '')
1626 return input_str.replace('"', '')
1626
1627
1627 data = {
1628 data = {
1628 'pref': pref,
1629 'pref': pref,
1629 'cls': quote_cleaner('issue-tracker-link'),
1630 'cls': quote_cleaner('issue-tracker-link'),
1630 'url': quote_cleaner(_url),
1631 'url': quote_cleaner(_url),
1631 'id-repr': issue_id,
1632 'id-repr': issue_id,
1632 'issue-prefix': entry['pref'],
1633 'issue-prefix': entry['pref'],
1633 'serv': entry['url'],
1634 'serv': entry['url'],
1634 }
1635 }
1635 if return_raw_data:
1636 if return_raw_data:
1636 return {
1637 return {
1637 'id': issue_id,
1638 'id': issue_id,
1638 'url': _url
1639 'url': _url
1639 }
1640 }
1640 return tmpl % data
1641 return tmpl % data
1641
1642
1642
1643
1643 def get_active_pattern_entries(repo_name):
1644 def get_active_pattern_entries(repo_name):
1644 repo = None
1645 repo = None
1645 if repo_name:
1646 if repo_name:
1646 # Retrieving repo_name to avoid invalid repo_name to explode on
1647 # Retrieving repo_name to avoid invalid repo_name to explode on
1647 # IssueTrackerSettingsModel but still passing invalid name further down
1648 # IssueTrackerSettingsModel but still passing invalid name further down
1648 repo = Repository.get_by_repo_name(repo_name, cache=True)
1649 repo = Repository.get_by_repo_name(repo_name, cache=True)
1649
1650
1650 settings_model = IssueTrackerSettingsModel(repo=repo)
1651 settings_model = IssueTrackerSettingsModel(repo=repo)
1651 active_entries = settings_model.get_settings(cache=True)
1652 active_entries = settings_model.get_settings(cache=True)
1652 return active_entries
1653 return active_entries
1653
1654
1654
1655
1655 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1656 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1656
1657
1657 allowed_formats = ['html', 'rst', 'markdown']
1658 allowed_formats = ['html', 'rst', 'markdown']
1658 if link_format not in allowed_formats:
1659 if link_format not in allowed_formats:
1659 raise ValueError('Link format can be only one of:{} got {}'.format(
1660 raise ValueError('Link format can be only one of:{} got {}'.format(
1660 allowed_formats, link_format))
1661 allowed_formats, link_format))
1661
1662
1662 active_entries = active_entries or get_active_pattern_entries(repo_name)
1663 active_entries = active_entries or get_active_pattern_entries(repo_name)
1663 issues_data = []
1664 issues_data = []
1664 newtext = text_string
1665 newtext = text_string
1665
1666
1666 for uid, entry in active_entries.items():
1667 for uid, entry in active_entries.items():
1667 log.debug('found issue tracker entry with uid %s', uid)
1668 log.debug('found issue tracker entry with uid %s', uid)
1668
1669
1669 if not (entry['pat'] and entry['url']):
1670 if not (entry['pat'] and entry['url']):
1670 log.debug('skipping due to missing data')
1671 log.debug('skipping due to missing data')
1671 continue
1672 continue
1672
1673
1673 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1674 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1674 uid, entry['pat'], entry['url'], entry['pref'])
1675 uid, entry['pat'], entry['url'], entry['pref'])
1675
1676
1676 try:
1677 try:
1677 pattern = re.compile(r'%s' % entry['pat'])
1678 pattern = re.compile(r'%s' % entry['pat'])
1678 except re.error:
1679 except re.error:
1679 log.exception(
1680 log.exception(
1680 'issue tracker pattern: `%s` failed to compile',
1681 'issue tracker pattern: `%s` failed to compile',
1681 entry['pat'])
1682 entry['pat'])
1682 continue
1683 continue
1683
1684
1684 data_func = partial(
1685 data_func = partial(
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1686 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1686 return_raw_data=True)
1687 return_raw_data=True)
1687
1688
1688 for match_obj in pattern.finditer(text_string):
1689 for match_obj in pattern.finditer(text_string):
1689 issues_data.append(data_func(match_obj))
1690 issues_data.append(data_func(match_obj))
1690
1691
1691 url_func = partial(
1692 url_func = partial(
1692 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1693 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1693 link_format=link_format)
1694 link_format=link_format)
1694
1695
1695 newtext = pattern.sub(url_func, newtext)
1696 newtext = pattern.sub(url_func, newtext)
1696 log.debug('processed prefix:uid `%s`', uid)
1697 log.debug('processed prefix:uid `%s`', uid)
1697
1698
1698 return newtext, issues_data
1699 return newtext, issues_data
1699
1700
1700
1701
1701 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1702 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1702 """
1703 """
1703 Parses given text message and makes proper links.
1704 Parses given text message and makes proper links.
1704 issues are linked to given issue-server, and rest is a commit link
1705 issues are linked to given issue-server, and rest is a commit link
1705
1706
1706 :param commit_text:
1707 :param commit_text:
1707 :param repository:
1708 :param repository:
1708 """
1709 """
1709 def escaper(string):
1710 def escaper(string):
1710 return string.replace('<', '&lt;').replace('>', '&gt;')
1711 return string.replace('<', '&lt;').replace('>', '&gt;')
1711
1712
1712 newtext = escaper(commit_text)
1713 newtext = escaper(commit_text)
1713
1714
1714 # extract http/https links and make them real urls
1715 # extract http/https links and make them real urls
1715 newtext = urlify_text(newtext, safe=False)
1716 newtext = urlify_text(newtext, safe=False)
1716
1717
1717 # urlify commits - extract commit ids and make link out of them, if we have
1718 # urlify commits - extract commit ids and make link out of them, if we have
1718 # the scope of repository present.
1719 # the scope of repository present.
1719 if repository:
1720 if repository:
1720 newtext = urlify_commits(newtext, repository)
1721 newtext = urlify_commits(newtext, repository)
1721
1722
1722 # process issue tracker patterns
1723 # process issue tracker patterns
1723 newtext, issues = process_patterns(newtext, repository or '',
1724 newtext, issues = process_patterns(newtext, repository or '',
1724 active_entries=active_pattern_entries)
1725 active_entries=active_pattern_entries)
1725
1726
1726 return literal(newtext)
1727 return literal(newtext)
1727
1728
1728
1729
1729 def render_binary(repo_name, file_obj):
1730 def render_binary(repo_name, file_obj):
1730 """
1731 """
1731 Choose how to render a binary file
1732 Choose how to render a binary file
1732 """
1733 """
1733
1734
1734 filename = file_obj.name
1735 filename = file_obj.name
1735
1736
1736 # images
1737 # images
1737 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1738 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1738 if fnmatch.fnmatch(filename, pat=ext):
1739 if fnmatch.fnmatch(filename, pat=ext):
1739 alt = escape(filename)
1740 alt = escape(filename)
1740 src = route_path(
1741 src = route_path(
1741 'repo_file_raw', repo_name=repo_name,
1742 'repo_file_raw', repo_name=repo_name,
1742 commit_id=file_obj.commit.raw_id,
1743 commit_id=file_obj.commit.raw_id,
1743 f_path=file_obj.path)
1744 f_path=file_obj.path)
1744 return literal(
1745 return literal(
1745 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1746 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1746
1747
1747
1748
1748 def renderer_from_filename(filename, exclude=None):
1749 def renderer_from_filename(filename, exclude=None):
1749 """
1750 """
1750 choose a renderer based on filename, this works only for text based files
1751 choose a renderer based on filename, this works only for text based files
1751 """
1752 """
1752
1753
1753 # ipython
1754 # ipython
1754 for ext in ['*.ipynb']:
1755 for ext in ['*.ipynb']:
1755 if fnmatch.fnmatch(filename, pat=ext):
1756 if fnmatch.fnmatch(filename, pat=ext):
1756 return 'jupyter'
1757 return 'jupyter'
1757
1758
1758 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1759 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1759 if is_markup:
1760 if is_markup:
1760 return is_markup
1761 return is_markup
1761 return None
1762 return None
1762
1763
1763
1764
1764 def render(source, renderer='rst', mentions=False, relative_urls=None,
1765 def render(source, renderer='rst', mentions=False, relative_urls=None,
1765 repo_name=None):
1766 repo_name=None):
1766
1767
1767 def maybe_convert_relative_links(html_source):
1768 def maybe_convert_relative_links(html_source):
1768 if relative_urls:
1769 if relative_urls:
1769 return relative_links(html_source, relative_urls)
1770 return relative_links(html_source, relative_urls)
1770 return html_source
1771 return html_source
1771
1772
1772 if renderer == 'plain':
1773 if renderer == 'plain':
1773 return literal(
1774 return literal(
1774 MarkupRenderer.plain(source, leading_newline=False))
1775 MarkupRenderer.plain(source, leading_newline=False))
1775
1776
1776 elif renderer == 'rst':
1777 elif renderer == 'rst':
1777 if repo_name:
1778 if repo_name:
1778 # process patterns on comments if we pass in repo name
1779 # process patterns on comments if we pass in repo name
1779 source, issues = process_patterns(
1780 source, issues = process_patterns(
1780 source, repo_name, link_format='rst')
1781 source, repo_name, link_format='rst')
1781
1782
1782 return literal(
1783 return literal(
1783 '<div class="rst-block">%s</div>' %
1784 '<div class="rst-block">%s</div>' %
1784 maybe_convert_relative_links(
1785 maybe_convert_relative_links(
1785 MarkupRenderer.rst(source, mentions=mentions)))
1786 MarkupRenderer.rst(source, mentions=mentions)))
1786
1787
1787 elif renderer == 'markdown':
1788 elif renderer == 'markdown':
1788 if repo_name:
1789 if repo_name:
1789 # process patterns on comments if we pass in repo name
1790 # process patterns on comments if we pass in repo name
1790 source, issues = process_patterns(
1791 source, issues = process_patterns(
1791 source, repo_name, link_format='markdown')
1792 source, repo_name, link_format='markdown')
1792
1793
1793 return literal(
1794 return literal(
1794 '<div class="markdown-block">%s</div>' %
1795 '<div class="markdown-block">%s</div>' %
1795 maybe_convert_relative_links(
1796 maybe_convert_relative_links(
1796 MarkupRenderer.markdown(source, flavored=True,
1797 MarkupRenderer.markdown(source, flavored=True,
1797 mentions=mentions)))
1798 mentions=mentions)))
1798
1799
1799 elif renderer == 'jupyter':
1800 elif renderer == 'jupyter':
1800 return literal(
1801 return literal(
1801 '<div class="ipynb">%s</div>' %
1802 '<div class="ipynb">%s</div>' %
1802 maybe_convert_relative_links(
1803 maybe_convert_relative_links(
1803 MarkupRenderer.jupyter(source)))
1804 MarkupRenderer.jupyter(source)))
1804
1805
1805 # None means just show the file-source
1806 # None means just show the file-source
1806 return None
1807 return None
1807
1808
1808
1809
1809 def commit_status(repo, commit_id):
1810 def commit_status(repo, commit_id):
1810 return ChangesetStatusModel().get_status(repo, commit_id)
1811 return ChangesetStatusModel().get_status(repo, commit_id)
1811
1812
1812
1813
1813 def commit_status_lbl(commit_status):
1814 def commit_status_lbl(commit_status):
1814 return dict(ChangesetStatus.STATUSES).get(commit_status)
1815 return dict(ChangesetStatus.STATUSES).get(commit_status)
1815
1816
1816
1817
1817 def commit_time(repo_name, commit_id):
1818 def commit_time(repo_name, commit_id):
1818 repo = Repository.get_by_repo_name(repo_name)
1819 repo = Repository.get_by_repo_name(repo_name)
1819 commit = repo.get_commit(commit_id=commit_id)
1820 commit = repo.get_commit(commit_id=commit_id)
1820 return commit.date
1821 return commit.date
1821
1822
1822
1823
1823 def get_permission_name(key):
1824 def get_permission_name(key):
1824 return dict(Permission.PERMS).get(key)
1825 return dict(Permission.PERMS).get(key)
1825
1826
1826
1827
1827 def journal_filter_help(request):
1828 def journal_filter_help(request):
1828 _ = request.translate
1829 _ = request.translate
1829 from rhodecode.lib.audit_logger import ACTIONS
1830 from rhodecode.lib.audit_logger import ACTIONS
1830 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1831 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1831
1832
1832 return _(
1833 return _(
1833 'Example filter terms:\n' +
1834 'Example filter terms:\n' +
1834 ' repository:vcs\n' +
1835 ' repository:vcs\n' +
1835 ' username:marcin\n' +
1836 ' username:marcin\n' +
1836 ' username:(NOT marcin)\n' +
1837 ' username:(NOT marcin)\n' +
1837 ' action:*push*\n' +
1838 ' action:*push*\n' +
1838 ' ip:127.0.0.1\n' +
1839 ' ip:127.0.0.1\n' +
1839 ' date:20120101\n' +
1840 ' date:20120101\n' +
1840 ' date:[20120101100000 TO 20120102]\n' +
1841 ' date:[20120101100000 TO 20120102]\n' +
1841 '\n' +
1842 '\n' +
1842 'Actions: {actions}\n' +
1843 'Actions: {actions}\n' +
1843 '\n' +
1844 '\n' +
1844 'Generate wildcards using \'*\' character:\n' +
1845 'Generate wildcards using \'*\' character:\n' +
1845 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1846 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1846 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1847 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1847 '\n' +
1848 '\n' +
1848 'Optional AND / OR operators in queries\n' +
1849 'Optional AND / OR operators in queries\n' +
1849 ' "repository:vcs OR repository:test"\n' +
1850 ' "repository:vcs OR repository:test"\n' +
1850 ' "username:test AND repository:test*"\n'
1851 ' "username:test AND repository:test*"\n'
1851 ).format(actions=actions)
1852 ).format(actions=actions)
1852
1853
1853
1854
1854 def not_mapped_error(repo_name):
1855 def not_mapped_error(repo_name):
1855 from rhodecode.translation import _
1856 from rhodecode.translation import _
1856 flash(_('%s repository is not mapped to db perhaps'
1857 flash(_('%s repository is not mapped to db perhaps'
1857 ' it was created or renamed from the filesystem'
1858 ' it was created or renamed from the filesystem'
1858 ' please run the application again'
1859 ' please run the application again'
1859 ' in order to rescan repositories') % repo_name, category='error')
1860 ' in order to rescan repositories') % repo_name, category='error')
1860
1861
1861
1862
1862 def ip_range(ip_addr):
1863 def ip_range(ip_addr):
1863 from rhodecode.model.db import UserIpMap
1864 from rhodecode.model.db import UserIpMap
1864 s, e = UserIpMap._get_ip_range(ip_addr)
1865 s, e = UserIpMap._get_ip_range(ip_addr)
1865 return '%s - %s' % (s, e)
1866 return '%s - %s' % (s, e)
1866
1867
1867
1868
1868 def form(url, method='post', needs_csrf_token=True, **attrs):
1869 def form(url, method='post', needs_csrf_token=True, **attrs):
1869 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1870 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1870 if method.lower() != 'get' and needs_csrf_token:
1871 if method.lower() != 'get' and needs_csrf_token:
1871 raise Exception(
1872 raise Exception(
1872 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1873 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1873 'CSRF token. If the endpoint does not require such token you can ' +
1874 'CSRF token. If the endpoint does not require such token you can ' +
1874 'explicitly set the parameter needs_csrf_token to false.')
1875 'explicitly set the parameter needs_csrf_token to false.')
1875
1876
1876 return wh_form(url, method=method, **attrs)
1877 return wh_form(url, method=method, **attrs)
1877
1878
1878
1879
1879 def secure_form(form_url, method="POST", multipart=False, **attrs):
1880 def secure_form(form_url, method="POST", multipart=False, **attrs):
1880 """Start a form tag that points the action to an url. This
1881 """Start a form tag that points the action to an url. This
1881 form tag will also include the hidden field containing
1882 form tag will also include the hidden field containing
1882 the auth token.
1883 the auth token.
1883
1884
1884 The url options should be given either as a string, or as a
1885 The url options should be given either as a string, or as a
1885 ``url()`` function. The method for the form defaults to POST.
1886 ``url()`` function. The method for the form defaults to POST.
1886
1887
1887 Options:
1888 Options:
1888
1889
1889 ``multipart``
1890 ``multipart``
1890 If set to True, the enctype is set to "multipart/form-data".
1891 If set to True, the enctype is set to "multipart/form-data".
1891 ``method``
1892 ``method``
1892 The method to use when submitting the form, usually either
1893 The method to use when submitting the form, usually either
1893 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1894 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1894 hidden input with name _method is added to simulate the verb
1895 hidden input with name _method is added to simulate the verb
1895 over POST.
1896 over POST.
1896
1897
1897 """
1898 """
1898 from webhelpers.pylonslib.secure_form import insecure_form
1899 from webhelpers.pylonslib.secure_form import insecure_form
1899
1900
1900 if 'request' in attrs:
1901 if 'request' in attrs:
1901 session = attrs['request'].session
1902 session = attrs['request'].session
1902 del attrs['request']
1903 del attrs['request']
1903 else:
1904 else:
1904 raise ValueError(
1905 raise ValueError(
1905 'Calling this form requires request= to be passed as argument')
1906 'Calling this form requires request= to be passed as argument')
1906
1907
1907 form = insecure_form(form_url, method, multipart, **attrs)
1908 form = insecure_form(form_url, method, multipart, **attrs)
1908 token = literal(
1909 token = literal(
1909 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1910 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1910 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1911 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1911
1912
1912 return literal("%s\n%s" % (form, token))
1913 return literal("%s\n%s" % (form, token))
1913
1914
1914
1915
1915 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1916 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1916 select_html = select(name, selected, options, **attrs)
1917 select_html = select(name, selected, options, **attrs)
1917 select2 = """
1918 select2 = """
1918 <script>
1919 <script>
1919 $(document).ready(function() {
1920 $(document).ready(function() {
1920 $('#%s').select2({
1921 $('#%s').select2({
1921 containerCssClass: 'drop-menu',
1922 containerCssClass: 'drop-menu',
1922 dropdownCssClass: 'drop-menu-dropdown',
1923 dropdownCssClass: 'drop-menu-dropdown',
1923 dropdownAutoWidth: true%s
1924 dropdownAutoWidth: true%s
1924 });
1925 });
1925 });
1926 });
1926 </script>
1927 </script>
1927 """
1928 """
1928 filter_option = """,
1929 filter_option = """,
1929 minimumResultsForSearch: -1
1930 minimumResultsForSearch: -1
1930 """
1931 """
1931 input_id = attrs.get('id') or name
1932 input_id = attrs.get('id') or name
1932 filter_enabled = "" if enable_filter else filter_option
1933 filter_enabled = "" if enable_filter else filter_option
1933 select_script = literal(select2 % (input_id, filter_enabled))
1934 select_script = literal(select2 % (input_id, filter_enabled))
1934
1935
1935 return literal(select_html+select_script)
1936 return literal(select_html+select_script)
1936
1937
1937
1938
1938 def get_visual_attr(tmpl_context_var, attr_name):
1939 def get_visual_attr(tmpl_context_var, attr_name):
1939 """
1940 """
1940 A safe way to get a variable from visual variable of template context
1941 A safe way to get a variable from visual variable of template context
1941
1942
1942 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1943 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1943 :param attr_name: name of the attribute we fetch from the c.visual
1944 :param attr_name: name of the attribute we fetch from the c.visual
1944 """
1945 """
1945 visual = getattr(tmpl_context_var, 'visual', None)
1946 visual = getattr(tmpl_context_var, 'visual', None)
1946 if not visual:
1947 if not visual:
1947 return
1948 return
1948 else:
1949 else:
1949 return getattr(visual, attr_name, None)
1950 return getattr(visual, attr_name, None)
1950
1951
1951
1952
1952 def get_last_path_part(file_node):
1953 def get_last_path_part(file_node):
1953 if not file_node.path:
1954 if not file_node.path:
1954 return u'/'
1955 return u'/'
1955
1956
1956 path = safe_unicode(file_node.path.split('/')[-1])
1957 path = safe_unicode(file_node.path.split('/')[-1])
1957 return u'../' + path
1958 return u'../' + path
1958
1959
1959
1960
1960 def route_url(*args, **kwargs):
1961 def route_url(*args, **kwargs):
1961 """
1962 """
1962 Wrapper around pyramids `route_url` (fully qualified url) function.
1963 Wrapper around pyramids `route_url` (fully qualified url) function.
1963 """
1964 """
1964 req = get_current_request()
1965 req = get_current_request()
1965 return req.route_url(*args, **kwargs)
1966 return req.route_url(*args, **kwargs)
1966
1967
1967
1968
1968 def route_path(*args, **kwargs):
1969 def route_path(*args, **kwargs):
1969 """
1970 """
1970 Wrapper around pyramids `route_path` function.
1971 Wrapper around pyramids `route_path` function.
1971 """
1972 """
1972 req = get_current_request()
1973 req = get_current_request()
1973 return req.route_path(*args, **kwargs)
1974 return req.route_path(*args, **kwargs)
1974
1975
1975
1976
1976 def route_path_or_none(*args, **kwargs):
1977 def route_path_or_none(*args, **kwargs):
1977 try:
1978 try:
1978 return route_path(*args, **kwargs)
1979 return route_path(*args, **kwargs)
1979 except KeyError:
1980 except KeyError:
1980 return None
1981 return None
1981
1982
1982
1983
1983 def current_route_path(request, **kw):
1984 def current_route_path(request, **kw):
1984 new_args = request.GET.mixed()
1985 new_args = request.GET.mixed()
1985 new_args.update(kw)
1986 new_args.update(kw)
1986 return request.current_route_path(_query=new_args)
1987 return request.current_route_path(_query=new_args)
1987
1988
1988
1989
1989 def api_call_example(method, args):
1990 def api_call_example(method, args):
1990 """
1991 """
1991 Generates an API call example via CURL
1992 Generates an API call example via CURL
1992 """
1993 """
1993 args_json = json.dumps(OrderedDict([
1994 args_json = json.dumps(OrderedDict([
1994 ('id', 1),
1995 ('id', 1),
1995 ('auth_token', 'SECRET'),
1996 ('auth_token', 'SECRET'),
1996 ('method', method),
1997 ('method', method),
1997 ('args', args)
1998 ('args', args)
1998 ]))
1999 ]))
1999 return literal(
2000 return literal(
2000 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2001 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2001 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2002 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2002 "and needs to be of `api calls` role."
2003 "and needs to be of `api calls` role."
2003 .format(
2004 .format(
2004 api_url=route_url('apiv2'),
2005 api_url=route_url('apiv2'),
2005 token_url=route_url('my_account_auth_tokens'),
2006 token_url=route_url('my_account_auth_tokens'),
2006 data=args_json))
2007 data=args_json))
2007
2008
2008
2009
2009 def notification_description(notification, request):
2010 def notification_description(notification, request):
2010 """
2011 """
2011 Generate notification human readable description based on notification type
2012 Generate notification human readable description based on notification type
2012 """
2013 """
2013 from rhodecode.model.notification import NotificationModel
2014 from rhodecode.model.notification import NotificationModel
2014 return NotificationModel().make_description(
2015 return NotificationModel().make_description(
2015 notification, translate=request.translate)
2016 notification, translate=request.translate)
2016
2017
2017
2018
2018 def go_import_header(request, db_repo=None):
2019 def go_import_header(request, db_repo=None):
2019 """
2020 """
2020 Creates a header for go-import functionality in Go Lang
2021 Creates a header for go-import functionality in Go Lang
2021 """
2022 """
2022
2023
2023 if not db_repo:
2024 if not db_repo:
2024 return
2025 return
2025 if 'go-get' not in request.GET:
2026 if 'go-get' not in request.GET:
2026 return
2027 return
2027
2028
2028 clone_url = db_repo.clone_url()
2029 clone_url = db_repo.clone_url()
2029 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2030 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2030 # we have a repo and go-get flag,
2031 # we have a repo and go-get flag,
2031 return literal('<meta name="go-import" content="{} {} {}">'.format(
2032 return literal('<meta name="go-import" content="{} {} {}">'.format(
2032 prefix, db_repo.repo_type, clone_url))
2033 prefix, db_repo.repo_type, clone_url))
2033
2034
2034
2035
2035 def reviewer_as_json(*args, **kwargs):
2036 def reviewer_as_json(*args, **kwargs):
2036 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2037 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2037 return _reviewer_as_json(*args, **kwargs)
2038 return _reviewer_as_json(*args, **kwargs)
2038
2039
2039
2040
2040 def get_repo_view_type(request):
2041 def get_repo_view_type(request):
2041 route_name = request.matched_route.name
2042 route_name = request.matched_route.name
2042 route_to_view_type = {
2043 route_to_view_type = {
2043 'repo_changelog': 'changelog',
2044 'repo_changelog': 'changelog',
2044 'repo_files': 'files',
2045 'repo_files': 'files',
2045 'repo_summary': 'summary',
2046 'repo_summary': 'summary',
2046 'repo_commit': 'commit'
2047 'repo_commit': 'commit'
2047 }
2048 }
2048
2049
2049 return route_to_view_type.get(route_name)
2050 return route_to_view_type.get(route_name)
@@ -1,5138 +1,5149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import warnings
32 import warnings
33 import ipaddress
33 import ipaddress
34 import functools
34 import functools
35 import traceback
35 import traceback
36 import collections
36 import collections
37
37
38 from sqlalchemy import (
38 from sqlalchemy import (
39 or_, and_, not_, func, TypeDecorator, event,
39 or_, and_, not_, func, TypeDecorator, event,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Text, Float, PickleType)
42 Text, Float, PickleType)
43 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.orm import (
45 from sqlalchemy.orm import (
46 relationship, joinedload, class_mapper, validates, aliased)
46 relationship, joinedload, class_mapper, validates, aliased)
47 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.dialects.mysql import LONGTEXT
50 from sqlalchemy.dialects.mysql import LONGTEXT
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from pyramid import compat
52 from pyramid import compat
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers.text import collapse, remove_formatting
54 from webhelpers.text import collapse, remove_formatting
55
55
56 from rhodecode.translation import _
56 from rhodecode.translation import _
57 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 JsonRaw
64 JsonRaw
65 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt2 import Encryptor
68 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.model.meta import Base, Session
69 from rhodecode.model.meta import Base, Session
70
70
71 URL_SEP = '/'
71 URL_SEP = '/'
72 log = logging.getLogger(__name__)
72 log = logging.getLogger(__name__)
73
73
74 # =============================================================================
74 # =============================================================================
75 # BASE CLASSES
75 # BASE CLASSES
76 # =============================================================================
76 # =============================================================================
77
77
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # beaker.session.secret if first is not set.
79 # beaker.session.secret if first is not set.
80 # and initialized at environment.py
80 # and initialized at environment.py
81 ENCRYPTION_KEY = None
81 ENCRYPTION_KEY = None
82
82
83 # used to sort permissions by types, '#' used here is not allowed to be in
83 # used to sort permissions by types, '#' used here is not allowed to be in
84 # usernames, and it's very early in sorted string.printable table.
84 # usernames, and it's very early in sorted string.printable table.
85 PERMISSION_TYPE_SORT = {
85 PERMISSION_TYPE_SORT = {
86 'admin': '####',
86 'admin': '####',
87 'write': '###',
87 'write': '###',
88 'read': '##',
88 'read': '##',
89 'none': '#',
89 'none': '#',
90 }
90 }
91
91
92
92
93 def display_user_sort(obj):
93 def display_user_sort(obj):
94 """
94 """
95 Sort function used to sort permissions in .permissions() function of
95 Sort function used to sort permissions in .permissions() function of
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 of all other resources
97 of all other resources
98 """
98 """
99
99
100 if obj.username == User.DEFAULT_USER:
100 if obj.username == User.DEFAULT_USER:
101 return '#####'
101 return '#####'
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 return prefix + obj.username
103 return prefix + obj.username
104
104
105
105
106 def display_user_group_sort(obj):
106 def display_user_group_sort(obj):
107 """
107 """
108 Sort function used to sort permissions in .permissions() function of
108 Sort function used to sort permissions in .permissions() function of
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 of all other resources
110 of all other resources
111 """
111 """
112
112
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 return prefix + obj.users_group_name
114 return prefix + obj.users_group_name
115
115
116
116
117 def _hash_key(k):
117 def _hash_key(k):
118 return sha1_safe(k)
118 return sha1_safe(k)
119
119
120
120
121 def in_filter_generator(qry, items, limit=500):
121 def in_filter_generator(qry, items, limit=500):
122 """
122 """
123 Splits IN() into multiple with OR
123 Splits IN() into multiple with OR
124 e.g.::
124 e.g.::
125 cnt = Repository.query().filter(
125 cnt = Repository.query().filter(
126 or_(
126 or_(
127 *in_filter_generator(Repository.repo_id, range(100000))
127 *in_filter_generator(Repository.repo_id, range(100000))
128 )).count()
128 )).count()
129 """
129 """
130 if not items:
130 if not items:
131 # empty list will cause empty query which might cause security issues
131 # empty list will cause empty query which might cause security issues
132 # this can lead to hidden unpleasant results
132 # this can lead to hidden unpleasant results
133 items = [-1]
133 items = [-1]
134
134
135 parts = []
135 parts = []
136 for chunk in xrange(0, len(items), limit):
136 for chunk in xrange(0, len(items), limit):
137 parts.append(
137 parts.append(
138 qry.in_(items[chunk: chunk + limit])
138 qry.in_(items[chunk: chunk + limit])
139 )
139 )
140
140
141 return parts
141 return parts
142
142
143
143
144 base_table_args = {
144 base_table_args = {
145 'extend_existing': True,
145 'extend_existing': True,
146 'mysql_engine': 'InnoDB',
146 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8',
147 'mysql_charset': 'utf8',
148 'sqlite_autoincrement': True
148 'sqlite_autoincrement': True
149 }
149 }
150
150
151
151
152 class EncryptedTextValue(TypeDecorator):
152 class EncryptedTextValue(TypeDecorator):
153 """
153 """
154 Special column for encrypted long text data, use like::
154 Special column for encrypted long text data, use like::
155
155
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157
157
158 This column is intelligent so if value is in unencrypted form it return
158 This column is intelligent so if value is in unencrypted form it return
159 unencrypted form, but on save it always encrypts
159 unencrypted form, but on save it always encrypts
160 """
160 """
161 impl = Text
161 impl = Text
162
162
163 def process_bind_param(self, value, dialect):
163 def process_bind_param(self, value, dialect):
164 """
164 """
165 Setter for storing value
165 Setter for storing value
166 """
166 """
167 import rhodecode
167 import rhodecode
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 # protect against double encrypting if values is already encrypted
171 # protect against double encrypting if values is already encrypted
172 if value.startswith('enc$aes$') \
172 if value.startswith('enc$aes$') \
173 or value.startswith('enc$aes_hmac$') \
173 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc2$'):
174 or value.startswith('enc2$'):
175 raise ValueError('value needs to be in unencrypted format, '
175 raise ValueError('value needs to be in unencrypted format, '
176 'ie. not starting with enc$ or enc2$')
176 'ie. not starting with enc$ or enc2$')
177
177
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 if algo == 'aes':
179 if algo == 'aes':
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 elif algo == 'fernet':
181 elif algo == 'fernet':
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 else:
183 else:
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185
185
186 def process_result_value(self, value, dialect):
186 def process_result_value(self, value, dialect):
187 """
187 """
188 Getter for retrieving value
188 Getter for retrieving value
189 """
189 """
190
190
191 import rhodecode
191 import rhodecode
192 if not value:
192 if not value:
193 return value
193 return value
194
194
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 if algo == 'aes':
197 if algo == 'aes':
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 elif algo == 'fernet':
199 elif algo == 'fernet':
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 else:
201 else:
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 return decrypted_data
203 return decrypted_data
204
204
205
205
206 class BaseModel(object):
206 class BaseModel(object):
207 """
207 """
208 Base Model for all classes
208 Base Model for all classes
209 """
209 """
210
210
211 @classmethod
211 @classmethod
212 def _get_keys(cls):
212 def _get_keys(cls):
213 """return column names for this model """
213 """return column names for this model """
214 return class_mapper(cls).c.keys()
214 return class_mapper(cls).c.keys()
215
215
216 def get_dict(self):
216 def get_dict(self):
217 """
217 """
218 return dict with keys and values corresponding
218 return dict with keys and values corresponding
219 to this model data """
219 to this model data """
220
220
221 d = {}
221 d = {}
222 for k in self._get_keys():
222 for k in self._get_keys():
223 d[k] = getattr(self, k)
223 d[k] = getattr(self, k)
224
224
225 # also use __json__() if present to get additional fields
225 # also use __json__() if present to get additional fields
226 _json_attr = getattr(self, '__json__', None)
226 _json_attr = getattr(self, '__json__', None)
227 if _json_attr:
227 if _json_attr:
228 # update with attributes from __json__
228 # update with attributes from __json__
229 if callable(_json_attr):
229 if callable(_json_attr):
230 _json_attr = _json_attr()
230 _json_attr = _json_attr()
231 for k, val in _json_attr.iteritems():
231 for k, val in _json_attr.iteritems():
232 d[k] = val
232 d[k] = val
233 return d
233 return d
234
234
235 def get_appstruct(self):
235 def get_appstruct(self):
236 """return list with keys and values tuples corresponding
236 """return list with keys and values tuples corresponding
237 to this model data """
237 to this model data """
238
238
239 lst = []
239 lst = []
240 for k in self._get_keys():
240 for k in self._get_keys():
241 lst.append((k, getattr(self, k),))
241 lst.append((k, getattr(self, k),))
242 return lst
242 return lst
243
243
244 def populate_obj(self, populate_dict):
244 def populate_obj(self, populate_dict):
245 """populate model with data from given populate_dict"""
245 """populate model with data from given populate_dict"""
246
246
247 for k in self._get_keys():
247 for k in self._get_keys():
248 if k in populate_dict:
248 if k in populate_dict:
249 setattr(self, k, populate_dict[k])
249 setattr(self, k, populate_dict[k])
250
250
251 @classmethod
251 @classmethod
252 def query(cls):
252 def query(cls):
253 return Session().query(cls)
253 return Session().query(cls)
254
254
255 @classmethod
255 @classmethod
256 def get(cls, id_):
256 def get(cls, id_):
257 if id_:
257 if id_:
258 return cls.query().get(id_)
258 return cls.query().get(id_)
259
259
260 @classmethod
260 @classmethod
261 def get_or_404(cls, id_):
261 def get_or_404(cls, id_):
262 from pyramid.httpexceptions import HTTPNotFound
262 from pyramid.httpexceptions import HTTPNotFound
263
263
264 try:
264 try:
265 id_ = int(id_)
265 id_ = int(id_)
266 except (TypeError, ValueError):
266 except (TypeError, ValueError):
267 raise HTTPNotFound()
267 raise HTTPNotFound()
268
268
269 res = cls.query().get(id_)
269 res = cls.query().get(id_)
270 if not res:
270 if not res:
271 raise HTTPNotFound()
271 raise HTTPNotFound()
272 return res
272 return res
273
273
274 @classmethod
274 @classmethod
275 def getAll(cls):
275 def getAll(cls):
276 # deprecated and left for backward compatibility
276 # deprecated and left for backward compatibility
277 return cls.get_all()
277 return cls.get_all()
278
278
279 @classmethod
279 @classmethod
280 def get_all(cls):
280 def get_all(cls):
281 return cls.query().all()
281 return cls.query().all()
282
282
283 @classmethod
283 @classmethod
284 def delete(cls, id_):
284 def delete(cls, id_):
285 obj = cls.query().get(id_)
285 obj = cls.query().get(id_)
286 Session().delete(obj)
286 Session().delete(obj)
287
287
288 @classmethod
288 @classmethod
289 def identity_cache(cls, session, attr_name, value):
289 def identity_cache(cls, session, attr_name, value):
290 exist_in_session = []
290 exist_in_session = []
291 for (item_cls, pkey), instance in session.identity_map.items():
291 for (item_cls, pkey), instance in session.identity_map.items():
292 if cls == item_cls and getattr(instance, attr_name) == value:
292 if cls == item_cls and getattr(instance, attr_name) == value:
293 exist_in_session.append(instance)
293 exist_in_session.append(instance)
294 if exist_in_session:
294 if exist_in_session:
295 if len(exist_in_session) == 1:
295 if len(exist_in_session) == 1:
296 return exist_in_session[0]
296 return exist_in_session[0]
297 log.exception(
297 log.exception(
298 'multiple objects with attr %s and '
298 'multiple objects with attr %s and '
299 'value %s found with same name: %r',
299 'value %s found with same name: %r',
300 attr_name, value, exist_in_session)
300 attr_name, value, exist_in_session)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 if hasattr(self, '__unicode__'):
303 if hasattr(self, '__unicode__'):
304 # python repr needs to return str
304 # python repr needs to return str
305 try:
305 try:
306 return safe_str(self.__unicode__())
306 return safe_str(self.__unicode__())
307 except UnicodeDecodeError:
307 except UnicodeDecodeError:
308 pass
308 pass
309 return '<DB:%s>' % (self.__class__.__name__)
309 return '<DB:%s>' % (self.__class__.__name__)
310
310
311
311
312 class RhodeCodeSetting(Base, BaseModel):
312 class RhodeCodeSetting(Base, BaseModel):
313 __tablename__ = 'rhodecode_settings'
313 __tablename__ = 'rhodecode_settings'
314 __table_args__ = (
314 __table_args__ = (
315 UniqueConstraint('app_settings_name'),
315 UniqueConstraint('app_settings_name'),
316 base_table_args
316 base_table_args
317 )
317 )
318
318
319 SETTINGS_TYPES = {
319 SETTINGS_TYPES = {
320 'str': safe_str,
320 'str': safe_str,
321 'int': safe_int,
321 'int': safe_int,
322 'unicode': safe_unicode,
322 'unicode': safe_unicode,
323 'bool': str2bool,
323 'bool': str2bool,
324 'list': functools.partial(aslist, sep=',')
324 'list': functools.partial(aslist, sep=',')
325 }
325 }
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 GLOBAL_CONF_KEY = 'app_settings'
327 GLOBAL_CONF_KEY = 'app_settings'
328
328
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333
333
334 def __init__(self, key='', val='', type='unicode'):
334 def __init__(self, key='', val='', type='unicode'):
335 self.app_settings_name = key
335 self.app_settings_name = key
336 self.app_settings_type = type
336 self.app_settings_type = type
337 self.app_settings_value = val
337 self.app_settings_value = val
338
338
339 @validates('_app_settings_value')
339 @validates('_app_settings_value')
340 def validate_settings_value(self, key, val):
340 def validate_settings_value(self, key, val):
341 assert type(val) == unicode
341 assert type(val) == unicode
342 return val
342 return val
343
343
344 @hybrid_property
344 @hybrid_property
345 def app_settings_value(self):
345 def app_settings_value(self):
346 v = self._app_settings_value
346 v = self._app_settings_value
347 _type = self.app_settings_type
347 _type = self.app_settings_type
348 if _type:
348 if _type:
349 _type = self.app_settings_type.split('.')[0]
349 _type = self.app_settings_type.split('.')[0]
350 # decode the encrypted value
350 # decode the encrypted value
351 if 'encrypted' in self.app_settings_type:
351 if 'encrypted' in self.app_settings_type:
352 cipher = EncryptedTextValue()
352 cipher = EncryptedTextValue()
353 v = safe_unicode(cipher.process_result_value(v, None))
353 v = safe_unicode(cipher.process_result_value(v, None))
354
354
355 converter = self.SETTINGS_TYPES.get(_type) or \
355 converter = self.SETTINGS_TYPES.get(_type) or \
356 self.SETTINGS_TYPES['unicode']
356 self.SETTINGS_TYPES['unicode']
357 return converter(v)
357 return converter(v)
358
358
359 @app_settings_value.setter
359 @app_settings_value.setter
360 def app_settings_value(self, val):
360 def app_settings_value(self, val):
361 """
361 """
362 Setter that will always make sure we use unicode in app_settings_value
362 Setter that will always make sure we use unicode in app_settings_value
363
363
364 :param val:
364 :param val:
365 """
365 """
366 val = safe_unicode(val)
366 val = safe_unicode(val)
367 # encode the encrypted value
367 # encode the encrypted value
368 if 'encrypted' in self.app_settings_type:
368 if 'encrypted' in self.app_settings_type:
369 cipher = EncryptedTextValue()
369 cipher = EncryptedTextValue()
370 val = safe_unicode(cipher.process_bind_param(val, None))
370 val = safe_unicode(cipher.process_bind_param(val, None))
371 self._app_settings_value = val
371 self._app_settings_value = val
372
372
373 @hybrid_property
373 @hybrid_property
374 def app_settings_type(self):
374 def app_settings_type(self):
375 return self._app_settings_type
375 return self._app_settings_type
376
376
377 @app_settings_type.setter
377 @app_settings_type.setter
378 def app_settings_type(self, val):
378 def app_settings_type(self, val):
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 raise Exception('type must be one of %s got %s'
380 raise Exception('type must be one of %s got %s'
381 % (self.SETTINGS_TYPES.keys(), val))
381 % (self.SETTINGS_TYPES.keys(), val))
382 self._app_settings_type = val
382 self._app_settings_type = val
383
383
384 @classmethod
384 @classmethod
385 def get_by_prefix(cls, prefix):
385 def get_by_prefix(cls, prefix):
386 return RhodeCodeSetting.query()\
386 return RhodeCodeSetting.query()\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .all()
388 .all()
389
389
390 def __unicode__(self):
390 def __unicode__(self):
391 return u"<%s('%s:%s[%s]')>" % (
391 return u"<%s('%s:%s[%s]')>" % (
392 self.__class__.__name__,
392 self.__class__.__name__,
393 self.app_settings_name, self.app_settings_value,
393 self.app_settings_name, self.app_settings_value,
394 self.app_settings_type
394 self.app_settings_type
395 )
395 )
396
396
397
397
398 class RhodeCodeUi(Base, BaseModel):
398 class RhodeCodeUi(Base, BaseModel):
399 __tablename__ = 'rhodecode_ui'
399 __tablename__ = 'rhodecode_ui'
400 __table_args__ = (
400 __table_args__ = (
401 UniqueConstraint('ui_key'),
401 UniqueConstraint('ui_key'),
402 base_table_args
402 base_table_args
403 )
403 )
404
404
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 # HG
406 # HG
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PULL = 'outgoing.pull_logger'
408 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PUSH = 'changegroup.push_logger'
411 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
413
413
414 HOOKS_BUILTIN = [
414 HOOKS_BUILTIN = [
415 HOOK_PRE_PULL,
415 HOOK_PRE_PULL,
416 HOOK_PULL,
416 HOOK_PULL,
417 HOOK_PRE_PUSH,
417 HOOK_PRE_PUSH,
418 HOOK_PRETX_PUSH,
418 HOOK_PRETX_PUSH,
419 HOOK_PUSH,
419 HOOK_PUSH,
420 HOOK_PUSH_KEY,
420 HOOK_PUSH_KEY,
421 ]
421 ]
422
422
423 # TODO: johbo: Unify way how hooks are configured for git and hg,
423 # TODO: johbo: Unify way how hooks are configured for git and hg,
424 # git part is currently hardcoded.
424 # git part is currently hardcoded.
425
425
426 # SVN PATTERNS
426 # SVN PATTERNS
427 SVN_BRANCH_ID = 'vcs_svn_branch'
427 SVN_BRANCH_ID = 'vcs_svn_branch'
428 SVN_TAG_ID = 'vcs_svn_tag'
428 SVN_TAG_ID = 'vcs_svn_tag'
429
429
430 ui_id = Column(
430 ui_id = Column(
431 "ui_id", Integer(), nullable=False, unique=True, default=None,
431 "ui_id", Integer(), nullable=False, unique=True, default=None,
432 primary_key=True)
432 primary_key=True)
433 ui_section = Column(
433 ui_section = Column(
434 "ui_section", String(255), nullable=True, unique=None, default=None)
434 "ui_section", String(255), nullable=True, unique=None, default=None)
435 ui_key = Column(
435 ui_key = Column(
436 "ui_key", String(255), nullable=True, unique=None, default=None)
436 "ui_key", String(255), nullable=True, unique=None, default=None)
437 ui_value = Column(
437 ui_value = Column(
438 "ui_value", String(255), nullable=True, unique=None, default=None)
438 "ui_value", String(255), nullable=True, unique=None, default=None)
439 ui_active = Column(
439 ui_active = Column(
440 "ui_active", Boolean(), nullable=True, unique=None, default=True)
440 "ui_active", Boolean(), nullable=True, unique=None, default=True)
441
441
442 def __repr__(self):
442 def __repr__(self):
443 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
443 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
444 self.ui_key, self.ui_value)
444 self.ui_key, self.ui_value)
445
445
446
446
447 class RepoRhodeCodeSetting(Base, BaseModel):
447 class RepoRhodeCodeSetting(Base, BaseModel):
448 __tablename__ = 'repo_rhodecode_settings'
448 __tablename__ = 'repo_rhodecode_settings'
449 __table_args__ = (
449 __table_args__ = (
450 UniqueConstraint(
450 UniqueConstraint(
451 'app_settings_name', 'repository_id',
451 'app_settings_name', 'repository_id',
452 name='uq_repo_rhodecode_setting_name_repo_id'),
452 name='uq_repo_rhodecode_setting_name_repo_id'),
453 base_table_args
453 base_table_args
454 )
454 )
455
455
456 repository_id = Column(
456 repository_id = Column(
457 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
457 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
458 nullable=False)
458 nullable=False)
459 app_settings_id = Column(
459 app_settings_id = Column(
460 "app_settings_id", Integer(), nullable=False, unique=True,
460 "app_settings_id", Integer(), nullable=False, unique=True,
461 default=None, primary_key=True)
461 default=None, primary_key=True)
462 app_settings_name = Column(
462 app_settings_name = Column(
463 "app_settings_name", String(255), nullable=True, unique=None,
463 "app_settings_name", String(255), nullable=True, unique=None,
464 default=None)
464 default=None)
465 _app_settings_value = Column(
465 _app_settings_value = Column(
466 "app_settings_value", String(4096), nullable=True, unique=None,
466 "app_settings_value", String(4096), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_type = Column(
468 _app_settings_type = Column(
469 "app_settings_type", String(255), nullable=True, unique=None,
469 "app_settings_type", String(255), nullable=True, unique=None,
470 default=None)
470 default=None)
471
471
472 repository = relationship('Repository')
472 repository = relationship('Repository')
473
473
474 def __init__(self, repository_id, key='', val='', type='unicode'):
474 def __init__(self, repository_id, key='', val='', type='unicode'):
475 self.repository_id = repository_id
475 self.repository_id = repository_id
476 self.app_settings_name = key
476 self.app_settings_name = key
477 self.app_settings_type = type
477 self.app_settings_type = type
478 self.app_settings_value = val
478 self.app_settings_value = val
479
479
480 @validates('_app_settings_value')
480 @validates('_app_settings_value')
481 def validate_settings_value(self, key, val):
481 def validate_settings_value(self, key, val):
482 assert type(val) == unicode
482 assert type(val) == unicode
483 return val
483 return val
484
484
485 @hybrid_property
485 @hybrid_property
486 def app_settings_value(self):
486 def app_settings_value(self):
487 v = self._app_settings_value
487 v = self._app_settings_value
488 type_ = self.app_settings_type
488 type_ = self.app_settings_type
489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
490 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
491 return converter(v)
491 return converter(v)
492
492
493 @app_settings_value.setter
493 @app_settings_value.setter
494 def app_settings_value(self, val):
494 def app_settings_value(self, val):
495 """
495 """
496 Setter that will always make sure we use unicode in app_settings_value
496 Setter that will always make sure we use unicode in app_settings_value
497
497
498 :param val:
498 :param val:
499 """
499 """
500 self._app_settings_value = safe_unicode(val)
500 self._app_settings_value = safe_unicode(val)
501
501
502 @hybrid_property
502 @hybrid_property
503 def app_settings_type(self):
503 def app_settings_type(self):
504 return self._app_settings_type
504 return self._app_settings_type
505
505
506 @app_settings_type.setter
506 @app_settings_type.setter
507 def app_settings_type(self, val):
507 def app_settings_type(self, val):
508 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
508 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
509 if val not in SETTINGS_TYPES:
509 if val not in SETTINGS_TYPES:
510 raise Exception('type must be one of %s got %s'
510 raise Exception('type must be one of %s got %s'
511 % (SETTINGS_TYPES.keys(), val))
511 % (SETTINGS_TYPES.keys(), val))
512 self._app_settings_type = val
512 self._app_settings_type = val
513
513
514 def __unicode__(self):
514 def __unicode__(self):
515 return u"<%s('%s:%s:%s[%s]')>" % (
515 return u"<%s('%s:%s:%s[%s]')>" % (
516 self.__class__.__name__, self.repository.repo_name,
516 self.__class__.__name__, self.repository.repo_name,
517 self.app_settings_name, self.app_settings_value,
517 self.app_settings_name, self.app_settings_value,
518 self.app_settings_type
518 self.app_settings_type
519 )
519 )
520
520
521
521
522 class RepoRhodeCodeUi(Base, BaseModel):
522 class RepoRhodeCodeUi(Base, BaseModel):
523 __tablename__ = 'repo_rhodecode_ui'
523 __tablename__ = 'repo_rhodecode_ui'
524 __table_args__ = (
524 __table_args__ = (
525 UniqueConstraint(
525 UniqueConstraint(
526 'repository_id', 'ui_section', 'ui_key',
526 'repository_id', 'ui_section', 'ui_key',
527 name='uq_repo_rhodecode_ui_repository_id_section_key'),
527 name='uq_repo_rhodecode_ui_repository_id_section_key'),
528 base_table_args
528 base_table_args
529 )
529 )
530
530
531 repository_id = Column(
531 repository_id = Column(
532 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
532 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
533 nullable=False)
533 nullable=False)
534 ui_id = Column(
534 ui_id = Column(
535 "ui_id", Integer(), nullable=False, unique=True, default=None,
535 "ui_id", Integer(), nullable=False, unique=True, default=None,
536 primary_key=True)
536 primary_key=True)
537 ui_section = Column(
537 ui_section = Column(
538 "ui_section", String(255), nullable=True, unique=None, default=None)
538 "ui_section", String(255), nullable=True, unique=None, default=None)
539 ui_key = Column(
539 ui_key = Column(
540 "ui_key", String(255), nullable=True, unique=None, default=None)
540 "ui_key", String(255), nullable=True, unique=None, default=None)
541 ui_value = Column(
541 ui_value = Column(
542 "ui_value", String(255), nullable=True, unique=None, default=None)
542 "ui_value", String(255), nullable=True, unique=None, default=None)
543 ui_active = Column(
543 ui_active = Column(
544 "ui_active", Boolean(), nullable=True, unique=None, default=True)
544 "ui_active", Boolean(), nullable=True, unique=None, default=True)
545
545
546 repository = relationship('Repository')
546 repository = relationship('Repository')
547
547
548 def __repr__(self):
548 def __repr__(self):
549 return '<%s[%s:%s]%s=>%s]>' % (
549 return '<%s[%s:%s]%s=>%s]>' % (
550 self.__class__.__name__, self.repository.repo_name,
550 self.__class__.__name__, self.repository.repo_name,
551 self.ui_section, self.ui_key, self.ui_value)
551 self.ui_section, self.ui_key, self.ui_value)
552
552
553
553
554 class User(Base, BaseModel):
554 class User(Base, BaseModel):
555 __tablename__ = 'users'
555 __tablename__ = 'users'
556 __table_args__ = (
556 __table_args__ = (
557 UniqueConstraint('username'), UniqueConstraint('email'),
557 UniqueConstraint('username'), UniqueConstraint('email'),
558 Index('u_username_idx', 'username'),
558 Index('u_username_idx', 'username'),
559 Index('u_email_idx', 'email'),
559 Index('u_email_idx', 'email'),
560 base_table_args
560 base_table_args
561 )
561 )
562
562
563 DEFAULT_USER = 'default'
563 DEFAULT_USER = 'default'
564 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
564 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
565 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
565 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
566
566
567 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
568 username = Column("username", String(255), nullable=True, unique=None, default=None)
568 username = Column("username", String(255), nullable=True, unique=None, default=None)
569 password = Column("password", String(255), nullable=True, unique=None, default=None)
569 password = Column("password", String(255), nullable=True, unique=None, default=None)
570 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
571 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
571 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
572 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
572 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
573 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
573 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
574 _email = Column("email", String(255), nullable=True, unique=None, default=None)
574 _email = Column("email", String(255), nullable=True, unique=None, default=None)
575 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
575 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
577
577
578 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
578 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
579 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
579 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
580 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
580 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
581 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
581 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
583 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
583 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
584
584
585 user_log = relationship('UserLog')
585 user_log = relationship('UserLog')
586 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
586 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
587
587
588 repositories = relationship('Repository')
588 repositories = relationship('Repository')
589 repository_groups = relationship('RepoGroup')
589 repository_groups = relationship('RepoGroup')
590 user_groups = relationship('UserGroup')
590 user_groups = relationship('UserGroup')
591
591
592 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
592 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
593 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
593 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
594
594
595 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
595 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
596 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
596 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
597 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
597 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
598
598
599 group_member = relationship('UserGroupMember', cascade='all')
599 group_member = relationship('UserGroupMember', cascade='all')
600
600
601 notifications = relationship('UserNotification', cascade='all')
601 notifications = relationship('UserNotification', cascade='all')
602 # notifications assigned to this user
602 # notifications assigned to this user
603 user_created_notifications = relationship('Notification', cascade='all')
603 user_created_notifications = relationship('Notification', cascade='all')
604 # comments created by this user
604 # comments created by this user
605 user_comments = relationship('ChangesetComment', cascade='all')
605 user_comments = relationship('ChangesetComment', cascade='all')
606 # user profile extra info
606 # user profile extra info
607 user_emails = relationship('UserEmailMap', cascade='all')
607 user_emails = relationship('UserEmailMap', cascade='all')
608 user_ip_map = relationship('UserIpMap', cascade='all')
608 user_ip_map = relationship('UserIpMap', cascade='all')
609 user_auth_tokens = relationship('UserApiKeys', cascade='all')
609 user_auth_tokens = relationship('UserApiKeys', cascade='all')
610 user_ssh_keys = relationship('UserSshKeys', cascade='all')
610 user_ssh_keys = relationship('UserSshKeys', cascade='all')
611
611
612 # gists
612 # gists
613 user_gists = relationship('Gist', cascade='all')
613 user_gists = relationship('Gist', cascade='all')
614 # user pull requests
614 # user pull requests
615 user_pull_requests = relationship('PullRequest', cascade='all')
615 user_pull_requests = relationship('PullRequest', cascade='all')
616 # external identities
616 # external identities
617 extenal_identities = relationship(
617 extenal_identities = relationship(
618 'ExternalIdentity',
618 'ExternalIdentity',
619 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
619 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
620 cascade='all')
620 cascade='all')
621 # review rules
621 # review rules
622 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
622 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
623
623
624 def __unicode__(self):
624 def __unicode__(self):
625 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
625 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
626 self.user_id, self.username)
626 self.user_id, self.username)
627
627
628 @hybrid_property
628 @hybrid_property
629 def email(self):
629 def email(self):
630 return self._email
630 return self._email
631
631
632 @email.setter
632 @email.setter
633 def email(self, val):
633 def email(self, val):
634 self._email = val.lower() if val else None
634 self._email = val.lower() if val else None
635
635
636 @hybrid_property
636 @hybrid_property
637 def first_name(self):
637 def first_name(self):
638 from rhodecode.lib import helpers as h
638 from rhodecode.lib import helpers as h
639 if self.name:
639 if self.name:
640 return h.escape(self.name)
640 return h.escape(self.name)
641 return self.name
641 return self.name
642
642
643 @hybrid_property
643 @hybrid_property
644 def last_name(self):
644 def last_name(self):
645 from rhodecode.lib import helpers as h
645 from rhodecode.lib import helpers as h
646 if self.lastname:
646 if self.lastname:
647 return h.escape(self.lastname)
647 return h.escape(self.lastname)
648 return self.lastname
648 return self.lastname
649
649
650 @hybrid_property
650 @hybrid_property
651 def api_key(self):
651 def api_key(self):
652 """
652 """
653 Fetch if exist an auth-token with role ALL connected to this user
653 Fetch if exist an auth-token with role ALL connected to this user
654 """
654 """
655 user_auth_token = UserApiKeys.query()\
655 user_auth_token = UserApiKeys.query()\
656 .filter(UserApiKeys.user_id == self.user_id)\
656 .filter(UserApiKeys.user_id == self.user_id)\
657 .filter(or_(UserApiKeys.expires == -1,
657 .filter(or_(UserApiKeys.expires == -1,
658 UserApiKeys.expires >= time.time()))\
658 UserApiKeys.expires >= time.time()))\
659 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
659 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
660 if user_auth_token:
660 if user_auth_token:
661 user_auth_token = user_auth_token.api_key
661 user_auth_token = user_auth_token.api_key
662
662
663 return user_auth_token
663 return user_auth_token
664
664
665 @api_key.setter
665 @api_key.setter
666 def api_key(self, val):
666 def api_key(self, val):
667 # don't allow to set API key this is deprecated for now
667 # don't allow to set API key this is deprecated for now
668 self._api_key = None
668 self._api_key = None
669
669
670 @property
670 @property
671 def reviewer_pull_requests(self):
671 def reviewer_pull_requests(self):
672 return PullRequestReviewers.query() \
672 return PullRequestReviewers.query() \
673 .options(joinedload(PullRequestReviewers.pull_request)) \
673 .options(joinedload(PullRequestReviewers.pull_request)) \
674 .filter(PullRequestReviewers.user_id == self.user_id) \
674 .filter(PullRequestReviewers.user_id == self.user_id) \
675 .all()
675 .all()
676
676
677 @property
677 @property
678 def firstname(self):
678 def firstname(self):
679 # alias for future
679 # alias for future
680 return self.name
680 return self.name
681
681
682 @property
682 @property
683 def emails(self):
683 def emails(self):
684 other = UserEmailMap.query()\
684 other = UserEmailMap.query()\
685 .filter(UserEmailMap.user == self) \
685 .filter(UserEmailMap.user == self) \
686 .order_by(UserEmailMap.email_id.asc()) \
686 .order_by(UserEmailMap.email_id.asc()) \
687 .all()
687 .all()
688 return [self.email] + [x.email for x in other]
688 return [self.email] + [x.email for x in other]
689
689
690 @property
690 @property
691 def auth_tokens(self):
691 def auth_tokens(self):
692 auth_tokens = self.get_auth_tokens()
692 auth_tokens = self.get_auth_tokens()
693 return [x.api_key for x in auth_tokens]
693 return [x.api_key for x in auth_tokens]
694
694
695 def get_auth_tokens(self):
695 def get_auth_tokens(self):
696 return UserApiKeys.query()\
696 return UserApiKeys.query()\
697 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.user == self)\
698 .order_by(UserApiKeys.user_api_key_id.asc())\
698 .order_by(UserApiKeys.user_api_key_id.asc())\
699 .all()
699 .all()
700
700
701 @LazyProperty
701 @LazyProperty
702 def feed_token(self):
702 def feed_token(self):
703 return self.get_feed_token()
703 return self.get_feed_token()
704
704
705 def get_feed_token(self, cache=True):
705 def get_feed_token(self, cache=True):
706 feed_tokens = UserApiKeys.query()\
706 feed_tokens = UserApiKeys.query()\
707 .filter(UserApiKeys.user == self)\
707 .filter(UserApiKeys.user == self)\
708 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
708 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
709 if cache:
709 if cache:
710 feed_tokens = feed_tokens.options(
710 feed_tokens = feed_tokens.options(
711 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
711 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
712
712
713 feed_tokens = feed_tokens.all()
713 feed_tokens = feed_tokens.all()
714 if feed_tokens:
714 if feed_tokens:
715 return feed_tokens[0].api_key
715 return feed_tokens[0].api_key
716 return 'NO_FEED_TOKEN_AVAILABLE'
716 return 'NO_FEED_TOKEN_AVAILABLE'
717
717
718 @classmethod
718 @classmethod
719 def get(cls, user_id, cache=False):
719 def get(cls, user_id, cache=False):
720 if not user_id:
720 if not user_id:
721 return
721 return
722
722
723 user = cls.query()
723 user = cls.query()
724 if cache:
724 if cache:
725 user = user.options(
725 user = user.options(
726 FromCache("sql_cache_short", "get_users_%s" % user_id))
726 FromCache("sql_cache_short", "get_users_%s" % user_id))
727 return user.get(user_id)
727 return user.get(user_id)
728
728
729 @classmethod
729 @classmethod
730 def extra_valid_auth_tokens(cls, user, role=None):
730 def extra_valid_auth_tokens(cls, user, role=None):
731 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
731 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
732 .filter(or_(UserApiKeys.expires == -1,
732 .filter(or_(UserApiKeys.expires == -1,
733 UserApiKeys.expires >= time.time()))
733 UserApiKeys.expires >= time.time()))
734 if role:
734 if role:
735 tokens = tokens.filter(or_(UserApiKeys.role == role,
735 tokens = tokens.filter(or_(UserApiKeys.role == role,
736 UserApiKeys.role == UserApiKeys.ROLE_ALL))
736 UserApiKeys.role == UserApiKeys.ROLE_ALL))
737 return tokens.all()
737 return tokens.all()
738
738
739 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
739 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
740 from rhodecode.lib import auth
740 from rhodecode.lib import auth
741
741
742 log.debug('Trying to authenticate user: %s via auth-token, '
742 log.debug('Trying to authenticate user: %s via auth-token, '
743 'and roles: %s', self, roles)
743 'and roles: %s', self, roles)
744
744
745 if not auth_token:
745 if not auth_token:
746 return False
746 return False
747
747
748 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
748 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
749 tokens_q = UserApiKeys.query()\
749 tokens_q = UserApiKeys.query()\
750 .filter(UserApiKeys.user_id == self.user_id)\
750 .filter(UserApiKeys.user_id == self.user_id)\
751 .filter(or_(UserApiKeys.expires == -1,
751 .filter(or_(UserApiKeys.expires == -1,
752 UserApiKeys.expires >= time.time()))
752 UserApiKeys.expires >= time.time()))
753
753
754 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
754 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
755
755
756 crypto_backend = auth.crypto_backend()
756 crypto_backend = auth.crypto_backend()
757 enc_token_map = {}
757 enc_token_map = {}
758 plain_token_map = {}
758 plain_token_map = {}
759 for token in tokens_q:
759 for token in tokens_q:
760 if token.api_key.startswith(crypto_backend.ENC_PREF):
760 if token.api_key.startswith(crypto_backend.ENC_PREF):
761 enc_token_map[token.api_key] = token
761 enc_token_map[token.api_key] = token
762 else:
762 else:
763 plain_token_map[token.api_key] = token
763 plain_token_map[token.api_key] = token
764 log.debug(
764 log.debug(
765 'Found %s plain and %s encrypted user tokens to check for authentication',
765 'Found %s plain and %s encrypted user tokens to check for authentication',
766 len(plain_token_map), len(enc_token_map))
766 len(plain_token_map), len(enc_token_map))
767
767
768 # plain token match comes first
768 # plain token match comes first
769 match = plain_token_map.get(auth_token)
769 match = plain_token_map.get(auth_token)
770
770
771 # check encrypted tokens now
771 # check encrypted tokens now
772 if not match:
772 if not match:
773 for token_hash, token in enc_token_map.items():
773 for token_hash, token in enc_token_map.items():
774 # NOTE(marcink): this is expensive to calculate, but most secure
774 # NOTE(marcink): this is expensive to calculate, but most secure
775 if crypto_backend.hash_check(auth_token, token_hash):
775 if crypto_backend.hash_check(auth_token, token_hash):
776 match = token
776 match = token
777 break
777 break
778
778
779 if match:
779 if match:
780 log.debug('Found matching token %s', match)
780 log.debug('Found matching token %s', match)
781 if match.repo_id:
781 if match.repo_id:
782 log.debug('Found scope, checking for scope match of token %s', match)
782 log.debug('Found scope, checking for scope match of token %s', match)
783 if match.repo_id == scope_repo_id:
783 if match.repo_id == scope_repo_id:
784 return True
784 return True
785 else:
785 else:
786 log.debug(
786 log.debug(
787 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
787 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
788 'and calling scope is:%s, skipping further checks',
788 'and calling scope is:%s, skipping further checks',
789 match.repo, scope_repo_id)
789 match.repo, scope_repo_id)
790 return False
790 return False
791 else:
791 else:
792 return True
792 return True
793
793
794 return False
794 return False
795
795
796 @property
796 @property
797 def ip_addresses(self):
797 def ip_addresses(self):
798 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
798 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
799 return [x.ip_addr for x in ret]
799 return [x.ip_addr for x in ret]
800
800
801 @property
801 @property
802 def username_and_name(self):
802 def username_and_name(self):
803 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
803 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
804
804
805 @property
805 @property
806 def username_or_name_or_email(self):
806 def username_or_name_or_email(self):
807 full_name = self.full_name if self.full_name is not ' ' else None
807 full_name = self.full_name if self.full_name is not ' ' else None
808 return self.username or full_name or self.email
808 return self.username or full_name or self.email
809
809
810 @property
810 @property
811 def full_name(self):
811 def full_name(self):
812 return '%s %s' % (self.first_name, self.last_name)
812 return '%s %s' % (self.first_name, self.last_name)
813
813
814 @property
814 @property
815 def full_name_or_username(self):
815 def full_name_or_username(self):
816 return ('%s %s' % (self.first_name, self.last_name)
816 return ('%s %s' % (self.first_name, self.last_name)
817 if (self.first_name and self.last_name) else self.username)
817 if (self.first_name and self.last_name) else self.username)
818
818
819 @property
819 @property
820 def full_contact(self):
820 def full_contact(self):
821 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
821 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
822
822
823 @property
823 @property
824 def short_contact(self):
824 def short_contact(self):
825 return '%s %s' % (self.first_name, self.last_name)
825 return '%s %s' % (self.first_name, self.last_name)
826
826
827 @property
827 @property
828 def is_admin(self):
828 def is_admin(self):
829 return self.admin
829 return self.admin
830
830
831 def AuthUser(self, **kwargs):
831 def AuthUser(self, **kwargs):
832 """
832 """
833 Returns instance of AuthUser for this user
833 Returns instance of AuthUser for this user
834 """
834 """
835 from rhodecode.lib.auth import AuthUser
835 from rhodecode.lib.auth import AuthUser
836 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
836 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
837
837
838 @hybrid_property
838 @hybrid_property
839 def user_data(self):
839 def user_data(self):
840 if not self._user_data:
840 if not self._user_data:
841 return {}
841 return {}
842
842
843 try:
843 try:
844 return json.loads(self._user_data)
844 return json.loads(self._user_data)
845 except TypeError:
845 except TypeError:
846 return {}
846 return {}
847
847
848 @user_data.setter
848 @user_data.setter
849 def user_data(self, val):
849 def user_data(self, val):
850 if not isinstance(val, dict):
850 if not isinstance(val, dict):
851 raise Exception('user_data must be dict, got %s' % type(val))
851 raise Exception('user_data must be dict, got %s' % type(val))
852 try:
852 try:
853 self._user_data = json.dumps(val)
853 self._user_data = json.dumps(val)
854 except Exception:
854 except Exception:
855 log.error(traceback.format_exc())
855 log.error(traceback.format_exc())
856
856
857 @classmethod
857 @classmethod
858 def get_by_username(cls, username, case_insensitive=False,
858 def get_by_username(cls, username, case_insensitive=False,
859 cache=False, identity_cache=False):
859 cache=False, identity_cache=False):
860 session = Session()
860 session = Session()
861
861
862 if case_insensitive:
862 if case_insensitive:
863 q = cls.query().filter(
863 q = cls.query().filter(
864 func.lower(cls.username) == func.lower(username))
864 func.lower(cls.username) == func.lower(username))
865 else:
865 else:
866 q = cls.query().filter(cls.username == username)
866 q = cls.query().filter(cls.username == username)
867
867
868 if cache:
868 if cache:
869 if identity_cache:
869 if identity_cache:
870 val = cls.identity_cache(session, 'username', username)
870 val = cls.identity_cache(session, 'username', username)
871 if val:
871 if val:
872 return val
872 return val
873 else:
873 else:
874 cache_key = "get_user_by_name_%s" % _hash_key(username)
874 cache_key = "get_user_by_name_%s" % _hash_key(username)
875 q = q.options(
875 q = q.options(
876 FromCache("sql_cache_short", cache_key))
876 FromCache("sql_cache_short", cache_key))
877
877
878 return q.scalar()
878 return q.scalar()
879
879
880 @classmethod
880 @classmethod
881 def get_by_auth_token(cls, auth_token, cache=False):
881 def get_by_auth_token(cls, auth_token, cache=False):
882 q = UserApiKeys.query()\
882 q = UserApiKeys.query()\
883 .filter(UserApiKeys.api_key == auth_token)\
883 .filter(UserApiKeys.api_key == auth_token)\
884 .filter(or_(UserApiKeys.expires == -1,
884 .filter(or_(UserApiKeys.expires == -1,
885 UserApiKeys.expires >= time.time()))
885 UserApiKeys.expires >= time.time()))
886 if cache:
886 if cache:
887 q = q.options(
887 q = q.options(
888 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
888 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
889
889
890 match = q.first()
890 match = q.first()
891 if match:
891 if match:
892 return match.user
892 return match.user
893
893
894 @classmethod
894 @classmethod
895 def get_by_email(cls, email, case_insensitive=False, cache=False):
895 def get_by_email(cls, email, case_insensitive=False, cache=False):
896
896
897 if case_insensitive:
897 if case_insensitive:
898 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
898 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
899
899
900 else:
900 else:
901 q = cls.query().filter(cls.email == email)
901 q = cls.query().filter(cls.email == email)
902
902
903 email_key = _hash_key(email)
903 email_key = _hash_key(email)
904 if cache:
904 if cache:
905 q = q.options(
905 q = q.options(
906 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
906 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
907
907
908 ret = q.scalar()
908 ret = q.scalar()
909 if ret is None:
909 if ret is None:
910 q = UserEmailMap.query()
910 q = UserEmailMap.query()
911 # try fetching in alternate email map
911 # try fetching in alternate email map
912 if case_insensitive:
912 if case_insensitive:
913 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
913 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
914 else:
914 else:
915 q = q.filter(UserEmailMap.email == email)
915 q = q.filter(UserEmailMap.email == email)
916 q = q.options(joinedload(UserEmailMap.user))
916 q = q.options(joinedload(UserEmailMap.user))
917 if cache:
917 if cache:
918 q = q.options(
918 q = q.options(
919 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
919 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
920 ret = getattr(q.scalar(), 'user', None)
920 ret = getattr(q.scalar(), 'user', None)
921
921
922 return ret
922 return ret
923
923
924 @classmethod
924 @classmethod
925 def get_from_cs_author(cls, author):
925 def get_from_cs_author(cls, author):
926 """
926 """
927 Tries to get User objects out of commit author string
927 Tries to get User objects out of commit author string
928
928
929 :param author:
929 :param author:
930 """
930 """
931 from rhodecode.lib.helpers import email, author_name
931 from rhodecode.lib.helpers import email, author_name
932 # Valid email in the attribute passed, see if they're in the system
932 # Valid email in the attribute passed, see if they're in the system
933 _email = email(author)
933 _email = email(author)
934 if _email:
934 if _email:
935 user = cls.get_by_email(_email, case_insensitive=True)
935 user = cls.get_by_email(_email, case_insensitive=True)
936 if user:
936 if user:
937 return user
937 return user
938 # Maybe we can match by username?
938 # Maybe we can match by username?
939 _author = author_name(author)
939 _author = author_name(author)
940 user = cls.get_by_username(_author, case_insensitive=True)
940 user = cls.get_by_username(_author, case_insensitive=True)
941 if user:
941 if user:
942 return user
942 return user
943
943
944 def update_userdata(self, **kwargs):
944 def update_userdata(self, **kwargs):
945 usr = self
945 usr = self
946 old = usr.user_data
946 old = usr.user_data
947 old.update(**kwargs)
947 old.update(**kwargs)
948 usr.user_data = old
948 usr.user_data = old
949 Session().add(usr)
949 Session().add(usr)
950 log.debug('updated userdata with ', kwargs)
950 log.debug('updated userdata with ', kwargs)
951
951
952 def update_lastlogin(self):
952 def update_lastlogin(self):
953 """Update user lastlogin"""
953 """Update user lastlogin"""
954 self.last_login = datetime.datetime.now()
954 self.last_login = datetime.datetime.now()
955 Session().add(self)
955 Session().add(self)
956 log.debug('updated user %s lastlogin', self.username)
956 log.debug('updated user %s lastlogin', self.username)
957
957
958 def update_password(self, new_password):
958 def update_password(self, new_password):
959 from rhodecode.lib.auth import get_crypt_password
959 from rhodecode.lib.auth import get_crypt_password
960
960
961 self.password = get_crypt_password(new_password)
961 self.password = get_crypt_password(new_password)
962 Session().add(self)
962 Session().add(self)
963
963
964 @classmethod
964 @classmethod
965 def get_first_super_admin(cls):
965 def get_first_super_admin(cls):
966 user = User.query()\
966 user = User.query()\
967 .filter(User.admin == true()) \
967 .filter(User.admin == true()) \
968 .order_by(User.user_id.asc()) \
968 .order_by(User.user_id.asc()) \
969 .first()
969 .first()
970
970
971 if user is None:
971 if user is None:
972 raise Exception('FATAL: Missing administrative account!')
972 raise Exception('FATAL: Missing administrative account!')
973 return user
973 return user
974
974
975 @classmethod
975 @classmethod
976 def get_all_super_admins(cls, only_active=False):
976 def get_all_super_admins(cls, only_active=False):
977 """
977 """
978 Returns all admin accounts sorted by username
978 Returns all admin accounts sorted by username
979 """
979 """
980 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
980 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
981 if only_active:
981 if only_active:
982 qry = qry.filter(User.active == true())
982 qry = qry.filter(User.active == true())
983 return qry.all()
983 return qry.all()
984
984
985 @classmethod
985 @classmethod
986 def get_default_user(cls, cache=False, refresh=False):
986 def get_default_user(cls, cache=False, refresh=False):
987 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
987 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
988 if user is None:
988 if user is None:
989 raise Exception('FATAL: Missing default account!')
989 raise Exception('FATAL: Missing default account!')
990 if refresh:
990 if refresh:
991 # The default user might be based on outdated state which
991 # The default user might be based on outdated state which
992 # has been loaded from the cache.
992 # has been loaded from the cache.
993 # A call to refresh() ensures that the
993 # A call to refresh() ensures that the
994 # latest state from the database is used.
994 # latest state from the database is used.
995 Session().refresh(user)
995 Session().refresh(user)
996 return user
996 return user
997
997
998 def _get_default_perms(self, user, suffix=''):
998 def _get_default_perms(self, user, suffix=''):
999 from rhodecode.model.permission import PermissionModel
999 from rhodecode.model.permission import PermissionModel
1000 return PermissionModel().get_default_perms(user.user_perms, suffix)
1000 return PermissionModel().get_default_perms(user.user_perms, suffix)
1001
1001
1002 def get_default_perms(self, suffix=''):
1002 def get_default_perms(self, suffix=''):
1003 return self._get_default_perms(self, suffix)
1003 return self._get_default_perms(self, suffix)
1004
1004
1005 def get_api_data(self, include_secrets=False, details='full'):
1005 def get_api_data(self, include_secrets=False, details='full'):
1006 """
1006 """
1007 Common function for generating user related data for API
1007 Common function for generating user related data for API
1008
1008
1009 :param include_secrets: By default secrets in the API data will be replaced
1009 :param include_secrets: By default secrets in the API data will be replaced
1010 by a placeholder value to prevent exposing this data by accident. In case
1010 by a placeholder value to prevent exposing this data by accident. In case
1011 this data shall be exposed, set this flag to ``True``.
1011 this data shall be exposed, set this flag to ``True``.
1012
1012
1013 :param details: details can be 'basic|full' basic gives only a subset of
1013 :param details: details can be 'basic|full' basic gives only a subset of
1014 the available user information that includes user_id, name and emails.
1014 the available user information that includes user_id, name and emails.
1015 """
1015 """
1016 user = self
1016 user = self
1017 user_data = self.user_data
1017 user_data = self.user_data
1018 data = {
1018 data = {
1019 'user_id': user.user_id,
1019 'user_id': user.user_id,
1020 'username': user.username,
1020 'username': user.username,
1021 'firstname': user.name,
1021 'firstname': user.name,
1022 'lastname': user.lastname,
1022 'lastname': user.lastname,
1023 'email': user.email,
1023 'email': user.email,
1024 'emails': user.emails,
1024 'emails': user.emails,
1025 }
1025 }
1026 if details == 'basic':
1026 if details == 'basic':
1027 return data
1027 return data
1028
1028
1029 auth_token_length = 40
1029 auth_token_length = 40
1030 auth_token_replacement = '*' * auth_token_length
1030 auth_token_replacement = '*' * auth_token_length
1031
1031
1032 extras = {
1032 extras = {
1033 'auth_tokens': [auth_token_replacement],
1033 'auth_tokens': [auth_token_replacement],
1034 'active': user.active,
1034 'active': user.active,
1035 'admin': user.admin,
1035 'admin': user.admin,
1036 'extern_type': user.extern_type,
1036 'extern_type': user.extern_type,
1037 'extern_name': user.extern_name,
1037 'extern_name': user.extern_name,
1038 'last_login': user.last_login,
1038 'last_login': user.last_login,
1039 'last_activity': user.last_activity,
1039 'last_activity': user.last_activity,
1040 'ip_addresses': user.ip_addresses,
1040 'ip_addresses': user.ip_addresses,
1041 'language': user_data.get('language')
1041 'language': user_data.get('language')
1042 }
1042 }
1043 data.update(extras)
1043 data.update(extras)
1044
1044
1045 if include_secrets:
1045 if include_secrets:
1046 data['auth_tokens'] = user.auth_tokens
1046 data['auth_tokens'] = user.auth_tokens
1047 return data
1047 return data
1048
1048
1049 def __json__(self):
1049 def __json__(self):
1050 data = {
1050 data = {
1051 'full_name': self.full_name,
1051 'full_name': self.full_name,
1052 'full_name_or_username': self.full_name_or_username,
1052 'full_name_or_username': self.full_name_or_username,
1053 'short_contact': self.short_contact,
1053 'short_contact': self.short_contact,
1054 'full_contact': self.full_contact,
1054 'full_contact': self.full_contact,
1055 }
1055 }
1056 data.update(self.get_api_data())
1056 data.update(self.get_api_data())
1057 return data
1057 return data
1058
1058
1059
1059
1060 class UserApiKeys(Base, BaseModel):
1060 class UserApiKeys(Base, BaseModel):
1061 __tablename__ = 'user_api_keys'
1061 __tablename__ = 'user_api_keys'
1062 __table_args__ = (
1062 __table_args__ = (
1063 Index('uak_api_key_idx', 'api_key', unique=True),
1063 Index('uak_api_key_idx', 'api_key', unique=True),
1064 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1064 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1065 base_table_args
1065 base_table_args
1066 )
1066 )
1067 __mapper_args__ = {}
1067 __mapper_args__ = {}
1068
1068
1069 # ApiKey role
1069 # ApiKey role
1070 ROLE_ALL = 'token_role_all'
1070 ROLE_ALL = 'token_role_all'
1071 ROLE_HTTP = 'token_role_http'
1071 ROLE_HTTP = 'token_role_http'
1072 ROLE_VCS = 'token_role_vcs'
1072 ROLE_VCS = 'token_role_vcs'
1073 ROLE_API = 'token_role_api'
1073 ROLE_API = 'token_role_api'
1074 ROLE_FEED = 'token_role_feed'
1074 ROLE_FEED = 'token_role_feed'
1075 ROLE_PASSWORD_RESET = 'token_password_reset'
1075 ROLE_PASSWORD_RESET = 'token_password_reset'
1076
1076
1077 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1077 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1078
1078
1079 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 api_key = Column("api_key", String(255), nullable=False, unique=True)
1081 api_key = Column("api_key", String(255), nullable=False, unique=True)
1082 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1082 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1083 expires = Column('expires', Float(53), nullable=False)
1083 expires = Column('expires', Float(53), nullable=False)
1084 role = Column('role', String(255), nullable=True)
1084 role = Column('role', String(255), nullable=True)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086
1086
1087 # scope columns
1087 # scope columns
1088 repo_id = Column(
1088 repo_id = Column(
1089 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1089 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1090 nullable=True, unique=None, default=None)
1090 nullable=True, unique=None, default=None)
1091 repo = relationship('Repository', lazy='joined')
1091 repo = relationship('Repository', lazy='joined')
1092
1092
1093 repo_group_id = Column(
1093 repo_group_id = Column(
1094 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1094 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1095 nullable=True, unique=None, default=None)
1095 nullable=True, unique=None, default=None)
1096 repo_group = relationship('RepoGroup', lazy='joined')
1096 repo_group = relationship('RepoGroup', lazy='joined')
1097
1097
1098 user = relationship('User', lazy='joined')
1098 user = relationship('User', lazy='joined')
1099
1099
1100 def __unicode__(self):
1100 def __unicode__(self):
1101 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1101 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1102
1102
1103 def __json__(self):
1103 def __json__(self):
1104 data = {
1104 data = {
1105 'auth_token': self.api_key,
1105 'auth_token': self.api_key,
1106 'role': self.role,
1106 'role': self.role,
1107 'scope': self.scope_humanized,
1107 'scope': self.scope_humanized,
1108 'expired': self.expired
1108 'expired': self.expired
1109 }
1109 }
1110 return data
1110 return data
1111
1111
1112 def get_api_data(self, include_secrets=False):
1112 def get_api_data(self, include_secrets=False):
1113 data = self.__json__()
1113 data = self.__json__()
1114 if include_secrets:
1114 if include_secrets:
1115 return data
1115 return data
1116 else:
1116 else:
1117 data['auth_token'] = self.token_obfuscated
1117 data['auth_token'] = self.token_obfuscated
1118 return data
1118 return data
1119
1119
1120 @hybrid_property
1120 @hybrid_property
1121 def description_safe(self):
1121 def description_safe(self):
1122 from rhodecode.lib import helpers as h
1122 from rhodecode.lib import helpers as h
1123 return h.escape(self.description)
1123 return h.escape(self.description)
1124
1124
1125 @property
1125 @property
1126 def expired(self):
1126 def expired(self):
1127 if self.expires == -1:
1127 if self.expires == -1:
1128 return False
1128 return False
1129 return time.time() > self.expires
1129 return time.time() > self.expires
1130
1130
1131 @classmethod
1131 @classmethod
1132 def _get_role_name(cls, role):
1132 def _get_role_name(cls, role):
1133 return {
1133 return {
1134 cls.ROLE_ALL: _('all'),
1134 cls.ROLE_ALL: _('all'),
1135 cls.ROLE_HTTP: _('http/web interface'),
1135 cls.ROLE_HTTP: _('http/web interface'),
1136 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1136 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1137 cls.ROLE_API: _('api calls'),
1137 cls.ROLE_API: _('api calls'),
1138 cls.ROLE_FEED: _('feed access'),
1138 cls.ROLE_FEED: _('feed access'),
1139 }.get(role, role)
1139 }.get(role, role)
1140
1140
1141 @property
1141 @property
1142 def role_humanized(self):
1142 def role_humanized(self):
1143 return self._get_role_name(self.role)
1143 return self._get_role_name(self.role)
1144
1144
1145 def _get_scope(self):
1145 def _get_scope(self):
1146 if self.repo:
1146 if self.repo:
1147 return 'Repository: {}'.format(self.repo.repo_name)
1147 return 'Repository: {}'.format(self.repo.repo_name)
1148 if self.repo_group:
1148 if self.repo_group:
1149 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1149 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1150 return 'Global'
1150 return 'Global'
1151
1151
1152 @property
1152 @property
1153 def scope_humanized(self):
1153 def scope_humanized(self):
1154 return self._get_scope()
1154 return self._get_scope()
1155
1155
1156 @property
1156 @property
1157 def token_obfuscated(self):
1157 def token_obfuscated(self):
1158 if self.api_key:
1158 if self.api_key:
1159 return self.api_key[:4] + "****"
1159 return self.api_key[:4] + "****"
1160
1160
1161
1161
1162 class UserEmailMap(Base, BaseModel):
1162 class UserEmailMap(Base, BaseModel):
1163 __tablename__ = 'user_email_map'
1163 __tablename__ = 'user_email_map'
1164 __table_args__ = (
1164 __table_args__ = (
1165 Index('uem_email_idx', 'email'),
1165 Index('uem_email_idx', 'email'),
1166 UniqueConstraint('email'),
1166 UniqueConstraint('email'),
1167 base_table_args
1167 base_table_args
1168 )
1168 )
1169 __mapper_args__ = {}
1169 __mapper_args__ = {}
1170
1170
1171 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1171 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1173 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1174 user = relationship('User', lazy='joined')
1174 user = relationship('User', lazy='joined')
1175
1175
1176 @validates('_email')
1176 @validates('_email')
1177 def validate_email(self, key, email):
1177 def validate_email(self, key, email):
1178 # check if this email is not main one
1178 # check if this email is not main one
1179 main_email = Session().query(User).filter(User.email == email).scalar()
1179 main_email = Session().query(User).filter(User.email == email).scalar()
1180 if main_email is not None:
1180 if main_email is not None:
1181 raise AttributeError('email %s is present is user table' % email)
1181 raise AttributeError('email %s is present is user table' % email)
1182 return email
1182 return email
1183
1183
1184 @hybrid_property
1184 @hybrid_property
1185 def email(self):
1185 def email(self):
1186 return self._email
1186 return self._email
1187
1187
1188 @email.setter
1188 @email.setter
1189 def email(self, val):
1189 def email(self, val):
1190 self._email = val.lower() if val else None
1190 self._email = val.lower() if val else None
1191
1191
1192
1192
1193 class UserIpMap(Base, BaseModel):
1193 class UserIpMap(Base, BaseModel):
1194 __tablename__ = 'user_ip_map'
1194 __tablename__ = 'user_ip_map'
1195 __table_args__ = (
1195 __table_args__ = (
1196 UniqueConstraint('user_id', 'ip_addr'),
1196 UniqueConstraint('user_id', 'ip_addr'),
1197 base_table_args
1197 base_table_args
1198 )
1198 )
1199 __mapper_args__ = {}
1199 __mapper_args__ = {}
1200
1200
1201 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1202 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1203 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1203 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1204 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1204 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1205 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1205 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1206 user = relationship('User', lazy='joined')
1206 user = relationship('User', lazy='joined')
1207
1207
1208 @hybrid_property
1208 @hybrid_property
1209 def description_safe(self):
1209 def description_safe(self):
1210 from rhodecode.lib import helpers as h
1210 from rhodecode.lib import helpers as h
1211 return h.escape(self.description)
1211 return h.escape(self.description)
1212
1212
1213 @classmethod
1213 @classmethod
1214 def _get_ip_range(cls, ip_addr):
1214 def _get_ip_range(cls, ip_addr):
1215 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1215 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1216 return [str(net.network_address), str(net.broadcast_address)]
1216 return [str(net.network_address), str(net.broadcast_address)]
1217
1217
1218 def __json__(self):
1218 def __json__(self):
1219 return {
1219 return {
1220 'ip_addr': self.ip_addr,
1220 'ip_addr': self.ip_addr,
1221 'ip_range': self._get_ip_range(self.ip_addr),
1221 'ip_range': self._get_ip_range(self.ip_addr),
1222 }
1222 }
1223
1223
1224 def __unicode__(self):
1224 def __unicode__(self):
1225 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1225 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1226 self.user_id, self.ip_addr)
1226 self.user_id, self.ip_addr)
1227
1227
1228
1228
1229 class UserSshKeys(Base, BaseModel):
1229 class UserSshKeys(Base, BaseModel):
1230 __tablename__ = 'user_ssh_keys'
1230 __tablename__ = 'user_ssh_keys'
1231 __table_args__ = (
1231 __table_args__ = (
1232 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1232 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1233
1233
1234 UniqueConstraint('ssh_key_fingerprint'),
1234 UniqueConstraint('ssh_key_fingerprint'),
1235
1235
1236 base_table_args
1236 base_table_args
1237 )
1237 )
1238 __mapper_args__ = {}
1238 __mapper_args__ = {}
1239
1239
1240 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1241 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1242 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1242 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1243
1243
1244 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1244 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1245
1245
1246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1247 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1247 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1249
1249
1250 user = relationship('User', lazy='joined')
1250 user = relationship('User', lazy='joined')
1251
1251
1252 def __json__(self):
1252 def __json__(self):
1253 data = {
1253 data = {
1254 'ssh_fingerprint': self.ssh_key_fingerprint,
1254 'ssh_fingerprint': self.ssh_key_fingerprint,
1255 'description': self.description,
1255 'description': self.description,
1256 'created_on': self.created_on
1256 'created_on': self.created_on
1257 }
1257 }
1258 return data
1258 return data
1259
1259
1260 def get_api_data(self):
1260 def get_api_data(self):
1261 data = self.__json__()
1261 data = self.__json__()
1262 return data
1262 return data
1263
1263
1264
1264
1265 class UserLog(Base, BaseModel):
1265 class UserLog(Base, BaseModel):
1266 __tablename__ = 'user_logs'
1266 __tablename__ = 'user_logs'
1267 __table_args__ = (
1267 __table_args__ = (
1268 base_table_args,
1268 base_table_args,
1269 )
1269 )
1270
1270
1271 VERSION_1 = 'v1'
1271 VERSION_1 = 'v1'
1272 VERSION_2 = 'v2'
1272 VERSION_2 = 'v2'
1273 VERSIONS = [VERSION_1, VERSION_2]
1273 VERSIONS = [VERSION_1, VERSION_2]
1274
1274
1275 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1275 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1276 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1277 username = Column("username", String(255), nullable=True, unique=None, default=None)
1277 username = Column("username", String(255), nullable=True, unique=None, default=None)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1279 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1280 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1280 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1281 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1281 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1282 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1282 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1283
1283
1284 version = Column("version", String(255), nullable=True, default=VERSION_1)
1284 version = Column("version", String(255), nullable=True, default=VERSION_1)
1285 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1285 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287
1287
1288 def __unicode__(self):
1288 def __unicode__(self):
1289 return u"<%s('id:%s:%s')>" % (
1289 return u"<%s('id:%s:%s')>" % (
1290 self.__class__.__name__, self.repository_name, self.action)
1290 self.__class__.__name__, self.repository_name, self.action)
1291
1291
1292 def __json__(self):
1292 def __json__(self):
1293 return {
1293 return {
1294 'user_id': self.user_id,
1294 'user_id': self.user_id,
1295 'username': self.username,
1295 'username': self.username,
1296 'repository_id': self.repository_id,
1296 'repository_id': self.repository_id,
1297 'repository_name': self.repository_name,
1297 'repository_name': self.repository_name,
1298 'user_ip': self.user_ip,
1298 'user_ip': self.user_ip,
1299 'action_date': self.action_date,
1299 'action_date': self.action_date,
1300 'action': self.action,
1300 'action': self.action,
1301 }
1301 }
1302
1302
1303 @hybrid_property
1303 @hybrid_property
1304 def entry_id(self):
1304 def entry_id(self):
1305 return self.user_log_id
1305 return self.user_log_id
1306
1306
1307 @property
1307 @property
1308 def action_as_day(self):
1308 def action_as_day(self):
1309 return datetime.date(*self.action_date.timetuple()[:3])
1309 return datetime.date(*self.action_date.timetuple()[:3])
1310
1310
1311 user = relationship('User')
1311 user = relationship('User')
1312 repository = relationship('Repository', cascade='')
1312 repository = relationship('Repository', cascade='')
1313
1313
1314
1314
1315 class UserGroup(Base, BaseModel):
1315 class UserGroup(Base, BaseModel):
1316 __tablename__ = 'users_groups'
1316 __tablename__ = 'users_groups'
1317 __table_args__ = (
1317 __table_args__ = (
1318 base_table_args,
1318 base_table_args,
1319 )
1319 )
1320
1320
1321 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1322 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1322 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1323 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1323 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1324 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1324 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1325 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1325 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1328 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1328 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1329
1329
1330 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1330 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1331 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1331 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1332 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1332 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1333 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1333 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1334 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1334 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1335 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1335 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1336
1336
1337 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1337 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1338 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1338 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1339
1339
1340 @classmethod
1340 @classmethod
1341 def _load_group_data(cls, column):
1341 def _load_group_data(cls, column):
1342 if not column:
1342 if not column:
1343 return {}
1343 return {}
1344
1344
1345 try:
1345 try:
1346 return json.loads(column) or {}
1346 return json.loads(column) or {}
1347 except TypeError:
1347 except TypeError:
1348 return {}
1348 return {}
1349
1349
1350 @hybrid_property
1350 @hybrid_property
1351 def description_safe(self):
1351 def description_safe(self):
1352 from rhodecode.lib import helpers as h
1352 from rhodecode.lib import helpers as h
1353 return h.escape(self.user_group_description)
1353 return h.escape(self.user_group_description)
1354
1354
1355 @hybrid_property
1355 @hybrid_property
1356 def group_data(self):
1356 def group_data(self):
1357 return self._load_group_data(self._group_data)
1357 return self._load_group_data(self._group_data)
1358
1358
1359 @group_data.expression
1359 @group_data.expression
1360 def group_data(self, **kwargs):
1360 def group_data(self, **kwargs):
1361 return self._group_data
1361 return self._group_data
1362
1362
1363 @group_data.setter
1363 @group_data.setter
1364 def group_data(self, val):
1364 def group_data(self, val):
1365 try:
1365 try:
1366 self._group_data = json.dumps(val)
1366 self._group_data = json.dumps(val)
1367 except Exception:
1367 except Exception:
1368 log.error(traceback.format_exc())
1368 log.error(traceback.format_exc())
1369
1369
1370 @classmethod
1370 @classmethod
1371 def _load_sync(cls, group_data):
1371 def _load_sync(cls, group_data):
1372 if group_data:
1372 if group_data:
1373 return group_data.get('extern_type')
1373 return group_data.get('extern_type')
1374
1374
1375 @property
1375 @property
1376 def sync(self):
1376 def sync(self):
1377 return self._load_sync(self.group_data)
1377 return self._load_sync(self.group_data)
1378
1378
1379 def __unicode__(self):
1379 def __unicode__(self):
1380 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1380 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1381 self.users_group_id,
1381 self.users_group_id,
1382 self.users_group_name)
1382 self.users_group_name)
1383
1383
1384 @classmethod
1384 @classmethod
1385 def get_by_group_name(cls, group_name, cache=False,
1385 def get_by_group_name(cls, group_name, cache=False,
1386 case_insensitive=False):
1386 case_insensitive=False):
1387 if case_insensitive:
1387 if case_insensitive:
1388 q = cls.query().filter(func.lower(cls.users_group_name) ==
1388 q = cls.query().filter(func.lower(cls.users_group_name) ==
1389 func.lower(group_name))
1389 func.lower(group_name))
1390
1390
1391 else:
1391 else:
1392 q = cls.query().filter(cls.users_group_name == group_name)
1392 q = cls.query().filter(cls.users_group_name == group_name)
1393 if cache:
1393 if cache:
1394 q = q.options(
1394 q = q.options(
1395 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1395 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1396 return q.scalar()
1396 return q.scalar()
1397
1397
1398 @classmethod
1398 @classmethod
1399 def get(cls, user_group_id, cache=False):
1399 def get(cls, user_group_id, cache=False):
1400 if not user_group_id:
1400 if not user_group_id:
1401 return
1401 return
1402
1402
1403 user_group = cls.query()
1403 user_group = cls.query()
1404 if cache:
1404 if cache:
1405 user_group = user_group.options(
1405 user_group = user_group.options(
1406 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1406 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1407 return user_group.get(user_group_id)
1407 return user_group.get(user_group_id)
1408
1408
1409 def permissions(self, with_admins=True, with_owner=True,
1409 def permissions(self, with_admins=True, with_owner=True,
1410 expand_from_user_groups=False):
1410 expand_from_user_groups=False):
1411 """
1411 """
1412 Permissions for user groups
1412 Permissions for user groups
1413 """
1413 """
1414 _admin_perm = 'usergroup.admin'
1414 _admin_perm = 'usergroup.admin'
1415
1415
1416 owner_row = []
1416 owner_row = []
1417 if with_owner:
1417 if with_owner:
1418 usr = AttributeDict(self.user.get_dict())
1418 usr = AttributeDict(self.user.get_dict())
1419 usr.owner_row = True
1419 usr.owner_row = True
1420 usr.permission = _admin_perm
1420 usr.permission = _admin_perm
1421 owner_row.append(usr)
1421 owner_row.append(usr)
1422
1422
1423 super_admin_ids = []
1423 super_admin_ids = []
1424 super_admin_rows = []
1424 super_admin_rows = []
1425 if with_admins:
1425 if with_admins:
1426 for usr in User.get_all_super_admins():
1426 for usr in User.get_all_super_admins():
1427 super_admin_ids.append(usr.user_id)
1427 super_admin_ids.append(usr.user_id)
1428 # if this admin is also owner, don't double the record
1428 # if this admin is also owner, don't double the record
1429 if usr.user_id == owner_row[0].user_id:
1429 if usr.user_id == owner_row[0].user_id:
1430 owner_row[0].admin_row = True
1430 owner_row[0].admin_row = True
1431 else:
1431 else:
1432 usr = AttributeDict(usr.get_dict())
1432 usr = AttributeDict(usr.get_dict())
1433 usr.admin_row = True
1433 usr.admin_row = True
1434 usr.permission = _admin_perm
1434 usr.permission = _admin_perm
1435 super_admin_rows.append(usr)
1435 super_admin_rows.append(usr)
1436
1436
1437 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1437 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1438 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1438 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1439 joinedload(UserUserGroupToPerm.user),
1439 joinedload(UserUserGroupToPerm.user),
1440 joinedload(UserUserGroupToPerm.permission),)
1440 joinedload(UserUserGroupToPerm.permission),)
1441
1441
1442 # get owners and admins and permissions. We do a trick of re-writing
1442 # get owners and admins and permissions. We do a trick of re-writing
1443 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1443 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1444 # has a global reference and changing one object propagates to all
1444 # has a global reference and changing one object propagates to all
1445 # others. This means if admin is also an owner admin_row that change
1445 # others. This means if admin is also an owner admin_row that change
1446 # would propagate to both objects
1446 # would propagate to both objects
1447 perm_rows = []
1447 perm_rows = []
1448 for _usr in q.all():
1448 for _usr in q.all():
1449 usr = AttributeDict(_usr.user.get_dict())
1449 usr = AttributeDict(_usr.user.get_dict())
1450 # if this user is also owner/admin, mark as duplicate record
1450 # if this user is also owner/admin, mark as duplicate record
1451 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1451 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1452 usr.duplicate_perm = True
1452 usr.duplicate_perm = True
1453 usr.permission = _usr.permission.permission_name
1453 usr.permission = _usr.permission.permission_name
1454 perm_rows.append(usr)
1454 perm_rows.append(usr)
1455
1455
1456 # filter the perm rows by 'default' first and then sort them by
1456 # filter the perm rows by 'default' first and then sort them by
1457 # admin,write,read,none permissions sorted again alphabetically in
1457 # admin,write,read,none permissions sorted again alphabetically in
1458 # each group
1458 # each group
1459 perm_rows = sorted(perm_rows, key=display_user_sort)
1459 perm_rows = sorted(perm_rows, key=display_user_sort)
1460
1460
1461 user_groups_rows = []
1461 user_groups_rows = []
1462 if expand_from_user_groups:
1462 if expand_from_user_groups:
1463 for ug in self.permission_user_groups(with_members=True):
1463 for ug in self.permission_user_groups(with_members=True):
1464 for user_data in ug.members:
1464 for user_data in ug.members:
1465 user_groups_rows.append(user_data)
1465 user_groups_rows.append(user_data)
1466
1466
1467 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1467 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1468
1468
1469 def permission_user_groups(self, with_members=False):
1469 def permission_user_groups(self, with_members=False):
1470 q = UserGroupUserGroupToPerm.query()\
1470 q = UserGroupUserGroupToPerm.query()\
1471 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1471 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1472 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1472 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1473 joinedload(UserGroupUserGroupToPerm.target_user_group),
1473 joinedload(UserGroupUserGroupToPerm.target_user_group),
1474 joinedload(UserGroupUserGroupToPerm.permission),)
1474 joinedload(UserGroupUserGroupToPerm.permission),)
1475
1475
1476 perm_rows = []
1476 perm_rows = []
1477 for _user_group in q.all():
1477 for _user_group in q.all():
1478 entry = AttributeDict(_user_group.user_group.get_dict())
1478 entry = AttributeDict(_user_group.user_group.get_dict())
1479 entry.permission = _user_group.permission.permission_name
1479 entry.permission = _user_group.permission.permission_name
1480 if with_members:
1480 if with_members:
1481 entry.members = [x.user.get_dict()
1481 entry.members = [x.user.get_dict()
1482 for x in _user_group.user_group.members]
1482 for x in _user_group.user_group.members]
1483 perm_rows.append(entry)
1483 perm_rows.append(entry)
1484
1484
1485 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1485 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1486 return perm_rows
1486 return perm_rows
1487
1487
1488 def _get_default_perms(self, user_group, suffix=''):
1488 def _get_default_perms(self, user_group, suffix=''):
1489 from rhodecode.model.permission import PermissionModel
1489 from rhodecode.model.permission import PermissionModel
1490 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1490 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1491
1491
1492 def get_default_perms(self, suffix=''):
1492 def get_default_perms(self, suffix=''):
1493 return self._get_default_perms(self, suffix)
1493 return self._get_default_perms(self, suffix)
1494
1494
1495 def get_api_data(self, with_group_members=True, include_secrets=False):
1495 def get_api_data(self, with_group_members=True, include_secrets=False):
1496 """
1496 """
1497 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1497 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1498 basically forwarded.
1498 basically forwarded.
1499
1499
1500 """
1500 """
1501 user_group = self
1501 user_group = self
1502 data = {
1502 data = {
1503 'users_group_id': user_group.users_group_id,
1503 'users_group_id': user_group.users_group_id,
1504 'group_name': user_group.users_group_name,
1504 'group_name': user_group.users_group_name,
1505 'group_description': user_group.user_group_description,
1505 'group_description': user_group.user_group_description,
1506 'active': user_group.users_group_active,
1506 'active': user_group.users_group_active,
1507 'owner': user_group.user.username,
1507 'owner': user_group.user.username,
1508 'sync': user_group.sync,
1508 'sync': user_group.sync,
1509 'owner_email': user_group.user.email,
1509 'owner_email': user_group.user.email,
1510 }
1510 }
1511
1511
1512 if with_group_members:
1512 if with_group_members:
1513 users = []
1513 users = []
1514 for user in user_group.members:
1514 for user in user_group.members:
1515 user = user.user
1515 user = user.user
1516 users.append(user.get_api_data(include_secrets=include_secrets))
1516 users.append(user.get_api_data(include_secrets=include_secrets))
1517 data['users'] = users
1517 data['users'] = users
1518
1518
1519 return data
1519 return data
1520
1520
1521
1521
1522 class UserGroupMember(Base, BaseModel):
1522 class UserGroupMember(Base, BaseModel):
1523 __tablename__ = 'users_groups_members'
1523 __tablename__ = 'users_groups_members'
1524 __table_args__ = (
1524 __table_args__ = (
1525 base_table_args,
1525 base_table_args,
1526 )
1526 )
1527
1527
1528 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1528 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531
1531
1532 user = relationship('User', lazy='joined')
1532 user = relationship('User', lazy='joined')
1533 users_group = relationship('UserGroup')
1533 users_group = relationship('UserGroup')
1534
1534
1535 def __init__(self, gr_id='', u_id=''):
1535 def __init__(self, gr_id='', u_id=''):
1536 self.users_group_id = gr_id
1536 self.users_group_id = gr_id
1537 self.user_id = u_id
1537 self.user_id = u_id
1538
1538
1539
1539
1540 class RepositoryField(Base, BaseModel):
1540 class RepositoryField(Base, BaseModel):
1541 __tablename__ = 'repositories_fields'
1541 __tablename__ = 'repositories_fields'
1542 __table_args__ = (
1542 __table_args__ = (
1543 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1543 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1544 base_table_args,
1544 base_table_args,
1545 )
1545 )
1546
1546
1547 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1547 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1548
1548
1549 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1549 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1550 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1551 field_key = Column("field_key", String(250))
1551 field_key = Column("field_key", String(250))
1552 field_label = Column("field_label", String(1024), nullable=False)
1552 field_label = Column("field_label", String(1024), nullable=False)
1553 field_value = Column("field_value", String(10000), nullable=False)
1553 field_value = Column("field_value", String(10000), nullable=False)
1554 field_desc = Column("field_desc", String(1024), nullable=False)
1554 field_desc = Column("field_desc", String(1024), nullable=False)
1555 field_type = Column("field_type", String(255), nullable=False, unique=None)
1555 field_type = Column("field_type", String(255), nullable=False, unique=None)
1556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1557
1557
1558 repository = relationship('Repository')
1558 repository = relationship('Repository')
1559
1559
1560 @property
1560 @property
1561 def field_key_prefixed(self):
1561 def field_key_prefixed(self):
1562 return 'ex_%s' % self.field_key
1562 return 'ex_%s' % self.field_key
1563
1563
1564 @classmethod
1564 @classmethod
1565 def un_prefix_key(cls, key):
1565 def un_prefix_key(cls, key):
1566 if key.startswith(cls.PREFIX):
1566 if key.startswith(cls.PREFIX):
1567 return key[len(cls.PREFIX):]
1567 return key[len(cls.PREFIX):]
1568 return key
1568 return key
1569
1569
1570 @classmethod
1570 @classmethod
1571 def get_by_key_name(cls, key, repo):
1571 def get_by_key_name(cls, key, repo):
1572 row = cls.query()\
1572 row = cls.query()\
1573 .filter(cls.repository == repo)\
1573 .filter(cls.repository == repo)\
1574 .filter(cls.field_key == key).scalar()
1574 .filter(cls.field_key == key).scalar()
1575 return row
1575 return row
1576
1576
1577
1577
1578 class Repository(Base, BaseModel):
1578 class Repository(Base, BaseModel):
1579 __tablename__ = 'repositories'
1579 __tablename__ = 'repositories'
1580 __table_args__ = (
1580 __table_args__ = (
1581 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1581 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1582 base_table_args,
1582 base_table_args,
1583 )
1583 )
1584 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1584 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1585 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1585 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1586 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1586 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1587
1587
1588 STATE_CREATED = 'repo_state_created'
1588 STATE_CREATED = 'repo_state_created'
1589 STATE_PENDING = 'repo_state_pending'
1589 STATE_PENDING = 'repo_state_pending'
1590 STATE_ERROR = 'repo_state_error'
1590 STATE_ERROR = 'repo_state_error'
1591
1591
1592 LOCK_AUTOMATIC = 'lock_auto'
1592 LOCK_AUTOMATIC = 'lock_auto'
1593 LOCK_API = 'lock_api'
1593 LOCK_API = 'lock_api'
1594 LOCK_WEB = 'lock_web'
1594 LOCK_WEB = 'lock_web'
1595 LOCK_PULL = 'lock_pull'
1595 LOCK_PULL = 'lock_pull'
1596
1596
1597 NAME_SEP = URL_SEP
1597 NAME_SEP = URL_SEP
1598
1598
1599 repo_id = Column(
1599 repo_id = Column(
1600 "repo_id", Integer(), nullable=False, unique=True, default=None,
1600 "repo_id", Integer(), nullable=False, unique=True, default=None,
1601 primary_key=True)
1601 primary_key=True)
1602 _repo_name = Column(
1602 _repo_name = Column(
1603 "repo_name", Text(), nullable=False, default=None)
1603 "repo_name", Text(), nullable=False, default=None)
1604 _repo_name_hash = Column(
1604 _repo_name_hash = Column(
1605 "repo_name_hash", String(255), nullable=False, unique=True)
1605 "repo_name_hash", String(255), nullable=False, unique=True)
1606 repo_state = Column("repo_state", String(255), nullable=True)
1606 repo_state = Column("repo_state", String(255), nullable=True)
1607
1607
1608 clone_uri = Column(
1608 clone_uri = Column(
1609 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1609 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1610 default=None)
1610 default=None)
1611 push_uri = Column(
1611 push_uri = Column(
1612 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1612 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1613 default=None)
1613 default=None)
1614 repo_type = Column(
1614 repo_type = Column(
1615 "repo_type", String(255), nullable=False, unique=False, default=None)
1615 "repo_type", String(255), nullable=False, unique=False, default=None)
1616 user_id = Column(
1616 user_id = Column(
1617 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1617 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1618 unique=False, default=None)
1618 unique=False, default=None)
1619 private = Column(
1619 private = Column(
1620 "private", Boolean(), nullable=True, unique=None, default=None)
1620 "private", Boolean(), nullable=True, unique=None, default=None)
1621 archived = Column(
1621 archived = Column(
1622 "archived", Boolean(), nullable=True, unique=None, default=None)
1622 "archived", Boolean(), nullable=True, unique=None, default=None)
1623 enable_statistics = Column(
1623 enable_statistics = Column(
1624 "statistics", Boolean(), nullable=True, unique=None, default=True)
1624 "statistics", Boolean(), nullable=True, unique=None, default=True)
1625 enable_downloads = Column(
1625 enable_downloads = Column(
1626 "downloads", Boolean(), nullable=True, unique=None, default=True)
1626 "downloads", Boolean(), nullable=True, unique=None, default=True)
1627 description = Column(
1627 description = Column(
1628 "description", String(10000), nullable=True, unique=None, default=None)
1628 "description", String(10000), nullable=True, unique=None, default=None)
1629 created_on = Column(
1629 created_on = Column(
1630 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1630 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1631 default=datetime.datetime.now)
1631 default=datetime.datetime.now)
1632 updated_on = Column(
1632 updated_on = Column(
1633 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1633 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1634 default=datetime.datetime.now)
1634 default=datetime.datetime.now)
1635 _landing_revision = Column(
1635 _landing_revision = Column(
1636 "landing_revision", String(255), nullable=False, unique=False,
1636 "landing_revision", String(255), nullable=False, unique=False,
1637 default=None)
1637 default=None)
1638 enable_locking = Column(
1638 enable_locking = Column(
1639 "enable_locking", Boolean(), nullable=False, unique=None,
1639 "enable_locking", Boolean(), nullable=False, unique=None,
1640 default=False)
1640 default=False)
1641 _locked = Column(
1641 _locked = Column(
1642 "locked", String(255), nullable=True, unique=False, default=None)
1642 "locked", String(255), nullable=True, unique=False, default=None)
1643 _changeset_cache = Column(
1643 _changeset_cache = Column(
1644 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1644 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1645
1645
1646 fork_id = Column(
1646 fork_id = Column(
1647 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1647 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1648 nullable=True, unique=False, default=None)
1648 nullable=True, unique=False, default=None)
1649 group_id = Column(
1649 group_id = Column(
1650 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1650 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1651 unique=False, default=None)
1651 unique=False, default=None)
1652
1652
1653 user = relationship('User', lazy='joined')
1653 user = relationship('User', lazy='joined')
1654 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1654 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1655 group = relationship('RepoGroup', lazy='joined')
1655 group = relationship('RepoGroup', lazy='joined')
1656 repo_to_perm = relationship(
1656 repo_to_perm = relationship(
1657 'UserRepoToPerm', cascade='all',
1657 'UserRepoToPerm', cascade='all',
1658 order_by='UserRepoToPerm.repo_to_perm_id')
1658 order_by='UserRepoToPerm.repo_to_perm_id')
1659 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1659 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1660 stats = relationship('Statistics', cascade='all', uselist=False)
1660 stats = relationship('Statistics', cascade='all', uselist=False)
1661
1661
1662 followers = relationship(
1662 followers = relationship(
1663 'UserFollowing',
1663 'UserFollowing',
1664 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1664 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1665 cascade='all')
1665 cascade='all')
1666 extra_fields = relationship(
1666 extra_fields = relationship(
1667 'RepositoryField', cascade="all, delete, delete-orphan")
1667 'RepositoryField', cascade="all, delete, delete-orphan")
1668 logs = relationship('UserLog')
1668 logs = relationship('UserLog')
1669 comments = relationship(
1669 comments = relationship(
1670 'ChangesetComment', cascade="all, delete, delete-orphan")
1670 'ChangesetComment', cascade="all, delete, delete-orphan")
1671 pull_requests_source = relationship(
1671 pull_requests_source = relationship(
1672 'PullRequest',
1672 'PullRequest',
1673 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1673 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1674 cascade="all, delete, delete-orphan")
1674 cascade="all, delete, delete-orphan")
1675 pull_requests_target = relationship(
1675 pull_requests_target = relationship(
1676 'PullRequest',
1676 'PullRequest',
1677 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1677 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1678 cascade="all, delete, delete-orphan")
1678 cascade="all, delete, delete-orphan")
1679 ui = relationship('RepoRhodeCodeUi', cascade="all")
1679 ui = relationship('RepoRhodeCodeUi', cascade="all")
1680 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1680 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1681 integrations = relationship('Integration',
1681 integrations = relationship('Integration',
1682 cascade="all, delete, delete-orphan")
1682 cascade="all, delete, delete-orphan")
1683
1683
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1685
1686 def __unicode__(self):
1686 def __unicode__(self):
1687 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1687 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1688 safe_unicode(self.repo_name))
1688 safe_unicode(self.repo_name))
1689
1689
1690 @hybrid_property
1690 @hybrid_property
1691 def description_safe(self):
1691 def description_safe(self):
1692 from rhodecode.lib import helpers as h
1692 from rhodecode.lib import helpers as h
1693 return h.escape(self.description)
1693 return h.escape(self.description)
1694
1694
1695 @hybrid_property
1695 @hybrid_property
1696 def landing_rev(self):
1696 def landing_rev(self):
1697 # always should return [rev_type, rev]
1697 # always should return [rev_type, rev]
1698 if self._landing_revision:
1698 if self._landing_revision:
1699 _rev_info = self._landing_revision.split(':')
1699 _rev_info = self._landing_revision.split(':')
1700 if len(_rev_info) < 2:
1700 if len(_rev_info) < 2:
1701 _rev_info.insert(0, 'rev')
1701 _rev_info.insert(0, 'rev')
1702 return [_rev_info[0], _rev_info[1]]
1702 return [_rev_info[0], _rev_info[1]]
1703 return [None, None]
1703 return [None, None]
1704
1704
1705 @landing_rev.setter
1705 @landing_rev.setter
1706 def landing_rev(self, val):
1706 def landing_rev(self, val):
1707 if ':' not in val:
1707 if ':' not in val:
1708 raise ValueError('value must be delimited with `:` and consist '
1708 raise ValueError('value must be delimited with `:` and consist '
1709 'of <rev_type>:<rev>, got %s instead' % val)
1709 'of <rev_type>:<rev>, got %s instead' % val)
1710 self._landing_revision = val
1710 self._landing_revision = val
1711
1711
1712 @hybrid_property
1712 @hybrid_property
1713 def locked(self):
1713 def locked(self):
1714 if self._locked:
1714 if self._locked:
1715 user_id, timelocked, reason = self._locked.split(':')
1715 user_id, timelocked, reason = self._locked.split(':')
1716 lock_values = int(user_id), timelocked, reason
1716 lock_values = int(user_id), timelocked, reason
1717 else:
1717 else:
1718 lock_values = [None, None, None]
1718 lock_values = [None, None, None]
1719 return lock_values
1719 return lock_values
1720
1720
1721 @locked.setter
1721 @locked.setter
1722 def locked(self, val):
1722 def locked(self, val):
1723 if val and isinstance(val, (list, tuple)):
1723 if val and isinstance(val, (list, tuple)):
1724 self._locked = ':'.join(map(str, val))
1724 self._locked = ':'.join(map(str, val))
1725 else:
1725 else:
1726 self._locked = None
1726 self._locked = None
1727
1727
1728 @hybrid_property
1728 @hybrid_property
1729 def changeset_cache(self):
1729 def changeset_cache(self):
1730 from rhodecode.lib.vcs.backends.base import EmptyCommit
1730 from rhodecode.lib.vcs.backends.base import EmptyCommit
1731 dummy = EmptyCommit().__json__()
1731 dummy = EmptyCommit().__json__()
1732 if not self._changeset_cache:
1732 if not self._changeset_cache:
1733 dummy['source_repo_id'] = self.repo_id
1733 dummy['source_repo_id'] = self.repo_id
1734 return json.loads(json.dumps(dummy))
1734 return json.loads(json.dumps(dummy))
1735
1735
1736 try:
1736 try:
1737 return json.loads(self._changeset_cache)
1737 return json.loads(self._changeset_cache)
1738 except TypeError:
1738 except TypeError:
1739 return dummy
1739 return dummy
1740 except Exception:
1740 except Exception:
1741 log.error(traceback.format_exc())
1741 log.error(traceback.format_exc())
1742 return dummy
1742 return dummy
1743
1743
1744 @changeset_cache.setter
1744 @changeset_cache.setter
1745 def changeset_cache(self, val):
1745 def changeset_cache(self, val):
1746 try:
1746 try:
1747 self._changeset_cache = json.dumps(val)
1747 self._changeset_cache = json.dumps(val)
1748 except Exception:
1748 except Exception:
1749 log.error(traceback.format_exc())
1749 log.error(traceback.format_exc())
1750
1750
1751 @hybrid_property
1751 @hybrid_property
1752 def repo_name(self):
1752 def repo_name(self):
1753 return self._repo_name
1753 return self._repo_name
1754
1754
1755 @repo_name.setter
1755 @repo_name.setter
1756 def repo_name(self, value):
1756 def repo_name(self, value):
1757 self._repo_name = value
1757 self._repo_name = value
1758 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1758 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1759
1759
1760 @classmethod
1760 @classmethod
1761 def normalize_repo_name(cls, repo_name):
1761 def normalize_repo_name(cls, repo_name):
1762 """
1762 """
1763 Normalizes os specific repo_name to the format internally stored inside
1763 Normalizes os specific repo_name to the format internally stored inside
1764 database using URL_SEP
1764 database using URL_SEP
1765
1765
1766 :param cls:
1766 :param cls:
1767 :param repo_name:
1767 :param repo_name:
1768 """
1768 """
1769 return cls.NAME_SEP.join(repo_name.split(os.sep))
1769 return cls.NAME_SEP.join(repo_name.split(os.sep))
1770
1770
1771 @classmethod
1771 @classmethod
1772 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1772 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1773 session = Session()
1773 session = Session()
1774 q = session.query(cls).filter(cls.repo_name == repo_name)
1774 q = session.query(cls).filter(cls.repo_name == repo_name)
1775
1775
1776 if cache:
1776 if cache:
1777 if identity_cache:
1777 if identity_cache:
1778 val = cls.identity_cache(session, 'repo_name', repo_name)
1778 val = cls.identity_cache(session, 'repo_name', repo_name)
1779 if val:
1779 if val:
1780 return val
1780 return val
1781 else:
1781 else:
1782 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1782 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1783 q = q.options(
1783 q = q.options(
1784 FromCache("sql_cache_short", cache_key))
1784 FromCache("sql_cache_short", cache_key))
1785
1785
1786 return q.scalar()
1786 return q.scalar()
1787
1787
1788 @classmethod
1788 @classmethod
1789 def get_by_id_or_repo_name(cls, repoid):
1789 def get_by_id_or_repo_name(cls, repoid):
1790 if isinstance(repoid, (int, long)):
1790 if isinstance(repoid, (int, long)):
1791 try:
1791 try:
1792 repo = cls.get(repoid)
1792 repo = cls.get(repoid)
1793 except ValueError:
1793 except ValueError:
1794 repo = None
1794 repo = None
1795 else:
1795 else:
1796 repo = cls.get_by_repo_name(repoid)
1796 repo = cls.get_by_repo_name(repoid)
1797 return repo
1797 return repo
1798
1798
1799 @classmethod
1799 @classmethod
1800 def get_by_full_path(cls, repo_full_path):
1800 def get_by_full_path(cls, repo_full_path):
1801 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1801 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1802 repo_name = cls.normalize_repo_name(repo_name)
1802 repo_name = cls.normalize_repo_name(repo_name)
1803 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1803 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1804
1804
1805 @classmethod
1805 @classmethod
1806 def get_repo_forks(cls, repo_id):
1806 def get_repo_forks(cls, repo_id):
1807 return cls.query().filter(Repository.fork_id == repo_id)
1807 return cls.query().filter(Repository.fork_id == repo_id)
1808
1808
1809 @classmethod
1809 @classmethod
1810 def base_path(cls):
1810 def base_path(cls):
1811 """
1811 """
1812 Returns base path when all repos are stored
1812 Returns base path when all repos are stored
1813
1813
1814 :param cls:
1814 :param cls:
1815 """
1815 """
1816 q = Session().query(RhodeCodeUi)\
1816 q = Session().query(RhodeCodeUi)\
1817 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1817 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1818 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1818 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1819 return q.one().ui_value
1819 return q.one().ui_value
1820
1820
1821 @classmethod
1821 @classmethod
1822 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1822 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1823 case_insensitive=True, archived=False):
1823 case_insensitive=True, archived=False):
1824 q = Repository.query()
1824 q = Repository.query()
1825
1825
1826 if not archived:
1826 if not archived:
1827 q = q.filter(Repository.archived.isnot(true()))
1827 q = q.filter(Repository.archived.isnot(true()))
1828
1828
1829 if not isinstance(user_id, Optional):
1829 if not isinstance(user_id, Optional):
1830 q = q.filter(Repository.user_id == user_id)
1830 q = q.filter(Repository.user_id == user_id)
1831
1831
1832 if not isinstance(group_id, Optional):
1832 if not isinstance(group_id, Optional):
1833 q = q.filter(Repository.group_id == group_id)
1833 q = q.filter(Repository.group_id == group_id)
1834
1834
1835 if case_insensitive:
1835 if case_insensitive:
1836 q = q.order_by(func.lower(Repository.repo_name))
1836 q = q.order_by(func.lower(Repository.repo_name))
1837 else:
1837 else:
1838 q = q.order_by(Repository.repo_name)
1838 q = q.order_by(Repository.repo_name)
1839
1839
1840 return q.all()
1840 return q.all()
1841
1841
1842 @property
1842 @property
1843 def forks(self):
1843 def forks(self):
1844 """
1844 """
1845 Return forks of this repo
1845 Return forks of this repo
1846 """
1846 """
1847 return Repository.get_repo_forks(self.repo_id)
1847 return Repository.get_repo_forks(self.repo_id)
1848
1848
1849 @property
1849 @property
1850 def parent(self):
1850 def parent(self):
1851 """
1851 """
1852 Returns fork parent
1852 Returns fork parent
1853 """
1853 """
1854 return self.fork
1854 return self.fork
1855
1855
1856 @property
1856 @property
1857 def just_name(self):
1857 def just_name(self):
1858 return self.repo_name.split(self.NAME_SEP)[-1]
1858 return self.repo_name.split(self.NAME_SEP)[-1]
1859
1859
1860 @property
1860 @property
1861 def groups_with_parents(self):
1861 def groups_with_parents(self):
1862 groups = []
1862 groups = []
1863 if self.group is None:
1863 if self.group is None:
1864 return groups
1864 return groups
1865
1865
1866 cur_gr = self.group
1866 cur_gr = self.group
1867 groups.insert(0, cur_gr)
1867 groups.insert(0, cur_gr)
1868 while 1:
1868 while 1:
1869 gr = getattr(cur_gr, 'parent_group', None)
1869 gr = getattr(cur_gr, 'parent_group', None)
1870 cur_gr = cur_gr.parent_group
1870 cur_gr = cur_gr.parent_group
1871 if gr is None:
1871 if gr is None:
1872 break
1872 break
1873 groups.insert(0, gr)
1873 groups.insert(0, gr)
1874
1874
1875 return groups
1875 return groups
1876
1876
1877 @property
1877 @property
1878 def groups_and_repo(self):
1878 def groups_and_repo(self):
1879 return self.groups_with_parents, self
1879 return self.groups_with_parents, self
1880
1880
1881 @LazyProperty
1881 @LazyProperty
1882 def repo_path(self):
1882 def repo_path(self):
1883 """
1883 """
1884 Returns base full path for that repository means where it actually
1884 Returns base full path for that repository means where it actually
1885 exists on a filesystem
1885 exists on a filesystem
1886 """
1886 """
1887 q = Session().query(RhodeCodeUi).filter(
1887 q = Session().query(RhodeCodeUi).filter(
1888 RhodeCodeUi.ui_key == self.NAME_SEP)
1888 RhodeCodeUi.ui_key == self.NAME_SEP)
1889 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1889 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1890 return q.one().ui_value
1890 return q.one().ui_value
1891
1891
1892 @property
1892 @property
1893 def repo_full_path(self):
1893 def repo_full_path(self):
1894 p = [self.repo_path]
1894 p = [self.repo_path]
1895 # we need to split the name by / since this is how we store the
1895 # we need to split the name by / since this is how we store the
1896 # names in the database, but that eventually needs to be converted
1896 # names in the database, but that eventually needs to be converted
1897 # into a valid system path
1897 # into a valid system path
1898 p += self.repo_name.split(self.NAME_SEP)
1898 p += self.repo_name.split(self.NAME_SEP)
1899 return os.path.join(*map(safe_unicode, p))
1899 return os.path.join(*map(safe_unicode, p))
1900
1900
1901 @property
1901 @property
1902 def cache_keys(self):
1902 def cache_keys(self):
1903 """
1903 """
1904 Returns associated cache keys for that repo
1904 Returns associated cache keys for that repo
1905 """
1905 """
1906 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1906 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1907 repo_id=self.repo_id)
1907 repo_id=self.repo_id)
1908 return CacheKey.query()\
1908 return CacheKey.query()\
1909 .filter(CacheKey.cache_args == invalidation_namespace)\
1909 .filter(CacheKey.cache_args == invalidation_namespace)\
1910 .order_by(CacheKey.cache_key)\
1910 .order_by(CacheKey.cache_key)\
1911 .all()
1911 .all()
1912
1912
1913 @property
1913 @property
1914 def cached_diffs_relative_dir(self):
1914 def cached_diffs_relative_dir(self):
1915 """
1915 """
1916 Return a relative to the repository store path of cached diffs
1916 Return a relative to the repository store path of cached diffs
1917 used for safe display for users, who shouldn't know the absolute store
1917 used for safe display for users, who shouldn't know the absolute store
1918 path
1918 path
1919 """
1919 """
1920 return os.path.join(
1920 return os.path.join(
1921 os.path.dirname(self.repo_name),
1921 os.path.dirname(self.repo_name),
1922 self.cached_diffs_dir.split(os.path.sep)[-1])
1922 self.cached_diffs_dir.split(os.path.sep)[-1])
1923
1923
1924 @property
1924 @property
1925 def cached_diffs_dir(self):
1925 def cached_diffs_dir(self):
1926 path = self.repo_full_path
1926 path = self.repo_full_path
1927 return os.path.join(
1927 return os.path.join(
1928 os.path.dirname(path),
1928 os.path.dirname(path),
1929 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1929 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1930
1930
1931 def cached_diffs(self):
1931 def cached_diffs(self):
1932 diff_cache_dir = self.cached_diffs_dir
1932 diff_cache_dir = self.cached_diffs_dir
1933 if os.path.isdir(diff_cache_dir):
1933 if os.path.isdir(diff_cache_dir):
1934 return os.listdir(diff_cache_dir)
1934 return os.listdir(diff_cache_dir)
1935 return []
1935 return []
1936
1936
1937 def shadow_repos(self):
1937 def shadow_repos(self):
1938 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1938 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1939 return [
1939 return [
1940 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1940 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1941 if x.startswith(shadow_repos_pattern)]
1941 if x.startswith(shadow_repos_pattern)]
1942
1942
1943 def get_new_name(self, repo_name):
1943 def get_new_name(self, repo_name):
1944 """
1944 """
1945 returns new full repository name based on assigned group and new new
1945 returns new full repository name based on assigned group and new new
1946
1946
1947 :param group_name:
1947 :param group_name:
1948 """
1948 """
1949 path_prefix = self.group.full_path_splitted if self.group else []
1949 path_prefix = self.group.full_path_splitted if self.group else []
1950 return self.NAME_SEP.join(path_prefix + [repo_name])
1950 return self.NAME_SEP.join(path_prefix + [repo_name])
1951
1951
1952 @property
1952 @property
1953 def _config(self):
1953 def _config(self):
1954 """
1954 """
1955 Returns db based config object.
1955 Returns db based config object.
1956 """
1956 """
1957 from rhodecode.lib.utils import make_db_config
1957 from rhodecode.lib.utils import make_db_config
1958 return make_db_config(clear_session=False, repo=self)
1958 return make_db_config(clear_session=False, repo=self)
1959
1959
1960 def permissions(self, with_admins=True, with_owner=True,
1960 def permissions(self, with_admins=True, with_owner=True,
1961 expand_from_user_groups=False):
1961 expand_from_user_groups=False):
1962 """
1962 """
1963 Permissions for repositories
1963 Permissions for repositories
1964 """
1964 """
1965 _admin_perm = 'repository.admin'
1965 _admin_perm = 'repository.admin'
1966
1966
1967 owner_row = []
1967 owner_row = []
1968 if with_owner:
1968 if with_owner:
1969 usr = AttributeDict(self.user.get_dict())
1969 usr = AttributeDict(self.user.get_dict())
1970 usr.owner_row = True
1970 usr.owner_row = True
1971 usr.permission = _admin_perm
1971 usr.permission = _admin_perm
1972 usr.permission_id = None
1972 usr.permission_id = None
1973 owner_row.append(usr)
1973 owner_row.append(usr)
1974
1974
1975 super_admin_ids = []
1975 super_admin_ids = []
1976 super_admin_rows = []
1976 super_admin_rows = []
1977 if with_admins:
1977 if with_admins:
1978 for usr in User.get_all_super_admins():
1978 for usr in User.get_all_super_admins():
1979 super_admin_ids.append(usr.user_id)
1979 super_admin_ids.append(usr.user_id)
1980 # if this admin is also owner, don't double the record
1980 # if this admin is also owner, don't double the record
1981 if usr.user_id == owner_row[0].user_id:
1981 if usr.user_id == owner_row[0].user_id:
1982 owner_row[0].admin_row = True
1982 owner_row[0].admin_row = True
1983 else:
1983 else:
1984 usr = AttributeDict(usr.get_dict())
1984 usr = AttributeDict(usr.get_dict())
1985 usr.admin_row = True
1985 usr.admin_row = True
1986 usr.permission = _admin_perm
1986 usr.permission = _admin_perm
1987 usr.permission_id = None
1987 usr.permission_id = None
1988 super_admin_rows.append(usr)
1988 super_admin_rows.append(usr)
1989
1989
1990 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1990 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1991 q = q.options(joinedload(UserRepoToPerm.repository),
1991 q = q.options(joinedload(UserRepoToPerm.repository),
1992 joinedload(UserRepoToPerm.user),
1992 joinedload(UserRepoToPerm.user),
1993 joinedload(UserRepoToPerm.permission),)
1993 joinedload(UserRepoToPerm.permission),)
1994
1994
1995 # get owners and admins and permissions. We do a trick of re-writing
1995 # get owners and admins and permissions. We do a trick of re-writing
1996 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1996 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1997 # has a global reference and changing one object propagates to all
1997 # has a global reference and changing one object propagates to all
1998 # others. This means if admin is also an owner admin_row that change
1998 # others. This means if admin is also an owner admin_row that change
1999 # would propagate to both objects
1999 # would propagate to both objects
2000 perm_rows = []
2000 perm_rows = []
2001 for _usr in q.all():
2001 for _usr in q.all():
2002 usr = AttributeDict(_usr.user.get_dict())
2002 usr = AttributeDict(_usr.user.get_dict())
2003 # if this user is also owner/admin, mark as duplicate record
2003 # if this user is also owner/admin, mark as duplicate record
2004 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2004 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2005 usr.duplicate_perm = True
2005 usr.duplicate_perm = True
2006 # also check if this permission is maybe used by branch_permissions
2006 # also check if this permission is maybe used by branch_permissions
2007 if _usr.branch_perm_entry:
2007 if _usr.branch_perm_entry:
2008 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2008 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2009
2009
2010 usr.permission = _usr.permission.permission_name
2010 usr.permission = _usr.permission.permission_name
2011 usr.permission_id = _usr.repo_to_perm_id
2011 usr.permission_id = _usr.repo_to_perm_id
2012 perm_rows.append(usr)
2012 perm_rows.append(usr)
2013
2013
2014 # filter the perm rows by 'default' first and then sort them by
2014 # filter the perm rows by 'default' first and then sort them by
2015 # admin,write,read,none permissions sorted again alphabetically in
2015 # admin,write,read,none permissions sorted again alphabetically in
2016 # each group
2016 # each group
2017 perm_rows = sorted(perm_rows, key=display_user_sort)
2017 perm_rows = sorted(perm_rows, key=display_user_sort)
2018
2018
2019 user_groups_rows = []
2019 user_groups_rows = []
2020 if expand_from_user_groups:
2020 if expand_from_user_groups:
2021 for ug in self.permission_user_groups(with_members=True):
2021 for ug in self.permission_user_groups(with_members=True):
2022 for user_data in ug.members:
2022 for user_data in ug.members:
2023 user_groups_rows.append(user_data)
2023 user_groups_rows.append(user_data)
2024
2024
2025 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2025 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2026
2026
2027 def permission_user_groups(self, with_members=True):
2027 def permission_user_groups(self, with_members=True):
2028 q = UserGroupRepoToPerm.query()\
2028 q = UserGroupRepoToPerm.query()\
2029 .filter(UserGroupRepoToPerm.repository == self)
2029 .filter(UserGroupRepoToPerm.repository == self)
2030 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2030 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2031 joinedload(UserGroupRepoToPerm.users_group),
2031 joinedload(UserGroupRepoToPerm.users_group),
2032 joinedload(UserGroupRepoToPerm.permission),)
2032 joinedload(UserGroupRepoToPerm.permission),)
2033
2033
2034 perm_rows = []
2034 perm_rows = []
2035 for _user_group in q.all():
2035 for _user_group in q.all():
2036 entry = AttributeDict(_user_group.users_group.get_dict())
2036 entry = AttributeDict(_user_group.users_group.get_dict())
2037 entry.permission = _user_group.permission.permission_name
2037 entry.permission = _user_group.permission.permission_name
2038 if with_members:
2038 if with_members:
2039 entry.members = [x.user.get_dict()
2039 entry.members = [x.user.get_dict()
2040 for x in _user_group.users_group.members]
2040 for x in _user_group.users_group.members]
2041 perm_rows.append(entry)
2041 perm_rows.append(entry)
2042
2042
2043 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2043 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2044 return perm_rows
2044 return perm_rows
2045
2045
2046 def get_api_data(self, include_secrets=False):
2046 def get_api_data(self, include_secrets=False):
2047 """
2047 """
2048 Common function for generating repo api data
2048 Common function for generating repo api data
2049
2049
2050 :param include_secrets: See :meth:`User.get_api_data`.
2050 :param include_secrets: See :meth:`User.get_api_data`.
2051
2051
2052 """
2052 """
2053 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2053 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2054 # move this methods on models level.
2054 # move this methods on models level.
2055 from rhodecode.model.settings import SettingsModel
2055 from rhodecode.model.settings import SettingsModel
2056 from rhodecode.model.repo import RepoModel
2056 from rhodecode.model.repo import RepoModel
2057
2057
2058 repo = self
2058 repo = self
2059 _user_id, _time, _reason = self.locked
2059 _user_id, _time, _reason = self.locked
2060
2060
2061 data = {
2061 data = {
2062 'repo_id': repo.repo_id,
2062 'repo_id': repo.repo_id,
2063 'repo_name': repo.repo_name,
2063 'repo_name': repo.repo_name,
2064 'repo_type': repo.repo_type,
2064 'repo_type': repo.repo_type,
2065 'clone_uri': repo.clone_uri or '',
2065 'clone_uri': repo.clone_uri or '',
2066 'push_uri': repo.push_uri or '',
2066 'push_uri': repo.push_uri or '',
2067 'url': RepoModel().get_url(self),
2067 'url': RepoModel().get_url(self),
2068 'private': repo.private,
2068 'private': repo.private,
2069 'created_on': repo.created_on,
2069 'created_on': repo.created_on,
2070 'description': repo.description_safe,
2070 'description': repo.description_safe,
2071 'landing_rev': repo.landing_rev,
2071 'landing_rev': repo.landing_rev,
2072 'owner': repo.user.username,
2072 'owner': repo.user.username,
2073 'fork_of': repo.fork.repo_name if repo.fork else None,
2073 'fork_of': repo.fork.repo_name if repo.fork else None,
2074 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2074 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2075 'enable_statistics': repo.enable_statistics,
2075 'enable_statistics': repo.enable_statistics,
2076 'enable_locking': repo.enable_locking,
2076 'enable_locking': repo.enable_locking,
2077 'enable_downloads': repo.enable_downloads,
2077 'enable_downloads': repo.enable_downloads,
2078 'last_changeset': repo.changeset_cache,
2078 'last_changeset': repo.changeset_cache,
2079 'locked_by': User.get(_user_id).get_api_data(
2079 'locked_by': User.get(_user_id).get_api_data(
2080 include_secrets=include_secrets) if _user_id else None,
2080 include_secrets=include_secrets) if _user_id else None,
2081 'locked_date': time_to_datetime(_time) if _time else None,
2081 'locked_date': time_to_datetime(_time) if _time else None,
2082 'lock_reason': _reason if _reason else None,
2082 'lock_reason': _reason if _reason else None,
2083 }
2083 }
2084
2084
2085 # TODO: mikhail: should be per-repo settings here
2085 # TODO: mikhail: should be per-repo settings here
2086 rc_config = SettingsModel().get_all_settings()
2086 rc_config = SettingsModel().get_all_settings()
2087 repository_fields = str2bool(
2087 repository_fields = str2bool(
2088 rc_config.get('rhodecode_repository_fields'))
2088 rc_config.get('rhodecode_repository_fields'))
2089 if repository_fields:
2089 if repository_fields:
2090 for f in self.extra_fields:
2090 for f in self.extra_fields:
2091 data[f.field_key_prefixed] = f.field_value
2091 data[f.field_key_prefixed] = f.field_value
2092
2092
2093 return data
2093 return data
2094
2094
2095 @classmethod
2095 @classmethod
2096 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2096 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2097 if not lock_time:
2097 if not lock_time:
2098 lock_time = time.time()
2098 lock_time = time.time()
2099 if not lock_reason:
2099 if not lock_reason:
2100 lock_reason = cls.LOCK_AUTOMATIC
2100 lock_reason = cls.LOCK_AUTOMATIC
2101 repo.locked = [user_id, lock_time, lock_reason]
2101 repo.locked = [user_id, lock_time, lock_reason]
2102 Session().add(repo)
2102 Session().add(repo)
2103 Session().commit()
2103 Session().commit()
2104
2104
2105 @classmethod
2105 @classmethod
2106 def unlock(cls, repo):
2106 def unlock(cls, repo):
2107 repo.locked = None
2107 repo.locked = None
2108 Session().add(repo)
2108 Session().add(repo)
2109 Session().commit()
2109 Session().commit()
2110
2110
2111 @classmethod
2111 @classmethod
2112 def getlock(cls, repo):
2112 def getlock(cls, repo):
2113 return repo.locked
2113 return repo.locked
2114
2114
2115 def is_user_lock(self, user_id):
2115 def is_user_lock(self, user_id):
2116 if self.lock[0]:
2116 if self.lock[0]:
2117 lock_user_id = safe_int(self.lock[0])
2117 lock_user_id = safe_int(self.lock[0])
2118 user_id = safe_int(user_id)
2118 user_id = safe_int(user_id)
2119 # both are ints, and they are equal
2119 # both are ints, and they are equal
2120 return all([lock_user_id, user_id]) and lock_user_id == user_id
2120 return all([lock_user_id, user_id]) and lock_user_id == user_id
2121
2121
2122 return False
2122 return False
2123
2123
2124 def get_locking_state(self, action, user_id, only_when_enabled=True):
2124 def get_locking_state(self, action, user_id, only_when_enabled=True):
2125 """
2125 """
2126 Checks locking on this repository, if locking is enabled and lock is
2126 Checks locking on this repository, if locking is enabled and lock is
2127 present returns a tuple of make_lock, locked, locked_by.
2127 present returns a tuple of make_lock, locked, locked_by.
2128 make_lock can have 3 states None (do nothing) True, make lock
2128 make_lock can have 3 states None (do nothing) True, make lock
2129 False release lock, This value is later propagated to hooks, which
2129 False release lock, This value is later propagated to hooks, which
2130 do the locking. Think about this as signals passed to hooks what to do.
2130 do the locking. Think about this as signals passed to hooks what to do.
2131
2131
2132 """
2132 """
2133 # TODO: johbo: This is part of the business logic and should be moved
2133 # TODO: johbo: This is part of the business logic and should be moved
2134 # into the RepositoryModel.
2134 # into the RepositoryModel.
2135
2135
2136 if action not in ('push', 'pull'):
2136 if action not in ('push', 'pull'):
2137 raise ValueError("Invalid action value: %s" % repr(action))
2137 raise ValueError("Invalid action value: %s" % repr(action))
2138
2138
2139 # defines if locked error should be thrown to user
2139 # defines if locked error should be thrown to user
2140 currently_locked = False
2140 currently_locked = False
2141 # defines if new lock should be made, tri-state
2141 # defines if new lock should be made, tri-state
2142 make_lock = None
2142 make_lock = None
2143 repo = self
2143 repo = self
2144 user = User.get(user_id)
2144 user = User.get(user_id)
2145
2145
2146 lock_info = repo.locked
2146 lock_info = repo.locked
2147
2147
2148 if repo and (repo.enable_locking or not only_when_enabled):
2148 if repo and (repo.enable_locking or not only_when_enabled):
2149 if action == 'push':
2149 if action == 'push':
2150 # check if it's already locked !, if it is compare users
2150 # check if it's already locked !, if it is compare users
2151 locked_by_user_id = lock_info[0]
2151 locked_by_user_id = lock_info[0]
2152 if user.user_id == locked_by_user_id:
2152 if user.user_id == locked_by_user_id:
2153 log.debug(
2153 log.debug(
2154 'Got `push` action from user %s, now unlocking', user)
2154 'Got `push` action from user %s, now unlocking', user)
2155 # unlock if we have push from user who locked
2155 # unlock if we have push from user who locked
2156 make_lock = False
2156 make_lock = False
2157 else:
2157 else:
2158 # we're not the same user who locked, ban with
2158 # we're not the same user who locked, ban with
2159 # code defined in settings (default is 423 HTTP Locked) !
2159 # code defined in settings (default is 423 HTTP Locked) !
2160 log.debug('Repo %s is currently locked by %s', repo, user)
2160 log.debug('Repo %s is currently locked by %s', repo, user)
2161 currently_locked = True
2161 currently_locked = True
2162 elif action == 'pull':
2162 elif action == 'pull':
2163 # [0] user [1] date
2163 # [0] user [1] date
2164 if lock_info[0] and lock_info[1]:
2164 if lock_info[0] and lock_info[1]:
2165 log.debug('Repo %s is currently locked by %s', repo, user)
2165 log.debug('Repo %s is currently locked by %s', repo, user)
2166 currently_locked = True
2166 currently_locked = True
2167 else:
2167 else:
2168 log.debug('Setting lock on repo %s by %s', repo, user)
2168 log.debug('Setting lock on repo %s by %s', repo, user)
2169 make_lock = True
2169 make_lock = True
2170
2170
2171 else:
2171 else:
2172 log.debug('Repository %s do not have locking enabled', repo)
2172 log.debug('Repository %s do not have locking enabled', repo)
2173
2173
2174 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2174 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2175 make_lock, currently_locked, lock_info)
2175 make_lock, currently_locked, lock_info)
2176
2176
2177 from rhodecode.lib.auth import HasRepoPermissionAny
2177 from rhodecode.lib.auth import HasRepoPermissionAny
2178 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2178 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2179 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2179 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2180 # if we don't have at least write permission we cannot make a lock
2180 # if we don't have at least write permission we cannot make a lock
2181 log.debug('lock state reset back to FALSE due to lack '
2181 log.debug('lock state reset back to FALSE due to lack '
2182 'of at least read permission')
2182 'of at least read permission')
2183 make_lock = False
2183 make_lock = False
2184
2184
2185 return make_lock, currently_locked, lock_info
2185 return make_lock, currently_locked, lock_info
2186
2186
2187 @property
2187 @property
2188 def last_commit_cache_update_diff(self):
2189 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2190
2191 @property
2188 def last_commit_change(self):
2192 def last_commit_change(self):
2189 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2193 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2190 empty_date = datetime.datetime.fromtimestamp(0)
2194 empty_date = datetime.datetime.fromtimestamp(0)
2191 date_latest = self.changeset_cache.get('date', empty_date)
2195 date_latest = self.changeset_cache.get('date', empty_date)
2192 try:
2196 try:
2193 return parse_datetime(date_latest)
2197 return parse_datetime(date_latest)
2194 except Exception:
2198 except Exception:
2195 return empty_date
2199 return empty_date
2196
2200
2197 @property
2201 @property
2198 def last_db_change(self):
2202 def last_db_change(self):
2199 return self.updated_on
2203 return self.updated_on
2200
2204
2201 @property
2205 @property
2202 def clone_uri_hidden(self):
2206 def clone_uri_hidden(self):
2203 clone_uri = self.clone_uri
2207 clone_uri = self.clone_uri
2204 if clone_uri:
2208 if clone_uri:
2205 import urlobject
2209 import urlobject
2206 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2210 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2207 if url_obj.password:
2211 if url_obj.password:
2208 clone_uri = url_obj.with_password('*****')
2212 clone_uri = url_obj.with_password('*****')
2209 return clone_uri
2213 return clone_uri
2210
2214
2211 @property
2215 @property
2212 def push_uri_hidden(self):
2216 def push_uri_hidden(self):
2213 push_uri = self.push_uri
2217 push_uri = self.push_uri
2214 if push_uri:
2218 if push_uri:
2215 import urlobject
2219 import urlobject
2216 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2220 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2217 if url_obj.password:
2221 if url_obj.password:
2218 push_uri = url_obj.with_password('*****')
2222 push_uri = url_obj.with_password('*****')
2219 return push_uri
2223 return push_uri
2220
2224
2221 def clone_url(self, **override):
2225 def clone_url(self, **override):
2222 from rhodecode.model.settings import SettingsModel
2226 from rhodecode.model.settings import SettingsModel
2223
2227
2224 uri_tmpl = None
2228 uri_tmpl = None
2225 if 'with_id' in override:
2229 if 'with_id' in override:
2226 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2230 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2227 del override['with_id']
2231 del override['with_id']
2228
2232
2229 if 'uri_tmpl' in override:
2233 if 'uri_tmpl' in override:
2230 uri_tmpl = override['uri_tmpl']
2234 uri_tmpl = override['uri_tmpl']
2231 del override['uri_tmpl']
2235 del override['uri_tmpl']
2232
2236
2233 ssh = False
2237 ssh = False
2234 if 'ssh' in override:
2238 if 'ssh' in override:
2235 ssh = True
2239 ssh = True
2236 del override['ssh']
2240 del override['ssh']
2237
2241
2238 # we didn't override our tmpl from **overrides
2242 # we didn't override our tmpl from **overrides
2239 if not uri_tmpl:
2243 if not uri_tmpl:
2240 rc_config = SettingsModel().get_all_settings(cache=True)
2244 rc_config = SettingsModel().get_all_settings(cache=True)
2241 if ssh:
2245 if ssh:
2242 uri_tmpl = rc_config.get(
2246 uri_tmpl = rc_config.get(
2243 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2247 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2244 else:
2248 else:
2245 uri_tmpl = rc_config.get(
2249 uri_tmpl = rc_config.get(
2246 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2250 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2247
2251
2248 request = get_current_request()
2252 request = get_current_request()
2249 return get_clone_url(request=request,
2253 return get_clone_url(request=request,
2250 uri_tmpl=uri_tmpl,
2254 uri_tmpl=uri_tmpl,
2251 repo_name=self.repo_name,
2255 repo_name=self.repo_name,
2252 repo_id=self.repo_id, **override)
2256 repo_id=self.repo_id, **override)
2253
2257
2254 def set_state(self, state):
2258 def set_state(self, state):
2255 self.repo_state = state
2259 self.repo_state = state
2256 Session().add(self)
2260 Session().add(self)
2257 #==========================================================================
2261 #==========================================================================
2258 # SCM PROPERTIES
2262 # SCM PROPERTIES
2259 #==========================================================================
2263 #==========================================================================
2260
2264
2261 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2265 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2262 return get_commit_safe(
2266 return get_commit_safe(
2263 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2267 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2264
2268
2265 def get_changeset(self, rev=None, pre_load=None):
2269 def get_changeset(self, rev=None, pre_load=None):
2266 warnings.warn("Use get_commit", DeprecationWarning)
2270 warnings.warn("Use get_commit", DeprecationWarning)
2267 commit_id = None
2271 commit_id = None
2268 commit_idx = None
2272 commit_idx = None
2269 if isinstance(rev, compat.string_types):
2273 if isinstance(rev, compat.string_types):
2270 commit_id = rev
2274 commit_id = rev
2271 else:
2275 else:
2272 commit_idx = rev
2276 commit_idx = rev
2273 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2277 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2274 pre_load=pre_load)
2278 pre_load=pre_load)
2275
2279
2276 def get_landing_commit(self):
2280 def get_landing_commit(self):
2277 """
2281 """
2278 Returns landing commit, or if that doesn't exist returns the tip
2282 Returns landing commit, or if that doesn't exist returns the tip
2279 """
2283 """
2280 _rev_type, _rev = self.landing_rev
2284 _rev_type, _rev = self.landing_rev
2281 commit = self.get_commit(_rev)
2285 commit = self.get_commit(_rev)
2282 if isinstance(commit, EmptyCommit):
2286 if isinstance(commit, EmptyCommit):
2283 return self.get_commit()
2287 return self.get_commit()
2284 return commit
2288 return commit
2285
2289
2286 def update_commit_cache(self, cs_cache=None, config=None):
2290 def update_commit_cache(self, cs_cache=None, config=None):
2287 """
2291 """
2288 Update cache of last changeset for repository, keys should be::
2292 Update cache of last changeset for repository, keys should be::
2289
2293
2290 source_repo_id
2294 source_repo_id
2291 short_id
2295 short_id
2292 raw_id
2296 raw_id
2293 revision
2297 revision
2294 parents
2298 parents
2295 message
2299 message
2296 date
2300 date
2297 author
2301 author
2302 updated_on
2298
2303
2299 """
2304 """
2300 from rhodecode.lib.vcs.backends.base import BaseChangeset
2305 from rhodecode.lib.vcs.backends.base import BaseChangeset
2301 if cs_cache is None:
2306 if cs_cache is None:
2302 # use no-cache version here
2307 # use no-cache version here
2303 scm_repo = self.scm_instance(cache=False, config=config)
2308 scm_repo = self.scm_instance(cache=False, config=config)
2304
2309
2305 empty = not scm_repo or scm_repo.is_empty()
2310 empty = not scm_repo or scm_repo.is_empty()
2306 if not empty:
2311 if not empty:
2307 cs_cache = scm_repo.get_commit(
2312 cs_cache = scm_repo.get_commit(
2308 pre_load=["author", "date", "message", "parents"])
2313 pre_load=["author", "date", "message", "parents"])
2309 else:
2314 else:
2310 cs_cache = EmptyCommit()
2315 cs_cache = EmptyCommit()
2311
2316
2312 if isinstance(cs_cache, BaseChangeset):
2317 if isinstance(cs_cache, BaseChangeset):
2313 cs_cache = cs_cache.__json__()
2318 cs_cache = cs_cache.__json__()
2314
2319
2315 def is_outdated(new_cs_cache):
2320 def is_outdated(new_cs_cache):
2316 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2321 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2317 new_cs_cache['revision'] != self.changeset_cache['revision']):
2322 new_cs_cache['revision'] != self.changeset_cache['revision']):
2318 return True
2323 return True
2319 return False
2324 return False
2320
2325
2321 # check if we have maybe already latest cached revision
2326 # check if we have maybe already latest cached revision
2322 if is_outdated(cs_cache) or not self.changeset_cache:
2327 if is_outdated(cs_cache) or not self.changeset_cache:
2323 _default = datetime.datetime.utcnow()
2328 _default = datetime.datetime.utcnow()
2324 last_change = cs_cache.get('date') or _default
2329 last_change = cs_cache.get('date') or _default
2325 # we check if last update is newer than the new value
2330 # we check if last update is newer than the new value
2326 # if yes, we use the current timestamp instead. Imagine you get
2331 # if yes, we use the current timestamp instead. Imagine you get
2327 # old commit pushed 1y ago, we'd set last update 1y to ago.
2332 # old commit pushed 1y ago, we'd set last update 1y to ago.
2328 last_change_timestamp = datetime_to_time(last_change)
2333 last_change_timestamp = datetime_to_time(last_change)
2329 current_timestamp = datetime_to_time(last_change)
2334 current_timestamp = datetime_to_time(last_change)
2330 if last_change_timestamp > current_timestamp:
2335 if last_change_timestamp > current_timestamp:
2331 cs_cache['date'] = _default
2336 cs_cache['date'] = _default
2332
2337
2338 cs_cache['updated_on'] = time.time()
2333 self.changeset_cache = cs_cache
2339 self.changeset_cache = cs_cache
2334 Session().add(self)
2340 Session().add(self)
2335 Session().commit()
2341 Session().commit()
2336
2342
2337 log.debug('updated repo %s with new commit cache %s',
2343 log.debug('updated repo %s with new commit cache %s',
2338 self.repo_name, cs_cache)
2344 self.repo_name, cs_cache)
2339 else:
2345 else:
2340 log.debug('Skipping update_commit_cache for repo:`%s` '
2346 log.debug('Skipping update_commit_cache for repo:`%s` '
2341 'commit already with latest changes', self.repo_name)
2347 'commit already with latest changes', self.repo_name)
2342
2348
2343 @property
2349 @property
2344 def tip(self):
2350 def tip(self):
2345 return self.get_commit('tip')
2351 return self.get_commit('tip')
2346
2352
2347 @property
2353 @property
2348 def author(self):
2354 def author(self):
2349 return self.tip.author
2355 return self.tip.author
2350
2356
2351 @property
2357 @property
2352 def last_change(self):
2358 def last_change(self):
2353 return self.scm_instance().last_change
2359 return self.scm_instance().last_change
2354
2360
2355 def get_comments(self, revisions=None):
2361 def get_comments(self, revisions=None):
2356 """
2362 """
2357 Returns comments for this repository grouped by revisions
2363 Returns comments for this repository grouped by revisions
2358
2364
2359 :param revisions: filter query by revisions only
2365 :param revisions: filter query by revisions only
2360 """
2366 """
2361 cmts = ChangesetComment.query()\
2367 cmts = ChangesetComment.query()\
2362 .filter(ChangesetComment.repo == self)
2368 .filter(ChangesetComment.repo == self)
2363 if revisions:
2369 if revisions:
2364 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2370 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2365 grouped = collections.defaultdict(list)
2371 grouped = collections.defaultdict(list)
2366 for cmt in cmts.all():
2372 for cmt in cmts.all():
2367 grouped[cmt.revision].append(cmt)
2373 grouped[cmt.revision].append(cmt)
2368 return grouped
2374 return grouped
2369
2375
2370 def statuses(self, revisions=None):
2376 def statuses(self, revisions=None):
2371 """
2377 """
2372 Returns statuses for this repository
2378 Returns statuses for this repository
2373
2379
2374 :param revisions: list of revisions to get statuses for
2380 :param revisions: list of revisions to get statuses for
2375 """
2381 """
2376 statuses = ChangesetStatus.query()\
2382 statuses = ChangesetStatus.query()\
2377 .filter(ChangesetStatus.repo == self)\
2383 .filter(ChangesetStatus.repo == self)\
2378 .filter(ChangesetStatus.version == 0)
2384 .filter(ChangesetStatus.version == 0)
2379
2385
2380 if revisions:
2386 if revisions:
2381 # Try doing the filtering in chunks to avoid hitting limits
2387 # Try doing the filtering in chunks to avoid hitting limits
2382 size = 500
2388 size = 500
2383 status_results = []
2389 status_results = []
2384 for chunk in xrange(0, len(revisions), size):
2390 for chunk in xrange(0, len(revisions), size):
2385 status_results += statuses.filter(
2391 status_results += statuses.filter(
2386 ChangesetStatus.revision.in_(
2392 ChangesetStatus.revision.in_(
2387 revisions[chunk: chunk+size])
2393 revisions[chunk: chunk+size])
2388 ).all()
2394 ).all()
2389 else:
2395 else:
2390 status_results = statuses.all()
2396 status_results = statuses.all()
2391
2397
2392 grouped = {}
2398 grouped = {}
2393
2399
2394 # maybe we have open new pullrequest without a status?
2400 # maybe we have open new pullrequest without a status?
2395 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2401 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2396 status_lbl = ChangesetStatus.get_status_lbl(stat)
2402 status_lbl = ChangesetStatus.get_status_lbl(stat)
2397 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2403 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2398 for rev in pr.revisions:
2404 for rev in pr.revisions:
2399 pr_id = pr.pull_request_id
2405 pr_id = pr.pull_request_id
2400 pr_repo = pr.target_repo.repo_name
2406 pr_repo = pr.target_repo.repo_name
2401 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2407 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2402
2408
2403 for stat in status_results:
2409 for stat in status_results:
2404 pr_id = pr_repo = None
2410 pr_id = pr_repo = None
2405 if stat.pull_request:
2411 if stat.pull_request:
2406 pr_id = stat.pull_request.pull_request_id
2412 pr_id = stat.pull_request.pull_request_id
2407 pr_repo = stat.pull_request.target_repo.repo_name
2413 pr_repo = stat.pull_request.target_repo.repo_name
2408 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2414 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2409 pr_id, pr_repo]
2415 pr_id, pr_repo]
2410 return grouped
2416 return grouped
2411
2417
2412 # ==========================================================================
2418 # ==========================================================================
2413 # SCM CACHE INSTANCE
2419 # SCM CACHE INSTANCE
2414 # ==========================================================================
2420 # ==========================================================================
2415
2421
2416 def scm_instance(self, **kwargs):
2422 def scm_instance(self, **kwargs):
2417 import rhodecode
2423 import rhodecode
2418
2424
2419 # Passing a config will not hit the cache currently only used
2425 # Passing a config will not hit the cache currently only used
2420 # for repo2dbmapper
2426 # for repo2dbmapper
2421 config = kwargs.pop('config', None)
2427 config = kwargs.pop('config', None)
2422 cache = kwargs.pop('cache', None)
2428 cache = kwargs.pop('cache', None)
2423 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2429 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2424 # if cache is NOT defined use default global, else we have a full
2430 # if cache is NOT defined use default global, else we have a full
2425 # control over cache behaviour
2431 # control over cache behaviour
2426 if cache is None and full_cache and not config:
2432 if cache is None and full_cache and not config:
2427 return self._get_instance_cached()
2433 return self._get_instance_cached()
2428 return self._get_instance(cache=bool(cache), config=config)
2434 return self._get_instance(cache=bool(cache), config=config)
2429
2435
2430 def _get_instance_cached(self):
2436 def _get_instance_cached(self):
2431 from rhodecode.lib import rc_cache
2437 from rhodecode.lib import rc_cache
2432
2438
2433 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2439 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2434 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2440 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2435 repo_id=self.repo_id)
2441 repo_id=self.repo_id)
2436 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2442 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2437
2443
2438 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2444 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2439 def get_instance_cached(repo_id, context_id):
2445 def get_instance_cached(repo_id, context_id):
2440 return self._get_instance()
2446 return self._get_instance()
2441
2447
2442 # we must use thread scoped cache here,
2448 # we must use thread scoped cache here,
2443 # because each thread of gevent needs it's own not shared connection and cache
2449 # because each thread of gevent needs it's own not shared connection and cache
2444 # we also alter `args` so the cache key is individual for every green thread.
2450 # we also alter `args` so the cache key is individual for every green thread.
2445 inv_context_manager = rc_cache.InvalidationContext(
2451 inv_context_manager = rc_cache.InvalidationContext(
2446 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2452 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2447 thread_scoped=True)
2453 thread_scoped=True)
2448 with inv_context_manager as invalidation_context:
2454 with inv_context_manager as invalidation_context:
2449 args = (self.repo_id, inv_context_manager.cache_key)
2455 args = (self.repo_id, inv_context_manager.cache_key)
2450 # re-compute and store cache if we get invalidate signal
2456 # re-compute and store cache if we get invalidate signal
2451 if invalidation_context.should_invalidate():
2457 if invalidation_context.should_invalidate():
2452 instance = get_instance_cached.refresh(*args)
2458 instance = get_instance_cached.refresh(*args)
2453 else:
2459 else:
2454 instance = get_instance_cached(*args)
2460 instance = get_instance_cached(*args)
2455
2461
2456 log.debug(
2462 log.debug(
2457 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2463 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2458 return instance
2464 return instance
2459
2465
2460 def _get_instance(self, cache=True, config=None):
2466 def _get_instance(self, cache=True, config=None):
2461 config = config or self._config
2467 config = config or self._config
2462 custom_wire = {
2468 custom_wire = {
2463 'cache': cache # controls the vcs.remote cache
2469 'cache': cache # controls the vcs.remote cache
2464 }
2470 }
2465 repo = get_vcs_instance(
2471 repo = get_vcs_instance(
2466 repo_path=safe_str(self.repo_full_path),
2472 repo_path=safe_str(self.repo_full_path),
2467 config=config,
2473 config=config,
2468 with_wire=custom_wire,
2474 with_wire=custom_wire,
2469 create=False,
2475 create=False,
2470 _vcs_alias=self.repo_type)
2476 _vcs_alias=self.repo_type)
2471
2477
2472 return repo
2478 return repo
2473
2479
2474 def __json__(self):
2480 def __json__(self):
2475 return {'landing_rev': self.landing_rev}
2481 return {'landing_rev': self.landing_rev}
2476
2482
2477 def get_dict(self):
2483 def get_dict(self):
2478
2484
2479 # Since we transformed `repo_name` to a hybrid property, we need to
2485 # Since we transformed `repo_name` to a hybrid property, we need to
2480 # keep compatibility with the code which uses `repo_name` field.
2486 # keep compatibility with the code which uses `repo_name` field.
2481
2487
2482 result = super(Repository, self).get_dict()
2488 result = super(Repository, self).get_dict()
2483 result['repo_name'] = result.pop('_repo_name', None)
2489 result['repo_name'] = result.pop('_repo_name', None)
2484 return result
2490 return result
2485
2491
2486
2492
2487 class RepoGroup(Base, BaseModel):
2493 class RepoGroup(Base, BaseModel):
2488 __tablename__ = 'groups'
2494 __tablename__ = 'groups'
2489 __table_args__ = (
2495 __table_args__ = (
2490 UniqueConstraint('group_name', 'group_parent_id'),
2496 UniqueConstraint('group_name', 'group_parent_id'),
2491 base_table_args,
2497 base_table_args,
2492 )
2498 )
2493 __mapper_args__ = {'order_by': 'group_name'}
2499 __mapper_args__ = {'order_by': 'group_name'}
2494
2500
2495 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2501 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2496
2502
2497 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2503 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2498 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2504 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2499 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2505 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2500 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2506 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2501 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2507 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2502 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2508 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2503 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2504 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2510 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2505 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2511 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2506 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2512 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2507 _changeset_cache = Column(
2513 _changeset_cache = Column(
2508 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2514 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2509
2515
2510 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2516 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2511 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2517 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2512 parent_group = relationship('RepoGroup', remote_side=group_id)
2518 parent_group = relationship('RepoGroup', remote_side=group_id)
2513 user = relationship('User')
2519 user = relationship('User')
2514 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2520 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2515
2521
2516 def __init__(self, group_name='', parent_group=None):
2522 def __init__(self, group_name='', parent_group=None):
2517 self.group_name = group_name
2523 self.group_name = group_name
2518 self.parent_group = parent_group
2524 self.parent_group = parent_group
2519
2525
2520 def __unicode__(self):
2526 def __unicode__(self):
2521 return u"<%s('id:%s:%s')>" % (
2527 return u"<%s('id:%s:%s')>" % (
2522 self.__class__.__name__, self.group_id, self.group_name)
2528 self.__class__.__name__, self.group_id, self.group_name)
2523
2529
2524 @hybrid_property
2530 @hybrid_property
2525 def group_name(self):
2531 def group_name(self):
2526 return self._group_name
2532 return self._group_name
2527
2533
2528 @group_name.setter
2534 @group_name.setter
2529 def group_name(self, value):
2535 def group_name(self, value):
2530 self._group_name = value
2536 self._group_name = value
2531 self.group_name_hash = self.hash_repo_group_name(value)
2537 self.group_name_hash = self.hash_repo_group_name(value)
2532
2538
2533 @hybrid_property
2539 @hybrid_property
2534 def changeset_cache(self):
2540 def changeset_cache(self):
2535 from rhodecode.lib.vcs.backends.base import EmptyCommit
2541 from rhodecode.lib.vcs.backends.base import EmptyCommit
2536 dummy = EmptyCommit().__json__()
2542 dummy = EmptyCommit().__json__()
2537 if not self._changeset_cache:
2543 if not self._changeset_cache:
2538 dummy['source_repo_id'] = ''
2544 dummy['source_repo_id'] = ''
2539 return json.loads(json.dumps(dummy))
2545 return json.loads(json.dumps(dummy))
2540
2546
2541 try:
2547 try:
2542 return json.loads(self._changeset_cache)
2548 return json.loads(self._changeset_cache)
2543 except TypeError:
2549 except TypeError:
2544 return dummy
2550 return dummy
2545 except Exception:
2551 except Exception:
2546 log.error(traceback.format_exc())
2552 log.error(traceback.format_exc())
2547 return dummy
2553 return dummy
2548
2554
2549 @changeset_cache.setter
2555 @changeset_cache.setter
2550 def changeset_cache(self, val):
2556 def changeset_cache(self, val):
2551 try:
2557 try:
2552 self._changeset_cache = json.dumps(val)
2558 self._changeset_cache = json.dumps(val)
2553 except Exception:
2559 except Exception:
2554 log.error(traceback.format_exc())
2560 log.error(traceback.format_exc())
2555
2561
2556 @validates('group_parent_id')
2562 @validates('group_parent_id')
2557 def validate_group_parent_id(self, key, val):
2563 def validate_group_parent_id(self, key, val):
2558 """
2564 """
2559 Check cycle references for a parent group to self
2565 Check cycle references for a parent group to self
2560 """
2566 """
2561 if self.group_id and val:
2567 if self.group_id and val:
2562 assert val != self.group_id
2568 assert val != self.group_id
2563
2569
2564 return val
2570 return val
2565
2571
2566 @hybrid_property
2572 @hybrid_property
2567 def description_safe(self):
2573 def description_safe(self):
2568 from rhodecode.lib import helpers as h
2574 from rhodecode.lib import helpers as h
2569 return h.escape(self.group_description)
2575 return h.escape(self.group_description)
2570
2576
2571 @classmethod
2577 @classmethod
2572 def hash_repo_group_name(cls, repo_group_name):
2578 def hash_repo_group_name(cls, repo_group_name):
2573 val = remove_formatting(repo_group_name)
2579 val = remove_formatting(repo_group_name)
2574 val = safe_str(val).lower()
2580 val = safe_str(val).lower()
2575 chars = []
2581 chars = []
2576 for c in val:
2582 for c in val:
2577 if c not in string.ascii_letters:
2583 if c not in string.ascii_letters:
2578 c = str(ord(c))
2584 c = str(ord(c))
2579 chars.append(c)
2585 chars.append(c)
2580
2586
2581 return ''.join(chars)
2587 return ''.join(chars)
2582
2588
2583 @classmethod
2589 @classmethod
2584 def _generate_choice(cls, repo_group):
2590 def _generate_choice(cls, repo_group):
2585 from webhelpers.html import literal as _literal
2591 from webhelpers.html import literal as _literal
2586 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2592 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2587 return repo_group.group_id, _name(repo_group.full_path_splitted)
2593 return repo_group.group_id, _name(repo_group.full_path_splitted)
2588
2594
2589 @classmethod
2595 @classmethod
2590 def groups_choices(cls, groups=None, show_empty_group=True):
2596 def groups_choices(cls, groups=None, show_empty_group=True):
2591 if not groups:
2597 if not groups:
2592 groups = cls.query().all()
2598 groups = cls.query().all()
2593
2599
2594 repo_groups = []
2600 repo_groups = []
2595 if show_empty_group:
2601 if show_empty_group:
2596 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2602 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2597
2603
2598 repo_groups.extend([cls._generate_choice(x) for x in groups])
2604 repo_groups.extend([cls._generate_choice(x) for x in groups])
2599
2605
2600 repo_groups = sorted(
2606 repo_groups = sorted(
2601 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2607 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2602 return repo_groups
2608 return repo_groups
2603
2609
2604 @classmethod
2610 @classmethod
2605 def url_sep(cls):
2611 def url_sep(cls):
2606 return URL_SEP
2612 return URL_SEP
2607
2613
2608 @classmethod
2614 @classmethod
2609 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2615 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2610 if case_insensitive:
2616 if case_insensitive:
2611 gr = cls.query().filter(func.lower(cls.group_name)
2617 gr = cls.query().filter(func.lower(cls.group_name)
2612 == func.lower(group_name))
2618 == func.lower(group_name))
2613 else:
2619 else:
2614 gr = cls.query().filter(cls.group_name == group_name)
2620 gr = cls.query().filter(cls.group_name == group_name)
2615 if cache:
2621 if cache:
2616 name_key = _hash_key(group_name)
2622 name_key = _hash_key(group_name)
2617 gr = gr.options(
2623 gr = gr.options(
2618 FromCache("sql_cache_short", "get_group_%s" % name_key))
2624 FromCache("sql_cache_short", "get_group_%s" % name_key))
2619 return gr.scalar()
2625 return gr.scalar()
2620
2626
2621 @classmethod
2627 @classmethod
2622 def get_user_personal_repo_group(cls, user_id):
2628 def get_user_personal_repo_group(cls, user_id):
2623 user = User.get(user_id)
2629 user = User.get(user_id)
2624 if user.username == User.DEFAULT_USER:
2630 if user.username == User.DEFAULT_USER:
2625 return None
2631 return None
2626
2632
2627 return cls.query()\
2633 return cls.query()\
2628 .filter(cls.personal == true()) \
2634 .filter(cls.personal == true()) \
2629 .filter(cls.user == user) \
2635 .filter(cls.user == user) \
2630 .order_by(cls.group_id.asc()) \
2636 .order_by(cls.group_id.asc()) \
2631 .first()
2637 .first()
2632
2638
2633 @classmethod
2639 @classmethod
2634 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2640 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2635 case_insensitive=True):
2641 case_insensitive=True):
2636 q = RepoGroup.query()
2642 q = RepoGroup.query()
2637
2643
2638 if not isinstance(user_id, Optional):
2644 if not isinstance(user_id, Optional):
2639 q = q.filter(RepoGroup.user_id == user_id)
2645 q = q.filter(RepoGroup.user_id == user_id)
2640
2646
2641 if not isinstance(group_id, Optional):
2647 if not isinstance(group_id, Optional):
2642 q = q.filter(RepoGroup.group_parent_id == group_id)
2648 q = q.filter(RepoGroup.group_parent_id == group_id)
2643
2649
2644 if case_insensitive:
2650 if case_insensitive:
2645 q = q.order_by(func.lower(RepoGroup.group_name))
2651 q = q.order_by(func.lower(RepoGroup.group_name))
2646 else:
2652 else:
2647 q = q.order_by(RepoGroup.group_name)
2653 q = q.order_by(RepoGroup.group_name)
2648 return q.all()
2654 return q.all()
2649
2655
2650 @property
2656 @property
2651 def parents(self, parents_recursion_limit = 10):
2657 def parents(self, parents_recursion_limit = 10):
2652 groups = []
2658 groups = []
2653 if self.parent_group is None:
2659 if self.parent_group is None:
2654 return groups
2660 return groups
2655 cur_gr = self.parent_group
2661 cur_gr = self.parent_group
2656 groups.insert(0, cur_gr)
2662 groups.insert(0, cur_gr)
2657 cnt = 0
2663 cnt = 0
2658 while 1:
2664 while 1:
2659 cnt += 1
2665 cnt += 1
2660 gr = getattr(cur_gr, 'parent_group', None)
2666 gr = getattr(cur_gr, 'parent_group', None)
2661 cur_gr = cur_gr.parent_group
2667 cur_gr = cur_gr.parent_group
2662 if gr is None:
2668 if gr is None:
2663 break
2669 break
2664 if cnt == parents_recursion_limit:
2670 if cnt == parents_recursion_limit:
2665 # this will prevent accidental infinit loops
2671 # this will prevent accidental infinit loops
2666 log.error('more than %s parents found for group %s, stopping '
2672 log.error('more than %s parents found for group %s, stopping '
2667 'recursive parent fetching', parents_recursion_limit, self)
2673 'recursive parent fetching', parents_recursion_limit, self)
2668 break
2674 break
2669
2675
2670 groups.insert(0, gr)
2676 groups.insert(0, gr)
2671 return groups
2677 return groups
2672
2678
2673 @property
2679 @property
2680 def last_commit_cache_update_diff(self):
2681 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2682
2683 @property
2674 def last_commit_change(self):
2684 def last_commit_change(self):
2675 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2685 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2676 empty_date = datetime.datetime.fromtimestamp(0)
2686 empty_date = datetime.datetime.fromtimestamp(0)
2677 date_latest = self.changeset_cache.get('date', empty_date)
2687 date_latest = self.changeset_cache.get('date', empty_date)
2678 try:
2688 try:
2679 return parse_datetime(date_latest)
2689 return parse_datetime(date_latest)
2680 except Exception:
2690 except Exception:
2681 return empty_date
2691 return empty_date
2682
2692
2683 @property
2693 @property
2684 def last_db_change(self):
2694 def last_db_change(self):
2685 return self.updated_on
2695 return self.updated_on
2686
2696
2687 @property
2697 @property
2688 def children(self):
2698 def children(self):
2689 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2699 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2690
2700
2691 @property
2701 @property
2692 def name(self):
2702 def name(self):
2693 return self.group_name.split(RepoGroup.url_sep())[-1]
2703 return self.group_name.split(RepoGroup.url_sep())[-1]
2694
2704
2695 @property
2705 @property
2696 def full_path(self):
2706 def full_path(self):
2697 return self.group_name
2707 return self.group_name
2698
2708
2699 @property
2709 @property
2700 def full_path_splitted(self):
2710 def full_path_splitted(self):
2701 return self.group_name.split(RepoGroup.url_sep())
2711 return self.group_name.split(RepoGroup.url_sep())
2702
2712
2703 @property
2713 @property
2704 def repositories(self):
2714 def repositories(self):
2705 return Repository.query()\
2715 return Repository.query()\
2706 .filter(Repository.group == self)\
2716 .filter(Repository.group == self)\
2707 .order_by(Repository.repo_name)
2717 .order_by(Repository.repo_name)
2708
2718
2709 @property
2719 @property
2710 def repositories_recursive_count(self):
2720 def repositories_recursive_count(self):
2711 cnt = self.repositories.count()
2721 cnt = self.repositories.count()
2712
2722
2713 def children_count(group):
2723 def children_count(group):
2714 cnt = 0
2724 cnt = 0
2715 for child in group.children:
2725 for child in group.children:
2716 cnt += child.repositories.count()
2726 cnt += child.repositories.count()
2717 cnt += children_count(child)
2727 cnt += children_count(child)
2718 return cnt
2728 return cnt
2719
2729
2720 return cnt + children_count(self)
2730 return cnt + children_count(self)
2721
2731
2722 def _recursive_objects(self, include_repos=True, include_groups=True):
2732 def _recursive_objects(self, include_repos=True, include_groups=True):
2723 all_ = []
2733 all_ = []
2724
2734
2725 def _get_members(root_gr):
2735 def _get_members(root_gr):
2726 if include_repos:
2736 if include_repos:
2727 for r in root_gr.repositories:
2737 for r in root_gr.repositories:
2728 all_.append(r)
2738 all_.append(r)
2729 childs = root_gr.children.all()
2739 childs = root_gr.children.all()
2730 if childs:
2740 if childs:
2731 for gr in childs:
2741 for gr in childs:
2732 if include_groups:
2742 if include_groups:
2733 all_.append(gr)
2743 all_.append(gr)
2734 _get_members(gr)
2744 _get_members(gr)
2735
2745
2736 root_group = []
2746 root_group = []
2737 if include_groups:
2747 if include_groups:
2738 root_group = [self]
2748 root_group = [self]
2739
2749
2740 _get_members(self)
2750 _get_members(self)
2741 return root_group + all_
2751 return root_group + all_
2742
2752
2743 def recursive_groups_and_repos(self):
2753 def recursive_groups_and_repos(self):
2744 """
2754 """
2745 Recursive return all groups, with repositories in those groups
2755 Recursive return all groups, with repositories in those groups
2746 """
2756 """
2747 return self._recursive_objects()
2757 return self._recursive_objects()
2748
2758
2749 def recursive_groups(self):
2759 def recursive_groups(self):
2750 """
2760 """
2751 Returns all children groups for this group including children of children
2761 Returns all children groups for this group including children of children
2752 """
2762 """
2753 return self._recursive_objects(include_repos=False)
2763 return self._recursive_objects(include_repos=False)
2754
2764
2755 def recursive_repos(self):
2765 def recursive_repos(self):
2756 """
2766 """
2757 Returns all children repositories for this group
2767 Returns all children repositories for this group
2758 """
2768 """
2759 return self._recursive_objects(include_groups=False)
2769 return self._recursive_objects(include_groups=False)
2760
2770
2761 def get_new_name(self, group_name):
2771 def get_new_name(self, group_name):
2762 """
2772 """
2763 returns new full group name based on parent and new name
2773 returns new full group name based on parent and new name
2764
2774
2765 :param group_name:
2775 :param group_name:
2766 """
2776 """
2767 path_prefix = (self.parent_group.full_path_splitted if
2777 path_prefix = (self.parent_group.full_path_splitted if
2768 self.parent_group else [])
2778 self.parent_group else [])
2769 return RepoGroup.url_sep().join(path_prefix + [group_name])
2779 return RepoGroup.url_sep().join(path_prefix + [group_name])
2770
2780
2771 def update_commit_cache(self, config=None):
2781 def update_commit_cache(self, config=None):
2772 """
2782 """
2773 Update cache of last changeset for newest repository inside this group, keys should be::
2783 Update cache of last changeset for newest repository inside this group, keys should be::
2774
2784
2775 source_repo_id
2785 source_repo_id
2776 short_id
2786 short_id
2777 raw_id
2787 raw_id
2778 revision
2788 revision
2779 parents
2789 parents
2780 message
2790 message
2781 date
2791 date
2782 author
2792 author
2783
2793
2784 """
2794 """
2785 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2795 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2786
2796
2787 def repo_groups_and_repos():
2797 def repo_groups_and_repos():
2788 all_entries = OrderedDefaultDict(list)
2798 all_entries = OrderedDefaultDict(list)
2789
2799
2790 def _get_members(root_gr, pos=0):
2800 def _get_members(root_gr, pos=0):
2791
2801
2792 for repo in root_gr.repositories:
2802 for repo in root_gr.repositories:
2793 all_entries[root_gr].append(repo)
2803 all_entries[root_gr].append(repo)
2794
2804
2795 # fill in all parent positions
2805 # fill in all parent positions
2796 for parent_group in root_gr.parents:
2806 for parent_group in root_gr.parents:
2797 all_entries[parent_group].extend(all_entries[root_gr])
2807 all_entries[parent_group].extend(all_entries[root_gr])
2798
2808
2799 children_groups = root_gr.children.all()
2809 children_groups = root_gr.children.all()
2800 if children_groups:
2810 if children_groups:
2801 for cnt, gr in enumerate(children_groups, 1):
2811 for cnt, gr in enumerate(children_groups, 1):
2802 _get_members(gr, pos=pos+cnt)
2812 _get_members(gr, pos=pos+cnt)
2803
2813
2804 _get_members(root_gr=self)
2814 _get_members(root_gr=self)
2805 return all_entries
2815 return all_entries
2806
2816
2807 empty_date = datetime.datetime.fromtimestamp(0)
2817 empty_date = datetime.datetime.fromtimestamp(0)
2808 for repo_group, repos in repo_groups_and_repos().items():
2818 for repo_group, repos in repo_groups_and_repos().items():
2809
2819
2810 latest_repo_cs_cache = {}
2820 latest_repo_cs_cache = {}
2811 for repo in repos:
2821 for repo in repos:
2812 repo_cs_cache = repo.changeset_cache
2822 repo_cs_cache = repo.changeset_cache
2813 date_latest = latest_repo_cs_cache.get('date', empty_date)
2823 date_latest = latest_repo_cs_cache.get('date', empty_date)
2814 date_current = repo_cs_cache.get('date', empty_date)
2824 date_current = repo_cs_cache.get('date', empty_date)
2815 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2825 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2816 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2826 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2817 latest_repo_cs_cache = repo_cs_cache
2827 latest_repo_cs_cache = repo_cs_cache
2818 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2828 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2819
2829
2830 latest_repo_cs_cache['updated_on'] = time.time()
2820 repo_group.changeset_cache = latest_repo_cs_cache
2831 repo_group.changeset_cache = latest_repo_cs_cache
2821 Session().add(repo_group)
2832 Session().add(repo_group)
2822 Session().commit()
2833 Session().commit()
2823
2834
2824 log.debug('updated repo group %s with new commit cache %s',
2835 log.debug('updated repo group %s with new commit cache %s',
2825 repo_group.group_name, latest_repo_cs_cache)
2836 repo_group.group_name, latest_repo_cs_cache)
2826
2837
2827 def permissions(self, with_admins=True, with_owner=True,
2838 def permissions(self, with_admins=True, with_owner=True,
2828 expand_from_user_groups=False):
2839 expand_from_user_groups=False):
2829 """
2840 """
2830 Permissions for repository groups
2841 Permissions for repository groups
2831 """
2842 """
2832 _admin_perm = 'group.admin'
2843 _admin_perm = 'group.admin'
2833
2844
2834 owner_row = []
2845 owner_row = []
2835 if with_owner:
2846 if with_owner:
2836 usr = AttributeDict(self.user.get_dict())
2847 usr = AttributeDict(self.user.get_dict())
2837 usr.owner_row = True
2848 usr.owner_row = True
2838 usr.permission = _admin_perm
2849 usr.permission = _admin_perm
2839 owner_row.append(usr)
2850 owner_row.append(usr)
2840
2851
2841 super_admin_ids = []
2852 super_admin_ids = []
2842 super_admin_rows = []
2853 super_admin_rows = []
2843 if with_admins:
2854 if with_admins:
2844 for usr in User.get_all_super_admins():
2855 for usr in User.get_all_super_admins():
2845 super_admin_ids.append(usr.user_id)
2856 super_admin_ids.append(usr.user_id)
2846 # if this admin is also owner, don't double the record
2857 # if this admin is also owner, don't double the record
2847 if usr.user_id == owner_row[0].user_id:
2858 if usr.user_id == owner_row[0].user_id:
2848 owner_row[0].admin_row = True
2859 owner_row[0].admin_row = True
2849 else:
2860 else:
2850 usr = AttributeDict(usr.get_dict())
2861 usr = AttributeDict(usr.get_dict())
2851 usr.admin_row = True
2862 usr.admin_row = True
2852 usr.permission = _admin_perm
2863 usr.permission = _admin_perm
2853 super_admin_rows.append(usr)
2864 super_admin_rows.append(usr)
2854
2865
2855 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2866 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2856 q = q.options(joinedload(UserRepoGroupToPerm.group),
2867 q = q.options(joinedload(UserRepoGroupToPerm.group),
2857 joinedload(UserRepoGroupToPerm.user),
2868 joinedload(UserRepoGroupToPerm.user),
2858 joinedload(UserRepoGroupToPerm.permission),)
2869 joinedload(UserRepoGroupToPerm.permission),)
2859
2870
2860 # get owners and admins and permissions. We do a trick of re-writing
2871 # get owners and admins and permissions. We do a trick of re-writing
2861 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2872 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2862 # has a global reference and changing one object propagates to all
2873 # has a global reference and changing one object propagates to all
2863 # others. This means if admin is also an owner admin_row that change
2874 # others. This means if admin is also an owner admin_row that change
2864 # would propagate to both objects
2875 # would propagate to both objects
2865 perm_rows = []
2876 perm_rows = []
2866 for _usr in q.all():
2877 for _usr in q.all():
2867 usr = AttributeDict(_usr.user.get_dict())
2878 usr = AttributeDict(_usr.user.get_dict())
2868 # if this user is also owner/admin, mark as duplicate record
2879 # if this user is also owner/admin, mark as duplicate record
2869 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2880 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2870 usr.duplicate_perm = True
2881 usr.duplicate_perm = True
2871 usr.permission = _usr.permission.permission_name
2882 usr.permission = _usr.permission.permission_name
2872 perm_rows.append(usr)
2883 perm_rows.append(usr)
2873
2884
2874 # filter the perm rows by 'default' first and then sort them by
2885 # filter the perm rows by 'default' first and then sort them by
2875 # admin,write,read,none permissions sorted again alphabetically in
2886 # admin,write,read,none permissions sorted again alphabetically in
2876 # each group
2887 # each group
2877 perm_rows = sorted(perm_rows, key=display_user_sort)
2888 perm_rows = sorted(perm_rows, key=display_user_sort)
2878
2889
2879 user_groups_rows = []
2890 user_groups_rows = []
2880 if expand_from_user_groups:
2891 if expand_from_user_groups:
2881 for ug in self.permission_user_groups(with_members=True):
2892 for ug in self.permission_user_groups(with_members=True):
2882 for user_data in ug.members:
2893 for user_data in ug.members:
2883 user_groups_rows.append(user_data)
2894 user_groups_rows.append(user_data)
2884
2895
2885 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2896 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2886
2897
2887 def permission_user_groups(self, with_members=False):
2898 def permission_user_groups(self, with_members=False):
2888 q = UserGroupRepoGroupToPerm.query()\
2899 q = UserGroupRepoGroupToPerm.query()\
2889 .filter(UserGroupRepoGroupToPerm.group == self)
2900 .filter(UserGroupRepoGroupToPerm.group == self)
2890 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2901 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2891 joinedload(UserGroupRepoGroupToPerm.users_group),
2902 joinedload(UserGroupRepoGroupToPerm.users_group),
2892 joinedload(UserGroupRepoGroupToPerm.permission),)
2903 joinedload(UserGroupRepoGroupToPerm.permission),)
2893
2904
2894 perm_rows = []
2905 perm_rows = []
2895 for _user_group in q.all():
2906 for _user_group in q.all():
2896 entry = AttributeDict(_user_group.users_group.get_dict())
2907 entry = AttributeDict(_user_group.users_group.get_dict())
2897 entry.permission = _user_group.permission.permission_name
2908 entry.permission = _user_group.permission.permission_name
2898 if with_members:
2909 if with_members:
2899 entry.members = [x.user.get_dict()
2910 entry.members = [x.user.get_dict()
2900 for x in _user_group.users_group.members]
2911 for x in _user_group.users_group.members]
2901 perm_rows.append(entry)
2912 perm_rows.append(entry)
2902
2913
2903 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2914 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2904 return perm_rows
2915 return perm_rows
2905
2916
2906 def get_api_data(self):
2917 def get_api_data(self):
2907 """
2918 """
2908 Common function for generating api data
2919 Common function for generating api data
2909
2920
2910 """
2921 """
2911 group = self
2922 group = self
2912 data = {
2923 data = {
2913 'group_id': group.group_id,
2924 'group_id': group.group_id,
2914 'group_name': group.group_name,
2925 'group_name': group.group_name,
2915 'group_description': group.description_safe,
2926 'group_description': group.description_safe,
2916 'parent_group': group.parent_group.group_name if group.parent_group else None,
2927 'parent_group': group.parent_group.group_name if group.parent_group else None,
2917 'repositories': [x.repo_name for x in group.repositories],
2928 'repositories': [x.repo_name for x in group.repositories],
2918 'owner': group.user.username,
2929 'owner': group.user.username,
2919 }
2930 }
2920 return data
2931 return data
2921
2932
2922 def get_dict(self):
2933 def get_dict(self):
2923 # Since we transformed `group_name` to a hybrid property, we need to
2934 # Since we transformed `group_name` to a hybrid property, we need to
2924 # keep compatibility with the code which uses `group_name` field.
2935 # keep compatibility with the code which uses `group_name` field.
2925 result = super(RepoGroup, self).get_dict()
2936 result = super(RepoGroup, self).get_dict()
2926 result['group_name'] = result.pop('_group_name', None)
2937 result['group_name'] = result.pop('_group_name', None)
2927 return result
2938 return result
2928
2939
2929
2940
2930 class Permission(Base, BaseModel):
2941 class Permission(Base, BaseModel):
2931 __tablename__ = 'permissions'
2942 __tablename__ = 'permissions'
2932 __table_args__ = (
2943 __table_args__ = (
2933 Index('p_perm_name_idx', 'permission_name'),
2944 Index('p_perm_name_idx', 'permission_name'),
2934 base_table_args,
2945 base_table_args,
2935 )
2946 )
2936
2947
2937 PERMS = [
2948 PERMS = [
2938 ('hg.admin', _('RhodeCode Super Administrator')),
2949 ('hg.admin', _('RhodeCode Super Administrator')),
2939
2950
2940 ('repository.none', _('Repository no access')),
2951 ('repository.none', _('Repository no access')),
2941 ('repository.read', _('Repository read access')),
2952 ('repository.read', _('Repository read access')),
2942 ('repository.write', _('Repository write access')),
2953 ('repository.write', _('Repository write access')),
2943 ('repository.admin', _('Repository admin access')),
2954 ('repository.admin', _('Repository admin access')),
2944
2955
2945 ('group.none', _('Repository group no access')),
2956 ('group.none', _('Repository group no access')),
2946 ('group.read', _('Repository group read access')),
2957 ('group.read', _('Repository group read access')),
2947 ('group.write', _('Repository group write access')),
2958 ('group.write', _('Repository group write access')),
2948 ('group.admin', _('Repository group admin access')),
2959 ('group.admin', _('Repository group admin access')),
2949
2960
2950 ('usergroup.none', _('User group no access')),
2961 ('usergroup.none', _('User group no access')),
2951 ('usergroup.read', _('User group read access')),
2962 ('usergroup.read', _('User group read access')),
2952 ('usergroup.write', _('User group write access')),
2963 ('usergroup.write', _('User group write access')),
2953 ('usergroup.admin', _('User group admin access')),
2964 ('usergroup.admin', _('User group admin access')),
2954
2965
2955 ('branch.none', _('Branch no permissions')),
2966 ('branch.none', _('Branch no permissions')),
2956 ('branch.merge', _('Branch access by web merge')),
2967 ('branch.merge', _('Branch access by web merge')),
2957 ('branch.push', _('Branch access by push')),
2968 ('branch.push', _('Branch access by push')),
2958 ('branch.push_force', _('Branch access by push with force')),
2969 ('branch.push_force', _('Branch access by push with force')),
2959
2970
2960 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2971 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2961 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2972 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2962
2973
2963 ('hg.usergroup.create.false', _('User Group creation disabled')),
2974 ('hg.usergroup.create.false', _('User Group creation disabled')),
2964 ('hg.usergroup.create.true', _('User Group creation enabled')),
2975 ('hg.usergroup.create.true', _('User Group creation enabled')),
2965
2976
2966 ('hg.create.none', _('Repository creation disabled')),
2977 ('hg.create.none', _('Repository creation disabled')),
2967 ('hg.create.repository', _('Repository creation enabled')),
2978 ('hg.create.repository', _('Repository creation enabled')),
2968 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2979 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2969 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2980 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2970
2981
2971 ('hg.fork.none', _('Repository forking disabled')),
2982 ('hg.fork.none', _('Repository forking disabled')),
2972 ('hg.fork.repository', _('Repository forking enabled')),
2983 ('hg.fork.repository', _('Repository forking enabled')),
2973
2984
2974 ('hg.register.none', _('Registration disabled')),
2985 ('hg.register.none', _('Registration disabled')),
2975 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2986 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2976 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2987 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2977
2988
2978 ('hg.password_reset.enabled', _('Password reset enabled')),
2989 ('hg.password_reset.enabled', _('Password reset enabled')),
2979 ('hg.password_reset.hidden', _('Password reset hidden')),
2990 ('hg.password_reset.hidden', _('Password reset hidden')),
2980 ('hg.password_reset.disabled', _('Password reset disabled')),
2991 ('hg.password_reset.disabled', _('Password reset disabled')),
2981
2992
2982 ('hg.extern_activate.manual', _('Manual activation of external account')),
2993 ('hg.extern_activate.manual', _('Manual activation of external account')),
2983 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2994 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2984
2995
2985 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2996 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2986 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2997 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2987 ]
2998 ]
2988
2999
2989 # definition of system default permissions for DEFAULT user, created on
3000 # definition of system default permissions for DEFAULT user, created on
2990 # system setup
3001 # system setup
2991 DEFAULT_USER_PERMISSIONS = [
3002 DEFAULT_USER_PERMISSIONS = [
2992 # object perms
3003 # object perms
2993 'repository.read',
3004 'repository.read',
2994 'group.read',
3005 'group.read',
2995 'usergroup.read',
3006 'usergroup.read',
2996 # branch, for backward compat we need same value as before so forced pushed
3007 # branch, for backward compat we need same value as before so forced pushed
2997 'branch.push_force',
3008 'branch.push_force',
2998 # global
3009 # global
2999 'hg.create.repository',
3010 'hg.create.repository',
3000 'hg.repogroup.create.false',
3011 'hg.repogroup.create.false',
3001 'hg.usergroup.create.false',
3012 'hg.usergroup.create.false',
3002 'hg.create.write_on_repogroup.true',
3013 'hg.create.write_on_repogroup.true',
3003 'hg.fork.repository',
3014 'hg.fork.repository',
3004 'hg.register.manual_activate',
3015 'hg.register.manual_activate',
3005 'hg.password_reset.enabled',
3016 'hg.password_reset.enabled',
3006 'hg.extern_activate.auto',
3017 'hg.extern_activate.auto',
3007 'hg.inherit_default_perms.true',
3018 'hg.inherit_default_perms.true',
3008 ]
3019 ]
3009
3020
3010 # defines which permissions are more important higher the more important
3021 # defines which permissions are more important higher the more important
3011 # Weight defines which permissions are more important.
3022 # Weight defines which permissions are more important.
3012 # The higher number the more important.
3023 # The higher number the more important.
3013 PERM_WEIGHTS = {
3024 PERM_WEIGHTS = {
3014 'repository.none': 0,
3025 'repository.none': 0,
3015 'repository.read': 1,
3026 'repository.read': 1,
3016 'repository.write': 3,
3027 'repository.write': 3,
3017 'repository.admin': 4,
3028 'repository.admin': 4,
3018
3029
3019 'group.none': 0,
3030 'group.none': 0,
3020 'group.read': 1,
3031 'group.read': 1,
3021 'group.write': 3,
3032 'group.write': 3,
3022 'group.admin': 4,
3033 'group.admin': 4,
3023
3034
3024 'usergroup.none': 0,
3035 'usergroup.none': 0,
3025 'usergroup.read': 1,
3036 'usergroup.read': 1,
3026 'usergroup.write': 3,
3037 'usergroup.write': 3,
3027 'usergroup.admin': 4,
3038 'usergroup.admin': 4,
3028
3039
3029 'branch.none': 0,
3040 'branch.none': 0,
3030 'branch.merge': 1,
3041 'branch.merge': 1,
3031 'branch.push': 3,
3042 'branch.push': 3,
3032 'branch.push_force': 4,
3043 'branch.push_force': 4,
3033
3044
3034 'hg.repogroup.create.false': 0,
3045 'hg.repogroup.create.false': 0,
3035 'hg.repogroup.create.true': 1,
3046 'hg.repogroup.create.true': 1,
3036
3047
3037 'hg.usergroup.create.false': 0,
3048 'hg.usergroup.create.false': 0,
3038 'hg.usergroup.create.true': 1,
3049 'hg.usergroup.create.true': 1,
3039
3050
3040 'hg.fork.none': 0,
3051 'hg.fork.none': 0,
3041 'hg.fork.repository': 1,
3052 'hg.fork.repository': 1,
3042 'hg.create.none': 0,
3053 'hg.create.none': 0,
3043 'hg.create.repository': 1
3054 'hg.create.repository': 1
3044 }
3055 }
3045
3056
3046 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3057 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3047 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3058 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3048 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3059 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3049
3060
3050 def __unicode__(self):
3061 def __unicode__(self):
3051 return u"<%s('%s:%s')>" % (
3062 return u"<%s('%s:%s')>" % (
3052 self.__class__.__name__, self.permission_id, self.permission_name
3063 self.__class__.__name__, self.permission_id, self.permission_name
3053 )
3064 )
3054
3065
3055 @classmethod
3066 @classmethod
3056 def get_by_key(cls, key):
3067 def get_by_key(cls, key):
3057 return cls.query().filter(cls.permission_name == key).scalar()
3068 return cls.query().filter(cls.permission_name == key).scalar()
3058
3069
3059 @classmethod
3070 @classmethod
3060 def get_default_repo_perms(cls, user_id, repo_id=None):
3071 def get_default_repo_perms(cls, user_id, repo_id=None):
3061 q = Session().query(UserRepoToPerm, Repository, Permission)\
3072 q = Session().query(UserRepoToPerm, Repository, Permission)\
3062 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3073 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3063 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3074 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3064 .filter(UserRepoToPerm.user_id == user_id)
3075 .filter(UserRepoToPerm.user_id == user_id)
3065 if repo_id:
3076 if repo_id:
3066 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3077 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3067 return q.all()
3078 return q.all()
3068
3079
3069 @classmethod
3080 @classmethod
3070 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3081 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3071 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3082 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3072 .join(
3083 .join(
3073 Permission,
3084 Permission,
3074 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3085 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3075 .join(
3086 .join(
3076 UserRepoToPerm,
3087 UserRepoToPerm,
3077 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3088 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3078 .filter(UserRepoToPerm.user_id == user_id)
3089 .filter(UserRepoToPerm.user_id == user_id)
3079
3090
3080 if repo_id:
3091 if repo_id:
3081 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3092 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3082 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3093 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3083
3094
3084 @classmethod
3095 @classmethod
3085 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3096 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3086 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3097 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3087 .join(
3098 .join(
3088 Permission,
3099 Permission,
3089 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3100 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3090 .join(
3101 .join(
3091 Repository,
3102 Repository,
3092 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3103 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3093 .join(
3104 .join(
3094 UserGroup,
3105 UserGroup,
3095 UserGroupRepoToPerm.users_group_id ==
3106 UserGroupRepoToPerm.users_group_id ==
3096 UserGroup.users_group_id)\
3107 UserGroup.users_group_id)\
3097 .join(
3108 .join(
3098 UserGroupMember,
3109 UserGroupMember,
3099 UserGroupRepoToPerm.users_group_id ==
3110 UserGroupRepoToPerm.users_group_id ==
3100 UserGroupMember.users_group_id)\
3111 UserGroupMember.users_group_id)\
3101 .filter(
3112 .filter(
3102 UserGroupMember.user_id == user_id,
3113 UserGroupMember.user_id == user_id,
3103 UserGroup.users_group_active == true())
3114 UserGroup.users_group_active == true())
3104 if repo_id:
3115 if repo_id:
3105 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3116 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3106 return q.all()
3117 return q.all()
3107
3118
3108 @classmethod
3119 @classmethod
3109 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3120 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3110 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3121 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3111 .join(
3122 .join(
3112 Permission,
3123 Permission,
3113 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3124 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3114 .join(
3125 .join(
3115 UserGroupRepoToPerm,
3126 UserGroupRepoToPerm,
3116 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3127 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3117 .join(
3128 .join(
3118 UserGroup,
3129 UserGroup,
3119 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3130 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3120 .join(
3131 .join(
3121 UserGroupMember,
3132 UserGroupMember,
3122 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3133 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3123 .filter(
3134 .filter(
3124 UserGroupMember.user_id == user_id,
3135 UserGroupMember.user_id == user_id,
3125 UserGroup.users_group_active == true())
3136 UserGroup.users_group_active == true())
3126
3137
3127 if repo_id:
3138 if repo_id:
3128 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3139 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3129 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3140 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3130
3141
3131 @classmethod
3142 @classmethod
3132 def get_default_group_perms(cls, user_id, repo_group_id=None):
3143 def get_default_group_perms(cls, user_id, repo_group_id=None):
3133 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3144 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3134 .join(
3145 .join(
3135 Permission,
3146 Permission,
3136 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3147 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3137 .join(
3148 .join(
3138 RepoGroup,
3149 RepoGroup,
3139 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3150 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3140 .filter(UserRepoGroupToPerm.user_id == user_id)
3151 .filter(UserRepoGroupToPerm.user_id == user_id)
3141 if repo_group_id:
3152 if repo_group_id:
3142 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3153 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3143 return q.all()
3154 return q.all()
3144
3155
3145 @classmethod
3156 @classmethod
3146 def get_default_group_perms_from_user_group(
3157 def get_default_group_perms_from_user_group(
3147 cls, user_id, repo_group_id=None):
3158 cls, user_id, repo_group_id=None):
3148 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3159 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3149 .join(
3160 .join(
3150 Permission,
3161 Permission,
3151 UserGroupRepoGroupToPerm.permission_id ==
3162 UserGroupRepoGroupToPerm.permission_id ==
3152 Permission.permission_id)\
3163 Permission.permission_id)\
3153 .join(
3164 .join(
3154 RepoGroup,
3165 RepoGroup,
3155 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3166 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3156 .join(
3167 .join(
3157 UserGroup,
3168 UserGroup,
3158 UserGroupRepoGroupToPerm.users_group_id ==
3169 UserGroupRepoGroupToPerm.users_group_id ==
3159 UserGroup.users_group_id)\
3170 UserGroup.users_group_id)\
3160 .join(
3171 .join(
3161 UserGroupMember,
3172 UserGroupMember,
3162 UserGroupRepoGroupToPerm.users_group_id ==
3173 UserGroupRepoGroupToPerm.users_group_id ==
3163 UserGroupMember.users_group_id)\
3174 UserGroupMember.users_group_id)\
3164 .filter(
3175 .filter(
3165 UserGroupMember.user_id == user_id,
3176 UserGroupMember.user_id == user_id,
3166 UserGroup.users_group_active == true())
3177 UserGroup.users_group_active == true())
3167 if repo_group_id:
3178 if repo_group_id:
3168 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3179 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3169 return q.all()
3180 return q.all()
3170
3181
3171 @classmethod
3182 @classmethod
3172 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3183 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3173 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3184 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3174 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3185 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3175 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3186 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3176 .filter(UserUserGroupToPerm.user_id == user_id)
3187 .filter(UserUserGroupToPerm.user_id == user_id)
3177 if user_group_id:
3188 if user_group_id:
3178 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3189 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3179 return q.all()
3190 return q.all()
3180
3191
3181 @classmethod
3192 @classmethod
3182 def get_default_user_group_perms_from_user_group(
3193 def get_default_user_group_perms_from_user_group(
3183 cls, user_id, user_group_id=None):
3194 cls, user_id, user_group_id=None):
3184 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3195 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3185 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3196 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3186 .join(
3197 .join(
3187 Permission,
3198 Permission,
3188 UserGroupUserGroupToPerm.permission_id ==
3199 UserGroupUserGroupToPerm.permission_id ==
3189 Permission.permission_id)\
3200 Permission.permission_id)\
3190 .join(
3201 .join(
3191 TargetUserGroup,
3202 TargetUserGroup,
3192 UserGroupUserGroupToPerm.target_user_group_id ==
3203 UserGroupUserGroupToPerm.target_user_group_id ==
3193 TargetUserGroup.users_group_id)\
3204 TargetUserGroup.users_group_id)\
3194 .join(
3205 .join(
3195 UserGroup,
3206 UserGroup,
3196 UserGroupUserGroupToPerm.user_group_id ==
3207 UserGroupUserGroupToPerm.user_group_id ==
3197 UserGroup.users_group_id)\
3208 UserGroup.users_group_id)\
3198 .join(
3209 .join(
3199 UserGroupMember,
3210 UserGroupMember,
3200 UserGroupUserGroupToPerm.user_group_id ==
3211 UserGroupUserGroupToPerm.user_group_id ==
3201 UserGroupMember.users_group_id)\
3212 UserGroupMember.users_group_id)\
3202 .filter(
3213 .filter(
3203 UserGroupMember.user_id == user_id,
3214 UserGroupMember.user_id == user_id,
3204 UserGroup.users_group_active == true())
3215 UserGroup.users_group_active == true())
3205 if user_group_id:
3216 if user_group_id:
3206 q = q.filter(
3217 q = q.filter(
3207 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3218 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3208
3219
3209 return q.all()
3220 return q.all()
3210
3221
3211
3222
3212 class UserRepoToPerm(Base, BaseModel):
3223 class UserRepoToPerm(Base, BaseModel):
3213 __tablename__ = 'repo_to_perm'
3224 __tablename__ = 'repo_to_perm'
3214 __table_args__ = (
3225 __table_args__ = (
3215 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3226 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3216 base_table_args
3227 base_table_args
3217 )
3228 )
3218
3229
3219 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3230 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3220 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3231 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3221 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3232 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3222 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3233 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3223
3234
3224 user = relationship('User')
3235 user = relationship('User')
3225 repository = relationship('Repository')
3236 repository = relationship('Repository')
3226 permission = relationship('Permission')
3237 permission = relationship('Permission')
3227
3238
3228 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3239 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3229
3240
3230 @classmethod
3241 @classmethod
3231 def create(cls, user, repository, permission):
3242 def create(cls, user, repository, permission):
3232 n = cls()
3243 n = cls()
3233 n.user = user
3244 n.user = user
3234 n.repository = repository
3245 n.repository = repository
3235 n.permission = permission
3246 n.permission = permission
3236 Session().add(n)
3247 Session().add(n)
3237 return n
3248 return n
3238
3249
3239 def __unicode__(self):
3250 def __unicode__(self):
3240 return u'<%s => %s >' % (self.user, self.repository)
3251 return u'<%s => %s >' % (self.user, self.repository)
3241
3252
3242
3253
3243 class UserUserGroupToPerm(Base, BaseModel):
3254 class UserUserGroupToPerm(Base, BaseModel):
3244 __tablename__ = 'user_user_group_to_perm'
3255 __tablename__ = 'user_user_group_to_perm'
3245 __table_args__ = (
3256 __table_args__ = (
3246 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3257 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3247 base_table_args
3258 base_table_args
3248 )
3259 )
3249
3260
3250 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3261 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3251 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3252 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3263 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3253 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3264 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3254
3265
3255 user = relationship('User')
3266 user = relationship('User')
3256 user_group = relationship('UserGroup')
3267 user_group = relationship('UserGroup')
3257 permission = relationship('Permission')
3268 permission = relationship('Permission')
3258
3269
3259 @classmethod
3270 @classmethod
3260 def create(cls, user, user_group, permission):
3271 def create(cls, user, user_group, permission):
3261 n = cls()
3272 n = cls()
3262 n.user = user
3273 n.user = user
3263 n.user_group = user_group
3274 n.user_group = user_group
3264 n.permission = permission
3275 n.permission = permission
3265 Session().add(n)
3276 Session().add(n)
3266 return n
3277 return n
3267
3278
3268 def __unicode__(self):
3279 def __unicode__(self):
3269 return u'<%s => %s >' % (self.user, self.user_group)
3280 return u'<%s => %s >' % (self.user, self.user_group)
3270
3281
3271
3282
3272 class UserToPerm(Base, BaseModel):
3283 class UserToPerm(Base, BaseModel):
3273 __tablename__ = 'user_to_perm'
3284 __tablename__ = 'user_to_perm'
3274 __table_args__ = (
3285 __table_args__ = (
3275 UniqueConstraint('user_id', 'permission_id'),
3286 UniqueConstraint('user_id', 'permission_id'),
3276 base_table_args
3287 base_table_args
3277 )
3288 )
3278
3289
3279 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3290 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3280 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3281 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3292 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3282
3293
3283 user = relationship('User')
3294 user = relationship('User')
3284 permission = relationship('Permission', lazy='joined')
3295 permission = relationship('Permission', lazy='joined')
3285
3296
3286 def __unicode__(self):
3297 def __unicode__(self):
3287 return u'<%s => %s >' % (self.user, self.permission)
3298 return u'<%s => %s >' % (self.user, self.permission)
3288
3299
3289
3300
3290 class UserGroupRepoToPerm(Base, BaseModel):
3301 class UserGroupRepoToPerm(Base, BaseModel):
3291 __tablename__ = 'users_group_repo_to_perm'
3302 __tablename__ = 'users_group_repo_to_perm'
3292 __table_args__ = (
3303 __table_args__ = (
3293 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3304 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3294 base_table_args
3305 base_table_args
3295 )
3306 )
3296
3307
3297 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3308 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3298 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3309 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3299 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3310 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3300 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3301
3312
3302 users_group = relationship('UserGroup')
3313 users_group = relationship('UserGroup')
3303 permission = relationship('Permission')
3314 permission = relationship('Permission')
3304 repository = relationship('Repository')
3315 repository = relationship('Repository')
3305 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3316 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3306
3317
3307 @classmethod
3318 @classmethod
3308 def create(cls, users_group, repository, permission):
3319 def create(cls, users_group, repository, permission):
3309 n = cls()
3320 n = cls()
3310 n.users_group = users_group
3321 n.users_group = users_group
3311 n.repository = repository
3322 n.repository = repository
3312 n.permission = permission
3323 n.permission = permission
3313 Session().add(n)
3324 Session().add(n)
3314 return n
3325 return n
3315
3326
3316 def __unicode__(self):
3327 def __unicode__(self):
3317 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3328 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3318
3329
3319
3330
3320 class UserGroupUserGroupToPerm(Base, BaseModel):
3331 class UserGroupUserGroupToPerm(Base, BaseModel):
3321 __tablename__ = 'user_group_user_group_to_perm'
3332 __tablename__ = 'user_group_user_group_to_perm'
3322 __table_args__ = (
3333 __table_args__ = (
3323 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3334 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3324 CheckConstraint('target_user_group_id != user_group_id'),
3335 CheckConstraint('target_user_group_id != user_group_id'),
3325 base_table_args
3336 base_table_args
3326 )
3337 )
3327
3338
3328 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)
3339 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)
3329 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3340 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3330 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3341 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3331 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3342 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3332
3343
3333 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3344 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3334 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3345 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3335 permission = relationship('Permission')
3346 permission = relationship('Permission')
3336
3347
3337 @classmethod
3348 @classmethod
3338 def create(cls, target_user_group, user_group, permission):
3349 def create(cls, target_user_group, user_group, permission):
3339 n = cls()
3350 n = cls()
3340 n.target_user_group = target_user_group
3351 n.target_user_group = target_user_group
3341 n.user_group = user_group
3352 n.user_group = user_group
3342 n.permission = permission
3353 n.permission = permission
3343 Session().add(n)
3354 Session().add(n)
3344 return n
3355 return n
3345
3356
3346 def __unicode__(self):
3357 def __unicode__(self):
3347 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3358 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3348
3359
3349
3360
3350 class UserGroupToPerm(Base, BaseModel):
3361 class UserGroupToPerm(Base, BaseModel):
3351 __tablename__ = 'users_group_to_perm'
3362 __tablename__ = 'users_group_to_perm'
3352 __table_args__ = (
3363 __table_args__ = (
3353 UniqueConstraint('users_group_id', 'permission_id',),
3364 UniqueConstraint('users_group_id', 'permission_id',),
3354 base_table_args
3365 base_table_args
3355 )
3366 )
3356
3367
3357 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3368 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3358 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3369 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3359 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3370 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3360
3371
3361 users_group = relationship('UserGroup')
3372 users_group = relationship('UserGroup')
3362 permission = relationship('Permission')
3373 permission = relationship('Permission')
3363
3374
3364
3375
3365 class UserRepoGroupToPerm(Base, BaseModel):
3376 class UserRepoGroupToPerm(Base, BaseModel):
3366 __tablename__ = 'user_repo_group_to_perm'
3377 __tablename__ = 'user_repo_group_to_perm'
3367 __table_args__ = (
3378 __table_args__ = (
3368 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3379 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3369 base_table_args
3380 base_table_args
3370 )
3381 )
3371
3382
3372 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3383 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3374 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3385 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3375 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3376
3387
3377 user = relationship('User')
3388 user = relationship('User')
3378 group = relationship('RepoGroup')
3389 group = relationship('RepoGroup')
3379 permission = relationship('Permission')
3390 permission = relationship('Permission')
3380
3391
3381 @classmethod
3392 @classmethod
3382 def create(cls, user, repository_group, permission):
3393 def create(cls, user, repository_group, permission):
3383 n = cls()
3394 n = cls()
3384 n.user = user
3395 n.user = user
3385 n.group = repository_group
3396 n.group = repository_group
3386 n.permission = permission
3397 n.permission = permission
3387 Session().add(n)
3398 Session().add(n)
3388 return n
3399 return n
3389
3400
3390
3401
3391 class UserGroupRepoGroupToPerm(Base, BaseModel):
3402 class UserGroupRepoGroupToPerm(Base, BaseModel):
3392 __tablename__ = 'users_group_repo_group_to_perm'
3403 __tablename__ = 'users_group_repo_group_to_perm'
3393 __table_args__ = (
3404 __table_args__ = (
3394 UniqueConstraint('users_group_id', 'group_id'),
3405 UniqueConstraint('users_group_id', 'group_id'),
3395 base_table_args
3406 base_table_args
3396 )
3407 )
3397
3408
3398 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)
3409 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)
3399 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3410 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3400 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3411 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3401 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3412 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3402
3413
3403 users_group = relationship('UserGroup')
3414 users_group = relationship('UserGroup')
3404 permission = relationship('Permission')
3415 permission = relationship('Permission')
3405 group = relationship('RepoGroup')
3416 group = relationship('RepoGroup')
3406
3417
3407 @classmethod
3418 @classmethod
3408 def create(cls, user_group, repository_group, permission):
3419 def create(cls, user_group, repository_group, permission):
3409 n = cls()
3420 n = cls()
3410 n.users_group = user_group
3421 n.users_group = user_group
3411 n.group = repository_group
3422 n.group = repository_group
3412 n.permission = permission
3423 n.permission = permission
3413 Session().add(n)
3424 Session().add(n)
3414 return n
3425 return n
3415
3426
3416 def __unicode__(self):
3427 def __unicode__(self):
3417 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3428 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3418
3429
3419
3430
3420 class Statistics(Base, BaseModel):
3431 class Statistics(Base, BaseModel):
3421 __tablename__ = 'statistics'
3432 __tablename__ = 'statistics'
3422 __table_args__ = (
3433 __table_args__ = (
3423 base_table_args
3434 base_table_args
3424 )
3435 )
3425
3436
3426 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3437 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3427 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3438 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3428 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3439 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3429 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3440 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3430 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3441 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3431 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3442 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3432
3443
3433 repository = relationship('Repository', single_parent=True)
3444 repository = relationship('Repository', single_parent=True)
3434
3445
3435
3446
3436 class UserFollowing(Base, BaseModel):
3447 class UserFollowing(Base, BaseModel):
3437 __tablename__ = 'user_followings'
3448 __tablename__ = 'user_followings'
3438 __table_args__ = (
3449 __table_args__ = (
3439 UniqueConstraint('user_id', 'follows_repository_id'),
3450 UniqueConstraint('user_id', 'follows_repository_id'),
3440 UniqueConstraint('user_id', 'follows_user_id'),
3451 UniqueConstraint('user_id', 'follows_user_id'),
3441 base_table_args
3452 base_table_args
3442 )
3453 )
3443
3454
3444 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3455 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3456 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3446 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3457 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3447 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3458 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3448 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3459 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3449
3460
3450 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3461 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3451
3462
3452 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3463 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3453 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3464 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3454
3465
3455 @classmethod
3466 @classmethod
3456 def get_repo_followers(cls, repo_id):
3467 def get_repo_followers(cls, repo_id):
3457 return cls.query().filter(cls.follows_repo_id == repo_id)
3468 return cls.query().filter(cls.follows_repo_id == repo_id)
3458
3469
3459
3470
3460 class CacheKey(Base, BaseModel):
3471 class CacheKey(Base, BaseModel):
3461 __tablename__ = 'cache_invalidation'
3472 __tablename__ = 'cache_invalidation'
3462 __table_args__ = (
3473 __table_args__ = (
3463 UniqueConstraint('cache_key'),
3474 UniqueConstraint('cache_key'),
3464 Index('key_idx', 'cache_key'),
3475 Index('key_idx', 'cache_key'),
3465 base_table_args,
3476 base_table_args,
3466 )
3477 )
3467
3478
3468 CACHE_TYPE_FEED = 'FEED'
3479 CACHE_TYPE_FEED = 'FEED'
3469 CACHE_TYPE_README = 'README'
3480 CACHE_TYPE_README = 'README'
3470 # namespaces used to register process/thread aware caches
3481 # namespaces used to register process/thread aware caches
3471 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3482 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3472 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3483 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3473
3484
3474 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3485 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3475 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3486 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3476 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3487 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3477 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3488 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3478
3489
3479 def __init__(self, cache_key, cache_args=''):
3490 def __init__(self, cache_key, cache_args=''):
3480 self.cache_key = cache_key
3491 self.cache_key = cache_key
3481 self.cache_args = cache_args
3492 self.cache_args = cache_args
3482 self.cache_active = False
3493 self.cache_active = False
3483
3494
3484 def __unicode__(self):
3495 def __unicode__(self):
3485 return u"<%s('%s:%s[%s]')>" % (
3496 return u"<%s('%s:%s[%s]')>" % (
3486 self.__class__.__name__,
3497 self.__class__.__name__,
3487 self.cache_id, self.cache_key, self.cache_active)
3498 self.cache_id, self.cache_key, self.cache_active)
3488
3499
3489 def _cache_key_partition(self):
3500 def _cache_key_partition(self):
3490 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3501 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3491 return prefix, repo_name, suffix
3502 return prefix, repo_name, suffix
3492
3503
3493 def get_prefix(self):
3504 def get_prefix(self):
3494 """
3505 """
3495 Try to extract prefix from existing cache key. The key could consist
3506 Try to extract prefix from existing cache key. The key could consist
3496 of prefix, repo_name, suffix
3507 of prefix, repo_name, suffix
3497 """
3508 """
3498 # this returns prefix, repo_name, suffix
3509 # this returns prefix, repo_name, suffix
3499 return self._cache_key_partition()[0]
3510 return self._cache_key_partition()[0]
3500
3511
3501 def get_suffix(self):
3512 def get_suffix(self):
3502 """
3513 """
3503 get suffix that might have been used in _get_cache_key to
3514 get suffix that might have been used in _get_cache_key to
3504 generate self.cache_key. Only used for informational purposes
3515 generate self.cache_key. Only used for informational purposes
3505 in repo_edit.mako.
3516 in repo_edit.mako.
3506 """
3517 """
3507 # prefix, repo_name, suffix
3518 # prefix, repo_name, suffix
3508 return self._cache_key_partition()[2]
3519 return self._cache_key_partition()[2]
3509
3520
3510 @classmethod
3521 @classmethod
3511 def delete_all_cache(cls):
3522 def delete_all_cache(cls):
3512 """
3523 """
3513 Delete all cache keys from database.
3524 Delete all cache keys from database.
3514 Should only be run when all instances are down and all entries
3525 Should only be run when all instances are down and all entries
3515 thus stale.
3526 thus stale.
3516 """
3527 """
3517 cls.query().delete()
3528 cls.query().delete()
3518 Session().commit()
3529 Session().commit()
3519
3530
3520 @classmethod
3531 @classmethod
3521 def set_invalidate(cls, cache_uid, delete=False):
3532 def set_invalidate(cls, cache_uid, delete=False):
3522 """
3533 """
3523 Mark all caches of a repo as invalid in the database.
3534 Mark all caches of a repo as invalid in the database.
3524 """
3535 """
3525
3536
3526 try:
3537 try:
3527 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3538 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3528 if delete:
3539 if delete:
3529 qry.delete()
3540 qry.delete()
3530 log.debug('cache objects deleted for cache args %s',
3541 log.debug('cache objects deleted for cache args %s',
3531 safe_str(cache_uid))
3542 safe_str(cache_uid))
3532 else:
3543 else:
3533 qry.update({"cache_active": False})
3544 qry.update({"cache_active": False})
3534 log.debug('cache objects marked as invalid for cache args %s',
3545 log.debug('cache objects marked as invalid for cache args %s',
3535 safe_str(cache_uid))
3546 safe_str(cache_uid))
3536
3547
3537 Session().commit()
3548 Session().commit()
3538 except Exception:
3549 except Exception:
3539 log.exception(
3550 log.exception(
3540 'Cache key invalidation failed for cache args %s',
3551 'Cache key invalidation failed for cache args %s',
3541 safe_str(cache_uid))
3552 safe_str(cache_uid))
3542 Session().rollback()
3553 Session().rollback()
3543
3554
3544 @classmethod
3555 @classmethod
3545 def get_active_cache(cls, cache_key):
3556 def get_active_cache(cls, cache_key):
3546 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3557 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3547 if inv_obj:
3558 if inv_obj:
3548 return inv_obj
3559 return inv_obj
3549 return None
3560 return None
3550
3561
3551
3562
3552 class ChangesetComment(Base, BaseModel):
3563 class ChangesetComment(Base, BaseModel):
3553 __tablename__ = 'changeset_comments'
3564 __tablename__ = 'changeset_comments'
3554 __table_args__ = (
3565 __table_args__ = (
3555 Index('cc_revision_idx', 'revision'),
3566 Index('cc_revision_idx', 'revision'),
3556 base_table_args,
3567 base_table_args,
3557 )
3568 )
3558
3569
3559 COMMENT_OUTDATED = u'comment_outdated'
3570 COMMENT_OUTDATED = u'comment_outdated'
3560 COMMENT_TYPE_NOTE = u'note'
3571 COMMENT_TYPE_NOTE = u'note'
3561 COMMENT_TYPE_TODO = u'todo'
3572 COMMENT_TYPE_TODO = u'todo'
3562 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3573 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3563
3574
3564 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3575 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3565 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3576 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3566 revision = Column('revision', String(40), nullable=True)
3577 revision = Column('revision', String(40), nullable=True)
3567 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3578 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3568 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3579 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3569 line_no = Column('line_no', Unicode(10), nullable=True)
3580 line_no = Column('line_no', Unicode(10), nullable=True)
3570 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3581 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3571 f_path = Column('f_path', Unicode(1000), nullable=True)
3582 f_path = Column('f_path', Unicode(1000), nullable=True)
3572 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3583 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3573 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3584 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3574 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3575 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3586 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3576 renderer = Column('renderer', Unicode(64), nullable=True)
3587 renderer = Column('renderer', Unicode(64), nullable=True)
3577 display_state = Column('display_state', Unicode(128), nullable=True)
3588 display_state = Column('display_state', Unicode(128), nullable=True)
3578
3589
3579 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3590 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3580 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3591 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3581
3592
3582 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3593 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3583 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3594 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3584
3595
3585 author = relationship('User', lazy='joined')
3596 author = relationship('User', lazy='joined')
3586 repo = relationship('Repository')
3597 repo = relationship('Repository')
3587 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3598 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3588 pull_request = relationship('PullRequest', lazy='joined')
3599 pull_request = relationship('PullRequest', lazy='joined')
3589 pull_request_version = relationship('PullRequestVersion')
3600 pull_request_version = relationship('PullRequestVersion')
3590
3601
3591 @classmethod
3602 @classmethod
3592 def get_users(cls, revision=None, pull_request_id=None):
3603 def get_users(cls, revision=None, pull_request_id=None):
3593 """
3604 """
3594 Returns user associated with this ChangesetComment. ie those
3605 Returns user associated with this ChangesetComment. ie those
3595 who actually commented
3606 who actually commented
3596
3607
3597 :param cls:
3608 :param cls:
3598 :param revision:
3609 :param revision:
3599 """
3610 """
3600 q = Session().query(User)\
3611 q = Session().query(User)\
3601 .join(ChangesetComment.author)
3612 .join(ChangesetComment.author)
3602 if revision:
3613 if revision:
3603 q = q.filter(cls.revision == revision)
3614 q = q.filter(cls.revision == revision)
3604 elif pull_request_id:
3615 elif pull_request_id:
3605 q = q.filter(cls.pull_request_id == pull_request_id)
3616 q = q.filter(cls.pull_request_id == pull_request_id)
3606 return q.all()
3617 return q.all()
3607
3618
3608 @classmethod
3619 @classmethod
3609 def get_index_from_version(cls, pr_version, versions):
3620 def get_index_from_version(cls, pr_version, versions):
3610 num_versions = [x.pull_request_version_id for x in versions]
3621 num_versions = [x.pull_request_version_id for x in versions]
3611 try:
3622 try:
3612 return num_versions.index(pr_version) +1
3623 return num_versions.index(pr_version) +1
3613 except (IndexError, ValueError):
3624 except (IndexError, ValueError):
3614 return
3625 return
3615
3626
3616 @property
3627 @property
3617 def outdated(self):
3628 def outdated(self):
3618 return self.display_state == self.COMMENT_OUTDATED
3629 return self.display_state == self.COMMENT_OUTDATED
3619
3630
3620 def outdated_at_version(self, version):
3631 def outdated_at_version(self, version):
3621 """
3632 """
3622 Checks if comment is outdated for given pull request version
3633 Checks if comment is outdated for given pull request version
3623 """
3634 """
3624 return self.outdated and self.pull_request_version_id != version
3635 return self.outdated and self.pull_request_version_id != version
3625
3636
3626 def older_than_version(self, version):
3637 def older_than_version(self, version):
3627 """
3638 """
3628 Checks if comment is made from previous version than given
3639 Checks if comment is made from previous version than given
3629 """
3640 """
3630 if version is None:
3641 if version is None:
3631 return self.pull_request_version_id is not None
3642 return self.pull_request_version_id is not None
3632
3643
3633 return self.pull_request_version_id < version
3644 return self.pull_request_version_id < version
3634
3645
3635 @property
3646 @property
3636 def resolved(self):
3647 def resolved(self):
3637 return self.resolved_by[0] if self.resolved_by else None
3648 return self.resolved_by[0] if self.resolved_by else None
3638
3649
3639 @property
3650 @property
3640 def is_todo(self):
3651 def is_todo(self):
3641 return self.comment_type == self.COMMENT_TYPE_TODO
3652 return self.comment_type == self.COMMENT_TYPE_TODO
3642
3653
3643 @property
3654 @property
3644 def is_inline(self):
3655 def is_inline(self):
3645 return self.line_no and self.f_path
3656 return self.line_no and self.f_path
3646
3657
3647 def get_index_version(self, versions):
3658 def get_index_version(self, versions):
3648 return self.get_index_from_version(
3659 return self.get_index_from_version(
3649 self.pull_request_version_id, versions)
3660 self.pull_request_version_id, versions)
3650
3661
3651 def __repr__(self):
3662 def __repr__(self):
3652 if self.comment_id:
3663 if self.comment_id:
3653 return '<DB:Comment #%s>' % self.comment_id
3664 return '<DB:Comment #%s>' % self.comment_id
3654 else:
3665 else:
3655 return '<DB:Comment at %#x>' % id(self)
3666 return '<DB:Comment at %#x>' % id(self)
3656
3667
3657 def get_api_data(self):
3668 def get_api_data(self):
3658 comment = self
3669 comment = self
3659 data = {
3670 data = {
3660 'comment_id': comment.comment_id,
3671 'comment_id': comment.comment_id,
3661 'comment_type': comment.comment_type,
3672 'comment_type': comment.comment_type,
3662 'comment_text': comment.text,
3673 'comment_text': comment.text,
3663 'comment_status': comment.status_change,
3674 'comment_status': comment.status_change,
3664 'comment_f_path': comment.f_path,
3675 'comment_f_path': comment.f_path,
3665 'comment_lineno': comment.line_no,
3676 'comment_lineno': comment.line_no,
3666 'comment_author': comment.author,
3677 'comment_author': comment.author,
3667 'comment_created_on': comment.created_on,
3678 'comment_created_on': comment.created_on,
3668 'comment_resolved_by': self.resolved
3679 'comment_resolved_by': self.resolved
3669 }
3680 }
3670 return data
3681 return data
3671
3682
3672 def __json__(self):
3683 def __json__(self):
3673 data = dict()
3684 data = dict()
3674 data.update(self.get_api_data())
3685 data.update(self.get_api_data())
3675 return data
3686 return data
3676
3687
3677
3688
3678 class ChangesetStatus(Base, BaseModel):
3689 class ChangesetStatus(Base, BaseModel):
3679 __tablename__ = 'changeset_statuses'
3690 __tablename__ = 'changeset_statuses'
3680 __table_args__ = (
3691 __table_args__ = (
3681 Index('cs_revision_idx', 'revision'),
3692 Index('cs_revision_idx', 'revision'),
3682 Index('cs_version_idx', 'version'),
3693 Index('cs_version_idx', 'version'),
3683 UniqueConstraint('repo_id', 'revision', 'version'),
3694 UniqueConstraint('repo_id', 'revision', 'version'),
3684 base_table_args
3695 base_table_args
3685 )
3696 )
3686
3697
3687 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3698 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3688 STATUS_APPROVED = 'approved'
3699 STATUS_APPROVED = 'approved'
3689 STATUS_REJECTED = 'rejected'
3700 STATUS_REJECTED = 'rejected'
3690 STATUS_UNDER_REVIEW = 'under_review'
3701 STATUS_UNDER_REVIEW = 'under_review'
3691
3702
3692 STATUSES = [
3703 STATUSES = [
3693 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3704 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3694 (STATUS_APPROVED, _("Approved")),
3705 (STATUS_APPROVED, _("Approved")),
3695 (STATUS_REJECTED, _("Rejected")),
3706 (STATUS_REJECTED, _("Rejected")),
3696 (STATUS_UNDER_REVIEW, _("Under Review")),
3707 (STATUS_UNDER_REVIEW, _("Under Review")),
3697 ]
3708 ]
3698
3709
3699 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3710 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3700 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3711 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3701 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3712 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3702 revision = Column('revision', String(40), nullable=False)
3713 revision = Column('revision', String(40), nullable=False)
3703 status = Column('status', String(128), nullable=False, default=DEFAULT)
3714 status = Column('status', String(128), nullable=False, default=DEFAULT)
3704 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3715 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3705 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3716 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3706 version = Column('version', Integer(), nullable=False, default=0)
3717 version = Column('version', Integer(), nullable=False, default=0)
3707 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3718 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3708
3719
3709 author = relationship('User', lazy='joined')
3720 author = relationship('User', lazy='joined')
3710 repo = relationship('Repository')
3721 repo = relationship('Repository')
3711 comment = relationship('ChangesetComment', lazy='joined')
3722 comment = relationship('ChangesetComment', lazy='joined')
3712 pull_request = relationship('PullRequest', lazy='joined')
3723 pull_request = relationship('PullRequest', lazy='joined')
3713
3724
3714 def __unicode__(self):
3725 def __unicode__(self):
3715 return u"<%s('%s[v%s]:%s')>" % (
3726 return u"<%s('%s[v%s]:%s')>" % (
3716 self.__class__.__name__,
3727 self.__class__.__name__,
3717 self.status, self.version, self.author
3728 self.status, self.version, self.author
3718 )
3729 )
3719
3730
3720 @classmethod
3731 @classmethod
3721 def get_status_lbl(cls, value):
3732 def get_status_lbl(cls, value):
3722 return dict(cls.STATUSES).get(value)
3733 return dict(cls.STATUSES).get(value)
3723
3734
3724 @property
3735 @property
3725 def status_lbl(self):
3736 def status_lbl(self):
3726 return ChangesetStatus.get_status_lbl(self.status)
3737 return ChangesetStatus.get_status_lbl(self.status)
3727
3738
3728 def get_api_data(self):
3739 def get_api_data(self):
3729 status = self
3740 status = self
3730 data = {
3741 data = {
3731 'status_id': status.changeset_status_id,
3742 'status_id': status.changeset_status_id,
3732 'status': status.status,
3743 'status': status.status,
3733 }
3744 }
3734 return data
3745 return data
3735
3746
3736 def __json__(self):
3747 def __json__(self):
3737 data = dict()
3748 data = dict()
3738 data.update(self.get_api_data())
3749 data.update(self.get_api_data())
3739 return data
3750 return data
3740
3751
3741
3752
3742 class _SetState(object):
3753 class _SetState(object):
3743 """
3754 """
3744 Context processor allowing changing state for sensitive operation such as
3755 Context processor allowing changing state for sensitive operation such as
3745 pull request update or merge
3756 pull request update or merge
3746 """
3757 """
3747
3758
3748 def __init__(self, pull_request, pr_state, back_state=None):
3759 def __init__(self, pull_request, pr_state, back_state=None):
3749 self._pr = pull_request
3760 self._pr = pull_request
3750 self._org_state = back_state or pull_request.pull_request_state
3761 self._org_state = back_state or pull_request.pull_request_state
3751 self._pr_state = pr_state
3762 self._pr_state = pr_state
3752
3763
3753 def __enter__(self):
3764 def __enter__(self):
3754 log.debug('StateLock: entering set state context, setting state to: `%s`',
3765 log.debug('StateLock: entering set state context, setting state to: `%s`',
3755 self._pr_state)
3766 self._pr_state)
3756 self._pr.pull_request_state = self._pr_state
3767 self._pr.pull_request_state = self._pr_state
3757 Session().add(self._pr)
3768 Session().add(self._pr)
3758 Session().commit()
3769 Session().commit()
3759
3770
3760 def __exit__(self, exc_type, exc_val, exc_tb):
3771 def __exit__(self, exc_type, exc_val, exc_tb):
3761 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3772 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3762 self._org_state)
3773 self._org_state)
3763 self._pr.pull_request_state = self._org_state
3774 self._pr.pull_request_state = self._org_state
3764 Session().add(self._pr)
3775 Session().add(self._pr)
3765 Session().commit()
3776 Session().commit()
3766
3777
3767
3778
3768 class _PullRequestBase(BaseModel):
3779 class _PullRequestBase(BaseModel):
3769 """
3780 """
3770 Common attributes of pull request and version entries.
3781 Common attributes of pull request and version entries.
3771 """
3782 """
3772
3783
3773 # .status values
3784 # .status values
3774 STATUS_NEW = u'new'
3785 STATUS_NEW = u'new'
3775 STATUS_OPEN = u'open'
3786 STATUS_OPEN = u'open'
3776 STATUS_CLOSED = u'closed'
3787 STATUS_CLOSED = u'closed'
3777
3788
3778 # available states
3789 # available states
3779 STATE_CREATING = u'creating'
3790 STATE_CREATING = u'creating'
3780 STATE_UPDATING = u'updating'
3791 STATE_UPDATING = u'updating'
3781 STATE_MERGING = u'merging'
3792 STATE_MERGING = u'merging'
3782 STATE_CREATED = u'created'
3793 STATE_CREATED = u'created'
3783
3794
3784 title = Column('title', Unicode(255), nullable=True)
3795 title = Column('title', Unicode(255), nullable=True)
3785 description = Column(
3796 description = Column(
3786 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3797 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3787 nullable=True)
3798 nullable=True)
3788 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3799 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3789
3800
3790 # new/open/closed status of pull request (not approve/reject/etc)
3801 # new/open/closed status of pull request (not approve/reject/etc)
3791 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3802 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3792 created_on = Column(
3803 created_on = Column(
3793 'created_on', DateTime(timezone=False), nullable=False,
3804 'created_on', DateTime(timezone=False), nullable=False,
3794 default=datetime.datetime.now)
3805 default=datetime.datetime.now)
3795 updated_on = Column(
3806 updated_on = Column(
3796 'updated_on', DateTime(timezone=False), nullable=False,
3807 'updated_on', DateTime(timezone=False), nullable=False,
3797 default=datetime.datetime.now)
3808 default=datetime.datetime.now)
3798
3809
3799 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3810 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3800
3811
3801 @declared_attr
3812 @declared_attr
3802 def user_id(cls):
3813 def user_id(cls):
3803 return Column(
3814 return Column(
3804 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3815 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3805 unique=None)
3816 unique=None)
3806
3817
3807 # 500 revisions max
3818 # 500 revisions max
3808 _revisions = Column(
3819 _revisions = Column(
3809 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3820 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3810
3821
3811 @declared_attr
3822 @declared_attr
3812 def source_repo_id(cls):
3823 def source_repo_id(cls):
3813 # TODO: dan: rename column to source_repo_id
3824 # TODO: dan: rename column to source_repo_id
3814 return Column(
3825 return Column(
3815 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3826 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3816 nullable=False)
3827 nullable=False)
3817
3828
3818 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3829 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3819
3830
3820 @hybrid_property
3831 @hybrid_property
3821 def source_ref(self):
3832 def source_ref(self):
3822 return self._source_ref
3833 return self._source_ref
3823
3834
3824 @source_ref.setter
3835 @source_ref.setter
3825 def source_ref(self, val):
3836 def source_ref(self, val):
3826 parts = (val or '').split(':')
3837 parts = (val or '').split(':')
3827 if len(parts) != 3:
3838 if len(parts) != 3:
3828 raise ValueError(
3839 raise ValueError(
3829 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3840 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3830 self._source_ref = safe_unicode(val)
3841 self._source_ref = safe_unicode(val)
3831
3842
3832 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3843 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3833
3844
3834 @hybrid_property
3845 @hybrid_property
3835 def target_ref(self):
3846 def target_ref(self):
3836 return self._target_ref
3847 return self._target_ref
3837
3848
3838 @target_ref.setter
3849 @target_ref.setter
3839 def target_ref(self, val):
3850 def target_ref(self, val):
3840 parts = (val or '').split(':')
3851 parts = (val or '').split(':')
3841 if len(parts) != 3:
3852 if len(parts) != 3:
3842 raise ValueError(
3853 raise ValueError(
3843 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3854 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3844 self._target_ref = safe_unicode(val)
3855 self._target_ref = safe_unicode(val)
3845
3856
3846 @declared_attr
3857 @declared_attr
3847 def target_repo_id(cls):
3858 def target_repo_id(cls):
3848 # TODO: dan: rename column to target_repo_id
3859 # TODO: dan: rename column to target_repo_id
3849 return Column(
3860 return Column(
3850 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3861 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3851 nullable=False)
3862 nullable=False)
3852
3863
3853 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3864 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3854
3865
3855 # TODO: dan: rename column to last_merge_source_rev
3866 # TODO: dan: rename column to last_merge_source_rev
3856 _last_merge_source_rev = Column(
3867 _last_merge_source_rev = Column(
3857 'last_merge_org_rev', String(40), nullable=True)
3868 'last_merge_org_rev', String(40), nullable=True)
3858 # TODO: dan: rename column to last_merge_target_rev
3869 # TODO: dan: rename column to last_merge_target_rev
3859 _last_merge_target_rev = Column(
3870 _last_merge_target_rev = Column(
3860 'last_merge_other_rev', String(40), nullable=True)
3871 'last_merge_other_rev', String(40), nullable=True)
3861 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3872 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3862 merge_rev = Column('merge_rev', String(40), nullable=True)
3873 merge_rev = Column('merge_rev', String(40), nullable=True)
3863
3874
3864 reviewer_data = Column(
3875 reviewer_data = Column(
3865 'reviewer_data_json', MutationObj.as_mutable(
3876 'reviewer_data_json', MutationObj.as_mutable(
3866 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3877 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3867
3878
3868 @property
3879 @property
3869 def reviewer_data_json(self):
3880 def reviewer_data_json(self):
3870 return json.dumps(self.reviewer_data)
3881 return json.dumps(self.reviewer_data)
3871
3882
3872 @hybrid_property
3883 @hybrid_property
3873 def description_safe(self):
3884 def description_safe(self):
3874 from rhodecode.lib import helpers as h
3885 from rhodecode.lib import helpers as h
3875 return h.escape(self.description)
3886 return h.escape(self.description)
3876
3887
3877 @hybrid_property
3888 @hybrid_property
3878 def revisions(self):
3889 def revisions(self):
3879 return self._revisions.split(':') if self._revisions else []
3890 return self._revisions.split(':') if self._revisions else []
3880
3891
3881 @revisions.setter
3892 @revisions.setter
3882 def revisions(self, val):
3893 def revisions(self, val):
3883 self._revisions = ':'.join(val)
3894 self._revisions = ':'.join(val)
3884
3895
3885 @hybrid_property
3896 @hybrid_property
3886 def last_merge_status(self):
3897 def last_merge_status(self):
3887 return safe_int(self._last_merge_status)
3898 return safe_int(self._last_merge_status)
3888
3899
3889 @last_merge_status.setter
3900 @last_merge_status.setter
3890 def last_merge_status(self, val):
3901 def last_merge_status(self, val):
3891 self._last_merge_status = val
3902 self._last_merge_status = val
3892
3903
3893 @declared_attr
3904 @declared_attr
3894 def author(cls):
3905 def author(cls):
3895 return relationship('User', lazy='joined')
3906 return relationship('User', lazy='joined')
3896
3907
3897 @declared_attr
3908 @declared_attr
3898 def source_repo(cls):
3909 def source_repo(cls):
3899 return relationship(
3910 return relationship(
3900 'Repository',
3911 'Repository',
3901 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3912 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3902
3913
3903 @property
3914 @property
3904 def source_ref_parts(self):
3915 def source_ref_parts(self):
3905 return self.unicode_to_reference(self.source_ref)
3916 return self.unicode_to_reference(self.source_ref)
3906
3917
3907 @declared_attr
3918 @declared_attr
3908 def target_repo(cls):
3919 def target_repo(cls):
3909 return relationship(
3920 return relationship(
3910 'Repository',
3921 'Repository',
3911 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3922 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3912
3923
3913 @property
3924 @property
3914 def target_ref_parts(self):
3925 def target_ref_parts(self):
3915 return self.unicode_to_reference(self.target_ref)
3926 return self.unicode_to_reference(self.target_ref)
3916
3927
3917 @property
3928 @property
3918 def shadow_merge_ref(self):
3929 def shadow_merge_ref(self):
3919 return self.unicode_to_reference(self._shadow_merge_ref)
3930 return self.unicode_to_reference(self._shadow_merge_ref)
3920
3931
3921 @shadow_merge_ref.setter
3932 @shadow_merge_ref.setter
3922 def shadow_merge_ref(self, ref):
3933 def shadow_merge_ref(self, ref):
3923 self._shadow_merge_ref = self.reference_to_unicode(ref)
3934 self._shadow_merge_ref = self.reference_to_unicode(ref)
3924
3935
3925 @staticmethod
3936 @staticmethod
3926 def unicode_to_reference(raw):
3937 def unicode_to_reference(raw):
3927 """
3938 """
3928 Convert a unicode (or string) to a reference object.
3939 Convert a unicode (or string) to a reference object.
3929 If unicode evaluates to False it returns None.
3940 If unicode evaluates to False it returns None.
3930 """
3941 """
3931 if raw:
3942 if raw:
3932 refs = raw.split(':')
3943 refs = raw.split(':')
3933 return Reference(*refs)
3944 return Reference(*refs)
3934 else:
3945 else:
3935 return None
3946 return None
3936
3947
3937 @staticmethod
3948 @staticmethod
3938 def reference_to_unicode(ref):
3949 def reference_to_unicode(ref):
3939 """
3950 """
3940 Convert a reference object to unicode.
3951 Convert a reference object to unicode.
3941 If reference is None it returns None.
3952 If reference is None it returns None.
3942 """
3953 """
3943 if ref:
3954 if ref:
3944 return u':'.join(ref)
3955 return u':'.join(ref)
3945 else:
3956 else:
3946 return None
3957 return None
3947
3958
3948 def get_api_data(self, with_merge_state=True):
3959 def get_api_data(self, with_merge_state=True):
3949 from rhodecode.model.pull_request import PullRequestModel
3960 from rhodecode.model.pull_request import PullRequestModel
3950
3961
3951 pull_request = self
3962 pull_request = self
3952 if with_merge_state:
3963 if with_merge_state:
3953 merge_status = PullRequestModel().merge_status(pull_request)
3964 merge_status = PullRequestModel().merge_status(pull_request)
3954 merge_state = {
3965 merge_state = {
3955 'status': merge_status[0],
3966 'status': merge_status[0],
3956 'message': safe_unicode(merge_status[1]),
3967 'message': safe_unicode(merge_status[1]),
3957 }
3968 }
3958 else:
3969 else:
3959 merge_state = {'status': 'not_available',
3970 merge_state = {'status': 'not_available',
3960 'message': 'not_available'}
3971 'message': 'not_available'}
3961
3972
3962 merge_data = {
3973 merge_data = {
3963 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3974 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3964 'reference': (
3975 'reference': (
3965 pull_request.shadow_merge_ref._asdict()
3976 pull_request.shadow_merge_ref._asdict()
3966 if pull_request.shadow_merge_ref else None),
3977 if pull_request.shadow_merge_ref else None),
3967 }
3978 }
3968
3979
3969 data = {
3980 data = {
3970 'pull_request_id': pull_request.pull_request_id,
3981 'pull_request_id': pull_request.pull_request_id,
3971 'url': PullRequestModel().get_url(pull_request),
3982 'url': PullRequestModel().get_url(pull_request),
3972 'title': pull_request.title,
3983 'title': pull_request.title,
3973 'description': pull_request.description,
3984 'description': pull_request.description,
3974 'status': pull_request.status,
3985 'status': pull_request.status,
3975 'state': pull_request.pull_request_state,
3986 'state': pull_request.pull_request_state,
3976 'created_on': pull_request.created_on,
3987 'created_on': pull_request.created_on,
3977 'updated_on': pull_request.updated_on,
3988 'updated_on': pull_request.updated_on,
3978 'commit_ids': pull_request.revisions,
3989 'commit_ids': pull_request.revisions,
3979 'review_status': pull_request.calculated_review_status(),
3990 'review_status': pull_request.calculated_review_status(),
3980 'mergeable': merge_state,
3991 'mergeable': merge_state,
3981 'source': {
3992 'source': {
3982 'clone_url': pull_request.source_repo.clone_url(),
3993 'clone_url': pull_request.source_repo.clone_url(),
3983 'repository': pull_request.source_repo.repo_name,
3994 'repository': pull_request.source_repo.repo_name,
3984 'reference': {
3995 'reference': {
3985 'name': pull_request.source_ref_parts.name,
3996 'name': pull_request.source_ref_parts.name,
3986 'type': pull_request.source_ref_parts.type,
3997 'type': pull_request.source_ref_parts.type,
3987 'commit_id': pull_request.source_ref_parts.commit_id,
3998 'commit_id': pull_request.source_ref_parts.commit_id,
3988 },
3999 },
3989 },
4000 },
3990 'target': {
4001 'target': {
3991 'clone_url': pull_request.target_repo.clone_url(),
4002 'clone_url': pull_request.target_repo.clone_url(),
3992 'repository': pull_request.target_repo.repo_name,
4003 'repository': pull_request.target_repo.repo_name,
3993 'reference': {
4004 'reference': {
3994 'name': pull_request.target_ref_parts.name,
4005 'name': pull_request.target_ref_parts.name,
3995 'type': pull_request.target_ref_parts.type,
4006 'type': pull_request.target_ref_parts.type,
3996 'commit_id': pull_request.target_ref_parts.commit_id,
4007 'commit_id': pull_request.target_ref_parts.commit_id,
3997 },
4008 },
3998 },
4009 },
3999 'merge': merge_data,
4010 'merge': merge_data,
4000 'author': pull_request.author.get_api_data(include_secrets=False,
4011 'author': pull_request.author.get_api_data(include_secrets=False,
4001 details='basic'),
4012 details='basic'),
4002 'reviewers': [
4013 'reviewers': [
4003 {
4014 {
4004 'user': reviewer.get_api_data(include_secrets=False,
4015 'user': reviewer.get_api_data(include_secrets=False,
4005 details='basic'),
4016 details='basic'),
4006 'reasons': reasons,
4017 'reasons': reasons,
4007 'review_status': st[0][1].status if st else 'not_reviewed',
4018 'review_status': st[0][1].status if st else 'not_reviewed',
4008 }
4019 }
4009 for obj, reviewer, reasons, mandatory, st in
4020 for obj, reviewer, reasons, mandatory, st in
4010 pull_request.reviewers_statuses()
4021 pull_request.reviewers_statuses()
4011 ]
4022 ]
4012 }
4023 }
4013
4024
4014 return data
4025 return data
4015
4026
4016 def set_state(self, pull_request_state, final_state=None):
4027 def set_state(self, pull_request_state, final_state=None):
4017 """
4028 """
4018 # goes from initial state to updating to initial state.
4029 # goes from initial state to updating to initial state.
4019 # initial state can be changed by specifying back_state=
4030 # initial state can be changed by specifying back_state=
4020 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4031 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4021 pull_request.merge()
4032 pull_request.merge()
4022
4033
4023 :param pull_request_state:
4034 :param pull_request_state:
4024 :param final_state:
4035 :param final_state:
4025
4036
4026 """
4037 """
4027
4038
4028 return _SetState(self, pull_request_state, back_state=final_state)
4039 return _SetState(self, pull_request_state, back_state=final_state)
4029
4040
4030
4041
4031 class PullRequest(Base, _PullRequestBase):
4042 class PullRequest(Base, _PullRequestBase):
4032 __tablename__ = 'pull_requests'
4043 __tablename__ = 'pull_requests'
4033 __table_args__ = (
4044 __table_args__ = (
4034 base_table_args,
4045 base_table_args,
4035 )
4046 )
4036
4047
4037 pull_request_id = Column(
4048 pull_request_id = Column(
4038 'pull_request_id', Integer(), nullable=False, primary_key=True)
4049 'pull_request_id', Integer(), nullable=False, primary_key=True)
4039
4050
4040 def __repr__(self):
4051 def __repr__(self):
4041 if self.pull_request_id:
4052 if self.pull_request_id:
4042 return '<DB:PullRequest #%s>' % self.pull_request_id
4053 return '<DB:PullRequest #%s>' % self.pull_request_id
4043 else:
4054 else:
4044 return '<DB:PullRequest at %#x>' % id(self)
4055 return '<DB:PullRequest at %#x>' % id(self)
4045
4056
4046 reviewers = relationship('PullRequestReviewers',
4057 reviewers = relationship('PullRequestReviewers',
4047 cascade="all, delete, delete-orphan")
4058 cascade="all, delete, delete-orphan")
4048 statuses = relationship('ChangesetStatus',
4059 statuses = relationship('ChangesetStatus',
4049 cascade="all, delete, delete-orphan")
4060 cascade="all, delete, delete-orphan")
4050 comments = relationship('ChangesetComment',
4061 comments = relationship('ChangesetComment',
4051 cascade="all, delete, delete-orphan")
4062 cascade="all, delete, delete-orphan")
4052 versions = relationship('PullRequestVersion',
4063 versions = relationship('PullRequestVersion',
4053 cascade="all, delete, delete-orphan",
4064 cascade="all, delete, delete-orphan",
4054 lazy='dynamic')
4065 lazy='dynamic')
4055
4066
4056 @classmethod
4067 @classmethod
4057 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4068 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4058 internal_methods=None):
4069 internal_methods=None):
4059
4070
4060 class PullRequestDisplay(object):
4071 class PullRequestDisplay(object):
4061 """
4072 """
4062 Special object wrapper for showing PullRequest data via Versions
4073 Special object wrapper for showing PullRequest data via Versions
4063 It mimics PR object as close as possible. This is read only object
4074 It mimics PR object as close as possible. This is read only object
4064 just for display
4075 just for display
4065 """
4076 """
4066
4077
4067 def __init__(self, attrs, internal=None):
4078 def __init__(self, attrs, internal=None):
4068 self.attrs = attrs
4079 self.attrs = attrs
4069 # internal have priority over the given ones via attrs
4080 # internal have priority over the given ones via attrs
4070 self.internal = internal or ['versions']
4081 self.internal = internal or ['versions']
4071
4082
4072 def __getattr__(self, item):
4083 def __getattr__(self, item):
4073 if item in self.internal:
4084 if item in self.internal:
4074 return getattr(self, item)
4085 return getattr(self, item)
4075 try:
4086 try:
4076 return self.attrs[item]
4087 return self.attrs[item]
4077 except KeyError:
4088 except KeyError:
4078 raise AttributeError(
4089 raise AttributeError(
4079 '%s object has no attribute %s' % (self, item))
4090 '%s object has no attribute %s' % (self, item))
4080
4091
4081 def __repr__(self):
4092 def __repr__(self):
4082 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4093 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4083
4094
4084 def versions(self):
4095 def versions(self):
4085 return pull_request_obj.versions.order_by(
4096 return pull_request_obj.versions.order_by(
4086 PullRequestVersion.pull_request_version_id).all()
4097 PullRequestVersion.pull_request_version_id).all()
4087
4098
4088 def is_closed(self):
4099 def is_closed(self):
4089 return pull_request_obj.is_closed()
4100 return pull_request_obj.is_closed()
4090
4101
4091 @property
4102 @property
4092 def pull_request_version_id(self):
4103 def pull_request_version_id(self):
4093 return getattr(pull_request_obj, 'pull_request_version_id', None)
4104 return getattr(pull_request_obj, 'pull_request_version_id', None)
4094
4105
4095 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
4106 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
4096
4107
4097 attrs.author = StrictAttributeDict(
4108 attrs.author = StrictAttributeDict(
4098 pull_request_obj.author.get_api_data())
4109 pull_request_obj.author.get_api_data())
4099 if pull_request_obj.target_repo:
4110 if pull_request_obj.target_repo:
4100 attrs.target_repo = StrictAttributeDict(
4111 attrs.target_repo = StrictAttributeDict(
4101 pull_request_obj.target_repo.get_api_data())
4112 pull_request_obj.target_repo.get_api_data())
4102 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4113 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4103
4114
4104 if pull_request_obj.source_repo:
4115 if pull_request_obj.source_repo:
4105 attrs.source_repo = StrictAttributeDict(
4116 attrs.source_repo = StrictAttributeDict(
4106 pull_request_obj.source_repo.get_api_data())
4117 pull_request_obj.source_repo.get_api_data())
4107 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4118 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4108
4119
4109 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4120 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4110 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4121 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4111 attrs.revisions = pull_request_obj.revisions
4122 attrs.revisions = pull_request_obj.revisions
4112
4123
4113 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4124 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4114 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4125 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4115 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4126 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4116
4127
4117 return PullRequestDisplay(attrs, internal=internal_methods)
4128 return PullRequestDisplay(attrs, internal=internal_methods)
4118
4129
4119 def is_closed(self):
4130 def is_closed(self):
4120 return self.status == self.STATUS_CLOSED
4131 return self.status == self.STATUS_CLOSED
4121
4132
4122 def __json__(self):
4133 def __json__(self):
4123 return {
4134 return {
4124 'revisions': self.revisions,
4135 'revisions': self.revisions,
4125 }
4136 }
4126
4137
4127 def calculated_review_status(self):
4138 def calculated_review_status(self):
4128 from rhodecode.model.changeset_status import ChangesetStatusModel
4139 from rhodecode.model.changeset_status import ChangesetStatusModel
4129 return ChangesetStatusModel().calculated_review_status(self)
4140 return ChangesetStatusModel().calculated_review_status(self)
4130
4141
4131 def reviewers_statuses(self):
4142 def reviewers_statuses(self):
4132 from rhodecode.model.changeset_status import ChangesetStatusModel
4143 from rhodecode.model.changeset_status import ChangesetStatusModel
4133 return ChangesetStatusModel().reviewers_statuses(self)
4144 return ChangesetStatusModel().reviewers_statuses(self)
4134
4145
4135 @property
4146 @property
4136 def workspace_id(self):
4147 def workspace_id(self):
4137 from rhodecode.model.pull_request import PullRequestModel
4148 from rhodecode.model.pull_request import PullRequestModel
4138 return PullRequestModel()._workspace_id(self)
4149 return PullRequestModel()._workspace_id(self)
4139
4150
4140 def get_shadow_repo(self):
4151 def get_shadow_repo(self):
4141 workspace_id = self.workspace_id
4152 workspace_id = self.workspace_id
4142 vcs_obj = self.target_repo.scm_instance()
4153 vcs_obj = self.target_repo.scm_instance()
4143 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4154 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4144 self.target_repo.repo_id, workspace_id)
4155 self.target_repo.repo_id, workspace_id)
4145 if os.path.isdir(shadow_repository_path):
4156 if os.path.isdir(shadow_repository_path):
4146 return vcs_obj._get_shadow_instance(shadow_repository_path)
4157 return vcs_obj._get_shadow_instance(shadow_repository_path)
4147
4158
4148
4159
4149 class PullRequestVersion(Base, _PullRequestBase):
4160 class PullRequestVersion(Base, _PullRequestBase):
4150 __tablename__ = 'pull_request_versions'
4161 __tablename__ = 'pull_request_versions'
4151 __table_args__ = (
4162 __table_args__ = (
4152 base_table_args,
4163 base_table_args,
4153 )
4164 )
4154
4165
4155 pull_request_version_id = Column(
4166 pull_request_version_id = Column(
4156 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4167 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4157 pull_request_id = Column(
4168 pull_request_id = Column(
4158 'pull_request_id', Integer(),
4169 'pull_request_id', Integer(),
4159 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4170 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4160 pull_request = relationship('PullRequest')
4171 pull_request = relationship('PullRequest')
4161
4172
4162 def __repr__(self):
4173 def __repr__(self):
4163 if self.pull_request_version_id:
4174 if self.pull_request_version_id:
4164 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4175 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4165 else:
4176 else:
4166 return '<DB:PullRequestVersion at %#x>' % id(self)
4177 return '<DB:PullRequestVersion at %#x>' % id(self)
4167
4178
4168 @property
4179 @property
4169 def reviewers(self):
4180 def reviewers(self):
4170 return self.pull_request.reviewers
4181 return self.pull_request.reviewers
4171
4182
4172 @property
4183 @property
4173 def versions(self):
4184 def versions(self):
4174 return self.pull_request.versions
4185 return self.pull_request.versions
4175
4186
4176 def is_closed(self):
4187 def is_closed(self):
4177 # calculate from original
4188 # calculate from original
4178 return self.pull_request.status == self.STATUS_CLOSED
4189 return self.pull_request.status == self.STATUS_CLOSED
4179
4190
4180 def calculated_review_status(self):
4191 def calculated_review_status(self):
4181 return self.pull_request.calculated_review_status()
4192 return self.pull_request.calculated_review_status()
4182
4193
4183 def reviewers_statuses(self):
4194 def reviewers_statuses(self):
4184 return self.pull_request.reviewers_statuses()
4195 return self.pull_request.reviewers_statuses()
4185
4196
4186
4197
4187 class PullRequestReviewers(Base, BaseModel):
4198 class PullRequestReviewers(Base, BaseModel):
4188 __tablename__ = 'pull_request_reviewers'
4199 __tablename__ = 'pull_request_reviewers'
4189 __table_args__ = (
4200 __table_args__ = (
4190 base_table_args,
4201 base_table_args,
4191 )
4202 )
4192
4203
4193 @hybrid_property
4204 @hybrid_property
4194 def reasons(self):
4205 def reasons(self):
4195 if not self._reasons:
4206 if not self._reasons:
4196 return []
4207 return []
4197 return self._reasons
4208 return self._reasons
4198
4209
4199 @reasons.setter
4210 @reasons.setter
4200 def reasons(self, val):
4211 def reasons(self, val):
4201 val = val or []
4212 val = val or []
4202 if any(not isinstance(x, compat.string_types) for x in val):
4213 if any(not isinstance(x, compat.string_types) for x in val):
4203 raise Exception('invalid reasons type, must be list of strings')
4214 raise Exception('invalid reasons type, must be list of strings')
4204 self._reasons = val
4215 self._reasons = val
4205
4216
4206 pull_requests_reviewers_id = Column(
4217 pull_requests_reviewers_id = Column(
4207 'pull_requests_reviewers_id', Integer(), nullable=False,
4218 'pull_requests_reviewers_id', Integer(), nullable=False,
4208 primary_key=True)
4219 primary_key=True)
4209 pull_request_id = Column(
4220 pull_request_id = Column(
4210 "pull_request_id", Integer(),
4221 "pull_request_id", Integer(),
4211 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4222 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4212 user_id = Column(
4223 user_id = Column(
4213 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4224 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4214 _reasons = Column(
4225 _reasons = Column(
4215 'reason', MutationList.as_mutable(
4226 'reason', MutationList.as_mutable(
4216 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4227 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4217
4228
4218 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4229 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4219 user = relationship('User')
4230 user = relationship('User')
4220 pull_request = relationship('PullRequest')
4231 pull_request = relationship('PullRequest')
4221
4232
4222 rule_data = Column(
4233 rule_data = Column(
4223 'rule_data_json',
4234 'rule_data_json',
4224 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4235 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4225
4236
4226 def rule_user_group_data(self):
4237 def rule_user_group_data(self):
4227 """
4238 """
4228 Returns the voting user group rule data for this reviewer
4239 Returns the voting user group rule data for this reviewer
4229 """
4240 """
4230
4241
4231 if self.rule_data and 'vote_rule' in self.rule_data:
4242 if self.rule_data and 'vote_rule' in self.rule_data:
4232 user_group_data = {}
4243 user_group_data = {}
4233 if 'rule_user_group_entry_id' in self.rule_data:
4244 if 'rule_user_group_entry_id' in self.rule_data:
4234 # means a group with voting rules !
4245 # means a group with voting rules !
4235 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4246 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4236 user_group_data['name'] = self.rule_data['rule_name']
4247 user_group_data['name'] = self.rule_data['rule_name']
4237 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4248 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4238
4249
4239 return user_group_data
4250 return user_group_data
4240
4251
4241 def __unicode__(self):
4252 def __unicode__(self):
4242 return u"<%s('id:%s')>" % (self.__class__.__name__,
4253 return u"<%s('id:%s')>" % (self.__class__.__name__,
4243 self.pull_requests_reviewers_id)
4254 self.pull_requests_reviewers_id)
4244
4255
4245
4256
4246 class Notification(Base, BaseModel):
4257 class Notification(Base, BaseModel):
4247 __tablename__ = 'notifications'
4258 __tablename__ = 'notifications'
4248 __table_args__ = (
4259 __table_args__ = (
4249 Index('notification_type_idx', 'type'),
4260 Index('notification_type_idx', 'type'),
4250 base_table_args,
4261 base_table_args,
4251 )
4262 )
4252
4263
4253 TYPE_CHANGESET_COMMENT = u'cs_comment'
4264 TYPE_CHANGESET_COMMENT = u'cs_comment'
4254 TYPE_MESSAGE = u'message'
4265 TYPE_MESSAGE = u'message'
4255 TYPE_MENTION = u'mention'
4266 TYPE_MENTION = u'mention'
4256 TYPE_REGISTRATION = u'registration'
4267 TYPE_REGISTRATION = u'registration'
4257 TYPE_PULL_REQUEST = u'pull_request'
4268 TYPE_PULL_REQUEST = u'pull_request'
4258 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4269 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4259
4270
4260 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4271 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4261 subject = Column('subject', Unicode(512), nullable=True)
4272 subject = Column('subject', Unicode(512), nullable=True)
4262 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4273 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4263 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4274 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4275 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4265 type_ = Column('type', Unicode(255))
4276 type_ = Column('type', Unicode(255))
4266
4277
4267 created_by_user = relationship('User')
4278 created_by_user = relationship('User')
4268 notifications_to_users = relationship('UserNotification', lazy='joined',
4279 notifications_to_users = relationship('UserNotification', lazy='joined',
4269 cascade="all, delete, delete-orphan")
4280 cascade="all, delete, delete-orphan")
4270
4281
4271 @property
4282 @property
4272 def recipients(self):
4283 def recipients(self):
4273 return [x.user for x in UserNotification.query()\
4284 return [x.user for x in UserNotification.query()\
4274 .filter(UserNotification.notification == self)\
4285 .filter(UserNotification.notification == self)\
4275 .order_by(UserNotification.user_id.asc()).all()]
4286 .order_by(UserNotification.user_id.asc()).all()]
4276
4287
4277 @classmethod
4288 @classmethod
4278 def create(cls, created_by, subject, body, recipients, type_=None):
4289 def create(cls, created_by, subject, body, recipients, type_=None):
4279 if type_ is None:
4290 if type_ is None:
4280 type_ = Notification.TYPE_MESSAGE
4291 type_ = Notification.TYPE_MESSAGE
4281
4292
4282 notification = cls()
4293 notification = cls()
4283 notification.created_by_user = created_by
4294 notification.created_by_user = created_by
4284 notification.subject = subject
4295 notification.subject = subject
4285 notification.body = body
4296 notification.body = body
4286 notification.type_ = type_
4297 notification.type_ = type_
4287 notification.created_on = datetime.datetime.now()
4298 notification.created_on = datetime.datetime.now()
4288
4299
4289 # For each recipient link the created notification to his account
4300 # For each recipient link the created notification to his account
4290 for u in recipients:
4301 for u in recipients:
4291 assoc = UserNotification()
4302 assoc = UserNotification()
4292 assoc.user_id = u.user_id
4303 assoc.user_id = u.user_id
4293 assoc.notification = notification
4304 assoc.notification = notification
4294
4305
4295 # if created_by is inside recipients mark his notification
4306 # if created_by is inside recipients mark his notification
4296 # as read
4307 # as read
4297 if u.user_id == created_by.user_id:
4308 if u.user_id == created_by.user_id:
4298 assoc.read = True
4309 assoc.read = True
4299 Session().add(assoc)
4310 Session().add(assoc)
4300
4311
4301 Session().add(notification)
4312 Session().add(notification)
4302
4313
4303 return notification
4314 return notification
4304
4315
4305
4316
4306 class UserNotification(Base, BaseModel):
4317 class UserNotification(Base, BaseModel):
4307 __tablename__ = 'user_to_notification'
4318 __tablename__ = 'user_to_notification'
4308 __table_args__ = (
4319 __table_args__ = (
4309 UniqueConstraint('user_id', 'notification_id'),
4320 UniqueConstraint('user_id', 'notification_id'),
4310 base_table_args
4321 base_table_args
4311 )
4322 )
4312
4323
4313 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4324 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4314 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4325 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4315 read = Column('read', Boolean, default=False)
4326 read = Column('read', Boolean, default=False)
4316 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4327 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4317
4328
4318 user = relationship('User', lazy="joined")
4329 user = relationship('User', lazy="joined")
4319 notification = relationship('Notification', lazy="joined",
4330 notification = relationship('Notification', lazy="joined",
4320 order_by=lambda: Notification.created_on.desc(),)
4331 order_by=lambda: Notification.created_on.desc(),)
4321
4332
4322 def mark_as_read(self):
4333 def mark_as_read(self):
4323 self.read = True
4334 self.read = True
4324 Session().add(self)
4335 Session().add(self)
4325
4336
4326
4337
4327 class Gist(Base, BaseModel):
4338 class Gist(Base, BaseModel):
4328 __tablename__ = 'gists'
4339 __tablename__ = 'gists'
4329 __table_args__ = (
4340 __table_args__ = (
4330 Index('g_gist_access_id_idx', 'gist_access_id'),
4341 Index('g_gist_access_id_idx', 'gist_access_id'),
4331 Index('g_created_on_idx', 'created_on'),
4342 Index('g_created_on_idx', 'created_on'),
4332 base_table_args
4343 base_table_args
4333 )
4344 )
4334
4345
4335 GIST_PUBLIC = u'public'
4346 GIST_PUBLIC = u'public'
4336 GIST_PRIVATE = u'private'
4347 GIST_PRIVATE = u'private'
4337 DEFAULT_FILENAME = u'gistfile1.txt'
4348 DEFAULT_FILENAME = u'gistfile1.txt'
4338
4349
4339 ACL_LEVEL_PUBLIC = u'acl_public'
4350 ACL_LEVEL_PUBLIC = u'acl_public'
4340 ACL_LEVEL_PRIVATE = u'acl_private'
4351 ACL_LEVEL_PRIVATE = u'acl_private'
4341
4352
4342 gist_id = Column('gist_id', Integer(), primary_key=True)
4353 gist_id = Column('gist_id', Integer(), primary_key=True)
4343 gist_access_id = Column('gist_access_id', Unicode(250))
4354 gist_access_id = Column('gist_access_id', Unicode(250))
4344 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4355 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4345 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4356 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4346 gist_expires = Column('gist_expires', Float(53), nullable=False)
4357 gist_expires = Column('gist_expires', Float(53), nullable=False)
4347 gist_type = Column('gist_type', Unicode(128), nullable=False)
4358 gist_type = Column('gist_type', Unicode(128), nullable=False)
4348 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4349 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4360 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4350 acl_level = Column('acl_level', Unicode(128), nullable=True)
4361 acl_level = Column('acl_level', Unicode(128), nullable=True)
4351
4362
4352 owner = relationship('User')
4363 owner = relationship('User')
4353
4364
4354 def __repr__(self):
4365 def __repr__(self):
4355 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4366 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4356
4367
4357 @hybrid_property
4368 @hybrid_property
4358 def description_safe(self):
4369 def description_safe(self):
4359 from rhodecode.lib import helpers as h
4370 from rhodecode.lib import helpers as h
4360 return h.escape(self.gist_description)
4371 return h.escape(self.gist_description)
4361
4372
4362 @classmethod
4373 @classmethod
4363 def get_or_404(cls, id_):
4374 def get_or_404(cls, id_):
4364 from pyramid.httpexceptions import HTTPNotFound
4375 from pyramid.httpexceptions import HTTPNotFound
4365
4376
4366 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4377 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4367 if not res:
4378 if not res:
4368 raise HTTPNotFound()
4379 raise HTTPNotFound()
4369 return res
4380 return res
4370
4381
4371 @classmethod
4382 @classmethod
4372 def get_by_access_id(cls, gist_access_id):
4383 def get_by_access_id(cls, gist_access_id):
4373 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4384 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4374
4385
4375 def gist_url(self):
4386 def gist_url(self):
4376 from rhodecode.model.gist import GistModel
4387 from rhodecode.model.gist import GistModel
4377 return GistModel().get_url(self)
4388 return GistModel().get_url(self)
4378
4389
4379 @classmethod
4390 @classmethod
4380 def base_path(cls):
4391 def base_path(cls):
4381 """
4392 """
4382 Returns base path when all gists are stored
4393 Returns base path when all gists are stored
4383
4394
4384 :param cls:
4395 :param cls:
4385 """
4396 """
4386 from rhodecode.model.gist import GIST_STORE_LOC
4397 from rhodecode.model.gist import GIST_STORE_LOC
4387 q = Session().query(RhodeCodeUi)\
4398 q = Session().query(RhodeCodeUi)\
4388 .filter(RhodeCodeUi.ui_key == URL_SEP)
4399 .filter(RhodeCodeUi.ui_key == URL_SEP)
4389 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4400 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4390 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4401 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4391
4402
4392 def get_api_data(self):
4403 def get_api_data(self):
4393 """
4404 """
4394 Common function for generating gist related data for API
4405 Common function for generating gist related data for API
4395 """
4406 """
4396 gist = self
4407 gist = self
4397 data = {
4408 data = {
4398 'gist_id': gist.gist_id,
4409 'gist_id': gist.gist_id,
4399 'type': gist.gist_type,
4410 'type': gist.gist_type,
4400 'access_id': gist.gist_access_id,
4411 'access_id': gist.gist_access_id,
4401 'description': gist.gist_description,
4412 'description': gist.gist_description,
4402 'url': gist.gist_url(),
4413 'url': gist.gist_url(),
4403 'expires': gist.gist_expires,
4414 'expires': gist.gist_expires,
4404 'created_on': gist.created_on,
4415 'created_on': gist.created_on,
4405 'modified_at': gist.modified_at,
4416 'modified_at': gist.modified_at,
4406 'content': None,
4417 'content': None,
4407 'acl_level': gist.acl_level,
4418 'acl_level': gist.acl_level,
4408 }
4419 }
4409 return data
4420 return data
4410
4421
4411 def __json__(self):
4422 def __json__(self):
4412 data = dict(
4423 data = dict(
4413 )
4424 )
4414 data.update(self.get_api_data())
4425 data.update(self.get_api_data())
4415 return data
4426 return data
4416 # SCM functions
4427 # SCM functions
4417
4428
4418 def scm_instance(self, **kwargs):
4429 def scm_instance(self, **kwargs):
4419 """
4430 """
4420 Get explicit Mercurial repository used
4431 Get explicit Mercurial repository used
4421 :param kwargs:
4432 :param kwargs:
4422 :return:
4433 :return:
4423 """
4434 """
4424 from rhodecode.model.gist import GistModel
4435 from rhodecode.model.gist import GistModel
4425 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4436 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4426 return get_vcs_instance(
4437 return get_vcs_instance(
4427 repo_path=safe_str(full_repo_path), create=False,
4438 repo_path=safe_str(full_repo_path), create=False,
4428 _vcs_alias=GistModel.vcs_backend)
4439 _vcs_alias=GistModel.vcs_backend)
4429
4440
4430
4441
4431 class ExternalIdentity(Base, BaseModel):
4442 class ExternalIdentity(Base, BaseModel):
4432 __tablename__ = 'external_identities'
4443 __tablename__ = 'external_identities'
4433 __table_args__ = (
4444 __table_args__ = (
4434 Index('local_user_id_idx', 'local_user_id'),
4445 Index('local_user_id_idx', 'local_user_id'),
4435 Index('external_id_idx', 'external_id'),
4446 Index('external_id_idx', 'external_id'),
4436 base_table_args
4447 base_table_args
4437 )
4448 )
4438
4449
4439 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4450 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4440 external_username = Column('external_username', Unicode(1024), default=u'')
4451 external_username = Column('external_username', Unicode(1024), default=u'')
4441 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4452 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4442 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4453 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4443 access_token = Column('access_token', String(1024), default=u'')
4454 access_token = Column('access_token', String(1024), default=u'')
4444 alt_token = Column('alt_token', String(1024), default=u'')
4455 alt_token = Column('alt_token', String(1024), default=u'')
4445 token_secret = Column('token_secret', String(1024), default=u'')
4456 token_secret = Column('token_secret', String(1024), default=u'')
4446
4457
4447 @classmethod
4458 @classmethod
4448 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4459 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4449 """
4460 """
4450 Returns ExternalIdentity instance based on search params
4461 Returns ExternalIdentity instance based on search params
4451
4462
4452 :param external_id:
4463 :param external_id:
4453 :param provider_name:
4464 :param provider_name:
4454 :return: ExternalIdentity
4465 :return: ExternalIdentity
4455 """
4466 """
4456 query = cls.query()
4467 query = cls.query()
4457 query = query.filter(cls.external_id == external_id)
4468 query = query.filter(cls.external_id == external_id)
4458 query = query.filter(cls.provider_name == provider_name)
4469 query = query.filter(cls.provider_name == provider_name)
4459 if local_user_id:
4470 if local_user_id:
4460 query = query.filter(cls.local_user_id == local_user_id)
4471 query = query.filter(cls.local_user_id == local_user_id)
4461 return query.first()
4472 return query.first()
4462
4473
4463 @classmethod
4474 @classmethod
4464 def user_by_external_id_and_provider(cls, external_id, provider_name):
4475 def user_by_external_id_and_provider(cls, external_id, provider_name):
4465 """
4476 """
4466 Returns User instance based on search params
4477 Returns User instance based on search params
4467
4478
4468 :param external_id:
4479 :param external_id:
4469 :param provider_name:
4480 :param provider_name:
4470 :return: User
4481 :return: User
4471 """
4482 """
4472 query = User.query()
4483 query = User.query()
4473 query = query.filter(cls.external_id == external_id)
4484 query = query.filter(cls.external_id == external_id)
4474 query = query.filter(cls.provider_name == provider_name)
4485 query = query.filter(cls.provider_name == provider_name)
4475 query = query.filter(User.user_id == cls.local_user_id)
4486 query = query.filter(User.user_id == cls.local_user_id)
4476 return query.first()
4487 return query.first()
4477
4488
4478 @classmethod
4489 @classmethod
4479 def by_local_user_id(cls, local_user_id):
4490 def by_local_user_id(cls, local_user_id):
4480 """
4491 """
4481 Returns all tokens for user
4492 Returns all tokens for user
4482
4493
4483 :param local_user_id:
4494 :param local_user_id:
4484 :return: ExternalIdentity
4495 :return: ExternalIdentity
4485 """
4496 """
4486 query = cls.query()
4497 query = cls.query()
4487 query = query.filter(cls.local_user_id == local_user_id)
4498 query = query.filter(cls.local_user_id == local_user_id)
4488 return query
4499 return query
4489
4500
4490 @classmethod
4501 @classmethod
4491 def load_provider_plugin(cls, plugin_id):
4502 def load_provider_plugin(cls, plugin_id):
4492 from rhodecode.authentication.base import loadplugin
4503 from rhodecode.authentication.base import loadplugin
4493 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4504 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4494 auth_plugin = loadplugin(_plugin_id)
4505 auth_plugin = loadplugin(_plugin_id)
4495 return auth_plugin
4506 return auth_plugin
4496
4507
4497
4508
4498 class Integration(Base, BaseModel):
4509 class Integration(Base, BaseModel):
4499 __tablename__ = 'integrations'
4510 __tablename__ = 'integrations'
4500 __table_args__ = (
4511 __table_args__ = (
4501 base_table_args
4512 base_table_args
4502 )
4513 )
4503
4514
4504 integration_id = Column('integration_id', Integer(), primary_key=True)
4515 integration_id = Column('integration_id', Integer(), primary_key=True)
4505 integration_type = Column('integration_type', String(255))
4516 integration_type = Column('integration_type', String(255))
4506 enabled = Column('enabled', Boolean(), nullable=False)
4517 enabled = Column('enabled', Boolean(), nullable=False)
4507 name = Column('name', String(255), nullable=False)
4518 name = Column('name', String(255), nullable=False)
4508 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4519 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4509 default=False)
4520 default=False)
4510
4521
4511 settings = Column(
4522 settings = Column(
4512 'settings_json', MutationObj.as_mutable(
4523 'settings_json', MutationObj.as_mutable(
4513 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4524 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4514 repo_id = Column(
4525 repo_id = Column(
4515 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4526 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4516 nullable=True, unique=None, default=None)
4527 nullable=True, unique=None, default=None)
4517 repo = relationship('Repository', lazy='joined')
4528 repo = relationship('Repository', lazy='joined')
4518
4529
4519 repo_group_id = Column(
4530 repo_group_id = Column(
4520 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4531 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4521 nullable=True, unique=None, default=None)
4532 nullable=True, unique=None, default=None)
4522 repo_group = relationship('RepoGroup', lazy='joined')
4533 repo_group = relationship('RepoGroup', lazy='joined')
4523
4534
4524 @property
4535 @property
4525 def scope(self):
4536 def scope(self):
4526 if self.repo:
4537 if self.repo:
4527 return repr(self.repo)
4538 return repr(self.repo)
4528 if self.repo_group:
4539 if self.repo_group:
4529 if self.child_repos_only:
4540 if self.child_repos_only:
4530 return repr(self.repo_group) + ' (child repos only)'
4541 return repr(self.repo_group) + ' (child repos only)'
4531 else:
4542 else:
4532 return repr(self.repo_group) + ' (recursive)'
4543 return repr(self.repo_group) + ' (recursive)'
4533 if self.child_repos_only:
4544 if self.child_repos_only:
4534 return 'root_repos'
4545 return 'root_repos'
4535 return 'global'
4546 return 'global'
4536
4547
4537 def __repr__(self):
4548 def __repr__(self):
4538 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4549 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4539
4550
4540
4551
4541 class RepoReviewRuleUser(Base, BaseModel):
4552 class RepoReviewRuleUser(Base, BaseModel):
4542 __tablename__ = 'repo_review_rules_users'
4553 __tablename__ = 'repo_review_rules_users'
4543 __table_args__ = (
4554 __table_args__ = (
4544 base_table_args
4555 base_table_args
4545 )
4556 )
4546
4557
4547 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4558 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4548 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4559 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4549 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4550 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4561 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4551 user = relationship('User')
4562 user = relationship('User')
4552
4563
4553 def rule_data(self):
4564 def rule_data(self):
4554 return {
4565 return {
4555 'mandatory': self.mandatory
4566 'mandatory': self.mandatory
4556 }
4567 }
4557
4568
4558
4569
4559 class RepoReviewRuleUserGroup(Base, BaseModel):
4570 class RepoReviewRuleUserGroup(Base, BaseModel):
4560 __tablename__ = 'repo_review_rules_users_groups'
4571 __tablename__ = 'repo_review_rules_users_groups'
4561 __table_args__ = (
4572 __table_args__ = (
4562 base_table_args
4573 base_table_args
4563 )
4574 )
4564
4575
4565 VOTE_RULE_ALL = -1
4576 VOTE_RULE_ALL = -1
4566
4577
4567 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4578 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4568 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4579 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4569 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4580 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4570 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4581 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4571 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4582 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4572 users_group = relationship('UserGroup')
4583 users_group = relationship('UserGroup')
4573
4584
4574 def rule_data(self):
4585 def rule_data(self):
4575 return {
4586 return {
4576 'mandatory': self.mandatory,
4587 'mandatory': self.mandatory,
4577 'vote_rule': self.vote_rule
4588 'vote_rule': self.vote_rule
4578 }
4589 }
4579
4590
4580 @property
4591 @property
4581 def vote_rule_label(self):
4592 def vote_rule_label(self):
4582 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4593 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4583 return 'all must vote'
4594 return 'all must vote'
4584 else:
4595 else:
4585 return 'min. vote {}'.format(self.vote_rule)
4596 return 'min. vote {}'.format(self.vote_rule)
4586
4597
4587
4598
4588 class RepoReviewRule(Base, BaseModel):
4599 class RepoReviewRule(Base, BaseModel):
4589 __tablename__ = 'repo_review_rules'
4600 __tablename__ = 'repo_review_rules'
4590 __table_args__ = (
4601 __table_args__ = (
4591 base_table_args
4602 base_table_args
4592 )
4603 )
4593
4604
4594 repo_review_rule_id = Column(
4605 repo_review_rule_id = Column(
4595 'repo_review_rule_id', Integer(), primary_key=True)
4606 'repo_review_rule_id', Integer(), primary_key=True)
4596 repo_id = Column(
4607 repo_id = Column(
4597 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4608 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4598 repo = relationship('Repository', backref='review_rules')
4609 repo = relationship('Repository', backref='review_rules')
4599
4610
4600 review_rule_name = Column('review_rule_name', String(255))
4611 review_rule_name = Column('review_rule_name', String(255))
4601 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4612 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4602 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4613 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4603 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4614 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4604
4615
4605 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4616 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4606 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4617 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4607 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4618 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4608 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4619 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4609
4620
4610 rule_users = relationship('RepoReviewRuleUser')
4621 rule_users = relationship('RepoReviewRuleUser')
4611 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4622 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4612
4623
4613 def _validate_pattern(self, value):
4624 def _validate_pattern(self, value):
4614 re.compile('^' + glob2re(value) + '$')
4625 re.compile('^' + glob2re(value) + '$')
4615
4626
4616 @hybrid_property
4627 @hybrid_property
4617 def source_branch_pattern(self):
4628 def source_branch_pattern(self):
4618 return self._branch_pattern or '*'
4629 return self._branch_pattern or '*'
4619
4630
4620 @source_branch_pattern.setter
4631 @source_branch_pattern.setter
4621 def source_branch_pattern(self, value):
4632 def source_branch_pattern(self, value):
4622 self._validate_pattern(value)
4633 self._validate_pattern(value)
4623 self._branch_pattern = value or '*'
4634 self._branch_pattern = value or '*'
4624
4635
4625 @hybrid_property
4636 @hybrid_property
4626 def target_branch_pattern(self):
4637 def target_branch_pattern(self):
4627 return self._target_branch_pattern or '*'
4638 return self._target_branch_pattern or '*'
4628
4639
4629 @target_branch_pattern.setter
4640 @target_branch_pattern.setter
4630 def target_branch_pattern(self, value):
4641 def target_branch_pattern(self, value):
4631 self._validate_pattern(value)
4642 self._validate_pattern(value)
4632 self._target_branch_pattern = value or '*'
4643 self._target_branch_pattern = value or '*'
4633
4644
4634 @hybrid_property
4645 @hybrid_property
4635 def file_pattern(self):
4646 def file_pattern(self):
4636 return self._file_pattern or '*'
4647 return self._file_pattern or '*'
4637
4648
4638 @file_pattern.setter
4649 @file_pattern.setter
4639 def file_pattern(self, value):
4650 def file_pattern(self, value):
4640 self._validate_pattern(value)
4651 self._validate_pattern(value)
4641 self._file_pattern = value or '*'
4652 self._file_pattern = value or '*'
4642
4653
4643 def matches(self, source_branch, target_branch, files_changed):
4654 def matches(self, source_branch, target_branch, files_changed):
4644 """
4655 """
4645 Check if this review rule matches a branch/files in a pull request
4656 Check if this review rule matches a branch/files in a pull request
4646
4657
4647 :param source_branch: source branch name for the commit
4658 :param source_branch: source branch name for the commit
4648 :param target_branch: target branch name for the commit
4659 :param target_branch: target branch name for the commit
4649 :param files_changed: list of file paths changed in the pull request
4660 :param files_changed: list of file paths changed in the pull request
4650 """
4661 """
4651
4662
4652 source_branch = source_branch or ''
4663 source_branch = source_branch or ''
4653 target_branch = target_branch or ''
4664 target_branch = target_branch or ''
4654 files_changed = files_changed or []
4665 files_changed = files_changed or []
4655
4666
4656 branch_matches = True
4667 branch_matches = True
4657 if source_branch or target_branch:
4668 if source_branch or target_branch:
4658 if self.source_branch_pattern == '*':
4669 if self.source_branch_pattern == '*':
4659 source_branch_match = True
4670 source_branch_match = True
4660 else:
4671 else:
4661 if self.source_branch_pattern.startswith('re:'):
4672 if self.source_branch_pattern.startswith('re:'):
4662 source_pattern = self.source_branch_pattern[3:]
4673 source_pattern = self.source_branch_pattern[3:]
4663 else:
4674 else:
4664 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4675 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4665 source_branch_regex = re.compile(source_pattern)
4676 source_branch_regex = re.compile(source_pattern)
4666 source_branch_match = bool(source_branch_regex.search(source_branch))
4677 source_branch_match = bool(source_branch_regex.search(source_branch))
4667 if self.target_branch_pattern == '*':
4678 if self.target_branch_pattern == '*':
4668 target_branch_match = True
4679 target_branch_match = True
4669 else:
4680 else:
4670 if self.target_branch_pattern.startswith('re:'):
4681 if self.target_branch_pattern.startswith('re:'):
4671 target_pattern = self.target_branch_pattern[3:]
4682 target_pattern = self.target_branch_pattern[3:]
4672 else:
4683 else:
4673 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4684 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4674 target_branch_regex = re.compile(target_pattern)
4685 target_branch_regex = re.compile(target_pattern)
4675 target_branch_match = bool(target_branch_regex.search(target_branch))
4686 target_branch_match = bool(target_branch_regex.search(target_branch))
4676
4687
4677 branch_matches = source_branch_match and target_branch_match
4688 branch_matches = source_branch_match and target_branch_match
4678
4689
4679 files_matches = True
4690 files_matches = True
4680 if self.file_pattern != '*':
4691 if self.file_pattern != '*':
4681 files_matches = False
4692 files_matches = False
4682 if self.file_pattern.startswith('re:'):
4693 if self.file_pattern.startswith('re:'):
4683 file_pattern = self.file_pattern[3:]
4694 file_pattern = self.file_pattern[3:]
4684 else:
4695 else:
4685 file_pattern = glob2re(self.file_pattern)
4696 file_pattern = glob2re(self.file_pattern)
4686 file_regex = re.compile(file_pattern)
4697 file_regex = re.compile(file_pattern)
4687 for filename in files_changed:
4698 for filename in files_changed:
4688 if file_regex.search(filename):
4699 if file_regex.search(filename):
4689 files_matches = True
4700 files_matches = True
4690 break
4701 break
4691
4702
4692 return branch_matches and files_matches
4703 return branch_matches and files_matches
4693
4704
4694 @property
4705 @property
4695 def review_users(self):
4706 def review_users(self):
4696 """ Returns the users which this rule applies to """
4707 """ Returns the users which this rule applies to """
4697
4708
4698 users = collections.OrderedDict()
4709 users = collections.OrderedDict()
4699
4710
4700 for rule_user in self.rule_users:
4711 for rule_user in self.rule_users:
4701 if rule_user.user.active:
4712 if rule_user.user.active:
4702 if rule_user.user not in users:
4713 if rule_user.user not in users:
4703 users[rule_user.user.username] = {
4714 users[rule_user.user.username] = {
4704 'user': rule_user.user,
4715 'user': rule_user.user,
4705 'source': 'user',
4716 'source': 'user',
4706 'source_data': {},
4717 'source_data': {},
4707 'data': rule_user.rule_data()
4718 'data': rule_user.rule_data()
4708 }
4719 }
4709
4720
4710 for rule_user_group in self.rule_user_groups:
4721 for rule_user_group in self.rule_user_groups:
4711 source_data = {
4722 source_data = {
4712 'user_group_id': rule_user_group.users_group.users_group_id,
4723 'user_group_id': rule_user_group.users_group.users_group_id,
4713 'name': rule_user_group.users_group.users_group_name,
4724 'name': rule_user_group.users_group.users_group_name,
4714 'members': len(rule_user_group.users_group.members)
4725 'members': len(rule_user_group.users_group.members)
4715 }
4726 }
4716 for member in rule_user_group.users_group.members:
4727 for member in rule_user_group.users_group.members:
4717 if member.user.active:
4728 if member.user.active:
4718 key = member.user.username
4729 key = member.user.username
4719 if key in users:
4730 if key in users:
4720 # skip this member as we have him already
4731 # skip this member as we have him already
4721 # this prevents from override the "first" matched
4732 # this prevents from override the "first" matched
4722 # users with duplicates in multiple groups
4733 # users with duplicates in multiple groups
4723 continue
4734 continue
4724
4735
4725 users[key] = {
4736 users[key] = {
4726 'user': member.user,
4737 'user': member.user,
4727 'source': 'user_group',
4738 'source': 'user_group',
4728 'source_data': source_data,
4739 'source_data': source_data,
4729 'data': rule_user_group.rule_data()
4740 'data': rule_user_group.rule_data()
4730 }
4741 }
4731
4742
4732 return users
4743 return users
4733
4744
4734 def user_group_vote_rule(self, user_id):
4745 def user_group_vote_rule(self, user_id):
4735
4746
4736 rules = []
4747 rules = []
4737 if not self.rule_user_groups:
4748 if not self.rule_user_groups:
4738 return rules
4749 return rules
4739
4750
4740 for user_group in self.rule_user_groups:
4751 for user_group in self.rule_user_groups:
4741 user_group_members = [x.user_id for x in user_group.users_group.members]
4752 user_group_members = [x.user_id for x in user_group.users_group.members]
4742 if user_id in user_group_members:
4753 if user_id in user_group_members:
4743 rules.append(user_group)
4754 rules.append(user_group)
4744 return rules
4755 return rules
4745
4756
4746 def __repr__(self):
4757 def __repr__(self):
4747 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4758 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4748 self.repo_review_rule_id, self.repo)
4759 self.repo_review_rule_id, self.repo)
4749
4760
4750
4761
4751 class ScheduleEntry(Base, BaseModel):
4762 class ScheduleEntry(Base, BaseModel):
4752 __tablename__ = 'schedule_entries'
4763 __tablename__ = 'schedule_entries'
4753 __table_args__ = (
4764 __table_args__ = (
4754 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4765 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4755 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4766 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4756 base_table_args,
4767 base_table_args,
4757 )
4768 )
4758
4769
4759 schedule_types = ['crontab', 'timedelta', 'integer']
4770 schedule_types = ['crontab', 'timedelta', 'integer']
4760 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4771 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4761
4772
4762 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4773 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4763 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4774 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4764 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4775 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4765
4776
4766 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4777 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4767 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4778 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4768
4779
4769 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4780 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4770 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4781 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4771
4782
4772 # task
4783 # task
4773 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4784 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4774 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4785 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4775 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4786 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4776 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4787 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4777
4788
4778 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4789 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4779 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4790 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4780
4791
4781 @hybrid_property
4792 @hybrid_property
4782 def schedule_type(self):
4793 def schedule_type(self):
4783 return self._schedule_type
4794 return self._schedule_type
4784
4795
4785 @schedule_type.setter
4796 @schedule_type.setter
4786 def schedule_type(self, val):
4797 def schedule_type(self, val):
4787 if val not in self.schedule_types:
4798 if val not in self.schedule_types:
4788 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4799 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4789 val, self.schedule_type))
4800 val, self.schedule_type))
4790
4801
4791 self._schedule_type = val
4802 self._schedule_type = val
4792
4803
4793 @classmethod
4804 @classmethod
4794 def get_uid(cls, obj):
4805 def get_uid(cls, obj):
4795 args = obj.task_args
4806 args = obj.task_args
4796 kwargs = obj.task_kwargs
4807 kwargs = obj.task_kwargs
4797 if isinstance(args, JsonRaw):
4808 if isinstance(args, JsonRaw):
4798 try:
4809 try:
4799 args = json.loads(args)
4810 args = json.loads(args)
4800 except ValueError:
4811 except ValueError:
4801 args = tuple()
4812 args = tuple()
4802
4813
4803 if isinstance(kwargs, JsonRaw):
4814 if isinstance(kwargs, JsonRaw):
4804 try:
4815 try:
4805 kwargs = json.loads(kwargs)
4816 kwargs = json.loads(kwargs)
4806 except ValueError:
4817 except ValueError:
4807 kwargs = dict()
4818 kwargs = dict()
4808
4819
4809 dot_notation = obj.task_dot_notation
4820 dot_notation = obj.task_dot_notation
4810 val = '.'.join(map(safe_str, [
4821 val = '.'.join(map(safe_str, [
4811 sorted(dot_notation), args, sorted(kwargs.items())]))
4822 sorted(dot_notation), args, sorted(kwargs.items())]))
4812 return hashlib.sha1(val).hexdigest()
4823 return hashlib.sha1(val).hexdigest()
4813
4824
4814 @classmethod
4825 @classmethod
4815 def get_by_schedule_name(cls, schedule_name):
4826 def get_by_schedule_name(cls, schedule_name):
4816 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4827 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4817
4828
4818 @classmethod
4829 @classmethod
4819 def get_by_schedule_id(cls, schedule_id):
4830 def get_by_schedule_id(cls, schedule_id):
4820 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4831 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4821
4832
4822 @property
4833 @property
4823 def task(self):
4834 def task(self):
4824 return self.task_dot_notation
4835 return self.task_dot_notation
4825
4836
4826 @property
4837 @property
4827 def schedule(self):
4838 def schedule(self):
4828 from rhodecode.lib.celerylib.utils import raw_2_schedule
4839 from rhodecode.lib.celerylib.utils import raw_2_schedule
4829 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4840 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4830 return schedule
4841 return schedule
4831
4842
4832 @property
4843 @property
4833 def args(self):
4844 def args(self):
4834 try:
4845 try:
4835 return list(self.task_args or [])
4846 return list(self.task_args or [])
4836 except ValueError:
4847 except ValueError:
4837 return list()
4848 return list()
4838
4849
4839 @property
4850 @property
4840 def kwargs(self):
4851 def kwargs(self):
4841 try:
4852 try:
4842 return dict(self.task_kwargs or {})
4853 return dict(self.task_kwargs or {})
4843 except ValueError:
4854 except ValueError:
4844 return dict()
4855 return dict()
4845
4856
4846 def _as_raw(self, val):
4857 def _as_raw(self, val):
4847 if hasattr(val, 'de_coerce'):
4858 if hasattr(val, 'de_coerce'):
4848 val = val.de_coerce()
4859 val = val.de_coerce()
4849 if val:
4860 if val:
4850 val = json.dumps(val)
4861 val = json.dumps(val)
4851
4862
4852 return val
4863 return val
4853
4864
4854 @property
4865 @property
4855 def schedule_definition_raw(self):
4866 def schedule_definition_raw(self):
4856 return self._as_raw(self.schedule_definition)
4867 return self._as_raw(self.schedule_definition)
4857
4868
4858 @property
4869 @property
4859 def args_raw(self):
4870 def args_raw(self):
4860 return self._as_raw(self.task_args)
4871 return self._as_raw(self.task_args)
4861
4872
4862 @property
4873 @property
4863 def kwargs_raw(self):
4874 def kwargs_raw(self):
4864 return self._as_raw(self.task_kwargs)
4875 return self._as_raw(self.task_kwargs)
4865
4876
4866 def __repr__(self):
4877 def __repr__(self):
4867 return '<DB:ScheduleEntry({}:{})>'.format(
4878 return '<DB:ScheduleEntry({}:{})>'.format(
4868 self.schedule_entry_id, self.schedule_name)
4879 self.schedule_entry_id, self.schedule_name)
4869
4880
4870
4881
4871 @event.listens_for(ScheduleEntry, 'before_update')
4882 @event.listens_for(ScheduleEntry, 'before_update')
4872 def update_task_uid(mapper, connection, target):
4883 def update_task_uid(mapper, connection, target):
4873 target.task_uid = ScheduleEntry.get_uid(target)
4884 target.task_uid = ScheduleEntry.get_uid(target)
4874
4885
4875
4886
4876 @event.listens_for(ScheduleEntry, 'before_insert')
4887 @event.listens_for(ScheduleEntry, 'before_insert')
4877 def set_task_uid(mapper, connection, target):
4888 def set_task_uid(mapper, connection, target):
4878 target.task_uid = ScheduleEntry.get_uid(target)
4889 target.task_uid = ScheduleEntry.get_uid(target)
4879
4890
4880
4891
4881 class _BaseBranchPerms(BaseModel):
4892 class _BaseBranchPerms(BaseModel):
4882 @classmethod
4893 @classmethod
4883 def compute_hash(cls, value):
4894 def compute_hash(cls, value):
4884 return sha1_safe(value)
4895 return sha1_safe(value)
4885
4896
4886 @hybrid_property
4897 @hybrid_property
4887 def branch_pattern(self):
4898 def branch_pattern(self):
4888 return self._branch_pattern or '*'
4899 return self._branch_pattern or '*'
4889
4900
4890 @hybrid_property
4901 @hybrid_property
4891 def branch_hash(self):
4902 def branch_hash(self):
4892 return self._branch_hash
4903 return self._branch_hash
4893
4904
4894 def _validate_glob(self, value):
4905 def _validate_glob(self, value):
4895 re.compile('^' + glob2re(value) + '$')
4906 re.compile('^' + glob2re(value) + '$')
4896
4907
4897 @branch_pattern.setter
4908 @branch_pattern.setter
4898 def branch_pattern(self, value):
4909 def branch_pattern(self, value):
4899 self._validate_glob(value)
4910 self._validate_glob(value)
4900 self._branch_pattern = value or '*'
4911 self._branch_pattern = value or '*'
4901 # set the Hash when setting the branch pattern
4912 # set the Hash when setting the branch pattern
4902 self._branch_hash = self.compute_hash(self._branch_pattern)
4913 self._branch_hash = self.compute_hash(self._branch_pattern)
4903
4914
4904 def matches(self, branch):
4915 def matches(self, branch):
4905 """
4916 """
4906 Check if this the branch matches entry
4917 Check if this the branch matches entry
4907
4918
4908 :param branch: branch name for the commit
4919 :param branch: branch name for the commit
4909 """
4920 """
4910
4921
4911 branch = branch or ''
4922 branch = branch or ''
4912
4923
4913 branch_matches = True
4924 branch_matches = True
4914 if branch:
4925 if branch:
4915 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4926 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4916 branch_matches = bool(branch_regex.search(branch))
4927 branch_matches = bool(branch_regex.search(branch))
4917
4928
4918 return branch_matches
4929 return branch_matches
4919
4930
4920
4931
4921 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4932 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4922 __tablename__ = 'user_to_repo_branch_permissions'
4933 __tablename__ = 'user_to_repo_branch_permissions'
4923 __table_args__ = (
4934 __table_args__ = (
4924 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4925 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4926 )
4937 )
4927
4938
4928 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4939 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4929
4940
4930 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4941 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4931 repo = relationship('Repository', backref='user_branch_perms')
4942 repo = relationship('Repository', backref='user_branch_perms')
4932
4943
4933 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4944 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4934 permission = relationship('Permission')
4945 permission = relationship('Permission')
4935
4946
4936 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4947 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4937 user_repo_to_perm = relationship('UserRepoToPerm')
4948 user_repo_to_perm = relationship('UserRepoToPerm')
4938
4949
4939 rule_order = Column('rule_order', Integer(), nullable=False)
4950 rule_order = Column('rule_order', Integer(), nullable=False)
4940 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4951 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4941 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4952 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4942
4953
4943 def __unicode__(self):
4954 def __unicode__(self):
4944 return u'<UserBranchPermission(%s => %r)>' % (
4955 return u'<UserBranchPermission(%s => %r)>' % (
4945 self.user_repo_to_perm, self.branch_pattern)
4956 self.user_repo_to_perm, self.branch_pattern)
4946
4957
4947
4958
4948 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4959 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4949 __tablename__ = 'user_group_to_repo_branch_permissions'
4960 __tablename__ = 'user_group_to_repo_branch_permissions'
4950 __table_args__ = (
4961 __table_args__ = (
4951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4962 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4952 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4963 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4953 )
4964 )
4954
4965
4955 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4966 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4956
4967
4957 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4968 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4958 repo = relationship('Repository', backref='user_group_branch_perms')
4969 repo = relationship('Repository', backref='user_group_branch_perms')
4959
4970
4960 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4971 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4961 permission = relationship('Permission')
4972 permission = relationship('Permission')
4962
4973
4963 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4974 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4964 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4975 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4965
4976
4966 rule_order = Column('rule_order', Integer(), nullable=False)
4977 rule_order = Column('rule_order', Integer(), nullable=False)
4967 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4978 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4968 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4979 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4969
4980
4970 def __unicode__(self):
4981 def __unicode__(self):
4971 return u'<UserBranchPermission(%s => %r)>' % (
4982 return u'<UserBranchPermission(%s => %r)>' % (
4972 self.user_group_repo_to_perm, self.branch_pattern)
4983 self.user_group_repo_to_perm, self.branch_pattern)
4973
4984
4974
4985
4975 class UserBookmark(Base, BaseModel):
4986 class UserBookmark(Base, BaseModel):
4976 __tablename__ = 'user_bookmarks'
4987 __tablename__ = 'user_bookmarks'
4977 __table_args__ = (
4988 __table_args__ = (
4978 UniqueConstraint('user_id', 'bookmark_repo_id'),
4989 UniqueConstraint('user_id', 'bookmark_repo_id'),
4979 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4990 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4980 UniqueConstraint('user_id', 'bookmark_position'),
4991 UniqueConstraint('user_id', 'bookmark_position'),
4981 base_table_args
4992 base_table_args
4982 )
4993 )
4983
4994
4984 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4995 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4985 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4986 position = Column("bookmark_position", Integer(), nullable=False)
4997 position = Column("bookmark_position", Integer(), nullable=False)
4987 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4998 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4988 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4999 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4989 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5000 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4990
5001
4991 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5002 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4992 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5003 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4993
5004
4994 user = relationship("User")
5005 user = relationship("User")
4995
5006
4996 repository = relationship("Repository")
5007 repository = relationship("Repository")
4997 repository_group = relationship("RepoGroup")
5008 repository_group = relationship("RepoGroup")
4998
5009
4999 @classmethod
5010 @classmethod
5000 def get_by_position_for_user(cls, position, user_id):
5011 def get_by_position_for_user(cls, position, user_id):
5001 return cls.query() \
5012 return cls.query() \
5002 .filter(UserBookmark.user_id == user_id) \
5013 .filter(UserBookmark.user_id == user_id) \
5003 .filter(UserBookmark.position == position).scalar()
5014 .filter(UserBookmark.position == position).scalar()
5004
5015
5005 @classmethod
5016 @classmethod
5006 def get_bookmarks_for_user(cls, user_id):
5017 def get_bookmarks_for_user(cls, user_id):
5007 return cls.query() \
5018 return cls.query() \
5008 .filter(UserBookmark.user_id == user_id) \
5019 .filter(UserBookmark.user_id == user_id) \
5009 .options(joinedload(UserBookmark.repository)) \
5020 .options(joinedload(UserBookmark.repository)) \
5010 .options(joinedload(UserBookmark.repository_group)) \
5021 .options(joinedload(UserBookmark.repository_group)) \
5011 .order_by(UserBookmark.position.asc()) \
5022 .order_by(UserBookmark.position.asc()) \
5012 .all()
5023 .all()
5013
5024
5014 def __unicode__(self):
5025 def __unicode__(self):
5015 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5026 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5016
5027
5017
5028
5018 class FileStore(Base, BaseModel):
5029 class FileStore(Base, BaseModel):
5019 __tablename__ = 'file_store'
5030 __tablename__ = 'file_store'
5020 __table_args__ = (
5031 __table_args__ = (
5021 base_table_args
5032 base_table_args
5022 )
5033 )
5023
5034
5024 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5035 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5025 file_uid = Column('file_uid', String(1024), nullable=False)
5036 file_uid = Column('file_uid', String(1024), nullable=False)
5026 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5037 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5027 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5038 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5028 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5039 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5029
5040
5030 # sha256 hash
5041 # sha256 hash
5031 file_hash = Column('file_hash', String(512), nullable=False)
5042 file_hash = Column('file_hash', String(512), nullable=False)
5032 file_size = Column('file_size', Integer(), nullable=False)
5043 file_size = Column('file_size', Integer(), nullable=False)
5033
5044
5034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5045 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5035 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5046 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5036 accessed_count = Column('accessed_count', Integer(), default=0)
5047 accessed_count = Column('accessed_count', Integer(), default=0)
5037
5048
5038 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5049 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5039
5050
5040 # if repo/repo_group reference is set, check for permissions
5051 # if repo/repo_group reference is set, check for permissions
5041 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5052 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5042
5053
5043 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5054 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5044 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5055 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5045
5056
5046 # scope limited to user, which requester have access to
5057 # scope limited to user, which requester have access to
5047 scope_user_id = Column(
5058 scope_user_id = Column(
5048 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5059 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5049 nullable=True, unique=None, default=None)
5060 nullable=True, unique=None, default=None)
5050 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5061 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5051
5062
5052 # scope limited to user group, which requester have access to
5063 # scope limited to user group, which requester have access to
5053 scope_user_group_id = Column(
5064 scope_user_group_id = Column(
5054 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5065 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5055 nullable=True, unique=None, default=None)
5066 nullable=True, unique=None, default=None)
5056 user_group = relationship('UserGroup', lazy='joined')
5067 user_group = relationship('UserGroup', lazy='joined')
5057
5068
5058 # scope limited to repo, which requester have access to
5069 # scope limited to repo, which requester have access to
5059 scope_repo_id = Column(
5070 scope_repo_id = Column(
5060 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5071 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5061 nullable=True, unique=None, default=None)
5072 nullable=True, unique=None, default=None)
5062 repo = relationship('Repository', lazy='joined')
5073 repo = relationship('Repository', lazy='joined')
5063
5074
5064 # scope limited to repo group, which requester have access to
5075 # scope limited to repo group, which requester have access to
5065 scope_repo_group_id = Column(
5076 scope_repo_group_id = Column(
5066 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5077 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5067 nullable=True, unique=None, default=None)
5078 nullable=True, unique=None, default=None)
5068 repo_group = relationship('RepoGroup', lazy='joined')
5079 repo_group = relationship('RepoGroup', lazy='joined')
5069
5080
5070 @classmethod
5081 @classmethod
5071 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5082 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5072 file_description='', enabled=True, check_acl=True, user_id=None,
5083 file_description='', enabled=True, check_acl=True, user_id=None,
5073 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5084 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5074
5085
5075 store_entry = FileStore()
5086 store_entry = FileStore()
5076 store_entry.file_uid = file_uid
5087 store_entry.file_uid = file_uid
5077 store_entry.file_display_name = file_display_name
5088 store_entry.file_display_name = file_display_name
5078 store_entry.file_org_name = filename
5089 store_entry.file_org_name = filename
5079 store_entry.file_size = file_size
5090 store_entry.file_size = file_size
5080 store_entry.file_hash = file_hash
5091 store_entry.file_hash = file_hash
5081 store_entry.file_description = file_description
5092 store_entry.file_description = file_description
5082
5093
5083 store_entry.check_acl = check_acl
5094 store_entry.check_acl = check_acl
5084 store_entry.enabled = enabled
5095 store_entry.enabled = enabled
5085
5096
5086 store_entry.user_id = user_id
5097 store_entry.user_id = user_id
5087 store_entry.scope_user_id = scope_user_id
5098 store_entry.scope_user_id = scope_user_id
5088 store_entry.scope_repo_id = scope_repo_id
5099 store_entry.scope_repo_id = scope_repo_id
5089 store_entry.scope_repo_group_id = scope_repo_group_id
5100 store_entry.scope_repo_group_id = scope_repo_group_id
5090 return store_entry
5101 return store_entry
5091
5102
5092 @classmethod
5103 @classmethod
5093 def bump_access_counter(cls, file_uid, commit=True):
5104 def bump_access_counter(cls, file_uid, commit=True):
5094 FileStore().query()\
5105 FileStore().query()\
5095 .filter(FileStore.file_uid == file_uid)\
5106 .filter(FileStore.file_uid == file_uid)\
5096 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5107 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5097 FileStore.accessed_on: datetime.datetime.now()})
5108 FileStore.accessed_on: datetime.datetime.now()})
5098 if commit:
5109 if commit:
5099 Session().commit()
5110 Session().commit()
5100
5111
5101 def __repr__(self):
5112 def __repr__(self):
5102 return '<FileStore({})>'.format(self.file_store_id)
5113 return '<FileStore({})>'.format(self.file_store_id)
5103
5114
5104
5115
5105 class DbMigrateVersion(Base, BaseModel):
5116 class DbMigrateVersion(Base, BaseModel):
5106 __tablename__ = 'db_migrate_version'
5117 __tablename__ = 'db_migrate_version'
5107 __table_args__ = (
5118 __table_args__ = (
5108 base_table_args,
5119 base_table_args,
5109 )
5120 )
5110
5121
5111 repository_id = Column('repository_id', String(250), primary_key=True)
5122 repository_id = Column('repository_id', String(250), primary_key=True)
5112 repository_path = Column('repository_path', Text)
5123 repository_path = Column('repository_path', Text)
5113 version = Column('version', Integer)
5124 version = Column('version', Integer)
5114
5125
5115 @classmethod
5126 @classmethod
5116 def set_version(cls, version):
5127 def set_version(cls, version):
5117 """
5128 """
5118 Helper for forcing a different version, usually for debugging purposes via ishell.
5129 Helper for forcing a different version, usually for debugging purposes via ishell.
5119 """
5130 """
5120 ver = DbMigrateVersion.query().first()
5131 ver = DbMigrateVersion.query().first()
5121 ver.version = version
5132 ver.version = version
5122 Session().commit()
5133 Session().commit()
5123
5134
5124
5135
5125 class DbSession(Base, BaseModel):
5136 class DbSession(Base, BaseModel):
5126 __tablename__ = 'db_session'
5137 __tablename__ = 'db_session'
5127 __table_args__ = (
5138 __table_args__ = (
5128 base_table_args,
5139 base_table_args,
5129 )
5140 )
5130
5141
5131 def __repr__(self):
5142 def __repr__(self):
5132 return '<DB:DbSession({})>'.format(self.id)
5143 return '<DB:DbSession({})>'.format(self.id)
5133
5144
5134 id = Column('id', Integer())
5145 id = Column('id', Integer())
5135 namespace = Column('namespace', String(255), primary_key=True)
5146 namespace = Column('namespace', String(255), primary_key=True)
5136 accessed = Column('accessed', DateTime, nullable=False)
5147 accessed = Column('accessed', DateTime, nullable=False)
5137 created = Column('created', DateTime, nullable=False)
5148 created = Column('created', DateTime, nullable=False)
5138 data = Column('data', PickleType, nullable=False)
5149 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now